From 838d1b07b1c5713824602cc9b53628c955bbbe78 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 28 Feb 2026 19:08:39 -0800 Subject: [PATCH 001/232] Fix local HTML file routing in open wrapper (#684) * Route local HTML open targets to cmux browser * Keep file:// omnibar navigation inside cmux browser * Load local file URLs via WKWebView file API * Add browser regression test for local file URL loads * Address PR feedback on local HTML and file URL handling --- Resources/bin/open | 204 ++++++++++++++++-- Sources/Panels/BrowserPanel.swift | 33 ++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 54 +++++ tests/test_open_wrapper.py | 164 ++++++++++++++ tests_v2/test_browser_file_url_load.py | 93 ++++++++ 5 files changed, 533 insertions(+), 15 deletions(-) create mode 100644 tests_v2/test_browser_file_url_load.py diff --git a/Resources/bin/open b/Resources/bin/open index 9c81ea54..0b0ab639 100755 --- a/Resources/bin/open +++ b/Resources/bin/open @@ -105,6 +105,169 @@ is_http_url() { return 1 } +is_file_url() { + local value="$1" + case "$value" in + [Ff][Ii][Ll][Ee]://*) + return 0 + ;; + esac + return 1 +} + +has_uri_scheme() { + local value="$1" + [[ "$value" =~ ^[A-Za-z][A-Za-z0-9+.-]*: ]] +} + +is_html_extension() { + local value + value="$(to_lower_ascii "$(trim "$1")")" + case "$value" in + *.html|*.htm) + return 0 + ;; + esac + return 1 +} + +is_explicit_local_path() { + local value="$1" + case "$value" in + /*|./*|../*|~|~/*) + return 0 + ;; + esac + return 1 +} + +file_url_points_to_html() { + local value="$1" + if [[ -n "$PYTHON3_BIN" ]]; then + "$PYTHON3_BIN" - "$value" <<'PY' >/dev/null 2>&1 +import sys +from urllib.parse import unquote, urlsplit + +value = sys.argv[1].strip() +if not value: + raise SystemExit(1) + +parts = urlsplit(value) +path = unquote(parts.path or "") +lower = path.lower() +if lower.endswith(".html") or lower.endswith(".htm"): + raise SystemExit(0) +raise SystemExit(1) +PY + return $? + fi + + local without_fragment="${value%%\#*}" + local without_query="${without_fragment%%\?*}" + local remainder path_part + + case "$without_query" in + [Ff][Ii][Ll][Ee]://*) + remainder="${without_query#*://}" + ;; + *) + return 1 + ;; + esac + + if [[ "$remainder" == /* ]]; then + path_part="$remainder" + elif [[ "$remainder" == */* ]]; then + path_part="/${remainder#*/}" + else + return 1 + fi + + is_html_extension "$path_part" +} + +path_to_file_url_without_python() { + local raw="$1" + local expanded="$raw" + case "$expanded" in + "~") + expanded="$HOME" + ;; + "~/"*) + expanded="$HOME/${expanded#~/}" + ;; + esac + + local absolute + if [[ "$expanded" == /* ]]; then + absolute="$expanded" + else + absolute="$(pwd)/$expanded" + fi + + local directory="$absolute" + local basename="" + if [[ "$absolute" == */* ]]; then + directory="${absolute%/*}" + basename="${absolute##*/}" + fi + + local resolved_directory + if resolved_directory="$(cd "$directory" 2>/dev/null && pwd -P)"; then + absolute="$resolved_directory" + if [[ -n "$basename" ]]; then + absolute="$absolute/$basename" + fi + fi + + local encoded="" + local length=${#absolute} + local index char hex + local LC_ALL=C + for ((index = 0; index < length; index++)); do + char="${absolute:index:1}" + case "$char" in + [a-zA-Z0-9.~_-]|/) + encoded+="$char" + ;; + *) + printf -v hex '%02X' "'$char" + encoded+="%$hex" + ;; + esac + done + printf 'file://%s\n' "$encoded" +} + +path_to_file_url() { + local raw="$1" + if [[ -n "$PYTHON3_BIN" ]]; then + local converted + if converted="$("$PYTHON3_BIN" - "$raw" <<'PY' 2>/dev/null +import pathlib +import sys + +raw = sys.argv[1] +if not raw: + raise SystemExit(1) + +path = pathlib.Path(raw).expanduser() +if path.is_absolute(): + resolved = path.resolve(strict=False) +else: + resolved = (pathlib.Path.cwd() / path).resolve(strict=False) + +sys.stdout.write(resolved.as_uri()) +PY + )"; then + printf '%s\n' "$converted" + return 0 + fi + fi + + path_to_file_url_without_python "$raw" +} + normalize_host() { local value value="$(trim "$1")" @@ -212,9 +375,10 @@ if [[ $# -eq 0 ]]; then fi # Scan for flags that indicate explicit user intent → pass through. -# Also collect non-flag arguments (potential URLs/files). +# Also collect non-flag arguments and route eligible browser targets to cmux. passthrough=false -urls=() +cmux_targets=() +passthrough_args=() for arg in "$@"; do case "$arg" in -a|-b|-R|-e|-t|-f|-W|-g|-n|-h|-s|-j|-u|--env|--stdin|--stdout|--stderr) @@ -228,17 +392,33 @@ for arg in "$@"; do ;; *) if is_http_url "$arg"; then - urls+=("$arg") + cmux_targets+=("$arg") + elif is_file_url "$arg"; then + if file_url_points_to_html "$arg"; then + cmux_targets+=("$arg") + else + passthrough_args+=("$arg") + fi + elif has_uri_scheme "$arg"; then + passthrough_args+=("$arg") + elif is_html_extension "$arg"; then + if is_explicit_local_path "$arg" || [[ -e "$arg" ]]; then + if local_file_url="$(path_to_file_url "$arg")"; then + cmux_targets+=("$local_file_url") + else + passthrough_args+=("$arg") + fi + else + passthrough_args+=("$arg") + fi else - # Non-URL, non-flag argument (file path, etc.) → pass through all - passthrough=true - break + passthrough_args+=("$arg") fi ;; esac done -if [[ "$passthrough" == true ]] || [[ ${#urls[@]} -eq 0 ]]; then +if [[ "$passthrough" == true ]] || [[ ${#cmux_targets[@]} -eq 0 ]]; then system_open "$@" fi @@ -269,15 +449,15 @@ fi # Open each URL in cmux's in-app browser; track failures individually. failed_urls=() -for url in "${urls[@]}"; do - if ! host_matches_whitelist "$url"; then +for url in "${cmux_targets[@]}"; do + if is_http_url "$url" && ! host_matches_whitelist "$url"; then failed_urls+=("$url") continue fi "$CMUX_CLI" browser open "$url" 2>/dev/null || failed_urls+=("$url") done -# Fall back to system open only for URLs that failed. -if [[ ${#failed_urls[@]} -gt 0 ]]; then - system_open "${failed_urls[@]}" +# Fall back to system open for unmatched args and URLs that failed. +if [[ ${#passthrough_args[@]} -gt 0 ]] || [[ ${#failed_urls[@]} -gt 0 ]]; then + system_open "${passthrough_args[@]}" "${failed_urls[@]}" fi diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 9188d093..f91576d0 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -394,11 +394,35 @@ func browserPreparedNavigationRequest(_ request: URLRequest) -> URLRequest { return preparedRequest } +func browserReadAccessURL(forLocalFileURL fileURL: URL, fileManager: FileManager = .default) -> URL? { + guard fileURL.isFileURL, fileURL.path.hasPrefix("/") else { return nil } + let path = fileURL.path + var isDirectory: ObjCBool = false + if fileManager.fileExists(atPath: path, isDirectory: &isDirectory), isDirectory.boolValue { + return fileURL + } + + let parent = fileURL.deletingLastPathComponent() + guard !parent.path.isEmpty, parent.path.hasPrefix("/") else { return nil } + return parent +} + +@discardableResult +func browserLoadRequest(_ request: URLRequest, in webView: WKWebView) -> WKNavigation? { + guard let url = request.url else { return nil } + if url.isFileURL { + guard let readAccessURL = browserReadAccessURL(forLocalFileURL: url) else { return nil } + return webView.loadFileURL(url, allowingReadAccessTo: readAccessURL) + } + return webView.load(browserPreparedNavigationRequest(request)) +} + private let browserEmbeddedNavigationSchemes: Set = [ "about", "applewebdata", "blob", "data", + "file", "http", "https", "javascript", @@ -1901,7 +1925,7 @@ final class BrowserPanel: Panel, ObservableObject { BrowserHistoryStore.shared.recordTypedNavigation(url: url) } navigationDelegate?.lastAttemptedURL = url - webView.load(browserPreparedNavigationRequest(request)) + browserLoadRequest(request, in: webView) } /// Navigate with smart URL/search detection @@ -2047,6 +2071,9 @@ func resolveBrowserNavigableURL(_ input: String) -> URL? { if scheme == "http" || scheme == "https" { return url } + if scheme == "file", url.isFileURL, url.path.hasPrefix("/") { + return url + } return nil } @@ -3125,7 +3152,7 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { let targetURL = navigationAction.request.url?.absoluteString ?? "nil" dlog("browser.nav.decidePolicy.action kind=loadInPlaceFromNilTarget url=\(targetURL)") #endif - webView.load(navigationAction.request) + browserLoadRequest(navigationAction.request, in: webView) decisionHandler(.cancel) return } @@ -3288,7 +3315,7 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { #if DEBUG dlog("browser.nav.createWebView.action kind=loadInPlace url=\(url.absoluteString)") #endif - webView.load(navigationAction.request) + browserLoadRequest(navigationAction.request, in: webView) } } return nil diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 08063767..3a90c8a1 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -7708,6 +7708,58 @@ final class TerminalOpenURLTargetResolutionTests: XCTestCase { } } +final class BrowserNavigableURLResolutionTests: XCTestCase { + func testResolvesFileSchemeAsNavigableURL() throws { + let resolved = try XCTUnwrap(resolveBrowserNavigableURL("file:///tmp/cmux-local-test.html")) + XCTAssertTrue(resolved.isFileURL) + XCTAssertEqual(resolved.path, "/tmp/cmux-local-test.html") + } + + func testRejectsNonWebNonFileScheme() { + XCTAssertNil(resolveBrowserNavigableURL("mailto:test@example.com")) + XCTAssertNil(resolveBrowserNavigableURL("ftp://example.com/file.html")) + } + + func testRejectsHostOnlyFileURL() { + XCTAssertNil(resolveBrowserNavigableURL("file://example.html")) + } +} + +final class BrowserReadAccessURLTests: XCTestCase { + func testUsesParentDirectoryForFileURL() throws { + let tempRoot = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + let dir = tempRoot.appendingPathComponent("BrowserReadAccessURLTests-\(UUID().uuidString)", isDirectory: true) + let file = dir.appendingPathComponent("sample.html") + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: dir) } + try "".write(to: file, atomically: true, encoding: .utf8) + + let readAccessURL = try XCTUnwrap(browserReadAccessURL(forLocalFileURL: file)) + XCTAssertEqual(readAccessURL.standardizedFileURL, dir.standardizedFileURL) + } + + func testUsesDirectoryURLWhenTargetIsDirectory() throws { + let tempRoot = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + let dir = tempRoot.appendingPathComponent("BrowserReadAccessURLTests-\(UUID().uuidString)", isDirectory: true) + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: dir) } + + let readAccessURL = try XCTUnwrap(browserReadAccessURL(forLocalFileURL: dir)) + XCTAssertEqual(readAccessURL.standardizedFileURL, dir.standardizedFileURL) + } + + func testUsesParentDirectoryWhenFileDoesNotExist() throws { + let missing = URL(fileURLWithPath: "/tmp/\(UUID().uuidString).html") + let readAccessURL = try XCTUnwrap(browserReadAccessURL(forLocalFileURL: missing)) + XCTAssertEqual(readAccessURL.standardizedFileURL, missing.deletingLastPathComponent().standardizedFileURL) + } + + func testReturnsNilForHostOnlyFileURL() throws { + let hostOnly = try XCTUnwrap(URL(string: "file://example.html")) + XCTAssertNil(browserReadAccessURL(forLocalFileURL: hostOnly)) + } +} + final class BrowserExternalNavigationSchemeTests: XCTestCase { func testCustomAppSchemesOpenExternally() throws { let discord = try XCTUnwrap(URL(string: "discord://login/one-time?token=abc")) @@ -7726,6 +7778,7 @@ final class BrowserExternalNavigationSchemeTests: XCTestCase { let http = try XCTUnwrap(URL(string: "http://example.com")) let about = try XCTUnwrap(URL(string: "about:blank")) let data = try XCTUnwrap(URL(string: "data:text/plain,hello")) + let file = try XCTUnwrap(URL(string: "file:///tmp/cmux-local-test.html")) let blob = try XCTUnwrap(URL(string: "blob:https://example.com/550e8400-e29b-41d4-a716-446655440000")) let javascript = try XCTUnwrap(URL(string: "javascript:void(0)")) let webkitInternal = try XCTUnwrap(URL(string: "applewebdata://local/page")) @@ -7734,6 +7787,7 @@ final class BrowserExternalNavigationSchemeTests: XCTestCase { XCTAssertFalse(browserShouldOpenURLExternally(http)) XCTAssertFalse(browserShouldOpenURLExternally(about)) XCTAssertFalse(browserShouldOpenURLExternally(data)) + XCTAssertFalse(browserShouldOpenURLExternally(file)) XCTAssertFalse(browserShouldOpenURLExternally(blob)) XCTAssertFalse(browserShouldOpenURLExternally(javascript)) XCTAssertFalse(browserShouldOpenURLExternally(webkitInternal)) diff --git a/tests/test_open_wrapper.py b/tests/test_open_wrapper.py index 6119033a..e54f134f 100755 --- a/tests/test_open_wrapper.py +++ b/tests/test_open_wrapper.py @@ -34,6 +34,8 @@ def run_wrapper( legacy_open_setting: str | None = None, whitelist: str | None, fail_urls: list[str] | None = None, + local_files: list[str] | None = None, + python_bin: str | None = None, ) -> tuple[list[str], list[str], int, str]: with tempfile.TemporaryDirectory(prefix="cmux-open-wrapper-test-") as td: tmp = Path(td) @@ -113,6 +115,12 @@ exit 0 """, ) + if local_files: + for relative_path in local_files: + target = tmp / relative_path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text("fixture", encoding="utf-8") + env = os.environ.copy() env["CMUX_SOCKET_PATH"] = "/tmp/cmux-open-wrapper-test.sock" env["CMUX_BUNDLE_ID"] = "com.cmuxterm.app.debug.test" @@ -120,6 +128,10 @@ exit 0 env["CMUX_OPEN_WRAPPER_DEFAULTS"] = str(defaults) env["FAKE_OPEN_LOG"] = str(open_log) env["FAKE_CMUX_LOG"] = str(cmux_log) + if python_bin is None: + env.pop("CMUX_OPEN_WRAPPER_PYTHON3", None) + else: + env["CMUX_OPEN_WRAPPER_PYTHON3"] = python_bin if intercept_setting is None: env.pop("FAKE_DEFAULTS_INTERCEPT_OPEN", None) @@ -143,6 +155,7 @@ exit 0 result = subprocess.run( ["/bin/bash", str(wrapper), *args], + cwd=tmp, env=env, capture_output=True, text=True, @@ -282,6 +295,149 @@ def test_uppercase_scheme_routes_to_cmux(failures: list[str]) -> None: expect(cmux_log == [f"browser open {url}"], f"uppercase scheme: unexpected cmux log {cmux_log}", failures) +def test_local_html_file_routes_to_cmux(failures: list[str]) -> None: + filename = "fixtures/hello page.HTML" + open_log, cmux_log, code, stderr = run_wrapper( + args=[filename], + intercept_setting="1", + whitelist="", + local_files=[filename], + ) + expect(code == 0, f"local html file: wrapper exited {code}: {stderr}", failures) + expect(open_log == [], f"local html file: system open should not be called, got {open_log}", failures) + expect(len(cmux_log) == 1, f"local html file: expected exactly one cmux call, got {cmux_log}", failures) + if cmux_log: + expect( + cmux_log[0].startswith("browser open file://"), + f"local html file: expected file:// target, got {cmux_log[0]}", + failures, + ) + expect( + "hello%20page.HTML" in cmux_log[0], + f"local html file: expected URL-encoded filename in cmux target, got {cmux_log[0]}", + failures, + ) + + +def test_file_url_html_routes_to_cmux(failures: list[str]) -> None: + url = "file:///tmp/cmux-open-wrapper-fixture.html" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="", + ) + expect(code == 0, f"file url html: wrapper exited {code}: {stderr}", failures) + expect(open_log == [], f"file url html: system open should not be called, got {open_log}", failures) + expect(cmux_log == [f"browser open {url}"], f"file url html: unexpected cmux log {cmux_log}", failures) + + +def test_file_url_html_routes_to_cmux_without_python_binary(failures: list[str]) -> None: + url = "file:///tmp/cmux-open-wrapper-fixture.html" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="", + python_bin="/definitely/missing/python3", + ) + expect(code == 0, f"file url html no-python fallback: wrapper exited {code}: {stderr}", failures) + expect( + open_log == [], + f"file url html no-python fallback: system open should not be called, got {open_log}", + failures, + ) + expect( + cmux_log == [f"browser open {url}"], + f"file url html no-python fallback: unexpected cmux log {cmux_log}", + failures, + ) + + +def test_local_html_file_routes_to_cmux_without_python_binary(failures: list[str]) -> None: + filename = "fixtures/no python fallback.html" + open_log, cmux_log, code, stderr = run_wrapper( + args=[filename], + intercept_setting="1", + whitelist="", + local_files=[filename], + python_bin="/definitely/missing/python3", + ) + expect(code == 0, f"local html no-python fallback: wrapper exited {code}: {stderr}", failures) + expect(open_log == [], f"local html no-python fallback: system open should not be called, got {open_log}", failures) + expect( + len(cmux_log) == 1, + f"local html no-python fallback: expected exactly one cmux call, got {cmux_log}", + failures, + ) + if cmux_log: + expect( + cmux_log[0].startswith("browser open file://"), + f"local html no-python fallback: expected file:// target, got {cmux_log[0]}", + failures, + ) + expect( + "no%20python%20fallback.html" in cmux_log[0], + f"local html no-python fallback: expected URL-encoded filename, got {cmux_log[0]}", + failures, + ) + + +def test_domain_like_html_argument_passthrough(failures: list[str]) -> None: + arg = "example.com/report.html" + open_log, cmux_log, code, stderr = run_wrapper( + args=[arg], + intercept_setting="1", + whitelist="", + ) + expect(code == 0, f"domain-like html argument: wrapper exited {code}: {stderr}", failures) + expect( + cmux_log == [], + f"domain-like html argument: cmux should not be called, got {cmux_log}", + failures, + ) + expect( + open_log == [arg], + f"domain-like html argument: expected system open [{arg}], got {open_log}", + failures, + ) + + +def test_non_file_scheme_html_passthrough(failures: list[str]) -> None: + url = "ftp://example.com/report.html" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="", + ) + expect(code == 0, f"non-file scheme html: wrapper exited {code}: {stderr}", failures) + expect(cmux_log == [], f"non-file scheme html: cmux should not be called, got {cmux_log}", failures) + expect(open_log == [url], f"non-file scheme html: expected system open [{url}], got {open_log}", failures) + + +def test_mailto_html_passthrough(failures: list[str]) -> None: + url = "mailto:help@example.com?subject=report.html" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="", + ) + expect(code == 0, f"mailto html: wrapper exited {code}: {stderr}", failures) + expect(cmux_log == [], f"mailto html: cmux should not be called, got {cmux_log}", failures) + expect(open_log == [url], f"mailto html: expected system open [{url}], got {open_log}", failures) + + +def test_local_non_html_file_passthrough(failures: list[str]) -> None: + filename = "fixtures/readme.md" + open_log, cmux_log, code, stderr = run_wrapper( + args=[filename], + intercept_setting="1", + whitelist="", + local_files=[filename], + ) + expect(code == 0, f"local non-html file: wrapper exited {code}: {stderr}", failures) + expect(cmux_log == [], f"local non-html file: cmux should not be called, got {cmux_log}", failures) + expect(open_log == [filename], f"local non-html file: expected system open [{filename}], got {open_log}", failures) + + def test_unicode_whitelist_matches_punycode_url(failures: list[str]) -> None: url = "https://xn--bcher-kva.example/path" open_log, cmux_log, code, stderr = run_wrapper( @@ -316,6 +472,14 @@ def main() -> int: test_legacy_toggle_fallback_passthrough(failures) test_legacy_toggle_fallback_case_insensitive_passthrough(failures) test_uppercase_scheme_routes_to_cmux(failures) + test_local_html_file_routes_to_cmux(failures) + test_file_url_html_routes_to_cmux(failures) + test_file_url_html_routes_to_cmux_without_python_binary(failures) + test_local_html_file_routes_to_cmux_without_python_binary(failures) + test_domain_like_html_argument_passthrough(failures) + test_non_file_scheme_html_passthrough(failures) + test_mailto_html_passthrough(failures) + test_local_non_html_file_passthrough(failures) test_unicode_whitelist_matches_punycode_url(failures) test_punycode_whitelist_matches_unicode_url(failures) diff --git a/tests_v2/test_browser_file_url_load.py b/tests_v2/test_browser_file_url_load.py new file mode 100644 index 00000000..a4c63110 --- /dev/null +++ b/tests_v2/test_browser_file_url_load.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""v2 regression: browser can render local file:// HTML pages.""" + +import os +import sys +import tempfile +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmux, cmuxError + + +SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def _wait_until(pred, timeout_s: float, label: str) -> None: + deadline = time.time() + timeout_s + last_exc = None + while time.time() < deadline: + try: + if pred(): + return + except Exception as exc: # noqa: BLE001 + last_exc = exc + time.sleep(0.05) + if last_exc is not None: + raise cmuxError(f"Timed out waiting for {label}: {last_exc}") + raise cmuxError(f"Timed out waiting for {label}") + + +def main() -> int: + with tempfile.TemporaryDirectory(prefix="cmux-file-url-") as root: + html_path = Path(root) / "local-test.html" + html_path.write_text( + """ + + + cmux file url load + +

local HTML file loaded

+

This page is loaded via file://

+ + +""".strip(), + encoding="utf-8", + ) + file_url = html_path.resolve().as_uri() + + with cmux(SOCKET_PATH) as c: + opened = c._call("browser.open_split", {"url": "about:blank"}) or {} + sid = str(opened.get("surface_id") or "") + _must(bool(sid), f"browser.open_split returned no surface_id: {opened}") + + c._call("browser.navigate", {"surface_id": sid, "url": file_url}) + + _wait_until( + lambda: str((c._call("browser.get.title", {"surface_id": sid}) or {}).get("title") or "") + == "cmux file url load", + timeout_s=5.0, + label="browser.get.title(file://)", + ) + + page_text = c._call( + "browser.eval", + { + "surface_id": sid, + "script": "document.body ? (document.body.innerText || '') : ''", + }, + ) or {} + _must( + "local HTML file loaded" in str(page_text.get("value") or ""), + f"Expected file:// page body text: {page_text}", + ) + + url_payload = c._call("browser.url.get", {"surface_id": sid}) or {} + actual_url = str(url_payload.get("url") or "") + _must( + actual_url.startswith("file://"), + f"Expected browser.url.get to stay on file:// URL: {url_payload}", + ) + + print("PASS: browser loads local file:// HTML") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From f451766d12f546c9fbbc27292fb0611129b3246e Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:25:41 -0800 Subject: [PATCH 002/232] Add `cmux ` to open directories and Homebrew binary stanza (#705) * Add `cmux ` to open directories and Homebrew binary stanza CLI: `cmux .` or `cmux /path/to/dir` opens a new workspace at the given directory. If the app isn't running, it launches first and waits for the socket. Also adds `--cwd` flag to `new-workspace`. Server: `workspace.create` now accepts an optional `cwd` parameter, passed through to `TabManager.addWorkspace(workingDirectory:)`. Homebrew: adds `binary` stanza to the cask so `cmux` CLI is globally available after `brew install --cask cmux`. Updated both the cask file, the CI workflow template, and the manual release script so automated version bumps preserve the stanza. * Address review: validate cwd type, fix socket detection, propagate errors - looksLikePath now also matches paths containing `/` (e.g. `foo/bar`) - openPath uses socket connection attempt instead of fileExists to detect whether the app is running (Unix sockets may not appear on filesystem) - launchApp/activateApp now throw instead of swallowing errors with try? - Server validates that cwd param is a string, returns invalid_params error if wrong type is passed --- .github/workflows/update-homebrew.yml | 1 + CLI/cmux.swift | 133 +++++++++++++++++++++++--- Sources/TerminalController.swift | 12 ++- scripts/build-sign-upload.sh | 1 + 4 files changed, 131 insertions(+), 16 deletions(-) diff --git a/.github/workflows/update-homebrew.yml b/.github/workflows/update-homebrew.yml index d92de590..967cb442 100644 --- a/.github/workflows/update-homebrew.yml +++ b/.github/workflows/update-homebrew.yml @@ -94,6 +94,7 @@ jobs: depends_on macos: ">= :sonoma" app "cmux.app" + binary "#{appdir}/cmux.app/Contents/Resources/bin/cmux" zap trash: [ "~/Library/Application Support/cmux", diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 09fd854a..e83a0121 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -690,6 +690,12 @@ struct CMUXCLI { return } + // If the argument looks like a path (not a known command), open a workspace there. + if looksLikePath(command) { + try openPath(command, socketPath: socketPath) + return + } + // Check for --help/-h on subcommands before connecting to the socket, // so help text is available even when cmux is not running. if commandArgs.contains("--help") || commandArgs.contains("-h") { @@ -875,22 +881,25 @@ struct CMUXCLI { } case "new-workspace": - let (commandOpt, remaining) = parseOption(commandArgs, name: "--command") + let (commandOpt, rem0) = parseOption(commandArgs, name: "--command") + let (cwdOpt, remaining) = parseOption(rem0, name: "--cwd") if let unknown = remaining.first(where: { $0.hasPrefix("--") }) { - throw CLIError(message: "new-workspace: unknown flag '\(unknown)'. Known flags: --command ") + throw CLIError(message: "new-workspace: unknown flag '\(unknown)'. Known flags: --command , --cwd ") } - let response = try sendV1Command("new_workspace", client: client) - print(response) - if let commandText = commandOpt { - guard response.hasPrefix("OK ") else { - throw CLIError(message: "new-workspace failed, cannot run --command") - } - let wsId = String(response.dropFirst(3)).trimmingCharacters(in: .whitespacesAndNewlines) + var params: [String: Any] = [:] + if let cwdOpt { + let resolved = resolvePath(cwdOpt) + params["cwd"] = resolved + } + let response = try client.sendV2(method: "workspace.create", params: params) + let wsId = (response["workspace_ref"] as? String) ?? (response["workspace_id"] as? String) ?? "" + print("OK \(wsId)") + if let commandText = commandOpt, !wsId.isEmpty { // Wait for shell to initialize Thread.sleep(forTimeInterval: 0.5) let text = unescapeSendText(commandText + "\\n") - let params: [String: Any] = ["text": text, "workspace_id": wsId] - _ = try client.sendV2(method: "surface.send_text", params: params) + let sendParams: [String: Any] = ["text": text, "workspace_id": wsId] + _ = try client.sendV2(method: "surface.send_text", params: sendParams) } case "new-split": @@ -1499,6 +1508,97 @@ struct CMUXCLI { } } + private func resolvePath(_ path: String) -> String { + let expanded = NSString(string: path).expandingTildeInPath + if expanded.hasPrefix("/") { return expanded } + let cwd = FileManager.default.currentDirectoryPath + return (cwd as NSString).appendingPathComponent(expanded) + } + + /// Returns true if the argument looks like a filesystem path rather than a CLI command. + private func looksLikePath(_ arg: String) -> Bool { + if arg == "." || arg == ".." { return true } + if arg.hasPrefix("/") || arg.hasPrefix("./") || arg.hasPrefix("../") || arg.hasPrefix("~") { return true } + if arg.contains("/") { return true } + return false + } + + /// Open a path in cmux by creating a new workspace with the given directory. + /// Launches the app if it isn't already running. + private func openPath(_ path: String, socketPath: String) throws { + let resolved = resolvePath(path) + var isDir: ObjCBool = false + let exists = FileManager.default.fileExists(atPath: resolved, isDirectory: &isDir) + + let directory: String + if exists && isDir.boolValue { + directory = resolved + } else if exists { + // It's a file; use its parent directory + directory = (resolved as NSString).deletingLastPathComponent + } else { + throw CLIError(message: "Path does not exist: \(resolved)") + } + + // Try connecting to the socket. If it fails, launch the app and retry. + let client = SocketClient(path: socketPath) + if (try? client.connect()) == nil { + client.close() + try launchApp() + // Poll until socket accepts connections (up to 10 seconds) + let pollClient = SocketClient(path: socketPath) + var connected = false + for _ in 0..<100 { + if (try? pollClient.connect()) != nil { + connected = true + break + } + pollClient.close() + Thread.sleep(forTimeInterval: 0.1) + } + guard connected else { + throw CLIError(message: "cmux app did not start in time (socket not found at \(socketPath))") + } + // Use pollClient since it's connected + defer { pollClient.close() } + let params: [String: Any] = ["cwd": directory] + let response = try pollClient.sendV2(method: "workspace.create", params: params) + let wsRef = (response["workspace_ref"] as? String) ?? (response["workspace_id"] as? String) ?? "" + if !wsRef.isEmpty { + print("OK \(wsRef)") + } + try activateApp() + return + } + defer { client.close() } + + let params: [String: Any] = ["cwd": directory] + let response = try client.sendV2(method: "workspace.create", params: params) + let wsRef = (response["workspace_ref"] as? String) ?? (response["workspace_id"] as? String) ?? "" + if !wsRef.isEmpty { + print("OK \(wsRef)") + } + + // Bring the app to front + try activateApp() + } + + private func launchApp() throws { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/open") + process.arguments = ["-a", "cmux"] + try process.run() + process.waitUntilExit() + } + + private func activateApp() throws { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/open") + process.arguments = ["-a", "cmux"] + try process.run() + process.waitUntilExit() + } + private func sendV1Command(_ command: String, client: SocketClient) throws -> String { let response = try client.send(command: command) if response.hasPrefix("ERROR:") { @@ -3626,16 +3726,18 @@ struct CMUXCLI { """ case "new-workspace": return """ - Usage: cmux new-workspace [--command ] + Usage: cmux new-workspace [--cwd ] [--command ] Create a new workspace in the current window. Flags: + --cwd Set the working directory for the new workspace --command Send text+Enter to the new workspace after creation Example: cmux new-workspace - cmux new-workspace --command "npm test" + cmux new-workspace --cwd ~/projects/myapp + cmux new-workspace --cwd . --command "npm test" """ case "list-workspaces": return """ @@ -6221,7 +6323,8 @@ struct CMUXCLI { cmux - control cmux via Unix socket Usage: - cmux [--socket PATH] [--window WINDOW] [--password PASSWORD] [--json] [--id-format refs|uuids|both] [--version] [options] + cmux Open a directory in a new workspace (launches cmux if needed) + cmux [global-options] [options] Handle Inputs: For most v2-backed commands you can use UUIDs, short refs (window:1/workspace:2/pane:3/surface:4), or indexes. @@ -6245,7 +6348,7 @@ struct CMUXCLI { reorder-workspace --workspace (--index | --before | --after ) [--window ] workspace-action --action [--workspace ] [--title ] list-workspaces - new-workspace [--command ] + new-workspace [--cwd ] [--command ] new-split [--workspace ] [--surface ] [--panel ] list-panes [--workspace ] list-pane-surfaces [--workspace ] [--pane ] diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 956b9a4a..9eefcd40 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -2368,13 +2368,23 @@ class TerminalController { return .err(code: "unavailable", message: "TabManager not available", data: nil) } + let cwd: String? + if let raw = params["cwd"] { + guard let str = raw as? String else { + return .err(code: "invalid_params", message: "cwd must be a string", data: nil) + } + cwd = str + } else { + cwd = nil + } + var newId: UUID? let shouldFocus = v2FocusAllowed() #if DEBUG let startedAt = ProcessInfo.processInfo.systemUptime #endif v2MainSync { - let ws = tabManager.addWorkspace(select: shouldFocus) + let ws = tabManager.addWorkspace(workingDirectory: cwd, select: shouldFocus) newId = ws.id } #if DEBUG diff --git a/scripts/build-sign-upload.sh b/scripts/build-sign-upload.sh index 06f4e8d8..ac870d30 100755 --- a/scripts/build-sign-upload.sh +++ b/scripts/build-sign-upload.sh @@ -177,6 +177,7 @@ cask "cmux" do depends_on macos: ">= :ventura" app "cmux.app" + binary "#{appdir}/cmux.app/Contents/Resources/bin/cmux" zap trash: [ "~/Library/Application Support/cmux", From aa8fc7232ae8825b87e9a6baad49b80624deaaf4 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Sat, 28 Feb 2026 21:12:17 -0800 Subject: [PATCH 003/232] Fix Shift+Space IME toggle inserting space (#641) (#670) --- Sources/GhosttyTerminalView.swift | 26 ++++++++++++++-- cmuxTests/CJKIMEInputTests.swift | 52 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index efdb0fe4..46f7b101 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2813,6 +2813,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { var keyTextAccumulatorForTesting: [String]? { keyTextAccumulator } + func shouldSuppressShiftSpaceFallbackTextForTesting(event: NSEvent, markedTextBefore: Bool) -> Bool { + shouldSuppressShiftSpaceFallbackText(event: event, markedTextBefore: markedTextBefore) + } // Test-only IME point override so firstRect behavior can be regression tested. private var imePointOverrideForTesting: (x: Double, y: Double, width: Double, height: Double)? @@ -3117,12 +3120,13 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { keyEvent.composing = markedText.length > 0 || markedTextBefore // Use accumulated text from insertText (for IME), or compute text for key - if let accumulated = keyTextAccumulator, !accumulated.isEmpty { + let accumulatedText = keyTextAccumulator ?? [] + if !accumulatedText.isEmpty { // Accumulated text comes from insertText (IME composition result). // These never have "composing" set to true because these are the // result of a composition. keyEvent.composing = false - for text in accumulated { + for text in accumulatedText { if shouldSendText(text) { text.withCString { ptr in keyEvent.text = ptr @@ -3137,8 +3141,13 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { // Get the appropriate text for this key event // For control characters, this returns the unmodified character // so Ghostty's KeyEncoder can handle ctrl encoding + let suppressShiftSpaceFallbackText = + shouldSuppressShiftSpaceFallbackText( + event: translationEvent, + markedTextBefore: markedTextBefore + ) if let text = textForKeyEvent(translationEvent) { - if shouldSendText(text) { + if shouldSendText(text), !suppressShiftSpaceFallbackText { text.withCString { ptr in keyEvent.text = ptr _ = ghostty_surface_key(surface, keyEvent) @@ -3250,6 +3259,17 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { return first >= 0x20 } + /// If AppKit consumed Shift+Space for IME/input-source switching, interpretKeyEvents + /// can return without insertText and without a detectable layout ID change. + /// In that case we must not synthesize a literal space fallback. + private func shouldSuppressShiftSpaceFallbackText(event: NSEvent, markedTextBefore: Bool) -> Bool { + guard event.keyCode == 49 else { return false } + let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + guard flags == [.shift] else { return false } + guard !markedTextBefore, markedText.length == 0 else { return false } + return true + } + private func ghosttyKeyEvent(for event: NSEvent, surface: ghostty_surface_t) -> ghostty_input_key_s { var keyEvent = ghostty_input_key_s() keyEvent.action = GHOSTTY_ACTION_PRESS diff --git a/cmuxTests/CJKIMEInputTests.swift b/cmuxTests/CJKIMEInputTests.swift index 42ad48b2..6ef9aa03 100644 --- a/cmuxTests/CJKIMEInputTests.swift +++ b/cmuxTests/CJKIMEInputTests.swift @@ -762,6 +762,58 @@ final class CJKIMEKeyTextAccumulatorTests: XCTestCase { } } +// MARK: - Shift+Space fallback suppression (IME source-switch shortcut) + +final class CJKIMEShiftSpaceFallbackTests: XCTestCase { + func testSuppressesShiftSpaceFallbackWhenNoMarkedTextAndNoIMECommit() { + let view = GhosttyNSView(frame: .zero) + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: 0, + context: nil, + characters: " ", + charactersIgnoringModifiers: " ", + isARepeat: false, + keyCode: 49 + ) else { + XCTFail("Failed to create Shift+Space event") + return + } + + XCTAssertTrue( + view.shouldSuppressShiftSpaceFallbackTextForTesting(event: event, markedTextBefore: false), + "Shift+Space should suppress synthesized space fallback when IME did not commit text" + ) + } + + func testDoesNotSuppressRegularSpaceFallback() { + let view = GhosttyNSView(frame: .zero) + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: 0, + context: nil, + characters: " ", + charactersIgnoringModifiers: " ", + isARepeat: false, + keyCode: 49 + ) else { + XCTFail("Failed to create Space event") + return + } + + XCTAssertFalse( + view.shouldSuppressShiftSpaceFallbackTextForTesting(event: event, markedTextBefore: false), + "Only Shift+Space should be suppressed" + ) + } +} + // MARK: - Space release regression (Codex hold-to-talk in cmux) @MainActor From 8b4abd8ff048b1579a4d6c940afd5e96d559c735 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:12:10 -0800 Subject: [PATCH 004/232] Remove Xcode Cloud CI scripts (#709) Xcode Cloud workflows have been deleted from App Store Connect. These hook scripts are no longer needed since CI/CD runs through GitHub Actions. --- ci_scripts/ci_post_clone.sh | 48 --------------------------------- ci_scripts/ci_pre_xcodebuild.sh | 43 ----------------------------- 2 files changed, 91 deletions(-) delete mode 100755 ci_scripts/ci_post_clone.sh delete mode 100755 ci_scripts/ci_pre_xcodebuild.sh diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh deleted file mode 100755 index 986b22a8..00000000 --- a/ci_scripts/ci_post_clone.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -set -euo pipefail - -echo "=== ci_post_clone.sh ===" - -# Initialize submodules (needed for vendor/bonsplit SPM package) -echo "Initializing submodules..." -git submodule update --init --recursive - -# Get ghostty submodule SHA -GHOSTTY_SHA=$(git -C "$CI_PRIMARY_REPOSITORY_PATH/ghostty" rev-parse HEAD) -echo "Ghostty SHA: $GHOSTTY_SHA" - -# Download pre-built xcframework from manaflow-ai/ghostty releases -TAG="xcframework-$GHOSTTY_SHA" -URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" - -echo "Downloading xcframework from $URL" - -MAX_RETRIES=30 -RETRY_DELAY=20 - -for i in $(seq 1 $MAX_RETRIES); do - if curl -fSL -o "$CI_PRIMARY_REPOSITORY_PATH/GhosttyKit.xcframework.tar.gz" "$URL"; then - echo "Download succeeded on attempt $i" - break - fi - if [ "$i" -eq "$MAX_RETRIES" ]; then - echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 - exit 1 - fi - echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." - sleep $RETRY_DELAY -done - -# Extract xcframework to project root -echo "Extracting xcframework..." -cd "$CI_PRIMARY_REPOSITORY_PATH" -tar xzf GhosttyKit.xcframework.tar.gz -rm GhosttyKit.xcframework.tar.gz -test -d GhosttyKit.xcframework -echo "GhosttyKit.xcframework extracted successfully" - -# Download Metal toolchain (required for shader compilation) -echo "Downloading Metal toolchain..." -xcodebuild -downloadComponent MetalToolchain - -echo "=== ci_post_clone.sh done ===" diff --git a/ci_scripts/ci_pre_xcodebuild.sh b/ci_scripts/ci_pre_xcodebuild.sh deleted file mode 100755 index d4def0b5..00000000 --- a/ci_scripts/ci_pre_xcodebuild.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -set -euo pipefail - -ROOT="${CI_PRIMARY_REPOSITORY_PATH:-$PWD}" -cd "$ROOT" - -echo "ci_pre_xcodebuild: repository root is $ROOT" - -if [ -f "vendor/bonsplit/Package.swift" ]; then - echo "ci_pre_xcodebuild: vendor/bonsplit already present" - exit 0 -fi - -if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "ci_pre_xcodebuild: attempting submodule init for vendor/bonsplit" - git submodule sync --recursive || true - git submodule update --init --recursive vendor/bonsplit || true -fi - -if [ ! -f "vendor/bonsplit/Package.swift" ]; then - echo "ci_pre_xcodebuild: submodule not present, cloning fallback" - rm -rf vendor/bonsplit - mkdir -p vendor - git clone --depth 1 https://github.com/manaflow-ai/bonsplit.git vendor/bonsplit - - if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - expected_sha="$(git ls-tree HEAD vendor/bonsplit | awk '{print $3}')" - if [ -n "${expected_sha:-}" ]; then - ( - cd vendor/bonsplit - git fetch --depth 1 origin "$expected_sha" || true - git checkout "$expected_sha" || true - ) - fi - fi -fi - -if [ ! -f "vendor/bonsplit/Package.swift" ]; then - echo "ci_pre_xcodebuild: missing vendor/bonsplit/Package.swift after recovery" >&2 - exit 1 -fi - -echo "ci_pre_xcodebuild: vendor/bonsplit is ready" From b6efd73d57ea484857cc8cefcaff4565244e7316 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:32:51 -0800 Subject: [PATCH 005/232] Switch CI and Build GhosttyKit to Depot macOS runners (#710) * Switch CI and Build GhosttyKit to Depot macOS runners Moves the tests job (unit + UI) and build-ghosttykit job from self-hosted to depot-macos-latest so CI doesn't block the local Mac Mini. CI tests now download the pre-built xcframework from the ghostty releases instead of building with zig. Nightly and release workflows stay on self-hosted for signing/notarization. * Add on-demand Depot test workflow, revert CI changes Adds test-depot.yml (workflow_dispatch) for running tests on Depot macOS runners during local dev without tying up the Mac Mini. Reverts ci.yml and build-ghosttykit.yml back to self-hosted. --- .github/workflows/test-depot.yml | 146 +++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 .github/workflows/test-depot.yml diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml new file mode 100644 index 00000000..4a0a4aaa --- /dev/null +++ b/.github/workflows/test-depot.yml @@ -0,0 +1,146 @@ +name: Run tests on Depot + +on: + workflow_dispatch: + inputs: + ref: + description: Branch or SHA to test + required: false + default: "" + skip_ui_tests: + description: Skip UI tests (headless Depot runners may not support them) + required: false + default: false + type: boolean + +jobs: + tests: + runs-on: depot-macos-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ github.event.inputs.ref || github.ref }} + submodules: recursive + + - name: Select Xcode + run: | + set -euo pipefail + if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then + XCODE_DIR="/Applications/Xcode.app/Contents/Developer" + else + XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)" + if [ -n "$XCODE_APP" ]; then + XCODE_DIR="$XCODE_APP/Contents/Developer" + else + echo "No Xcode.app found under /Applications" >&2 + exit 1 + fi + fi + echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" + export DEVELOPER_DIR="$XCODE_DIR" + xcodebuild -version + xcrun --sdk macosx --show-sdk-path + + - name: Download pre-built GhosttyKit.xcframework + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 + exit 1 + fi + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz + test -d GhosttyKit.xcframework + + - name: Clean DerivedData + run: | + rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + + - name: Resolve Swift packages + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + rm -rf "$SOURCE_PACKAGES_DIR" + mkdir -p "$SOURCE_PACKAGES_DIR" + + for attempt in 1 2 3; do + if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -resolvePackageDependencies; then + exit 0 + fi + if [ "$attempt" -eq 3 ]; then + echo "Failed to resolve Swift packages after 3 attempts" >&2 + exit 1 + fi + echo "Package resolution failed on attempt $attempt, retrying..." + sleep $((attempt * 5)) + done + + - name: Run unit tests + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + run_unit_tests() { + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" test 2>&1 + } + + set +e + OUTPUT=$(run_unit_tests) + EXIT_CODE=$? + set -e + + # SwiftPM binary artifact resolution can occasionally fail with + # "Could not resolve package dependencies". Retry once after clearing + # SwiftPM/DerivedData caches to recover from transient corruption. + if [ "$EXIT_CODE" -ne 0 ] && echo "$OUTPUT" | grep -q "Could not resolve package dependencies"; then + echo "SwiftPM package resolution failed, clearing caches and retrying once" + rm -rf ~/Library/Caches/org.swift.swiftpm + rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + set +e + OUTPUT=$(run_unit_tests) + EXIT_CODE=$? + set -e + fi + + echo "$OUTPUT" + if [ "$EXIT_CODE" -ne 0 ]; then + SUMMARY=$(echo "$OUTPUT" | grep "Executed.*tests.*with.*failures" | tail -1) + if echo "$SUMMARY" | grep -q "(0 unexpected)"; then + echo "All failures are expected, treating as pass" + else + echo "Unexpected test failures detected" + exit 1 + fi + fi + + - name: Run UI tests + if: ${{ !inputs.skip_ui_tests }} + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" \ + -only-testing:cmuxUITests/UpdatePillUITests test From 9d00ae9ab00e3463f7ecd80da1c00a03a59fe05e Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:46:15 -0800 Subject: [PATCH 006/232] Move Build GhosttyKit to Depot to avoid self-hosted concurrency cancellations (#713) build-ghosttykit shared the self-hosted-build concurrency group with CI tests, causing one to get cancelled when both trigger on the same push. Most runs are no-ops (xcframework already exists), so Depot is a good fit. Eliminates the red X on pushes to main. --- .github/workflows/build-ghosttykit.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-ghosttykit.yml b/.github/workflows/build-ghosttykit.yml index 0dc2871a..1f2fb84f 100644 --- a/.github/workflows/build-ghosttykit.yml +++ b/.github/workflows/build-ghosttykit.yml @@ -8,12 +8,9 @@ on: jobs: build-ghosttykit: - # Never run self-hosted jobs for fork pull requests. + # Never run Depot jobs for fork pull requests (avoid billing on external PRs). if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: self-hosted - concurrency: - group: self-hosted-build - cancel-in-progress: false + runs-on: depot-macos-latest steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 From dba1e232d39bde1aa62cb9bac33dfeebfc676295 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Sat, 28 Feb 2026 23:18:21 -0800 Subject: [PATCH 007/232] Fix SwiftPM binary artifact cache collision in CI (#716) After rm -rf of the SPM cache dir, recreate it as an empty directory so binary target downloads (e.g. Sentry.xcframework.zip) don't hit "already exists in file system" errors from stale artifacts on the self-hosted runner. Co-authored-by: Claude Opus 4.6 --- .github/workflows/ci.yml | 1 + .github/workflows/nightly.yml | 1 + .github/workflows/release.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 145bc80e..37ba0110 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,6 +144,7 @@ jobs: if [ "$EXIT_CODE" -ne 0 ] && echo "$OUTPUT" | grep -q "Could not resolve package dependencies"; then echo "SwiftPM package resolution failed, clearing caches and retrying once" rm -rf ~/Library/Caches/org.swift.swiftpm + mkdir -p ~/Library/Caches/org.swift.swiftpm rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* set +e OUTPUT=$(run_unit_tests) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 052e67e3..a3cb18f8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -128,6 +128,7 @@ jobs: - name: Clear SPM cache run: | rm -rf ~/Library/Caches/org.swift.swiftpm + mkdir -p ~/Library/Caches/org.swift.swiftpm rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* - name: Configure SwiftPM cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7adf546a..84825b5f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,6 +123,7 @@ jobs: if: steps.guard_release_assets.outputs.skip_all != 'true' run: | rm -rf ~/Library/Caches/org.swift.swiftpm + mkdir -p ~/Library/Caches/org.swift.swiftpm rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* - name: Configure SwiftPM cache From e01236115ee9d412225a0212b501f8b370b6ac36 Mon Sep 17 00:00:00 2001 From: doug Date: Sun, 1 Mar 2026 02:42:41 -0800 Subject: [PATCH 008/232] =?UTF-8?q?Fix:=20use=20policy=20constants=20for?= =?UTF-8?q?=20minimum=20window=20size=20instead=20of=20hardcoded=20800?= =?UTF-8?q?=C3=97600=20(#715)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root ContentView body had `.frame(minWidth: 800, minHeight: 600)` hardcoded since the initial commit, preventing users from resizing the window narrower than 800px even when the sidebar is hidden and a narrow terminal layout is perfectly usable. Replace the magic numbers with the existing SessionPersistencePolicy constants (minimumWindowWidth = 300, minimumWindowHeight = 200), which were already defined and used for session-restore frame validation. This gives those constants a second job as the canonical size floor and makes it easy to tune the minimum in one place. Co-authored-by: Claude Sonnet 4.6 --- Sources/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 29ed585a..620fe3ee 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -2036,7 +2036,7 @@ struct ContentView: View { .padding(.top, 4) } } - .frame(minWidth: 800, minHeight: 600) + .frame(minWidth: CGFloat(SessionPersistencePolicy.minimumWindowWidth), minHeight: CGFloat(SessionPersistencePolicy.minimumWindowHeight)) .background(Color.clear) ) From e295f384fcddca5ed1861c1b1e252586343926e8 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 02:58:27 -0800 Subject: [PATCH 009/232] Run all XCUITests in CI and add skip_unit_tests to Depot workflow (#718) * Run all XCUITests in CI instead of just UpdatePillUITests Removes the -only-testing:cmuxUITests/UpdatePillUITests filter so new test classes are picked up automatically. No more workflow edits needed when adding tests. * Add skip_unit_tests input to test-depot workflow --- .github/workflows/ci.yml | 2 +- .github/workflows/test-depot.yml | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37ba0110..91128bce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,4 +171,4 @@ jobs: -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ -disableAutomaticPackageResolution \ -destination "platform=macOS" \ - -only-testing:cmuxUITests/UpdatePillUITests test + -only-testing:cmuxUITests test diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml index 4a0a4aaa..b63fe387 100644 --- a/.github/workflows/test-depot.yml +++ b/.github/workflows/test-depot.yml @@ -7,8 +7,13 @@ on: description: Branch or SHA to test required: false default: "" + skip_unit_tests: + description: Skip unit tests (run only UI tests) + required: false + default: false + type: boolean skip_ui_tests: - description: Skip UI tests (headless Depot runners may not support them) + description: Skip UI tests (run only unit tests) required: false default: false type: boolean @@ -95,6 +100,7 @@ jobs: done - name: Run unit tests + if: ${{ !inputs.skip_unit_tests }} run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" @@ -143,4 +149,4 @@ jobs: -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ -disableAutomaticPackageResolution \ -destination "platform=macOS" \ - -only-testing:cmuxUITests/UpdatePillUITests test + -only-testing:cmuxUITests test From bc1b6fd9ebb36dab4396c0cc419b500eeafde41f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 03:48:46 -0800 Subject: [PATCH 010/232] Honor Ghostty background-opacity across all cmux chrome (#667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Honor Ghostty background-opacity across all cmux chrome Parse background-opacity from Ghostty config and propagate it through the entire chrome pipeline: bonsplit tab bar (via RRGGBBAA hex), browser panel/omnibar, titlebar, empty panel, and window background. Decouple glass effect from sidebar blend mode — bgGlassEnabled now defaults to false so opacity works independently. Add GhosttyBackgroundTheme helper for consistent color+opacity resolution across all UI surfaces. Fixes https://github.com/manaflow-ai/cmux/issues/263 * Titlebar and chrome opacity matches terminal background-opacity Use CALayer-level opacity for the titlebar background instead of SwiftUI Color alpha, matching the terminal's Metal compositing path. Account for the double alpha stacking in the terminal area (Bonsplit container bg + Ghostty renderer) so the titlebar visually matches. Also fix opacity-only config changes not triggering titlebar refresh on Cmd+Shift+, reload. --- Sources/ContentView.swift | 102 ++++++++++++------ Sources/GhosttyConfig.swift | 5 + Sources/GhosttyTerminalView.swift | 55 +++++++--- Sources/Panels/BrowserPanel.swift | 53 ++++++++- Sources/Panels/BrowserPanelView.swift | 21 +++- Sources/Workspace.swift | 63 ++++++++--- Sources/WorkspaceContentView.swift | 13 ++- Sources/cmuxApp.swift | 6 +- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 86 +++++++++++++++ cmuxTests/GhosttyConfigTests.swift | 84 +++++++++++++++ vendor/bonsplit | 2 +- 11 files changed, 414 insertions(+), 76 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 620fe3ee..49d634f0 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -227,6 +227,26 @@ enum WindowGlassEffect { } } +/// CALayer-backed titlebar background. Uses layer-level opacity (not per-pixel alpha) +/// to match how the terminal's Metal surface composites its background. +struct TitlebarLayerBackground: NSViewRepresentable { + var backgroundColor: NSColor + var opacity: CGFloat + + func makeNSView(context: Context) -> NSView { + let view = NSView() + view.wantsLayer = true + view.layer?.backgroundColor = backgroundColor.withAlphaComponent(1.0).cgColor + view.layer?.opacity = Float(opacity) + return view + } + + func updateNSView(_ nsView: NSView, context: Context) { + nsView.layer?.backgroundColor = backgroundColor.withAlphaComponent(1.0).cgColor + nsView.layer?.opacity = Float(opacity) + } +} + final class SidebarState: ObservableObject { @Published var isVisible: Bool @Published var persistedWidth: CGFloat @@ -1831,19 +1851,11 @@ struct ContentView: View { // Background glass settings @AppStorage("bgGlassTintHex") private var bgGlassTintHex = "#000000" @AppStorage("bgGlassTintOpacity") private var bgGlassTintOpacity = 0.03 - @AppStorage("bgGlassEnabled") private var bgGlassEnabled = true + @AppStorage("bgGlassEnabled") private var bgGlassEnabled = false @AppStorage("debugTitlebarLeadingExtra") private var debugTitlebarLeadingExtra: Double = 0 @State private var titlebarLeadingInset: CGFloat = 12 private var windowIdentifier: String { "cmux.main.\(windowId.uuidString)" } - private var fakeTitlebarBackground: Color { - _ = titlebarThemeGeneration - let ghosttyBackground = GhosttyApp.shared.defaultBackgroundColor - let configuredOpacity = CGFloat(max(0, min(1, GhosttyApp.shared.defaultBackgroundOpacity))) - let minimumChromeOpacity: CGFloat = ghosttyBackground.isLightColor ? 0.90 : 0.84 - let chromeOpacity = max(minimumChromeOpacity, configuredOpacity) - return Color(nsColor: ghosttyBackground.withAlphaComponent(chromeOpacity)) - } private var fakeTitlebarTextColor: Color { _ = titlebarThemeGeneration let ghosttyBackground = GhosttyApp.shared.defaultBackgroundColor @@ -1902,7 +1914,17 @@ struct ContentView: View { .frame(height: titlebarPadding) .frame(maxWidth: .infinity) .contentShape(Rectangle()) - .background(fakeTitlebarBackground) + .background({ + // The terminal area has two stacked semi-transparent layers: the Bonsplit + // container chrome background plus Ghostty's own Metal-rendered background. + // Compute the effective composited opacity so the titlebar matches visually. + let alpha = CGFloat(GhosttyApp.shared.defaultBackgroundOpacity) + let effective = alpha >= 0.999 ? alpha : 1.0 - pow(1.0 - alpha, 2) + return TitlebarLayerBackground( + backgroundColor: GhosttyApp.shared.defaultBackgroundColor, + opacity: effective + ) + }()) .overlay(alignment: .bottom) { Rectangle() .fill(Color(nsColor: .separatorColor)) @@ -2435,23 +2457,31 @@ struct ContentView: View { // Background glass: skip on macOS 26+ where NSGlassEffectView can cause blank // or incorrectly tinted SwiftUI content. Keep native window rendering there so // Ghostty theme colors remain authoritative. - if sidebarBlendMode == SidebarBlendModeOption.behindWindow.rawValue + let currentThemeBackground = GhosttyBackgroundTheme.currentColor() + let shouldApplyWindowGlassFallback = + sidebarBlendMode == SidebarBlendModeOption.behindWindow.rawValue && bgGlassEnabled - && !WindowGlassEffect.isAvailable { + && !WindowGlassEffect.isAvailable + let shouldForceTransparentHosting = + shouldApplyWindowGlassFallback || currentThemeBackground.alphaComponent < 0.999 + + if shouldForceTransparentHosting { window.isOpaque = false - window.backgroundColor = .clear - // Configure contentView and all subviews for transparency + // Keep the window clear whenever translucency is active. Relying only on + // terminal focus-driven updates can leave stale opaque window fills. + window.backgroundColor = NSColor.white.withAlphaComponent(0.001) + // Configure contentView hierarchy for transparency. if let contentView = window.contentView { - contentView.wantsLayer = true - contentView.layer?.backgroundColor = NSColor.clear.cgColor - contentView.layer?.isOpaque = false - // Make SwiftUI hosting view transparent - for subview in contentView.subviews { - subview.wantsLayer = true - subview.layer?.backgroundColor = NSColor.clear.cgColor - subview.layer?.isOpaque = false - } + makeViewHierarchyTransparent(contentView) } + } else { + // Browser-focused workspaces may not have an active terminal panel to refresh + // the NSWindow background. Keep opaque theme changes applied here as well. + window.backgroundColor = currentThemeBackground + window.isOpaque = currentThemeBackground.alphaComponent >= 0.999 + } + + if shouldApplyWindowGlassFallback { // Apply liquid glass effect to the window with tint from settings let tintColor = (NSColor(hex: bgGlassTintHex) ?? .black).withAlphaComponent(bgGlassTintOpacity) WindowGlassEffect.apply(to: window, tintColor: tintColor) @@ -2519,6 +2549,16 @@ struct ContentView: View { sidebarSelectionState.selection = .tabs } + private func makeViewHierarchyTransparent(_ root: NSView) { + var stack: [NSView] = [root] + while let view = stack.popLast() { + view.wantsLayer = true + view.layer?.backgroundColor = NSColor.clear.cgColor + view.layer?.isOpaque = false + stack.append(contentsOf: view.subviews) + } + } + private func updateWindowGlassTint() { // Find this view's main window by identifier (keyWindow might be a debug panel/settings). guard let window = NSApp.windows.first(where: { $0.identifier?.rawValue == windowIdentifier }) else { return } @@ -9008,18 +9048,20 @@ enum SidebarPresetOption: String, CaseIterable, Identifiable { } extension NSColor { - func hexString() -> String { + func hexString(includeAlpha: Bool = false) -> String { let color = usingColorSpace(.sRGB) ?? self var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - return String( - format: "#%02X%02X%02X", - min(255, max(0, Int(red * 255))), - min(255, max(0, Int(green * 255))), - min(255, max(0, Int(blue * 255))) - ) + let redByte = min(255, max(0, Int(red * 255))) + let greenByte = min(255, max(0, Int(green * 255))) + let blueByte = min(255, max(0, Int(blue * 255))) + if includeAlpha { + let alphaByte = min(255, max(0, Int(alpha * 255))) + return String(format: "#%02X%02X%02X%02X", redByte, greenByte, blueByte, alphaByte) + } + return String(format: "#%02X%02X%02X", redByte, greenByte, blueByte) } } diff --git a/Sources/GhosttyConfig.swift b/Sources/GhosttyConfig.swift index a3516ae2..1e3aae49 100644 --- a/Sources/GhosttyConfig.swift +++ b/Sources/GhosttyConfig.swift @@ -21,6 +21,7 @@ struct GhosttyConfig { // Colors (from theme or config) var backgroundColor: NSColor = NSColor(hex: "#272822")! + var backgroundOpacity: Double = 1.0 var foregroundColor: NSColor = NSColor(hex: "#fdfff1")! var cursorColor: NSColor = NSColor(hex: "#c0c1b5")! var cursorTextColor: NSColor = NSColor(hex: "#8d8e82")! @@ -148,6 +149,10 @@ struct GhosttyConfig { if let color = NSColor(hex: value) { backgroundColor = color } + case "background-opacity": + if let opacity = Double(value) { + backgroundOpacity = opacity + } case "foreground": if let color = NSColor(hex: value) { foregroundColor = color diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 46f7b101..cd120bb1 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -10,12 +10,22 @@ import Bonsplit import IOSurface #if os(macOS) -private func cmuxShouldUseTransparentBackgroundWindow() -> Bool { +func cmuxShouldUseTransparentBackgroundWindow() -> Bool { let defaults = UserDefaults.standard let sidebarBlendMode = defaults.string(forKey: "sidebarBlendMode") ?? "withinWindow" - let bgGlassEnabled = defaults.object(forKey: "bgGlassEnabled") as? Bool ?? true + let bgGlassEnabled = defaults.object(forKey: "bgGlassEnabled") as? Bool ?? false return sidebarBlendMode == "behindWindow" && bgGlassEnabled && !WindowGlassEffect.isAvailable } + +func cmuxShouldUseClearWindowBackground(for opacity: Double) -> Bool { + cmuxShouldUseTransparentBackgroundWindow() || opacity < 0.999 +} + +private func cmuxTransparentWindowBaseColor() -> NSColor { + // A tiny non-zero alpha matches Ghostty's window compositing behavior on macOS and + // avoids visual artifacts that can happen with a fully clear window background. + NSColor.white.withAlphaComponent(0.001) +} #endif #if DEBUG @@ -831,6 +841,7 @@ class GhosttyApp { var opacity = defaultBackgroundOpacity let opacityKey = "background-opacity" _ = ghostty_config_get(config, &opacity, opacityKey, UInt(opacityKey.lengthOfBytes(using: .utf8))) + opacity = min(1.0, max(0.0, opacity)) applyDefaultBackground( color: resolvedColor, opacity: opacity, @@ -1391,11 +1402,11 @@ class GhosttyApp { private func applyBackgroundToKeyWindow() { guard let window = activeMainWindow() else { return } - if cmuxShouldUseTransparentBackgroundWindow() { - window.backgroundColor = .clear + if cmuxShouldUseClearWindowBackground(for: defaultBackgroundOpacity) { + window.backgroundColor = cmuxTransparentWindowBaseColor() window.isOpaque = false if backgroundLogEnabled { - logBackground("applied transparent window for behindWindow blur") + logBackground("applied transparent window background opacity=\(String(format: "%.3f", defaultBackgroundOpacity))") } } else { let color = defaultBackgroundColor.withAlphaComponent(defaultBackgroundOpacity) @@ -2334,8 +2345,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { if let layer { CATransaction.begin() CATransaction.setDisableActions(true) - layer.backgroundColor = color.cgColor - layer.isOpaque = color.alphaComponent >= 1.0 + // GhosttySurfaceScrollView owns the panel background fill. Keeping this layer clear + // avoids stacking multiple identical translucent backgrounds (which looks opaque). + layer.backgroundColor = NSColor.clear.cgColor + layer.isOpaque = false CATransaction.commit() } terminalSurface?.hostedView.setBackgroundColor(color) @@ -2361,15 +2374,15 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } applySurfaceBackground() let color = effectiveBackgroundColor() - if cmuxShouldUseTransparentBackgroundWindow() { - window.backgroundColor = .clear + if cmuxShouldUseClearWindowBackground(for: color.alphaComponent) { + window.backgroundColor = cmuxTransparentWindowBaseColor() window.isOpaque = false } else { window.backgroundColor = color window.isOpaque = color.alphaComponent >= 1.0 } if GhosttyApp.shared.backgroundLogEnabled { - let signature = "\(cmuxShouldUseTransparentBackgroundWindow() ? "transparent" : color.hexString()):\(String(format: "%.3f", color.alphaComponent))" + let signature = "\(cmuxShouldUseClearWindowBackground(for: color.alphaComponent) ? "transparent" : color.hexString()):\(String(format: "%.3f", color.alphaComponent))" if signature != lastLoggedWindowBackgroundSignature { lastLoggedWindowBackgroundSignature = signature let hasOverride = backgroundColor != nil @@ -2377,7 +2390,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { let defaultHex = GhosttyApp.shared.defaultBackgroundColor.hexString() let source = hasOverride ? "surfaceOverride" : "defaultBackground" GhosttyApp.shared.logBackground( - "window background applied tab=\(tabId?.uuidString ?? "unknown") surface=\(terminalSurface?.id.uuidString ?? "unknown") source=\(source) override=\(overrideHex) default=\(defaultHex) transparent=\(cmuxShouldUseTransparentBackgroundWindow()) color=\(color.hexString()) opacity=\(String(format: "%.3f", color.alphaComponent))" + "window background applied tab=\(tabId?.uuidString ?? "unknown") surface=\(terminalSurface?.id.uuidString ?? "unknown") source=\(source) override=\(overrideHex) default=\(defaultHex) transparent=\(cmuxShouldUseClearWindowBackground(for: color.alphaComponent)) color=\(color.hexString()) opacity=\(String(format: "%.3f", color.alphaComponent))" ) } } @@ -3810,6 +3823,13 @@ final class GhosttySurfaceScrollView: NSView { private var pendingDropZone: DropZone? private var dropZoneOverlayAnimationGeneration: UInt64 = 0 // Intentionally no focus retry loops: rely on AppKit first-responder and bonsplit selection. + + private static func panelBackgroundFillColor(for terminalBackgroundColor: NSColor) -> NSColor { + // The Ghostty renderer already draws translucent terminal backgrounds. If we paint an + // additional translucent layer here, alpha stacks and appears effectively opaque. + terminalBackgroundColor.alphaComponent < 0.999 ? .clear : terminalBackgroundColor + } + #if DEBUG private var lastDropZoneOverlayLogSignature: String? private static var flashCounts: [UUID: Int] = [:] @@ -3949,10 +3969,11 @@ final class GhosttySurfaceScrollView: NSView { layer?.masksToBounds = true backgroundView.wantsLayer = true - backgroundView.layer?.backgroundColor = - GhosttyApp.shared.defaultBackgroundColor - .withAlphaComponent(GhosttyApp.shared.defaultBackgroundOpacity) - .cgColor + let initialTerminalBackground = GhosttyApp.shared.defaultBackgroundColor + .withAlphaComponent(GhosttyApp.shared.defaultBackgroundOpacity) + let initialPanelFill = Self.panelBackgroundFillColor(for: initialTerminalBackground) + backgroundView.layer?.backgroundColor = initialPanelFill.cgColor + backgroundView.layer?.isOpaque = initialPanelFill.alphaComponent >= 1.0 addSubview(backgroundView) addSubview(scrollView) inactiveOverlayView.wantsLayer = true @@ -4167,9 +4188,11 @@ final class GhosttySurfaceScrollView: NSView { func setBackgroundColor(_ color: NSColor) { guard let layer = backgroundView.layer else { return } + let fillColor = Self.panelBackgroundFillColor(for: color) CATransaction.begin() CATransaction.setDisableActions(true) - layer.backgroundColor = color.cgColor + layer.backgroundColor = fillColor.cgColor + layer.isOpaque = fillColor.alphaComponent >= 1.0 CATransaction.commit() } diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index f91576d0..24a7150a 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -4,6 +4,53 @@ import WebKit import AppKit import Bonsplit +enum GhosttyBackgroundTheme { + static func clampedOpacity(_ opacity: Double) -> CGFloat { + CGFloat(max(0.0, min(1.0, opacity))) + } + + static func color(backgroundColor: NSColor, opacity: Double) -> NSColor { + backgroundColor.withAlphaComponent(clampedOpacity(opacity)) + } + + static func color( + from notification: Notification?, + fallbackColor: NSColor, + fallbackOpacity: Double + ) -> NSColor { + let userInfo = notification?.userInfo + let backgroundColor = + (userInfo?[GhosttyNotificationKey.backgroundColor] as? NSColor) + ?? fallbackColor + + let opacity: Double + if let value = userInfo?[GhosttyNotificationKey.backgroundOpacity] as? Double { + opacity = value + } else if let value = userInfo?[GhosttyNotificationKey.backgroundOpacity] as? NSNumber { + opacity = value.doubleValue + } else { + opacity = fallbackOpacity + } + + return color(backgroundColor: backgroundColor, opacity: opacity) + } + + static func color(from notification: Notification?) -> NSColor { + color( + from: notification, + fallbackColor: GhosttyApp.shared.defaultBackgroundColor, + fallbackOpacity: GhosttyApp.shared.defaultBackgroundOpacity + ) + } + + static func currentColor() -> NSColor { + color( + backgroundColor: GhosttyApp.shared.defaultBackgroundColor, + opacity: GhosttyApp.shared.defaultBackgroundOpacity + ) + } +} + enum BrowserSearchEngine: String, CaseIterable, Identifiable { case google case duckduckgo @@ -1429,7 +1476,7 @@ final class BrowserPanel: Panel, ObservableObject { // Match the empty-page background to the terminal theme so newly-created browsers // don't flash white before content loads. - webView.underPageBackgroundColor = Self.resolvedBrowserChromeBackgroundColor() + webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor() // Always present as Safari. webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent @@ -1646,7 +1693,7 @@ final class BrowserPanel: Panel, ObservableObject { NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange) .sink { [weak self] notification in guard let self else { return } - self.webView.underPageBackgroundColor = Self.resolvedBrowserChromeBackgroundColor(from: notification) + self.webView.underPageBackgroundColor = GhosttyBackgroundTheme.color(from: notification) } .store(in: &cancellables) } @@ -2418,7 +2465,7 @@ extension BrowserPanel { } func refreshAppearanceDrivenColors() { - webView.underPageBackgroundColor = Self.resolvedBrowserChromeBackgroundColor() + webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor() } func suppressOmnibarAutofocus(for seconds: TimeInterval) { diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index ea282f33..1b46904c 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -231,6 +231,7 @@ struct BrowserPanelView: View { @State private var omnibarPillFrame: CGRect = .zero @State private var lastHandledAddressBarFocusRequestId: UUID? @State private var isBrowserThemeMenuPresented = false + @State private var ghosttyBackgroundGeneration: Int = 0 // Keep this below half of the compact omnibar height so it reads as a squircle, // not a capsule. private let omnibarPillCornerRadius: CGFloat = 10 @@ -275,17 +276,24 @@ struct BrowserPanelView: View { BrowserThemeSettings.mode(for: browserThemeModeRaw) } + private var browserChromeBackground: Color { + _ = ghosttyBackgroundGeneration + return Color(nsColor: GhosttyBackgroundTheme.currentColor()) + } + private var browserChromeBackgroundColor: NSColor { - resolvedBrowserChromeBackgroundColor( + _ = ghosttyBackgroundGeneration + return resolvedBrowserChromeBackgroundColor( for: colorScheme, - themeBackgroundColor: GhosttyApp.shared.defaultBackgroundColor + themeBackgroundColor: GhosttyBackgroundTheme.currentColor() ) } private var browserChromeColorScheme: ColorScheme { - resolvedBrowserChromeColorScheme( + _ = ghosttyBackgroundGeneration + return resolvedBrowserChromeColorScheme( for: colorScheme, - themeBackgroundColor: GhosttyApp.shared.defaultBackgroundColor + themeBackgroundColor: GhosttyBackgroundTheme.currentColor() ) } @@ -454,6 +462,9 @@ struct BrowserPanelView: View { addressBarFocused = false } } + .onReceive(NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)) { _ in + ghosttyBackgroundGeneration &+= 1 + } } private var addressBar: some View { @@ -471,7 +482,7 @@ struct BrowserPanelView: View { } .padding(.horizontal, 8) .padding(.vertical, addressBarVerticalPadding) - .background(Color(nsColor: browserChromeBackgroundColor)) + .background(browserChromeBackground) // Keep the omnibar stack above WKWebView so the suggestions popup is visible. .zIndex(1) .environment(\.colorScheme, browserChromeColorScheme) diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 46924d9e..4417cb13 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -998,7 +998,19 @@ final class Workspace: Identifiable, ObservableObject { } private static func bonsplitAppearance(from config: GhosttyConfig) -> BonsplitConfiguration.Appearance { - bonsplitAppearance(from: config.backgroundColor) + bonsplitAppearance( + from: config.backgroundColor, + backgroundOpacity: config.backgroundOpacity + ) + } + + static func bonsplitChromeHex(backgroundColor: NSColor, backgroundOpacity: Double) -> String { + let themedColor = GhosttyBackgroundTheme.color( + backgroundColor: backgroundColor, + opacity: backgroundOpacity + ) + let includeAlpha = themedColor.alphaComponent < 0.999 + return themedColor.hexString(includeAlpha: includeAlpha) } nonisolated static func resolvedChromeColors( @@ -1007,37 +1019,49 @@ final class Workspace: Identifiable, ObservableObject { .init(backgroundHex: backgroundColor.hexString()) } - private static func bonsplitAppearance(from backgroundColor: NSColor) -> BonsplitConfiguration.Appearance { - let chromeColors = resolvedChromeColors(from: backgroundColor) - return BonsplitConfiguration.Appearance( + private static func bonsplitAppearance( + from backgroundColor: NSColor, + backgroundOpacity: Double + ) -> BonsplitConfiguration.Appearance { + BonsplitConfiguration.Appearance( splitButtonTooltips: Self.currentSplitButtonTooltips(), enableAnimations: false, - chromeColors: chromeColors + chromeColors: .init( + backgroundHex: Self.bonsplitChromeHex( + backgroundColor: backgroundColor, + backgroundOpacity: backgroundOpacity + ) + ) ) } func applyGhosttyChrome(from config: GhosttyConfig, reason: String = "unspecified") { - applyGhosttyChrome(backgroundColor: config.backgroundColor, reason: reason) + applyGhosttyChrome( + backgroundColor: config.backgroundColor, + backgroundOpacity: config.backgroundOpacity, + reason: reason + ) } - func applyGhosttyChrome(backgroundColor: NSColor, reason: String = "unspecified") { + func applyGhosttyChrome(backgroundColor: NSColor, backgroundOpacity: Double, reason: String = "unspecified") { + let nextHex = Self.bonsplitChromeHex( + backgroundColor: backgroundColor, + backgroundOpacity: backgroundOpacity + ) let currentChromeColors = bonsplitController.configuration.appearance.chromeColors - let nextChromeColors = Self.resolvedChromeColors(from: backgroundColor) - let isNoOp = currentChromeColors.backgroundHex == nextChromeColors.backgroundHex && - currentChromeColors.borderHex == nextChromeColors.borderHex + let isNoOp = currentChromeColors.backgroundHex == nextHex if GhosttyApp.shared.backgroundLogEnabled { let currentBackgroundHex = currentChromeColors.backgroundHex ?? "nil" - let nextBackgroundHex = nextChromeColors.backgroundHex ?? "nil" GhosttyApp.shared.logBackground( - "theme apply workspace=\(id.uuidString) reason=\(reason) currentBg=\(currentBackgroundHex) nextBg=\(nextBackgroundHex) currentBorder=\(currentChromeColors.borderHex ?? "nil") nextBorder=\(nextChromeColors.borderHex ?? "nil") noop=\(isNoOp)" + "theme apply workspace=\(id.uuidString) reason=\(reason) currentBg=\(currentBackgroundHex) nextBg=\(nextHex) noop=\(isNoOp)" ) } if isNoOp { return } - bonsplitController.configuration.appearance.chromeColors = nextChromeColors + bonsplitController.configuration.appearance.chromeColors.backgroundHex = nextHex if GhosttyApp.shared.backgroundLogEnabled { GhosttyApp.shared.logBackground( "theme applied workspace=\(id.uuidString) reason=\(reason) resultingBg=\(bonsplitController.configuration.appearance.chromeColors.backgroundHex ?? "nil") resultingBorder=\(bonsplitController.configuration.appearance.chromeColors.borderHex ?? "nil")" @@ -1045,6 +1069,14 @@ final class Workspace: Identifiable, ObservableObject { } } + func applyGhosttyChrome(backgroundColor: NSColor, reason: String = "unspecified") { + applyGhosttyChrome( + backgroundColor: backgroundColor, + backgroundOpacity: backgroundColor.alphaComponent, + reason: reason + ) + } + init( title: String = "Terminal", workingDirectory: String? = nil, @@ -1067,7 +1099,10 @@ final class Workspace: Identifiable, ObservableObject { // and keep split entry instantaneous. // Avoid re-reading/parsing Ghostty config on every new workspace; this hot path // runs for socket/CLI workspace creation and can cause visible typing lag. - let appearance = Self.bonsplitAppearance(from: GhosttyApp.shared.defaultBackgroundColor) + let appearance = Self.bonsplitAppearance( + from: GhosttyApp.shared.defaultBackgroundColor, + backgroundOpacity: GhosttyApp.shared.defaultBackgroundOpacity + ) let config = BonsplitConfiguration( allowSplits: true, allowCloseTabs: true, diff --git a/Sources/WorkspaceContentView.swift b/Sources/WorkspaceContentView.swift index 0d3cc451..e8c087ac 100644 --- a/Sources/WorkspaceContentView.swift +++ b/Sources/WorkspaceContentView.swift @@ -177,7 +177,8 @@ struct WorkspaceContentView: View { reason: String = "unspecified", backgroundOverride: NSColor? = nil, loadConfig: () -> GhosttyConfig = { GhosttyConfig.load() }, - defaultBackground: () -> NSColor = { GhosttyApp.shared.defaultBackgroundColor } + defaultBackground: () -> NSColor = { GhosttyApp.shared.defaultBackgroundColor }, + defaultBackgroundOpacity: () -> Double = { GhosttyApp.shared.defaultBackgroundOpacity } ) -> GhosttyConfig { var next = loadConfig() let loadedBackgroundHex = next.backgroundColor.hexString() @@ -194,9 +195,12 @@ struct WorkspaceContentView: View { } next.backgroundColor = resolvedBackground + // Use the runtime opacity from the Ghostty engine, which may differ from the + // file-level value parsed by GhosttyConfig.load(). + next.backgroundOpacity = defaultBackgroundOpacity() if GhosttyApp.shared.backgroundLogEnabled { GhosttyApp.shared.logBackground( - "theme resolve reason=\(reason) loadedBg=\(loadedBackgroundHex) overrideBg=\(backgroundOverride?.hexString() ?? "nil") defaultBg=\(defaultBackgroundHex) finalBg=\(next.backgroundColor.hexString()) theme=\(next.theme ?? "nil")" + "theme resolve reason=\(reason) loadedBg=\(loadedBackgroundHex) overrideBg=\(backgroundOverride?.hexString() ?? "nil") defaultBg=\(defaultBackgroundHex) finalBg=\(next.backgroundColor.hexString()) opacity=\(String(format: "%.3f", next.backgroundOpacity)) theme=\(next.theme ?? "nil")" ) } return next @@ -218,7 +222,8 @@ struct WorkspaceContentView: View { let sourceLabel = backgroundSource ?? "nil" let payloadLabel = notificationPayloadHex ?? "nil" let backgroundChanged = previousBackgroundHex != next.backgroundColor.hexString() - let shouldRequestTitlebarRefresh = backgroundChanged || reason == "onAppear" + let opacityChanged = abs(config.backgroundOpacity - next.backgroundOpacity) > 0.0001 + let shouldRequestTitlebarRefresh = backgroundChanged || opacityChanged || reason == "onAppear" logTheme( "theme refresh begin workspace=\(workspace.id.uuidString) reason=\(reason) event=\(eventLabel) source=\(sourceLabel) payload=\(payloadLabel) previousBg=\(previousBackgroundHex) nextBg=\(next.backgroundColor.hexString()) overrideBg=\(backgroundOverride?.hexString() ?? "nil")" ) @@ -400,7 +405,7 @@ struct EmptyPanelView: View { } } .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(nsColor: .windowBackgroundColor)) + .background(Color(nsColor: GhosttyBackgroundTheme.currentColor())) #if DEBUG .onAppear { DebugUIEventCounters.emptyPanelAppearCount += 1 diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 80bde744..443cf148 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -1316,7 +1316,7 @@ private enum DebugWindowConfigSnapshot { """ let backgroundPayload = """ - bgGlassEnabled=\(boolValue(defaults, key: "bgGlassEnabled", fallback: true)) + bgGlassEnabled=\(boolValue(defaults, key: "bgGlassEnabled", fallback: false)) bgGlassMaterial=\(stringValue(defaults, key: "bgGlassMaterial", fallback: "hudWindow")) bgGlassTintHex=\(stringValue(defaults, key: "bgGlassTintHex", fallback: "#000000")) bgGlassTintOpacity=\(String(format: "%.2f", doubleValue(defaults, key: "bgGlassTintOpacity", fallback: 0.03))) @@ -2373,7 +2373,7 @@ private struct BackgroundDebugView: View { @AppStorage("bgGlassTintHex") private var bgGlassTintHex = "#000000" @AppStorage("bgGlassTintOpacity") private var bgGlassTintOpacity = 0.03 @AppStorage("bgGlassMaterial") private var bgGlassMaterial = "hudWindow" - @AppStorage("bgGlassEnabled") private var bgGlassEnabled = true + @AppStorage("bgGlassEnabled") private var bgGlassEnabled = false var body: some View { ScrollView { @@ -2419,7 +2419,7 @@ private struct BackgroundDebugView: View { bgGlassTintHex = "#000000" bgGlassTintOpacity = 0.03 bgGlassMaterial = "hudWindow" - bgGlassEnabled = true + bgGlassEnabled = false updateWindowGlassTint() } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 3a90c8a1..d524f4a5 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -1302,6 +1302,92 @@ final class BrowserDeveloperToolsConfigurationTests: XCTestCase { panel.setBrowserThemeMode(.system) XCTAssertNil(panel.webView.appearance) } + + func testBrowserPanelRefreshesUnderPageBackgroundColorWithGhosttyOpacity() { + let panel = BrowserPanel(workspaceId: UUID()) + let updatedColor = NSColor(srgbRed: 0.18, green: 0.29, blue: 0.44, alpha: 1.0) + + NotificationCenter.default.post( + name: .ghosttyDefaultBackgroundDidChange, + object: nil, + userInfo: [ + GhosttyNotificationKey.backgroundColor: updatedColor, + GhosttyNotificationKey.backgroundOpacity: NSNumber(value: 0.57), + ] + ) + + guard let actual = panel.webView.underPageBackgroundColor?.usingColorSpace(.sRGB), + let expected = updatedColor.withAlphaComponent(0.57).usingColorSpace(.sRGB) else { + XCTFail("Expected sRGB-convertible under-page background colors") + return + } + + XCTAssertEqual(actual.redComponent, expected.redComponent, accuracy: 0.005) + XCTAssertEqual(actual.greenComponent, expected.greenComponent, accuracy: 0.005) + XCTAssertEqual(actual.blueComponent, expected.blueComponent, accuracy: 0.005) + XCTAssertEqual(actual.alphaComponent, expected.alphaComponent, accuracy: 0.005) + } +} + +final class GhosttyBackgroundThemeTests: XCTestCase { + func testColorClampsOpacity() { + let base = NSColor(srgbRed: 0.10, green: 0.20, blue: 0.30, alpha: 1.0) + + let lowerClamped = GhosttyBackgroundTheme.color(backgroundColor: base, opacity: -2.0) + XCTAssertEqual(lowerClamped.alphaComponent, 0.0, accuracy: 0.0001) + + let upperClamped = GhosttyBackgroundTheme.color(backgroundColor: base, opacity: 5.0) + XCTAssertEqual(upperClamped.alphaComponent, 1.0, accuracy: 0.0001) + } + + func testColorFromNotificationUsesBackgroundAndOpacity() { + let fallbackColor = NSColor.black + let fallbackOpacity = 1.0 + let notification = Notification( + name: .ghosttyDefaultBackgroundDidChange, + object: nil, + userInfo: [ + GhosttyNotificationKey.backgroundColor: NSColor(srgbRed: 0.18, green: 0.29, blue: 0.44, alpha: 1.0), + GhosttyNotificationKey.backgroundOpacity: NSNumber(value: 0.57), + ] + ) + + let actual = GhosttyBackgroundTheme.color( + from: notification, + fallbackColor: fallbackColor, + fallbackOpacity: fallbackOpacity + ) + guard let srgb = actual.usingColorSpace(.sRGB) else { + XCTFail("Expected sRGB-convertible color") + return + } + + XCTAssertEqual(srgb.redComponent, 0.18, accuracy: 0.005) + XCTAssertEqual(srgb.greenComponent, 0.29, accuracy: 0.005) + XCTAssertEqual(srgb.blueComponent, 0.44, accuracy: 0.005) + XCTAssertEqual(srgb.alphaComponent, 0.57, accuracy: 0.005) + } + + func testColorFromNotificationFallsBackWhenPayloadMissing() { + let fallbackColor = NSColor(srgbRed: 0.12, green: 0.34, blue: 0.56, alpha: 1.0) + let fallbackOpacity = 0.42 + let notification = Notification(name: .ghosttyDefaultBackgroundDidChange) + + let actual = GhosttyBackgroundTheme.color( + from: notification, + fallbackColor: fallbackColor, + fallbackOpacity: fallbackOpacity + ) + guard let srgb = actual.usingColorSpace(.sRGB) else { + XCTFail("Expected sRGB-convertible color") + return + } + + XCTAssertEqual(srgb.redComponent, 0.12, accuracy: 0.005) + XCTAssertEqual(srgb.greenComponent, 0.34, accuracy: 0.005) + XCTAssertEqual(srgb.blueComponent, 0.56, accuracy: 0.005) + XCTAssertEqual(srgb.alphaComponent, 0.42, accuracy: 0.005) + } } @MainActor diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index 3f85abba..65fe2841 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -134,6 +134,12 @@ final class GhosttyConfigTests: XCTestCase { XCTAssertEqual(rgb255(darkConfig.backgroundColor), RGB(red: 0, green: 43, blue: 54)) } + func testParseBackgroundOpacityReadsConfigValue() { + var config = GhosttyConfig() + config.parse("background-opacity = 0.42") + XCTAssertEqual(config.backgroundOpacity, 0.42, accuracy: 0.0001) + } + func testLoadThemeResolvesBuiltinAliasFromGhosttyResourcesDir() throws { let root = FileManager.default.temporaryDirectory .appendingPathComponent("cmux-ghostty-themes-\(UUID().uuidString)") @@ -529,6 +535,84 @@ final class WorkspaceAppearanceConfigResolutionTests: XCTestCase { } } +@MainActor +final class WorkspaceChromeColorTests: XCTestCase { + func testBonsplitChromeHexIncludesAlphaWhenTranslucent() { + let color = NSColor( + srgbRed: 17.0 / 255.0, + green: 34.0 / 255.0, + blue: 51.0 / 255.0, + alpha: 1.0 + ) + + let hex = Workspace.bonsplitChromeHex(backgroundColor: color, backgroundOpacity: 0.5) + XCTAssertEqual(hex, "#1122337F") + } + + func testBonsplitChromeHexOmitsAlphaWhenOpaque() { + let color = NSColor( + srgbRed: 17.0 / 255.0, + green: 34.0 / 255.0, + blue: 51.0 / 255.0, + alpha: 1.0 + ) + + let hex = Workspace.bonsplitChromeHex(backgroundColor: color, backgroundOpacity: 1.0) + XCTAssertEqual(hex, "#112233") + } +} + +final class WindowTransparencyDecisionTests: XCTestCase { + private let sidebarBlendModeKey = "sidebarBlendMode" + private let bgGlassEnabledKey = "bgGlassEnabled" + + func testTranslucentOpacityForcesClearWindowBackgroundOutsideSidebarBlendModePath() { + withTemporaryWindowBackgroundDefaults { + let defaults = UserDefaults.standard + defaults.set("withinWindow", forKey: sidebarBlendModeKey) + defaults.set(false, forKey: bgGlassEnabledKey) + + XCTAssertFalse(cmuxShouldUseTransparentBackgroundWindow()) + XCTAssertTrue(cmuxShouldUseClearWindowBackground(for: 0.80)) + XCTAssertFalse(cmuxShouldUseClearWindowBackground(for: 1.0)) + } + } + + func testBehindWindowGlassPathStillControlsTransparentWindowFallback() { + withTemporaryWindowBackgroundDefaults { + let defaults = UserDefaults.standard + defaults.set("behindWindow", forKey: sidebarBlendModeKey) + defaults.set(true, forKey: bgGlassEnabledKey) + + let expectedTransparentFallback = !WindowGlassEffect.isAvailable + XCTAssertEqual(cmuxShouldUseTransparentBackgroundWindow(), expectedTransparentFallback) + XCTAssertEqual( + cmuxShouldUseClearWindowBackground(for: 1.0), + expectedTransparentFallback + ) + } + } + + private func withTemporaryWindowBackgroundDefaults(_ body: () -> Void) { + let defaults = UserDefaults.standard + let originalBlendMode = defaults.object(forKey: sidebarBlendModeKey) + let originalGlassEnabled = defaults.object(forKey: bgGlassEnabledKey) + defer { + restoreDefaultsValue(originalBlendMode, key: sidebarBlendModeKey, defaults: defaults) + restoreDefaultsValue(originalGlassEnabled, key: bgGlassEnabledKey, defaults: defaults) + } + body() + } + + private func restoreDefaultsValue(_ value: Any?, key: String, defaults: UserDefaults) { + if let value { + defaults.set(value, forKey: key) + } else { + defaults.removeObject(forKey: key) + } + } +} + final class NotificationBurstCoalescerTests: XCTestCase { func testSignalsInSameBurstFlushOnce() { let coalescer = NotificationBurstCoalescer(delay: 0.01) diff --git a/vendor/bonsplit b/vendor/bonsplit index c4b8f5cc..335facd9 160000 --- a/vendor/bonsplit +++ b/vendor/bonsplit @@ -1 +1 @@ -Subproject commit c4b8f5cc3def0a44c1c3634d4f358a66fd956606 +Subproject commit 335facd9fd1d81a3c71fea69345af30f7e3601f9 From 8378bbeaa243f93fa9a7a057052fc763d41d8f39 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 03:57:09 -0800 Subject: [PATCH 011/232] Add dark mode app icon for macOS Sequoia (#702) * Add dark mode app icon variant for macOS Sequoia Adds dark appearance entries to the AppIcon asset catalog so macOS 15+ automatically shows a dark-background icon when the system is in dark mode. The chevron gradient and glow are preserved by recompositing the foreground over a dark background (#1C1C1E). Includes a generation script (scripts/generate_dark_icon.py) that derives the dark PNGs from the light originals. * Add icon picker in Settings and fix dark icon quality Use the Figma chevron layer (design/cmux-icon-chevron.png) composited over a dark background for pixel-perfect results, no white halo or darkened gradient. Falls back to mathematical recomposition if the Figma layer is missing. Add an "App Icon" picker to Settings (under Theme) with three visual options: Automatic (follows system appearance via asset catalog dark variants on macOS 15+), Light, and Dark. The selection persists via UserDefaults and is applied on launch in AppDelegate.ensureApplicationIcon. * Fix dark icon chevron scale to match light icon The Figma export was ~25% larger than the repo icon. Scale the Figma chevron layer by 0.80x before compositing so the chevron size matches exactly between light and dark variants. * Use enhanced glow for dark icon Add a soft blue bloom around the chevron on the dark background using two Gaussian blur passes (wide at r=25 and tight at r=12) composited at reduced opacity beneath the sharp chevron. Makes the icon pop more against the dark squircle. --- .../AppIcon.appiconset/128@2x_dark.png | Bin 0 -> 16563 bytes .../AppIcon.appiconset/128_dark.png | Bin 0 -> 6127 bytes .../AppIcon.appiconset/16@2x_dark.png | Bin 0 -> 1171 bytes .../AppIcon.appiconset/16_dark.png | Bin 0 -> 555 bytes .../AppIcon.appiconset/256@2x_dark.png | Bin 0 -> 52401 bytes .../AppIcon.appiconset/256_dark.png | Bin 0 -> 16563 bytes .../AppIcon.appiconset/32@2x_dark.png | Bin 0 -> 2535 bytes .../AppIcon.appiconset/32_dark.png | Bin 0 -> 1171 bytes .../AppIcon.appiconset/512@2x_dark.png | Bin 0 -> 148367 bytes .../AppIcon.appiconset/512_dark.png | Bin 0 -> 52401 bytes .../AppIcon.appiconset/Contents.json | 208 ++++++++++++---- .../AppIconDark.imageset/AppIconDark.png | Bin 0 -> 148367 bytes .../AppIconDark.imageset/Contents.json | 12 + .../AppIconLight.imageset/AppIconLight.png | Bin 0 -> 203126 bytes .../AppIconLight.imageset/Contents.json | 12 + Sources/AppDelegate.swift | 10 +- Sources/cmuxApp.swift | 140 +++++++++++ design/cmux-icon-chevron.png | Bin 0 -> 498099 bytes scripts/generate_dark_icon.py | 229 ++++++++++++++++++ 19 files changed, 565 insertions(+), 46 deletions(-) create mode 100644 Assets.xcassets/AppIcon.appiconset/128@2x_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/128_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/16@2x_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/16_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/256@2x_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/256_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/32@2x_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/32_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/512@2x_dark.png create mode 100644 Assets.xcassets/AppIcon.appiconset/512_dark.png create mode 100644 Assets.xcassets/AppIconDark.imageset/AppIconDark.png create mode 100644 Assets.xcassets/AppIconDark.imageset/Contents.json create mode 100644 Assets.xcassets/AppIconLight.imageset/AppIconLight.png create mode 100644 Assets.xcassets/AppIconLight.imageset/Contents.json create mode 100644 design/cmux-icon-chevron.png create mode 100755 scripts/generate_dark_icon.py diff --git a/Assets.xcassets/AppIcon.appiconset/128@2x_dark.png b/Assets.xcassets/AppIcon.appiconset/128@2x_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7a4090cb708b5bca5638edc9630accfe8776c89c GIT binary patch literal 16563 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 Xc92@;#DxAc0t`*nw<`5YHc|fv)>EbJ literal 0 HcmV?d00001 diff --git a/Assets.xcassets/AppIcon.appiconset/128_dark.png b/Assets.xcassets/AppIcon.appiconset/128_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c5bf91f71fbf5fba8fc987a7d01b0860bd4e9ae6 GIT binary patch literal 6127 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 literal 0 HcmV?d00001 diff --git a/Assets.xcassets/AppIcon.appiconset/256_dark.png b/Assets.xcassets/AppIcon.appiconset/256_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7a4090cb708b5bca5638edc9630accfe8776c89c GIT binary patch literal 16563 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 Xc92@;#DxAc0t`*nw<`5YHc|fv)>EbJ literal 0 HcmV?d00001 diff --git a/Assets.xcassets/AppIcon.appiconset/32@2x_dark.png b/Assets.xcassets/AppIcon.appiconset/32@2x_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a952979d551a142eb37aeccd88e4967be22d3f29 GIT binary patch literal 2535 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 literal 0 HcmV?d00001 diff --git a/Assets.xcassets/AppIcon.appiconset/32_dark.png b/Assets.xcassets/AppIcon.appiconset/32_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0996a9075d5acb069d1fd73d38eb7cef0b19ba GIT binary patch literal 1171 zcmV;E1Z?|>P)~k=0AFfR(1H(AEp4=d-}oZ<(u(2->Vp(%3%>Y8 zL8#V>8mY8?6cqJA#6A=eu^@_?lnwR+YMW#?yE}95@nL3@%_f`7G(9lP+`H$V|Nor* z-??Xmu#LqU(qNIME*G0wdo)F?}2L~%#M07LAh^m%BJphp< zLE9Bl)%LSk5t#=5Qq?1qlP5pz#^@#>0F_Fm-&+097;{%U;%ZDJf^g1#4Q_k2TK%t+ zwBJo24E1B*_cmE;1Jakc+f<_r)bqSe*6J9rDF)X$P$I$Ep`oFBjqwjT=K>(JI2{wM zaPDHr1jd+*wQh8B^5j?>v#n5x=$dUrs$ZP32sCG*Q3vBK`Z<9K;6&KgIoZ8MhE>%p zML@%VnTMXGdBbh!%=tK^>2_PrjH(({9gfGYO`ubi#SXR|+yzu=-h4aLdk)eXeHtr* z294C!5TV$CxgZ52qW;p$h=9T-*7FD+c^=(y05`Z6*O-d6k@!_$n6EsY1f+LO1;Q5I z*?*CoptT0F{#J534&iTj3~QUH4G>AMjbwkKDw4@je)Bo{K6no)+krC@|7EKwy~l((-D|w677oCEN=P?r~@&mI#5S8e>q-u z4XO@VK@YvRT~Ep$+Nk0BFT=m*MM^hr$DC^bg6Uhs=|7HA{pl6L^MB)KN)f4q1QH#4 zTAw0Zw-NK;F8nLj!s!~4EfUr)F!AH7T=-=_o^xapfOiuhu4PYWC(yWmCvNLb@d*L@B6waR@NNQgN8}%+|H%8~zB!2G3Xm&;OPzIn zJ|c6cp8>3hj*FO^G*VELEOW<)EW*{sI)CtXbrJ1+D@hxbUOPek*w(L z(r4w9-piZB$SaIucFU^TzdyFB&Ky(?XUTZSG8V{s&$#EZy1>FvT1fu?_s9Q}!T-a- z{~yf&*AjiV95Qk*rq9*^JDLE4d{kb?UFDJmx%t?MXDUNj%loMA)gcN7JB0z*&~8BJ zf<$aqwKc&rU)(R0VQ7pP%UiJI*&1t6ez3}=z?)rM?z?!i%yUB8+K+H=K6Y^V!(!~f z_Q2POb$8N4yGw^_s_%Y$al!DCoW0k;YSJL_ZA**`rCDsv(3L%Dl|P)^UyTEX;_oi! zbsKC|rfk<-=rA2@%-m9HE}m>pgMlb7OOKFY;^(YOTRXs$l`pp#GT1_UH6iOvHsw{D z-kku{7hbBx*qPmDk)8f({5n0sZ#a;lo$?*t2&S)Q_I_jn}&nO&%*?^7Ou> zom@>oNw<(zlY}M$;7^F2tjC_sNW-$8TA|_(!?o0A&MjA_d4db5R#?6gM8sr>jNry7 z^^?FOcWUI%-OK?HBD&YNWhi0%>Sz=^N(p0mdXT(JgmZ5T*;epD@v z=etaypHNv|cpbw|PA(k@_jV-nVMyXvQWAdUF9EptwNrLXHPtkP+fGRI zGuvZOZIs6-~{Ax1+qVg4E=%-K$K+7$@VUSc0RPoZTp8P9Zt_m)^ z1_aasz5y>4q*Ct@k*;2g1K%0=pAhpXE00e}B3uBlhY%`8DyMvLvSI#w@{|z2$#8`d zQZ^7sb?V@VWv$|x%t%%;q6DZX%1fnm0+Ov4{Po))_hC1eBc`+>Cx#=-;OZD{{B9T{ zkaR>)3|RYDrU5xCQfko|NVFpPwn!H#!~#u0f<_0z)uvEeCK?BPH$msV3TWNB8)L#m|t$Pr?RTdwqx z#P}V$5AM~i1;=`y$GhRV|8n8w>`_b7JQqvqbmQek)f{tN~(VNLaq zC;+c9j|@KB(!Vbwl}^M8yrc_M!28cl+G!zxG7m)rJ^?MV8{pvr!SyeMpKilG zfG!y>3gHdlXuf}SVK8JO^}-GUfKmD<*dv*syp(S|P2?gZ6%WyCwR7tNIPci?9U3rC z`=Bt$!-Y=Z?_unZ^k9LG4;+L5rEMuVnCz0gQ%z48h3yQc?fiui|LyPho;v}~UdD`G zO9ZaOWWzI}Ezot8fDLW?=J6wb33_80up@HTDFuP1vhQc|dOxcjpulSK|2>jA4+K$v z{>CB*Z}NxG;pOIGKm-W6PF3Jvd?CwPR3jCqHu(5C$p8o~?F@mLao(Ei@Aft$Z7Tkf z8g@5IkFkZks+Y~?XAQb*6*9}&MCQR(Ty2~F>AJdt5}yiRp)3Zw0Ea<{+~Ob6!W$`(m%Awy zMmYW|QLFtWq#zNH0Wbq~fkJCVa&Q6eNS06giNP&T^q8UK6TOUCU)4i4k`gRnP4Z(& zv3DQ6lB}u@Om@w_n!~_Yrx2UZY)p1 zc?=s#jtpn~)4d!v`BNa0|H3Fe;nJWh^)d9g#*%(cjOxcilF5Y7ayRTm2k9O_qHo2HsLwo1?lWbvZ!n`1h3zbH%Sa`-_-uei8+V@e;c;*@H$=}@aj2&yXcbPlmEzw zUq4-!47^lKx&TbJLLN6+)VPnd4g_+YA|69jiOPnSz_&{-2giL0a~JUvk*l=M z$5t@uyORY}DXUq6-LR&9JQDC~lpE)>{it!Q$fdA6vXzoUxGwZI2!T$oHIUJoa>ZTq zA6}YLR_*~+RIOB)b`&Sj#M32o7=Xl={7*_>2waR$ffJmtrWCvaLz%1N^dXkZZk*}( z^rrsRETBR~?Y0TvH3)Gw#3s8=B(eQRdKuS&cHP=Q*brkhfX;GkSSLN5i~Wmsl$YOq zZhRK7bGGgj085J|LuUm;)SY&$BlYq>ARTkg@0Y-U=7a_DIiCjbbHL;zgjExxc#m_a z@3vTsO`Bfp+M#CLqiAYmR*r6!Q*6j(vrMB0nUoLdb~oJL61k9Xc_|3fNoDmfvd z<|8l}=0Oc8QH>Lcoc}S}PfldCyXfXSA8E;w>fja56$Y~(2x>(h!q4xbz}tE)5^3P&;87Y0 zF;|8r|Cy?iJS%ABS@KKhAKHBH19+R7){B7G zDkSR=Zz%tgnK)#K98VoN4%1qY)1>d>pM45M!%N9J0S_t9)Dp3qS($#t@B>?DA*@$(?-VaPSWEu{}B5-eCYn zC~S?euZd?KE0)6mGB#=S8%1`f>H0HsXN&gg1 zjTKfihmvE6NwD=ksSQ}MoZkMYC@XVb*vh>RU(==7@%3R8U{q8IfxIq2UmX?)D0r-z1qj>D==>vvbGPBwo~7c{^6i;^(N2( zlL9!pQwM%PrCaye{b$~AK|lhj=RhQ74>~g*vp<=<3fQfAI1^71C6RJ~m1@Nf$qQg` zFHt%MBhI@nA@dCdJ{e#~-e{r$*NKA>MTW7X3dR0U(-8aTyZw zxY?sw;6Cerj)^~OMlv(z%(NUx1E&MnX-NEs1L7r*{`p{ae`c^GO_3c+_#lJ7c?n#n zqXTA=Mb@hip%`(pr0>i>IPEsfz?(8sF90)KoIty@iwwyxa#Q7>m!9<9g#!%fPP$R> zv~bnNmGKmd*LB#%b;+Ugng7|wBT5uclze@knF&I%`be<(48n&0X}Xb)}bODs&qOc8vZ{1_{BE_~@P6^x!%1I_~@rIj!L znT-S$7XFnNmPkXe(nR2u(hEMl+F8c`#Qp5K)h0xwvsSBD=?WtJuHd?h3;5Qhfi9)1(PgTjR5UE^I+v4oKDrBm@5Mr^)pT0VD)) z^DMXyll&R;MdV;q-HDd;Pm_1!APhXakpzJ7R`UFL2n6c+)Bt7|r$!b|`e(NCu{y9K z&iYRY&v+oaT@NPNUM;0NE^weaiK_x}j|?LZNWpDO(qXm{S1tBB-c;MK{}&xmsj%Io-1b(Ue+HWXJJ{EAld}@U93A7R=bZ?cEAYP0y*X>v&s6x6bz(Vgev(y~)@COoUJrtmew_mj&@VozVOq}Z)VCS%A)$gSX zES!=97UW&^jT3lB^V|N5Tu3{I!q|)L8L5mwxk2?J1&-N195|{S+Sg*vud5zu*DYQF zGypnSqk%q6^U`RB%vJ9@ilkJWVc6$g75AAVuh;ZwlI}=zzhz8dea22epX&e<2&Gk% zO!;C0$rP|7uqT6-X$J7fjqYGwa5$6#9DZ;7z6K)H%B(%KQ=P%}s19su@K;No;Y+5^ z@0Oy(FKMlsHVG3do;Z)XjZ8rXjWcYs;ssALfeZ8yG19N4UdbPjSg$*?@J||*IlbA4 z>f!r))02d(#>?dZK@PM&y2!z835%i4gGjKD2-UBmeRy=0emw%X@%S6JZTJ-62Zb|V zydJ-PLF^C>#3=-kXF+mtvcn{=Wdf2fsSF_awLBVgN{@q zc$7VmRdC-E273w&e4%LJo6z$>7qZ1Ai%ohIe&iA+YgerRhKv|n3MaBYsDsqU-*XYjpD_aTo0M#2YqvNU zv3J_4c`^Yy*k7^Nnj(NxL_a3z(l8<7pv_3o2`J|GdoF!WbgdFt`jw7dASTIDL)GIE zGsqjk$|3#%W8n_s5EhUUur!9Gf<{W%P<0Aqp(eG%jQLAdWzB=LkE7wY8IG?gYY+gy z=|;F1Y=Qm=Kz~Ug+|`+{$imLo-Vbd6bGKg{x$KhfEz-FT6cGNiW=nSBB@2v~4WVR#5lDU>BSCDsZ zi!40r27D2aFqmmx6)Cydb&z7IU+D5-Ubju33?ToaQ10a(rTvug(I>Tq+Ed#ezu%!owHKlWtM1dO= zmm!mYU!WoFs-$0Jz-(*`Ce(Iy!RHxf=r4<3^ea+Ls_xf&{ctGQ#CPNm=qEDK8_RWM zfyJ#YW*M&{HmrsP`(+>z@mw2o=S!w6`Hj|UdahFLZCpwGlD;$jdwXnWL>oIfgk9dV z(GW+I&hF>T+pzL?7H_6R6|5^x)^arjm+O|L#Xeo8zAZC@8Bw7z1S;|?zGsG{J)vVbg-}zFLiuvBf0FgKL zQKX^D-%PN*Apj*Ba|obYIVO53==(g!j9}|;h|UgpXF<40>7n#LvZ1pMLdei$lQ4_}gkLuFt_z z`}TUSRy_)%b#iY0j&*Rz{~X|rID)Q94I*)y7owUHN{Zb&uQeXTIq>kF|L9}Cs;BfGBgCH~QHuHNg4LHvZNJ=> z_pj)X+4?3mbEL?J*wVGq9l`W2k0F`jZaN+bE`cMKA%{R% z=XU9CHIZlN;0cL*qWcYRf*;KXpUzIra$r%}3E3&MvFIEVm?fc^)Tr@&tmOt@<*qfQkJ`mO7V^$Zyx1^YSd~L2 z=4xuiE>jw;nFIZSiPEGhX6Qlhu}EZ z4pB~ywG~4Wy7;UU` z-Q%6z2Hjv`I)TqWbCAEyCc{2Peo!M0=mKQS`w~p}l3ru3$TwRjwDFuUjZ{HGB>xNM zyMDCM6h}@&UN?!XGsqT@gWu)h1d8=f!xY&iT{^D9aX4|$LA%w+!E3K3mKBPlNzT?% z1rp6br{3x}Kk~wBaD{{`5107rnX%jZ`43jJwD!a>vuTICoswW@W8-Jj3veFv!x&WL z6ug{;5iN`b-+EIxXy&c>BYs>Av3p9i9`LeBQ3;u(^&`tQJV`; z*8W!qWqR%=$bO#5roq<&&zp9e)kg%~4T$61*tu^zkJ2K?)i>}pOaqcDzijqN1j70# z`OrRE$OlsmI<8iO=lQ;q%H?ihj22E#Xvxc=sSZKLbRkBTk6x?}nV^Eh+l1HabkEJh zUtH0k(T1A0N1<-L_RwjFqBK59K}q(gpW!fF>i}gsO>BylidI++S@&<~wyzyR1$WD7 zl0g#zZYJ%$uSf@2nl-}*IHlOo-gld)WAE~6M*I#CKCy=aXh9Z1DS8|#O&PA-p!cja zRfH<%W7emSH1=|GeKzV@Ct%~$Ji_9}`%#XARo71Hq<7x0P*uOCCQct*r2k{=HC@*C zhePrw!OzH}@cMaG3e=P4Bgd#5u-!bebt1*r-fj195Z!|gPvfG;ZF)NVx4oC+Px7V+ zuNgij{v5xr`h3u-SNn}Je(haeg9Ucg-F|mklKk@s-}d!|MrW8cpEn{*jt>8xJj~|+ z?R*V1Rzpg7raeCwTsADNjQOelXL{3 z_j0j0WcfNAuzZ~%u4=m2g2n{biVI;l3$LPWKK+nl)@+e2*z?`Zk70adNFE_F^oCBF ze3m&-*oUA`!1PY^Bw)qqZ8jeat>1yln#XRprf#Fp>Lrs69_v@D*(xm}bC!d`MB}v|Ss&cub&@#hWN+ zVStppwJi>nC*oa^*wUG;MkX-qZt&nYE}pSNNr6$QPi^mV4;6_((_)hjw6fCp9d)hldyXs4lT$#Nhx$S-tU&s#4>va-UzhV}1 zkltwdr>1+Jk`?-G-GJ7*>XTbk)+~fJPn|PzRopVYEXG~H{c#|>w~ETNYUNVtnm9>i zZ~KtULD=&=UJ95v&Fl0_Q74TnSPGtbHYnNxXqaSYfROgyjEHMjrT$gi?B-+8O$Jw&KC^Hz={p<-{<*S@Q9HS+lr!uYj_$tnNPP@ctiD z(ygHfjh32xftP6yM+Yg{EWseYS@u=Q7xZ_t$UhiUAumtoMJ*ON!}NDyUW3oxBY!?yi2xxWC^a9Fryasn2)W%jPfBB$DB9Prd%EkObYXpFD3hbGb1x z7^gPxx%BYClEP*%f=yG>c<{DzhBYLr+WU+C_y2SR3ev03$7(J=M9#9|>eX$rhc}q% zaG#Ju9o6S4?={1{FM?ltrzn~_xU?}QsKiCMc^uWiw;q%fGo6`^n(dmMOoPjfWDecG z2}{;^(60xMc2il5wklBIv;Ks<@ry6oqhYJBVu4}x&PC1r!;8XrQ!0JzLknSi z#X)gvb$ApDD4s_L6P3lhVgoSZ&9-hEubpAL25RDIl@!bz$C(YMEHFfTnvkp{2i#5Q zVVc(9J{mR0b3Y{gyjhn@KT)l4*ik%1%8*{lsS zHyW8C-+FU*phP=wgfE+Z;pz?0fmRBE(eDD%O~=)B;STnXV&VCEsa*KF%4}|_CrNC! z6e04Qwr?9Cd5T z=Ul$i&grQZLgwdZcg-LCyoXQ2{iaqRmHUnLq#mpnSlN~UY#*)CrjSTuAu2iWE8^RcekMo+sY;LWqR@mK_l3#p+ty`~ z_=H^BOXx@YdZFN_bMBRI^PHbu+b~?5v-HLp`l~0|p0i`s04|hbH-MIe3b-lJbTn+r zP@Si*oO&amvtGSuG-A>TloQA7JwGMI377LhrP82ju!cfT;V{|AQs}2C&3gZ}6MuDK z#S$oh39w@Spf68G2VA!`uB9P~UBaIR!WPhlx+mN3lLqU}**-kJ%14gV@d}i1rmqZ_ z3NZPtJ0YL7=J)$gnY`anJ+DW8?M~6Y=ek%GZ;3i71>dbf|8p5~>y=`iIxcb~QQ8&V>854;y{|w$m_W8)+JqMEk;*+D|MWpBjaNvDY6u*c;;I|S3 zk*}wYM&%}n&qon47iZ$s`Xm-Kun3m#z_~)()Yh4VvorM%R#Q7hg9d$g9LnIA#m;CP z=H6ek#!^j0X|NWSGPRIzS=y0dswo$@(+=SS;^rn`l_+apdOH;^-~Zb!pa4xD$mMW!ghNqEbMXvEtb4Yu{`4--8jIC9|mJdmS-w-5YQ{q@pO4DYSQn-cY>k+?0NB*T5{?!Q9)X4li4r^GyLy%pa zvw$)j*54JyJ*j@^Gz9dWE}i5fIanfr1>=W2Q|J6cRK{pf>O$>pQ#^Vb_;|^20=pwCflxK9X_*cmBNLEx?8qtvI3k zwN(c}W^jq-d^^}DdF&BqB3<-9hi*Kmj0n3+3++uL$fNw0l=vNPUlmK|(&X%Fe%L{a zfLf#R`|&$3o^wbBEZ6hpsee(R?z{NFkS(#-rGJF4ahY$M(bk>eibU||#<9UFmTc4e zqMw77N#whS47y#Pd3?S`^OL?)b6BDnF%%f_WNg?h5DjQuDna`b8%s@4nTUp81E_>- zAOrqFdg&u4v(V)(!1=~^=S-Lg8|;|S+tJ>7lf4d6P~S)a~-qX)GT5aBbGWD3=P zur(`d3h;eXgw}af-!kIxh5=I|Gnp@j-A)d?M3v;?VUDZWf%eo*ZVyhIvkb)vf{zAj~ zTyH&Nst*n2w7N$0eH`!@t{qt^c%AoABbyxx)vd#4{JDmhlHvK*15TYk1ay)8V~>UBIAwpNU>QC^T&7?D!`47+Ow>zL0j1dg^Bo8 z$nrf6#%b4sIkDbQ(59rBrb8?z3wFf@Hr|Y9?tuu`z2&(2+SLTeB0EZ|2}2A7?GJ~j zEro5_h@o3hkz_rsjs#ku4$bw}Q*u}L?^xT$M!`R1AP&4qFOtvYwqlI~E-Y`}>6sUe z#M~PAcHZZ6cLmcrL){Fg_Hw$?IGV3y8Lg1Ok-EvvebDTVU)hrcmi&=SP+s=pihTZc z%(p~tGmDRy#RpEy0)Q5K7S)vv?Vt@tcuG_ZveS0tK$#O`%1np81%7e@AB%1+=^*|l zusE9N4p&EwI|oL(g*PEGA71+ktvqPS<5Q?UZ?eJGOA~IHKc3&IU?q;Xf+B(Tr@%0? zN1OY$A;p66O@OuJ&dht{r2Q$cHBDnRv(Z1329|?q%jj(3iaN-@WvsWHO80LJgMwSrk6l^yQQpv3<8GbHmLdkzU)Vn~dD_~Ba z_;6W7YQuR1`yeL+HssAmhZg0;>rhS((KPBHiV%&*HQbq-Uh6!eP%}?EW+xK{;&`%* zs}d4AILmI=l7e-k;li9~aR%7y^RxXICaks#X^J9sQ-9kK{63VFM4TAZDALX$QL@wFRJSLj_~@jl&;4-%<4o~72q(_3fC6S zN&h#wi#S`&9W3~gSxdg%V98YW#$-ilj@Wo0RB00tH zcYFe#U`!_F3QlGF!#BMQRIYx;T)?=E)E@N{?k_5{X{<Li0BUxG5Zn_7&SY)%OF@>-)1&0)#3<;{tC&kiI5&SSGgx$orI;Rl zGTYDqIOOs6YI3|$9m^&)Rz=`7TJfepId@0ly19ja-#imLOj zl7qOEDKdf2{X$WVf8u$E8ieaA&QU4a19x^mY~J{4IqE7q_-4;1{xC2pc#3Qcg zPp2avO=5>a!spO433w}G_qb1M<*&;3z&ORpgAc_;;M<$(s7N?F`@Tj`C{f*5t***Jb%V5yQpfi&A|ySXO`Tf$Pjo##Jfb*g4qw3h?Ek#}x$XeUI& zQhlLJmed; z%9YsR{ctGtkaB)wIL~y~#+w?d{6l&&HLTUdmQQ!c zI2r}t`dOvF`+CcU*ZYJ|3^o6qgkEyWxDswQPxOnOgtI)-Z7JS#Ij&j1oaw*OmzISE z4GAh9e7A`j>sDv?>Of#hz%yI?Q4iBbL(goX_IL-x&%eS$HZDSq`6eTbR*UTdf(9@= zbhxbBP+C-A0Y5K?xwk5!ar>|Locy`Is`xVnBTonAx!g`H2 zSwrhQbn#5qt(E;J=(U?3Wo~lk4W6^|Qfk&kh^f!`wOm+O^zu`sS?);xGfq~et5;fl z<6(DKy9aP7@xXm~hcWtd_bXt_P{l{x#O`~4tAWIwA3 z=g-RUIm!o52i1YH(_rF8iIk7065K2bTM}s9V$?CMABN_*Byw~A{nal!k^yghSrOqs zHlnf@U&?d#!Rd`oS-w^Pyf`vp^wqQ0SAv$xK}rqzmvAd9*w$y@khjPNm@_S>=X~JQ zo8Syf$zwr(qz1)KAOZfmbwkc|+lODmZLdj~nOv60kIssnb<%UBaJCs5aRnt984umWdFxgkI1=1f7&)cYbVB;9mnjoPal!tJlU2y}49QztQ5kPCZ8Amf*MxB*DXo0Sl5h?a}=*SDZajig^(nHEFMlVIXz&V%vZ(Hy)#KRI; z^#QYI5(@myr0j#wkJiJnO6ji=7^F|zLYcL>1Lnz0uQVsDLHZ>d8R!|kPjT<%lQ^`Q zMbWvK@X{0~@N{Md->ttHbs0;b>Yu?llE2pACYvo8-uIPvau>82U#9)ubnto#R$^+z zQj3&1%R+UJP;FFS;ZHajV}E^_H*=;^LfA>H&iR+Y`H+Ramld8b->M71^v4xXz+Z>A zosjr}(*OGONu5_Pr6J+Xe*Qs-pIzWOU+~a2t^SodCoo7u7k0+34712CZJsutWdQds z!+d0>2%l}UyncHMp6%jo6@s70gngp%9_#{2D6tnH@7sT2AeF-5C_<=BJHWAD7`<*d zyWJG2n)(osbiaV3I_B)HWeetX;~PpO;jxt65q&8t*`E(mRm;=vu_yHorbvmji;0ol z((v4GsPk_hTojnj_IlJ~^EKSSRSE@IQ~qY;(9Kcb73ZkhaRDQ2DCRPD5{T4LoMYQx zEw~P($5Oy#DSWJ{4jI(`d<673U~9jEeT;=J>znnhT@2S9Mx<=h3oEsya z?4KrE35Q_eK`TnPR3Bc-6jU&iSeCk=?PaXjop;AphDCev=+t9LF|t9pvgZJxXJZz&~E{ zlvRSCu36@u{>|d^%X{gb4TX2W%G0zBgt z*p};Pc(nF_`V<`oyd@TS`SC>wsjJ`_1q|j1UBp^qf4auREm&u#<#HL*t_< zE7M3^F)iey4OPCf+tLGC?F@QDRqy>2)=%->%O98c{QgCr$_tYcQQpy>eN{STCpFUM z%@$(3_xxVM>p1dhB=-3Ss%%<9=&847qZ1ix_A>Ry4&dCMrPL4Xa;nDjG&NSAc8ZyX z`dEKr%8%>h37!4_}^Ut+B&>R!f)T5LX(ES#UpWJcD7`@x> z|Kd#kLRUDcgJ+n}v%SmeD&O#PFwPS1Kh@sv+BAWqQ3rsWiH^Il`Zj$C~P>xgq}m|!LXhn=A=I-81_6&+LQ|T zL>BCMs~`>e^EsgO-paPWO6F&2M6-^v*J{ot;an#Y8XY)<4#ldJIX{z)umFk?8f0O~ zDkgi+)EvuuzH0Q}J0uRw)(g%sG%s|Yk9M)Yl*p}MJY?zg=DT|XOqP3c%J@TdYRmHA zSSHc<;`|347j0Kvyn~HJI~*__uHDJ7v^&_D$t2ad>)a=qYHo_;Bk>*_I{WBVQ{5{v z8ciJAe9zv)d{%WLFQa(KnS@LjnuGy8hps$(&bNTe2RJTm74Dj0MVQnY9eEC5kG?vZ zl#uy4AD;_NtriI>w?~2-M(x6qubZr|o;A&fX{cc;?t!>)#*R- ze*$LjfTyG?re&qo*JL&{))sLL2dX}*(c?iUKi7L`@=YTqI ziAOnnQZi25%TfdMh990A?A65RL|%W3QF7#xpWMvH-uRliR@hMjeLkc=UP-47IzHnp z@ungT)tr`26+o;>ua`Yc1Z4#kBg&Ko!<2^4${GLpR(j|~rMhu9r)mXTyYn%X!O9=~ zU@L%~>S$)cLT)CE^btl_$bpfVkQd0JDa<7S4`Vru{J{=c@TF26s8$cMJs>XrR&tiZ z&y7h+LD2AHTwb}0t?i_!JSx?1LLW-?k$$>kTM!2*lvjJ=gIyQ?DiLbiCB;)A)oeey zC8_y-Fv$5?h>|ZjCjB`>{Y2)sT55appzIrBG6QB%QsQ2Ri)`5TKoT@#Ez8u@ytJgi{3h`rYne7Y(1ZXylr+j9_qkzO)RmG4OlkX_`AQIwPrcF_RbBTNZilP`tn#--ej| zI%9uO=#Jm{Q-avvZukjCiQxqb-7D@Oy ze(sW7o*%x>1upUw7UY>`kl%f^UAG@v#xt%Qti>3Y)|*V3#{Feew($cjHv`!dC&*)N z&nZc=stc8yPZxcK?WVLogN`tCDgj2;-f?`chOY^ZMy~`Ycoku2=WO8Vp|oPNr-1PL zay)jMAb>$!)U)O0_btXs8@x6XL-ON$dGarwhWnx{u=E$x6a5g4(P zr-upwoWt-Pp394r!E91mzAwsG=TrrFo6f^uzfX<=5>*lv=yO!2J{Yf6Pw&LlGMoOLj~?Mx-gl2h zawKVNFt{;DomUb{t;6&q#W@kbey|#XKTzujPv?yIjhQ*l$~T;(IG1on#`yt=R--(l zY5Ru4_Teir_AACwr>5XhSIKP4Fp#5}iQX!HIAqG^#SW#`6t4_CYDUe&k_BU`RdVFgI_~I-QhHw6%8$oMH+t7016==Gcoo)5h|m{ zu&;*LnhK(I3QEe9T&jZvHU6JFf#YqGUBE4_lFEB^35H>V*xJ`l;6$+!?C+Amq;6ZL%yQuvLvxsU=p6DH~cfE0CDlwh6Cpa!>guM3?1POC8LTW zLx*x93xVy;bK@`7e~t24yuM!QUR^Zmrp}4>KNLQxV)SI>D(@#^d!pGxosi=Gu5veF z+IY&!`Sc0o35uqyO1k??QRy0MlKj=1VyDIrP&j}J#sNo*HQ}ZYTTt72oTr{m?v{+o zh*IN~#q6K3D1Ng_)3;j8YrHE5)_sUN&55Vjg(jlS8D|+0C&B6gC?iY`xvGKLOY)A3 zD?gf1MUbX`bCMzX6F4@v8w!Z#`Q`7bqa4RnvJk^Te=L>1YmJP$$G7ah^WS9?M_%I7Pf8s+q(8bAwy0pqFO)JI(bw$ZUNW>}U zv&6A;kt5E8*9lJTd z@BNp$1s&Jui*EaBY4A1D;S_ZHt_&1`g+pfx=Ev8smw+=>$er3e@*5iu2tT^f=}_+` zU|*CE^B9Tg)z+qL69z0;F#01T0_3~JMo69f0mN+mU^5-WYyp&mJB0EL#p@!Pe2!h? zknmgoH#T2r369V%QRv%Uaqp2JXI?7)ndD8r=?V2|lV&Y%fnDw&Z&cUiZ;wW-?n^Sc z%YCT2J6&{>slNE8#Ci576|vY=n@b;K#7}wm|2{)STxl1z=0e-C0tT^9PWgJP0n5#L zjBC3bjjK=k?C==iUp*89sz_T*edC@H2tJlespqz}B;9D1#vj{&~_Ka`@%+ z`cv?(t4tULalkx*2Ks;h^MIKt6-FKoK| z3}NegThpr=QGn}hf!pp(behm4+_OUA;*7?sNLoCHL`XTe)Tq*}KmDnhZE?#jbGPh6 zSf0hOJe9m7OgjHjWAkaEO1jCL=kiX&ixp=cucN6ijN%^r0p(^|Vmy*0aK+E=AXLoc zWS*}C-{;k|xY0qexq{lOHHE)9M6RS((cIrSIib{$h?EkcTp0I0k*0l`>s0ph8B=pR z3{2{Id-)Uke2<4>{7AG2?YRfJyVo!*G7XEY>r?Qvn}#A5a5IXJyp?;(ZA78#e(ACA zW9XqSp_YgO^N2w4kqv(zN_z8BN(=gIY2P$<|AV0klsj!Ld{%LM<|K1fy~Ne6Mwu(D zJ8=Bcw{@w6C$JGj<@8YQS)qGEcRV#W-7l}0`|lW6w^JJ~&zf|>;$t4)d|=CQ1eE5V+vohKn{yWhaV)NBhFtQWKcqA2>jn_-KRI)IP&6kJ8&Ear!D z6h%`y$y^U1X94fgL;UK!qpZJ(=PrS2Lx|?3$bG5GXDe(4N85e=$&z<{`y1Ueq1`@X z&x*t+ul`IFzaHLjs|?uS-Go+ImfWsCz9nv>zU34&|E)OnG8NplEs@NJYrtWKVdRvp zK$UXL<&IoFv)q`Ej$BPIsGiuXlr%ofeChp*_b#?-oSS{8F0?oyj8{1aSWJ_|(oF7U z2V?hR)2k1~@MGdmeB>4SAlc(?$_C&l`$0wH73(xP;yX*ofulzdNqbH1ZS6NKCGZ*gP*HV@=68X5l z^x1n2F(r_Sl!L$^gK$VtLEz+1@PZ-OJI^*Z9`Fatu&c1jyY~IBmCL zeD&;R;_i)=sJQLS|Vt)jFmK4sW2(IEV(B$(9Yxx}{pa$nBRZ7O;6GP@(7 zJ!;3ag-ft5c}D$gv0M4za@Tu+y;9Jmo?(S}o z4(S-WTT)6oMN;WOk!~fVOA+brP+Gbhlp4Al2HxZE|9(23=bE$6v!A`zy7#|t?B=8Z zS&<4Ho|*@?IzJJ?XMm^Z2m2=okyGdAsxbx;1z+wW%zaS$-o{Ai^kG&8u@iL{Hr%F-N1O zmZJ^k_~k_#`UpRM|9F-li<<*8{q`ig-W+ZqewG-)-S8^xJIg)QB!!$1auFm}OxRsm zHm^P;%yd#lCVI;j`mB=tJ`t@En-C@`Qh4Mc)l{4DB(z=itA+jF?VK#bq!YA*zk&(K zQ&JEAHFP}WwJm$1+mhu56L-!di}E)*kImiM!zWrBa}G`cI&FQ@8CDfIkJVoTZ%KB` z$Jwt;K&)|AD*F~IKa^7ETDlUM>SVReef&`I@et3 z;pyhDO1kXklgOnupHnirf5D*H5!AxR{{#!kz(OL=SErAgQwLKWhBq7_lXC-y@0Kx5 z;|Nuk-&cv&wB4Q6h_&n;m6g_<5PDOPD9@E4hMK zk3XFo-gw)Op4z!jCW$dqc|4d_<}W_X4iSG3Z&yH_udWww7O^VxQm@+(ThG7vC= zR{~AAGC2;RY zRRgy@KTJlRe9K)BeP8BYnv+h@B~;G)Qk2Z#6`V`?#HMg6s0DY@XS^E;n9NUv9L?q3 zf98F^eD9ri9NKHJV-47N6NB+~NOgv=@bxTUJX=TuA;5vx`p*6(dyxXO+>dJ@Xq^}{aKQa`qr7SJ-x2X{BPL{H3K(tjys=K( z#1P>sLBR3;=9b!5*2kY`+V#6zIQ+cUAM3y(&aEsm<{xIJ&lo`uJieqK?d5MEel!&^@{%_QOb6Ydf#Y{-&_J5|g5BCdL zFfBZ}QIX<1WNmpkoX>+}#s5@-?QFzDf~}w?;<+22LxRc`8XM*(6ImC7gZF8b5+YAS*bfTGy^Km(_VG4M-~n;b!^eXur33hcll z*cLS~Iudir+mxOK{AiMT%w2&(&?w6EU;p_q*xvsN`Dd9GNssAd4$HDJEeTvUe5@Y> z=WUpg`=2Uk%iZ|wUAlL}**G%}5!HE$iD^oz3*RQ9KV1pIIrLE<;wySbmvZAUw^Ubh zpFgX%FiS=_F-`VndpR$QnuZx#&Wlmgv2+K|flFD6)|9WEEW?g!AJGP;Di!lP=RcT! z>5|sO*&)TxB*Z5j9|uqNZm8s9gDW)hFhZ(`3dlDy(xV$M zCI%Nv+Uk@4O{K|_#i(Q!@c$)Vgdq4ewOM~2r_r6im~>nje-k#y=QxsyKDo>DT{fI{ zsV)1u$clB}^1MNsP9|!=iCK2}cN(V9<=SumfMHxmcy%MbkLK?6^C(?)5#y|jjDE(5 zb&;TRIA{(H|w1$)7 z;8$XN+el5|r=j{!mJAD<#2uFOniHSP@)}BaB`{(}qmgU>3-_Rt+E%iCQoA&I& z!AVO+&L%Ii?of$ZLF~_4nkGM|d8?z57Vh2LM0Cw+M>UgjOJi7J6heAfa;73>u&Rio z+O?>Ya2Z^`yM9?YwB2ic;x1|LYPw>S3nx?PPQE=h`pPu^IE#n-1jHWQu@GGIrm`3r z(9zQiz&#XJYC>IE*N<8N(cxcc3IZD)EAL{4Wx|`jPu>g9lgbTz`5OdR#LY3QQ`n+F zae483LYK3a0g)UFUTBSH{*lct%vK>)nT+ZzMk}-{=~L;oQA^AvWgt;w@mvZ~HZ@LNY z)0~lv*Xnp)k)LxE)dV%ZW^CJ>Vi@yd=*zv~*|`saurAx7s|KR68|uX61b&3}jG$0F z$Gl%a4o%*ZWaQBogv9y)jT;_G94eb`?6VSuB1O|1k&k)NN`p$E@;)2uaMOF)#O9Cv zlP&T8#t>NcK?Jt{{Q5KxnIY?DOSS;K zmW~ZmN(%iep7hch!kP3!x^8GC3c*uof*LP^piv-p)TkDl>dJF|VuLgXVRpouK5|8a zL3$&ebg>Q@CA&l@O_9zld=RyEg3Lg^73Ix2=`Sff#uRr&FarLxCo7qQtT;$EXfiE)fu}*$}QF>2TP5 z+2?Nvhs~n%HLJN?JRVYj%TU@(2;~Nv88WN*5oeubUVk~3NM9UCv{1-va)g9Dopa6fJ^##f4qH$XFKd!UD z`Tm7I5xCw>u;0wI12nNJnw=&f!_ODz;SB4!Ob)RCdv=pXF!u zrt7-}*wd#)w0)@_bK|fc(}S_iZDliL|MV?0;sn0j>)S%~#)Z;TzU$ioX7%UIi4HP! z8zGORv7I4Im_EC(zkE4wjKA_HSikJwQAzqfyYC3P4`m`f3r=Lwi4hi)!HcUdu+ZoO2ql64Dww|^se5NL7)R4t@;`*K0b`m z2kQ@6$sk$=zIkPLX4<=7wS1%Ya9(QLtbdCd(^jr(R$hodn}|4Aef&F?b-(+ycBr(n zcL2ZWAhY?8-?gZlJzgD3(J!esspwpNvynklBvR@!A-=6Q)&>9~a$Tul>t6+rrb&O+bzJ zcG|D00N4aQK-CLerplrN$@yk%K;lUJ3f`_>Fy9l~H0`C&lzp6ccxo!ZguA!zCSU(} zDLIGb{ucdpEA{H@vBQVgJnf?j0>&!s$K^LR-$u?|L9(Mcbh*VJ-wV({3t>5>-ib(P z4}Llc7q01@aXJ=84x)S;k@4sKS4m_u1|xAEI9|YU%>bcs(btrG-Lh>?c7Dx`hff&_LB;9 zjaqC)-pB6?oZ(^kFma2Zh;W;glp-oOs$F`yw9e2#{S;M(0t!mo$tzI6DxfkwHPo4N- z*C|MRf1<3w;hiO=sVBVm>ig8Djr#?9q`m9cY9rwOJto63PI$JzB8JA4HZNo_b0X*f ztNO0`xa;QSam-5}8e1Q9dMI;&S)} z{W6_&a>J@gg4>N??=A^1^Da=|7q2nV zopVadFzprNfo0nAiC4h}W(2<}i7J}S#a#0)rs>!yO`K&{g(TcX+(Na2S2Q3v8K)O+ z=NqQ3#E>xD7D?FhlPi}r+y&p_)GslBF7_MFn$Kise4F$3WXwi4QV>XI_o=uS2G?2Taq3XdpkkkmRo-- zpgG4@#Ip6y-ZAllx_L#h6*JH6S69hIe*1f^>NG7aE}o;CI4?6@Uza#e2viPj2R)YQ z%JLkyEay^^E99;ze-?;sKkR#kpPsO`z}t}jQjB$F{K4E>NIO>>pJCDnpK1*&AZk|Zc9(9Mhh>)m)xu^! z6|+u{QZeVz(L)1LhQQOj87igO+{HwMtcExPq%LrXAJ9Z7ok~aSISkL5daM3ZjhX~t zUce6}1r8|Dm41Q24QER?k@Te!1GvgBaVn&Er#PYklQ!Xd4dlA>dAg{Uo_Fkb<>Z9l z%FiZNSNkS%hOnuL^dRycsUO6Qezu$%iwsZb6gWM$CTOrL;dm^i(qwL@*bB2=Off#B zp2s=gduQELu9}$RSs@#up^v>IH4GV%lxdje9V`D~yzQdeR^q z^Ji@HUptu}S&I7WG~|BM(EDX?nqdw)CT`!sqrpyQwP|Y!{aFU=bT;uDu^{CNQNory z;Iy@&c2Z}{`~bMUx|iKOO*_#S=o`r-+rpU%;nF>+9PMQqIo<|6CU^PqVTLH)VSAtn zMd>#8I`;q9S9GY5_vZX=hh$bv+{F}0^cZ5pgu^&LQUj} z&siiiqocV8R6Qz~9Qu=sTTuhybEDOhEpnicsULsg$;q6_Do}+GUQB|!1>%p{Po+vA zRxmZ}`v?fR^5+AB!5EpA>t{-v3dH2ESa~xEt+>aY)9Ps|CD}_J6!>E$;cU}rVx*gU za0&X-0$(wQF^@)>8Zgjb;VR#x0=j<8olTpC|1gJ!?ay4y=@hrDf)Q(@;{I#er>yi5 zzw4Ro-y%MvcgD)dMREO2<5b`u)8SgM(mC&nfHw){;9rY&7A=iZRo6(79_^>@^<<^gzlZovjPwo;q zPY|^DVpj}|ge!Cf&|kP)yN@`FFR^M}AZ_)(DJMC;8V+b*}kU!^dH9bD`EC8dBcZ z&0~*iw4UYmnSO3ln2CzqH8-E?f^mJc?L|DEwR|Rv?H_$~;?)b90k;yzp>p?qXkbWI zY~sGy*cugG({T6hdf4I^Ny;i3cXvBKoABxS3*L0n?oVlaOxUqRuyje`6>)%#fb_3o z;L2sQD}4)33X=C0ge4VA0e_PN{4fE_Lq>Te$D+LwL4SIYDYVpm~ zx61uz&v=XSG{rLI$n2T+pY}@)Qbs(>KMzebx9uYesG;$$DO#NCF}unuFom0Uua-(L zCC_%pFS_K4lq|!ar(8EDmATz(PR7MK&~`CDbp?LVjXFP|8#6%Fxs&GCG5&T-@j>1P zSHzcxTGlEBEm@L`1KvZ>;-#5eXn?rJh=AzMENBGZuut~>T~95G#~YxKHHK`SwyN&F}q2g1izM* zcDcu1_~P-$>w`DE-C*N4YEpPLse#g~K=ibT*Kdf}(kx@u^kz2%eU zU2sbHaJL{4iBn4PlxO6fQ!;hGcFDwnOpV}Nnsvl1wK`tZ^i$}d->IWi8S|L$(U zaqD0Q;xymczq++|USTW2!x*}8XCW!1eG8G=qN!5B2w8aW^~;})vF?1HP#E1-i$c}K=u7c&MO2{PJ%(AE!SPvyUH|S7%>c9L2cRqdGwB80 zgtKj$d49GWSKqbQwJ3wdClZYF-1`*vi#k{(41(BltcmlDS!%uHIy}PD`YE(KWg2o( z`J9?Kha6Uo`L$amRQEOAmA?s#r-!u0KTDt2)uF=shGV(Ldn`68e=P81bRTf&6fy0Y z-sHYu!AJ9{A;C9qd9JS{J7G=e?dT2x-qziucZYb+K5Yw%w;wv!QSZ!LkeDIey)|%7 zUB7zxfG2*Mvwdsx1yv#yJGMQR_Cb$DHk7Z2DD1^g>$;z&WH>Ne6H0K3(4pLN`tWD0 zI;}dR$D%EW!y3`QS340$@LE6I6$8+|v^Pf6%b3CrsH4SQLlE{I*g2NEbpz8Yw2GZH z(&bdjdHRvK_36HvgS56KiiM7`vlSWpACH!+v)+v%RI*ijSAOd>X1qm<}#P#;eGGVUHT^HX;V>TBj$^)ec!XrotF8AS5c35#RUPgSRN<( zYw@Rj0r?S-=SWa8z2+`(7iJ_p`Cc_C-n?!QCpmV1Bor14rEXi1+tgU8V+-AIif zmM(F~3i(2~y3l4bP<>;gv^wc`@k{c?vG$0S5Q1Bqx^^OU4P)Lo)AAMS{m!nMcRo}l zvk;kv@dR}>A`zme+$E{Pu9_Xq{tN46-T^Lg0-W! z1*=E^lHJl;Sl^S8zj1(oDvoD|enABVyhh*~zlUZL>h$U5peFc5KE2*GW{dZ`kx9>i9J%NAqXG9+!7M-?rQC&X?GDfA~k0 z;M3&qj>X8snRAhusb>-xqq$|x7doOFDu6G*(pl0-B50lq(WKxSwG+z#X1TSb#Pu4@ zY$4%bIRvl*sgFO-KaY8rQLGH^??8o*-yLe@3Z@T#T-EOXmerbJnmC)mCezSbe<9j# zO1);X^g}%2+Lo(031yPUWfk4a28rGflRQ<2ge<X-9gSVEG}+9UOe01{RBDw8Raa zA@sxT#DK5vc*RB3}pRO$H_VXT%P06Pj!Ss{3u`cazlPY(t}mv_ibeJG(W zWqDQPr!L(;Q0y0qM)d#O9g=RyuzNds^aUq-ROror&zBx7KUlJ2!CThucVG-Du|FZ>oZ%>bRtV zYYFE&TteW#rw77M%hx7rFoaPM!J92`-oL=-fVGSgDwm!D)L3Cdc21T#1^XcIeU}a! z`tTMcsi*T#lV@l@8~O!VJ_Q*oIy2CN^{nL-`DqIOOSyEHZD2Is?|;Cr0c)KRjDTk+ zc=y@%htMZL1aVVEEX^vsf-0tXZ0E)X`yz zIbwJ7;<+-iXp~9Zhj+??I&4#!3QQ_j7fWZ7GxSo|61URbeWt$)Z_cCQKF#VP`J|bb z{A~KAy~OV3+5L4Z2w8>rqR^Tex9Ta%UH&>&yK5x(!EonT26bqMSDyHol6e~~(Lk~g zbwHGU%mCH{e-+C{8T3IP8AfZ)!^euw<=zgMg)Gi%OxoWY(h%pxKrs=rp&o|K1*UvDx3b| z?Q9~ct!0VQPsf>QWR-=b>{EIPW6d{nAD?>Kh21-{pG#+6iDBthP6aB2T`F>8SgquE z=JXwNDh3+)##{`iU6vAZZ->MT{z^!4{}ggDcCM>XiikOt(+f#dd=&TF&0fB2d+M7{ zq@D3qBb!@AJN*11P@^*2oXRsk>LQ#R&pkh2o9$1I$P>ju`%Nu_(Gf5>t!lyqNLH`G zyZR+v6}%xFlpyngBrt3mkl#&!Sq%PF8wVM?A&E@}RbGRw3dM!05VY0p`Qp~Aa_deh zj^nimz=9@FUinkTC>6ZS^^eYUH8Q$}1EAX^TK9(oT`wbNOeM!sN~Izmi)~@z&^{8r z3fPwCwrKtMHDhPw&yCF0jK6RRz1hu%01rEBlLz5#L{72FLd}Rco^xxPbIRpX)St=t zoj6>BZrcX8PaQE-ncsBxQybmtjbjGwgLg7sxjNv^d%D-8_r3WX=ivS<#U~#@GMi4; zf@@66c(4}3nfqYZbaq!Mcay#kUyuh1H6NLm%?nQ&lO{2Ls}u^hbrMD?=6-hJQY>*Ft9?@oGH15?jFk7kZyYOsp#o zG|PGfiglSnw#)1fH`oV}ODB-%)Xrow@A0q`J;E$Zpt!i8!##ly3+3^9q~gt)cdT~% zu$MdSQ?tX)Pm>Ou6ArK77IBTV@7&BrO*zNZ5T*+nKXB)Ko%rtm&XK@yo?Ch|>R*0^2?ds~;#ktKB-Ewb(M0St9QVdY-~RO>AuSzCAWMN?PJ- z`2~9A{LtOIt2PG#3rEZaUPV%KDmPbRo{Kix{#m}fXCo=Ta&x`s4m5l1kEU9j4So}M z)i}-1-WO~_*_A?_H72y?j2);;3Nc|qSJr*$)7Ks_u8KdabDc!@mA8fH*HRMESG@*S z9w-2Gpf!kI6DM(eO9dv%(xnJS<|Jbu`3#a(#Ydk8LJ$De0f?g9->yanSm5-uER{96 z&AR?)dJt2;nP~#nzRdGH&LPX7hq)$6)OL*o2ErlB!&pv?oamndY1_zocuw zNzvST6N!-Q$Knu!UQ_^1Zo)PNt*m1oTV3P#D2-KAIaf||`h!ZtbGQkqx3n{%T zVGzAG$y#7-?d8`+{+Dc(>J9tIW3-Un!V`=B5-aUQqeTE4q2H*K#d3{;k7`JMC|jrGFLb zam)NGXsk!7GK8$6ZBL!vh%QzP1b``e|7E#f;Q{v}6(Bq#oq`kF3|5efB^-dPrD1um z)A0zgD?1w7qxl@7kn)TkVzeR`yK)aG#5PlfAFq$`f9%UO)Qw zwHAC!&j0mE8v%2rS3wK^?XdLNXh@}^I<)IWTVZ?fdTM3!$2!xwKEJ+ZbD7)>FSUXm zZ($QqsXSEK%x}=)&))u%P>Vwc$oPN(I6{DmsYX+s0Pg_HLoqlQ>Hm>_BC$<=m&uk5G0+f zn0fxxLmQ3-BCAY_{m(F=;UwPFun%li*SSy6kM8?JsAAUI!0nUw&jM4bu7=LDXH!)X zUv0oM212bEf#<22#o}XrbIZ1>`|WRA1lWxO#^Y8Is^;hSU0XBcY~_icG>k*CVBiZgaQ? z6H8fyEp33M7~eUYCKMHRI1!9~697x}>V8mu9#FxSIm$iFSoVV*#_xaGZwn$foY-$M zl?zAOamFZ1-J!ilSw0VWG&j2)lYuN~zYlzF`}2ni!Q{v;iQ;kHx>=jUa(`hfEhJL) zB8$5lYaSz5#eNjaadx9Y(kRp@fGZ#}FRRt@ve_OIng3{C1|#nNh~OMITYyeM+4*u+ zs-@zZyx#4HH@409@{MGq+DY+rC{AU{J?Evj)W}8GJyVQ3tndP9d}9`Z6S$?3eTa$k z5I(!P_9TOCG2n}v1#OZ2&6#V*T9HqHlEwfx>9;7x2eKpRhz3`uJ)>V0t#@)Aw(q*B=?KRTs1qMSJdPwQpZ-F&V|1VO{ z-tDrE(#j1e7N}LHenXHca(U^3u4WRyL3+?(VDf^K{yh*mE|3hm9XS5G*ZF8KdHD2j zpSZ=nQxH+6W&xUF0X*@V)jc8Hq!@`P*T^LqywbRvd-QcBro)iwBB8n-AH>@>p|t9NVD_=C*yx*}PptBMop8 zhsB_)q(I87VZPwxGo;ODV1wi?VjNh7iNmiY86cz5YwYve5mw)5{hugAG4c2!4|S0b zsI5d`TUyS8u!T zjQx4nONy>ZR8`$YF9MAH(}GTC1--VyH5_ObT98a{D`ajGa()i-4MGI8}%6$4T55;$?kXulW0uGHf_Siziej(`4xJc1~ zJI%UU8DZ7D_7pPdDL)H3@vawRmnch~vH`GX3j9lV0%T7f$vq9r!utb=zrsz*IJEzn z+kHK9aBH|7Jr?)?VV@f0edM0>cm+B@x|unwkH?2(ZC5bYt`&%zB1ZjQpjgRwQGj`YwH{Kp6kGkaFNjIAlY}*x~d<9#>C*Ijtp9IR_bm8WYe*Zi6 zmz(nT-N4_|4jkXtg0aLOqUymudh&`nQ+RP{YRW!{Pm*7|zJ;+*el^gDFZRVIGtf;S z(7Rt(H3-!~U8wAE@$7;+7Ab1`FVhn?dyR#pxqLc8qDgghq@}4Y-s0uwAZJCkg$K ziguafX1>qFXBky8lzw4njeQ7zY1Hw z=pP8jV(X(~lH2{TnT6q(?PBV)>RUETPVmRK@?l9>A?{Q$!hq|EjpjAtH{Ql9F9P)D z951ibVAzRYR34T=V3qRBqHI01pWpD8&b`+!X2M6tEj*UKt=W_Mb4kIM>8_uVn zmwxU?M*2Suim7q3292Pip1OJeCFl^dKXKGDS>;%~7^*Kcb~RI9u3px8L4e-TKKlI= z4(?Tl>zUBBE<{u2j<$u8p61OKio4=dEPP{oEQHOl6QK^HmV2K$CsVWrW9Wf1sy5;6 zE~4ipGa^lA&!ciu#MvqcRea$Gol-j`>hVVIk>Vq&7USDk%fw)`<;Yjce~ z$7hWPfOctl*zeT84XgnJ#HsV)_Md(UY@3pTmD~=WHfJol*Q16pz;0WPIU}2=8ml@K zN;~*cB?7c^{oOBE{*YF+p_s3R#jA%UJknMKuQmSZIDRA@_^BBiLtA6IwR9{l4}I;t z&w*OA)V^rAyuaNXx|Ix`}2yKV9+AqjuKZU>a(#^h671U{5Iba3%LK%n}ax zS5eD?u7zw;;H&pn@IqB#uCk#N_PtmUgAmh=fx!E-bDs{X#wP!zFdn7)GQ+0huelKc zmiJoTbmwiN*yq)grVXS{um8&G*vEvc?-LR9HCrAS;kcW$Iu?wZPA{1L5oNh8tXY) z4qhMScWSKfb#pm~%Lfe4wjbqWzIY*m%DCI4nHf0HY-K+j_!8llDXoFSWT(MRU>%Q? zUZTz(ER#!J)f_)lvrOx=ztU0Be)vv%ap~ggst8Sp8RLZ!KXo9o$l5iM=)0!`xrh(8 z!=`*gpC7M9a;m;wsAuNE54YUVnYosrmU1TSW^Cd zXttSuMZY9NtuYxb`34%9Jfm1T=)w>hffxnnL zG1pTul9le!7j;`a95i+4qlVW)-FiW{U-CwmoZ>-_@<)HKN*BdM+Gd0`BSXXtE(#hk zK!4lj1Pjcjl*&)L3ea8qeU8Jq)(_qOjPeJJZj0JuS9zRScWTjpzkM}{Ti(xaNhBLh zIPR~w^uar5_L^U6l*0WNq(u^Hyc~0Hfl#h`ccp$hJr;!4hxcTE@kby3pDid%OZ~ASLu?4OnCvjiP6<-NPGn$Ph;zk?vHMgmv>{m zKT9n?yul}`a&p$62`n~hr+o&5sHC#D-O!w?4_LGfOvlt_RHfDzcE+i@sR$S2IFCat zsEs0xPL#ZKxXLy}74KWMq3rAeI+xni?E-sSC5Ub`QwzWXyKcgkG1 z4>aqk_9oyy+y!0Rj8#I1_>!LObZH~m7y=N3bQ;095DU(RZ*Vhe8!c>1Fel z+*kGj6PE-bo2-C|=}7QeF)eN4zSHF0@jU%&Oc;GCKwzftMWCs)?cku!|}0L=##P&cByS=J{d`Skn>u0}-U^ zyB_VIMDF*K-y=}VsdIcVMF->mMwy+`ZgJfqTUf zU<`$_cU_Tss8d;*CQ`{6F9;c?J|;eipk~;lK7oK5@zp`BoV=(C`$y<+FuPa*#b#Ue zb=06#>uriGz~j||=jvqAR`6DU9y1r;I#RGNeaTe= z{A)iVKH_J@=$uZ8gn&H7= zEhQbr?JnO_KdafCX@Oqco}6i#1)m;Usl0OU2@xZWj=cOmZP+~S>L3q`8A`vLUcz9V zp;-6o%NY&DGz)nDuSwT3k7N_po+ zX+Aj>$w_hTExv-h%&^({%f0M{@R<*|V|sT+)=4(B_R#Dogh7L!n#cepu-1a{E4D@U zaPSizLI{z`k)!$Js{1yE`An0RWRB8bg4<%H8gG;1~nKI8``#F$lvMvt#P_)HG zrh?wwcEc+YGW2I|j?{|epNZ7PKDc`*V=Jjh;fMH}LP4x=Y=)HMY3S4><2xuLgOlM5 zsSi|oa`iI|RpOvjwDGNxiyLBZW5$QR%JtRjHmd8#e!A%QZO5TAI)1_GXsp^RE+d-e z0m?>mEKU)H%;iJYf-a?Zj%Pi=5+R0mP zTYHC3yEDA3UpmP9{)Fl#OOqYErpHY=1~za;P-B_}IC( zBe7Ejp>MioaqnLhv;>;VgGTFj4@e#{L^ zYwA@t;bCCy`&)K9&=NI>3rS@F*XvAT+F#q!oV3c-mSW)5es4p}kafE!{hT0Pat{`$ zG!A$0E7Egs4sB>|&dYe1;zdEYBCxvPSpL44h$fYqrHRmYoa?RAFUHb)KGUhf6L)x z6}XwA(K6UZR0+URk-E|#)b7jgTqUh7J^M}`0n`j3R8|Azhzg_Cy*Xx}1pIbD?W*&;EIgTX&y*C!Xaf}7!Kd>-?6s!$>1kt_0iL^TbDve}qMm&=P z+1BCxIqqKVt8vPE@||ED-N8 z2gMM(YFP>*_)KMdXmZ^fYggURl1LTqZ}x|7-MH+o`~u9d<%Og_N>M)?-JkS~b)s!- z04*SiEjbW>rZ6IR%ZQ;s64DBl%^+m5Ax13V=h+BJ7C8y|?;(fUtt>F3YwX7(Je#HD zK_Qz2a=u}3rsyZ6X;c`vB!}}8=rt~ogOEH#7|(LC0jnb745h+%4zuoUV_qJLBqTkt zN-nE$Fbb&s1Yn>_C~_H)yo|~I1b$g7auD(WjQn@(<`xdf&`G>&UinlwUdJ4ac&3HX zbc=iyd?b^9k67y)X$)ovPq=@#Q*P(_cCOhosCnY6C3{8#Ntm==*L7=I+Vi2qyi1ak zeq0OnQ44l%2@L0ny+u>Wh3{=w9o(OSUo@ls7D9EOtj;Fp_?bUz?%Y2rGv;;nyped) zTaWcA)dn6s9IN?Kk)Be~8877ZCpw#YeonNZC_MARGE8WdbZjcUr>U<1l|^1+MGV|R zDhlDyh92O4<8`wWg21H)gfdWs2pD6Wf3sSb0EFa8+UcQ^TTMXDWnl+nRQOBFtn~@w z^aJ@zJHRYZ0A5569j8X|2z(Ef(RuvQfc_&f-bCyA(B>uq8NiNxY3AiOugzNiCk;uq z(GhumaWv2}bv)k*l7>YQfY^WiqyP_ptYB2Fjn(}gYeOsy6@mTrpS*GzjmvJXjI>a!K}b0^=H85XL91jBXAW5(xY*C z($B^B%sD~w3hK_C0t#1c6&*Tp*F`js_S2L^$SDL}mxGT7rrN(DcQz7C%JE_8wUDom zc1$F{gS!A$7|c7M=_w_IT3hNOaQR{Dxceq)4Yd!1VgA4sM?)MDIm;6jVk3a;U4q%0 z-lrXLvw#KO{!0e-*0=?|*!WCAqZ|8<&@u+9Xp;k`*Iwb608RcMNoTFyFxKvF;&mIeUHji-~Zi@aCV+Eb7tJR&JRM_n$siV zl`)<{;u0eee2B1#y{1%cw_$z%A*tkFeHau2Nf%Ga>pBdF)_BWXHSJero%2GBVUN6@ z#X=T(A0%IEZlI_)32_XpiOxmYsI<{WUTl5%%no$(VwfWhIbRL#Jh#UxW}iqz$n|FIjf5y~SugKA3LXyx>kAOQ%z1{yW5)MZ@qwA?@>HW%2W=*RPj3-qBaKfdGNd@!tvDzt4c$1 zVM>|J(y+7*QLGSt^0!pTT%2;R)ga6G?AxlH8C@bp>PMRGpc#dK&XfktzVk{gq#B2c z;3g{I-T4j6zj5e=dy~Y{uo8vf2s}-Imy-RSJ3K9qYVZK!=X9iagfi~N)fv1mB~-+F zr>QIckHx`QrHVfP;_tB8JQ0clkNxE%2B@j!%VTy-hg8lzhtG!A6Se*0Svv?vjH0{I z6;1^Z&>=(hC(&s+IEg;oSLP2&thE2=CwW3OV=SjzxC}~UzjJ&qi_A&^Zbd3S-PJrA z<&Cde&+j^&mmA;z+DE&|2D2QJB#nJVk{mnLytZ96m7RM}X#SUoihGZi57pK>@h$AMB+IuDfGUZ!8QI|-x!eqM+2aVhSi{`cuDGg z5?r4dMQk4Qp2ZTgBEZd!)eRxa`>q?mpfd2c->}MV0%H1oQU3T zentT(?(*W<+On4X{n1B1W2=aMFk0q3pwEmm6pe|9A%dSVk%ggh@$q(~)gO@V{IG*3 z8L~;(9)+xro^S`2?*J|;Y_*NOF+;-~r43`8fbN5`w$x1*$| z3(wY56>}3sN+3SA`W5sid3vP-?;^Eisbw~69Yh|IoDkF_xT+kq))Qn4o`oFWcHN4~ zP6dz){Spw`8;9kHjP^^0(G1$?fe?#K5-u#V5GF=)kgI$x%f@Z0Z!WH2YWtF}! zJ(G+d{hVI@{T_vnU>T=+#{qlC__2RNNh4;C48P~r*o-tO;kzwvJVAbG?p*f=RAtE* zFBCfkWXtW!e+|xssu8b_<@8SV`lAo1cwyT6p6$~Wb4Lt|l+Vk%oG6bF$25Hz5azUe z4Y@XV{CmzeadT8!`mG0Ij=>w#`D@dYLMtb$o#4rk&CG_mub)ayWPWd$8ns+g0kzot zl=-)gi}<3;z$z|sUj_7>S{-M- zv9m}WMK$0#BT67F#2j9wPmy0Z5>RUbFMtFf892^?Fqx9yaRiXtD_M%rKX_azlyrLG z_fz(qWSKG+IF@|E^f%|>d@}*a;XO{)&AQe|GPKu0ayXSd1?hPsg8Zsy)M$OrDc;OC z&Ac!~H6!@|7wr-6|2B9Ta_~2Db>tZ>IEwMif{Lz_^?^Mf-Ho{QCwI*)iK`o?fX_35 z7V^#_+9$h%hn=gHCI5e%bo@Di?({V1wOsriIo<3fo|Qv?p@6qR%WuQGP0v+7zlMe* z{(Lr12D|An@1x*ma9b#?e)GAZZg9e~{DLRrKe?_=4#zy2q0@tom?(#Ga`CxGWuYTK z6=Ke29>a#pGhg+YP_`wPJD=k$1o3C(jG0E3zw&g|Ge3%QSKjo`)RO0v^v=5|gq)sW z=zH!Eo?NKhQRsVa{EKb%{ua?H;A>HdyL)NMsMwC~>K$7aQqV{0O3mv!T86?13zY5* z4d(q)7}4`OOcqtbhi2w`@p2wF;wfCaH!~giUZ(%&?)ucAsH}pOa7=kFYfnO z#6RWxhk(A=K-9Hp3ez;0gR!a%+mAQtaVEh>hf=uNk{-)3>r2ab)Ko1;E)`yn5dj+i z$#{ALSl4)Ca?ZVOV^7$8W+E#{N9;_S*GxD+0Bc+dZ-hdH-ygPeGHlP!878HV&&~02 z<$Eo<$o&28B^7y;;XC?hOpz9y1XnR`xb8UajRk1E_2>*A%xC|FtG^S zmZO_oKSt6xVk`E^X%Vc=LH{5|aOhgy7Za96 zp<<)=)snVIUso;o=yUOHZ!P%!#kH<;v&}{L%wP3^5+QfV>N7-PTu~&bwtgYAv^&Cp zr_t4h{5*z4&HYzZWxnTn4H~+Un3HKFm)c<@rNn@9E=}@*gk0+|1m&lm9BwD#fQ0H+Z=*amK?&y0iS|{OgPVb{n?Z4Bt7FGFpQF^HU$9&cbuH)-EQB z;Rny!#^KupcB&Vjmu)S8e)V`hOeHu&Qt6F9P1dBZg>#zi_|lT5N+6qi>(8oY(d}7D z#2+0-eNWUDyau+-d@fy($f#H%3}cs}^;O1{@`6bE(h=IWo z@u}Cu;=+TA_@5}nt)7GCShzUG3_JmqrvmY)mwtyKr;~S2*k50YL)gk>gQhExb2jaF z0KlzgoZg$WET#4rg^b<|eoIB6X{6fgku>idfZXYH|Lzhfo8&QkV+CA!3mAQMlQc{} z+!DAs-k-**yxb2%#%pGR9&>*h5?;QQCg6lzfya% zO10Z=Cjuk_rk+u}&+90_u^aX!3R)uEyFKpV53jP8Mq=X*J=IE*8tNCI35Vx`e6)mC zUG23UwY|@YS-B7S`2%v?FAvLVikVy{chUAP|5?*YL&hU@j%+6Csnm|%l+R^FVtfoe zK@0ME=k)M-j!=WvAj9saAnFuJr~zUb$cmm}?i!>lA9?B}n#VUPUwT><^0GOsK5n<7 zYKd*3UZ3181#KR;a9%P%jEVC)jG5%WaU!^otS~yRVOMMMz{Xm8)H687v0Wv>*Hha2j!)~}?tf+@$y)qX~ ztyRVk+VV^yqUI7VjCyX}?O&@hET}3uQtdI6|5C6cJ3}EZ3cXH$h{wm=g+l5pY0pRR zR6*G0P_m82f(D}uE^k)J)+=5P=X!_Q2+>nab}-@#>2@&(WNM@hBh*}etle@+QCTRs$jF&cr)g^`b`+XS}lsP8B+0B+98 zlqbhZ1(h?$8_mC7Y@!1}=^~($7@64`R6tB<+2R=<$uXLQu&p$aJLH%b=GR&b{hc7lGy3l@o7-L@Da?9t@gOh)LZp64-Y zG(EKrzb_osc@Y=4G^qb+a4fr;%JJ^b3h0f4PMUp!^qVg$iphg=c=|8JT1zp5GilnO%dqQK90Jud=n z@Io)RD|Kf;-$GnPYxJZYY_wq{T67T)%1)sz|!^Dm@bmcLeK0hGg3n5gxSM&}U&OBB)-lH|8 zl>Uho#;Cf*Q%Bs8bnh}OD@LbLD^?)+4lIl!8FfzngSZsw1w`By%FUmM;1nI?yHKFz8| zyLX|qZy!C7E^G0mKm)3A+9M7=;jfLs-$=(6q}l&hW26g2C4U_`7)s9=mpD`swIGs? zyxvzXD{1cwqu;+4{*@wA`wbo};QM#?DHV;5+H#Tzm8(!6^v>`;M}c8 z@24+JP!9|OME;o^JY2NPdE85Bw>{d8#(2JPv?U)gvA}TQPj5VH+kGR_Cis)OF!>wJ zaI+DHH60WBCufj6ev;wEU0uyURqryS)QTDvuUx;)aN0d0?}SEkIfd<$K?XfHhN;`| zeVan>=lN|bK-_&eKJH6fm^x)kR#Q3%8t z^WvYIKun3bWxqhWGVw28RGnpzU~@(HGyj11yhgdndhhF_qRpYYxQ}5i{-3bUJ;BY$ z&>ZFD&bt#Cy^bLQN1~=gNCOIJWA4IImU2;u+rDK!sKD2*O^roTa=|$Q!gb}C-kF&* z?_-XnwA6?s3XV{i*1%mcf4U-B+3pc!*m$=B?M-^f9{9IK8anEemFfzw#ch+DH7%GX zMQf>nmM~wd<3Qk2>E{u>b+}daH_Wt4aFuUXx4%o~TPZPu%|#YE+)Z79NVS=v1*eBg zRz@2v+_%WisFI@Ks$9|B>ada(-Luyu_`;hEC%bFC{o+vIzBDI*MhrhnySUQ|Ak!Ly z8sh_n|bZ@uGAu~S` z=sgr3nLI%@n&eFJ0jD#0Kgj zYnuPYup;%%wNA|n=(x=)=BQiyOO8LTW2}SH3d$8~KTfgbU+_G0g&?B7n|EYOJr+9| zSVme!zH0Q}>nVQIgZQ_o;-+os_%k`e=p#C-;i1jo;k`1gD;a#0-WAj5V#c3H<$e#> z*k|o?ULUO{(*M6uj$CVWj2!q%|3-uoh~LrT0bA+YhY2FfotwEN7Vuwa>|M#!{%w$& z(LO^IAXjQ%FAPFD5lX)6I0sSXk^#xg9{@DqEwXM}A^J~o;P>}Fm?>BvVt*WzwGsX~ zWw=CK#~JoPo&`TQc+Ji=Y-%t=Uo!O&59O^XEbL_s3&_MV2^GN5ZAmvE5_`?c#q9cx z8_Qo`;_j7GLe6dd?9)oQ@l8{`na2?(9lB!f{-51$NKya6r)K$d8^cNF*e@j;CXU3! z&Y^C9#Tp(U%6*M?%(d57tN%8&oxWWsKAEnk2iH#Q+)xxXj62#iwG(W2Gp|G&n41%` zugu$0UT3Iqs4)K;YD*aP8Z;pD5{xl2>d6f!ar`sS%DrQyz_K1La%FJP(xx@BuCeHn zvW!I=&k?J+CZ4S_URx2=mBwW-d4gW=xLnJ>`bUcMcZmOux8q61G3o|hEZ+PJF!lSt zFf0Vwe^Nte=Mxh-_@ElO9W&?6h-1M*s@>k6jua2!NL!%(!lL8* zh&Q=g)bIZCuIJ21#kRc&=~qGHXYmOix#}cRwIzt8d6Op2HxE#6S4O;g_u&PX{qgU3 z7qHOzyXZ!H?{~5v1HZFkr?#Xw`s5~IQXx&JvJ6Xei#t;dd0ErAE;o0(yAYIs&tW(eJQ!LV{!2kXt%*zdGz`i}l(B^Lf$^E7NXx z8r{5l=5+Y{%;rH}%HIt2*zR=C-w$L}^&~sQ2&SRzBNonTJktrIQv3HR?FsmOvK-z8DuFQ0kAhikZ z5`bKggSMx&twgBn}EgqO=dJn`p!f#a<&zs$nOcXFYPM+VVovD`^2ih)xt>6M0b zNb-@8wIQGEYLJyk-x|S~YOYY>=4q|#SZeMA&B@qu?^S5 ztLMQPz*;2zxpw8VRP5y-c(F0M)i;+68vDgU(1_q@EfR-$`_*stbJ@-J-4bdtQI2xAezzYQD0F9nT^&+NZkd3h!;gy`cW*h&af{68xI9 zqp9Pc6BdVh{?4tdLocGZI~>eren+xLeN1i(!FlzQ?G$k?MtFxSLK^c_fBgI8+N^&C zpAI>!%Y%<>f_sVSNV?Ob3&9fbJ@}+8td|JeI7>+!gj`+r(b&!dooU}*Y1ur)VSu;M zI$OzS47Vo~yljo`{E)kwqt@sz7N=j!V%|aswDyo8iB&x7;E`#IjNQ(G{8ou8ZqB{T~nY=$A z3|UDVa9zDCYgU42L#8N~0yfVL!?~;?nW2jXkxSA!B?jW`CNHN~%~7qepLrI1jC{7F ziq~DPc)dL$E@;%6)BfD;MwCb@WWG?F&4%H(zTP||JmKz03tX(nLca2vqW-JKA6(TGOg2xDej_5E0J6317)EC4jKa*Nj2C+Q zuj^0EQN}hi1H$QUcwIdGK28Cu&;m?Q0m)ED2vTsGaMJ7<#H#=ZOP{JBMa(IYL*$^X zOX1&8py0q&Ew5XdeKn`Rtl0+mNCaJi8pp6;H7Oc-ehYG^X6)*M6A0xlhg@J2R4q~e7!FE z&soPdGb;j#l2*Gkk_?Qp?#yJ>>wP6GYEmhPY&s790JrXqkHjWoqLxdDxq|pVtTx1` zV-%$KH)pC1>k8?`YANh&jlh8+dbr5$~s*PmU*t-}#ok{s&<#~DbabCkybh0uq44xs0n5j_4H6DqvwRYhiC zD_;4mn3UgiZh(%H>@?a`Mv~Je_aZ%O zrIPE~7%jeM0{2Oug0r6RVF^#wdu(x7s8n7xY<)|hZE+06u&%t2O8W?~!@?U9+?t;u zLrv#5d{Zb;?q3^oTz`Hxa%p0xr=+jj4elS9Hjw+V8^i6A%GQ>F4#Pp4aH-<$E=fN-hI~?iAJO!GP^^* z)&s2F*7~_YU(y^R&v@p~z+LC5GgI%5{GzvS_Tjl@1ja6{e8pt|xmh;cN5qH;K>(JA z4PwMXWK{bAEBP?AUXOkJFhf;x^jsrYX{2sD`o$fT_TGZcZ{;&L653YbHMRf2_fjL0 zM!-~mU}cW#`i%Lx;Ef5p${(rywDM?imsO=tbvU!HKetbtmqP0ahu*boMah!!aLhKg zRQ`Th?{ePc`&!zmSV=(3i>qC}w9e75fxEV?LCix`l)QZ5rEq*61>-XG-@{M0hBv(z zl(W3Ido9zJ%}2u(@sx)u;|F_pXdp&5t}-0_%cBk+vD29KUzV-UVvyb*+#>(z0c5ux z4<~>gCRv1KEL)*w`~k$Pff@SuDTMdUSOH-I8xCM92A>U0q@>$Uk6RG>04Bf0R|%We0;ePTcy z5YLbQ9_q%VtzV!nZ-N*?@|QTCjqeh{g{az~+i&&m)-@!X52fN0b?1ann~=$bG=X_a zD6wtGGPd|_?oxS;#YZ32xrSlK*HLg)rLqZgy#SNmqf{8i6JS$GrAVnTv_2%{8-joqSXcHxd*AZpCfUc3 z2V!La(l>{yFnYTubvs0^ro`{d+{3#&Rm#Ok)Pzwmr2|d4MzQmx#c=2)k zP(IxvyXb$1;YxdpU1E6UsV#c^9l_e<_j0YBLW|HRJ>{p>*`M1!c#pTy zyyPCe4b?9v8n5)1xcy@U9d;k0HO_zy!RvRh?Z?Q?bc{duH+ixjs)YISWw;GJIGa2l z9)5;uwH{TrL`}9*?|qJ<%@e9_7gnK3n3Uro0WslcrvxQq(s7R8)BXJFpjmP^62p0^ zDkC?0hs{KRFlsL{@}ydH_spUT##wiS28)W^5gSF}E< zizWxU=2)KM`!`cNr9U35N+0H)2kV<>eu@7?==WqFo5l#I&b!LN2ZHsT?VqBVr#k2U z9sL?UnkibUIYfeNTiPbs>V>vg5sVUa@mI^Ns7N31cSL08NOInL4!GDRDspiQ*$Jzp z&4-jNg`9rL37q=j`=-JlRX|(vwu~UMn@%;cnKTl1_)t2zyrAlZB6Yrih4HUtU$^4U zMLSSJ&#?5ocLZnYeUIu{WdwZS?)u=NaCgq#yYz=&NzHEaMhi+?UVv6t(v{E9xsS5* z`;Pa9&admrL_y5e`g6EF`t7lUMB$nHAnSv-IdfOCW&$?7z4fJn{!8{`bc-}N*)0LR zPxGJGR<8WNt3$~&nFx$)19c&YhVZBy-yP;9{8%&Ec(`}pY}~PWoEQ0Q=E&{`N8f6!YvDURD$EI34BcPr~oyJCjg+I0R zjwf`UCUsG>FT;_yj-J+M zEqfc8NPAs+-a zW}GnAo(Pp+gEIc3LF78vnEX;ce?2N3uvSAg9{1e?y$2J2Gu9Em(@i^XsQ%@M;Fws^ zt)iM-FLgj|ePS89Azl;U$gO?Xx^EO19W6}dt+2{Brh8lFo2fam?l(lypI5qRWvA}! zaGqMN&DnpY3{j1+97cS)ODOpz76FBwL;l!sXMJw!n)`Ks_^zY*!^Yt&ge$As4DAge z)x+Qu@w)%L0h6jiilNky)|!v|aY|e_lTGpcdl3+7F|UprH;MRGHY)SA2hceR**z7q8 z%etqKIsh?pt1~?MzxeQT-wzGbok@G+PHkr4mcDT>jGY=AJN1$)UUb*T^TI=P=JUmp z-IC_z=XW}-x5xi+eQu^NO!J0;`jyhFt#!$J0n*|;R46a297QY6C;%r9F0en=VapU8 zxPQXxdmr#e)e~N=Tfg)gK`tVH;abH4*^+=O0^H*{_atvrd{NHppJXg6R`?ERQ@2__b|CJ8w`md!u#!Cq!8y^`0aN-9`N&!V7rqMU@e04GA6IkuGftIu?oSTMB8_l8i_qFr-L^XE2}+0VhLK zoeQmX6(G3U{qN5YIMpu6|GXG--+$b3k#>x~zgL6$!^A|n&W@o}qsxHXge`lY3X8WWiWmWL`rGBLbytuwEJ zWvpRxYmpw;TX6QxxXS9quP0#1FzNeKVX0T6(%wzH?_U@$iB$)1Uhwal;dlLr@9dZ>yj|w zXj}IF6)^45-?kgYi*78;2c+`< z5&K?}tgppL{^g)BsZw_X+xKU8eC#n6em?7uT{CMj&VI4U^E}a!aw&uMW9NjKdUE6N zq`Yfx#1Fa#yjV-8QxyzOu`^1K)%_efzfG2xk0N$_TixniKn0|$1Z;tJOqvSK4Foh>5^M=1P zDS*61hXW{|EPSG50973d)omy#vdR4<0U7lvIZwfWFa~;};Hjmdi(=8e?URr%<=HQ>=n;bG;9AKIQj({y1$v4JI#X)Wfq%f| z+l-ToJWjbvJQZzd&5fRlT_R3wl6rYbi}U2#!Y*NZx#}%Ys<@q;O^wlH2Txw9eRcx+ zo1dXltdA!mK|Ktgkmc-%-%We>E{{J|GUh@+b(!BcLm<)8F`G%$QhosoyDKdM3JkOA z|Iz|b?vgPteD}%n+5TM%ouKXP?076PaG+R$;hZSfC!M&Pn4jjULmquZ=`=+ESyxal z@;erCoOlka_MCRIC$wUB_-p?I;^^iR!w-K3TI1q^vMc2!RG|wh=1RY&hNGQu!N1}w z<+GK(iOfAE6Zr(|*QQ%mAARK*EhT+b8twCQ4G)@bWt` zNuIXEA@)J=+@3xAfeD-L7^srN{m5pWPzLp5sHH4Jy0)7d6h@W5)s$bA-CIX;8sN6D zCg7YM*TVqk$@bn@Ug<7>6uI6&HE1+NSJFEXLGeT%t@+L)(@9eh2Ji!Ktz9M+45Xnsu9pi?&wP1g z6XoJC@^gfVq8bOsJ~03(fY0ul?#zGb2VwgXT(Z47b>W7en~ua81(N1_7fs~z!(QXV z3L&xGIPz=QFd{QBBmK2?qS?Ca!u`a~2k`h-JQQpVY5ynMmvg6x5MFvUJ?oi#Z%E7X1TAmE*<2=Iak7tR4tkv*h&!Gh`FarYVM zh{{K|{{>%qL!(jubYA9h%i{ez;|qg7VL2- z<0%@9qT^Pf6K$3Xv`pNx%D6{zc*R~!Sixggbv>`IN(@Jz5yH!-wFyme4yVJVL~XgD(}5>b zl3i7gGCrX4c8+}ZcF0VG8O5KIh|YH{`JcPe1=c)&vb?g-U+Yf-Mx#SeIZL8JeuuBP zV2saa5{zz18*ZE#`uzNdvB{V5^}fD54J~qFYiVH~*z-8OsxM>TN4?NuSJY146L`b^G-c8SID>!a z=<@L}vvin5eYIy~w&1FYt)&m*MpJB0cf)r#u-#aOQlF%RIEbu%$P{DA2Xw9Uj2Si*m|ov5)0XsnM`+;KH%d_tp) zaGbxZ&5t)+qYaL>T5qICAmHgpHciyPev7k(HORAc+0h=BvV89(ayOOJadcB2G>&}L ztew8bT@jJ@0TQ^FL75s*Kp+l+p$w3(tTS0@m42?r%FcKe(UEXnv(o1^ z(%-WCA_J&B8D5-a#|k7IYREjhk6B8yc=Q>^73KNld9 zp?F>|m&>2cl-xZP(SE`eAG#$ishWytdgr+-Ln8T`Ent{AY$_o>MOh~d9s9jIm~8JT zvl@!lNq0D4Qsz3mA`;M}h~RI7pMH6XSiTKf!S#~`7b_>AQA#s)m+9oEpcs~O8ui)5 zDHg4M8sLf#OMl7U-%kB==2v-&o9%0Uk%rc;7wWToE(9gmm1j~@5<3_o!ikGLWTA(6 zq@E2S(veJb%22m2!@US-Z|gKRuZ7N7JIYqdtl5s+L**$c4|9##Q)1JF=M}qA8YM zS{y}Qq3aDAvjrU8n!zFwgJTOjR{zkL>GI&WBHcEo#CHuZWEuXdgH!8uX-;$C<>Ou+ z40*EPAt?)|l6Cmb$RJ977-$p?bin;zs8QuaVyTC zOIqV1kG(Z?=+oA8Id>QV>1fn5yi)X^c%XUTM6XqV<`qtpgDYlu7(s z;;ygN#C+pP6tn7hsR19kexysjSBXK$pQLsIs<+FX_ERCVWqjq)KP_ti_0FPxry+(E zRsO1$ODldc5blZg9VE?Q=&{CsdOza_Iw#iQh&_9kjG1ZaxRkUBcKOk|SU+o~5HUAW(y&kLoRbx!?Aqy05$HqxzIP*c)SD zbN$Z3*Iu?YOU_X5uD`U}xrGEn7X;hGP`i-wn>U=)!5|uVZTp*2c^nDJm>?z3(6!Qz zK*#Ink_jZ+htZvAJTeK2D;hKZD-Xkpf={f90ELl_!DZ2cT|^=a0ThCqjYJ2W4rg!*l4(zH=Yh= z0ji$pRs*4Ui;6r)qMpYo!vC)VGw92}qd|Zn*LZEUYs;V9EkfA6#v!uQX>eM&9^fjs z&wh!^KECd!Oz@}apd{cA`!ml-tFpT{<;(T#1WoajTdXjOAqqj?xxgRn9zv*F>>l-~ z$M0YhELL-YD&^oGTA-zXoJX{s^C#>b@%y5y!V)9e#{;WQmqFC_=NC<1PFNIg)aZ*cVn%3By*(I#qxW?jQ6o1WPS3Vc{l2!yow%c;N1;sAy1C)KM9r~KeP`sU z*vDz{kDQ(9aog-)haNn(<(_Dkt~=VFHC><)mqiDhLajx>*mAJ(B+joA$#qH$%uKNy z3A?|^Sx zW$_>HabZKV3o;<2cTRZ;^&mPrS_`{4_&^^TX=TzD>1h?$%9d}r-BT60`g1W+Co~w1 zt(qM8wCyxmsPp;+>k8Bs8YF^HijopN(VUVkP>eirF1*EK zE;gk4me1=E8Q5HwxaQiwYCB#ZXs#h-2L}CRpG|Mucs4i&ENqIjpb3gU3+DIxEgfY1 zh2LYaPCj*21SfNb<5fsotlmLZRgOhv)H;5Ai8dPVQ4o_3p48Xw@$>T{CoH>|mZi%e zZPV!|W-NhU(Zj%iq1KvwvsaM8tLEwN!+4AI9v}NeZXG#hN9y;-4DCHUS>p>pE98Pa zG#=YmNat0N(3fpz6SJasOgDxFpj`Ix%vcxj2;>AXMza|A_L(Njc&E$Y6;6YzF z5=cZI3UBe7Lr1NP@Jk$=8v6u{PrdP?Oa0{5W^=xcCMxZ3m*qwvZ@qftTGTx?>6gb$ z5w3JPI0_>g0u5P1{-c85PGEa$K zNoFPE!1?d+Lx!$RuN!6=#dd;4R)(3IeKLqHeu&u@7tS?|L?JMJG+1h4de%?TsCsyw z3_m}8yn@rcgaz`yH5d|v*Yz|B0_rtQ`c%M z1C{N9LGLB=81y|i_dU*3o-o`w{jgI%(jCLC(f<&7WFtJ|YceIyvA!y&;a3i5b%%bi z)n_yV1iJ*SP$15=>{n04WONlp`)rv{*3(9g!b0{eU1R{^TPnEDQ$sHxKy`b53bzUb z>c9wmX{6Z~AdHMYw}pjrRt;M3nDT?OwFO^E#1I5J$@>fp;KLYV<@K{Ckd4dxF)o`} z7zUPnCqCCF*Cb%kZ~hLke3KG@zJ&To1`?V*Fb39V$84`qA~it-qYwf`)?j_8sj_ZF zgKF|ab&+j~uV$cKSKafC={J)QaSlDX7o3oK_p1F^Wj1CvxpCQV_hbd-1acy(*}Cy8 z(ccZ<)V|<3xAES0ifTz*G<&Y$hI*YUA11~z{X-V(XP8fOo)imDU1<@I{zOr!t#A0c z<)-#%pQkKEbnG^J_VumG>K@^f=+;DvgeTe-wbfDp9~#>E1_<0y?a^3)3S>@E!h~&K zq0kj`-pPnRtPuTTu_mbZs&bp-nW!Zz4X|URbQsa8?kqt z)FY+8(no)aWQqg59qAJ?gTCd@aU%b*^$DtRz;;tXu!1(4x z@DO9S-XEwYA8S>6U)0w}Q!}4mG(xy*>TD~-?0X1;iw=`a{6|s>!a4EEJz8Yd!4OSu z(E1g9xLL#=F464P8b4x_eLf zYi-e6A)H|3d*)<^u^st{`R%ygUK5bs;OWec%U+d|x|__DOsv%Yme=AfpoRReM&TB3 zVIt`UP&_C$o;(`}90ypz5#*Argh7_o4FlZt+tnO7g-)PI_11f;o#h9jlPJ!>$H5qs zt2WTAeMSTDrHk~+fg{;u(arYykBsB+Q{vwb0n^H#L?8=7@6mp||2P#b3KVRj&{ew1JDPk_G-OU9E)yS-Qlf0hE z;F+$8s%|>W=`4#|P=Iqczk~Y7J?B=j9d14@JoH?xhjBjfWQzPlHGlrk`7bCzfr#QGxwiBL+72dhdCegHf|KH6gXn&NV>0kWnYlE{-rLE?s zhdmM2tkHdcP;?wR6k;9AXFhq1_X-3S?9_ZE70v66$A8(u)`hYVVNABV-}FOA-Z2|l zLBM2hduRiVyeKaI(t?-HF)BPsu!3T?C^~Xsp>u*yhd%{%>^M|oRl@DE2hQLheZOwn z{&DD6VO+59m!+;4trNYy%Tq}%x3`@X_jw_S$MiN!KZ4+uZ_MF~T*%1;iuymEn*O?V zqM#SEnM$_Wb7Zy?S4(S6mJ z9cEm64=E6W-tgN&USYXx*EK?fp5EHkZ;#HV004& zL-i$y(F)+rI?v=0upFgxDP$SA-|fPp0eL;Dtt>whjwUQ)`0V3?*0tGw$@UCIQnGE- zRCtk`l@0xrEln4X(dBy~jVa!oB!HHGjz5*fOay$9PiZ;W3Z&`D|N-ob>(k) z9iGy_3WX%%pXiV#tzS9x{4 z$3bj3O4A|>M-b?r$bum+Dii1GP@x49c@Mahj&GR+RvJL#Xk;;QvNs#(OV~am%V4BH zZw-TMgg_tfiw8xc%P@(N6I(0&-Tt)krGN2?yO2l3&V_!K=65xh%Hr<_U>$=pL)9Ex zNpvS==lRP4OLed*bpWD%8$Yc|Z7mAeUX+q_t;3tnt#~M1ykkY2%*)| zTStT^coRcVC6bu>gNhi`Y7aSUyq#F2vXTctL-Ahjq5D3{ zaM+p42ox{X3=XTkuw>%(WDP($#~P;#5IIJiYX+ho)gfHLpH$$xM6(7mOIh&GS`sS0 zs2AmroVnt+%1%)VCV3|6f{$5UpdXilyFHhdLOK)>-7qK^Ms!6U+$yNTG3CTG7 zBoKwcFRS~1EL{a#R9)9TGYl|vNq2X5Iv@=af|N9plF~hNr!-Ouigb5NNl15tbc3Mu zJ3im{6VATY*=w!4_8m_+`q<7=Oj;;Q=&!qBA(6`0vlPdN1uYNP#@Cvov zaW6FMcnk^y8K2&&q+4>mr7k{-JH zoK^%kGOydB25ftuojzjz)qUjsyS)ql9H(bs_6%DpP-;@2Ig#Zp7rvBL8M$1k00y6K zXpoH&S92D7e&!-|0ZB|}okb2@VbjhZYS?7QCLAYhZk$SRvFMNcqIytECo_iA?-hdC_$`VfZO8)e)t*h|(jO*{PdmyM5;u)1 z4DL0J)B?Cbe<%CuEB^6*R)JAtJiL{RTu?(*r?OC96Mn|7u@zxPK>n=;M>bSP*@Vae z2nS)-9~??eLdk@?oFRpp>4xGu?+&0oQ%ll$4=PEsB!DTb|<%|-7N0};ZWco)~v;MM@#sz`0J@v~=A zoS}#BOxuL1V$LZ_E-doekcK_T@#DPq!=6IFcHDl*F!omM{f3yl*&Ho1iQp1S8p9m9 zymZkcB|E*Wpk_mc*)F(?G7K9`3Exfu7MRrEyq3ByHep9V_=vGu-R3KxdOL^kMF6*# zC-89+Ae8T^a{%~gt27a|}!P1m_nGjFLWR{wO2uefkMf;9pn8yc=m+<|*yausyCZSae}P9KwgUJj)EF zgSomCi>7ZfBghl)ID4H>@QOB5+hYm0ge4+bc*6>30jF$S9e1_U%WU0Sjl~uw(|MPQ zMa|n{4$&js;j6F2aWW?&5QmyEnYa}zyT}prmXbc_mx?NVtFwuOzWg~EJ73?4wHf2bR0$9I6aUmjaxOjL)?t0NiExIAu;)oIM zxEf{7kn1egRrmjg$6A|b-3PS@AeI+!r2NhOk6z}CAP>aD?z}u_4m8QN(-pEwvbwQT z*|3Ad=sEh6?bn73uy9!>ekL(5ES#Y%$_UAo~lm?=ltK?d%3!4M2D!*xbqHAePeUT=F5aISRb z^dxXm`Q&Q*)P%=du>WPm%*le92GUHQV0HSOKwfWwqbMjbPl@NstIleUP36Z@Vef{u zWp1p@@|#U3=2Wpy(v5>?VOKhdkM0*ph)vn!^)8MdHAmCXY?d-a-BTtAesn<9qB zMh-{ABFwDatowcM;#FjgfVp*}Mv0R4~oK^cYGmM5(mBVPU_SC9O2q zB183E!tb@MY@_9iYU`)E^&^{5)CkN)LIy09?uJfH15U{Eo$h!G8#^n#2r*xf;%8q{ z{wQ-*#&+Bev+2(OUTa8GlA9CZLv6o&I90?SJ`10+98pe67RUCsFHKqtQ}Id7Gre|R zie{L;S8% zzOig(Hwc?Go{?zk1NO>lByCSZjPT>uZDB(hK}G;FM8rMu z<-E=?XoyDiS|aH-1s)&&22ZmUnarPT_d&*^jcsqN4#}Jmx*l1B-^I&-Yl;J&M=X-V zTm6CGY~5X*a_t)R`(y^*d5Ew(_c+#rSJn{bqPU5 zk$qKaw+N8yGXm)+a<_Y)AyVh<_ynfHKkSgKzSVzmz2UG8!`1QbDb}t=`b356tt+zT zj%^VOC(skvz2kLIDdUzuPq^o35KO+A#7^aLn+cw!TfKI7e~xQjOY581?Lna=mn5PV z!{=rXX7xPzVTQ_a5Ogf;e&X;>Vz$o31+xv;+WZZ>W=Q_!0lVbLWn&K-aBinW&O@Ru zb<2Iex%Gs7^Gi?3Q$zDfIXvnN-=NKIA7Q5CwbFA2n+y_ht;GFUPHu}d4m zgtCiCmU17<4@I#4x+e0EJ>1|iiQwPm2*`FL8-7gmHWw&XvL<)?`NK$XZ=0=5zm&e3 zfl!s>y?;T?#yzZz#YYpuw{!f-(ulMru_r516Wv3r!MH#uB7Nza{o3cDPVik7B^wo# zMX8T3Ve+B=(6orSx~DhPd3?Aso!k)DkCNWWpozQG61pX77`S^+MzTBG-=IZoE^4Xt z0;LFVgdh@s^}K?*&;O7AGj)mI{PP`u1#yk=tJzQCQLrt@Pig3|QoDoI#!VCh57Yi6 zoGejGHj@-_%Qd6p7O!zX(7iX;U`k<^qZvBcFmvGUreAC=+!qP!UWqyMO}-+oNAL*9 z$LZJoCaehR&j=8aAae*vTAlcJrd^{^fDZV&?RDiZ&_~dKqDyO=1U@uGSTo*7h22}D)b`?<{>&d)mo6*XPkJsJ zuz^li^adCR%LGtGR) zA|12eD8-ATv%Suv;}BZ9Cge4KXO`Z6S?-*IhFM}I`j&ylDWL4~S^q%tlF7EtGn(YP zsH6Lxs~M#dMi;HOf48lB>|z1&xm#*oyW&NXOI>snK@d^BgS|9^gDN8Oc<{}#?^=Y* zeNtxvGZkWGy8q=g?9IQg3_=r7!F}G4%BPLVzY$5G)SeMD+_{z~wh|7Kncaeqy_CLa zsO8+c!!>QId>h2E;fP|b((`V2{+;~~V{=FC z8EwP7JlI*4m~qq#$yxF<+}-NRs5w6TiVD&GG)4p&pB0_6kU(VGni?6&H10E-=j+}U zqsWEP3F@5ZA7X0Et(AF^cKo7wje1%UMAG>+&I2*YslWJz7i7^8+;3ruSR~UKMAlh? zz)A8CsvWNYW_>|;+m=}oT(;&FptuFfrKA}zdn{YSBd3m*lp13LeOWfVRC50(TroS` zYjUi+*hXZ~A&7+*)Go7XD~j&i2gFJlKZoUb4iWBP){?UjUnYAY1VpClV8MAvpQ=h8QxOCA_58fzC~AOVJZ`$ zEB$)kQbN?~y=E6!vVzjM?)mk^u2af29tuG4KKJa4Z~(?h>z~hFzsR4S>E_O!8Qa&^m{;SEj%lY~15MALzS!1UC@ELzcCm_4@ zh{&9gW!&wT3>$|tsr41+U{@K$B;=DmTmZp*tb%KOKqKO&%qqx}e(0+?z*1?~HM-3r zf}a&b3{CWB)c1T<3z4{ZX&57;@Fo`1PLl4qr9uCZVLkk%ZJzgtl)U|JgKFcl{x7yn zQgzpRd*Q}}zp}3`B4%E1-3FAbQe?mBzF?TdpSiV{Z9`Wjag6X7Lhd=2Yp<4~U!RHW zJ}pv?nKV3iqV+3V8E~=hp^>XZ+Zy3iCT)3UwP!&ZQngOEpr`1!`LR*)JIbl|X<$%e z$L;{{@)h7k5j4tMC5n=;i43z%$xjdAz*7_m4#$&4(rX9xio-P|Kqy@j5T34dNe04> zHr{W)yMo;hpoEn%$tS{l<_xpR6;~-@Pg?fd-=-%fs^ilxj|d=Q*l?-!{}yxXM3aWl z8Zf*Z?2NgtLOj>@x{Vd>E(hc4Z|XLNp~tC#jWZl#Isqy{(3=tb`^O21`?1wS8x_{5!--HY0>YY zoqF;OLpYLqZ0~cjlG1w@wZN@a$GmRX;pOfd@M_8$z87o6jOn{6(1Fk5b^rTi11e+k zm~|~#Oy?DZK=z$G*?Wb05d{YR-{yO%AcHxGu`a#t@(=%U! zc1%2z>H{;}TAvUgm54zFnw_m&*saQW?xx92j3>TEIsYI)6<)5y*Bh@;plH1It-h9A z_|TDiZn(!iO-X6JiIhkPsL_@pq!$xU)`P^7e-Pr*h{rZD8aGnd$c#cf*LQK;e{|oH|u17#&If7EP0tj zJ@ga3Dw-bOq1b_Dfrv#{r0xo|)5rHnt(P_SzuuHS8VEevWrY+~q7i3B$-KGu8r%xH z2J?O$cSe_8SxjF%XPx0bxNPN)FCxgk4zHn4YJ9N!_~Mi`n(B)#q(H7koW2N4 zWX$U#%A~J0;epQUj?ZS65$yR@W^b30XKbAX%yMphl~SESZf_Uew`R(v1uhFD+Mft9 zs5+ff(LK0vp}F6h!T(Q>an6=c2r#8E02K?qAeV4d*nf&#N0sHj++sP+z8<+W zV8EvQDHXbYr=9UZQ~44_(L?6NUnAUnc}yNB;7&^&AVXiEmN&)41Gaeg_V7tU-Tb&G z?>e!*3qQdjrc1}Yz5q+1tEEO*Rn9S}YTnZ<9}RO3*JDcw-)K!w>NSx*^U!JR)5wYw%bQ>3D1q-L5Y+4=7I~&{j{F(RDj;B=IABqDt{C6G-A>ZEt)PsU=A~YW zYb!d}Jyg62-}2bbJ^jU;=*=AU%kdI=!~VXLj)OxEwPI-{rP1aO`hMkgZGEME>($4@ z_r!&rq}dH74!(=)h&-MR6&0Ze9LwyhHE#76lf(~l83xi=x}jIJt^b7H!w8{7p*h+?G8LLA$P^yJV?# zi{E@cM)UF|%wRhEVAB2j@CkYucrSRi1HJyRf`2sQz12dARH)ng6Z0bI{<|_WdxKSw zyq}yai}ZDg;BK~+Y<1V3)sprkcqrh();p(It1Z{Vi4*C26k~SUAEVMs=M~gS{=~q{ zte^E^ZO8oVXpOEqDD)R2UUNQAEZD_yf<0GTx9_v7aEBnu z;Q^QbPVicS1sar~3M0yjmW>q1tmh@fquCeY zO&les2`M)O1aQ4jSg_VfVQFX9Df;C_q$30(V=^Vi-b6Pz0oeDwdFFqwJA>TzjnO)G zWUHI*`C}1LhTrr&;dcY zCPn{vK^>yl|K7Mo?O@d7MwX5B`wFf|J_B&vNXWIkfzS&mlE>w+*X2WAPrIjNqyqI5 zY1_s0DJ{VdbYN;W3^S{Ui3$83oCGx|O%$Nh_$hX+-xn?2VWR(nYH>2?{OU&iut#$>*$4(`dkgf2@9%!M*OA^6vbOzi1m;-;gRtlxck|8i@R^aDq2!~ zELAq}oV&0dLX0sWKuyb?hQV>yT*dtdW&F}LW9lfdX6yicv=&7%+ghJ#eEEy}J`6G0 zOntj|sD$rQR*8e2-^!_W-ad8RpHT-7(0?0F!?0IuzxwuxZSEqkPlx)L$qT%wJGTvansx0HKIm5XQwy#W5F*uZ>;z=OmG2v zX8%Y=Yh)^S>blW^f3EQhQ>KG>ywdlp_7xw|7teLcdS+K&P132fQD!7gqVVULE-#0m zu0433rJ+8d_xrA**vG@NRSxi+A&V6I$w3JF#1ud!Uno%Fg~XuoY6Fu@7$HQwXu^HV z7S7K4euY%e3n`_QtC(fZfGtsHl1Su`Wi@*vl(Ex>s~0Kc#@E;0)aev==Lq zBv$$Y3n>vblR8J+ZxHh*e;26$RY(N=`U1lEa{c^8z%4C>|LyY+7v^wSevRqLhEKg^ zD`~Eb{BV+85kQElY${f9pr^Np_L?FHU!;n|8dX*rrYw0Fkz^jws68Nr9Me4e9V&ey zlk*d;z1&^Skp$;q2F1%V#>KQVtgYkqyR*$591w-YW8aTqC#-HKtxECF9OA$HX)w}A zq6I;=VHmzXQ@Wy3gYy@mjgn_8Uf(582P_gER1EzeW7by)X)mT`(ImIgfS^lE6kB@$ zY>n2d6xEB-pSC?xw#NAIs9Ajw;@UKGEI!Dk!J{V&SS98aapJIBhvo{k&iTl2ZfE%; z(iJEvJ-H0z{@$B>K}hx!^)!Uj;f38uYmc<{ z9XAn)R9?9Ax$w!VBkL-TC8_%TpquPBzUV}|^_G{vwDz>!=qn9}h}|zfwaKi$Ff#m` zd2c!tpTq+8WBndI-vZEl`%&x`-)l(XKjXw@8H8sYz|lX1j3lJ2e^3XDc;+)ULZvt% zF-|bt{&w@j+?d?7B2}2l5NEI)9<QoGP{KaZe-X9y zO^9I2krpFm-0&0O z>(=nh)Pq7?#}&l80hzPp`M7g?SQ=2oi`ThIoR4-U?oPn92UPGizw7LKdm-=np^-+3?K}LpT-lP0RL{{EBr>DmIMX$~8 z^7`q?VX>=E5hw*0a}*K+iE7_@7h__k}S$r?%bq! zED0+By)Jy9;d0L4R%hs@H z7p$!@yS4kydzRn;am+IVS_%U>Ar~*2?%CWn15N)aPf>-J+LHf4Lx^(Sd8Sz*tef|R zeOA&~#YCSGcA2_;#Lz6||~W^u-*&8>RC%=FanbQTxC{YWlo3O#Ap>h9pHM*nQ@)KcJRX(eMA7MN98* zTF-AJIz2?h>NE_kE?Vk2u#k=brm=cmCNz zCpvNL3CP*2>|xSPvk6haND*f!`Kg6WE1VkzhMv^c@b7LQE(#e%W44=7a`<37D1(0_ zsdc0`96|;`is~jvH_r$Xh)MN^2Fv8awiwUQEFN!9k+`L>{O<2~rT(IwIignR=e@c9 zyxl9wZonv`<(N)ShGYlG{O;%4jiBVB#dY-upU$mW{Ai-i}r6hIOd8Oh z+^rBG(6t%ZWy-gSl_#8-M@%sq0TdW|paUk{0x)T*LP6jhZp$)F=ZzpSS5GIp@2!&f_q=_bIJz}CU`Ffj|ywJ=BysIP?r~kYPv=l z$$8&6mijM6zxq9VTJV^`7^sr@jZsZVDr2jZ=KZik^m9b*ZO*)WxGjZkksmQsuYAE# zdZ#47X)HK&HW=P0oVe41>3E<^M>%Npu|Z>obcw1(eiilEocC-)=Ev@d3QT9K=gcDl z6W)*PTU#%8OC(Ql5<|{<;PABR_pQl~dsK8b^}adBCkK7ONN`Zah>adb^z! zbl`?imh7pwnn{qzl~u8F=rs1iVM@E(??adj>Wrzj#cCQguTe+l%?ee#p0+!~m`@*o zT%Y^P`SuGEMI;OJ)!-z!s$$C9eoRXZCeuXGr%5;P{vwL`a-;nGI{u&zDgfe@ia4oN z_byPz6ia9B%Cy}T)v;J>M@Y@HgTI?}UU8E=fGKdz2;sE%SFitBZp40ejk11r+c`NH zqbV=^FmVT)01w->gAQIJ{hp{?1-MuWAts%xgSa56vCL@EbuLn81G_mWe}!q(d$I@Z0UVF;uMb zBlY3?XDj_v{^2t6P%w*U$xIW;97@C6qCbKH(`}@xepQ6v=m7;hjY3KONEA#GXZJe^ zD*vOe=R>Z6&|Eb{h(vA!!fiawyj;A-z4xjG-S!Vfv9($!uOK@QYoYpY_qZ2Mjz(@i)8Kt@Qr7<2+J;XfGXnOXr;b2y&Q z8)Y5=4_fe^$3HZ~Sj7*pMDGf%d1i{>bU}Ms05X08eH2GbofFl>N#aJ-Sp$a$ zQyz@ff)-%4p;$MWD4GxDcuu8vgkXiu=Cec9##%Ymw=)H8RD6$?|?En7OodV-WDDY)emJ*RAT%Z{5&KOduyOW zH*#D6^j#xqXA|Feur6^>#&n6s$Cq>JJmW*1%(E3KdxqiP5XVC_qOdgh{(jX=H60l7`n7M;TbuBNesRs{@{ren znC*6zWUFjZt>UcBhHZldiTaOJ9Qdr8!qytMHnC6g>B5S=9}H>9d_6T&!zwEtRBIoj?@J_~${=FEs&SAeI2V_YI=CZ~<`D23 zkr9CqtDfU~1m3G;EZ2DOow?$wv2u{_qnb-i$9@eMZO7jTDgp42{~-`w!E2?ED@Io zJ`PD}siopbd?^Oj(QgwL<#3o^R^t^2iUAkI``k)50Soobb27jT%rMvQb-Ecn;p7pe z7PArNw3UDBm_+b!PVOv)?tOCV6M^>B6stQ2i$5B8T#GK3Tn75}2e0z9M9 z7UNt$id}v`w0$bCnlnO{_|40<#W+d=0Hg#bqCU)0_d!qSIFgTAUgzJ|kWY<+ym~MZ zRmy^JTVE8CrAGoIGRvg+>LDbX*QW@$vyY$l>iq-0D#Wxw$0{ew!vA&D@NUg=G1R$( zdFH^P`197aOlO}H9>Rre?mSy5i#R`p0nv@n)0VNak*t`N_NXw%FaZmGMuO@{08_?X z=h0DmFBG1cEr1v0>+#FNv3sN_UZ6PBT_^cD96#g|fDM!DrO8;J#F5Uk-YB5#lFl1~ zjs5VK)}B~@SyT7ySUd;BI>_f8itbBt3Vh9;(apKks5DT}h~jy?Kd>aUIyO+|pPo6( zR>P(HJK$#fu9@lC2}+b7^~%Y+L^qwfwz~rD$bq`FE!wfEY63zH=s5O zkgG4Iz~iBH>A72d6h5C`q3+dEfApO1xW{r>2F|I{TJULHz|(ePwmOIHhQe9=ER5|d zik#vA0%2*X{(!%(k(ejw?;Daesz*SFpH z#pO`@ElNkSo`Nn?Y@6l+HyvCq)EbwOv6f-=p}U7&&cI0i(LH;boQ9ExSyDn}?Ik4N z6yYV}XW6hq$rRzCLNYz-xIN|RJQInRx*_ImyOHEWHp}g{)zd3oqWw0QPpVYERvJ1N zFAb#;J#>hC&(OaiL4B~eY&b*9&>-vL$?m>QPThxfeR*G7U3_N?eW<-(G+k0>w|sGT z=(?fn_zK-$U#+nU2rT1Q9qMo&{bH`YJ;0}Fg7Yq~Q5cHZxgD*xtB$dCtn}3cCy8?{ubXCWz>O~K3#X0R`bU_6ZSSc5 zT~5&n<{7L@V)zdh>AEyFNvmfLU9)bO{)2uyO{di-+o8WV(!gRSu!JDX#7Ein{1e$$~L>Rld-ln`pO z8)$T_;dBt+52OhFP*PCcS@4uZJPev;r2E!%^Ts$^r-xDT^H(j~+6O$8k+!~x9Vx%P zi?#3KYY)0ShCZJmjm!T82mi2odaQuv3H2N&^peCf2Gg>Q%7F%4d15!q|`n)rjq zNs!*JrIkvTs*@P3w2}TM?v7ug@Xh6;jF3f;1UuzVG{}oP zJG`0seJ}azV-_V&5iWOBoV6B;?F;?#rJ2-dhi%3`CU&5{m`uf2|gCZzxn9hN!1^(Ly z3(pc>M!61)RI|JzW5KznN$wjp*C=X@IKI5Nww|mB zLLZ~|B2LH(qFwvq&Ra@FKSrRm7dF-Ff~B3`#PYSg>2SdEC2+Bek>H(UXrmn;(SuCK z=(uC?>&9SKV{*MPlkMQU5`I7y+(M=uVufO!AP4hEjS78A9Ir#&{>{@9Q_%&006h_r~0Aig=S}U00{;o4*2Bc+qthBfrEImA~v}r&D0mW0>6jV^`37J&{Xr z*bVGZS{*sx1X1@sARcbTk-YsN;=Mnd>_SCD1&F^bW)I9w{rn-)qJd6+QsA;;;*>gZ z_%B{abStb`y%^!UcvEl%(IdS_jOl_+{fjQV2;DVy0(l_wzmx)myl8p;O@FlQd%-b- zj(+W-Wvk)~eM0~)y|D9Y$=j+tG?^p@E79FVse|j1`5Zib`;ACrh%^I{_XJ0^j> z4hY`4O&s~<*YzoOgznznr2mAyGpe-nk{}~D5aQ-$co3|)K0XSYHLQeDcuoeA52k~Th!Bkc;mfXw=`ht(SYZYs**C;W|sDGu;pG4|VRR2YBn3&0_ zRzAZhb#f{<&8S#h!G6gONo;6@Je8KJNWY78{oNk#r7R)bG09z5ZMi1ltD$Y~@BSPG zj;eYC`9sxCPVK$6Hh|=GCsnv549=!EP3aV{e=DwJhG9A*t1rn$R z}jg`{D^`b)9JCL7hUE?xpi-Yl&M3{ zw(5FGKt#-}-IlJ{cO)-S8gJ?{r8h1i&YbHsGb9i+u2V*^Zu-~w+rVjTSJyDe)z0R{ zR1)El1-Vjvxhzc_`%83u&~z8Py5LhBaDXOS(ZdFE0Cve)@%k zX+JhAj@PO1!)uAZCr=dFzEo}?xbQ&!2DC0|9Zn*jhz8uUPC10rfLZpLli@+LEez42 zFFAsufuo++Gs7*q(?^H~0t^pSKQDL7avKO5|BCoy%SoJ;j3EEY&MVHvVzqg6%lpc< zJ^8DpVs}w1UAJ~aR1Gfn=(}!EW=%#eXL6Q0k_H}}?0x*d6cP2TG$2c0Fuz_wos{pb zXg$59KPHt>ekr%;(RmUD|MsQ#@Apg8mT%o%JjTy!~!+hZ!ys6vcD-TEh91*?Fx9_56xC5T9G((wF9A z&#X{a20Rlus!P-F5JK92pAKx7rTH6K5$LBM)BztSsa^l$iyHz8O{DatW9P(~LxU zSYp}|BT0TrcUnZ6jvy1(@4k$} zyCjuZmiY_Vy7}18POne--gg zqaix9ab=U%E6QfBiF@Mo1@~oiqS>~BP52&`*@J)?T{L@~kmjvrtMusY_ z?*BR8`}{oW=9efMipc5E`;1G-8D_2(oH>V$Kg8i6Ry}V*daH~Mh!DuX=+{gQt&KOC z38ImmAwPy4D{=2}A5b=;{lo-A;s0_E$?t4Wnj8_R2GS=Nr+s1Xe`^o&#$rRGpulz0 z>&s>3&CG64oHlrZ0O$>>l&dXH{IK~Mi+9Q=0zv6U>SxT51Gqt^X>KuQ@*E5iqDZVr zZJIy%>1$=Bzn(+yHwi=@dd5rxHe3vE*Yl9#(%-gp@#qk(W)&CcH-=c-*!r(hlnC1!72nG{$j}5U=aMz#xAHvyrmH z`(f76uXdu6|NJQ{uYc`B{BF^|a6i2&c|Y_*0VRW)41j@( zuVQvD52c*rb81_hpbld`q9=nFYgb$VM8(A@JpFfKhBS_gQ0FLrG*+6_T&m_pxDXi zxZ1L3fT|ziY1@DVR?@*c5Sc$n#{2!3{a2cKs!8kEvY4%d=-l>oqWP-)%} z@hsevp_iI6v|F)a@)K@+Jy2b0CVvqqdenqfbpi2oHbGvm`VWWf?EZIlj?$3q7599z zFPxg~j_SZ@)g~H^&H!dZDnOE7$Td5mnyVxUa?5i_1)?yzSihy}a9+#L;zG#)Q`}@> zq!Cg9E5>^L-pF$jOw)j0IL_Ne%+9|jsQV|#*BFD2z9SN~bRpPa;2wewy4czt?OAFa^ot zl!yC(N5s!go=M~Ym#iY{875xOzCJzXtsmq)@+Eq9SrOc7y!n+r&GJQP1uJc9M!3&8 z`KpBbh}D z=;hA7SabtIj}3=@kl~IuIEVq$C6yrXLYg9fbr%xkAO-G?k=sy%-sMJjS?sA><-6DD zyqyq0)=EsRKqmD1GKZ!c>d$+gjCU|NgoNQBdjCp^8EzTSk+o%d359bP0;_)n{hJ0NPQ;u%gY%tfOas?i!QR&w*(btB^7$jrBs*fbj4MD`JqwQ zA3WI4@ocRwp{!$@c78IDg%9n-7R9`m&y4LN)0cWDx6qg14Ql+3kjWPu8KTDS8U z7ML$4?CJaIy|34r@8iJy@|~J986s|H+>2of9FB$9Juo$R0Yy_~MIlB2t_pJYL?TwV zH^QLd?C+pLC@00qu6w_AVEUtXnN9o~pzC&P|AV=Rdamx6g-!)xbU(_;@D!o!G_=U( zQwr(O-$e3}@G*8N3B>*$Hne;@yZ!Kr=bbCf4-RjZrQbU;tN1})X*7XWjQvDRTqFgx zMUZMynSfh%5$|o$Gv6nQ__cE-BNtQf*9nRu=|md4urtCbq|=lgYMj=UR=)KBAAg9jK85rWSOc#ds#RSGm>u71qaYILF5UZ+M^ z441E3x6smhT*HY{R?gjcmM{Hu@29coaDXU_#O!37#C+uG%Pecw&$m!NM+fw)&N@kd z+m)LG$S;8FL)E=Mv&~}tzf(pQ-{)-M?>~OUcJ*h{Jd@}z!lCy@mcPhYer5Y{$l*6s zZ8FejP$2yDFLen$*wpSs!m5>xEb`ljgv44hIn}XSPW~o)7Oq&Aq<3 z=Yv4Rr_V}Yo=~ffte*=uVF)rJr*X~{Md@;3eMJ6Z$>!l#9U)Y=#Wh zr(z~2#s>)816-S}cx%AqXo>@5c1`82Xul82*SjGLq zSEZNhGPUX^qR57!bjEYB-11{2@&w~Y@M|#=6ju+>jQ__h?xO@OZ;M z3opq8(fgTZ`iq6Rb+pLT-2mes2!aII@p;5=aVEY5I`A8l4PfM| zag;_$6n~)4^{kVgQbEj#K)9Hv)vfl8sH`J$OipS{Q;6Z&#Qq*Z+uPodTb}%v`Ry*p z>|@>bmS8}(r3rllmUzfj6cY(rp&jLH?R+BU>?^z;mwfuT&16g1;kr~XJrtDO^~I*I zARRf-lj%?qejrlvnz{KT{b0dYb-f51gao1kBD%N$HU+W*iK=hE@8zXhIYB^Vv-5LB zrV#|;5a7iUkU<+Skh8n#Ueu8wtpK_?vm=ZNCxn0SBKE7!6Z7ag zk!8~k@6pGDzR+L1(%~L&(xp31CvFap5wgue2z?kCQ>d!uTbqul7}+KETNXtjp?(2x^m|s?IUTmh&_8Ohs(qOx_l4mdx)>3Oa@1zTIcD`(7FYNNz!HN*z{wl1$W47?( zO&vpI*M4dhfj*&OT5@0Gr<6k7lZ62BxlH}`qfh4~bX|h6b)DEFMCRj3yf*#S*Xel< zQVpf+bA?O`En=)h6=cnei^e(Tm~r=?N;oC+T;A*OnBQ&@_b<$c6JE9Inq4y+yN9fU z63f04(*Wc=3eRe*!|j_z$aU{&Z3vw5lgUHclxV?vY3!S8Hr~|XyDVAOYLc&P&+0hy zuOD9ZCTt+W=*M@@fIe9}$C$s3l>F7RWq<@&4-=*=Gnt9H#UBRzMu=_6tDCeez}1 zCgk>S`C1bEVrBi}8+TZ<8-=Tqbe_XjX~nw3$e#9{G2cMP+#9}L?T^;z3<3qb$-Qy1fT*veXz1Kjw>hF6CSVpOl>eBTOPF)|%f#S2_Tl2?2lfwMyy_XfazS0T{94YMO> z9K{cbq0r#S!8jdANd_LD5$iu`rficBy6ykGUT`tbOez8~B@n2qe;oC9esEMPd)5(} z6pBF}S>c+^MX5+5IVS7(or{`P1;un|<}3%O{fLP|LjaZ3ZnCIcp(O$&ffE32~>Z^w6{hw`|mOcufUeZ}0y7csN!BgB<&|o{lO((6=7YYUpj>tGr42n%$W<+=H+Hf8XqT>B> zPe3<78XZkG9HVdM2{U5*fef1%_$!dH^+=HZmheQP_GtZdbFzE2jDLW-s3n_M@ zpPyNyBr!rNL8zTgeTmbEVoJea$7=>eyfAc-RoY_Ib6J*`T0C#Q4Zk%X>X24%YyYJBJ+yBUz(zv*g{qDdu^*?tVad%$elgN?4M zUi(r(?k?e!#@=^st27kvh?d?LN$N4X$c&az7Q93yr32o75#VTjb@-Q?%e-*(iF5k-TfsJ**M@IKPmS$bvv^3q85EAj_*Q!sM;7aC%#DgOx9v zMPjDYcWgwNrFdaLjA7Z1PRR(^lVvfo+~JTYx3-fL^M(r&M~e4KgJz6_NcyId%23tw z@OTni@$6Fv(I!CghU9{gP<@QQaiu%6dw=L!>%Wc(;n{$3e)%YQ4X3A{vM62w; za5HKqDw~)D-O{_o#_X5ERw*9E#_X5U*ZSm`z{V&Ip@S*YZv`^%nd@9koY0n1`(;6g z`n=K<3|1p$oxp9ZFqOt=IOErS<6fdP!4ieTleOMi|9~z53xmHn_ByaidSJPwa%Go{ zZvefunRsK5ov9hKS1Sq>qb%g?R#KCO1C0TAUWkSzk5oXwm#b^bGe===Kidb*l9vTl z&*^M3=ck);e;ntUPz&XnFsaZP$7$7edUaUcGK)Djb3UDdx`SPsXccbAn@_u*C=|HF?U*mxvpHN>0O+hI>}wn zbX%z#2?~NXH2J{A=a=8{LY61O9PJLUD#9h|ODMsUtVTs25~UAd;a=ZdTlqa0^T?H| zF4d2&bzZltJ>Qh&#O0+bM@f2Ux)p7^q`;dUsdV%TFI*Qnb;GgZqm?iT=?5NPL+p9! z5z8`nvr;vW>s>`G+tvt7 zOyTke6^CyV(N(9uWCF1nj(?_h81`ue8kV28?;Mp4qlstENXjW~e&?%QS1TCLL$jB}d0y(3eVOMaGr&@{@>wubhDv;;{&)MomVHn09 zn?gJ$J~XmQ$$~$A>h+imKNJfLR!UCKrt%^7M{e@zA8zn)axUp-5y<9`6Z->?0!$42 zS}lW@)gLb6Kasb7bxj~S)Z`=BzxjzTg1^8WA`qw*EBz<0C=1EsXVcIC8+}c|a+Q29 zKIUx5;H|G39_Itp34>o3gLq01lcJ`o?ymd=s%a8lUicd68)kAmbfmiT!3Owp%xXIL zvb|SG{AL4)O_NVW)S%E_(tNZ8%qIW^4dmvK0`ki5h+}g9pAO|eb2dB&^tVen8|`TL zQs$%iwo0CJ>zi1N2@E5Jg^!Rhf=EOqlW~aXCB+f9F%nr$#r$`ubuCcv$ZiKF-EYSs zfeEB->&Qc}sEjtuPdojGOV%mt#XW10Z`PUfrd8fc%M^w2{~1v9(z6e&Upd zG>^G6LY8Tlo;A;?r1meT6XbF}q-~npW>b}R0w0)`P7MIH1_yWbN}NMFwlm!tdQp3e zKoXw%-U7yt)8!k{(w9L}=SkT96F}Gn7swEMW|{AfTk2_Ba6lw<0fN+;xLtK26xQ1g z!f)qWW29WZf5_UWk|aj5GLq^D@;m)zb%xgmgv<|Ri|tin=w#g?mWe&0Y~E@zl=U*> zkpxxiPWQ*$S6Z|FoM&%4%+h%ifj(~O7e3jDHYxLccD`5DATQ^8;^(RqvrUtHT+2&| z9SLlo)$7_`1ZCriv(M2Tqw~futMQnid=1q?^;UP_-|RN@H5ox$uI% zPH96#N0Fnq9@D(T1DVVJ(gd=uI`LgFuWhT*Q*@XPc@sM{our@rJ$Y!~$gKA*=t7oU z*qtD9FoJ9x?DFtb2&w7!+6Mjm_wAu&-v1_}AuVEmlXjc60C)cjBeCMl?Gib_=>XL!vwTt`+IoX%KK_2ozpvTLbWn6 z#CwdW@e5lTiqZv+Qe1E6EYvEsCDw~hyRJcc7X|}@UvUway$Qdku&Prdvhmg_9ZLzx za-#fqHmE!uz~2NYBkJ)8m0zP5j&8+&5B%I>Ii$H?7!*^>i@7fO$?>z#Iy?FqT~eh( zILG}A>Up8HYx-3{tyt^FlYs#gUVm2qjyYAG(^W4Rm&E-N9Q^6Hv84pa!eEr}5aGfD z8WPA#{!;#{rvuOs$#0quhI31w6U`&J^{xN*wRC#52b#Iy_N*=LnyZ3Jv`Fq7a9B;gp4@eG+UUqhF-;~#mFAVr+ z2@R!By*xVK4s-Pkz@*SLc$hV!+LvgwY_pl0e;XDrd8zEtM&U11~o7%H)umb43y;6{x5^%Dbql0&v4wah2V6NkN`@%nqiK@{zqwJxRk#`6x?q(00vZkc65-U*^(&BD85d}`fpcIc_v@q6E6%Uo-0-&MKVRAWlV4o%enMp`q}Ud~PdTao3 zdk3$R3IO&Q+1l0w(yAb_ITQK$#RwG#7<%-Hk5B^vE`!p0qd|#=V#XG&x&SGgyMRM( zqgBh2c$#$$5JHC^Dky!9>OJ#IVJxCi0bOX4_x(12rn)2`0Bi0XXEgz>%GaHjoi zQLHN^Ew>OR)}`J&%CDZE-y@~oc!S=vDIJK+yy*!~nYruO6{pLNPRBqwx~ZK+%=P;n zA@S8XO-DWxfue%^xO0{kX63$F3^y6#2IGI!&*fwVOeDQ#aMHYu)h9G6HM}!l`4521 z;&Z)jZQU-Hr@xf;E~M{^OFQC-M7$60FC|aiR~L^ts^!LK-4t&zP;%54`Q-7H#@lV&XmA_HnH7P5A( z+tT&4R2QBTV0zSYC--~|j*+c@nbIq&-JMXvAX76!Sybjc`&bxb(A&_@K_0<6HdI`2 z$@>BUd)Ctii>L0V*A&u%;UvnTE;%fTbAPy_?$A;Qb0gNx6cl1#-Qub3fL-(BTpJ*zkYQHO1^Ujn}6(Y zCngw=lqqNe^ql_DhOFZctH+5i$gO=t)bXO{nqk!71!F9vcBW~|5JSI`AB#<0x$o16 zpiKsd9^SovH1B^VNSVh&6bwr7O_$TPEqXCNT<-mR2l52=Tu}GP@QWHc9 z(aeo{FA8uENCSQ3{r|i33L0AjL4S!GG5v=pF%VxQ*V7#^L&531& z$bt&y*VQ#_ArwJlK#d!!VqQ2RV-K1{WHT3pMe2*uvb+c^A?ST4=|c_hpTa% zWN6iRR=fAUq1Qc>TCv27kJIZ2MtL>jUxSLOJ7OeJz=k!aKoh+;Au0eM<>w44RDiv-LrA*=x;%9;*X*ST>)G;A~VoX|lNu-`;(1l^w#3oL``NvDoze z_ZOAq4PgN>!}B+$&>8uhyluHk4dRj^=D#m??Aq9UrKo!G8XPP?6$_)Nt>$&SCA4}* z>8yU>E(aZDDmS<-%FNU#Ma7dt-8m!2Kng__yEx=tAMf?WJWnd{w?W;;;8*Vu^LNpv z2fALkf>XH4zO?3TJd?c_IKBBrI+iR-@|0gUgJLiB-`d+)!TpkEewF~e@Iy?*A6qcn zhf0>uZwi>9;Ax2b=A$EK){m_uJCk&3%gpvyg$s+mFLfXU1Sk~#X2>`SrtueNFt?4e z>qnF)KGl@z4S#m!5mFj~(L&T5Uju{Bhqlt~>2!hm@71&(4wmu?)U+TP%#kpA`Vj#( zw<_h}_uAGI!tU&v>#~fWfF$`U{+WGe+uCXJK6q|3RWo_A!Xjmh!(%dMuJZHAd1FdK zQbSgB zb9yX`?z>E{E=qSI=@EZVf?atv$YOwIaT$U7*>&-au8ZH^CL9rXrA8nLWtD#wFZ>4q zB!%IWgSCB+uSWF57q@fdMEn;MzB6{l+*;eM43I50(ZYis2r+MGkAMzPWh7P{g1J${ zN3;bR$@FHYi!1f@vTL96su{hgF&S0YMzVL;Y&%@Yc#Pyo_C_dVNs6lrcQ%F+zb=jh zqE!~Pg2Za_=W##RAs`LER8{?p?B}wOJ)yy^&|gwNc}Hwq$x-~Op>4`o$Un50muu!) ze@?P*GB=V^OSYLAUaR}ggT1S&C1%i{2{{JS_9u6d22`gjQ)A_w*{4FbN$4~0$X;XE zXEEqad)M)RMTh;9Xh8%;-GCl;j{Tt)wo_tEX~r(;K3$ydJ~Nr;k`ui^0z=nVIuU{) z0A%SO#x~l+dxrlYEN?@yq$IuF7K_F3Q1R=p78Py#bU6e_0ZFvVL8SRxTBXBmwn%q+N8N&r)0Clr(O@k4t~h5O&lzf z8yWVq96Ov4{y{Upog7L2edZ~PI*d{5ddPv{q|EOpxmXtBISCLOs8<5-d*}36PX_XB zl$8ua94hvQP0XE%$k$v6K|l4<17{O~WC16Ank@kK?J2c=ByHf3(0HAZM5a-P(I0Wz z61ww48v6Kd>7F-~12S-O-m&TG2IeYvf!-V<3b|C22yPaj&(oG_`1 zFI|n+l9z^u5I;53HUXow%BDueiZeZr`9cWM0B5qt%|2pMgUrkyVaZJ=++VBF{_j$X zP=9&@3CF5QlBX|2dGL`gm!v>dYNASWtKT?Gx@6R0&(%zP(NpWPJy?D~<`JsnAQ-S- zlFJowwSD#G;M@WQ$il%cNaMhf1NSI;mf&fmT(wUAyCeY5^=A_<<}t(hqvo1!M(^45 z&KsA-kqAzLHL7aZf;arV%iSjjGd;3=CPtlSl(E#lh;eaXQ%d&?EL zi}I6_vv)c^_{$XfR=8Pf+B}{P4G%X~$I^j;kOw}6bWUlM{i}>&=5p+e7f6bp5xirO zt<{^B<6GL>v3SIJADaBQ;UV#*IOb#)B!F2$(alV#J)m6T+C%L8usl%RgyeTt{pwPY z{6ma3#0g&B*Ny_W)g+J>)%tOs5iLq!3l3dE_muiaf$GElt*wk%8rY!i^2emjvv|nn znSG1=t-3G^8vR@3jMd)TM<=y*v?~=ZY()*QD(yhLZ-yXU?aicc@+ls3E3uBV#V3xS6~*+p8O2?^b5a6>;YQp)4d@is884@dl8!ur8+*54 z3`HE;%W2g0c7g-0>e@2j}-!mGF-L4&#E{;;{MOGqg)Y*&8 z7rjfj15IdrMC$&&Lb%CIjPLi`5TYUc(;hC1 ztOgp|Y9~ODCkDx1g64L;;W*Y}hz0m$F%=%C3r@55?X>)neGA1esXIbT>$nJ-w2JFK zB zg;h^-hW)&Y&eVFeQ&+W%X{1K2U5IkD^zu{56E&TnVY$Us~Z@q1A!k zu^v{3;%ZlikpwyVU*%#UQP&HkIpkQpDBTcvBFSQ^r^^4OUb!v?G_-}BQ#7_4m+*GE zr>1#VbAb=7^?pXqmFD5&Dl^7mX9KkNvw4Hy@l^LzYlKmhy z$j3e$Tt2(ptGh>8$X^OpJvfXJi9Ou-?BzQA^@$4WC?h;yv9!okO*++kPBIvA=PFw!2OBwShqhrbH`d8cXF(h$Q46*EbjN*q;HxM_{;3*@A!alvzrn^adOP zi^het-&BA)p?{g?l_%R*E2sbPt^t~m9Becp(d%9 zW<#VCNnP#&9SIhmvs6cLpMAt5?<@=ZyhVfv9nBs93=x!KpGpw1omY2%7tg^Z$+pTSyf~s~h=dh)feL8P?&Zl-;g! z0_S2#x=f*zxEKLYx>swq-EtEAQDGhZY+jtw@INw{zfX9q2 z{CoO=fW~v%WFvS7g4-gFutn`&pR@~`WOHBkU)qA8ZWTG*W+AOvg>HdUD*d|=H)Fpn z7bxz4fnVK7Q>TvQpXk|VPhYWIdI3=PJX(-#!$Jc2d{3$asl02xG?6Ray)ua{Ylro{M-GH&Nl#sFv9JU1=5Bnq z{JXGt?!6FxMU8fT#0^wM=?6BPj%|XkZV-;+yHi{`yJPuf<1?N{5jOl zlH_P5Pd(8$Gi7UmZnzo^ad=EDVA)0|gT`Ew2Djr6W{5;2>#SR)NSDv1oOFbri~ zGbit1IyYO>bxi!w_v6W3G8Q6xWfD%`(!e}zS=NP4`Nm+Nf0AqKk?6U{jGZ@;cy zcPCFW6Ud#F6ZCgV@Q^y8yIbxtETI9mgOSQ|TGD0m0zO3EMNWkTv#h?`8S|$@-#rd^ zFgfpZK4KZ&&&$u-xD33CQ>HI<$$g^D>!00Kw?3h2M5lvzh^@)nms7Ej`fWx$hihouVX|-&LIll{A9Me44p+c-xFMtf!u}ma%+UyhnI~>Y(H`&RyxTY_OyxuomovMtL7WXjw5iMgJ*{jx-;t`lnE}4;s zw_?6Qb*mU_rz9~bvzRd_80MNOhWIx)nm(oRbb7*Hit_YJ;|zfgkK=&J<;Z>J-`;7L z-~(cZ4Pm2z(o&7USII}$PDRI2g+WulL%q;%);!52={%!{6or8}U?Jl>OR>kpa*O3Q z^pS)TSzbGT8?4P2JVziPYBYXBuODnh57*ndPj80QJjnF28VjR-lzWanZ{7?ZrzRO`A7=deGI4V)(3X6mVOAQky zWZyR*9f?dtBJ{djUJJKf9geQ=M*xtrlG^Ew5DsSE_>i!yW}~ro;F3k*vFaL}G-=gDAzWpbi^nR=)U?{VwL6E8iK^4NRBA zGz0)@gXP`>b&I^@2d&{EtQ*TT>S3Yvo6LdkNk6N3?RSX-rAZI|uwgc}^%$)Z z1kdy0Y1v3HOa z--z)2s(ZD!HRc+9Nj%+|TpuxW^GnrRh$`+>|8OwILh0%oM5?!y(47eyTNQkfn$aBEFQSQpNp}Ne1gR{M!aYe3 zoHQO+q-VdeAfQ7aNeTNk%gb^|JxcL(mR03lHP=~hAHXNaNr?qk#ro{EqQE%ph)$xn zhXP3`ejGD#>P-K$x!Wp2o*3 zpqYAIkF6y)qaxGtVvVZms{p(D5Xm1-cODroCLRoY+C4J$7U;>@jZ1sLB!@cPmY&h0 zdZ!0k<`;rphnf7xD$Wyy?W-)7)(_h#+27YBVSfsiTIKL6%H(%yABh%-ASj1XDT4V!v-PG;dcc= zS)aqe6w&WRIx3_*56~PR_S(Y%8jaTSnfB_CrV^}nhr2_vMu+!> zC{0TiRAzW+w+u1spb;sHRdsk@v0ir3Eu7;5>p`KGdVhL(TwFJ8RL{$podz~(j%DOI zjR!}ozF(&53dSTFWpuC*-C2=E71j3aYf}fYCN*Gak(4X2y_}4HP;d(UwA@CVH>==q zHl+Hu(xFRH(bU}g_HS0t`6%2ANR9hpgQyOmqvB*3wKdl;mto)7e>P^Mx6|FIisxtC8K2Qw%@P#unTlVF z(l`vph}t99!}x`)4-2#n^+xjArsCnO*|ItE;h~cx14KyB(CcLV(002TDZ<$@ErBwk zd4lAF4c#xhAp-t~47wr+k>p)SsFCili(>c9Z=)NVN#%i1?6facEh{Z3fKK!iVn0Gfrm8g84m z8~hdImz-@t`x+bMJ&hQRGAHKCArxG7Wpu(k+}qt97Vp3CGP#-fZu?F|F_?A*K)GJoj%8d``nH& zz-#(?mFI)s$8*_{^g%KZS2IEAE)Rt42pu~QXtwPr_k4r!>`vH(e82+^B{5<&kHn4M z;$OXdhK(jymNKS&1V*Wa~`3R29{k z@^&D-V6I-JxyD9wz+{)~svN(4mm)@bkpmZn@pA2pu3l`%(qg_npw10wh2vH@L{ji+gfPVS>UT}occI(s1M!&TyO+Yp?jpCR|erRf$7}fZIhsr*P=z&|j5U=;s zDD~QJN!EZQs$3O;LxJ7hpMR*}Y|0C==ZMk)-k+i}a=rOyBw?Sg14L z&s=sBMiF(3_y&;$nr^cX=OJwEZP3aW*(&cP^w%b5Wa8U|U@iLGR_c$@aLuefDJ@cyh8(|qq> zWHXEu6x#YaTHAM*Z)T}1h4I2{-2m;{I18|9zIsLfFbQl#2CXsyA$Dfa;W>8N@IDx+Px|g?vL6x#9aV!>zy8LW zXCSi4j-uTMrx+3OJz~*hfm7GN`qk^(+x>4Nz2znM6t@UiIe;k-634w=`ICDU3y1nS z^nq{9t^7u=L$DBk|0>i!j&WrJFA1eUvN11Mk|BsppC4&mzkzWsdTmZ$!N_7H6JwW8 zzkHIo*QsoLV5`i9mRL&f_$Iuox175FdWfcS`4)!$Em094-zv9)lv`_r z%rVHw7bQjv&f&h+xPo+2hS>U4=9SYy9K;#= z4n1D%?%OAW7_L6}yf<7*&l)qpPwpDHTW*iEi_lSk1eSjAAOlR-r^o6WH%Khz0p`{tg_;Qr)q13(1?A_v z%DYhmQ|;eitiKA&{w}EBrcL_AY-%7uim<+F?WI`wgvw28!iG=$(TH5s0X=+227+kn5wmm}OZQci6EAwyrz(rbo6;!#V#CH9V$Q#=2 zt9x$<`{v5xgDNte)AjW&%!ZK*_6I#uf#3*?(_!S$0o;Ew1=>f6Fy}V$Gjw=KvPXRe z+PZ-?RfumHZu-tgoD-%9cLww`>viLJ&~B0^Zi$a+JeLCj@Sd(cR0WE3zK z|GL7IrOx>|Cd1itKC<4AEpU#mrJY9ueO*f+rw4z8H!kRtCsIGLe+-4y>%o+AfeSH;5Hr>y|@p-90@du+* z4CbT09+NmSBDS3_Wy-+a@eaC&+J^$%A0p9#O9~|FonkD%0FA0<*g%5J`P@`Q8ukg2 zN8=e2GbXkMy?@aab0rhWpj-m-Z+zpw;sFj~x8+Vy=xm&GvY+o{7}||XJ;dd9^uwc$ z8|3>A_Zv994R41D-OY>J6c0GNtkVMAB^J&%IpR{1-H!fck>1+jQVqrp0!BX_5G_UD z=6x$=3U_G(!=F9^W&Q>+4+#ba!9gVS9Yd}599OSk$(iBSv4`#zccOds9gyb;_jGZ0 zHj1Dqi4`D8N{!Oge&sZ{}U%7UgJSUtK82%dyf$iQ#_tH9{|brepWb`1GZ`uwIx|YPtjAv^b{ek~SeY z-lnSaW&XSH)>J0pD}t3mW24fYkGG9EP21r(%ARR`plz6uZ6? zb@xz+;r_>9+`2EDA65+HEHRN^lW~NjAt`cRLm5XODaEcA9n2&=NK!wzo$o9a#lk#v z5Fgq0ga*MooC`yAkDPH2FDAgni^wbWYHuiaN5w_SEPwqL=!0k4RtmvsE>hZ~MUfAF zaRo6_H@tA;|35{#I?va(a{BZ}+Uc-WdAQy(Ab?vxa%zv8BF*>+&g|bND(`#y8sGqp z!K5IN%h9ZBKiE$dM5YMc^@jX-Qv#>if-COa;bsFFXTt>$?DT7I3Nh7H4}v#zZ2>5J zO$m_HTHU!u7b8IrB(;jtQ1c_Xqn{)CWeH^(^#*-|u6}*>`V)Idv;}q{?~7wEMD+d) zDuY^l3A@EUpKId#G3Z^B zeW5~0l!Sdg>^FqTIhItvhS&;A?(Fi)(m~GOAho^ypK(StCTn~SL}nE>!Or( z)^pS*;O6Z!0A15h8Vw2(zq{|*D>qLE$l-3+4_98Nftt+AM0k_z;s&vgWf4M5rC@t$ zTyVir2;KoiU>1TW1m_3+!M9#4B<;Ze>gJ1`4!(DbJN9IB_xd%6@NDL$+dy|u&oBdb zx3N>a;ss}F0Qw({cjZZuh%t)3SG*z4EiN_ zpoVC#A~zWCr(Hwm)&-lHM7eI*5_o}6#EZ+|t%3vQacRUcCw67t0Gci_1%cB5K z-rPrr6O{~q+{~(pX+Y_NFM9!Mr>o!drOOU*EC9@Mp-y=teg_T>DD_*{qKA8#@Lgdb zA8fAdne>sC!o2#y2=;$j;&rDNe&jK=q5!B|GDS&;dDKrH+GdFY-G~%$ zyp*!jBVBxDu)s33nA_WnvTJY&6VK^WB#-va0mk9y5J5infc8%gMp@%;F}td3{KGFL zqO}&nW@V`b%LiPatSR ze8R=Gkj(eA+t5)EaHnFJe39oRA$s=?Hf9|HAQ_A+t6pO)D5Q-=2f(v#JcWPlGcTyi z1)hM$Ve|OC!;^s{WPOGauDK=JK256&uoOxG)D%LWn970EL2mX127IH6CBXO z3cAtz+%!8r6w#tapP)LF2YjvEemTBR<<<4Uf{Q%wzE1buL0nOpDr>prUpYKhut!PW z&dk~j$FnD8_e_w9QPvKVJ=QUn9^T zs0x}QTZEL0-Z=%JngH1rM_N`Sy%))Z0!oXk%|UB`B8&XT9!4qDuM zC74}_-C=q}Wz|AIM4?wT+h?AlVH6I2qAuLKC|lsDPk;h>agh>K`mbobZbxtvyxE*7 z2^rye$L=way=kJ* z^Fh)e0*r`~dum@Ne`k=OD%^@%zIlEZ)xafi+Fj*swJ`v30o}2uSh(%o-g#f(>=bRFXakQ8y!1rY?S0m)Ud{qdT~C_ zl{;N^w0s6DPg=U_PZvkvH}CLEQK#+6Q-@Kh8Q+Ppj%M4SEq@OM#eVi^7Yk;BNoTiL zv}A=MfJU26p0_i>k^!^2LNuxA>)PUmb+YXO?*cgBvaul~C5!Xq3bWB@MR42x`S7Q1)?l7;Lwe&+ z!W05jErYe z)OtgqwKNllSNGwE5VpZnbBoXYHjjfPq00C&beE~_$v zp#;YoIt93Q3AGV&BfLvgFh4u9(n340a3qQao2 zGEQnYFAnb&uQB&_IDjZ>AK0VG)vo^cx4=e==t#pQB)e))nJxjXrGuuUjKWFRly12W z1j=SYQ!{v$utw*4X`tG(^!s-~-={%?SE0r`tDs-C5o^>+f@Dg7%;m_HLd5G4_XyGm zA?vqCC56X@KyPW9^c}B5z6GUzeZsF|Lt2lDPD-ar=Aq>8Vls1TG_(Ff`b@gEt&6i) z?aE51Xpu|eg;bLcjSbgc>EA8si5%xC{58B0R(sD;;p4@jN?!nTX%=};$v56~^^`L>wsY|zwklmMljh`1qWbLull(ns4WGg2YQ z9J~453a`2GkJQ(DE(bO4kw2AIUUY2bTM>1b2Ycx|UQFAVRMWs13SIoV zQmkbMzkGDy00qW_fxAyUV$)#wwf>KxgqPw`FU_V^o9?$3Sa=&^IA2$vXtN0`1&{0; z>{pB9n~hJg4dcdE!j&;G&;46ou+4XR7v|tu`#X07O-1{>OA<9g1+Pu^rMR+5M9SNW z+&L4wsneYv*Y%xFRA8+J86hyHh3C(z)S_4|_rnEt`bd*&=Q#m{8?d57xzL*9 zxPo992y90$$cOC`0o_gz0L+={RVBPtxy(O&q;&TyWJDL@F7<;QRkr3Bp59ow$T+d2^?v$^e#nhyqG; z2t7giZWMvY4$ZV%+Jwd&J|RI6lB!Ea3}TNSQ%Au3fJEbyhejQ8D}kS7iI)S=D+vvu zUp|m9X_nV3$$|LH@jsUWDO-2I{!Qa8S_D~>Xq0WdUmCP7sP7&Sap6QjQ(;9DLP6Iq zRELPd(g3js(opg@IF`a2+nG1U5!C7IFVH#i5trbfM^O-(F-4N~<|6>mTo&PF>uI++ z2eIFq0T11a_)2)geL7FRoMHE?#??~Zwlz7Tw`Aeht3nxgOP~nQc0@SZ49+PvUj+&N3vr*@rN%>6gVdt|3LTEFm* zCpC%xFHzcg#q1e62<6xsb2qjW!*sR`L3LkUWZojyuEXwNg*xTOVWTL?lp{G4oIr37 zH007yJ^~5W3z^i2+ULF|e$1*}gUxuGL3?=Tt5?yiCA-obw`aW}&OsICM zZ`&V2)7S<)O!RqzGt&t)+`O9bV3xFD`PB*3<=-s;7r0}Y!H0kSodvck`U}LRLA$vy z?K*5ejOiHRhC+IvRCOCRPlQ0qpd!jX;k`*C;{C>c(w*s`Kf)=bB}9}Sly46Wusz_; zO+~2DeDy+fT>i`NRkMkzmlgK~IcVk^)*VcIUun1t#LlZZhEg}9oF~I?hf24KPdWbb zKi^+lqTX=;dgMQwaGzBJgP$OEYIdZ zvtiJw;2n#uE5Y1XDt+4wNCZ=W;jF4xCiXYCH3%Lu%&Vs@VQ~ zuYPg3r3RQc{E{E88PL}E&rn>@0RBfc7j2r4X%@@w;K-VbmRV-4n*A@(r?wRMZC{o% z_phKEs7&^-ZBXqKf2Px&CJo&B^_gm5u%F@yVd-Q4a?w{sWJY?k^6~*?43HB|azpXiOvV3`S}s zjBzB4Y=9HB#(KLBPw~plFrS>x`ppyb7W3)bX*?oHsSdHqt8huYJ#t@1l0Z5ZUce9f z??A^Yt5Ps=51jNbD5-hO2;WBxtvgN~@M{rns3xT*982ElwW#3@M8-M40g%{j@ER{* z^d|ChIq7T!lyJNTbuK6w;|Sut?iIXJ#97dw`n;jRc){X&*mc5Iv+|#c1F2H7^*f?Y z!`cOX3PPsq`C4Ul&D%|Zdcwqabx~>c?<8I}OT$ey5|-Y!&jyj%EM;@G<;8>VFxxeG z+btY3z^2|rIdMog)35XFym^On3(mMD8|z_qq#T8Sh^>9guUF&Mex9pAtM906TzNs= z(@D~hsq;d25W=uFz&*v6q6`gdxn;n|X`%aa zWOl`DPK3f~@OzsA+=70Ip~sW)^RM9Dw%c2BF9ZZ;V~8SOBJM3?c6IxiNLDM7Cyj@+ z^JT5EvrHFf8-SKDcmOVHc)FB!Xm10RBzi>=XniisDVxqkgTH_t&YQo+TRfak{VmlK zp~*c<>fJv3U%6A%zI?2;9+eW&c0n2FN|&h`>&&R+ z%z_VWN(__q)L2 zFKrwrpu?EGF#}xhs(J1czDPEL=A-GIDoi7o5~HRBqYQ1oRmj=L%10=n`{e|y-w((|qo~P0 zwSt&j{7DwAv!+`%k6b7Zpc=oMh-$wtwV3iGf!5u29|(P2CY+hOuUouxk|Q3YF8iv1 zIr6RV{P|U+E7`AfT~vHrSm4Ec^!>p)n5Qe69NStw+rHkg)P&_XX-(u#ZWK=QfC$;H z&B>BA&vWZ?@ygp?lxGIm7QFe;%a9Oe%`z*JYT-)mDXeptd+bkoO)j=DuBaV@n$*9& zD<;D81d?el>l;a5v4ki68@c<0fLP#*Wjx-C6M|-*;HNggBDgWv<1rDjgdw6}8R!M| zg}e9nz0=@wf(n3(mmH~UN@pQt18p3I7yzR>2P*Xyoot}<(iESW& z1o1c%BAYqrSo%*pE+Oq?`_3ks)F*gCkPtS1hJxv6@|1z>ddNgiHwlL}P^a@T>hRtT z*!>KZ8y`Ym`axp(U-*tyEXK6`gZR%;|01%IVP(iCDYRdogjw;1lGuMlmC1+d1o7uf zSs1bjYiZXGEhH9>KVD6y&WHpEpdRZ;Em_Eq}Z|~dh)euIljoA0ZYI- z9Ht;jrgH8tB?+LgtG{ml5^fA3IgCGAuPyh%X+`3SBz0BtqiKh)BvL|@P zud)kUkp)Mjweb8u@w|ZO^N%)gH}kH_X(&&44u6pcS7U91tA4b>HoueO=W5uuQaQGN zM~DK*i5Fn@AH!v}3M?|+*fgyKXpN zSNgj%=oW-~2kRX)K>$V+e`^BGZKqM7I~drjN|3qh3i8UED_PS!FnxeURlR z*f}H_3W<7Y#NE*MsLbZKZ*%%eW9EGANJw7o!YOKs>7^Kl+LD@Dq!~8elf(0$;>Upj z;tQb^_$za>uO5YxVy^y#e~x7xOclkxHC9fb$f5=k)e%BDZmdjS)ZNCIB0u`5P%N+?;$O8&biQlr-&eMaFs2+`Oy{Gba?5*1ayj}mzufN5E+uo3r$IP; zeImne*?%)ZL|f5#`KDyUZ#{zl;TJ!-{zko2o~PmPF>T6IWrJgNwS14XNVm1T|oh4{rl{cE)b3 zLp@Qm+oGy$Tab?!+i303a?^u#IBA^o&gJAxY$SvB|IrX`N)$<$5W}}g+XaezZ=RU@ ztJ`i@7^$u&Yt7KbGUtR6l`9C@#YtGXC_>u%bXZ(SFkEdG`fo1eRs?Y&Y7GAmunUKZ zOy7gPWXHcPl2*@J+JG(U`V8|w^+3@c0vlU1ACgmL)DESZe;+qF_deOC`psDxQ7-~Y zMtw57zkF13EAjSwt(pYJ=@#_6W@Dogw~bw1 zEI-^Nw3dbBvIA~>yKcqAvew3GV6Rh<*gDQE`s4;{ES=o%q}4Y-ft6p|Ye6Ryu>Qeh zaE7N;F!|WOmC{Ow+MDN)A?!%qVrww9XxTJrBHohmtk{#gDtd$i@P~gZm1iB!w-*vg zDt1X*7~-U;UjhG{#v=12dDSMy*&yfV{}vCgWpw>afHrukOh3tfegGuS(w{XwL!}O3 z*+?>bxydC^+=aJOsq~U4d_XY3(WIP1FD&oEF6GDh5zM*HRKKZz{tTb6-gRU2LEflI z3>*FSNb0!$ONh)xAi2QOo^pk5}W9Q3)rU#h*uwl#EMd~ZwXFzK~A)=L$1D}(Xn zH#lGJyK%4Ve^bcNQIlHj{A~=bXp59!%8)(gee?I%{^Qfj?gl^u4psr4qZCZ2C=|a%?AT&Abgwx~c5fRo73a|Ax8oR7UI)&PP zURdI#Wv8GMbbJBOP7~387fBaQsn7WAH~I6V!XBM^JvL@M8S{sLm~OV(Ef&GY+aIpk z+hlQ5Jp?8y7)9RBrzx1o;=3J4u@$V2Ui=P0bRf-;Hv8>19fY)tryZZ9XDdtn%=VqE zkr(<_`wg>c(zXYlw-3`7K1dOYnQngjj_;FU9o`lCZ52^e3+3GR34z<-&h?y!I)`vyL#N92FkWFU@Q~6uJWB7_Gf*yC=sP5#Ta z!?gKs=&9Z3ZZp@?Ca&|yGMco?!Muf_OR}-JRFP$;t4SPZ^M_5tRqYC+F8%86^5cs< z50%pE@#k?-S%=UV`L?)XSOm=h;*zmCn1Wp*X5Z+>8BYPGpHuqMVGF)R`}rP5nv+9} zGNI9#n7&r^X9Y!bV;#&|jFw*eK6hkgKs8d+?p+tx&{{$si$!NuoyY@e5OX|^%pR7<5*)XXjOb{B!2yQguEH|Zgb{zi*ei7qNI7o43E z5Pth7%dE3U>q1g6?*#AwaTsXO$ z7U^F<0MXEqsr#^fGi5{}d3Sd@5W zb2Zva<|K!wz;%T`5lD=2q`B@8fV?#)`tSE~g3I8RfH97Af;gUw8!L!8VU+HM_h`KX{uRYTwtN`eTM28V!QMey) zwz39D6YCHvaQQUM-;6S;XJ?P1_AQt@RVweL9Hx{Nswk3mZd@>}(I2V@c}MY4(9Dw6 zhMHcZDc!`ADzxZ{WtmNH$5$OYxA*-(e*SEG>SOz%i~X?1)@jqME9^Uua=a}oE?sA* z6+7^U&4+t(NaM`i7%y3TlRy)6>M1o7tY6f7cNr}0c{IvL&LVAAT>q#}L$A{cyCjlD0&|~cG^AM09 zOtlLXdTzlFXmN&`P$WcypHMxlD3pXs#S*I3---|&IykW1Ac~8hAg1SiTR#Ni${VGB zdbV!|<(TGPN`I!CdzF$+Pj0i;JjAqb8@%>f!!VDjGe99$WCvq<5IDcev z%x|uk;oyq$^;EbAesSJcq)j0@fysF<5byKd>EHpAgZG>7p;n_#sIhiCt<|q@7snsJ z@Ld%$Elm2P)7>~{F*oqH-_n?4M$Q5Fh&+zVrpzG-)gZUl>kcK$u|3f4leb2Js2UgS z&LE2bU@+b?AM`U_mmrzBE97^XJPQg&kE}r;0Qaj>0)DO_F03U#TKWc|6{ev(P#7&# zB@JK#g!XWeQEmzWB7{j{_yp1g56+gn|6#PxFPRvt3!Phl%@}bIU`%9s*?ADILWc4d z;!VJAp%fur`ISGT(K2+A(47V%Hpz!gHjzmd4|+u2xHk-SFdfNk$?ZOF_wd%-wy=^s zyrooi+SM-byTwXqaoO*>#r`?F46?M*a~mSv5$53ZGpDCK=ymj=gWtaQLVEyJuZ_HF zdNYN$wMt%kco;m>u(aM*^HhH?bYqK2re)Wn*y* zk$doQ#9KFwqo;4uUjIy8i26eIcjxFYdO3#mByGiSNU;NzGn>yxG{Hslcx4-PIcatukEzjH%2+y4{QMjLOe4#a^6~7($hAW`0F{RY9 z{Nr-jF5Ph3{6Tf2EAdk>(I?lW)t{n52dFf*pMD;&+eW`3v-S2Se>}i?DRM{6DTL|q z%wYh}cWUi?of>KWV z??I=pyRPFXxQ}Z%Pr!_NDzEPZgA~?btPkFGC=CF=khu{5Kst!wNC+!%U08Fm>hR$z zm>X8gp^oGx)@|e8KXyLBidLNJ6NOC&;9u`UZMVkSh@SRv-V-RN7a;5sO%6Rr#FCb( zjZZS&WR&OXq;L3SuUzF`b|=RZU-}TxcGRGMZIDge&FS=s%EL>aq)w{*MLyx&V)ejD zuEx_b*j3d@rjPa27_e}3~f2Ph>EosCR?UCE|x;!wBEv4s8F;gHWP z>_8Uwv&Z$mRR7ZMXcAqc9uzxdpH1(BRy7U7r`xy_{ygWny3E2-l z$w_bzQS=BJs7|jSxOx6CwF6gY5 z2I-|gVrKW|49~_FbjWj^w&W@}I4&PF7HJZ*NR~B%$ve0hVb-v-+Kh{s`o$0ujq6Nx z!A!sz+fkc#8_=B5c$|~TBLHWwxbdimb zcEm<0mDb-qb$wCtfX|hxyOgxxKr}HZhCM(oTl|qY*6Vfj*JI;_Jy`pcb9^X8l+*jq z#n;AdN8i*>)q`b2&gVRCUyMq8lm)g+r1%zuB&&v7&%QN9tC|>ApSt9A2-~eaJ{9RW zK5KnJEE>~m@(qD+?SrWLycy9#ZvN1QFyhU=YG}@~d7-@a@Gf}ca`(zAJmIoC7(b@g zk&pkin!*DN{R!1?x{o`&!UteB^kPQlz*uWlnSliV#$^yX#i?@_mRY1i&S-|w`@L2g zg#XYlGzUn#j<0%KfKfdpR+BHEc=JY_RZ)PBy2o$osPJz+6Xm1TZX2 z%-%Zcd;hY%VW)zTZzOBZcmI3_V}YvkqP(^Wg16VtTQRanTU}PJZ(ZJv4@&4_`N(s0 zD=9yCX~FBm`5$IJ3C33k(e}- zwUv&?UqkE`mxD{|+{WYxR6Ze~1#sjk7gn)GXtu0exSL+xZh#1Q*ee2kMjct`Fd
==lYq)XYWYbg4L2GuCNMUxnp`VUsPK(bvI?^2NdwrS058 zoOnFifv15maZK*`xnDC7zFn2dI}jZ<*o+PCNU_&d&ay&)A5 zYhME1kgZgfTAwXq+iejxZfEOZlzp%6HR~)e=j`Ynx~<2#Q#LK%xL|v{dJ0dN1!H($ z`LP$D@|#C_@}J_1Id@>aL~eH7>;pME+|6F}upT#dNRV(Ef?u#ULEJsVa=e^;F?sU% zcNP1bsuJL8+%leB<`L;xHhw~%6txEBg$oYsvRIL3dvOcL4{@yc}F zpQjt|_K|3}4sq%MQ3kC#L_+t+^a#V?=r??^Q_r|U*UwCygw5Q$9(;BgeYBEMf`}^) zWxL_0^Ke8W4fXuwuZaRnjL5fqR9X(t{Fom&aqk;w@a%#0*jWEwh~k|8dcUi1&1HLD zbLKfl92_M6X6cU_n^3aCgC5@Tg?Mcj$t#=pN!=slIQIAig0;YtByzr;V`Dn@rdV;Z2x zoFn3o+=+Z~hZkfnb%_=f4^Czrej*K3uxC*{b%yHM=8Ex6mnW<=0)fV z$N0#uJPZ+x_v?V0?y9DY-_-3&*qWG)M`MfApd17(c3sx&-EhG1m@YI%)9n7~RaQR0 z`B=2cYq)7ln&B8Q27W=li*iqf3fx=I#(>JkVVo;4Jv`O&F3Uq5Ta{@rJHAY51mSW) zFqD`1yG8dml&E&J2IV;EQ6($Rc@_d!gnupjeQ4j-KD}wr*wA$gR&v_)vF%jk%ZN$k z#Se{QP-?Juj-PEGL*Pq|c#)Y~QqG7*^QYT!*5QV6i^<`swb)reut!0tu3ZrgkAD zzG7aAvd#x7GC!_?oj0vU)J9`vtXY?4{{^vG|MFVCV*-#6i9yfYLVk-z%r zB=4OT{UD5ut^}0F$lllI4)R^jgYT3JNfn#4ztj6tcSen{Ii#_DfE<3|!s9wSmDgm? zs!d)y(a--9hw2l>x-HrdKG!5IF6m}tl?Yq&g2!dTE;rQL5doBaOsoa{LTeHmSo3ig z>PRVqvQ%2>rQSOcvU|9k#K*?paVfJx5r6YMmT{P4bG9tONj&|Zb6y)35-C4q2VJ7M%@S~q_3r26fytp#oY_6v6Pu@@nfNYwMA*kZ;E#nYrPu%%^V(f+FjlzA-PwVL|{Vgr)`AA5MeT zGZjMbzpa+V7R`je<~c1gcP`$%M#x=pdAlwZXMAK6`sxjL(q0ajo%r5q!?V+LLv@Yf z3-eG@Q;Gyoc4OWTvNx}LCCDf~OTLKb)8BshACaV3?Zrs!Hg$Lt#jlRut;nxspD)Bw z`du-P{23%A$5wlbmtTL#)Z)=6Ds5gT(u%%ny=IN?_vXY;QclqE{pO4SYAb1F``=ZT z9cw3aR&L|lFk@XHuoqIR!;90SS*!}qBo8T}{qg=xE4yMp$5DZV(r?f65?OTxvxhhc zt>>P4gfSp4s&pL8O(tNQIE&XfKk6U8R%^*6Ce zoA}3^oV5MI)gi}JbJV|8mD34Qo2bi4R`Bpnc4o6YGiGg)TA!XR$2;&A?x>G)WP+~7 zOlLoM#C0H%U|;y@$Ul0Kk-=#3;tApqJCB@QZ58q8`^E;Ho`O#Mp3ZhYlX|N6h7L3J z?mc(@s2yu;%V+l&Yk#Yr+}#ho)R|~T>2G+Ay>>OAog!^|@~@BuRPJBXjwkEJav!uG zS+t#z70b91rN zvoRcpT;3iBo!Ul%j;e}pO<8_~270}}-MOqfab-OuFPH;H*mi8b{8p;zdo9&e^uo^9 zEw}w#N@SkF+!MRsTX%&!Td=r;Q!ZHP`M|xpC%zqLeSW&UGk!WxJWBX>>sdf5K26Vs zjr!B;X_8%E$bHEMRKZ#|PhVsiJ|n*fjuYL&s#S!NudonU&m@TwFK`ki!FZ<{_~#}`fqZ)g z?EIwD(#dd)W!TQuE`5{lhX!8Z_lvz76u2=552v)5H3EwRWcJ%0=OmdSt3v#>)#NT}qg}u!9gUAd>Ow=hO9Z?Bb`h>p^Y_S44)Ze{Z)|98oiXe6p!J)<>}+40Xvo z$m21|4~vq3n!AYVE&Bn-IRJYSUnXWrR`{=mEXX zAd2OF7RH>%A{fo3pGGrFJR|)2F6f>>Jf(#K+>?pP;mHk$Jf-xym0SYSB6B>Xn98uh zoT8Kednb%)virW`hveT{RU3~SwyM^##SQ^}HMX(Dy^FNpl$7t^kg3z&DK8~P3X@;i zNrO67L`}0*n=KPoXlw1s5IBKx(6Qw>=;A}C3SG(cuSBiWYE@VHVQVtvUns2a$)sS$Re~77q6dze z{ziL4R46rb$`0Rh{jpk~(o(-)AR_isE=(a7TfB|$GtHeD)5RCdlcaYNI1PTN;^+|p z)Xu{19r9?@E$qT#I|}yVAJ>0hre+Z)JlslUW&iFy#g;_;kNf-)$AOppz_h^GbZHra zhBMKR3+v}hY$meCW&{%^Iic4ns`-Rnxd^tt>hNiGGjx-Bbfs$toKgC3E0lE-b{PnS zU$#bl@K#TSSBi;#(b2V>d1bxD3!w*0f&H9^gtm~jTaeR%7K4|0p?Tig(gK-G zJO^EETji#Tjma))SiXz`@~O||wGK+Z7Vfy+%`t9?Y9Vmt->5utF=JuvxwhLjL56C0 zsK{-Pm|;6=m8COKlv`JzdC)fjXEDY^SWbdwx$b(h7hGhoF;Z+OTj&hW3fb^JPEmEn zU+?fmJN@avS#PQ7bWyk5HqFO7WTJ)qaC*Fj6ZC^YRHs;t<0>GkKd+H4)!LbXjTCsron zEdhy$8^)$%%0Diirk60$H7En)lKR7G5Y+FN+-KxCChX0=i~?4unq<(7?AxgKeRi++ z5^0_%fk4hE2WMgH1}zOR#PMAUD6$ND*xwR7QA$g9=NMG}_RC}f4=fzy97>pFGjmRP zbh%);Ri7A4xU~*V`auM0zYZo8uV28@^azuYo|Pv;zt1Ra@IqO^cs65uJ^Mo@Ws;oe z|H%Y#XFr1B(lN3KEe~9`{|nU`jLIh&HEt~VukCj)EFqE%(|Fi-<_4v}bU>MS^@huw ztCCyxXgA6)Z~=16OU>(Pn8mxCW6a$fs4J|o1%K^0>8jbb6K{Q;w&{b4Dw z8kWlXFh!57`>(t&!ttq}f;%eO9q)~PZdI^(h4}LT^1{6CCs38GO7EA^yF%*v2=8S4 zGXi$LFyMU5QfYJJ){g*=MUb+ctm7jR~0C^47yWrfsC|6Jzf-mQP^N$tim@Fzk^ zcfjmigwUS1UkQrG2;BpG!hnyk-HF6U30;U2qRcR&l#pzFp8AOBYY!(=D7UPE7g}vV zJ73_qV-E3!S%f_>S;zhYaEhv4u;+`UMS|^{4v9`McLS%mp zs_nRIkLru{3R@}7!1iVl++G=?V1(S@9#p20CQz#+vsAwm=@DzN?#{NS42j6)w+JhC zJ#G(T0{njU>PdRfeu;gbdAav)Nz?e(_%9{9swEi#mi0Q7GLiH5Er^(J-sjnwnI-a9 z(`PI0$$4z@@E?|1xo{rriY8Q&20h?!XY&Z+Z_wZ#R$X>{aT@1|+$3)bd5g>s!jDcw zx*tKmUrA!+c@-4Jr(Z<`jI^dtB`NkmQG#Hs#51(RA0&D1P>DY64>Lx9#FUU{67jYk zsft_S&trWlOUjTiaokdYY`L8GMPFDxNK7v>^bXxINn!vDq`mzhMi4hey3^l?y;wMT z_Av#ZP@Z2#R;&Kjl2GwBibSoX2XZ>N)?nBxF`$dJG@~_6=;-jgpBZ-O!{e(ZZR?HZ zqGD@yMK@0#D`hQBsFCgJKfVPySyl}n0unr*_r@+G$Gz+E$G6Ulm=s?QD>)y;cM4@O zscJRdzMZXxj()y9VdiQ<-e_9B1y-p|k2)7wm@_VYMznT`Taa{)o57D+kQL=?N~9=@ zuFoP;QZ%YqTJKMNuAbaj&s6%_jip^Xf6|88Xj)t6hf(x#45)o0hh9U1zLpY!JvdTk zV|k(uKXWYAg&OU_uAB&^sOyD5kO_%6t$85spIC5*)M=^0;@EV3$ae?>Bw^Qucog`C zlrWj)2>8>GA1JJQRBGN|^XO@M{6jN%6%N*g$GdUjTFPDb5*8sbAD?(*f`@~iP z;2R8+Gk%B@bg4_Rbu*xp@n5ZTUu>DD{k2?S#7s2U)t@m=YkKWuDxKN$HF#}zZIkoo zt}_=;KxIea=k2ZQD-|hA-z0b5yVxlE^77Z!zZQw$&&^TcmnrSQ)J&}UXSCFWI3gcY z=sjD`Hfz6uk4;T0>F+T@zmNXnQ_kkab9VOJX>S5ua*!EA$7{nI$K`x6Z+P@;@~}UM zNHw6O&qC4&1e!YQ6P=-DM?$2YBB@!*c*~r$Ia!e&AAAg(buawDLqK6*U|iFs%$^?^u52JjOxK6=Ald!t(_#!l zk<^!a*BW@ZwZC9~-BDcv7;_eMB$rUvLkzpTcW!*y=A6fatqcFUx2sE-eu*V}JOS&4 z<14O@L5B~)I=0dY<;h22fueo~dH=r)9sDbX5!5TGxSQ2Xoh9;40DZ?&+ZP%92Y37& zD}L;Dyj?8#Ypy58R~#XIf%MOLGFxA8LB(b%Z65~ z^E!lUr2gxQzguX6tsn9M*YA-)OfF(lQ}-CeaB&!luX86|dm>0+sI-*;Th9{u09TAl zh|dHj0W;TTp=#0PZ`pi^!hKBy@4taKvjKj|@3g|t1U^`03t0|QA#pA2*aNu*!#d=# zuWq{vvxio@nspY{C-2i$>t`|>y+10B^DwI^a-XI1P~SwLh5IjC4|1c~)nL-t*5jJf zB-@egu2A)f-q>|78OvkO4{iv?r=jI_omqbl+8Y|Ml5=a`RUu|5DW|dGsvWVf{;wTi zp_#3#reZ}YWE8LXj=Mg?`uV{`v9t9JyQ3GOt`7&VBA?tj%(Yk>t8^yK}VL`()NO!%r@{V3aP9Z+;(WgyS)9x8CPL`k!}A zg3!w+%OO;yq%-h~cW+_I_`I(@!~4*4tCKC!{|OmxbD7ymxIoQ@mo|P%8;xcBke~18 z=x>%)Q_!X-4HTTm8+)zVU_E)mHN3&$18|LfMt`KGaNK5@i=DvQ8Or@AMGmj2g6`lu zMHRCtMF00(m@9D+`k4 z4XJ(C*%(V#s~%@2dT<+E7PLPq=c*nqo`m(~k0mxonUQwDf-V0*X<0dlMn^dqp3e`` z3^^|z;v8N@`Um~oO}+YP1w3X*Qt2v=XyyaCfa~)4@--jyNIG7`!#2bH8yvcVqOyiK z4RFobERX-Qu`!Zh6d(sAN)YNoIOGUCBsio6INhp(i&c@LjfuRhkoR1Uf>cA_17QlY zB2FOMwuTeC5Jd{5&8Y{APM`1J2qyY>1l{g+(pD4)vq!Yc9fqW=bGS$CPM3LB+;}Oo zDCoDcN9nWZelRF|`H-T~+*1)foF`rC{q#JcFUFP96WL6&XqSC|Ht24LATfEff0U_! zDgJS<6W{PVa#<%g8%v!BW)W^mg9b4>4KMHkct)S{g@?2!c5_dH!uOWl_$10_`qjem z4;L?gh5DYPjP*rFSmGv+#jMW}x_zdiM2mM_rNz%?mXB9 zah$vP4vLc)lQ#64I_h6-7-3Nj5C(o-$?qYq3Y=Y8CGCt2MittTB&arUUdPxQa)}f9 z9*DY;<*2lnElS>KX zSyl913~F*peL$h2rJvW3$+jRRP8Tjip#hssw!Tg2|DfZ+Oc5J|HPPXj+r7qOH65^X zAVlo@Bx9+3#%5NZiz~i#Pi0$q_NGppGRc`@3qQAL3qyv}Lc%C`B?s!Ge9ZGFRctO< z+#K^7kDFNw*tGRNEz50S9r8d*!Zrsi?j+TLb{o4})3y^!D0k;187wl+I&Nb8UjRr1 zo^e0tGc}p6prx!O4rDg~=O7|!|3UYYTeFB#e3cErMs^((i)Ha52{L@tGfk{{xNcrz z7a}U+lw=cFn95uk)}TIh&H+@EE){Zi)LBS%)zaluy}eTeTdg)?14ookY1WKby*8DF z`u@#%()oF)@#hsOzA807GOtIcbwZ!sVya@YP&&KS!62=9XZOkJ^9#o<Cu2$t- z%vHk7pSo89b-vI0yZDp7pd7&Wr+E_GTxYc&*$KF{hBKi%u!6Y}`ya+f@KR?@#n;GP zEDHdRvHxJ%b^{cGmw^#Aor$i0Y2A_7eu@m)u#QUIOjXpp1`VnGqp{$-GFSFhVJ`eJppdWkRW~rrZrN|bGKZdElTTg1kaBu4M@}b#GmX-ucgnC<||f?A6v#l#+PbFU;f2f4lDNZ zu6{EbzTDYssP}W=(>)(W-petRv2 z8JK#TAUTr}bx=k7;~uH?E$YM9xqMp~fr-&H*fYm9^}p(Y1H1>IbFwupAV7mi#8b{A zD#D#bKcf}GGT&Gl}A6@N=V>$yrwd;ath)d~6k( zQ;LTG`GnQKwLv+~eeFRpbmy+faiTyhp>0;EljO|`l97GB64gsAWBv_C2%FlR~iMsyf!yR915ir7|{*5}*{g%a6lZKQ+WooMJM zdvF5Jn#2j7U`A>y*9m3R>qcraxopRyyZZb0ZjI5j;w{0W^ktA_{>_%yfP1pf?P5w> zs6RY(JEr*!uL!N0d+w;=0nzNHd?-XVnGgyNmpcHR>cmWD0w+2B@Wo7z=li}B?qR&x z@sD0n5hQx95%`WF12D3xvp3_%XIS9>=0Rh=K@ttw8c3OsOO7 zwuw@q_?nk_#A%toXab-Sgi`&2AcP8A{>pT&upe;-KN3B66Qg+QX3h0ZaRpN8taNL0 zRH>DhZv2wkaPKZ>i~0}}W^Nh74#vKuI~Fo0$+AV`Lf-&P3sOfF;|NYbjKT;wfgC)t1}_{3PaR9_C2ygZq1;$f zQ`>JbMStaBNn=VoH0GZTum^gW$F4EMrYmS-&xSw{sxJr+_d<{5&T(x(Vj261Tu~>C zk>ZtIKMp_5*P@=3PuN{+9;<+Kok>Q}=Ri;0g~`&qub&}%pxnbF_wSH7*-npLTJ5m0 zHYi1kJ*Q$thF7)q8u2itkozpiy3RW;*nq#pjJi|2W{4UK4_uvm!eSMCrj?7x81bt# zV#Kxud{x{$J`(gfe0~_z5zeON{ZzVyI=|OBXX=~&k!{?2CBpH15=yUj`WvrOi!Oph zV}k#Or|xeQ+S8ZmL0K4uXb93w;?q_kWU86#CEE(piyzceBWhwf z!mTiix*f=Le)`f07=V6>fYT6}%*l$P>I=dCsz{N0#8QI>?kPIgFL&7gX*>2=&}*^0 z5O|9ogw0s<= z2JiyF-NA*Fwi(rTO&GsLrS=ZTu%$RP0)1M@)%wF(f2=d9tWI~lX8(AVJ6gm z=Vj=JE-HI2Gu?ze!!jov%u`D@45b$rV6ZSkErIF};5g{bb&EsT%L7yt;MO|0nZaY! zzyh!&0v&vnec1&fzZhU`Dz;>QI5=y@>04d_?VkAKC0f6b7cZUMI>d2Tnv9hD0W0gG z0wT+<3=g$OY+zG`To24D3yc$Y(faEcf$6gJn+oCaQhxO2KDm)8v1NAi`!C)rey-tP zj6>ApEJ_-qJU0~QY!sxF9=6UsHwj{Ugkk#-I2u>a)QM`Xc)dLOs)fa$^<9llq}|!| znvbJ~?>LxUOY~EJ{T==(?Lp53HRKUDw;2^#nXrogG;4nO;%uOpp+ee-tlyQF@&@;t==N$+b&Z=%j9Li!t9TUK;y>_?YKA2xcT~VtNR*wAvZ!PnbG;jWjg^ z_0;r4bdG=!{b^p{+>{fUE4&0|j)YDD3#WOB9GsPU2z|J%9m zv2b0aE$Y$>t)Z}bx?Ydy`Iq8Ov+x{gQNcLTzOLNtShPplLyYdi{JW&na70sgvqr|| zf`bXoPxc`F?AY(t+40p9UYU=_XQce1IM!msRA7f5GkSP7Ku~-}C_LmBy8RApbVQZ< zVNUbT!o2z~4y?J$&fir)D@TG6w-$CK+69uuhVlr=&DdzKQ6b$p3Y!F}k9I=m#op5m z2n0Jl+WHdOk6EY&;IoHNQt`W6p71|$e6-DN7M~@=lqalbgGzFzeanP#;H{aHv9_EP z$i=?MWYlru4Ecn1mbiyv%aPcrc;oO2+4Td9uB+SUDQ*1N+o8L3@S15ikhaRyWh&q> zdq2qYFyXqAZ$qrJuFn@CA@hn|)yT(efx{Xuq@zDbpS`zS!qNOtd!FIrl@!I#3Jk_| zGtHdk6~$xzR;Fmzh@!8>i8WO1zaKato1J9oe%aC_dtQJA*wdKwOLUx z1u9sSBmG~ejm(?l&}kJ@jlg05GocS;dUALlC~Ipy73Nhqv(+FWX!=pPr&;7konPHx z)@ecK3Beq3(igq5KFe)v?U`AgaE!|@**X+!4WsajnY^Jh(EKq@Q>~Uvw7WvwdX0pn z@KYh4bqm}5yReW$_{j13y(C>+JLum!d&~6r?kybKKQv&;zGVBJa1u|O zC=%()U4=PHsiddH;|Yow6t}}JqY1@blWkwxXJpWei8MT3nR{>K^<3ej)=$tJj=iB5 z`g>+KZmqZuc|&>Ld48?wax``NJya__<#vbmYuRxr0;WIGo`qg$K)73H8pz&iMi2M1 z;I|4Xv8wN`^{fovX$m_GZuONcJ2#uWju%L=lB(=!)al=BMu%dK!TH-uPE~7KXyVY9 z+*5#(tgirF^wkGhio%*pVY$pHf3Zku+z2VZn=mHmVZ95aBbtDQsDrWWj_$j_w5mX4 z0CtO>w6*^ZOuqxd(3&o=I=jI7Dx0XIIVl3y=Hj3PvSnHP0LrNWkYX`cf8&)Zn0vrukt zbmF!@>9qdmW*K$y&x^Xvd+F7^@eWmn>aIQK=I@g#bZqls*KU54P(seIheGe&8)*D1 z(n=6!3{Eq`$Jz9QQP%=vJ9h=x^u znDa%$>4D^rmC)0RLbNH!kIh>!`8dh|P#)lGctO%K(dqr*+@fgTC#`|1BdHkg28Qr= z?J8FVbGe?PAFH5#l)?Q>UR@t8YXV!AX}li!@!^=*G2(H!zsEw=Ye=p-Z**fQGUJ6e zLqN4%ut27G$TNtRuhF$($xG&uKS1$fU+L4d>V14|-u2(!NqlyW&QYQ2uf2#+!DwNv z`CpfNzjqm+HU@ES3^rUsc_H4Ux#Jk@5a!!vx;2CE`-A*FGx>woT)-f}IHBYxT%}Vj zn=HL={H~LFX$*lQq95l26}Ne2MyO0eb}XeN>Ri4jnIskCYs4DM_(jWvR{Hm!yV;l& zzXN8BrX#&v;$D~O{w83=lqW1l}v>3LV3pw$-9+jMm%^Sh^^ zp0osKN;bP`#NwMXV->S5Iz;*SU(Zb8IdnfQ^-Cmrq7e8Uf0YNJ(Plm?r`ydVINpJ?is;JXe69y8VCMhCRdt_fGume`z`S>Ax1U|PIb9=m%b(#x z|IqzSTs`PVz-fkNhIcx${FG-Oc1mHzBSh;kZch*aB?{ycJcK7%&T5yX>@yO@P6-g8 znVtBW_dl}X6z1@xk%@YZ1Qd4fb%!>dd8gEdz-Oe)Mmv5BS_eS9_W*UB*O&n$0ob6q zf#6E?496;B7Ogl2+u9&G!RpqD^bkT1X1%YaPxDlpY=erg8Fuo#Q#;SsJwNXFU zCn^3c7idyzgADZzfz24z$i-2<3yDOC!Yl#x*0P*(979Q|E}+%zW3^?eY!dlJuecVK zSgaV@fPvNKdXQ^9sJUjF4J!-2k1zDQAoQ!-XTWT3KbQXYs#w%r*!er3|O- zi)Oz*W<>R#Bqh#czTr^B-5F{Xm^p~S@0lLWs>1Zjov9`0i1&tRV#P*UyxLNhiwFng zlc@UM*5=dbhqHT5Y!2Y5O~vS?O+Ay)B*9%zqbO={oHQeZg|r zFGjh3FhJ+dPK(QSG~eVnCX>|aM6i|)zVEPY^n-nZceh6F735rXZMYl5-@;m#(~4;D#)q&&O$|6A zlV(pI(^)e~bnQMdg83&M>dovirh_HcpBB;5#tMIy|C4&(kr)W^w+DTH0G@(7>tmfH zYMaL@*nuF%XmIqGM4$Yfr;98{1|o8RbFBH8Tq{r8UGrnT+D{^=-J&F9YpoIN zr*oxj)x4yOSS;_OZ=0zq9~xNAwPA@4F;>gzz#X*mISXa4Jqo+Vp00AayX{f?-1a^S z)EqulH4%6>7i-phcJ}b@6UA`~|D*lKK8y$GdvGZQf`%5o(-sdFe*2axY4L}JMiPrt zbY)lhLQO};taG+oAugBF7{HXW>7a}hzdJwtv~om<&h`sDuhJW6JSAzB>fyr%w-d#4 z#`om>fR%M7#3!9*zX(wp24YDZa(@8^v-%g_2_R;Rc|kpW>=ez3dj0>86JkLz>PD>X zH(qv^68=O0bnXmzl{$w_E4crIVKGo8;kTGQc_7jC5YtOL4(BCYM?qNJ(DG#NI_DLb z18}=WBI8G&Dsbm%v=_-ACR_r>@=dtK71!V=f0xNCET(<7+cZ(tk+}baK6J^;NwwjF zJtpJ5sd=k%_Dz3fo1vd78H{|-PYq;-<0Ots1`q|(Ixg2jW{BfJQ52i z<2XgyZybU6qv#2)@qAw0V=o(=%_b(}Gz7ci$HLgt~XT~$O zx#{rcTj=upIWrbOPI5KAaY%H6u|X5Cg=$MMwp4l5a-&ueQ@~@>OZooAGL|BT1UskG z*jVYZ#$zaU-85x5LVv)WfQ|wn(0=yAi=@37G+iY@Chc#g2Sj^MMfrag9?m@v5b7jt z;oeIaUeuASFkF(KN5EJd`>(>B4ev6Ye~N}})Dm>Fs>r)igG5I?{2?%IrNCJu)-m58}{-?=A4o)Pi;k@qo#7=hy}t^YBnb4THOzwW?% z-3O!PbE_%~*(;|T)RjSl?-}$v-%SVBm%)dX$Jr0UElyg{N&adOT%pG0K3Q9l8=ukh zuFtPzV6Woda|tHhxnVyx*|BVqdNb!oMWap}eTfrhwRwz2-w%h;vAS_ivR>3K2w-^C z!a8ri2a(y*Fzgjdr#=5bVi{phS;ZplhgF0@tJOM(sFEda?p-WO^XWfKBw~U=m+5SQ z0Mn|_y=}>L;fPP%P^GQi2XRL&de1*UxWn%zKzq;X<**?ZF@gDkD7N(~5c*t^1==al zqcqG^e9W%`bhh&c!Km~}Fh#JDvH}*YHJJp!<7YvSh{5>t6^_FjfkahasDgBK*M-XV zU^`s>q>lFA=fNdTkfXU0_`-*ZK31EW7WMCGnSRk$W&;}$&G6UHW-J+MI9S5^4us3v z_*1+R@4vZVFYQ=&Yu;((A!6s1o72xD&#%T2LB1-PzSWD13Jf=Ohmv#Sf3CIOm%CYH z2;vGgxiC3@y2aK>J%KJ$1yPSeX?@NE-fqBmt|mau_JNh?llykT)$+84Uc}4Qq@L%c zMDzjC*!B+TA1y$<>>@Pd=_Z)=Z-WE5RzZiz<~ksNIuQ*JWs`6!V)?qapY&CblcAmv zt(6=;eC7~RuOLAyenam#{$dCZ>3dIoO}8xc*;8f(#^_Sdn}ibUqg0%_jRRPrM`ZrO zKE&v_jDYM$KTw@071g9X>#W_(tu98yu6eX|I5ot6Qpxd7RIlRNP2~zbEO#rT)4iqp z#vrQjcPHHq3xiwUVhf*ctBU}>k{&@u4a=Dhh0mXkCOCbND>+Qvq25r(lycIg{NS-` zds>AH7wY_3K-68fa&3AOzN5<@I3Nf`EJ@=1k&9C z_4hj}10tX(S8}%9JyXvFzHS)j*1$4IvD+fbX1Njq_Xo>ym#jmZ7uappFGzoa#-0i&HeBXcgP%aoU$l4} zr`?okC6m>(>|W#{Uh(}rKQoPMk$vMWiEN+N0x7-O5UGQ; zh~YliRFiSefAAWDu@d!>MDXo%)Cl(Eq2pEr*0y*D!x|TPq3|vjB@gpWgoZ;rI^?EG z3|lsk#O&K6|-PaF|C z600NKpE2q`dj!d%^clfk^F;#gh}X?>=TfJHyuJF9n>wlEU)sdUb;tpaDy=d9FaI0T z%Ogxc+I>RD(5>Uj{{pB|X5`kFDn^5r)yHzE7kDwmg^x23+dA9lT&ynoC4|D&y;~IL zA2iTM08u}_Ms@rPJ=X`G9w=C01$;ky2;7^ivAdA;q)9h%Xpo&pvcooT;nbl%{6>-| z+N2@9SNAZ$j>-H^LAwEDy4}pL*=JU6O7aK#+yv2_gK22*rU;~Ww<1xnKR#OAo4((YpY}B+4@k+? z1}|t;OTvIWyo@@C;7+^RRq@s*DbNVT$uPTHL0n;^R$F1BH-j;$i-O0OQ+dLfZ1K0w zFanIp+e{E>hZ_WDO>_5F#?b}qlS8`V59<6^5rMZ$37GX$;F(Eeba2=4;}GWFv`gy< zR$A1@1H^S^aaPn3K$!eQm4$_@Q+4rG5~49KRz4TZX4J5oULv`ReHk}SxOeEE~I z1RL$|PjGx3SYs6?dW0F+?(9VczX4#7o+rT#luSf>je{CtbEyBiPubd6-3i8X?Fjm;@84`%&veT6vs5 zPj=p{TZly|K!eXJOQ zQQY}`Y`zvlnTfuDG*a6G%lc3|hw58;Vb?BI7XNNgjQ4hrEn57g^SdWA!N0e0@ZnCi z8GLBjVuOEG4Q3TbI=>jU8O^c#+*AXOn}jG_hjMJJLQ z^?y#1`t)GOIA}H!l30ZRS;AgoFtzxSSW2A{EKK|__BqQT5nT(QLY~hCB6ME=H`9(Y ztz`z;=uQ(vtOn1m^&-qYFfjkbq@^(u^6B&P7B@x%UfS$2R1Wl={b?NqZdU}@M2KQ^ zpXsMtRjEDPD^v(kmlYbzanLWfzSV|XFn_rgHL9)S=?S;j_1>BYk(k|eJNFR!Va26B zMUlVG_ejP*Q8mI+x+Jw%jH<3*TW<>jG7k-tOdtfvyN>aalS^ngtc4 z$G?jp0-eUwL_vO(GQDit%De6$y6glZBPxxLzddFe+ zQN9??a|u?wGnO#8xgiL021=4lPpOwqP&WWHq(n3G+p?<)?BjY&;;##?NSVH8Sw5QX^Pn0j$M-`6b@af) zQ?24x={SxbI8EZuy?My``Tmm-Wzwzj{=3k!q4pW6`zT2~mXgn_g+HA0U&`E57h`Fu9S_Kv zBnDgCnb0gXKt)K@BT|PAEUkZ++wvMEuAzVy!$$O)ByIFn@Rd6c{(iL8 z=CpO5`}VY+>(l#gr=@^XSEp;<`kc)Z^!s|vSWOd#$mx=>9(=1g-PTDpzVD*Hz`NZG zes8j%Z^GKLo3lS#l?;TR^|x=_fA!2`XxFKqzFpjF-fh>tTvGy4cu7+uG7WZre2^FP zP&gNhHtA@=Y{#*g_DpXoyq`K(~;^)YKI>;g-z1t}1WC^;=I$ec7LCBEyNk41Z$3{Pp;=bi9V)WMID`^|1LFvqo0J z2;#!d+<1+CN>|8!R6)4q#JJQxIX5)wnutfLss075eTvr0e>=vw>I&8xadEx_D7J~! zWXJ8vMK0XH0Ebq_L-)U94ulFz44M&!Bg$|ixK9z|CrKmLsz4>EsIG#k-%t;X8d&rH7MlLYaku@Bnei`@c0NKn9~Rqinea%Gy7pC?`bcVdi}kY( zvtKemWh}4PQsu3ZwjN2y*dA;JYIYbv7T~z@@7Erc4Mdts^1Eg_6CSv}0X=OJxU7ek zIHiKlUz^_HSP6sCpr-$S6}L`bO#)_geqlNs3*avLhEh3e)S#)`NgE-aC8`{c z!{Qk~w}P+VG&WEq$c*kHGJgHC#6)vmxM-YFga=bs#J=sqPBBJ^ZKVRFRm$3$dBtPC zuKIhp@`bh~4r@XeX!c2J`GdM2wMuo|O7QzX=byrPD|Ilar!RLbyNSHv<1f-h3e)DC<|pJ^`P~SzpUvQVrm_xDYf|k@2q$9{Uv)kJ&eK|-d;a%^ z^-hI6{42PXZ+_#%!892`l)usghhF@7yST}{qwRVJ&T@Tz&Px7A!}A=XeChHn^=b97 z*7(UUI}$!co!MTVHZ~(1Tm*PedEuf~j9jpY+gPlg zBM1zwz{+@d{q4BT5z3rDTQpPAf>x1f(OT7f6|Lt1G{%*=T?A+BU3E-sHJ;=+XFoa2 z^pkUiQ}9pBJ^h!>q)S)}X{WvXp`Sg;bML$+6^KJW6Zum_7kfn@l&B!Y!R;bXTHFaS zUj*<3=gprz0tYuC!B)He%s{sysEnUo;RM}F8C%0zkBJ4wfkoO(B3t64D_D~M(K_F4 ze>e}Gm{|Q!ZT_d%#F22%wnwjjN5uj*BL5iL8_8Tmk|P$C15#!ON!p&kkv0m1=*SCi z=*kI~gJ?hU*K#YUC9JJ^staPi>9AVQvX+l}!xh1u%V`RU z(#^@reMg-$TJRQlxr(}!@jhJl5dnK@tFTg7SiEz(WSXKF=P{p~ilF{hW^n&=&UD}| zT`?B}wrKvM4e9F_EZfl4K23SooNN0FFT$!4xq_Oz#HMojc936`FU;IzDcxz=OOt^}*pfcHN{KVOaT82cNqm>!K z+ADyzkYL8Hcw7HoL{mfj!ddPl@K6c8(IA%}&W_Q){4SN2^lu0Qjw`2OL zT_j$w_~JC!t2yib;PO@LnqY@Th%x6Okjz+u z+4a#nCrPITv@%Z`QS&{sO9^^UtwXD#D@>-7S3FIR`J`LgZC7!Y;eVOCCBE>rBG||1 zSlXW((dEyS9DpKxb0+8d6p3`Iak4h;OevKB>H7&G-|i78_?d~l89G)&P#>$OwQEK< zU7oG=WDRt`c&B0y*23{N>f1+FtDvtDq=}!wgfM#f!)^T~^{TnJMmKmDm+{d-9Vqps zPsSxCgQIH+sh@Td7sGVXz-7`;e|aHo1Gl7_`M4CTd7}W|*sA~9KIfH& zoT7OECDw9Ja~O$6jM8=|xp~&kWSi@^*!sMuwP&Wsy67m&M3X=$4b<3YRnp( z3~!`RF^`<9!2DHELEZ%&6=}=iKkf6EUuN%B#pGm%j!yQp-&ccviHmO0?bc}>eWcVP zIIjh5!4x8DmIfUYL+N!eCG!tRy^OJe3%8gnA0K!AWr~O>c|E)5nImSl>7SbY_PFMZ z$umQBfj{9|!s6+jLqXsupXs^$Xxk{3y2EIsO^)7wpT`%F3DhsoDz5N)XUWN?F0ZQt zK7t`cMkvj5A$q}})-s1)NSmk3hby@;Gk=mXy`F=|V%*FYO)Q~XUQMU|=*q~y3G{%3 zg;j7E9a^)-4sRvyHAANTxc}j|Nbgrb9N*=LN*cMn1T3Sq3>gk=G{{A|?;sBZ+q@W6 zuJaH{GhUwNU8l-5UT^3FF9>G2p_wX?27N;dC;FPMH^S7;^yE#fQbOLa1**jF{#4d2 zX6g84Jyzl&5?r6+ayxYO4}P9w@3)xBU{?&z*M3&Cw8pWuJ7#t0>Z|wOp&2YGVCdw_ zubU$IiCXk;WvaD$5)y{gT+8m8`F*`6 zH+xR4P;Z)QIsKa47xvSCo}CoL1)LZMB|zi|la9a>op?d}=YApKKtQLX@gSPCPfo7^ z7?*;G-wSf~{}1B}xw`bk(-}2zQS$4QLw3lc4*PpmtT4{7IWVt*qm2uYcj;|S610MC zVth!x(DWSk#hfrAn4cF?_MWe@K%Zu1uk-VH{@7@cOjOhI$-Xaa#FAw$b}OP)*eP-} z)w;Kpe{welI^=!4i;Y(E`{%_T$@^7ZNB<4# z=J64giB?()ZS+z&e8SWH&O6q^^W<`_G2NhsA@V*eV{P>Hhm)uz?UUTXJ9G%8$edW3 zkX;^)KbyM!yaqcNT)e5`c;tiyx;N9N|4_L#yCCR-SsY6;7?)NsAOtmrhv++lLJ{L2 z16GN)WhKh1=tIwxY=wQK-kGEd4F&^csBig(pMtW8XVOwR!)}b zu_V?j`RC&%6;k~!uH)Zd*cTvzXMRn)s}}bB{<~5-6T7&4QgZJXBsvpc&)14fn#>LK zz`eY!;Vpg-xI?&83%RVbH-FHo6su?%<5W(4nhmiyZ)s?F{DpM?XaBP}vQ0I_)WLLI zXFCw|ven)=zB~L?l14VZ)16!VRx6Juj2924h+2$MFE?JxjHOIoPN!5D)u;&)Csk`) zK=eMNu;N~8V~c`+o zH1*dNs7|jDd++&Zv)?%JdfM)28iU;W;?Yi-z+PGF*2*RD0mpM)+2 zX`j)`^=jCx&~Yp;j2P&?&3oudESY2z;N)?M>F5qXzkp=Psw#maN9{Nnq7v!B+26eU zkFhNLQNM1e=Pf-DKV>q<+ys9I{Me5){e(ip(cfnh_9^2|2xc=NrHvC)AVEtBfX~Vs zNI(?u_#iscsWmfd>hvJlQ`+?5S*ptR`@Q&!MpQX6n}qcJi?jjw0K>mtuIoHN0#j1dwxhA&{BcoOI zDJ4CdxmO)fT8==Ti0_yp!+}K!Oo;VM9(-ryV)rs<;(F!5-YmT2h58)QCeQ4?{TC)1#MV$;r~aEyb#wih4j6+&Pf88b z3K$MT#k$o)uw9`cP8v6pKZl$%KDqniH~mf$?uNYIth#5`-xrf7`oqnhpBJKD$o7!i z!-YC}GGUbt6D>=%GS80%F;p+KLdao)#A`}?s?c>vtB1&~&-q}i=DuP>wu@Kqd84U@ zI#GqnL)?6ObCRa~8~WiLH*jl=YHRm9MV1qH+)G#BN@Vy~Iuk<71+I0Vol<0dUaqNzw#}M_jnl1CFS}aqBw`!gjH( z6EQJ|@WsBqL}gY$Pe>b9%~r9QN2WYF?Y+be2rr~tCAU)XoN4X>L^0cXt=MbVxbb65 z+gZC8T*yF&p91LJ6@v`T$mRx3D+OHg>UPxbji-}eB3YkOe&>QheuVJXgg`A<&)1;L zSw0LtyqS!)Lu#emsjZZ*@`pETy2kdJl&Hol2ycy|vizmPcE`%*#;)N5x>QP?swHPq zk~+K8`sPM;7C6X0`#*g%Vj+Fa!5maqQU)FJQ=}1u^i8aEPYq}E1#ZH6s2?4fv)AR{ zNck}))k%0Ec{ISKT7Gjy?51`X>Nz`I|6MM-kKA7E)TQrGCQwsl>s^^mvAh3I^Zj59 zr_ucn`Z~fN=u51B2rswZUcFBLLi+vu0Zc4a<*oqeBIA{h5lt=@+Dl1c))r*l01uAg zo1hko(rz=xVc*k-qU(trX)Ph{^UHno>2_#ueGnJ?99NdoleyrMY`9-;@Njd(MK(bl zd+`q5FR2Qs>x$t>MkM*?9wI-T6n`NpFAA8cPj3WdsvTrx&+hjv(O;DDN@Tp(gS##E zW3!3I-Ud@)r-U&CSyy>;bOaCoiwjlw^nJ_oxjrsi~^#L!I-?Qc*^|2^rw zLW|k=wNF0onZM2Q3-df8GwD>G9ZvEj;y%~enZ^M9W!MeuFoRqFK%?f@*tfj3G z1Zv{Pe2%xHeG@h(3sh^^X(Hl5l>xb>zwf4YG=%D;yi~V6RJG_`nV1! zI2%@*XE|o3EPoaMyRhXMW`6toYE_|h+(=up;#+@6w?l%6Aqi=Vwu)u)h@-P`y6|lepH_-?9-dM6qplh08B1;$p z+`kXA54D8lqyw%l_<0SAjo#m+wEmVK(>Uz9u>zhscIK%3)r6-%pF7ixzL-ik7i?8G zs<$|MuWFXnt339_soHPWMn%2JH$T{6xxAJ3!`j|r$Gf+ylkU6qWd?y?*X%{!Vtkly ze@~L*>1+>h8`xL}#}Q;E5bf9+gbwC9_aWATiJt5I@9tNQqx7B!&P zBQu*d)X9O9MG5G+Oe@&!8Gev7v#+-V{H+kONClod&0XfD`wrLK;$_=H2W7l0E`kb+ zTCjP|xS*z0UC}CReq0p&uCwQkV>~zRDZ%{`HCTFIT0u`E)0Fu-ILFR1{$ z9XlocXe*cQULl?M)isk_cn+t0%D8GnGv(O@@XGpXz_L z{>gRn2uc{{$e9yY^=)`ohoz|esafoAEP%+>=Z{J4|A>RXgDja0T_e9rOos*M9j^Tu z{jVU{X!NY@l98NG4-RrAP-v17*y?Mc%j?ge11B%I=}P$C5!q>SQ?P{^J(xNu$EGYi z31MM(_5@etIo0%!TY>2Gf^qs&sZ-hXgVu#fM$H{FM7Q+D`R2Ogk8euH z@G|)(c5e*+%fOO=*e~WOgFu`~*>iQIW_bmU+S|0W@^>bU$`% z7sW@~c{#-K=&rIg`S{VRf5n+N4(I3~sY0L)gyjEor*9Bs)F6%Sq@X^Pqz4HehR~zV z-iD(I6}zu_k{)>ZOdZWv@ouJ&>{dYeg&-azMg<+Olc{_NBzDFmfx z)%Ca{-+U)~b8pn%25(t+``wM`{SbleD|~s?0x3|0vy8d9@$6HvM3UKCM@?b--?)DE zi5xS1fg;Rv_)j-WcMXGX=I2xNd?ViUj`u+i_n-*wHNhmNI9hd#{ybMNj`8eGuZ4ae znD=lywfKml#>6_eO<7oGWKuo1f~Xr8%Q{>g-liOVkL{yvu)&g7D}ZzlTT++kzlz@c z$d||U(h}?JS4gj1sf(_k*Bt^tu@esD*5J(}0XjctR&Ds$i>}oN%B`bi9}*_Nl$D-8 zKT}~G5=-l*Qlq2k!6u30A3$%={t}3t zFZu=3+e))>ck$bd-H_9XjAqi!@cyUM-Ib3eh>|@-fs^XmPp4BIm1D5$k8lG46f220 zb^8^76E1);@>d__hKD9>i1etY7napzOdTrv{O>a?VBRVJcPe_T$%taqS=;|RN$-9Z zQPb0R`lPxEE^Q*)vhZ@a@Gsfp_d)$jWKBd(o&1|*zO@&|x)@0-+4~!2W1=C^M|U%8 zP8WjdjMSx_W8&Ra0@`Cx^{k$l?9cp4ioJ_J{d%hT38}-*-WgnBf)EF6wC`1Q8;v#h zQHY@S$FFN$gzD^#=E45w-OV%QW=m?-z7~9qo&bMG{tfjaxnbx4p}@jU0Pqisd*3M1 zDtNz6yalHfr2*HkI7t9Deeh!>ia7`)K?CX^c*0E%A3TV+O6?K-$78lhCL4r(Jl}~H zUoZB0*m>K(>%ZnLE`}D^F7%~6LFIOJgMX*MZZ2=ncHJx1W|?o9=+$ABIg6L*5-407 zD*US*?X!?cQ*3I0lX;-$e2+-1D)#`OR)26&~A}Qy9!WCZO@nVyxIa_ z>mUy(Ma}{4AHJz`q|}U}UFa%G5NJtwiO~l)w^GVK~@aEf8liJeGDDBnVvjuI+2UMsWhrF{7lApX_vR|9x&`h(f|8lQZ@v zhn+SdY5x1sXDnp>4@6@EYUoa60`#%!QYJbG^RJ>9Tub*`Ii|O~ajQ5y6YCR=o#h+h zQKOEgbI$gn{=3HsEucx{=^2yw;L}4{rn3|dc-ii&S;2)}+XwJISdyW`+EhZFruh9F z2vxRbtV?WVDMIcGjG@d5h}Trp<;?SMiqURNYQR32k!4m8famEv-S1-{A0`1ngz<2T ziXlE4+3+$@lir7F_yU>A0nPmDu&Tf@RF1?M4T@exvF!VNu4LOU*S32F>eCP8r8Qiw zls*LWrQgZkO?CyY3qG+zWsSFMMTq*V_?3-UaLnGxQ5IXZUXBWL`dfoD`S1*Pv_jFG zYoEwuJZR<~=AR_)bg_t;_NP@geNvmxL7dUm!Vg%MU7fjFb-gC5_y8k}dJr+v^osLe z@i1eVRVeXOt8oQU`s5^ue{0R`hycCQfH+6I?M?HhuTeX<$uUvD!!P)1Y3wy0P&k}U zJ&Fff6hg$6rKu{`|4S(Gd}euhqZ>31^m5s->;dxCnOjb0FT9pYE4Vm({e^#`g^0@a zr=7bOUm}oYN7ZJd5-WqUvI0MEjAmCZDs^Ua|_EPGX zA!jh~T6}goXEv8x=@zWf2v+~3a(3FqeF05>z*5L#gu98!cKvq@umZ4&Wlfu*=Dti#3xv~&Mp@7@)9uO*x6ME^(wg7*nPu3@%>I={e#%+VE=?7lWQ?*`#A!AB^ABs`bIDIG3{?Vh@85^u(fNX-Ut1B zsQmDt^PoC88%{bbb?@rlm6tzS0RC0{!%V_z87qX!6H42V-5TKYTDW1{(`I%`k>Y6? z|7k?6ABQ|{r`b4dsbJL^okbCWq3L8JtCGhQ?yv-5I7}G3+0|rBt{B)w#eVhOX#Zz5 zFX*zHMN^J|ay9lhp#O=o>Ci}{28p;3?D%QfGn^Mm|AJQl$U#539FNNU+1U`>6jhqV z_t7(lnD}?RpVeD(#AuPFK7!JB5$PF)z2vlQP^bVLSv~O`d!5*Orx7id?>-#&YnLYt z8}F?N5xU=Ql7-qS3aJ^*5`*cCiD?h5-yBe!p0hWqTZ1>FO^a%rg~AgVDMY)Oq?2G) zl_aL3-&0AGm$N8)ay-9Ik^r+pl)MQwp#c#n++@$vlclP22;bo#T{1RI^7O3CK1bYj zHvC{)q@oOD1D)mMPhv}0yoh1_9f& zTtkP!Fr-;c_=|^4w83?&P2rJj^w-2=`ho4Ok6j-sj_a%fxJG?TS?6j%8o_OYDGOdW zLhWHAdhe4Z@nP)pu4xLc_lX@!N&cJQg`fec_4Ax7JeY!H+_9QC*YosC*rLUOzi_qnLKe! z{2s-x`vW$%i;$&mV}U`*cgy%T*|>V>I7b#Z~g1<8jAJUt(`_uEzLOi~wz*H93CFoAfhU zN{e(oiVg) zUH{JQi1B0Uaos6^EJKBK_IZp3yZ_Bb@1(-FR(wU8f8Jp}hKexB$~J7J;h#pQCp>M^ zQa6}S|JBiHP+FtnksA2f?l}*g)BN>6a^rKx6&ndVwFF_K^xRP3$U6An~EbU88 zKQdKr@)rJniF9#owk;Oeb`z=_1RXnf1Wy~dKqd9G?ukb5nl*L5s+^Ypg37C(*GTzm zwW)XD0Q-|@SmXO-ba&oX-696s^?#W9@_4Ae|NlEC1@F$9}_He!o6LT@X1&kzYyXq=@u z)~16m21Q0$5xOlmTi;>M5%8m$l-`Lm%D<>#vu-e;~Owyt=P5|E#Z=+|Xt|3q2c_`+dxp#UD+pFM}pu#VXx# zCZ}I*+qI2bcw&D`RlI1Mb_uzX`BEJ@6sEEwd(WEszDsbMU^)Yx6Azyl8t@eqrjKs& z>oeNh$gt#sRaWuOWzJht#6jysV}^90x(sV$($H}dkma@^7lAHSya)G{r6h|oc1!{R zMg@%QC3fd159Ji5RDBiOa^T@!(%j-egqVct)9V@^uRlHiah?!dbsQY zApXoK?7kr5x_`AM=;_CVUPWu_Gb;z)+E!1^ooVJA3iILLlG{?r+iri3PYf$8(e+N@ z3$o8=ye*ucSGVz&5lBM+XWsTgu3=4NbngIP=(KU=M3Kg;L9yr6ESG$|(wexbVXj8) zpcrPNzhnk}^&PJc{d7c*zTTyyGF}4&CWfU4ZTLn8w|QN;b-?AyEA5aBzDj8uQ2iA< zgIi5Sv&$IQC3-?cd*mNF)rtZYx1X1VkmuEnEBObCa21C>)|2I`U4_uGN=1e} z{wK@!%tmZlIX%`=KKda(q3f2keN)(v)BOW22VcgWdcR)cDH)3)Mpa1X+tqhKN3SaL z-`@6L>~M{d&{g&Q=49Qk7mk$amqZ*F;u_bP@4Jx`m7UgjaC~Lp3s=~%;x+8D=1{ci zVdFc__`aI#a66iJgxXUj1b9FnZiqs)Jrd#Vh(EOCeH-=U^5lF8bzl~kpTY*g$_kjn z;}zgylqKYZXMfY%kQ<8w(Fq>6Y-W75NTJNUkyiobfw_2I(WH$}cng=aBaX3p@BD7m za|{OeOddu*GP$@ZOG`HG$hE6KniEczR6B6 z$*&3ZmcGgxl6iQqCem=t=WGWx#Oz2FAB6)NNeYKw~ zN$chd>2U9a#U{LTqf0M3P4kB8!tI;+?G(O*+sy9Srg?HPPKNWuL4_`UE}#6+Ego(y zx%s)BQ$e?4lzQCZ8O4ph&YtAJTXBUsG=reBnikcSnKSZrqK9-$1E;l5N{8u=dElbF zEM3kaYZ}o^?+C?lW{hGE$4R*(#&2mEoTP1>O>xp9hOrnx&+WPGGX5tQJQt+Qd^aOP z$`1eQ_OA8VV<16*GZhEkp`pJlV0CSNZ^-&g=vQGM4m+k(;}5Z8@YENS>VKqqvc9cW zj^Xz;mGd1&M35?Qd|N)4{{G}}_qFM1qs+HC6e7wHl!f$v1W9XcJspt0b@FEYcWowB zyL3Qw-r#s8E?>;}n66EvPIgr+{}t6>7kNRHngv_elWVw;{9syZkgpW4DmJ^_G9>{~ z-v&A7V{;V6m?t_}>aPmmAzxNl7WQWrQs%>2nfXj196Gq`?UFC!%v9*p!N0*EyG^>G z$YnxHCME)~9R0Y1&-7=gD-ja5M~oro)BV?{-xpu^q4egLZ*&#M^PZc!yMCHsurKU) zpa7}JDSe|))pnnO?Qiex9?=!?hZb!x_Tq0zzH%E|4$m%bgz67;Q0VjXT$jUiv)_c> z?BJu<^=mW7gL=(dsJ2O06I5UHO=1LVk)?e@@H>n&$hay?bJg){+-qle2f+qFcMvgg z;UokS$(p6`j1MbfJOO`z8!BzXDKnehtjXM@fZva3x!vHhf7GPa`@^UEe))r!wX-O}*C&IS8;U8?Rs?}aKdUe&~ z&0ubY_u?*n%B26dI-s8!Y`K0~!|>*oU)o}u6(KdQhIS#f^j)E}wB!n=kdrUgadd5W zFfCVNjd(>c`4O$#N{MCsLN-&2gu|%C!_E!J79#m&fW;EM?8b_>1okvLcaJHRfDe&_}s9K29c0uM zVbeP!M6V<&)GG;+;MVX>!*bp+6@ffkRfzb!ALnt`2o3(s zJ4Tm9?{AR~(W;)r@1)c9A=DS`Z%)Nejx-H$$wq1C8 zx99jLNOHI@kQKuERy$u4RS+VByO$B(_NjXZ64L@mN5o%aMdXWLeD>c(;xAke z;yZuawzE#H=qX+2}nB8jK8`f0t~qY|+4n zyK7C47Sc&K0$4rhNtK8ueJwRgbT%kZ3q~1SgtUe6d#SA?|BMH&$R^Gbqku zWaFbA+$=3iXYXSN{y?H}!~HAMa{J>Ne|+2WJ+imFt?<`6{f&5C|7gGLQ}OaoRuAU) z&|f_Nq!zT!C8-h8`%d|~DI`4ARczy_Wq}TuK~Z0|^icXtYgkE9*S5v?WRBe1W3aDs zK1J0vRc7{}498+GITmbLp7L;k!@1`=@xEw&`v!?P=9{w&tt#o$(SRlqTld)Bjr>Ir zv7sra24JHd-A3C7$Eh*81Mxj0`+nPvWB?2!kZ4#_PRDKVu)+1}~Z zHojG`({LIlP{_JYIRcmuO8am-RJ~P$9h6tna&71XA^81#9|Y18-?Dy;CTL!14Y*6Q zjQsszi3@&ou4L3ft1;-&6XHD{i-t++qnz#c_@4q;=H@)jU$LpIUJ|l8ifr7;l*gyI>O6tCUcL4ia(bDC5TaD;R#hL zXV8C}?UF-K^JKM;ZC>XaTT_lk=G&G9IhIpyeDhf@OZVT~Vj_YuzZ34FULuwC!?5Sx+xTMY3YT@LK z_!ipty_vb*H8ZSUYp2)^c&ohA4(re4aeB<5&Wxb7y}~TdEb{d&M0~JU_h)NbeYDcP z%*a1MU(#*=#NJHZk9*mWU{_LA#=r?xjBHZu-Hy1OV|ue&x3>%Wa1z?%UNoK;_dV5_ z(;b)dIx;$=-vO5iC|q7Y8drXCWJ!t9Kwy?nhs=7a&hBYG-L_4;L`jP{{zduYf|h!p z`|E>*UpE`{B4sHD%-cF8BT@9Gq)chL_RuTW)9U1X06*ozOBF}~#GgtVu=Qp}QbH`F z8I*!P4yf#0bB%;i#&AF|D`hFo(>@R+RG$3_7F^6IJV(_se%@hk(YcIQjC4)zWI>w0hrTUpi_I_4~*2`ZCy>-I#^;`Izhdlf`~pm#^+;=Odl{T?>~r~v}a zfRp=w4(cIUo4nzbb8Qe(b1AzZpxItz>bCVo=S)|7s{ZBunM@pwt(S9)D_ z_^77&yq3{dP0RV18%F?9(PFq3DT6wm3X-Ry`-503)Xz&V*kM@1uzPsbfqT#)c8C50 zO=h$Z6s1@E8ADh#cdA`iQoncEyz60m=k4zn(4h|%%trgD@HAoECYp$%)8wNI#mwV= z-KYC=<__Gt<@xO*eIX$|c}M8#Ez4Wi2f|aXT)+6p)K%n^)9HpK_0<8sbF8}Ftk&=? zzvL}JG=H0EnIbWRygoqan12^E{)zCCJ{`SZvrab&~Zw0iWSBRDbxC`TV3}_iMv7X0Pe{ zb!FpZA69o|JH%Qg9yXwcN7<64xafhrY0l~7F=zZ6Xh@?r7<%)AJjHoZe);`W#x6@r zuC-q2CcMlxuH(o=DeSM`=!ie?yvfvMi9aal-+6;(-aQ16O+dD^|H<4hQ%vcpqbtW$ z9cSiNOXhF+#f^NO-)iFfu)(e^MWUnt-%CESUP4j|TJB1JDM-(ef{u)=hMjKd2oE`? zWA{K%&^7Srwk^gwkE_Lb{WfL|Q_l;I(1&8)8@#dfeqFuej89o9l<9GT0A!d$H}~O1 zm#a!pxQ!z!bK*G?0O~IT`u4m|k0YD1<@_XNWovFGKTkl4+Xl~Y!}k&p6?lD^O3Bau zV<4}L=L=o`zoN23>}Suk5P(nQrab8Wk3aJS%58LF`AoPM31_6-^+%;e(lKimJ!v^(821sTLeH)-FO%C3_=e8GJd7;G9tz>+5F>LiB zFLViXv;f*7BCI*~f^s{2)ZHyPI~O0q+rCfDD|1wAYuy>n0+EdlH4`GfZG4&q5__(K zuhZp-jX9|+v45Cg1%31{N>-+`56Rt2l(3K!@gP!fsl_+NV&Khc44pZ{8es*w~R{V!nd5TUGuPH8YvUG+22oI%=eSQn2WwmLcs1wA*rJ zvQ@5wQ{UkBwSmgT?UbnSB^RJVsyK#l>UfR*#E(9COV3S3)M{P6N+>g9tjZY&h1b+pzR+3@ ze{=6(^Ahe*iWcXu$LX$Ni43yUGxCCOfBo9lSHF3*cjG$7V`o!Mb~N?2nr==bz9W(X z>1D%9)|)q^(-}&Ov);EV7N4eec*`QM8gNZ3@g1xUpxHf@;gE$DX4wsuM8nsl%P`Hi zAH>~eb_+@Uy&8O+^ND=KzSDFXp291D5Kr+b;Vc)oqSWfA9=^HsMT+)K->Zf_Rn^O# z_aFo8V1~3JZ`EDPcbYOvR>yXoX?%9pgCn8{*U7s$;%NOFYun_#Sl@HD=yueEbMG0R zcFpkYOE!kwcQCi($m2L2+cP3xfw0?M#Qb@W_q|!8t$=TBN<^6%Lcxpn3`#&u zzwoC4$O!0={9q*aC*>^YbI?5u`rqa2;!=y|fSHib<6q;qac5@S3w7H0-s`c_?N;Ph zw}0rLy{#pGMtM2-SY6~U`U-6K__h0LMUAz?*?c5jU$RDWdt9$i^s8SBt*wxy&Z-A_p;Zg+M2O7;*(_ z7{R`rhQ)H$8+YL%%tolHF4!1$S-atB<)CPDAAiU~qYGQhIcptNFmAivCvMcB{b5%~ z_Z}utlPd%{@b?9>7D1JuKf&uUeR}u+_J@Y1TS`5;7sH|%VR#6 zp@9<5x3Uy^-QM;vSK)OhIsT}dSt|~A&(zJoz{EV7t6u0FQT8je(Ts}@og-DULJToh z?-sDn7A>urmx=g>_*)i=<+u|qfL&xVDIky~)F%?%HP}CbZ=W4>pj7kzNkGQDZ2r;F zqWL%CncqIp^?O;FF~3FjlJ}YXtM)v8d!C9coqs0q=t|QTzwUd8(VO(#y`8?38k$Sj zwjd5FL>CdLJcdLv7_?BA96QP>zA4jlvLjMS&8;6vv@28SPV(6P@`fnNZ-;EjgRJ$6 zh|ZV+{?PH!57~%_GNXQ6WC3&}Ehf3giXz4$tQ$6*?^Y?s@OC1yO%`z$pYNh9(|bR+ zv0DhnB$qXmY$1Y9!NU=JD?20SPihasn=};dWfZ3sQ%8^;yY_8zQ+=6MXux*?IdS#N zha&Yej$!LH;w^Q9I|3a6Mp&&GE_{eru7|xiAx%Yk_U8Md-k1MN}mi+W)eN=(8SGAp zVS&mq|HTsRUOw`KmYQWmQdhEBX<3G8K#^7QxsvhSQS+Z=Gs@Qj*Xj1V1imMX-|PTXKM_w2(>J!-kl`&2-4vOV_D6qR z0iwkYhypySfzL)s#-a?H3xVjPCB-&~rJgaM8pm7?u5`I3F{7tz(4uQ;FZA{gs?A{c>TY6d7*+0lcrDXimfRYGs<_9hKpk^ITG)MYiq&uK6x zy-4mt-@Hrr^s92Zed}i?kd(i$W;TEWg@2g-ed7$F>a<`YeZl;V=LpXCc#Q=Se^4T*ja|+Y z^bvOV)-K0K{^`vBU&b`1MIN8g;tp*pJ<_uC>WYlKwrX%k>;`9WGu&yk3G?Wh(oeev zt6~YwuxrHkMJn`knjL*EX#RJ+?vYcJvb;i6Qys^F9luJOJ5*ms8RSt)Ef>aOHKJu6 zC3r>O_uor!pg*%~dK@8pilG1MBJ+i>ZNr*3yODCV5^# z#bHTS9;5yJvOny)xg}Olb6*Mb&=SiH`_X;=3QExZaigYD~wSrT1^1)0#ln17Yzed>Hehy`&(xP7Ps63yZ_bFN!R`sdr_ZKHtggyr=jS> zxS&{7KtiLpCHX3j_S0jJQm^OEef{27_RsZ27`&|5Iyb-ZY0Zl7dpYQrfP@Q`E|aj9 zmnGVqITjxZhA-CyT{T`11UR2tPOLWq&a@gb+A&BdTli-TqgMnkE4C3g49f<=kEa4a z_p8PP>~Yi)`Ptrv!Ot3xQq6*&Gw>i)_+_%c3U_|vP7M~&Dgx9<4yhw+KwFKSCfp5^ zro6q8AXU?br>`czQ#`RQSX+{XPo>_nR)qM^8P1oPFMp^OIli}SM<19h+Z6Clx=jet zR>9UU1@ug(;jE%J7;(aq-q>y5qE{Y0}^NCJr5b5HGWitEbxzJ{UeB9UqBh z^Belv(M)&pcf9({#~Uz>YZhava|u^!$>`N$ISUp{`v6z={P6pRkF zwgMGSCnWWi3`Uk`Qso0)(@vLi&9pz`efuijQ9tv2rnO1?36g=S;8D$k#gadxf<1~A z9D}T1j}OhIweG@Y(>!~kJbc~CH_mEBQaidlJYQeu<^bra^M}25UZj1WET9kRs)e%l z8zswFFVih2rUze+3pty>IfsgHBCLtd08#iBSm-C==E?Xax|S>=uCQura`NvOg?@3gd>t@HOM!za{BiYHu?Dc$pr$dNin z2Af2?ELxqiUSGo$>Y#00GK;LveAO_oToM`^%vnhZrdHm@@#F_0*RkOf3zoXu1=eYdZDqfS#6<{P~AB@HRJp3mU_Jg6O zNBTpx9gz;em3xv`6jFlJroSC$YIPAbHOTq=R@o3UJP3KFK)lb(!f?K~INdY5U2{&) z#;WbI;gbQI4xuM2#zM6WVY39+^lCZ>g8teK)V@}8_Z8?LlR`ikpB;b|r5j6RO|H^C zFb#$LpZy5u$5OInZqDuMV3c0`doOjV>qtfdPcfwdAaKhGN^`3Kr>w2SDZ|1@ePZ6s z-0TrAQGrX5)=&d;)=(_*-D|?zZ)ciuFX=wdqGwYw#r$ zzE+4z=0EZ=rrbiVp)N-4u&r68niDl^-G%iH7g+(H&Wi(h{whH1!sn8@pc-{%9v?Zy zIX2Bor3sh}c_jbXXh|PX1z7Z8Y}rw)ypHq>-?(7?dV|S7*u@4>0@loy6O8p%KwZ(p zLgsaV6rXlu=3Dkrl78MeDcW{*a+eB25h`5kx9DdbjP7JSNAbxYJ$NfZ>GtZcI5Fsv zdd*#jgQ}&PNq6P*a7{8+W+E~t6E1S;>!2N(k`M43o(W|caTno>Xf{oTJ6~FH!i|)- z8}Zt6xX>F|Y-h$@{0Sbl7q!j}c)(E-TMlP~{_g&3m8}jYpqUV~Ocg345C75;hj(F2?xF5D>+qg^=~NUiz&;DW(-Kzjw>=l+sO`7t>%=N3jm|L6=5ucQwGSDgYt<` zd2G0hoVFlvi_ZYkdhXRcn9p5>mQ!EDFR_+tt@~nKZko)D^sQdD7dF8PkBSWpE<8>u zx)XeWi{_PF;&@i(ckrpV)TBO^U-^E&{L&sbx3(__Y{smhun~+|21}$%I#gisM~Au= zzGwFmnaik&ngpnP0rFH)bfd5U4}|TqalQ>?+B8drS!GFaoh8Jq)3xIGlv})I4spQA zf?$x5<*{^|uUIqz5a{hC%<%*36nO6x)u)l_;h)bAY%N?ZL4vJcli;>Xm;wv|h->Y>bsWIF8maM8tci-KKNsMH zxE})##-vGX0*<&R@=1U0MjwMqC)a2*;bq2{XTBER$bI;59P3zSb5pb|9>JsrwL){}tM9%u`1Jpm? zc(VHwHk-Lg7cBLT+v#$xgYFd{2LU=r>fXeOs0*h@{2+u-D`(BE^a8$CY^Iyn1u&=wXqR;z&LS;F0}Sn^R?EkWg& zZ)NqKqj^idK*`(rwSu>z`?tGbUO|>VN zEjV7@-9XN}?^(UJ@>q!W26C+)kakPDSQq)-DpOF%?X{V0&Zb~L&TZU%^hH${>O(wN zA6Yy)R1E$msOFhHlu`Vx16vzOxTrD=OFrtB_6jC*5wS*!6Rxcnjim2)Bb$M+`9Q1O z47NHFe`B^@Rn@-+ly0V?OCA+1ag}QOnR1r#beO z2yyr?d=H^Wex#li!n&FWu77%eOvcL9ur0Vgvu#vvvnn~tp3Jn!t61GMwIGD^?roIT+TLxwg^2t3iEhy3NYH_#E zqGZ_AK_w@&<5$vcJ&%eE-h$qVKEN|x9D;|+vewgMt`aPI&yBV6GGA#x%4>Un&w5)k zo=sUg;&J_hIGNGHCE`;Jwvqj3eBM%G-pxIZNlZ;WbEIdH{ z{lbz$N;PAX6rm`GO~I!_x(xu}-~3g*JLLO1)FMAg=h(o=l#q$%zSB3wT2z0ei~jbP zF%JmQX6|!bjGIjlY7~9M89rTzmiS>K{Y-KM_SnDgD9>N0+qR9n`K{w2e zU@|dWv}6Ejm2DloE5&*fwQ*vN84si)m6#{))WS1L$c$8{30ftPb*k>eER}57@NXn? zS^J>Z&2sG9@{R|uv<$FYuv6d|Z4r3wCH1U=)I|MbJ@z?}_<)ZW zT5PJ&HNx0A+p*SXAQdU%o?NW*;(O99Ox-2;VYWmJ#7KvKsy?9U29^)1RWF)D5~N=&IHPCv?v4wIV8A zPJe$OD@#zAW)NI$c4{a%kTqHgTvo#NZygG|2IE-AX}-Lbf6oD4WHKcqVYbOx#_$+N z!*-HvCH(vibawXoTCqD)!@ER{r4}{*2<pR+K#JT z;sIb7>h=#dRi9ABDH3$);!~@Zy&^MMdEoJBa{Al{LTP>BDnP3Fu#7=GW3?QL_PBJ+B|vU1#V-(}?&SAI*96cZGIz#q_4Uh5oO?JXEJHnNiBr;a z>T?7w0iQCUdaunr((TSeC>9VGt-xio1~J{pMR3dU7ik%7|9eJitCF@sCwVp*QV`ip zc6+sv(_3(f81wnPUVdOaR6g(En*c6uRj-bLZz~J&F+~rfgVoWroxQW)>yK++zPx*J zQmDobqvBPyW5PQeT~DUo&|7*(hC=7xeD=~YuVrbrX}rhfEs*cG9yEM}0xetu=gXNJ zrDxYlu$ATT)l-;nv#R<;#BKz67{m&g^W|pxfZWQmM7x-#_xoiLgogl-2kkA@<7xh$ zwI_`l4y%iYkTi2^*U_kt4+0i8J{>-jDGv|Vcv<&Ly-g6DxrS1l^mN%A8fDl!{#Lhi zg}SuyU*yUIOX2H&oZ8w|DFYj-1#n+I;jyht1LFS}B>0@qA|eZ_9mzRVzBfH&&1m?1 zJo2$y`taijW8awGY1Wl$@>WJsH-0VZkAE8bSRe^;<9Uf-*E#H*a~v?(O@wHPUvI_; z`e>e*A(y-_=}}y~#%bk-DfJ=k`nhXhJ1Q~wRSPEJc~W#n-@;YYrIVB7n^D7Wh$H3{ zx^zMC@D@MG$GLc6klbHQ@?pAMB&&xod|x3d3u!cBIb0RSp~G=B7G1!nd|6nm2HEC_ zd`~_d-&5_B?vim}sDBHv@_G?G--|ogsTue>;Fzk`Ah0cc_#AJ@2P!cFIH-KA!7dq5 zj+3I)MeAz<}%zz?N%F!qJ8CPy; zd2e{(aPE)poI>rNI5lC~`=GEcU(Y7t4$ce2EHjVt4Gv1xQp^BP5LN07NO=!Rtgv1z zN~6*UH=1TO-r}@nE1RP^Pe+%7)uuv?Yj{l&Rh9 z?8m#qDbgIP`>p`Q*&Wp{N$z^A+5`Je(QH#BU*{&Bbo2{RTAT^2X7?En6o*J~))?KD!!Vf-KNZ=H zy=ltQyxD!6oq!eeX%5UOe1Lt*!)C5+D~u`%DAa%e-1VpudD4Pnf@*s5`SIwLeGOf= z##rK9HoPZM2?-?FuBIfN@iSDHf9N#jZTjs^=iU=KB})uo*&X;HoHMPi5F_bx16$RHs4Is zBRZ5deP&8En%?X3b}j>uzfbh5IwHrJtw*qs@RFBZs=6UlG_7%VkQbr-b3Vt1)1JlZ z<$8;v%-M0jQO1H)1ONPI=eEtXt%yA&ZYeFR!%na zt=l=yFAICx)wu`>9%fH^mpB%l1ic&Z9F)0i z)D{{V`s9fmQMSo^i=;K`O3!ConKm19;c9!Y;iS)|t90?);Ff22^~K2UQa% zGxXuaSwMBPZNDGvio57I$D_JtypSOdFFhx5xg;%GGD?iGgKuvCS(ZzKfpP?#*pt`L zM>7C(fLvdqe(~Hgfj0e?6F5&Qk?V7-$ zcIo4}<*#ign{D6S7_x@0mA54KwCexKT1GkWsi2`Lgr?~z^ih>Ppvl)L2ntwl2PMoG zIcJ&suVCw^PJ9XyIM&1+o!_%aJLlxxmawvJK2&0)!6W|57v+~aqHdX|u7?2Y@I?;V z;F!DpVmlb$G>mdRS80}VnZkb0Wh%aqqc`lI0(e~Sq=DbyCcN^hs}%Bu2+Ao~;%hvo z%rwwjs=#vxQB7Xo9vdI5YeN+EZwqCAcnfgZ z=_bY+C(rFfH!cxlS-(Ht%+qTXg&*?nwlN@>(iro6)#NV*e*jAf?=0*&GFj?5G%Pzh zgp$y0UpCpVi+4a6uwoV*=bpqg7i%^&_YEcPl{w}lpq@IA!+8YI;#KVI;$skhd1ZQk ziS8sNE!MQMRJt-?D~A|GAZ4GU2$V{vS|@2C-uPwsgc$3nDPhRs?b&?tnObJ*jY^$s zbtv-3y+6gvVKG$u?>D#E)n<&gMyu0)*tL!TnjwM0D}Zhsg7$OCl%mSS)FIkt0`?Oz z98kCuxO54IS!dHAasC8X=;-bxRVz2E20`aT@NtQ3KJ>6t#7Or9Ozext+ukn~{zk(jta;&t{NV6{qn+unp7d{JSFq;p)Tj|2f~- z+{3b0U|+Bk-bFUM6?b6Rhq#g4AK)wG7Ym`OF;%bxxCK4EVk<%zpZD}_Y+daSh%nMlDH6T4QMf)}$@Th<^Wc@Lc3D2przni@Zb;`o zfFW*e`RynJ)d_ucu$7HH+2Oh7#5l_+_Tt_*#s*LV~bzMmrmSyYp_{jw@ehYnQ+nt&R#qSUD7r3-ihMXTk=KrJs3oM z6CQVGUz9n!-mP}{tHf8pOT6 zxV@K>vwC2O;>((s{3ujOJkft*J|G(J!VxGq?ETO0gg92sEkKGjYO2aBhyVw;R=AX$ zzk|OA$&Bf=f8Y>tG5Qbv^Vo9269s}yr66?l`&V11=Q17Vu5tA8kI}>ybL9HyxrhX~ zfvOT1s>U$~Glexyw)e&>N(%XSkyq&NCJI=;RQGo2cCt=j$QPa$+8Y!sW)MGr?nZ_e z#M$kdpaAyu3gm{*3p=%Y0+Ggm6sln^#%Xq=gns0q zsxDY7WcMz!tnuIXBnOa3=Ud;2+VExSdA zziaAGVb~~NIK$+usM{KcMLQlajU3W@k9~h1O;Z70g<=7`%5Gk0=}xpA>RL#usG*Xa z;bmPbR1UQH220Q;5bU#n8>{1kv5w4(qPgqBcS|>TT{#QXke^T#zpEVEyHDSCmYI&kqsF?+jNuv`Q;pF2WCKgCngD9a2!9o8o)Uu zMi^bCNkcS7%#<9sa$e3oBd}8Zh?)2im)QR3D(gg(a2r*qx`equZ-E2>*>8#L$|Tg; zb^}yiB;L{0b^c^kx9G^zMenY&jNX$dl4rkY^9G%Sk=goYqB^iaX4o6x^U!Wh&R((0O$&Ra>grdt^MG3qnZy&gSN?xioNsJ zuJnZm@T{k0#-#$w4*EhL9U)y6i*V#1~FQY%GljC$2x^@0MGo z|H;|P@>J=n+LNrOcLam&20*Mwj-%YL7`H^xSF!S-S-Cx)Gub;{D)jI@ZcNU{gV7tV zd-P>OHuig7tuRYfj z(&@-ah_ps+^|>ykXW{9^`MP(xcXZha1wE=}bw(B$T~JWdI(7cB|8Ti1?LsT8=bTj^ z{gvrj_SX)yqFP_prw`@Q$Y}7ogHtsFHh5_iKNPhi)y$dmDE~f5l#>+efh=^rn!KqL zmv@Nf zuX=eE*OH##Rqqs{dNStsHsg*>c_WE>@Oijz=@m&kv0Jsrlsol6wkl=KZ5Q;6jX#KT z&^7>7d|%b^XFJ&5;)qgk`+)X(HL{NjkVkO^23Bcnq38bN{25J)m34BwN6GWmsUofr zpYKthH^GNAMIS-rL|}+rdk2qy7#W|iD;OX6Q)$|f5d92d`Z7jOf<&vV)ElA9?fh-m zng+QCTr%$Q^axF#hzKaWUkkMt`wlC?x?hmj(b&JXv?oZ6<#r_&R_6489Q1nx;!L1& z-k}tMPDP`m&0j4YJm)ri%x$Sd-SM{&1 zSE=bn&h0b>34+8>|HKAH>DQb01%HgTV}6w8VnfEo*Y#&-v)`lIy7+!k*9DU@3-`uz z{tc2mswS-l37^`bd5x(t2&|i-*I@WZmf#%k&}B&wwkB$5ARxs4}@5^V*_V;ET9l zDfu^B!UL@R*tVvxa0nObTf{})O4Rf@a`3m$YgMIjZf8M}<OGlFt zLv_6*W%!SU7fpOsFo6@J$Y^c;=j_GL-y*ijsgXpE-;v`Hm9XNoL3ZPdWajmRPrhXZ zY)g#p|7XpSt=T~EGh7lX{bSDOJ|+~|Xmp`~Byj=mFr(l*qV_Q8?^MN;q7Wo} z)hy@X!7a7OFvu)aeio?8Gm@WBgidm>g_A$u8Ll7K0dp>hmT7_7boPg|L#e1CUI4@C z%-Xxg3hp%p_qguV6orK8dG=*X>V!eep|e4%Vb7wGe3@C}0Kz4v2x0A)GUh0FP%CQ9 z7~h}y>(7%R29c$U6cf$I%4j(-qofi1_11m46QYo`{t=!{vPoZWE~n#iE4G5;VeH3- z!o&#H$x8&PL=$XLU2FX@SCCl0Ic*X+WtC~egtgN1m#=fNXc}Q>451H{vbF^ zul|^5`xFmS0?QI@v6L{(b>49B!l=~8@w-g-GPXg%CXgbND42)3&ZXw@3)H383}ifI z`QxBK(Mm1KhZOL}%Ag}b6x&4SbP$Bg#!IBY#G5w7MNOOA2FZSmv$C9y_f|6Of<8xrrKFytw|$aUvfw%T_y4sy$#=pV z@RqB}B*c~n%!|CI`hEZ3;vBpG{B=}9n6nTB`r-LG#$tiZh*sOxU4{6rvxfoR)?g5bjg?>Gf_mwf$9UN& z;#xeRNv7@H$8XMr|NYvhO?CLTb3BBBA7=;ym_?0qeE)kkh`DtllB~r7fD4ciq}ZgFXkdm(iUMReT#NtRSItQ2*vvN*~dmXlZbyVVVEEfbyk4uGX{19SY+$WDKVW zVw3+)F;D=Zp$V$=ctKT1RNk%$aHguFE33VlWEM|B8Q7z)>`e=RB27(aeds8abm`24)9S9G@Ag<^l4YYOhT_Nfr{YZ)&f_CL$g(*u90lt_Z-HO56?+Z|v96aYP)dZXQTR9hV9 zqRr%G?h?1+C@TZDtKPAiFDg>eUfr5Vh<E|loRytme6uFrVgyHv2P_gt z>ehsRHWxk+p+hF;9?1Em`VRT?qud&apH_kqj*%yXd+2{tk{Z(ul?yl0t6@SAKjgy? zeAV*;Cr&pxv}uzZ8s<^s{hwWGf{E6kJK|Moy^4?>wD=r&U+RdSA_QBaz-gZ$m0kF6 zmOW|_o<>I`T^732k@(mHxRmbr!dTSW4C?>h_AxF19Vk{bv@TQ^Cj@~>kp`0zj)^UL z@A}ZA77s1*N^k*EYW555CZy$fVaF=6W>0b{(k%j3Sm@iwE7BUs=O^z{EdU^JN}Ht8}61l{W>1$rn63eZmUOYU zyuk2YGgN43YuKK@)9q2Wf?I8WJU(RQa8Z zQpFZC=1RK%84JhK4gx;5!g29S6<@eFMi9JS3v5l7z9)p+M7}m-KvSew+ixfRYLOP)0gHQv11UDhY418+}ciVx@ z<(r2Pe**k(8TZFeLdjm(ol8cfXrJC)XXO6wHT zQ4z~yg9D~Ugs211VLr%ZFghLa_!l{pXKv9gmXm7$p;q$wsTC@M1T{qCN6-ap{| z{PKfuo3+-x?(4eNwbx$1du#RpJ{HA<;mTtM;cI^+(@lr161FRGOa%#*1jm^o*V*># zclBUoiPYJPDAiL2AVL7E(9Y!jm_$Rb^T^Q({IbRzbs!I3fIPRz4_pU1er~g=$+blJ z=!sa?|4V59zlE)#nm#Ru25V=#X$Fh|I3KC<y$fo`AHqz) zi_=#AgPKk+c<4E^X{o$SMv7bdXyQHi`6%KuhSJ>7bD_4E(8n*KsVeU^iqPvcV$N+aGD=q(xZ&`D^U{OU@#Fnd0$!s83Co&VM)`SOU9Y zEda7RIoLOxj2T5-W8zuWaR#U-5~#m^yGcVp{JR5ecw_+n^_KLnpMd1II3u_XISFdx z{Ppa3SxT8qff#ZN8Izj?{OChBbt%{3(bw0}QZfBpOY1{?LIk1$llGh2o0Que-jl3+ zf<=)4Xv~$UB^$qYarcv8H4SdMKQM4g*whH3a@BAxqVEwu%hP6006s>=+9$H09q?ZW zS>d5(Rls-EdvL~P!^R%5Twz6npk_f%9n4(58SwJkTm>}F#YLk(2L5T2C0oaHHXM*gkOXp+8(%3`bYK7?J*qKf{PO#==qU! zF3vaeW$bf~8+zL6%z)u>=6o&q8sW|4UxhyvoBMp8r=x1L!%;_ZXxTq0rwjg*{H(U? zAVws27hWG|(jd=18PbnJ zv+vlMIy5P?U8v=ZESgE+$5PPx08$Au-y59h@+(A2lkz}z5Uio$euGzUWc`LUP zfNDh3jPJSg`XzM%7hYVI6({ZLQ_KHa1AptVbqR3f1)#QE)T+|IzL|kfYCN~qkqdRN z{sIW~Met+cx*u}kH??zzLtp#qh4p{Efwu(r1ZvyXzy^RH;5A#ji?PM1xXNiF&*n|Y zPv1Bt_-~{M_B0zVnCULSx+r9dQmAGp{LuiI%0-A65zvPIz}9_}AN2c)q^p3)d{OK5 z@0na~E~Y)R2-zYKF4Y<^s^72z#f9gf#ZP=r>A&RL{)GNk1t-=Iz%Br;FF~nwUVTNq z`>F%L^SN%QInSH{+7EGF$g}@Ln?F~>3DT%{0kGF*@HfpS4eOB}fO1)9RC~~v8-+qP z!D49R(AQedJPXwK-aU+YI&(tQ89jLvZ2xLA7~2Utn=)wd?|KIS1IA6)eS!$fU*1=G z&EB(mtOb|GcmDdlzn2?VL9FLb=A>pSc@bhTam#=51G1c)%(!-_^+w!Ab@UP*hY8dU zS6A%6Z2+aXnEntn8=tR2&-aZ~Asuof=doj;1pgv{>IrhLWy;Z%aEB)2kYD=Kej0-1Wd$#>& zGGChAah!9zO*+|^piSunqXAhLHSrbh$zluO<;@k_8Stj<3}=JmdW?bhjMR%=OjP4VyMdepr~`O20;!Lut)0dei&9wWO7=!|>;qSyE6jP68h$j^2$aF19&g84m8*?=~JxgcG6@VQ8 zmOn|OJ_1HmdL0h?f|L^R+T41})3@BbBPWxU@cWvAFUZbFtos4q`An6w6CCxO4|Pjo z@;J`61J2Ggt+r|MUhl{{ZQq^SP8$E0=faHunr+_Sj4k$zK*Q0$bf%9Q$Q}T*;!8RZ zt7Y)J@E~uWv)OU%23{4n06EkWw*XzH;N*=NBPsO4AiH43I~+{81iM(rg7KW-=*emR zo|F|Wl^Hj>oQW#k?>=ivrg6(yQ&w@i-?B#0YCrNf=koQ~MCjN~*nqYOc?4&Zw`4&$ z$Zd29_uTI5xyjerl*?OW3qT7GnSWTIRb$U;vF7d2k4tW#U0QMj%o=pxeV+v{H}h4bI!{z!s2~BZBvIeVly~M#uICF ze2ZlcOf&*R`-sCAed91PJwWaGqJOn$fnM_}27~flP+sysk!(lQ>P0E62F{WxVSv15 zGan1V;{wjL_5bV#M4^IzK0OND=(HsM$;wbe%ieJN^;R)Tjfv>&rIRa@W6}&A2vn1c zn@$-kZ1n0COCzeu(TSVb^rO^Sv0`V;>pk%O@`=KM@z>?03sayv{}+HLY%f?a@>Lx5 zzN=sOAZg@_#(&z_WU|I&vR%J-IRMngZdS6+!pd^c?H%Z56VyDSoy`lD@lxI)J2m*H zNZoS%WqZ~Ba;V&r+y9Gg4Q5#x3i7gpoSy0dfap{5QWJPl&t6EpN-cago(9)%1H^A3 zIRjK_5|^3+1xy#I-nDB7;nFFES5a_7drVQenK(i;>6{3)^7)ZMU#OhlWev^ zX&6+;0r}G~G09reNi@K~1s&&^L{wN~yE_Jw4sBhY1!oaoEFWdzgmXiLMn5Z46qvDH zZ}yV=3baps#YG)}o6WNy#M%#1<2UU?f{J$o`9hp<##|#WehP@bUUa}@uF~AG^d%d* z=;;@P=)Mj#$;B>q&rxbK=63GT%xyb8A7J!^+l)HvLqn8>GIlDQC}Lq}s6!(;Tvpn@9_ly!2ROG+sOr7w2D%jd+XU4YE?-U{Dmlz9x=Nv`qG2#}Vz(H3WOlDxphR9`M)n7sHl zyPM;NPWHg0avy37TziehI#QMN1n+cgVyd(kckI}Fz@}C0j*-Z*wQ++<;hahT)9XEb zKsPdc%W63DbIY!^$!6zMgE^PC+i|a<0pP<%zd3MmPfva#PD{HEE91dBDL5fzVJF>i zW6pLM*lWHcaAx20F4V{d)JU^-s3ggb>*ZL3$z^SEsn<)!Efk=kv1Ga)z}+;v#V+-y z^PVvO@_l7FY%n5j)`FQC@TGi5bp0wg=YY}#3G)*YX7R5CMn>ORouDD~=VFe<;_z(D zvSHBK4)U=}JyWjL0Ytq@id8yXQQ^b1)e5Hbq$)xR)TXBO*3EFwU~l!}m^;u6|E~E9 z-O-AE3tQaE@J8tiz1)x`uOmr2o{^p%&hix?!G%@u>;_pws<){oHPZJy9W7p~l?S?h z5cXfR{W}6Wv%?hdDm{9VtE4>D%Uz7(^Qc`LmHX2Ya~P8Kz;ugWwr;GlR?rV` zT$rXYfW*bk4RVsBTa9gQTaBqket!jU#`N6>z{60sZL%`k;3W~AKxNd0a7pJ&bOE68 zRKzJ5yqu3xCf9H*hQa@UNZ<=bf?yhsxfcmS@s9(pc5bR&NLsp^;pOBzVVboVRp;rO zgUJoid3}MwYP&@T0KmDeFJrDIV~)>Px7X5+rIK%xE>8(vXW*m3sWTZks+P798Og5* z5cvpfx}X9iK^9ph1V@xlZnf*Z6e|->Mh2D@>wp5z7_hmVdO0g!CC2!=I_tyI^6C`( zIc63hI)VIl?~XtpJgYN8Mh!W^1;8l62{7m^1|b%JGrj--4DnG6#JSS?5=yi!lFsqc z;Qj&x%`kJnLQWFQFc)d$nB9PEh3f@|S24-e$2Zl2d;YBhY6Lmma5zTM7C*YQI2xft zSGQo2S^Lu*fvS}YV5@1d2;}#Zu7O1ljj;{rH#cAC)@E*ZfuOOfsAgAqG6j2nDM0(U z%czm%1D0AAk8E8q_AdXUlsl3(B^R6M4N9jyO3`ZdjW z+csQa-crD8UKfzY=Xp7C`>kzj^bTE~m(`|!U(YDB)@Hq|#ANdA&C})pBR1k7Od>Tg zN8^@IQnYe)zZi%67&jqjsg(_K-Zcjl_gatFO54HHU0RP^o&Y^n3&=9Tg~AO9+eg9EAkfua(mS*29|bhiTRsQ)x@4lX_ zU8@%Rf@D-yh|$3aon)76&+4l+=JDQs43rKE=p2gOp`q{4_=oC4cp2;LbUzQo%qpG0 zo_>(*k%hH%ufJlXp>07haQY$&otQPSz%=(PH0j}B(0r}?9|V*V#^F=ObMeQ`0N^Ax z)zXA>VY>|HCg&lguf&>N&E7~B8a2l`QCZ)xbOULp`rZy-$}z|@-qYly2_>4*k1w{c z3{O23KC#fH+Pi1C1ZhjwrS6LW7=*+970?7bPbzkX+*73HS>0?qy%ny+JK0OR9mA506yy@)$BGs)XKBfbaW z->_JK3$9yA>?e=G<47^-gXv(=Izu2VbMvn7g`MKxeccX)PgrD$@nNdE?5#@9z0R7o>4&TGT{~8_!wIVnd`x}g`@SWLev*vz2 z0LV<6E@!}*tpZ5+jPeE>uO`!rZk&b5S)90cPx;nxe-Do8yo^$t!9EI8ebBNU-x|JK zf|)P3O$j-x+u4#bv38&LN?YoJ(IP9CIIt00F~oY!da)dt0NG{~AX2Jgtb>HE@l$|BS?+H}LG1Gb|=lz?L;rB_i`JkIK(kH ztC=+VHdHet9URTr0~ArgAOkH0K(3G%NnVMr@A-6|fWy@%xuznMu&gv`GY!uD{yZaw_U|KYJ<>9Mv0U&P)yI zIKR_GTtnqNpU#jt>97JSu-24t0O%fgM&ZIcLvi<<2kXB;Zv-=(N%yAvh~)V|e1X=< z@Qtn}nJC9q zT*LqsL0zu4xWD6Uo>ny6`DS?X!YGC>4bwF5)Aw^GDCCtcHA|PtaTmA$9G+<}Ydv8B z$W)mPD$?^AqSno1lCo#-paWjkKl7!+k1x%eBeZ}ECy?beLfKwhxcVhkCEQD~CK~ro zit*O~5kJ}#CA=WaNg707JJt&w%z?rE4Zt&-rT06yYTvEx2gFbzF*uz3!9Twu0nA;bnbfc^W?s9U$8M zZ4HLG83MUW0vFs*AIF=g^%INLq23#cEgftf^b?w9=i41Bl;DrpT3%o<2eVeptzNi0 zUD>n&>FaRQ1qlF4Y8OrmUo7XsTg&sujqz{tbuh|YG|(+{YJJ_%a?{{`+qJr(#W6gXRf|SpfHaP%=PPPh?mm6Am1A3;V<8Y zVCsaF$CkPdf8IL3ZI#tX5=y4YELJUx+cMJ8@_DBfdRhs061p1e8a+;8sbc4$`u0y* zZSn`{Dq3$DjTYQ<7x(U|$?v3R9SqNR;)#G{J_k(YM4_^JqnUl*0r(9WUMz%M2jXW>Hs0od~~)f z|AzCk`gui-U%jLo9eW(}dmfBX&F|C4C?xawZ0@a(LjGyyf}exh`&XWMDX)*L{&N$* zxj&K~5=q&nD2gCugIIY`*ReK?DSPO;f+C?mQgxQ!>*%|96iWFhPYLF?DZ!+lDuy+} zUwjU>6J~<5SveKyGaF*@VP*z|e;84{iVpFZtG?QV@@C+&OsK12hjWY|=PCL&+*ib3 zIUFyt$|5!F@KrP?tq3>kr*`=rv3-|dQd48zp;p?h;IT;dWjSZZcMU&Ghu4NzoHC!K zh1HO|iT#xJiqDs>de1u1_uMode_t<%WCdugtq(v4JOrP{_K_9a zvVr?f{cMg0F}*nW-dY^VeLQ~8-{Aw~RFbY=h&<@Z?u>(dlJ4FEtO!Z+n9u3Ro$q(w z@@mTG;5lUxAt}t#AQuAti5zJ1kU3W*-8B=zE&tVg0noAR_V%)}`9J*(8wsrKOeghaYI<28eI z8j{ay+mtO&^V7e(r&`1+(`Oq}w zlVT$oS*7u)W%aQ2I`VZUX*U%ov`(lum-Qe0vg#pPOH)11AwiI~w%K&LRhtDcHXRSf zgubX3YP-WzJs1e3_8{>S{!G@ckAG39MnD{Cg$Osg$t&sH}6$+_mz^0uqNnK_nb1^JFd0ki+RhW ziN1v;!*{pktp4ze?J4H`08tE)ulZ}2~I<^5f7`KZU^?hAkK>I%CW~NnNAtFNWp<|!(9_}&ychkkN)99 zaULUT_iFF%5dL!|tl`1lEvYlKDkgh4s3loJlPm;X9eM3(DQ0H~XSKrsI8(VcF&lG# z_BWaFu@xx5_&)7!c+R4XTI+vPTh8z;!krrCP0KaMZ+!U^jTeDY8^fFTS(slB0q;~_ z5zqA#cfIZ#PiHzUyZC$~TgjeENDyR&5p42{uTc!4l%1itZgj2RND)JW^epAIUlhuprTR~*J?JI8k%2$MH7>(_}6AW+|DLT1BEMy;%(>B2;T#MqkzUZ{YNW zJ?ofPRDMN~@@bQ_D^#Jhn05sW}mJYkSszL>$Ms8G^GVz zjwFDzgcVq2lu#c_kQ2urwl-6np1mSQt6%^t#87RMzB`*wU?KSL9Kibe+bsFYKg=2UOR%XiNTO&qinuJlyP6>v8UUtS-Zhvx1 z{en!MNAX1ZG!2T};FyDRSW^F_w1=j&y3Xrr+cKqln7W}iyRiLXw;;kNVT=yN7#*_yme|}2t<8Q1iTZ&i_{OK73ZewUs zwNbZoK93zKuO^#Jx5`fn;mwRq?S_n1YZkrL=gFg+mwjB!6@S6IkjlFDN~-ye4Q9Cq zCF0>A)Q4PBnJ`=6SeaKeZpbLzI2)QJt`F*a_-d@W!Qo`z;tz!h{q-BGue7ao|81}E zK!vybs32bWF$e!!VHx-FyrL^gpK`RkNpgi!eS?^yxZ({q)VsqyeJK~j;BfAjx`Hv! z_Z=+BMjrcy6g^4qB@bxo@;Sj$zel*>^&^ptlHF(^toZU{Ou{1^uP>M!S1i<>!;6|+ zoB9U2r%N?WX>ifqma##a+3#fHXhnMd1iD&nWhpP6C-UBQ{I3RgTvbsBN)W`ono}so zbA(9;dX+~cksUtsDHz%ZCB)z`%ROG`{fBLE6e%D2$6kg4hodY-IAWNJC@mTB> z?-4K3cwp6Q_0cf1yF$u9zp7Y8BrcUC|Ldf&^Cb+>XnH9K&ENTc?XIF8b`hMshg;a* zR~h39u6xA3Ay^AWV`sx+2!o5%C5b3?=Sw7-@&cpj#u;=^hLP3Ey}VJqT@i*TRM!H+3@ zl>eP`nv;&Q>wgcZecAr%dU8Yqk4)lld0>yCYKK zKVzMHvlsP#99g3d7U*sj3#Ad&)EprZvVMKxu6jV|P2KEC6+jy#EQ|YsmF||q__Uci z^wY$ZpE{o_N%RRZT!c9|gS+!DCp_GhZ~*oes>vUx)IJb_w_AHT7FCgj0Zuwk>G~^Z zC1Zn8aqq&-3_8qn1xfT0F^#YLaSa$jkOWcBbeiOn`JBEU{s!Dp^*VM$Y7Q{xM0qJy zXds1CO zFIuJI5_batlibX`=}exQejMW(H*@OWWTiFHCSX6YSlV1GiSZggEIlWBK>e3ADQD)^ z#Jd6UthBOIsAqM_pGJ;l&#RtL(Jy%3i2phpME!?XS9o5va4ReYwGCUN)Jk~!!rGep z*W=12i)~myWaznax9po}=Jxch9m1w}wnL202#?;yQ|j&x8df)L=NBE1XJLI(*= zrAhA~AiYEQ$M5eY+)wW(N!H4>=FFVgXP5q13s@(*Oq8D%mzxL+J^D}FsV-rgGgyM;frvB~xJwvoCzZ=yd%CJWd+GxzOe{IL3Vxc&%)F{;Z4IMtT)J3jvUW~ccEJ~2&pF7N;@_oA+U@s7Y$ z@Xav8eZwu8h&fH)t;?2a*UeSx-*eCK7o=1D%8{0$hh+I&d+XOO0`p`HgN~Jrx2ry% zT=W6B0ogf|?0((bva%b?eQ(xkl0Mn5V?!YtKcxc}Pka|UNg8Wver0b4Q?SVmmz0!b zzp=fF6blRmWgi&TwYm6T|EPdlk%Un7(*;lkEJhUo$u!M-L$|!ucklJzEIZBGvy*=D zegGHxkzdJZ{3sGx*^P7T-c`buJa_Er&IrPJt6M(G%ADgJRiBVMx!Q$EJq2>NU-9xh0mg=c zM2y2Tb*+>}B&l=3F(3h(C+JB)F5Wdf^Cr)4)aniNL#zwXB|!ZF_WmJ|#A-DLQmkxf z@cWbcKs0m4d&+r~p$D;h5?%nEe$RU8z$7Af+)Hk&r+WX`*iU}t)wt=`XX8gtae;;Q zZbZ~&U^jVcQ`r1t8xzDXzj{qKj+ci8Boy7A*F;b7LvW0SUzY8dzI$S+ga@VYiHvmb~)bu2{ozYEXZF|Io`%>7Ywz+#wCx zl`H>BU0_xd{DsUTy<07~dPAW_HE$wlIRdws@?lj|8|+d~5^>Kas3#QeoIV(bULU@ z36Y`vvW`=X^!&7$Fw*A3vLYrD~oWV{d&3h>BSa1uLn|Qa@Y+u3Ku4b~u%TFO@1yU$YNMKi-19WgFCk z-n`>~5lwOTLLqaL6TB;Gg_8+8REr7`UAsv~+VvA8FNNWfw*vObB_rxB+?=!41s;26a1JM-hFSzr4q<-pweM=>}t^YGeDyv1RRt9LrgRFG4 z1e)G3x4w+OIh*Byd4hE9)uQOW6HVA#Jr+;}yDT@Y^0W;IthcF|*JJ9=Wu z`{;n4G;%ReCFUxMSQ~}-%H_x zn{=@nnl5lB@q=>ez3afOiOQalLF*qCSSf%%%N90LbvN*IcVKKWL?C~M7}Hs&_#0?*0m%or+eY;)ON99Z&?B%kk3azK zm@*;HyoG=t*Kh$6rEPu4{DxZm5$7GRbs-c!L1MK_^q78am$1>1?Q=I1B)VS&fU&J| z-dnTRUqkdp(Vs~i0L8m-b0g2ur{k9fJoug0g{U#8G9j5xfXR`O#a_QJ^CS+dTgK6Y zbiOPY^}}BUz_7_H!qbqh5b7z_M78I-W71bCtcfa^}JX;7TjQ zrw0bn87m=O^y5tlg@m}tD8Sv;GQ5u`WUI=%aHK`4bBtRId`H=9eYf?F|Dl~e=+Nw( zPK6%*8784gFl2cBzzTOw9m9-2fBBSPraGJmu!^^5xfuEOsr6)7?PQvKfXw$k0V@p! zP<>8C-x*p~JMTB8kfVC*59YM(* zeqpme{2@o@{}A;>V=47ZC55!IJ7Lr2qFF#|&?EgJ4u16N_nVZhsfDK*g4f9_gbgDM z%MeLfTn1Y8p=7^vS?so4E>%28;{ar{-v^FH4b86@tacyqd>aeEueea^5RlHmM1)0* zdz^+e{3$BF*4s5G%M0}G)-T?`JpvaJ#yGrfbCA_NT;)1hY_3Gu0ACxb&!o2>9<QDmvj(xi1u}*g z;&wgyeg12^%OUGMvEXZF^rQWM-Z0g$C@oF6f&Uv$sZ;}bacOk07nUNI@~l>rdq+sX{(8Q7k#na z2ZhLJ7T^XcaP-vB^k^_&2%lN|d*U8ps<7Cw$+2#_xtBV7<#|$&JHuon?LdX0ncoVz z`ieP|8sqs!uzvy}>?|{OwL?EujJ2%cFLthmh6B-XepzA&nu7g~Gz@p|HLkd}w4AUO z5vT}tD6?Svg5eB-nZC9~`}+i-8p+8)a!dfZ)RmOg+e?BEEkjWmnF)E}4?C_GK4rPE zM6Wd7DLMcG@LMOv&_*0tTX!RN@tvS<_PIvQO4y0?u|FIOU|*)2 z);&&pkSi0FQ{EN)Z-(4$>Yca@qqvb&U}2;41LCIr!nf z2dPzX2rIjHz4+F$hSm2g6@iBLYU3vQtr`Ej-r>tPe>U`Q6B#(Vqrol3_PdmoJ!+tU zqfT-_i4fL(PIyBrTva(+t?ez9fAqr3WmBhqIYa=y7c`*0_992o{S*yd&e^QVd+alb+6ul>wr@_GV*5sAzBDPgYfqZ3Fs3(PA07|M4-XQpp|z+Euk^N z_p9*)1gI`7WD#Yq(RZC5O{fWD&A3${=+}R8-DX?6|MxX@+utF4_6|e6pq!-`697X7 zovoysFE)puC<810bqEs@q~?g@0Nq@rE8%)^Y&&E06d4Q&LdRKNdRyTvKAilI+&GM^ z5G5ITPW}|)|Ho~8BCUgs4s*8ZQ8PR zBI#yU2-7cpA^%$zh{Y-F>E9FjPDC9z)F)X2 z;MRHSSCI|?0`5g9fXAaLIa{)s=WK8F1pZ$$S%-GF0KO$=n#0x<8H|v;C~oaNLbM43 z(yyG8Sgb^eYYzW~tdMtnHxoF;4+5BY4#<#Y5?93hXJ=E*4AN2W+HAU<+d9S&9H1(} z#FQ(C|HkUt zjb8$hu)x=t|BQgb;#hNF_-HoPxbxu2KX?3$?jBF_y9FqDEcdqW^|pV|j~S)P+_~M& zRCxKn?URbW$)PsjgDHjA>ldBnGXv5r2~61D5IZcmPbPM|ZSC|Q#dCDOAmj0$BqXb# zjqR^8H!~@ZWRZE_8-SG*Fg~3ND3t#x$WZ;D5M6XeNW5beFpkIbE zZdk*Spujwiq%#3@*=|4M1{*zi2&!EyfzjWxmsW?3Eq(-iM?OJBY2<(8sYR34pM{8V z^B|Y5$zIR!Sj#~F1*7*9Rk4P%)vSBLnNh&tJ?V#PZUby)>*PSQxfs)6H02X=P^9G$ z24zFFz~7U^*@|*@9#ADmAJEj))s3+!1)c)bt@@v@Z!rw!c-a$&3>{_={G&f#A5kvR zZQ?iO{Pot8hnX7k)L>Nil7!ypdVSorInx7Yu_c_=*qb|%%3Je86x0CX;bBra3(7R zp&qo$WF8tjz22?7>v!izTDi*XrXV;knuq4zay{os>ZO!!ojqmC zkyc0J`O7ClFfxy?)+5$K4SVBxj3m4c*B{Yow7oLj;a%*oI4euM z?>;s`a`rX1EbdA{Y_{In86w&ZFn@+-wBG=!qb2Zqe5C#_v3}QPTBO9g-vMHXs)&!C z&#?^ap(%lv4p2aSOr{ufQcqJuLj%=i6+vzyu+&41N4r0M{9r*wUoJ+TqUq$rK69F# z`?k0%DT|f2DQyZ5uiB%e!LigRQtaD7Dsk8rIfUt2>x%?cJEUQHZk6RfSi7z#^cP%T zD2j}S1+p^wb;djE!+;H0n;4k|kS*ulUQ${*SyWU8xN%p{Iz-2oD$C#ZO|!_RafM^R z6^L0MVDoxk+4B6i59nv60a037+e?rrX~m5htAzxRRnr}+c#ygk7Yn#Sf@+`0Lk&fI z271qm(MM1~u{6~2d5(ZIp&KNVpm(E207XTy1T@`t#I1XX=$dupbm;cYr4;VXndR2p zgTDP3a8*)Vd3N&}#@c##4VZiJ+dYFka6~q2jY;rl+%HEkVOb%f()w^KY>jrPMQk3| zS%#cYRQ=BiGz*Bd3xhKCkmm$rUztW5Enq4M{u6kk4Z?y0KW2c$Pbu$lC;Q_0S{nG4 zFKL)=h5uo;vy`})|PyATOOzg+FVdT96=5T#RR zQ}SIZJ1xliy+(WG^a#AdG{)p+f&A_~XrCGv8HJuy?Fth{HLxt1}#!_^E|J9JNvm7AZ+;iYu7?=)}f|Y|2GXREwcA8xw z;H`RnJnYJS%hbs@k1`C^Ve15zr$v$KKLQuHE3A|YA)i8?F#O~UlpVY7yQUo=Gb5>j z#v6uK%0j=R;7|<=kk6x!g{fhEJJ32T3~=?G+}wvo$Wee`HtK?&R#Z5)Zz7}Tl+1i$ zoBM-8lnVd=93j)Y4`ofOa5_W#XCV$S5rpLJ1nbRo;-T!o%N8bi4It-95L@mTtMs@c zF>C8-NZVm)2rE;0s1Aw94y3z(&`s#7B-9d0ry=<=IU)3@E3$nnR4$8)1yXuvL>TnX z&13}S)uzSC%IYN%;PP$bQ`-SzY zfd6jL#e6&P6NZDa?wo2nU0$|^1s#*Aq$jenE{f@>p*DM#)s`0HpIz#9!h<+ixPE$z&uE zZfG)yG{ZYmVzdP5xI(N=E|Dc>`0-b)MAcLxbzFm@Y zri#2gI0K&4$b+I55n+idc@6}wrsB$%kTN%+>r(Y1Vmr_FBb0*(!>2L=!lJ`G$T;xE zRh))L><7cypQ=ilZ@0I%ie}O!(6v}tf&ZQlM&64(?Rr=CQSd@in-%!Ggk9Z+15{e3 zPy&!~VWmQj9nn`nZD136iV}cC+rWMD9vk$Rpd0tpcAtk&ozUqQKg*ktiOA(-mbxR( z@=h^R=Lu*+g9@m>jRia#l9-of*i#pEDtSF``5&m>7c@u2kapVpal7knDOfL``2Em< z(!pvb(PB+ZtjfpL87&u_U`7*|K8e{~fTJ8J;GY;_GC6lcN_gBy-&mB$jI|3in)Ku% zd0w*;q2PdyA|%`cb{+MKx`y2_GMBGozg6BSi#tKMW-0F#q`V?QGi}eh(dpgbaeot{ z-4~EYn!9-+L{hzYbkUvSbHk)D4L5SD*ziq{YzCYey|)o}Au-t85@p&pZhDp134!WQ$uv(189= zX?)`Ia;m(EQR*8xJo-C7pW%ta@OqLUF)Ei9_T-3_=Xj!_X&>l++C%|;^b>(Y<*4&; zW%3uzaBI!^(JQPky?4#>O_IbH{9KpR4d)=G8K4Fg9o?P92(g zygl_bDU>ZR!hFn$!jZoQr zT{i$*dSDExwU}VyExuI7I_4JwI>D>C9cC9`s`U94nQ;SDP3kE5ZeMiX0g9TGBoeIp zi$$oafhuy6Jfic$P=>H@PU>Kn;&e0r8&bx1g3iZO)s0?bYSZSkkmK-Gj867yJ$W9DE5w+^6#5i05b>t}&} zPA;mM(Xud;adj#dUM>e#1)0qfn~`#diobMw%L39A28lkGu$RcLVW!ZC{Kq1LwoF!O zK?*2DGuv)5If1JWN;?CGXE^%)Akai=B9|Q=4CoMeUhqQYJznHr$hNC`X^Xc38fh^LH)HWjZy5Y{spy&7H zShI=<`R^=ZcgZ+&z(%9aDVeDT#AQ+Xe0EzxDCa)h)WpWS_dj`315n4|H?C6NtLLlo zfCNeTbw#L?Hpc|ha1E?{tLN~Ra`;ALFh%`36`ZId5X(sUg5f?IP7;xg)-UPx<;VK4 z>!>nUVIQRp4Qn%HLu9}N6^OWO5K&%QTKW!fTkfTn0ldk~Pr(%-AupdjdzJz3X8{|j zpC%c6U+L2U)bszNa*f_S+(5KYsqptAptj=4yskFGC7K7$bFU`@@XT9e=H!we$%sXE z!r8?xD8iS#($FNct#g#|`jDpoQus)}|Bc?C?&z~8#M1o$Gr7s4QUky_WoJ;%qv zvLXO>tI4;Bl?r{nGDj9LdL*Q@mnH5K*cAzhrO9xwu~o zWcvy{TF}E@ON6VGI(OjBJv+1h_{7sZpz7WjF_*E--aNG=e&!fj{{?OFaKA`(qFL)^vONhdrYpGOIRVS@=l6n{ zk871lU(8a9PHlvhRSrL9rGb-2>D)+#Oi`(1-=KN}(T?D2KO@O9{8tPxL#WH2K7FdR zr*0xWkvoPVM`yWwBLT#-1`twW5%2TdVmKNpzxSM7zCIBv+R*U$&-+5>uj`fdA1`?P z#X%3^CoHpZv$NyWoRSe{G77+tMq~@{dFG(9CTb|8Wb2?SJZ*|1+~#c*G;jc||6Ps` z@F0l8i}hA!XhaI%<1U7AdPs}$4Z1br82J%8;{Jkgc3(j1O>J6?RzD!dw(P1yy6>qa z>bqk}uI=q1ocCntO|E3a9l*nCGglx%pjn3ct4w!k%kzSUW7sd{_iOh-Pr3h--L#GO3+%tOVnWv5t3iDf z>5tUUjt-=8UQQF^!^{p;3gtXLq&BP*x%qLkK47iRTx+54V8i;DllX#{i3+NGm&<9~ zfmCPqy$vG`*A!X+5wH~3mTs1?I5tN#b8L@#Z6b>G`q_tnMI>!D`xUCXL^7i}8EOIQ zK-U8B&bnH~3}a`$Cko(*t{IvUHn8=77J~AWULrt6^5ZCj`42Kvq2WVN{)g?sQgm!B_|Q0)1$Vt*Y?h57mX?b zhEsU&6&4AXln#*Eym&b_HL=Kja=BJImTI0{7rrc(3o%XlDg$e}5Niry4%4E@H6q$j zb88`SlYzX40Vs2Sy1;e+4COQDdE;D2+Bn)0n&S*eLsCHRS>n5=7@+z~Z>ZoE2z^K$ zfSSI83$8%ec-!c^Oegm`{Mra3?V9A7RvG*A?M8ZfdV_oGb{owQh8~UnWRHk)#t99= zgH+D9Y3KF_6f}Y_10)Y}FJeI;`JEFb*OQIAN802qr}rnmUMZf+C{u`-19ZH@-$BE^ z^onNtQbg8am=G7$J+3)pt|HLJ+qSxW?vG>ln-r(aKex>e(8b!qpmJv+>%!Q1?KhdP z{Ssnq{O4Hi_|s0-8A}+8SY8|`zSwAbBj~rpSdmT~_EV?bevrg$Y&%XK&>CmFL8P?0 z}~xT_Mj?6>Q2U{InXJj+gVPp0 zxFTQ*-Io8K8$w^}MA?ui23bw2$P#XLAUZ!xjxF6G;%H$ta(@|-DS+mt8b$KHCwi`I zfT;}5jTcX~`*Ug?AzoWKV5?84QG7`&sFb54#R0`%&nKnkJC}AmtuH>+^$jC+_SNo( zKx?Gd)nm9O)8UkS)>-^yb=G2IZyHHtu0PYS%Zq_U8(^oni_mhsgt+C}(a`NSX^0)H z%FO^4P7OGG3?W08NU3Ll>PEEe)xeASoec?+DiyZLQzizHmgSb~dm$(g0^qT9WbME` zP!GRWq+5f=A~dp7&j*_)s8rXV(W4v^AboER;AZg-gJACqUWm?3)C#bE5dy0}7ns)z zVZH2hANI)LN1;D~3uzqCul&HbnTQ6N+@@Us8+{!cEnA@)2SD=goEiDBd8TD~`;fG8 zowv0*996+o1VRGhED7~R!Xjd_pX}|lTRt0E<1S(vHeXr=%-!Bcq4qFC#u&$!{d~u%np=WFx-f-F`>p5ZPvg*W@c~={0@*l30*W8?|p8MKVZ(my3?vAzPA4 znD~WZQwABVB?^;AE;raZnv04_{tY+=!I z9{!9VNu-))GOVgST25|rf;XGuxs{HCxcArIJ_kADa`O3*kM~^}8`F8x6IDuoQMBbQ zqPJao1-A#pOmN-k_rF&-nhsxy8=6q!f4R2Lo;*eQkw1Bd`VGy>)T27IerTqFTsiVcbRaF9!7%2)xkXr8XInc+*QGG%D6U&@J+kUsI$pB2i)2A9Qpa1@3 zPl`6Io4GH6bfVwh@GqP4yZ+^ux-HweHj)~wI)7$v;yxHdqOHzd$C4PU>cge2nn%(X z0_(ato4EM!nrStb6=Z-;J88H?kHi*o38{he!Z1!!TL|>90y-sFvr4$IT6x97+KH89xO^b(_pf-;?~JV@wR9X};m?jQ7&;!5`Ra2&Ac-Dp zFQo?RH8s)@8HnGSs=qq+{wWE@OJi-pE5yzKbe}UplL&YUFi>RHaWKh@>|K%d-wL-R zmhS3P3s4+?(_iTF7JkOMGYlW?57B`FZ8_>%S>=SmAE%fiR^5LuUhLnFEW)lY(J`p;G!w7ckg8}JZ5t?0z zT(m!vC3Mm)6%x3K76f&D_t&3&|N7y9^^Y#Wg=VtE^rXk^T&>RcOc~jxKS_H8{ImnIBWit^83B0kOLywaD@JZEv#!8|>oqxhC z(0~_N8N=n#ib{u`{+;0%d~U^!0RH=hF#^<8btQLRzHh#Fk9OGnngVyf4w64LdVwtSyK6hBBz;Y8RMjsQoKM8W*ugK731 z=#GIJhIi+r_v`~zNIP!vWXn)D^}Lfhd~8V49l4BiUqf!kC5G~hk^Dif{W{oY;wm@V zwT69Lr_yvi?GZ9Rv%>BN>&qXLgcTzjmhqapB$z^23`KZL7T3ijSp2yz(zasJdS&yL zc)h}Zgj;8XF1!#b*IRz9nrsu(Bg8=U;{f{YwrYPj;Q4mqKu85+3O*X#NWYnCjr!}q zN;0QWjQn%9fj3f2`gr#7X3`qAx#G>v!-Yg!sYh*tA(vQL73B?AT+EZY$ zxr3=g<57H!-L2e~k4#*AuMgqizkdCfE<7JLqk885(kbCmMv2V7uJQ`n6T464{0jHJ z{z0bOe9;-6^rxX$qc`zgc7bA$F2h$UVUX1C{nnp1Vs9p*HWFGR_W51iYr)@NqW&o- zh_?@1NG&8{?*sJwy>wi{NNZ%C3&1>Kq-j*c_ZQJCMrhud9lCE>7fw^b}LM_OX5W&p9m5a|I8EVRGD)=+3@}Popp6Ce}tgSNW@r zB<_07rV1(KL9v2xU6q2D@n&ZQOs#m%P|=p|FThP=`Nm@`$r{}%~8&`>afR|CwhzW8KzY) zN+82^JN?_xhQMO0ekx@9Uf7gsDr#|L?KQg3FJi47!<{ZTe4{Hj>E2)U7o^56%fCTZ zw>A7uBaQHF<{D>BJFi}W(w?c@yg)9!#F!jApfiMx-VOrrZop&)5RK0jwwpGW2Jas{ zAvk8jh;ER%2_)`Crjw#()3oxt5#w1m0>p5G9NX$fkNLN?3i8AnbUAXW$4NGFCz-t% zBQ(|agCvGseudLOLaU8bLX5z-$AXjrGRy^gy=7j*U>|A_cf!B6+ua$lA zPE#*H;AKPdpJ%Jo(tYXbeJz0!v(h@b+v_H+G49{;;16ngHdb$Y`3x)dIzc~y=V67c z@i`a5Z__#24y#)u&;JgE+Rl$<}St6}d77 zPp*vr&+|uM(ay3Sp*Q|auJ#OwF!=f*rih7-hx?5$$Hc8Nhx@s}7{xbLm40Gm0X=Gq1}eWHIRBZ9;nPz{;0ki{GlixG#^)PXmC*kQjvzvt zNJo~0t$SO+sj)HlMoFai1;*aQg6+RiKz@pyq@6r_g>CZf+jD_;%dy^z_R(E;J%Vkg zZly&Vq=ks2b4zh`Zc6W9WH=VjQVC58D()lW74Xdbx>%^pKP7HkYzLBXr4u?b5e<#t zrn*nU>Iyy00gs(Zw$rgtybgWr&11_%qpyZ^SF(JRJ;==p_`4bbhu5)k0m3!gNbAa1 z915;8FQ{Zuj#F3NuNo_lk~?UjMB2;RObSwT?ZG!*{`NZ5&PL(CL@pht`nA||1=}Os zzTulX_@+8Oz(MnqbX{HJmPZ#M)ctdKwKD&CJCd3jPH8jHJHI#!3pmpoFD7fAbk=zq zNL3TS^?{#@97L$^JGzEFhhe!8jFG+;qmrmyF70J@NFCQE={Uw7{msJxFj;@0nf+#6 zN`aTBcNr?yEciWSQ>p(jypZMeH^g>{RI4Yj*f)5`Q#mMC?IW=QTN?sKsZF9V~zE;6@KgvWBUsqm6C*U#l#y3xi2lt zxsH2WU08`jwi-eY5ToS?+!E5#FIgfZtFWIV%LAr>!cc!wj-;z|2Ckn1V}6Nw+3N+0 ze>v0!^AyaANyR@y-;xnEWHTh)9OG~0HGDJtLCqBosC+@T$2BZa{Q~T>>)nrOg=vw` zMBO(Myl<=ct!oJ7zPcq~jt|1M;P3rqq2u?hBN@i|UDSDVL&@s@JYYaBagYGd#SIW6 ze6LgYH6(YlXTRQt!h(99ia@3 zGQR(eO4@zX_p$M);9YN}x@p>>cA{neFbthnJM4_P|42)&_kbeSdu}RV!RR z((m*k#Kj@LJV5@t?Oo!m+z_#%MrGo75^GIlEr{`;e7XjU{5w`G4pYkf!T#Z)z8#$J zGnw}tlBmife&z3(`c8N2GUm4H9!J$i^j(*rwRz9(UtjqG zs%7p>vr4!R>oHmzTWoZXTV8Jl)31!mUXX@4wp8kigVT^Nx$r2s^-rP%ad078ibBT$ z4sF-7A#rlgUt~LkVSGPX#FK2Ji8s*{lYqVmRow+qpMWx5Q}AYvfhF__#-CJYysett zNTK6Yin9%Gem%}K^Ga~bW8|0u_%>U?8FOO(=GC$^@~;a=F6&vC0kN#I{e5!*G1WR` zd3bhpj+|~ZB2t6egK=^7QP|qL>%9UrT%DlHSQw=R^SY0vlb!(t1T7x5O`km6x=6ZaDbBsjC^k1|GE@K>Xy$YM--Y_yeY{#6 z*tp4sh`cOq89jJu98~$H2%QO%FqgyR*xK7!-4uIY=r&HvP%P$u^SJex@sSx`vEl}y z)It*uMO^CHLciB_iQ{^20s<1?WI?!Q)zBzF=)L5`QoJFG-=;d`9SMgZF~e-=F6?9x z&<1zwvvIx){4yAJg4DRv)cuC)=~}K(hR|Mw9L(hf?mt2`?0yW7OljMeOXX2vu3o;6 z{Q81n| zEXdJEu}a^(kU0O7Yjq9jsW?0Lu{dsLgvf+DY|@S=C!>%d z``KB!!=O9^DP*dGr=wrLNrX_Vt)`l-`CY3A9xR=Ei}>a5x9_IUs(0akf_t&gvc$V+ zC<1%*Q2u8JmH3Y*`N5YQ87q+a$*7b4n^Xh91C(?kHVY=Cg`=@k)h1>fq~>%EK>E?V z<=5(c*EsPyKVLtaoPmGGsTYRKD*70SJ_gtnI{6e_DJx!LBkUTSn>}EKpM+K*VZ-UkuJ1$0Y8-I_ zwq^rS@jX6)&y3NhqA5Db4ZnIWNyR6ZnUe%_hy|ih5)}A(gFVDAVVG}^41w}al z>h)A;d?zEw_}nYybn--OW-Mlx=G%e6=#@LVnAdHPbeOO*sCMH$9H_deINQ znrHn%vFz@3leQ2|XiUJDveC{TR|Ce%fAtjyhQbYIUoC~oeFi(Ry4~&igMu*uGwzF^TCl@l!vY_K-G6*9u1gQ)}2ZY&u7&%W1RZGR#6OUzzW~{+*Js4A& zF26_-sQ5(rXfi#z&CIJy9@%>>A%m5AvPFvweg<5afp}c8S=)ms6l{v6sR{NQRT8+| z%*5VE##$c0dfFb2u_wn`zd&xTR4Y6D1w*52eYQM4;te+U)-|Mf(*7XpN;#jjG#>^p z#VgqVig;6H*}(YVe1>(yIhry8Rs8Vng;)ns`Q>w`z)h=(`fY~YRg&ZzO)UBZP5S+B z?JF*E8BswrtB)I5eK1%wTg_=W&6%!BbGG4zR4_r7Rs7IPGemsxilqvKF+$-Oy&#+Y zYnBf;nSY$#2psc|by?)F8sXfup{S^x|D;)6McHK@#B#x@7}a;u3>hUKA{PG4LlxVr z%s$)K9NeHo^~FSRxq5({#4By6fu}j{2Tbj8OKymwxm%`**2kpNsd3M471;C>F5to? zkT*@V1ti$IpH;|t{w+(?NT=-`=qJJs4uw=flV81z{6f6}f(mT-ru=Y5D8gS!+e4z-LG(_%7Wy*nm4X&9wo_Foll7TFFDDF?cCv9 zu*Grud4?@^=2g}90bILWOhgUNXE?+kdVJLao${?)VC(P@8}Y1tJ)$wxrcsXPFKOQ0 zCY)vy?oGU4;0_&uo5UkOdDTJ+p^p~+P()B6MY!m!L*z~M&D^gGe-hCT0WmRIpYrl# zAq3A;RSFqM(%ta(-*mCs+p1n?t`q>I;0aRTc5{Qqx;hicDL_?q=Q@n7O+_dkmoO+|4v`TX8HAJHspeC@e$L$2rDkE7IAk~ICI_o;Z+ zSZMI)xP#S~)GdCxo^E9YBQoVR7OAur*dwU*eKy~=M)%!!%1Mihw&8JL8$V?`d-5mW z-h;Lj$R4lr>(hzKqJMt_A;M%}Y_#-+?;bDCcl6=LMeA7WR4Zx4rRX&lT5s>n&a`Di zqS(MOtmF|_Nx*nw6820qfV;c=8;hXp#a*Y_SH%i~24Qxz zPB!yVCPwIK-mLDsUzhhmi0L&V^MFui|5bcJKUWB`eq(`VoxGNJ=amHF#+(mTMYk(O3h^_J=Md;SK3}{$dYhTtVh; z+&CL@(fHlj1|JJiIn%zP9HOT&+XBy&sqzwsSb+}PvZc<4WRpH~=CAu)yMyf^V9S?oJ-K*HXWz0uJLnyYe zwA>v>|3z~%dH20^A|2P169Q{2sKJ!Tnk%_B_hG)1`Ip6d1-`pVgo_%w()V)6a3wpU zOogKC&(_#HEWg8LDHP@yRdIKDXLF9Gzh!R;PR?b=F@OGDO=%i)^sOh)SF-QPRbVb( zJEb)?fyg3uv|}#vV_edFUpfA^z)28;^%x)pembbHW`(o^icFh{S!pC@K$FJ)CURD{ z4Ao!AaK$CLwOA^93?TE?)mXtyqEXopDzTPTKt!o7VUg5>e=t4T8kx8q{(3T{5l2=S zNBEDTcwX{rTRFv zEU2$nMcpDHH^2u#6RdKA{Mc&h=SzxOR%~aPFsC4*JN*Euido%NHN!9I`H%EyrUK18K|z8mgIMjWvM zh=h|js!DD352wD03}4K3=LXqeZ=+nY9rR`A-^pYsw8pRWBo>^P$rc2U^oK@;esh1f zy{}T||Mczpiu}=s&(oqI1IQOty;AhAj;HvqXQ_}%6wLYSOoG8f7B7~YDESj*n7GY) z=Ba)FPPK_2A<=s!=~ukj-KQ0uQ`o4!T~8ps zQI?OZ|hEyltofbC!9ZZ1($C_KGn|YaW*{IVZL?X;bZETBd^lq9PsRK z(|EOMzIx1xwum_evIy32Pnq&@Q%1o^$_?xQ9Wo=& zq})&Ny<-yEriD7CkCY_Zlt#XwLoDlLdnOilp&Kx3`Q1!?Q4srb!c*2qB~gu(WZ}1@ zAWSWoLR$#QBqH0%4 zQH09juDFS9TYE@i3)2$?fIE-N1lZCdA9h1qj)?!_!#*h`yxe{5?AnbB^9>tkS!`cu zaAjL|1nQTO670jWw5l_5`(JKiazcwp#Am!7Mvmo1IDf*1`=Bl_TBAm6m0;lT2eSQK z9uL-x?`v0bt)>}wY1h=cbZYzXAhMh9G573cZ?{Wau1XdtT|fQU@qH2eSR1}6$e?zS z?8Wf5M+#=*+)j7XcZ@6;JanFz8{RPQ;KQCyfhLHf1urp7kdHUntO=JmsXn|NQUQaIpr2^p})m^2ViPSNBc2*s`x| zL&?VA55;uwc9$&hOLtJ^T+2kzVrAU;4_Vy~$adqe>Y%DQyXAVem}z*-R4T|5Vmv(@iNZ}pfrf~Oo8Gtz%WtT9t!WD39Oc(< zW?qmJ4pDfVVTUe+Bb3_8Y_cc^;4tQGY;$b21Q-yc|5|+N2AaH0HcJIhAS2>iULZ%G zGJT?ndJl5MOtr?0@SYR|A5Ech1EdO%e^N9D{Z}t9`1ZlZ0$*!4py#y2<`)42|-rRyJ`#{!Em#7w+_wYrTd%QbR6a-T^-J z?;#jD16}}q0uK+Lk^EvnGLcSwd$3;+BDqBdm&96p!$`q}ycn!Gg^gy3vwf*n?p^Z| z{$-)9Wsw*?Dl`0_$Awe$NDf*A5s~KP6M?F6@^rs5+pO&4mp%Pvr;tb~B+rw=BH)XS zmLIPdoBHiBr>&8CuCQ8q1g)st=OXkCJ+=tg8l}c_(DBDPGlkhfi^K&L#xKEz^x$f@M@lauNwAL`J z;N4%VxbVT(^7N_r)#$@1Ze+b2yV?kEc z&0gcy{lShu<@?P(rxX*KKG7=0+`-3@i z`JCw|3z=tN6-lSuEmrmDuA5}uRN8iR)o~%K{}XyYJBjNN-Z%}1sN(e4ajjh+8{s){ zDvr+gbh6WMswInL-rWOA3;&^reP9zZ^U!0=Ewq`KyTU5cmq}DXmW0QobI$GKC-Z>_kzW z%3?$GO{xNh^>iBJN&rr3G?AYd?67=U4-)4QVhiS>AMMxEzJE(MwHddS4xLot(6b89 zCWKQ~swD-~U?f{F=-i$oYUL%BQv?0=N-NEh{!R_!d;LG5RuWl)b5TNz0SU9#^RhnC zM?rI&89U3t${HW|yp&5-^Y@)J&JmySi7*wrWGuz<=k+EHVxmlkyE zDn@@_z^st~tH=T=fjB{^P+82b3__1~G3^UA3HkKt9$tGs-br4N^nCura$+|`q_PPz zAQeR)fzQcg)XBFFKoWMA0#EMXw4+Q#ZfJ?GdOU9n%JK^f$t&N9S$y$N;~Y7M9>c5N z=PNg!@{R|2qBfsz1^-Q~YTA_-Xj~$AZ%h<5-0kh6)tmyj{l_p*+H119@M%?%_MnQn zAbspfgi#gJw=QN9<^KWpfvwV2ej+6R3&S>YpD2MIvlTnu9N=;jYY+}!uk|aOu&*$d$r`D(dKX^UDn4~o5o7)$R~|Tl!*a|HR`$ z=xS8Z_oLaybVlj8Gh?F8KNkKJRf)Z?deqH58@d;6(ax%qbgwmbBPA4+Bf`I`SSB4W z;qxdL?#ecSQ$jcW9mWMC6leezLnU|xy_gF_hx1=%rnwV6>Qu7x>iOw zkee-4_-Ierb!vdxpP)t4_PG|oiT9d#K66cDV!h5i+%@;=tu^C5Ghvc8VUuHKOtgyB z!&GM-wtT*((QzJ)CNbdjT_eQ96oWA}k*kmkO#c{AgyGXiucWScUpxCwP;6uj7`b}#Z?F=kfL79Flj)vSkCwR{sbh%EgL)t{5!uGRP6HM8?Q*IrI$DpQW#{T0(N ziwz(;+8mKvVT#-{rGfa(Z+8{8)dnh)Hc-^*8{gftjY|mEEP0VHb9{plaiy{bf(ZCqqJr9v z;N2UZBa4ft%&aspyk@iUXc&qnADTiw_;iEb`c$i2vldEz$$-2y#Vkuqc6I^=#6E>i zz0W}xFT7WXT+Hn{RzbDZEL?>?iLJ-#Xa~v+01hvFDCA$Mo~dZ?*wqiO0otJ26y?if zja+yN(+m^b+dpU7Tdl3LXbaeSIw|s&{iz|hU52&ZWDscNT{rn+@7eP%jHoj_`^JJ! zC+!;_dGkOxu0Tm6f!C^msjQv_XrChj`eI#QyYNes%Deu|9jAEjr}=~XN#Xl!10!L8 z{!y}Lrvg!rv{%G@*gF%X=eh`(*xX>94d4Z5jd4Ym%tLCkQ@MD@O2j(?i%cA(SQykb zxtKHtTE}0Z!}rsrwaI%-V5xF***S&ex#PR_oZTkg-x<%{V-SRC1xV~A_=B?fUtEQf zKlnwMww&e9qy+V<6kh0Qel+WL)3*L+>h1H*$%>}ZgQinwUN(OV2lpy#`%iR?qP86D z9Z@%A7_Z=T0vV6gIJUZvm`gxwwv1_=gA&n+Tz2;nrc+N67?0IE8a~AwKGt{&df>?NPIiisMy&YBbkTDuj1|;H z=PI=e``)vTABb5^YEPHSo)#Fntpbcbds567ZnEIZT*E7Ux>hQ^>Q4o_a8r>}$k)zn zen#KlzmQ)^SX(f%s5VH6k$jzwX!xz{MHjpVb+QwI`TEyqzI}CRul9_MnUA79v5!Dh zU}bg*mt(SZbeW3TgzODcLxLTg;L${oL^6cFL0A;bh?cyy{#q?Kb3ex~CADYbg0~=!f=o4U!PM#J}UN z9Fp*WMo~ch{ba@4$iq=pI-ML@>o4=4Hs>NkX}sD+{3*;X61)1kBv3WoSTauSJ6FwL zbM9xhe3LQNPs+A3vu z0PEHAr;8{w`8V6)xuUahJ95~9b{f+xjA|vi&KvH=26k!a#1?MiX(u8aYEu7MU4Qk; zo0J5rlx%?N{)CVCzln-_9=r^Q3?~#czkM(+qED4OP&`NjL$;co;r794Alkd#Pk5|+tk?jpnnWH`$~&jdXR-5 zUJ=q0Ib`J|3z^;k{Hl`La8&$yc@ZeP&1ml`6frLs0uNY|&B<EjsnA(787bMeXUH-*-_LDdWy|9}u%)b|(}Tm{%@E6#309I18D2Q2j< z=dT2guhKVo=?6}BB4zpDV^HL)R9+HdnUb?!BQNso~NtQG#$;vW4~+1!#}Mr zK1GgLIifO3N^IESf%iM|X}L_y|X)=-IMIvD$5 z^ZA!@n`k$$Q)*3LvQxm}EDM+_=S|dA*az3*e;CVfy?eShg+@Up<(?QM<4;iC^VPc!@E8+c?UFlwEv*7eZ3nU&vk2j>6p0Ul&0Jmn zP5fPVR3LY#T^N2oetWZ+<9a5K7$xcLGlAKD(X-@R!=wgVM7;Y$PMc>e11AbRro?!k z#)peL^65+c?2oWm3|Q%pI5S@z>*t|!1I=f1mjlQr68Y^Gne(||E9zlJcei5Uiop{< zTsQg!FuzRz`7Ds=EBiU)Rz+F`pHeZh{-=tAlloR6vWp` zxM5~sPo|c{1?d}X#}&cviS7=*aRn5;dXi?+%ZZuFQ_lxB9KYuVgq_!WRzCi6t7UzUM z;W|RF5-Ink;6^=)D&s=gB$ALmo#VkUs#lEm!9)-{ja(4j-FqwxZKNo?4K^>OzLZm6&B+W6$wjrbG@@?PXA#R*xu8^VJmY<4Uc_pn_Cs5f5HoJD_?l})O$OR{txGeLeL-afCgrFO}g9cd+{`up{GcvW3E=Fa`EE*^a`J%lw6IH z7sPkG<-3JGHtmxQeBYD3`N_ePKa3btPK;937=OEEcgLW&5UfVaXLXuMLoApGhZTKB zg=_E>2b2fX)PQ$iwcX22uT##gR!L%?Qys4eA;YVn>8z^fTI;saIv1B3 zN0sADqn)j>4^kgh{wR+q_ONN>(zWdvH5@yRb(`954Wb`SB=u+ z^HDic&+5)zY!E>Uvh1FVXg=Fw!RDD4OJ1*3Tr!%yprZ(V>l5qNi>@h2DCP|q_F*Kk z{k9AfE}?p?Iv-;2+W)v^hPPJ@us>n+d}bY7pMp{=O`Kt^DHghLxM{SFFX}V1QrK*( zIrH})MHSl5XV0HOUj*??sDj57Ct_QpCne7LKUDzgW_EU}`;6jysN2}(8)*n-hko{7 zFNH4zh1`Zq+N!i_7N{8X7FTQg-^*secOpSmz9|1ji>|7xs(N~&Z5R*g3D`XwN~9`& z8lChfu_&cMMyDT;i-uN32)!zg5XyziWWYtR72tG!8kQ=dwRNSzfWFsm zfG`dL&~%^iez#x}sC52)tUCDbqg>K7s7$olOZIl%i~?y1+7DwmkF?7nQi#KkCeNKeIY^Ey`MZCc>C0(8cuH3iZ}Ca{ zqzn&9^jQ93{^^3R|BMmdLBk=_@+9O<+Ay0u7yueu4?~rcZO#t@=G#){e=iC8O>t7| zxZ4w*0zMW5v&1M<21(E8@Iv@B`BvHH%edbh?a^Mm-UH9o!Pv$KIn5w*OGR zVdXt=?i%?92VW%KkS}xQ|C{FgQdS{)JltK3)i5E0)N5q7ld>GthBtD#eQ3XPoZ~or zkt$WdicjHTY;WM4X6lh)-A&7m zc2)zSY->MkM6SbcOdWUb;|tX@=b*Uf8Bd}f+Pr#qKg|Y7D=?|P zfPePxoKZARb24`#)Lm;USW!N{&@@a-AB3{Fnuo}{YU6)Wwzt;8>vLW;XYl+~m41t6 zK)miX_R-lr+6?u1cIqjghXmFKwL`d0e<7bw^m_yP!Qd?cC8}Zs$Dj#QFK+!^lnkiG z`--3TT)mS#mG^UZZug?d z4*43+wvym!^tk%WFR_>XX<-^)az?r{5Z-3PL;vQ!B4S=q1GBqq-i7G7+hUZZSieUc^&U}Az$)Q|1 zd{VtKu>BX$LsX|m9e`1n(#kFwidah-2wnYGa<|^Ul zln0>1@)ZkNunVIU1GX**T;eRB2JXmaR;IE)`$*6vmA9jEAEN-7+l+y4(QiIbS88Q- zDySP_W*^I9T&oGyS|H)P7 zqHik6kBAvh*32ZnqSpzLZHMWa%_WqmicEORtRMW`i1HyQd{6Cx{9EAhp_}r9zmHLT z1bW=D_jPf8lj|fHeWI$KK*fFUH8=KEc8K6{g+QAYp_OYh1x+3ESLeOP?kZ2?DFz-v-RCWbxJkUO-YlGy758)Giq;Gzx zrOz}EDySx4w>7R}HZRFfQI(dp+1F&_6VO^~O7*pttKFcnUVBH=*ma($-=jInmM&Z$u5!*bV z_N}}korBJ1>#(+mmUiA^7N%2{iGntBnJxKm(jSe45~L5i@R6da0gGcnGavm61>_nd z{P65SLe4MCF+eefU!i!027wGI&_ke3DR*!jI>uO7+|k-F8Exy}{#IUO}S>haQ< zDB3aF>P~r7^Qv4cO##Dui+ujuU?5tId#Vv0I@=l|JMFq@$NptglS?$L<{}dN1L5)z zS_8RyRrNb}xr%PaC>H1ZMW}9n@?5Tz5b}4tUT(hO!fBN&bXR0f`}Ky#oTk#qes{G> zqw~(a^_j{>uh-SdeBWg-xSDml^Hc_S7b>d+JWc~({UhKQ4A%B+pcKuIe4dL6(u8F@ z4F_Dl%Y4nUP8%X`$>er;&2RT%yYCDc5-$_vGP01L;z&8l>&M#vD5YQWrd-_EKB*!_nJ=9klCBi6pby} zP2Rr4sAu*bwC|j(aXkgGTW{DR0$6$E2L6o( z9*VFJY0kSdUrk!+-|{mXDKd;Dv(trAs5c0KGkFjBk$aE#@Ez~dlS7NHz>V2gmuSc5 zCZ#6RoN}%u_hag>&)lv%obRtfZ>vu~pF_7Tv7B9^mk}1dZSk_k9^7WCz_`zn1-00M zU3kxhP1Ofod{%=Hi~+=^2F0Ndo(J?6_BiTp=|hD1K&)(!zwKcdn2JBzj`Ggg#Fg>R z|H>$Kfst^~iIa_8N8j-xpe`urdB2RjGIOSI>-6>)c>S00pyHi|1>&?*AFqDR&7JgM z`1Qqw;F@^hK;IZlQdJB7U2$u9wbgkx~s>%0eCy^GA;$x{2Y>sW^QwEkhDndkZ`2%_WJ&TOAnCxa}U6 zVgC)+nOq%y5*ZOAgJ6{USt#;}wUT$u5(Ilf3rNUOsFNEAAeX>_H(T9Rs$R3w>J6Kb zNO*Jhh8}D{R&&0crsWNaJ&0id^K@}6;#W_V&z99h$d;S;!Fb|b5p%Rh6oq|6jDrW&5*OI)Mr}&lcXMMdUX)(?#IdH|ZZa;gkDVr?K zAB5-BlrurGww>yYNSgajQ2qt^{BFcgx7+oyW|f-Pi2X zxtm#6B#t9hw`$^Uq~8V&}D_T^DE zUTl16N%rgVy`rUcCDX_6k12S9=eDfV^0&OI@4Gxvrw}T<6}(pPlHZ=O>g5skB^=E!SFHzW}8e>!DKmTx5D*w`#ACkY=WE6H9NawLmb7aDNA048a zN~yYf`mI+eUFhMQC@CP)f&0BNmRjM7%5Of zb8O&Y9ks_PNCR5V@98WZ;ikP*XhqwZ0p{#(PA&|iLqKJ(V;^&yz-}j1g`C{6Zo#`C z5Z+clMmJN_)iHPf8g8}`X7ug{EQJsFEnzyIM052qk9=l}&v{pZO@$d*tC{B(tM%Ma zo{=(1OSviozYD95)}?d0u1Z;x@FvWhv;O($Bnf2Nl+DO6wx%^`B$Pn_>DWI~Jn(ST zU4vWy1n7{O5_$pw$s_0ha~^2RJfq2iR@mID!Az7D=I6+-n}V02*Cd&3;$QL3V)m)W zAxBgLF6} zq*Ak_LLaF@OXp||tX~_5vIG(tqxfe`FS>~{JWzI%80+}eRkrpA+BT|!*)MFp)HJqB z$zMf6Xe!X(A>w+0L{sbf^~;h3d**WSFvMlz&GwCOp%%r#jAQzHsQ0WZTUhW zz9E8!zI39;0Z^8U===DFf&QeY1Tc<|tGaJ`aO+>bY_v{Y0 zhWUrKzi1(A-xvfD*{5P8mG53DrHb3b#t+R;=nGy8Jq2f0!(P0g&+&l#8sj{9vT^q~ z)}umkDv4a1b$$dR4xrZFV`86zI=z|{Vy*%lgj9{c;Hf6LWwouJ25~*$jACxKtN32z z{0&v!2rF6va{cD~J`ai(*iij|&n&Avm=ndzwJR~|@v+~pkdR+CuwhP{3j$0IYic5) zTm5ot%{J|IafyHxm~W%5D5S`dpG||dHn;e3<%HxUdOE7j+441~$!3rDkceO>drBy? zi@JeAUb~rjkspy3aa?_Z(LFQS#cYK&+k8Gb;6Jx4w;4(vH$N@`j(=h3u%U4 z)Z)CwlZ=`gOobi|s%S;(*F2e>q{}(nRGTlJc1V)UngoL+LRRMeK(OGxSa%Qz5yp7E zs@B`Z4o!IwM4|lHw?msz9D)R+@y)`2cbmeby8nxft$+ySMBZ~EYJ?%)>dQes(SIlt z`Yoe$mbQG_>?Ymxyh!~9G2(Kh)n(iZLX&OE@0!cMI>61{dM>JmpMlg9nIv%i&>_cL zoS;5vQPqLv8kN>aRL~gGMSuh!R!kCo>-i z=vg5S2#@;t|0-ImqlKz;9}<1p8GYh&%q0sug0V~} zsnOz#Eu%&r_W7>jda*$g?_D<4n-2)PwR!LS4YP~n=rq$k0}9yp@{iamkXA69&$84l zAKELrc36p>j$P4#r>Lhr^W~wzS~5_f#F3=m6FfZvXOaRzIQIFM?2@0j1A_jES^Tw< zBaROqxH5P%)%<;Baf~xpNER}C7VF=rTm2hT9E|~xV)EWtz}dRraaSnjFGwi&NO1Ky z_Gy@ig=K`XN^^=S_(45}8z|N#YMP>OESg z(Bc20L+!%km7T*2KU|N}^1goRn2#H0F$lydwfGt&-TeE#Rv=(?fs&0hOw~ z&k9bCi=mxGiL*KE8-S^x#SX{CB z-QP9tpD(#TR2Xk^y!k>&tah@?k|oET8+gh`IRQ` z&kyLH$YI_N-v=&}fw?`ubPOAuJ@9J%CnJjPd_cLfA}D%3{6mgR%xYdnl=4BB?wkkF zz2yo)cFP4anAHCZI&(w>UPZ3Y&RIMtEK78i(ouPq8oV>9K8-w{ zeJ(;IUDUtQnaY=Ts{=EAisNL0$xGc;$kj@6VnHJ$1V|9zr7F&N8-(h`pl!i2)#SXa zldsfW0XKxsR=)Jwd&LaNjKDF2kcm70SzL)xW!LbrLTK~j3%ZN`BS?Da#ib;O$Tu-V)}wJNVpqdbcP0KeNcY<{R6kw# zkOzHcKZO`TR=;G<<~+(*KNoU;NH zZ2oAS$cD{Mr9_9eE44yyf5!=~99yZqm?8ZXljoEyCz&PLQ$*x}e5^u~8PKFF|ZQf<#1N$l;LgC|$yOXVxp1c)Szvf>prYGmuX?8)?M+y33k?l#7uq+Mt$PZd>C8cS;u&8M7j8v z1$gTAO`6)vVc-D*`OYcQKJ6C-vDH)g|Js?EPAg%e1~ZT%DScF1h-?}V+&H~C)Jl~t zW+7vMmylok#w#Cm6l`Jhf?vL%UY{q!EO$xmR@mI7UU|&k7U}O&;p&Z7$k(k!X1yTn z-Isywr1)FH$*#*`?Nb9SuLZ@FG5IU2Jn;J*U2GdJ>;M{*6hPz5+w#g#pSZUUpYd#Z zNnxsG&{SW&?Z6Bg@*x&iZy8| z`{_Wd5P=h9JNG=bOKLgWmhUI!j|&Jif5`1Z8yO!&X# zKtD#nNG+e=lBKNUdJdz@W*au+P9nWmHf(#yi4$WWU{M!2x}?m!rlV<+=OSK8w1BNt zir3Y*1|FRdg3fNdu}>vjrj?N*ZQnk=heryQ&|`O;D!FVGLbj~&yGN&8#|pn$-W0R^ z=r`Tm?=eB&AN0pvA@xa|E<{Mn2N79HnkdUUGnmgG^H_zNT~#H%AC+2Vzs3zi_WfyJ zlh4)4HQ1(Kk92vQJ=?BaAIS#9Ouq=dC}J)w7_%Y2R=8^-;8_?_}ijkRSEp za(}_fAht{4-9Lp@zJp=vY6D%dIC{;~6W9H{Gc?BK%ps=%Chl>`KKW~KKKp-jD*c+~8m0Sa*IK z(>6Q2`6uEejFI7(DKtIsOm}D3bpi4(6b=He+vr9x>}B@qIDr$&cwEgW zaa*E`w;wNUQz?&)cE&ah6d6<8$d4I)Dn*r3Poz{E2=vzu-t{L`}APt-BKAFjL$j3E@y2H|*%3P= z3rV(S35CC=Gj-Pg&>R~YH!<4ZmXKX~7PwNJ9`M^s##^&gN&B>&wwbik*sFC-FtDJb zsxukXiEX*3;=9ztfcHUWU8b*2>h@f0BO#L{1Wq^}*M#et?E3w;Wamz-TP4odjwDP& zqG~JB&kHd%&{-|UfB0MZbE;f@MT+`DWS=Brh6s7MA=|mBHLfa5rOT^N?DUlrhH>p* zc5%Q_3V5i2UNcv1iGo7SmZT+i%)IJ2(P~S=4Wu2Bb zo%CNiXH%jij^Q!+NN+YUsv`|0G&D+JQ7CccB0E28u6s-TIKuC$ZLOW6BOs z+zPD}ahE%Q8dWJc4(Q>;&|RZ3{AtLe=ADDnOU6Tdi=^Iavi=+UoZ0cgZGX-)#>Y*Vf{CTW!*3Uqu1}Izel@*Y-JNh;m<5A+3k%Id7444J(1z?=N!KO7bf-waY|)7A%I%|`M-hXClbq5&%IaD(WItrH^YcWHZ*ih*Ed?*mSH&V<8t#&4 zzZA{(SQV|^8^X9qeIov`#JhUJ`wAZMqrZP^O(L#FTH){I_xz8T&Q~1#w<)1ZjyRmk zsRr}5+UC!W14wc`wEWW?K(xSM;m1gZl~=nEy8w>Ih@3%ew-9$U5MsjQzmw~InuCBj zrVU7}z1pbF^~l(DntC2gllVBlH;D4P4lYF%ryj$k_!jXMr5wnZGELIW4?1Cx|HG)1 zhjA@y`@LBt_C7={{vfekyLps^`;n@Ectz&ozAs$d78Vt$0$lVk|LN0VIQ z_abqiRB)^ksp)mm0G_~kW|0gKSI2&&%h>TS-nHN`zPdvoT9>bCIlVQtYkBkCC$E%? z{5=Dg;ytO1`vG5H5z&oGg-M_#^@=NUgg)9`I>NOU%g3Rw$4 zblfCUT`gf{lgHd;&#&wsoR!2LR-fG%(!z_nX5V+8bG>#b5c!_g%aWu2gR8`TZ*%Q@ z=e{{~l&NT~mbuVWlUO@Sl6QkW6#WwsNIEdL?&S_t74AIIj}UqYV=a;Y zta^s?2skW%D6ipDYkoL|De$Pf)$)t+gLEpeBKP{jrcXA9Rij1BcG+hs$<36Oali63 z$6>(#Mc<|4eK#E~^i~wYBx)LhYYMw~uTM@Gz4VZ9%6Q&xzAJ4AdWAi(qtia^D28%{ zOC+DfKj=dn?mz2$r}LS|S)!2LBKDAawECA(pxxlZovvE_$10^Y4^VT&XT8X#Oo!x3 z8yUape#ry{cdagw9_=KFlu)*u{D_ZId${jjQySF46-6OUMFs@zI2rV}En}%zkzQvc zaQ`E4bZguHrxGZb&lfvilZ{>O)`H~`Vn9LwJ)*>HSC;J@3d5UEU1)0ZOqj0$V4{R5e=i;{s9vQp}z1>+~ zFVJ7e@ukiy+i832KNHdCRI66e`ftyVRCbxDa@FYX-fF2BMZRmOJ)1o{9a-j-z_M|) z`~1X3XV^ujkqQ88HSLqel#iivz-`D&uCzmQy{%u8(d$nM8N2f|u#-IoYz67Y9%pR$ zuYYB1IrX!LcO^LDUoiW;==J|x395Z3(`TFB`lUj!iP+*~*`j@`(NUr#2FFyz?hY1HTg$22K-$|#{pwHtK2w9jHNM4bZRne+>%E>Yx3Gg<809+aaG%Y=< zNVwD4HCd|EO#G1_ssF!waSj_#;9G8CjO3m*V8a&Q0iPMOp9$p-t1f>}RUxhtl-eqZWpC|I*O1ep0f8fBfD2&$@&64k|G2Bcp^ulaWnP@Nv3EZ3*bvpO^rdkFs&EBG}p- z=f0Nh!t}gvg<6&uzt#+w*rDj>Yg4$2C#gzb*Cm8$|4r$>NdYpFJfIT$8c@un|IPu8 zQRB&v)eO6w!HD|tdtQ3MG^V4e{a}PR5!wOFCKH$kZSaKI&~KWWESG~9;_Sxou548l zBXWy}mt~C|lCd&K`1QRO&SQ}yICd<{h{gtN%Bc&>}_k+5n@{T%7uD=zw78Wy6YhUl7@TAP`>yDYvGL0(D_o;8W zLpg2|)fZRSj>^(o^J0gy_Gfa(D{vKS?b1WOJo9&7j|V0KG`dFp_N8O^pfO&e%2pIT zS+1h#)kyG>+|qyZ#@;e$a_-Jy$K$TRk$Xs%BLao$X89NhPHDqPD!w|H!5{M-JYjx2;s4gw05$5@MsG6J_9u?g$i#sZNQV zOhY?lq4F*XG;ivwKHoCe&3JC1b!2z`u3&1eZauWB*!++@*>|ozYkt>k*Q&za=U6f! z!axkGi~rBwaQ5rc#lhQnE{8|^cG5&>x6d2GX#sf-*aeT{=k6W)31e7a=h>&MG|n;0 zR75D-enF;nGBx}l2WuT2ZuxpO$&s|vr5d_*90THnRAjQmJl6YZCoL!yNuR z(>yQ8Xw_42259rONFM57xT^`}{+4|@I)Bk{ue0d+o?`v@ZZ)|F;P^keu?FuMD^;91 zC=H#PS#-(%HXq#1)vs+DbH8hER?(!rf(Xa}JM8<<%d7{=1${vKRxN=R4bmV@gr$;mX_ab| z(cErCjXpHBWHt(gDk2noXgxP<nzFiYB***Zt2#)Sgw*Sd5lttr;%$3Dv-OoW4ueVand_ zR=r%H$SZQ_mmXEw_WnBN2JuDw|c^NlD=w(SGIP-QAj(8 z*sqt(|Amu(&tc?vvb*_D%oN3_)7x z8Rr@Pz&b9t0k{ktXy6Pu3`$xjv|>EY6xWZ2?(g&p#4&{?nIG!U9cs@t_$vXZer?uF^jyCD(8O0xD_Oa__lzgEX;z|ssL}Dt_hBvp zT85k{Kcm@Xs5_J(OwN*~C#v<$)GJuS>l^4cSwiU~T91U4BzB;GWPh{#DXmySx;dSh zrk;usgX;zP`iV82a!nMn^I$5OCN%q=;-Na}4BAwrYq3nb{HF0^P<<}Phm zHQ-y`n;-IC4$r>JPqg*s{xp6@yWthAX5lAI40hat*C9go$bCWucG)+NOBz^c-fZV6 zIieSDceQDJ(z7lKrk%QZ=qn%JAf!oZ8R^77e-jiZ52n6Rc-f>r!X=1JVyBLpmq7kc z>v_j&VqN`1p(@@>%G;_jAxydV48W;X9=m)Mt#UQ!3wEvPt&WxokDYhz0S zV3o~VbK2`cl(tb8pD-_~sE?t}Hl<-oAmd`0wy*@jlzTdFAHivVF{!)_>PTs`3yKyA zt4j?R(eiwRen&OUcGYmiS$B{wCCIQhi-;t%F96hsb6awuCcL_KV8y zlvf68Q4NRZTQtk9xaL5-M-Y!y!pKGR$a4GFbXDhwn5|>$Y6as(^Zp0pR|5XqYlE_+ zQu_@FzRKUt?TE_^Uu`{!5l~IfgXtXH)~L1n*8?NtzSG={Hv^XLnpK6-BFyo;_B8*F zgL`JFrc`g(Cf!_adi$Y2%fx=>SRa!>rpQcC+Gz8%G-Lf6zgB#=J{Bf6$?}!h?x3}% zI<1&cQD#jtVVcu|XQ&CA5C)rSb4yfhC=t{a`q-PdpW}zOoMFkYtbWPgx>*=uhZ$Sk zK9;kHZfyk@DTDca<$DjKvSJGnSr50)>umJ5M~&P#&y0P_O%K@9@FA4Qy{^1dlenC1 zusGG($1AjV^`Rs_JYOwzxmnVbc9gzJBm~!<{^;hl$8t*l3>vTQX}oXIDQ6jujkHLS z4}P(;ts5zZZ{C5gw47-?i;T1dO#UXmRP&G8mo1~0i995;F0}JqRLN=D!AS<6${Cx2 z@yvfAzfgf>eO0>mMJQBNQlnqBVRJ`^aM1GN;2{agbKcYtrUlR6Ss?X}y!XSL$;jTI+rl>X z(M%*4(!@q&Buz)dkSiR-=8fY@LH_WiJSb5k@J*qKSD$;pOyHqEcpP2kS~sb-QG%JF z7=~wlhviy@cHs+0Q5FG@Cy)2#u0gd195j$1+(fd`E*;J-M@RDS0_s6dMR{8E#2c`an4KJ&+hu(2{$tDubh zB|A2;OG9s4IwCJrKY!%k(VwZ2eb1tOe+^(Z@Ne?@VN!&3<3tLsYdzq?Tl0O$Jy&%; z+5#ZWL7(T+Er&3Dr`l*19H$tew{Q5jc5F1~>;4k%CVVI#7N+{63%zlq5<7Cjq`n?w8%AMk2HJ=lCVWxlp=#eE{cHKs~ z&^-`zk~s6wpFjPw5W)UdjuZQo-Agibxf|$y9TnNb>xAw~j&%nnHswS}yfAN&&HnT3 zLck04_Q7Ml;49G|HV+vU_d}a&G>t5Nl|eo~Ey$lYH}E=l<3TMCTK!Bh6|eCOx?Wmb z@bWt6>Fbd@n@cojO#v|8izmD6a~F!;X#l7W&^O+zkOCYHAb{EYPw_cl;H1h-aGMA_Vr50heCtSl&Ip=2Q!+02X)3{jm?00 z6FDl=qv>BLX+uo%L`U7^_b!$||J=O3@dul^WuaEf>;f}?pf=gOG9XJ$+E^fWNK3iT z6-z5~yHsb$2EdUf(z|JT2X(%B=M6jalrKgu7Qb>|+oJsYq1+bv9G3*{HKth@x8WY1 z0vj#>fF2@tz2S&(-$>b}GZVSJXbuAJHnW4zSayu8D~x&PZP zJ;`kP=ia>NfE+2?5U#DmwM`(9d5hm{kX9O|q5XzavPYmReXG?%{!*_vU3?WoM@qjAn!#cHf1< ze!7Vb<TT2MeYQ5udJuH)I* z%g{z>Z1~R!2uFof0R?>V@}cm)#1Ef&nAkq&DmYG>-&O9Rm@hagf%kaTeyr>w-pcxY z7)~EflhS;^C%5Zx5m;_T9bx#YjpYz)CB750Fc3wam-)ancOf?_emP8$;++{IuQyB6 z+?rf>7nY9e;gIap_saNta>x_D{@t^jzRfD3>X8xgo~kQvU@I|p0%rqR(-{rBSA~-~ zZlAS5|4W!tDFBM#${EH_5c=`+$KNNLaoFEL;JE5lwPzt-`Ah+>1H%gk5KyR>GH-;Y zeDL&?t$r6nNzdFSU_CV?zkL4Abr;@ve*wc7C+l;(j6(yJof_=a!bexZxs zsi5DS@w_nnl6p`Gy@2lR!@4TFJ2UHC!qm`w&eDHeqHAERf z)f%zdrXc-MmR{Vr-AsZA#-j^Nqy9m(1=G&xd8+1+S;6W0U9OEi!2DXi&7*eM-Dur2 zzoF>TKwT*$^_wSN|A=-!Oj~S|rAH;>GFTw`b-1GQ_qy^g#@oykTOn8Gf$H&(AEv~$ zpRC&cnPm{axg`>p5+F-ve19Up%PsSAxyt@za{?j*7rJxSWqa~#iouuc>+HN=vtHLn zQ?HN7n5CNh;VDxb=Z$z$wyj5z#t)igrODLX*ZB-qSsbpSZ9Lynn2D1f)1}O)yx*8m zp~%bed7-LI-!HTfG@X9n{_^3G0V%HzVLlp8{w}@kS&*F}fvZwg2KpakTJiFq&>R&6 zI`4rYi%HGgjd|##3(diXn#^ zI-)A(kiRlmB$Dhm@1OGr&v*jw$JP+q9iIuaB?k-ykRLB~NuC{GzXUG0RldT$A_a#K zpxJ{)g!+0V-jXZAC@JS@Zpzr8=*-CAxSE$!igdFhZ5l$USW&lKi@+@ zv@W2MkrWqb7OQSg8*Zgk@~TFXOoIw_`UKfgI_DgH%u>6P+`r)F8}RwxZ}y=Y(MNUG zs;?T-`c>FHgq!1sx6Y!JNKc-TGJQ%yk9>PP#h*h8NvMw3%dFTvS8{?K^Gyo#?9E*m zl8zV@WQCWDmU>+y^vn_@>m_sv(QjZge}o&&$P_2QJ~Jt$Q`hx~&H{Q6W^z{9c!bE# z`j)%wE}8W|o&YrFHpGNnglwr%rzwxD%#^dZcraF{MmPqWLZ(7s5+9zP_i5xbr(M-Co|P@O<;3N$8d-3T#9;GO)&}coY}3klg+3TV)8%DbLv{cQVaVeMsnpm=q7#2|?ET^Cg?#DjlA1Rf{dASvSn*9_@f)yAu+H8V=Er_B z^|1b@+2Rw84R?>PAj9s1cvhQWvxBx3rCJTgI(w(XAEkhu>=`@Z)p6FEb+|> zmN&rTxx8l2xxJRq2k);kZ#^hx)7i-H;E|LsdtL4+|A3Xyld$C^#pv>b>|#B%RD~EO z+l9RHnqFTH-W!Zh(>wZ7?2&!)=StYDY8!)Pa{fJD!-pl{>mkX6|*(t3N_ zl$H-TQ%oT`JT8C?pSXJpJd$~8l%MQ7ogy&n3>foHRU>_hDslOrAGT*`YkCG_#K@S? zpS{z!i?yV`oKFjzz>XbIolA!E`IQmQoNCIx-$ESjB8?}zeqBAGCgb^Zb~+du)1?}9 z=^K;}Zai@KpZ?M7$=+^7ozNZh3x)0$_=$-9QO z$^@i5#d8)OsfHgFRYCYz@`vMeF4Y`A{&UyJBSyPc)^ryQ44)kJ!1!+@eLhCCre44D zF9#QgvS`m>YBi^S`|Ka$JTN)`ondkm>GFmSIq!XKk{X6*R|JP?NSmCLipi4I55FNb zk2@IPMoQa)L|+RGit?D%Y(ixieXgw%pO5y;Mf{AHH+z-6KAC*!9b=dG1^kWoU8AAH;ry@=z3T4e1K(D0qIyp7lrQzrn&=wcmzF1mS16kVezE_u9^T zJ#K#5U4J=34ya}A&PhibiR9hp`@enK+!svE+e?jh9;tp?@8a)CVJ-Z zHc8X*TSu1dVNJQx-#qyoSs>%v~#z+0RpD|sx0P%&$#Kb>Mi zTGKaAb{|`eK0dvVjB0J?bq97}I|o1-mqcy1*K zXWSai`NtG+pZ2rz4S!-#OhVo9VdHN;1t?w6;JETrimNwKa@BT(d!Hm=WIqC#tFMBn zJmxCHRS325iT$eIIbhAQ>iFCdr}F#P*8ht$X8u?1m5DJdMn>Z=a9=4^qx^&GjviFx zOaGb|r%qbnq>OQAN#-{las+OQZTE#mC%#Q`btO|e06u{o$EWjE;C9+%!izZ;Vt>bl zqY2N+KnO}|9`oeTA%6o)`B(4chF!6O7UxPqYr9aW%oHV>OUw=Pr!xBes~x+Q@E>V zh?zOzR9_b$d2BuCZu@y>$}9q$Q96w8m@(eCw zTXu9`=t(10ch6=U8CMlUgk_G_{%k)AA}Z_9#mrtX%rL7p+`X<508hS7wp;I86ha^F zNt8bI<{?uJ%fETD;hMiLPa3i{|25D^;;dCUg`QcH7bewhy$G`X=Sk*i+65&w6IKK0 zR2fXc!cAX_A-7b4nP^FIR96a=xCtfhzcXWqR#zco^d>ofKYEtlOz}%P*pB;tp4PoQ zbM_aNYUQjJ_DUq~ui&c0DAa^Ud{3y74W$!H!KW5F*lpc2ZLDe%Qg3q$cgfdnmzVe- z_scEhJw`?JlUuNoqU7j}8tqKIY#nCo?=_rd`Ct+=n#?E_&6EATnhPi)A=TnLSE!1Y&V9|PK?L?RxnLqDr^9$^1dbl8$ zlN5~a*+5KG8NVXue_QYOr84_{w2m_*hjMaK(wcS18r2pVsM_itmhcDxl%Pp1g_|KW zlgD??Xr*&Yu6J`e@liCe$zE?B|B-(ou$PwDx)C}+H#r+k1B}(BpK|h@Q6{Qp&$Bx* zCK;&}EEi0%fC&t6QwZOW{}7JhpRMd;OBh0Xd`-x^7ca!-KTw2EUYR;<%5!J>&B4r345-m1_CEw(T8d))UWJS= zSN*9n{tk+Ut9xhx?D4jiwCzJIHfCk5OHiYqGwI?= zPpD3Sg^xrvIMAJa<+R8uTq1-lWK+s~)VXCBRM!@;n}V?0O5jc@HT}K1=)3s)%T-#T ztTgYsjOpRag!c9;aoVl-u6Ky5b=H_3^g3f#M#ph2$MZcbWs_NE0};}^O)5dNZ=&Mn zElD-WXu$EQhXq~S_!jLkoICyt`+GS#1o^5OusC8mtsft zAH}>;_PJ=V4m&JbH~vv7n@xnX4N8CG@7Q47)~~mv|42Vay0=6qv_4D7C0hyZuL&=R z$RH`iiC@WIqYxmID7oU)7$LxlViTc(Q~Au;y(L^eK&3D=DH6cU$g7ba{4w2))#DkZ zaUSVQPM){+O|OW1)EL!84KB1{nRl*6UP8uG>d_-z^HSGD5Pm9DBI_=(8lNJMY@@pEn5XtTx zokI2l|D*S&c`2$RI+KMj)Ea-OasCn3rB=B`^DNko=0_F6URJMyycFo9SgrI#g>2$c zH~k}~NJd6L$f%oy7Ls2_?Z(J_t-^d*Tuj*Kl>8qQj9CQdM}!DuxhSiV#PdiK(6~y+ zNxh1DHe*@~t)bF+4pIIH+dB5cc>aqd^*cUF?#1o6p-##G=!wkpO}W%)hu3@D0{vFf2jd4u(N>)oV_W7T zdAgZ9=3wWQI}oRRvl0>8x#T624P`+FRF6;E{<>z6GjIrz?ol3I78R7rk{)WcQz&lV zGhw^Wzz1L0_kSd>gj>3iQLuz7yd1%i;=;J%D8K>ulEE#1?+Y6)*vGFDCAg{GqjtIB z#CQLG+SHnZN;)9+rz!tastgco5m;b_54*f58EKi{sS(M$^}-frJ%0GOb2<14ZFk#Q zBueHZ+Kudc7GdW^TsEnPijaJrHcq#oMno0&P%@v$QB4q-RIK8e-+E}HfX2uvL-F^7 z&Xcbmgh|o>d}pLw2mgEDQ?#4bT_u0fNl9J>YmN+DS5M7vC->>fxUp|8KAh;xVyx^va%#}if1SPJKsy4hP(%t zffd2DpLvzMUl@J+D)FSd94Gh)eqj7WfR<#?O=1>Zc3O@uJUzOD>WTU}d#?&62bmD> zj77RDRQj-#}=0ha5<_xtwC{(F~=eP7)qtjUi$PF$$`c0@VBc75}_;=%kBawgrieBpo~m; zpG+HeuAb=`GMF5y3=!HIyZ|d)R>5dw?)1Y~;hmxH9Flnz+9U$bVwMwAx1k3h2l4sn zhR}~wfi*Zacpsz`@uA=4X!*T$B?rHNOo>?T-5D+UJ+#Nu+|tF2iaWmnxD~qf;znDD z$I|6LsoSC)^rY`^dYQr>MlN#Nb*!5r8I*z~#Kc_QOfwPtlab~BB$h7Yg$d(_!VU9r zD^CdMHp21~Z>QxdRl0YJISZGJzy4$v){I|CKP3fIR`yEVhc6aC60@5B@~hEC3xr{9 zus;sD>(+V~6iUh=-_{0GgN6pHU%ORL5FGcpje{rUB;dIvD&o)N<%C4EZJ7&^#2W*^M~kl8dIua18HM8ei}W;D6zzN1)fBNeDOr23` zf4=@iS*Lv|wNW9GP8!&FvgpI#YWL26(NMKMq6wgB!$xHp-77{C3SC_MsSC#Lg!F6qS&ySA`*i%M#ze3gk^dInJF zJFt}M$jHQ_)GeF)*L475AtoRL0R*TaAMZV&;w_6xOdTel>bnN{``f=tVJhy|kqWI? z=!q!@ek1B<3Afg7dY7Q!BN2 zlM;MSCA{JT`tS`1Y1dVK^bq@S{}>(73>J>_S5%MRAUzjuX5;=)sEGfQ5IQ^i@y`C$ zAN87?nfyL{PfGK|-VZtLtep98ej`stih$dyPO z(kb*VArIt%0NLx7&cNizcp+cm=lm3Y{zkItZDn~QyCj!a)UQUSNcMRY^+nr=O@)qo zk0IU%0_nul2#(MVQU9Pv;WHD{M#qq%ZJyfmj#p&tBzu%S z#kiu&%9t>kj=(DicRGyB#=;!v4$0ZAd1L+P82;D(Z0{uGly{XppI@3%_S$R65H;xw zZAjzTdCt+i(_|F%e3^JMaFRUvHiKrUlMF=bq|%1%Gwm(QicO$=NX-mF*4hzirzru* zswvE}Ev!^xNEh#3Xs5!Vu>S4yGN`zhsN{K_ueF7jH<*?9B^0Q`yFK$%l4)Bvd^p$N zVdXb6d@-+zNStYHfGu&qg-y8CHKK8a=29R2eZW;TQSuI z_ODDD(UbuTx$K9)BEN&UG|3BB_r1Bk=dZ3uVo$DD?L6Op_wJib@Qx^UdS(Bp#n~zN zLT(P4b}hrZJuxb}JGQqMQJJ@qwcCFl_PFeT*^B>x4dXBFMWXRn?s#IvFUfA3;_UdR z9fiY{A^9_gm*<5MDAm$q24T+YyYXY^@s@1y-T}fze+HY2^66a4Sm16G1$O7m1&Tsg z|LkIFEF)Kr{D4?F{)R`oz>Vm6nvZ_m#Hq96zjoHnu%Ln|R@9vPrvYK!q5g1Yxy>nddnq~FSH-TW$ni}Ll0+iI%QNZKDC0Y=%52P-D9V$`RI z)=Bf52I9VKFQY{dMoC8Z}F; zLE^>auj>i`QrdU={+9i{^D?nT50*?(r^BOj;d)8N=#hP-1qdYliAdRuR%;_ANpcTk z>p)-LTOa2fLaEs_)DzTsRFCJV@Hr%P8^Mk+ZVmN|$yZ;)xuZ9pY{@&Vh%l)~?tcAl zRaQAX*J^7QNFIbgKWHEk3nc-7`HR%Y&-rmFMq3tD&i&y?N7g9 zGSfzha8GtE6C<{65-@cGrnvB048ChO=h>iv zF~X=h%#E*llh7pYb)X%NFky@#OL{~7kTMV9qvX-!{jG|u{8fob(rte!pP$6n3mDAS zlq;MvuNLLmPh$@ymuV}TPN4J|+&JDc3T~Xwb~FN5U8<-`TeL+xW(GI7neywr;J2NU z2I~z7WYvBt5EITJsJbup9c?v)amaxxKj_TE1$6cbHooU}7@1CtB4JFQBW|90-P=Rk z)Oe~4jqFL4A9L2dY}xYPn0Or9-~T)U$X6Mt)rWNZd=lw%`N-@6xku>+u~1BWMb1G- zGMmp^%9CugQx;5iDgoo}0N2CL^5DgNcJwc_GWAkGm4=Lrb zyffU)xGCa`iDPj8V!miB|{>Ozj&@oP%ZM*Xczo*KI#)Efz+_I_qi&voEBkMb0IB$&`UC$%<+von)%K2V~#CXyYIx8Y~>3~tqqauzk<%nBXnBheUQsqg&dVo^8Sl`F! z*SZ^gYIY4|_IH&GnI$=C;T%6mU|xbS+)o3*cF-_T)+2FY#6<;%6T^^nY}TBHqG(_TJ?iGE_2W z)$&)_e`EEIOfB4x*Pe<`&R_{TGH%bi@E4}DI!7_t)dS4q!z}mYo$8tX-Cdg{1N{8n z`mhY$X-nfEix!87^hry38;_|{yw0aWEAMd07!6)Bww3g}ZI`-j1Zmn8`!exK`-iu< z0#2==!9yaHs9LRY2jC;~8jR5y1a?URzHw4kf3K6x>d1TY?V_fOg~K5|FM8fNU7{$} z+QxYfK)LBr@M{w#JmNmyo2Au?5k#3(9{$-`S&WnF%v|z4B=*^;g$YM4*XeKO=JGD>2RObYqGG|L$f6I; zCVU0>A+^i}7|q~s_qGUkJ2?(Te)Su7lmqi-(1ZCBM!&z~-2GWhE(iX%O9jGsjJT1b*PpZ5l2HDJdf_uv4>a; z#_d-Cc!We=O-;m`i0)98P7ckleuIzF9Yc3ujvwLRkE)xg2WrIv?X<9=FA}JejOfL$ zya3N2RtOa6=2I-%08c0Q424nxmO9@ZrQ&vCtUBQL?sG{NRU{0P14JVzA!(ZB{6YQ8lcGb*39R|C&ND+TZu z=2N=#&VDWK`P6Vm)c!@&8K0&P4Ed6#u9Wsjm(}S%(j;hV!luNTUd2f{B`|`kYI#aK zG5iScu|m4f34wXS_P$O;&Ziw@YQqCG-IrD()xN}dm|}~2UXJg6P^xmIK(mWKW7TGN+Ym) z)BR^BxNs-60lTve_F7i)=5i3AK+DV2&%bWDEph(hQ?%w)Vv!qpwE##px|Yg+L~#vI zh?;I$<(Alc#uwh+Y?tQZwiPcyGSs%w_Z+rO%4*5vz7*u=)R6i@!&%Q8*Nvro*4 zj7fIPUbdPDMh%%P*8T0Nu}VB?c`hIVyz=nSrBXlm;rh7u6Qgnc1QjZ=y!XNXsF>rF zAD6t#SX0X8I3jD1hwa$yg`sG=qM(KVymDi8bs*bSI_pp2WEr%c4x%X_Z+0yFKTa~_ zdM5NFjrgq>`N`3&^ob)?&9%g?;{c^yH{xxDb7bQtcom`1fq5mxu4`naZu+_2dELZR zM7yD8+*M*hyi_N!ek+9kDe2Kogm2MxMEeP{t}dma(Hu95{A`*6(?P zOl`hA9z*1ADUdsbLZPaT-+)ezx`UQSu%kOpXVhzd%3fxzJ&w_VvTAvQoUa102rLnV z06??P#^e6lHjbszE9$q$j3I!GO69FaF=~*;X+?k;G>wX=Qsfhr^)9;j{(Vd3ELJZi z?vjCl0aF%ILp_oLz{lHL=37-&S8ZH;^r)J$AiqJ3C&>|hQuU_&N_JSB)M_dd{~N?< zpwp!ylcTReN@}el+BA19*ef{rVsw-cJ_4#m8+*+Bd^&i~qVeU7k8LJ0nfWM}%{7-z zHJ4w)SW?Iw@QE3oR{?$n;%|_r zL{%+a5l#e}1`yg$XrDgRQp8ej z6`46seQ$pKue0phnX1n;Go0`{?Qji~FtYLek*nD|##;aj(-lnuv2-!YU9A9AU~QEf zb}D__s!TUm)N)>2>uqcM0U8$-|6S|4=D?s&_$Wgw62J$4-^xnI&CRW*(!@YW1d<9E zJby)l-ALg-ce6293+laa1L?Z|$SQe6K4wqC^y7LPI4_4lthjs~o1T7J*A{waHV#rY z3pn~u`--A*i!NW&5b0_%JDsGGbzxPAu%1p*Q273u0ACSM-E*|oa=GSEi$}5LlXNMz zV;@C#ahn#_@OV?HEewCjUC6p2t>(*Cl0jL~c!XFAGfGxam;b={D&`Bqzv4cqF4DCz zIYx-{HqBky{u}4TutF2d2^^wf19oUOei;lt0J!2qzTMH%Zf+p-+f)~7 zk~%O|=RDF$Vdy>r!+sYALe(<#GQ>MRuxBHT0JAM=?N5v*SGpz2H*x+pkpFaNf1NN> z4NEc_I11fSB8out`kV-bk)t>fe4ruur{&A~IfW$;NTg6vP}+`L36bc}zAnZrN> z#Tv~5Z4}Z_{q{-a*%Ac2s;rdh5&U0g)4y0D-5!Mr#{J0XVmCLXZ*FEgk*XzbEtn+> zlA*^yZGFAq+F-65V$)Fh`sa#U`Vjq?fW;fZ{O5d3XXtb?@S|jI9k|mDr1hQc z8TJMqWzGD06R8QB?9>}n1b9B9U`(=*Z>|AE|APjirE=>eRh6^vztCB|rKW~2L)<3T zx9th3h?e9|<@I~}+)&+AsU=mqj{vG&AAE*-HUq)5@as-AF-X_UNb_q2$j>lB$c>x> z=dP^;K_Cj$sZNUEUn;g*Y^~fVlYs#N?N6mI1^)^`5F3;&zEtCa_Hj}?sbR4gbZM{Z z)?4HsonS;J^S7P&uE%l%6Hs;u+X}m&jUv9!LVwTCgI;|qemo}1WB_*}8PZHE*5rs8s$$##z1;~4 z%IgmO7uo$_du?yW^Q-AC6bJj zzy3kMdzn{Sdoge;QSn9QL~8@uYH}nm44_^8%rWaZx7t5M6KfGC5e8yC(w&+9MlDnL zd4Z2GEluPDO|21o8d+1`>^N-?=`8(X-_YWxcAhWYPE@ba8g^|(1pLPyEdl$_L#qV~ z@<_aNQ~jV^EW?#lcortSiO-?QZ*gJJQNg}1xSxR7bcS4p0(Jt0{M!1{hnIEKLlnef z1@kL!k@2Uh5Iw+9TV76XxhTU{_doXiR9vR{RiKcXX$O+YT%^oo2t-@L^r1wP?3zLF zp>~=MaSB=ARgYT;nN)<&IG=}Up}OB`hgiFXGzaoFUzp@0xjcWKQ(0`8Bns&z&jwoBBHelKxII6EeB`Hq z>hVh741Nq>F+~K&<%0aFfVNZqOZj=^_?z@#P5&LLzZs3iRI1m@@!g@1StWTPkjenG$ zViXIkdn$BcME=S#qL?8L{Tm|&n-9JssE3H^kw%EkP~ax?+Yg+hja#mx>>WM*4CCPAX@NVg>n-*SA0)Jq4^f+F!Fm#6R8ZTGZlIQ{$%;<7#<3i;8#qX`y8 zIoA}D$$%on1!MB10TgWiSd&vhad^jyoS;9xjSe#;Q~h^?m={e3Et1=lPs4pS`_?;5 zH8M9>cs&to?yFph&Qs2AXlUTC7$@dgknFt}s&|{1-I-}T+nsCWi%hz|CJkuWOpu#A ziG=eD_CD5a#=#L!5SMF^OLzXuw1%_3{|?uy$ao6fZ$X4SPO}v({3E8W`4Vd9Ye0ZP zhh@?AaZ=;X=y9lJo~yWZQ?i`=FnY&xo>ULWyFmdjH?L}GoDaGk*Oi8u3l(?HV+DOt z%v70npkd(5b+v0Iy=Kj>=3_`5O;|*9`&GCgNoOWhn&*@Vy7g>B82fQbfX2zty-4Ra zwjSYj=Ky1jzkP&M<#}`8e$_sD1_b z+vtN`y70G_Bup#(i)J?85DC|KNcSdykS*&Ez)zLi4*dt~q_Rv*kc)7P0-hd_-&+mXD%4U{V5oV35&|4tjFWSZW zUK1|Qeu#2uJxV&x}_?v!uMoWm9Fka-t7X?@5;M z{`#rl$dsU&8ohT657UPt&-joU);rqD~a0#5#b zwlK?~qJ1^Z`*9<++RUt7x|R*AV_kbYw}f4Lj+~s_xx2+4EZ+ikYVY&& z3=#bA-Tg{2J@a&})1fG9a7(w8dEkigt~HH&IY7yDB3jh1;HxR%j>)uQNlug;X(Tz9 zg10xfj-#p_3smn8t5`Yor%30xbR7>|xDR#N0NH@8K@2R>lBX2m%x0v?|0_OPw!e}o zxtRXaGVSkIL(!^(5No57#!tg8tpCZCEoeUpvxKnL)zIhaixDG&kgB1v@~T6SH7|V> zkC*l*Gm83tR7tN{%ju9wGhOLlgiiT;QzcQ!_cKH1GIfyDL&&eX5~SyAnF?F-av6*k zwBb^#Uw!uMAol={qm6PzSQ?d`@d}#sTnq3I7tl;4El3rS(!05M-6>(-?GC@MZW&V# zLTu(+wvg=7e$)KSefF*;ump98U$9g=U7*9b1+82ia;s`v=T-?>FCHo5iP!#I`GD&4 zmHwQr?$09kK1ez7FKX4I#1bsuA{2pJzhW#m#jiD0ATT6D4;n}F^E(bI`GFWoL!O+R+`CtDUqmur3Z z6Ti#%^{GI4cCoAW7ZYzVIJlRkzi6M7e-{TsZKH@kzckNWE`E?W_#MSC_d=Q)G$&rr zl?WV^?F;Q-M_iW7T8>f716|R3!iH(eb&5>Xnzov<(%#>0V4r6CP?FXUW~NaI&ZIq7L%s@TV=56W&g-kQlS~{#K}E8lg$q zIbVqXZ@qqlx?LJ4|07Ya7saC#VP&QV22_trQa2ejz(7_emu>$yPXmfrQ5|tUa+mIV zon`^{dg<156ysZO@jo07vKftq`17*fMk_+S2J3s&f+@nKnOtX}sCYyGw1Vm17GN#( zixv^UJ+uaCAzSy>G~4DOJ4qp3Po$5hOXG|+I=7=mi)QWi+=wg1~os=-MB=mYo1=j z;7-H!1E-?R{y6q$@cs^5-3u|OP~!1A8#-2Yiz%7eL^meV@;Y=4G=E8Dn~F^0Wt;hs zb$&Pl(}p*~=L*Ujo=48uBEw)EtmNwNk2GQon8ob<`?0v+C+VbcM`io319v=S=s{gq z%M1d+RG<^d1u&THUxBy6=P6%DMeiau%^@x2h^uNNej3~-{EIV7Q4WB8dMdLZKXYN~ z<3&x@A_KCot`W!&^Li{AF3&?IN9z*<^W?Q^vgZT4`arib=YBqhgeZezhC8DrQ~V`< z?2$|ylUHuZTVrk|Ic9l&5{hIDi5m{ahU)fwxQETT$Fz^gYcpqKqP*vKWmbr?7f@Ti zs879Y4q~_;ajp)|YXs?}LR0@B6rYFPa>q90yhX{)XmD z_&3eZUc`OLyT5zP-AFK*QSiw6V!LGGc;om}{vYXw-*90e1h^1Cg6-AgVcRuZNcF9k z{NGyL4Im;+#X#9DUH&_eQG^*pk=&Sh_zE0!24mL~5ZjE+PoXX2YTvTzO?)eE8D8xd zyW;+tEP!7(Ci?4rAst;&CNK8OFVRiiInRSi_V^ z=FR>HtjljhgR}(Ai}Qpe(|>+qQ?1z9_Eg>OP9w$-RA(I)O=9JtaX*7GK9Au3gMw9? zxzSDH)O$OLwo?j@fX``rE$C4)FQkbaXKbu$4C;Z(=V{ zS6;$qLZ5#mgZqWs#_-Njh4Jyn*U%ICMWwuBrTmBr3K`eetY2Q|Y4dyzJzEaF`hcoT zB=%_U-|tk%tl1{7n3~?&GiekUH2kX-Yew8^Y;WIv9yco+e>?V92ZxPr z)?xcT|DxufqU9gBzwu0ag9`t4fczZ<$`i$ld)r%97`Zyh#Yvd0C)BOF)b00qK3!KnQ)?g%ik@k} zcNgyZ+vnYxQLQc&9lJPNNt{S@T=yik$h7E}n4n-&u8#i~bm&Nr<*Cm-J6P?@lUNdP zWXP~O`jjv|2vTXM%LF@cd2v+Ql`u^(NeHS+2r^0xW_Yr3n2&MyL6&e_ykR3B!1H1V z_miw~5M*%PcZm{ug8RoIih~9PBof_U=Bg8Ik@EqR6W346V2)kPH&<&lCIIW`FRzDI zMW%RQoRo#u(Y9z4Y)YV<1xa)lfAt-*gXLJml{H%B>ZL7^)I;-3I3PQu$Aj_Vib=Pf4+t@;BjPikbL(0HS zpA`ZLAyEK9k7!^aSi#|~RljSegbnIV{*};@fC9}!=tJfoO`;~hQzD4m$gSW)w^GOfJT&tTn%mV zoAliqb1&$!-Q;uWW9c&y{drvv0h0cSv5T6Q5756lx`eQmGY9VypDOQopZ9^tUKoRq zBrel_wM!J`UT$t{>FqSm1-Begn*2{k=BpxBgD%MjeNQ{=yY>VtB*QTxVg>T;Yh$OV zvu2q^>0qFOQQMO3<*YenRZ+L$tfsi##dfk+4p%`kaxb8J&k@_us>&Bq^&QeFg;Kck z^GL?W1x-WTyuiC6bz2Fuf~1HmNVR;It@S82Ap}F-1@hrBB$IBMb)9zfJdpj$U&79! z8aQHz{Tk(|MjHVor1i*3jbD=i0QEv4@6if}LC^}(CgKtlb@kf8odTFXfR9$u8^cC( zBd><b9Yhzp6&cz2U&ONPs$l8BBRHBxx_X@GV*|*eh?x@~~3*jFk-gVnG;bvn_fej~| zDO0vN79o6dd4DK;r|Hygx8D+Vv0r+|$vv>=zWk<0x_bO?vR9_e(c(Cqctg@*kyJa;iERibuo4*uPqMF6DUWUVB}YohXd- zxpr(-;j}Zil4~6;j(n6C?SSX%)rvp`3nIVsajO!9Q8J=77-Ldx zMvEp8-bYrzNk20MTlF(rBBhh%qh^ zE)YzMScG~nA<{LyUU76O+2Eowq;O!jeFYqD){r literal 0 HcmV?d00001 diff --git a/Assets.xcassets/AppIcon.appiconset/Contents.json b/Assets.xcassets/AppIcon.appiconset/Contents.json index 93a6772e..b63ce430 100644 --- a/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,188 @@ { - "images" : [ + "images": [ { - "filename" : "16.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" + "filename": "16.png", + "idiom": "mac", + "scale": "1x", + "size": "16x16" }, { - "filename" : "16@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "16_dark.png", + "idiom": "mac", + "scale": "1x", + "size": "16x16" }, { - "filename" : "32.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" + "filename": "16@2x.png", + "idiom": "mac", + "scale": "2x", + "size": "16x16" }, { - "filename" : "32@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "16@2x_dark.png", + "idiom": "mac", + "scale": "2x", + "size": "16x16" }, { - "filename" : "128.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" + "filename": "32.png", + "idiom": "mac", + "scale": "1x", + "size": "32x32" }, { - "filename" : "128@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "32_dark.png", + "idiom": "mac", + "scale": "1x", + "size": "32x32" }, { - "filename" : "256.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" + "filename": "32@2x.png", + "idiom": "mac", + "scale": "2x", + "size": "32x32" }, { - "filename" : "256@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "32@2x_dark.png", + "idiom": "mac", + "scale": "2x", + "size": "32x32" }, { - "filename" : "512.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" + "filename": "128.png", + "idiom": "mac", + "scale": "1x", + "size": "128x128" }, { - "filename" : "512@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "128_dark.png", + "idiom": "mac", + "scale": "1x", + "size": "128x128" + }, + { + "filename": "128@2x.png", + "idiom": "mac", + "scale": "2x", + "size": "128x128" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "128@2x_dark.png", + "idiom": "mac", + "scale": "2x", + "size": "128x128" + }, + { + "filename": "256.png", + "idiom": "mac", + "scale": "1x", + "size": "256x256" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "256_dark.png", + "idiom": "mac", + "scale": "1x", + "size": "256x256" + }, + { + "filename": "256@2x.png", + "idiom": "mac", + "scale": "2x", + "size": "256x256" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "256@2x_dark.png", + "idiom": "mac", + "scale": "2x", + "size": "256x256" + }, + { + "filename": "512.png", + "idiom": "mac", + "scale": "1x", + "size": "512x512" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "512_dark.png", + "idiom": "mac", + "scale": "1x", + "size": "512x512" + }, + { + "filename": "512@2x.png", + "idiom": "mac", + "scale": "2x", + "size": "512x512" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "512@2x_dark.png", + "idiom": "mac", + "scale": "2x", + "size": "512x512" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } } diff --git a/Assets.xcassets/AppIconDark.imageset/AppIconDark.png b/Assets.xcassets/AppIconDark.imageset/AppIconDark.png new file mode 100644 index 0000000000000000000000000000000000000000..08e06a8f4795322ac8abe2304560420d84adc910 GIT binary patch literal 148367 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�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%0Dg4v*nc{(`=gOG!8Xz@Z?&Ox7knFJT-5{U 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 literal 0 HcmV?d00001 diff --git a/scripts/generate_dark_icon.py b/scripts/generate_dark_icon.py new file mode 100755 index 00000000..b5ea13fe --- /dev/null +++ b/scripts/generate_dark_icon.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""Generate dark mode app icon variants. + +Composites the Figma chevron layer (on transparent background) over a dark +squircle background derived from the light icon's alpha channel. This +preserves the exact chevron colors and glow without any halo artifacts. + +Requires the Figma export at: design/cmux-icon-chevron.png +Falls back to mathematical recomposition if the Figma layer is missing. +""" +import json +import os +import sys + +from PIL import Image, ImageFilter + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Apple systemBackground dark +DARK_BG = (28, 28, 30) + +# Figma chevron layer (exported from Figma at native resolution) +FIGMA_CHEVRON = os.path.join(REPO, "design", "cmux-icon-chevron.png") + +# The Figma export is ~25% larger than the repo icon. Scale and offset +# computed by matching the solid chevron (sat>0.5) bounding box center +# between the repo light icon and the scaled Figma chevron layer. +FIGMA_SCALE = 0.7996 +FIGMA_OFFSET = (290, 187) + +SIZES = [ + ("16.png", 16), + ("16@2x.png", 32), + ("32.png", 32), + ("32@2x.png", 64), + ("128.png", 128), + ("128@2x.png", 256), + ("256.png", 256), + ("256@2x.png", 512), + ("512.png", 512), + ("512@2x.png", 1024), +] + + +def make_dark_from_figma(light_1024: Image.Image, chevron: Image.Image) -> Image.Image: + """Create dark icon by compositing Figma chevron over dark background. + + Uses the light icon's alpha channel for the squircle shape mask, + fills it with the dark background color, then composites the + chevron layer on top at the precomputed offset. + """ + size = 1024 + light = light_1024.convert("RGBA") + if light.size != (size, size): + light = light.resize((size, size), Image.LANCZOS) + + # Create dark background with the squircle shape from the light icon + dark_bg = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + light_px = light.load() + dark_px = dark_bg.load() + for y in range(size): + for x in range(size): + _, _, _, a = light_px[x, y] + if a > 0: + dark_px[x, y] = (DARK_BG[0], DARK_BG[1], DARK_BG[2], a) + + # Scale chevron + chev = chevron.convert("RGBA") + cw, ch = chev.size + scaled_w = int(cw * FIGMA_SCALE) + scaled_h = int(ch * FIGMA_SCALE) + chev = chev.resize((scaled_w, scaled_h), Image.LANCZOS) + ox, oy = FIGMA_OFFSET + + # Build enhanced glow: brighten the chevron, blur at two radii + glow_src = chev.copy() + glow_px = glow_src.load() + for y in range(scaled_h): + for x in range(scaled_w): + r, g, b, a = glow_px[x, y] + if a > 0: + glow_px[x, y] = ( + min(255, int(r * 1.2)), + min(255, int(g * 1.2)), + min(255, int(b * 1.2)), + min(255, int(a * 1.1)), + ) + + glow_canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + glow_canvas.paste(glow_src, (ox, oy), glow_src) + + # Wide soft glow + tighter glow + glow_wide = glow_canvas.filter(ImageFilter.GaussianBlur(radius=25)) + glow_tight = glow_canvas.filter(ImageFilter.GaussianBlur(radius=12)) + + # Reduce glow opacity + for glow, factor in [(glow_wide, 0.45), (glow_tight, 0.35)]: + gpx = glow.load() + for y in range(size): + for x in range(size): + r, g, b, a = gpx[x, y] + gpx[x, y] = (r, g, b, int(a * factor)) + + # Composite: dark bg -> wide glow -> tight glow -> sharp chevron + result = Image.alpha_composite(dark_bg, glow_wide) + result = Image.alpha_composite(result, glow_tight) + chev_canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + chev_canvas.paste(chev, (ox, oy), chev) + result = Image.alpha_composite(result, chev_canvas) + + return result + + +def make_dark_fallback(img: Image.Image) -> Image.Image: + """Fallback: mathematical recomposition when Figma layer is unavailable.""" + img = img.convert("RGBA") + w, h = img.size + pixels = img.load() + + for y in range(h): + for x in range(w): + r, g, b, a = pixels[x, y] + if a == 0: + continue + max_dev = max(255 - r, 255 - g, 255 - b) + fg_alpha = min(max_dev / 60.0, 1.0) + bg_factor = 1.0 - fg_alpha + nr = max(0, int(r - bg_factor * (255 - DARK_BG[0]))) + ng = max(0, int(g - bg_factor * (255 - DARK_BG[1]))) + nb = max(0, int(b - bg_factor * (255 - DARK_BG[2]))) + pixels[x, y] = (nr, ng, nb, a) + + return img + + +def update_contents_json(icon_dir: str) -> None: + """Add dark appearance entries to Contents.json.""" + contents_path = os.path.join(icon_dir, "Contents.json") + with open(contents_path) as f: + contents = json.load(f) + + # Remove any existing dark entries to avoid duplicates + images = [ + img for img in contents["images"] + if not any( + ap.get("value") == "dark" + for ap in img.get("appearances", []) + ) + ] + + dark_images = [] + for img in images: + filename = img.get("filename", "") + if not filename: + continue + base, ext = os.path.splitext(filename) + dark_entry = { + "appearances": [ + {"appearance": "luminosity", "value": "dark"} + ], + "filename": f"{base}_dark{ext}", + "idiom": img["idiom"], + "scale": img["scale"], + "size": img["size"], + } + dark_images.append(dark_entry) + + # Interleave: light, dark, light, dark, ... + merged = [] + for i, img in enumerate(images): + merged.append(img) + if i < len(dark_images): + merged.append(dark_images[i]) + + contents["images"] = merged + with open(contents_path, "w") as f: + json.dump(contents, f, indent=2) + f.write("\n") + print(f" Updated {contents_path}") + + +def generate_dark_icons(icon_set: str) -> None: + """Generate dark variants for an icon set.""" + src_dir = os.path.join(REPO, "Assets.xcassets", f"{icon_set}.appiconset") + if not os.path.isdir(src_dir): + print(f"SKIP {icon_set} (not found)") + return + + use_figma = os.path.exists(FIGMA_CHEVRON) + if use_figma: + print(f"\n{icon_set} (using Figma chevron layer):") + chevron = Image.open(FIGMA_CHEVRON) + light_1024_path = os.path.join(src_dir, "512@2x.png") + light_1024 = Image.open(light_1024_path) + dark_1024 = make_dark_from_figma(light_1024, chevron) + else: + print(f"\n{icon_set} (fallback: mathematical recomposition):") + dark_1024 = None + + for filename, pixel_size in SIZES: + src_path = os.path.join(src_dir, filename) + if not os.path.exists(src_path): + print(f" SKIP {filename} (not found)") + continue + + base, ext = os.path.splitext(filename) + dst_path = os.path.join(src_dir, f"{base}_dark{ext}") + + if use_figma: + # Downscale the 1024x1024 Figma composite + dark_img = dark_1024.resize((pixel_size, pixel_size), Image.LANCZOS) + else: + img = Image.open(src_path) + if img.size != (pixel_size, pixel_size): + img = img.resize((pixel_size, pixel_size), Image.LANCZOS) + dark_img = make_dark_fallback(img) + + dark_img.save(dst_path, "PNG") + print(f" {base}_dark{ext} ({pixel_size}x{pixel_size})") + + update_contents_json(src_dir) + + +def main(): + generate_dark_icons("AppIcon") + + +if __name__ == "__main__": + main() From 1b160dc5ca47bf86795dbe60b8579b58ed22a638 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 04:04:32 -0800 Subject: [PATCH 012/232] Add display diagnostics, test filter, and per-test timeout to Depot workflow (#720) - Display diagnostics step to debug headless display issues - test_filter input to run specific XCUITest classes - test_timeout input (default 120s) to prevent test hangs --- .github/workflows/test-depot.yml | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml index b63fe387..b1a04d53 100644 --- a/.github/workflows/test-depot.yml +++ b/.github/workflows/test-depot.yml @@ -17,6 +17,14 @@ on: required: false default: false type: boolean + test_filter: + description: "Run specific UI test class (e.g. UpdatePillUITests) or empty for all" + required: false + default: "" + test_timeout: + description: "Per-test timeout in seconds (default: 120)" + required: false + default: "120" jobs: tests: @@ -74,6 +82,18 @@ jobs: rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework + - name: Display diagnostics + run: | + echo "=== Screen resolution ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "No display info available" + echo "" + echo "=== Window server ===" + /usr/sbin/system_profiler SPSoftwareDataType | grep -i "boot mode" || true + echo "" + echo "=== Accessibility permissions ===" + tccutil reset Accessibility 2>/dev/null || true + echo "Accessibility reset attempted" + - name: Clean DerivedData run: | rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* @@ -142,11 +162,23 @@ jobs: - name: Run UI tests if: ${{ !inputs.skip_ui_tests }} + env: + TEST_FILTER: ${{ inputs.test_filter }} + TEST_TIMEOUT: ${{ inputs.test_timeout || '120' }} run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + + # Build the -only-testing argument + if [ -n "$TEST_FILTER" ]; then + ONLY_TESTING="-only-testing:cmuxUITests/$TEST_FILTER" + else + ONLY_TESTING="-only-testing:cmuxUITests" + fi + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ -disableAutomaticPackageResolution \ -destination "platform=macOS" \ - -only-testing:cmuxUITests test + -maximum-test-execution-time-allowance "$TEST_TIMEOUT" \ + $ONLY_TESTING test From 2b66a2f5c14a1b5bc6dbe83b7b7064029c943609 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 04:25:36 -0800 Subject: [PATCH 013/232] Add virtual display for headless Depot runners (#721) Depot macOS runners have no physical display, causing XCUITests to fail with "Failed to activate application (current state: Running Background)". This adds a small ObjC tool that creates a virtual display using the private CGVirtualDisplay API before tests run. --- .github/workflows/test-depot.yml | 21 ++++---- scripts/create-virtual-display.m | 93 ++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 scripts/create-virtual-display.m diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml index b1a04d53..ca636bf6 100644 --- a/.github/workflows/test-depot.yml +++ b/.github/workflows/test-depot.yml @@ -82,17 +82,20 @@ jobs: rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework - - name: Display diagnostics + - name: Create virtual display run: | - echo "=== Screen resolution ===" - system_profiler SPDisplaysDataType 2>/dev/null || echo "No display info available" + set -euo pipefail + echo "=== Display before ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" echo "" - echo "=== Window server ===" - /usr/sbin/system_profiler SPSoftwareDataType | grep -i "boot mode" || true - echo "" - echo "=== Accessibility permissions ===" - tccutil reset Accessibility 2>/dev/null || true - echo "Accessibility reset attempted" + clang -framework Foundation -framework CoreGraphics \ + -o /tmp/create-virtual-display scripts/create-virtual-display.m + /tmp/create-virtual-display & + VDISPLAY_PID=$! + echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" + sleep 3 + echo "=== Display after ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" - name: Clean DerivedData run: | diff --git a/scripts/create-virtual-display.m b/scripts/create-virtual-display.m new file mode 100644 index 00000000..d3df1bae --- /dev/null +++ b/scripts/create-virtual-display.m @@ -0,0 +1,93 @@ +// Creates a virtual display on headless macOS (CI runners without a physical monitor). +// Uses the private CGVirtualDisplay API from CoreGraphics. +// The display stays alive as long as this process runs. +// +// Build: clang -framework Foundation -framework CoreGraphics -o create-virtual-display create-virtual-display.m +// Usage: ./create-virtual-display & + +#import +#import + +// Private CoreGraphics classes (declared here since they're not in public headers) +@interface CGVirtualDisplayMode : NSObject +- (instancetype)initWithWidth:(unsigned int)width height:(unsigned int)height refreshRate:(double)refreshRate; +@end + +@interface CGVirtualDisplayDescriptor : NSObject +@property (nonatomic, copy) NSString *name; +@property (nonatomic) unsigned int maxPixelsWide; +@property (nonatomic) unsigned int maxPixelsHigh; +@property (nonatomic) CGSize sizeInMillimeters; +@property (nonatomic) unsigned int vendorID; +@property (nonatomic) unsigned int productID; +@property (nonatomic) unsigned int serialNum; +@property (nonatomic, strong) dispatch_queue_t queue; +@end + +@interface CGVirtualDisplaySettings : NSObject +@property (nonatomic) unsigned int hiDPI; +@property (nonatomic, strong) NSArray *modes; +@end + +@interface CGVirtualDisplay : NSObject +- (instancetype)initWithDescriptor:(CGVirtualDisplayDescriptor *)descriptor; +- (BOOL)applySettings:(CGVirtualDisplaySettings *)settings; +@property (nonatomic, readonly) unsigned int displayID; +@end + +int main(int argc, const char *argv[]) { + @autoreleasepool { + unsigned int width = 1920; + unsigned int height = 1080; + + // Verify the private classes exist + if (!NSClassFromString(@"CGVirtualDisplay")) { + fprintf(stderr, "ERROR: CGVirtualDisplay API not available on this system\n"); + return 1; + } + + // Create display mode + CGVirtualDisplayMode *mode = [[CGVirtualDisplayMode alloc] initWithWidth:width height:height refreshRate:60.0]; + if (!mode) { + fprintf(stderr, "ERROR: Failed to create CGVirtualDisplayMode\n"); + return 1; + } + + // Configure descriptor + CGVirtualDisplayDescriptor *descriptor = [[CGVirtualDisplayDescriptor alloc] init]; + descriptor.name = @"CI Virtual Display"; + descriptor.maxPixelsWide = width; + descriptor.maxPixelsHigh = height; + descriptor.sizeInMillimeters = CGSizeMake(530, 300); + descriptor.vendorID = 0x1234; + descriptor.productID = 0x5678; + descriptor.serialNum = 0x0001; + descriptor.queue = dispatch_get_main_queue(); + + // Create virtual display + CGVirtualDisplay *display = [[CGVirtualDisplay alloc] initWithDescriptor:descriptor]; + if (!display) { + fprintf(stderr, "ERROR: Failed to create CGVirtualDisplay\n"); + return 1; + } + + // Apply settings with display mode + CGVirtualDisplaySettings *settings = [[CGVirtualDisplaySettings alloc] init]; + settings.hiDPI = 0; + settings.modes = @[mode]; + + BOOL ok = [display applySettings:settings]; + if (!ok) { + fprintf(stderr, "ERROR: Failed to apply display settings\n"); + return 1; + } + + printf("Virtual display created: %ux%u@60Hz (displayID: %u)\n", width, height, display.displayID); + printf("PID: %d\n", getpid()); + fflush(stdout); + + // Keep alive so the display persists + dispatch_main(); + } + return 0; +} From 1fcbdc94179080a3cf249940b3096164a7598c7c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 16:34:38 -0800 Subject: [PATCH 014/232] Split self-hosted concurrency groups per workflow (#729) * Add virtual display for headless Depot runners Depot macOS runners have no physical display, causing XCUITests to fail with "Failed to activate application (current state: Running Background)". This adds a small ObjC tool that creates a virtual display using the private CGVirtualDisplay API before tests run. * Split self-hosted concurrency groups per workflow CI, nightly, and release all shared `self-hosted-build`, so the hourly nightly cancelled in-progress CI runs. Now each workflow has its own group (self-hosted-ci, self-hosted-nightly, self-hosted-release). CI also gets cancel-in-progress: true so rapid pushes cancel stale runs. --- .github/workflows/ci.yml | 4 ++-- .github/workflows/nightly.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91128bce..29904925 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,8 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: self-hosted concurrency: - group: self-hosted-build - cancel-in-progress: false + group: self-hosted-ci + cancel-in-progress: true steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a3cb18f8..62e92928 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -83,7 +83,7 @@ jobs: if: needs.decide.outputs.should_build == 'true' runs-on: self-hosted concurrency: - group: self-hosted-build + group: self-hosted-nightly cancel-in-progress: false steps: - name: Checkout main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84825b5f..4a704f32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: build-sign-notarize: runs-on: self-hosted concurrency: - group: self-hosted-build + group: self-hosted-release cancel-in-progress: false steps: - name: Checkout From 7f9e04826a222b9bb11ef874d2b927e68591725f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:53:06 -0800 Subject: [PATCH 015/232] Fade in GitHub stars badge and reduce right padding (#703) Stars badge now fades in over 500ms instead of popping in abruptly. Reduced right padding from pr-2 to pr-1. --- web/app/components/github-stars.tsx | 2 +- web/app/globals.css | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/web/app/components/github-stars.tsx b/web/app/components/github-stars.tsx index bfd50f01..1dc3adb4 100644 --- a/web/app/components/github-stars.tsx +++ b/web/app/components/github-stars.tsx @@ -33,7 +33,7 @@ export function GitHubStarsBadge() { onClick={() => posthog.capture("cmuxterm_github_clicked", { location: "stars_badge" }) } - className="inline-flex items-center gap-1.5 pr-2 text-sm text-muted hover:text-foreground transition-colors" + className="inline-flex items-center gap-1.5 pr-1 text-sm text-muted hover:text-foreground transition-colors animate-fade-in" > Date: Sun, 1 Mar 2026 18:53:08 -0800 Subject: [PATCH 016/232] Add inline VS Code open-directory command palette action (#685) * Add inline VS Code directory command-palette action * Harden inline VS Code serve-web lifecycle handling * Add command-palette actions to stop/restart inline VS Code server * Fix inline VS Code serve-web review comments * Fix serve-web stop/start race and add regression test --- Sources/AppDelegate.swift | 382 +++++++++++++++++- Sources/ContentView.swift | 82 ++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 215 +++++++++- 3 files changed, 674 insertions(+), 5 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index a8718fb8..2c1d7c2d 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -54,10 +54,12 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { struct DetectionEnvironment { let homeDirectoryPath: String let fileExistsAtPath: (String) -> Bool + let isExecutableFileAtPath: (String) -> Bool static let live = DetectionEnvironment( homeDirectoryPath: FileManager.default.homeDirectoryForCurrentUser.path, - fileExistsAtPath: { FileManager.default.fileExists(atPath: $0) } + fileExistsAtPath: { FileManager.default.fileExists(atPath: $0) }, + isExecutableFileAtPath: { FileManager.default.isExecutableFile(atPath: $0) } ) } @@ -94,7 +96,7 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { case .tower: return "Open Current Directory in Tower" case .vscode: - return "Open Current Directory in VS Code" + return "Open Current Directory in VS Code (Inline)" case .warp: return "Open Current Directory in Warp" case .windsurf: @@ -126,7 +128,7 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { case .tower: return common + ["tower", "git", "client"] case .vscode: - return common + ["vs", "code", "visual", "studio"] + return common + ["vs", "code", "visual", "studio", "inline", "browser", "serve-web"] case .warp: return common + ["warp", "terminal", "shell"] case .windsurf: @@ -139,7 +141,12 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { } func isAvailable(in environment: DetectionEnvironment = .live) -> Bool { - applicationPath(in: environment) != nil + guard let applicationPath = applicationPath(in: environment) else { return false } + guard self == .vscode else { return true } + return VSCodeCLILaunchConfigurationBuilder.launchConfiguration( + vscodeApplicationURL: URL(fileURLWithPath: applicationPath, isDirectory: true), + isExecutableAtPath: environment.isExecutableFileAtPath + ) != nil } func applicationURL(in environment: DetectionEnvironment = .live) -> URL? { @@ -225,6 +232,372 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { } } +enum VSCodeServeWebURLBuilder { + static func extractWebUIURL(from output: String) -> URL? { + let prefix = "Web UI available at " + for line in output.split(whereSeparator: \.isNewline).reversed() { + guard let range = line.range(of: prefix) else { continue } + let rawURL = line[range.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines) + guard !rawURL.isEmpty, let url = URL(string: rawURL) else { continue } + return url + } + return nil + } + + static func openFolderURL(baseWebUIURL: URL, directoryPath: String) -> URL? { + var components = URLComponents(url: baseWebUIURL, resolvingAgainstBaseURL: false) + var queryItems = components?.queryItems ?? [] + queryItems.removeAll { $0.name == "folder" } + queryItems.append(URLQueryItem(name: "folder", value: directoryPath)) + components?.queryItems = queryItems + return components?.url + } +} + +struct VSCodeCLILaunchConfiguration { + let executableURL: URL + let argumentsPrefix: [String] + let environment: [String: String] +} + +enum VSCodeCLILaunchConfigurationBuilder { + static func launchConfiguration( + vscodeApplicationURL: URL, + baseEnvironment: [String: String] = ProcessInfo.processInfo.environment, + isExecutableAtPath: (String) -> Bool = { FileManager.default.isExecutableFile(atPath: $0) } + ) -> VSCodeCLILaunchConfiguration? { + let contentsURL = vscodeApplicationURL.appendingPathComponent("Contents", isDirectory: true) + let codeTunnelURL = contentsURL.appendingPathComponent("Resources/app/bin/code-tunnel", isDirectory: false) + guard isExecutableAtPath(codeTunnelURL.path) else { return nil } + + var environment = baseEnvironment + environment["ELECTRON_RUN_AS_NODE"] = "1" + environment.removeValue(forKey: "VSCODE_NODE_OPTIONS") + environment.removeValue(forKey: "VSCODE_NODE_REPL_EXTERNAL_MODULE") + if let nodeOptions = environment["NODE_OPTIONS"] { + environment["VSCODE_NODE_OPTIONS"] = nodeOptions + } + if let nodeReplExternalModule = environment["NODE_REPL_EXTERNAL_MODULE"] { + environment["VSCODE_NODE_REPL_EXTERNAL_MODULE"] = nodeReplExternalModule + } + environment.removeValue(forKey: "NODE_OPTIONS") + environment.removeValue(forKey: "NODE_REPL_EXTERNAL_MODULE") + + return VSCodeCLILaunchConfiguration( + executableURL: codeTunnelURL, + argumentsPrefix: [], + environment: environment + ) + } +} + +final class VSCodeServeWebController { + static let shared = VSCodeServeWebController() + private static let serveWebStartupTimeoutSeconds: TimeInterval = 60 + + private let queue = DispatchQueue(label: "cmux.vscode.serveWeb") + private let launchQueue = DispatchQueue(label: "cmux.vscode.serveWeb.launch") + private let launchProcessOverride: ((URL, UInt64) -> (process: Process, url: URL)?)? + private var serveWebProcess: Process? + private var launchingProcess: Process? + private var serveWebURL: URL? + private var pendingCompletions: [(generation: UInt64, completion: (URL?) -> Void)] = [] + private var isLaunching = false + private var activeLaunchGeneration: UInt64? + private var lifecycleGeneration: UInt64 = 0 + + private init(launchProcessOverride: ((URL, UInt64) -> (process: Process, url: URL)?)? = nil) { + self.launchProcessOverride = launchProcessOverride + } + +#if DEBUG + static func makeForTesting( + launchProcessOverride: @escaping (URL, UInt64) -> (process: Process, url: URL)? + ) -> VSCodeServeWebController { + VSCodeServeWebController(launchProcessOverride: launchProcessOverride) + } +#endif + + func ensureServeWebURL(vscodeApplicationURL: URL, completion: @escaping (URL?) -> Void) { + queue.async { + if let process = self.serveWebProcess, + process.isRunning, + let url = self.serveWebURL { + DispatchQueue.main.async { + completion(url) + } + return + } + + let completionGeneration = self.lifecycleGeneration + self.pendingCompletions.append((generation: completionGeneration, completion: completion)) + guard !self.isLaunching else { return } + + self.isLaunching = true + let launchGeneration = completionGeneration + self.activeLaunchGeneration = launchGeneration + + self.launchQueue.async { + let shouldLaunch = self.queue.sync { + self.lifecycleGeneration == launchGeneration + } + guard shouldLaunch else { + self.queue.async { + guard self.activeLaunchGeneration == launchGeneration else { return } + self.isLaunching = false + self.activeLaunchGeneration = nil + } + return + } + let launchResult = self.launchServeWebProcess( + vscodeApplicationURL: vscodeApplicationURL, + expectedGeneration: launchGeneration + ) + self.queue.async { + guard self.activeLaunchGeneration == launchGeneration else { + if let process = launchResult?.process, process.isRunning { + process.terminate() + } + return + } + self.isLaunching = false + self.activeLaunchGeneration = nil + + guard self.lifecycleGeneration == launchGeneration else { + if let launchedProcess = launchResult?.process, + self.launchingProcess === launchedProcess { + self.launchingProcess = nil + } + if let process = launchResult?.process, process.isRunning { + process.terminate() + } + return + } + + if let launchResult { + self.launchingProcess = nil + self.serveWebProcess = launchResult.process + self.serveWebURL = launchResult.url + } else { + self.launchingProcess = nil + self.serveWebProcess = nil + self.serveWebURL = nil + } + + var completions: [(URL?) -> Void] = [] + var remaining: [(generation: UInt64, completion: (URL?) -> Void)] = [] + for pending in self.pendingCompletions { + if pending.generation == launchGeneration { + completions.append(pending.completion) + } else { + remaining.append(pending) + } + } + self.pendingCompletions = remaining + let resolvedURL = self.serveWebURL + DispatchQueue.main.async { + completions.forEach { $0(resolvedURL) } + } + } + } + } + } + + func stop() { + let (processes, completions): ([Process], [(URL?) -> Void]) = queue.sync { + self.lifecycleGeneration &+= 1 + self.isLaunching = false + self.activeLaunchGeneration = nil + var processes: [Process] = [] + if let process = self.serveWebProcess { + processes.append(process) + } + if let process = self.launchingProcess, + !processes.contains(where: { $0 === process }) { + processes.append(process) + } + self.serveWebProcess = nil + self.launchingProcess = nil + self.serveWebURL = nil + let completions = self.pendingCompletions.map(\.completion) + self.pendingCompletions.removeAll() + return (processes, completions) + } + + for process in processes where process.isRunning { + process.terminate() + } + + if !completions.isEmpty { + DispatchQueue.main.async { + completions.forEach { $0(nil) } + } + } + } + + func restart(vscodeApplicationURL: URL, completion: @escaping (URL?) -> Void) { + stop() + ensureServeWebURL(vscodeApplicationURL: vscodeApplicationURL, completion: completion) + } + + private func launchServeWebProcess( + vscodeApplicationURL: URL, + expectedGeneration: UInt64 + ) -> (process: Process, url: URL)? { + if let launchProcessOverride { + return launchProcessOverride(vscodeApplicationURL, expectedGeneration) + } + + guard let launchConfiguration = VSCodeCLILaunchConfigurationBuilder.launchConfiguration( + vscodeApplicationURL: vscodeApplicationURL + ) else { return nil } + + let process = Process() + process.executableURL = launchConfiguration.executableURL + process.arguments = launchConfiguration.argumentsPrefix + [ + "serve-web", + "--accept-server-license-terms", + "--host", "127.0.0.1", + "--port", "0", + "--connection-token", Self.randomConnectionToken(), + ] + process.environment = launchConfiguration.environment + + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + process.standardOutput = stdoutPipe + process.standardError = stderrPipe + + let collector = ServeWebOutputCollector() + let outputReader: (FileHandle) -> Void = { fileHandle in + let data = fileHandle.availableData + guard !data.isEmpty else { return } + collector.append(data) + } + stdoutPipe.fileHandleForReading.readabilityHandler = outputReader + stderrPipe.fileHandleForReading.readabilityHandler = outputReader + + process.terminationHandler = { [weak self] terminatedProcess in + stdoutPipe.fileHandleForReading.readabilityHandler = nil + stderrPipe.fileHandleForReading.readabilityHandler = nil + Self.drainAvailableOutput(from: stdoutPipe.fileHandleForReading, collector: collector) + Self.drainAvailableOutput(from: stderrPipe.fileHandleForReading, collector: collector) + collector.markProcessExited() + self?.queue.async { + guard let self else { return } + if self.launchingProcess === terminatedProcess { + self.launchingProcess = nil + } + if self.serveWebProcess === terminatedProcess { + self.serveWebProcess = nil + self.serveWebURL = nil + } + } + } + + let didStart: Bool = queue.sync { + guard self.lifecycleGeneration == expectedGeneration, + self.activeLaunchGeneration == expectedGeneration else { + return false + } + self.launchingProcess = process + do { + try process.run() + return true + } catch { + if self.launchingProcess === process { + self.launchingProcess = nil + } + return false + } + } + guard didStart else { + stdoutPipe.fileHandleForReading.readabilityHandler = nil + stderrPipe.fileHandleForReading.readabilityHandler = nil + return nil + } + + guard collector.waitForURL(timeoutSeconds: Self.serveWebStartupTimeoutSeconds), + let serveWebURL = collector.webUIURL else { + stdoutPipe.fileHandleForReading.readabilityHandler = nil + stderrPipe.fileHandleForReading.readabilityHandler = nil + if process.isRunning { + process.terminate() + } + return nil + } + + return (process, serveWebURL) + } + + private static func drainAvailableOutput(from fileHandle: FileHandle, collector: ServeWebOutputCollector) { + while true { + let data = fileHandle.availableData + guard !data.isEmpty else { return } + collector.append(data) + } + } + + private static func randomConnectionToken() -> String { + UUID().uuidString.replacingOccurrences(of: "-", with: "") + } +} + +final class ServeWebOutputCollector { + private let lock = NSLock() + private let semaphore = DispatchSemaphore(value: 0) + private var outputBuffer = "" + private var resolvedURL: URL? + private var didSignal = false + + var webUIURL: URL? { + lock.lock() + defer { lock.unlock() } + return resolvedURL + } + + func append(_ data: Data) { + guard let text = String(data: data, encoding: .utf8), !text.isEmpty else { return } + lock.lock() + defer { lock.unlock() } + guard resolvedURL == nil else { return } + outputBuffer.append(text) + while let newlineIndex = outputBuffer.firstIndex(where: \.isNewline) { + let line = String(outputBuffer[.. Bool { + if webUIURL != nil { return true } + _ = semaphore.wait(timeout: .now() + timeoutSeconds) + return webUIURL != nil + } +} + enum WorkspaceShortcutMapper { /// Maps Cmd+digit workspace shortcuts to a zero-based workspace index. /// Cmd+1...Cmd+8 target fixed indices; Cmd+9 always targets the last workspace. @@ -1360,6 +1733,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent stopSessionAutosaveTimer() stopSocketListenerHealthMonitor() TerminalController.shared.stop() + VSCodeServeWebController.shared.stop() BrowserHistoryStore.shared.flushPendingSaves() if TelemetrySettings.enabledForCurrentLaunch { PostHogAnalytics.shared.flush() diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 49d634f0..e17b67c0 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -3982,6 +3982,30 @@ struct ContentView: View { ) ) } + contributions.append( + CommandPaletteCommandContribution( + commandId: "palette.vscodeServeWebStop", + title: constant("Stop VS Code Inline Server"), + subtitle: terminalPanelSubtitle, + keywords: ["vscode", "inline", "serve-web", "stop", "server"], + when: { context in + context.bool(CommandPaletteContextKeys.panelIsTerminal) + && context.bool(CommandPaletteContextKeys.terminalOpenTargetAvailable(.vscode)) + } + ) + ) + contributions.append( + CommandPaletteCommandContribution( + commandId: "palette.vscodeServeWebRestart", + title: constant("Restart VS Code Inline Server"), + subtitle: terminalPanelSubtitle, + keywords: ["vscode", "inline", "serve-web", "restart", "server"], + when: { context in + context.bool(CommandPaletteContextKeys.panelIsTerminal) + && context.bool(CommandPaletteContextKeys.terminalOpenTargetAvailable(.vscode)) + } + ) + ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFind", @@ -4326,6 +4350,14 @@ struct ContentView: View { } } } + registry.register(commandId: "palette.vscodeServeWebStop") { + stopInlineVSCodeServeWeb() + } + registry.register(commandId: "palette.vscodeServeWebRestart") { + if !restartInlineVSCodeServeWeb() { + NSSound.beep() + } + } registry.register(commandId: "palette.terminalFind") { tabManager.startSearch() } @@ -4959,6 +4991,8 @@ struct ContentView: View { case .finder: NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: directoryURL.path) return true + case .vscode: + return openFocusedDirectoryInInlineVSCode(directoryURL) default: guard let applicationURL = target.applicationURL() else { return false } let configuration = NSWorkspace.OpenConfiguration() @@ -4967,6 +5001,54 @@ struct ContentView: View { } } + private func openFocusedDirectoryInInlineVSCode(_ directoryURL: URL) -> Bool { + guard let vscodeApplicationURL = TerminalDirectoryOpenTarget.vscode.applicationURL(), + let workspace = tabManager.selectedWorkspace, + let sourcePanelId = workspace.focusedPanelId else { + return false + } + let sourceTabId = workspace.id + let tabManager = tabManager + VSCodeServeWebController.shared.ensureServeWebURL(vscodeApplicationURL: vscodeApplicationURL) { serveWebURL in + guard let serveWebURL, + let openFolderURL = VSCodeServeWebURLBuilder.openFolderURL( + baseWebUIURL: serveWebURL, + directoryPath: directoryURL.path + ) else { + NSSound.beep() + return + } + guard tabManager.newBrowserSplit( + tabId: sourceTabId, + fromPanelId: sourcePanelId, + orientation: SplitDirection.right.orientation, + insertFirst: SplitDirection.right.insertFirst, + url: openFolderURL, + focus: true + ) != nil else { + NSSound.beep() + return + } + } + return true + } + + private func stopInlineVSCodeServeWeb() { + VSCodeServeWebController.shared.stop() + } + + private func restartInlineVSCodeServeWeb() -> Bool { + guard let vscodeApplicationURL = TerminalDirectoryOpenTarget.vscode.applicationURL() else { + return false + } + VSCodeServeWebController.shared.restart(vscodeApplicationURL: vscodeApplicationURL) { serveWebURL in + if serveWebURL == nil { + NSSound.beep() + } + } + return true + } + private func focusedTerminalDirectoryURL() -> URL? { guard let workspace = tabManager.selectedWorkspace else { return nil } let rawDirectory: String = { diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index d524f4a5..547fda50 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -4755,7 +4755,8 @@ final class TerminalDirectoryOpenTargetAvailabilityTests: XCTestCase { ) -> TerminalDirectoryOpenTarget.DetectionEnvironment { TerminalDirectoryOpenTarget.DetectionEnvironment( homeDirectoryPath: homeDirectoryPath, - fileExistsAtPath: { existingPaths.contains($0) } + fileExistsAtPath: { existingPaths.contains($0) }, + isExecutableFileAtPath: { existingPaths.contains($0) } ) } @@ -4763,6 +4764,7 @@ final class TerminalDirectoryOpenTargetAvailabilityTests: XCTestCase { let env = environment( existingPaths: [ "/Applications/Visual Studio Code.app", + "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code-tunnel", "/System/Library/CoreServices/Finder.app", "/System/Applications/Utilities/Terminal.app", "/Applications/Zed Preview.app", @@ -4793,6 +4795,11 @@ final class TerminalDirectoryOpenTargetAvailabilityTests: XCTestCase { XCTAssertFalse(availableTargets.contains(.vscode)) } + func testVSCodeRequiresCodeTunnelExecutable() { + let env = environment(existingPaths: ["/Applications/Visual Studio Code.app"]) + XCTAssertFalse(TerminalDirectoryOpenTarget.vscode.isAvailable(in: env)) + } + func testITerm2DetectsLegacyBundleName() { let env = environment(existingPaths: ["/Applications/iTerm.app"]) XCTAssertTrue(TerminalDirectoryOpenTarget.iterm2.isAvailable(in: env)) @@ -4810,6 +4817,212 @@ final class TerminalDirectoryOpenTargetAvailabilityTests: XCTestCase { } } +final class VSCodeServeWebURLBuilderTests: XCTestCase { + func testExtractWebUIURLParsesServeWebOutput() { + let output = """ + * + * Visual Studio Code Server + * + Web UI available at http://127.0.0.1:5555?tkn=test-token + """ + + let url = VSCodeServeWebURLBuilder.extractWebUIURL(from: output) + XCTAssertEqual(url?.absoluteString, "http://127.0.0.1:5555?tkn=test-token") + } + + func testOpenFolderURLAppendsFolderQueryWhilePreservingToken() { + let baseURL = URL(string: "http://127.0.0.1:5555?tkn=test-token")! + + let url = VSCodeServeWebURLBuilder.openFolderURL( + baseWebUIURL: baseURL, + directoryPath: "/Users/tester/Projects/cmux" + ) + + let components = URLComponents(url: url!, resolvingAgainstBaseURL: false) + XCTAssertEqual(components?.queryItems?.first(where: { $0.name == "tkn" })?.value, "test-token") + XCTAssertEqual(components?.queryItems?.first(where: { $0.name == "folder" })?.value, "/Users/tester/Projects/cmux") + } + + func testOpenFolderURLReplacesExistingFolderQuery() { + let baseURL = URL(string: "http://127.0.0.1:5555?tkn=test-token&folder=/tmp/old")! + + let url = VSCodeServeWebURLBuilder.openFolderURL( + baseWebUIURL: baseURL, + directoryPath: "/Users/tester/New Folder" + ) + + let components = URLComponents(url: url!, resolvingAgainstBaseURL: false) + XCTAssertEqual( + components?.queryItems?.filter { $0.name == "folder" }.count, + 1 + ) + XCTAssertEqual( + components?.queryItems?.first(where: { $0.name == "folder" })?.value, + "/Users/tester/New Folder" + ) + } +} + +final class VSCodeCLILaunchConfigurationBuilderTests: XCTestCase { + func testLaunchConfigurationUsesCodeTunnelBinary() { + let appURL = URL(fileURLWithPath: "/Applications/Visual Studio Code.app", isDirectory: true) + let expectedExecutablePath = "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code-tunnel" + + let configuration = VSCodeCLILaunchConfigurationBuilder.launchConfiguration( + vscodeApplicationURL: appURL, + baseEnvironment: [:], + isExecutableAtPath: { $0 == expectedExecutablePath } + ) + + XCTAssertEqual(configuration?.executableURL.path, expectedExecutablePath) + XCTAssertEqual(configuration?.argumentsPrefix, []) + XCTAssertEqual(configuration?.environment["ELECTRON_RUN_AS_NODE"], "1") + } + + func testLaunchConfigurationMapsNodeEnvironmentVariables() { + let configuration = VSCodeCLILaunchConfigurationBuilder.launchConfiguration( + vscodeApplicationURL: URL(fileURLWithPath: "/Applications/Visual Studio Code.app", isDirectory: true), + baseEnvironment: [ + "PATH": "/usr/bin:/bin", + "NODE_OPTIONS": "--max-old-space-size=4096", + "NODE_REPL_EXTERNAL_MODULE": "module-name" + ], + isExecutableAtPath: { _ in true } + ) + + XCTAssertEqual(configuration?.environment["PATH"], "/usr/bin:/bin") + XCTAssertEqual(configuration?.environment["VSCODE_NODE_OPTIONS"], "--max-old-space-size=4096") + XCTAssertEqual(configuration?.environment["VSCODE_NODE_REPL_EXTERNAL_MODULE"], "module-name") + XCTAssertNil(configuration?.environment["NODE_OPTIONS"]) + XCTAssertNil(configuration?.environment["NODE_REPL_EXTERNAL_MODULE"]) + } + + func testLaunchConfigurationClearsStaleVSCodeNodeVariablesWhenNodeVariablesAreAbsent() { + let configuration = VSCodeCLILaunchConfigurationBuilder.launchConfiguration( + vscodeApplicationURL: URL(fileURLWithPath: "/Applications/Visual Studio Code.app", isDirectory: true), + baseEnvironment: [ + "PATH": "/usr/bin:/bin", + "VSCODE_NODE_OPTIONS": "--stale", + "VSCODE_NODE_REPL_EXTERNAL_MODULE": "stale-module" + ], + isExecutableAtPath: { _ in true } + ) + + XCTAssertEqual(configuration?.environment["PATH"], "/usr/bin:/bin") + XCTAssertNil(configuration?.environment["VSCODE_NODE_OPTIONS"]) + XCTAssertNil(configuration?.environment["VSCODE_NODE_REPL_EXTERNAL_MODULE"]) + } +} + +final class ServeWebOutputCollectorTests: XCTestCase { + func testWaitForURLReturnsFalseAfterProcessExitSignal() { + let collector = ServeWebOutputCollector() + + DispatchQueue.global().asyncAfter(deadline: .now() + 0.05) { + collector.markProcessExited() + } + + let start = Date() + let resolved = collector.waitForURL(timeoutSeconds: 1) + let elapsed = Date().timeIntervalSince(start) + + XCTAssertFalse(resolved) + XCTAssertLessThan(elapsed, 0.5) + } + + func testWaitForURLReturnsTrueWhenURLIsCollected() { + let collector = ServeWebOutputCollector() + let urlLine = "Web UI available at http://127.0.0.1:7777?tkn=test-token\n" + + DispatchQueue.global().asyncAfter(deadline: .now() + 0.05) { + collector.append(Data(urlLine.utf8)) + } + + XCTAssertTrue(collector.waitForURL(timeoutSeconds: 1)) + XCTAssertEqual(collector.webUIURL?.absoluteString, "http://127.0.0.1:7777?tkn=test-token") + } + + func testMarkProcessExitedParsesFinalURLWithoutTrailingNewline() { + let collector = ServeWebOutputCollector() + let finalChunk = "Web UI available at http://127.0.0.1:9001?tkn=final-token" + + collector.append(Data(finalChunk.utf8)) + collector.markProcessExited() + + XCTAssertTrue(collector.waitForURL(timeoutSeconds: 0.1)) + XCTAssertEqual(collector.webUIURL?.absoluteString, "http://127.0.0.1:9001?tkn=final-token") + } +} + +final class VSCodeServeWebControllerTests: XCTestCase { + func testStopDuringInFlightLaunchDoesNotDropNextGenerationCompletion() { + let firstLaunchStarted = expectation(description: "first launch started") + let firstCompletionCalled = expectation(description: "first generation completion called") + let secondCompletionCalled = expectation(description: "second generation completion called") + + let launchGate = DispatchSemaphore(value: 0) + let launchCallLock = NSLock() + var launchCallCount = 0 + + let controller = VSCodeServeWebController.makeForTesting { _, _ in + launchCallLock.lock() + launchCallCount += 1 + let callNumber = launchCallCount + launchCallLock.unlock() + + if callNumber == 1 { + firstLaunchStarted.fulfill() + _ = launchGate.wait(timeout: .now() + 1) + } + return nil + } + + let callbackLock = NSLock() + var firstGenerationCallbacks: [URL?] = [] + var secondGenerationCallbacks: [URL?] = [] + let vscodeAppURL = URL(fileURLWithPath: "/Applications/Visual Studio Code.app", isDirectory: true) + + controller.ensureServeWebURL(vscodeApplicationURL: vscodeAppURL) { url in + callbackLock.lock() + firstGenerationCallbacks.append(url) + callbackLock.unlock() + firstCompletionCalled.fulfill() + } + + wait(for: [firstLaunchStarted], timeout: 1) + controller.stop() + + controller.ensureServeWebURL(vscodeApplicationURL: vscodeAppURL) { url in + callbackLock.lock() + secondGenerationCallbacks.append(url) + callbackLock.unlock() + secondCompletionCalled.fulfill() + } + + launchGate.signal() + wait(for: [firstCompletionCalled, secondCompletionCalled], timeout: 2) + + callbackLock.lock() + let firstSnapshot = firstGenerationCallbacks + let secondSnapshot = secondGenerationCallbacks + callbackLock.unlock() + + launchCallLock.lock() + let launchCalls = launchCallCount + launchCallLock.unlock() + + XCTAssertEqual(firstSnapshot.count, 1) + if firstSnapshot.count == 1 { + XCTAssertNil(firstSnapshot[0]) + } + XCTAssertEqual(secondSnapshot.count, 1) + if secondSnapshot.count == 1 { + XCTAssertNil(secondSnapshot[0]) + } + XCTAssertEqual(launchCalls, 2) + } +} + final class BrowserSearchEngineTests: XCTestCase { func testGoogleSearchURL() throws { let url = try XCTUnwrap(BrowserSearchEngine.google.searchURL(query: "hello world")) From 58bb9bc6550942fe50b0b20f97741049eda1f3a8 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:06:45 -0800 Subject: [PATCH 017/232] Migrate all workflows from self-hosted Mac Mini to Depot runners (#730) * Migrate all workflows from self-hosted Mac Mini to Depot runners Move CI, nightly, and release workflows to depot-macos-latest. Replace zig GhosttyKit builds with pre-built xcframework downloads. Add virtual display for CI UI tests. Remove concurrency groups (ephemeral VMs don't need them). * Add per-test timeout to CI UI tests to prevent hangs on Depot SidebarResizeUITests hangs on headless Depot runners due to mouse drag simulation issues. Adding -maximum-test-execution-time-allowance 120 (matching test-depot.yml) ensures individual tests timeout after 2 min instead of blocking the entire run. * Skip SidebarResizeUITests in CI on Depot runners Mouse drag simulation hangs on headless Depot runners even with a virtual display. The per-test timeout doesn't prevent the hang either. Skip this test class in CI; it still runs fine on local machines. * Handle XCTExpectFailure in CI UI tests (exit 65 with 0 unexpected) xcodebuild exits 65 even when all failures use XCTExpectFailure. Add the same expected-failure handling from the unit test step so browser focus tests (which are expected to fail on headless runners) don't break CI. --- .github/workflows/ci.yml | 95 ++++++++++++++++++++++-------- .github/workflows/nightly.yml | 44 ++++++++------ .github/workflows/release.yml | 56 ++++++++---------- tests/test_ci_self_hosted_guard.sh | 12 ++-- 4 files changed, 127 insertions(+), 80 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29904925..c51b29a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Validate self-hosted runner guards + - name: Validate Depot runner guards run: ./tests/test_ci_self_hosted_guard.sh - name: Validate create-dmg version pinning @@ -44,12 +44,9 @@ jobs: run: bun tsc --noEmit tests: - # Never run self-hosted jobs for fork pull requests. + # Never run Depot jobs for fork pull requests (avoid billing on external PRs). if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: self-hosted - concurrency: - group: self-hosted-ci - cancel-in-progress: true + runs-on: depot-macos-latest steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -75,25 +72,48 @@ jobs: xcodebuild -version xcrun --sdk macosx --show-sdk-path - - name: Download Metal Toolchain - run: xcodebuild -downloadComponent MetalToolchain - - - name: Build GhosttyKit.xcframework + - name: Download pre-built GhosttyKit.xcframework + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail - if ! command -v zig >/dev/null 2>&1; then - if command -v brew >/dev/null 2>&1; then - brew install zig - else - echo "zig is required to build GhosttyKit.xcframework. Install zig and retry." >&2 + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 exit 1 fi - fi - (cd ghostty && zig build -Demit-xcframework=true -Demit-macos-app=false) - rm -rf GhosttyKit.xcframework - cp -R ghostty/macos/GhosttyKit.xcframework GhosttyKit.xcframework + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework + - name: Create virtual display + run: | + set -euo pipefail + echo "=== Display before ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" + echo "" + clang -framework Foundation -framework CoreGraphics \ + -o /tmp/create-virtual-display scripts/create-virtual-display.m + /tmp/create-virtual-display & + VDISPLAY_PID=$! + echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" + sleep 3 + echo "=== Display after ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" + - name: Clean DerivedData run: | # Remove stale build cache to avoid incremental build errors @@ -138,7 +158,7 @@ jobs: EXIT_CODE=$? set -e - # SwiftPM binary artifact resolution can occasionally fail on self-hosted + # SwiftPM binary artifact resolution can occasionally fail on ephemeral # runners with "Could not resolve package dependencies". Retry once after # clearing SwiftPM/DerivedData caches to recover from transient corruption. if [ "$EXIT_CODE" -ne 0 ] && echo "$OUTPUT" | grep -q "Could not resolve package dependencies"; then @@ -167,8 +187,33 @@ jobs: run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ - -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ - -disableAutomaticPackageResolution \ - -destination "platform=macOS" \ - -only-testing:cmuxUITests test + # SidebarResizeUITests hangs on headless Depot runners (mouse drag + # simulation doesn't work without a physical display, even with virtual + # display). Skip it in CI; it runs fine on local machines. + run_ui_tests() { + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" \ + -maximum-test-execution-time-allowance 120 \ + -only-testing:cmuxUITests \ + -skip-testing:cmuxUITests/SidebarResizeUITests test 2>&1 + } + + # xcodebuild exits 65 even for expected failures (XCTExpectFailure). + # Capture output and fail only if there are unexpected failures. + set +e + OUTPUT=$(run_ui_tests) + EXIT_CODE=$? + set -e + + echo "$OUTPUT" + if [ "$EXIT_CODE" -ne 0 ]; then + SUMMARY=$(echo "$OUTPUT" | grep "Executed.*tests.*with.*failures" | tail -1) + if echo "$SUMMARY" | grep -q "(0 unexpected)"; then + echo "All failures are expected, treating as pass" + else + echo "Unexpected test failures detected" + exit 1 + fi + fi diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 62e92928..64b3fd35 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -81,10 +81,7 @@ jobs: build-sign-notarize-nightly: needs: decide if: needs.decide.outputs.should_build == 'true' - runs-on: self-hosted - concurrency: - group: self-hosted-nightly - cancel-in-progress: false + runs-on: depot-macos-latest steps: - name: Checkout main uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -113,23 +110,34 @@ jobs: - name: Install build deps run: | - brew update - brew install zig npm install --global "create-dmg@${CREATE_DMG_VERSION}" - - name: Build GhosttyKit.xcframework + - name: Download pre-built GhosttyKit.xcframework + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd ghostty - zig build -Demit-xcframework=true -Demit-macos-app=false -Dxcframework-target=native -Doptimize=ReleaseFast - cd .. - rm -rf GhosttyKit.xcframework - cp -R ghostty/macos/GhosttyKit.xcframework GhosttyKit.xcframework - - - name: Clear SPM cache - run: | - rm -rf ~/Library/Caches/org.swift.swiftpm - mkdir -p ~/Library/Caches/org.swift.swiftpm - rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + set -euo pipefail + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 + exit 1 + fi + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz + test -d GhosttyKit.xcframework - name: Configure SwiftPM cache run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a704f32..57bfb154 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,7 @@ env: jobs: build-sign-notarize: - runs-on: self-hosted - concurrency: - group: self-hosted-release - cancel-in-progress: false + runs-on: depot-macos-latest steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -102,38 +99,35 @@ jobs: - name: Install build deps if: steps.guard_release_assets.outputs.skip_all != 'true' run: | - brew update - brew install zig npm install --global "create-dmg@${CREATE_DMG_VERSION}" - - name: Download Metal Toolchain - if: steps.guard_release_assets.outputs.skip_all != 'true' - run: xcodebuild -downloadComponent MetalToolchain - - - name: Build GhosttyKit.xcframework - if: steps.guard_release_assets.outputs.skip_all != 'true' - run: | - cd ghostty - zig build -Demit-xcframework=true -Demit-macos-app=false -Doptimize=ReleaseFast - cd .. - rm -rf GhosttyKit.xcframework - cp -R ghostty/macos/GhosttyKit.xcframework GhosttyKit.xcframework - - - name: Clear SPM cache - if: steps.guard_release_assets.outputs.skip_all != 'true' - run: | - rm -rf ~/Library/Caches/org.swift.swiftpm - mkdir -p ~/Library/Caches/org.swift.swiftpm - rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* - - - name: Configure SwiftPM cache + - name: Download pre-built GhosttyKit.xcframework if: steps.guard_release_assets.outputs.skip_all != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail - CACHE_DIR="${RUNNER_TEMP}/swiftpm-cache/${GITHUB_RUN_ID}" - rm -rf "$CACHE_DIR" - mkdir -p "$CACHE_DIR" - echo "SWIFTPM_CACHE_PATH=$CACHE_DIR" >> "$GITHUB_ENV" + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 + exit 1 + fi + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz + test -d GhosttyKit.xcframework - name: Derive Sparkle public key from private key if: steps.guard_release_assets.outputs.skip_all != 'true' diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index c63a3111..a05ee529 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Regression test for https://github.com/manaflow-ai/cmux/issues/385. -# Ensures self-hosted UI tests are never run for fork pull requests. +# Ensures Depot-hosted UI tests are never run for fork pull requests. set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" @@ -9,7 +9,7 @@ WORKFLOW_FILE="$ROOT_DIR/.github/workflows/ci.yml" EXPECTED_IF="if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository" if ! grep -Fq "$EXPECTED_IF" "$WORKFLOW_FILE"; then - echo "FAIL: Missing fork pull_request guard for ui-tests in $WORKFLOW_FILE" + echo "FAIL: Missing fork pull_request guard for tests in $WORKFLOW_FILE" echo "Expected line:" echo " $EXPECTED_IF" exit 1 @@ -18,12 +18,12 @@ fi if ! awk ' /^ tests:/ { in_tests=1; next } in_tests && /^ [^[:space:]]/ { in_tests=0 } - in_tests && /runs-on: self-hosted/ { saw_self_hosted=1 } + in_tests && /runs-on: depot-macos-latest/ { saw_depot=1 } in_tests && /github.event.pull_request.head.repo.full_name == github.repository/ { saw_guard=1 } - END { exit !(saw_self_hosted && saw_guard) } + END { exit !(saw_depot && saw_guard) } ' "$WORKFLOW_FILE"; then - echo "FAIL: tests block must keep both self-hosted and fork guard" + echo "FAIL: tests block must keep both depot-macos-latest runner and fork guard" exit 1 fi -echo "PASS: tests self-hosted fork guard is present" +echo "PASS: tests Depot runner fork guard is present" From aaf6a241655c19586835a346da4aab66cd71bcc1 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:07:20 -0800 Subject: [PATCH 018/232] Show GitHub star badge in mobile header (#735) Removed the `hidden md:flex` wrapper so the GitHubStarsBadge renders on all screen sizes. Made the component reusable with optional `location` and `className` props. Replaced the plain "GitHub" text link in the mobile drawer with the star badge component. --- web/app/components/github-stars.tsx | 34 +++++++++++++++++++---------- web/app/components/site-header.tsx | 15 ++----------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/web/app/components/github-stars.tsx b/web/app/components/github-stars.tsx index 1dc3adb4..45b2d626 100644 --- a/web/app/components/github-stars.tsx +++ b/web/app/components/github-stars.tsx @@ -11,7 +11,25 @@ function formatStars(count: number): string { return String(count); } -export function GitHubStarsBadge() { +const GITHUB_ICON = ( + +); + +export function GitHubStarsBadge({ + location = "stars_badge", + className, +}: { + location?: string; + className?: string; +} = {}) { const [stars, setStars] = useState(null); useEffect(() => { @@ -31,19 +49,11 @@ export function GitHubStarsBadge() { target="_blank" rel="noopener noreferrer" onClick={() => - posthog.capture("cmuxterm_github_clicked", { location: "stars_badge" }) + posthog.capture("cmuxterm_github_clicked", { location }) } - className="inline-flex items-center gap-1.5 pr-1 text-sm text-muted hover:text-foreground transition-colors animate-fade-in" + className={className ?? "inline-flex items-center gap-1.5 pr-1 text-sm text-muted hover:text-foreground transition-colors animate-fade-in"} > - + {GITHUB_ICON} {formatStars(stars)} ); diff --git a/web/app/components/site-header.tsx b/web/app/components/site-header.tsx index 81742b99..bc868280 100644 --- a/web/app/components/site-header.tsx +++ b/web/app/components/site-header.tsx @@ -1,7 +1,6 @@ "use client"; import Link from "next/link"; -import posthog from "posthog-js"; import { NavLinks } from "./nav-links"; import { DownloadButton } from "./download-button"; import { ThemeToggle } from "../theme"; @@ -58,9 +57,7 @@ export function SiteHeader({ {/* Right: GitHub stars + Download + theme + mobile */}

-
- -
+
@@ -137,15 +134,7 @@ export function SiteHeader({ > Community - posthog.capture("cmuxterm_github_clicked", { location: "mobile_drawer" })} - className="hover:text-foreground transition-colors py-1" - > - GitHub - +
From 33242f8780c70f8a9257103471289e8a8d0a5a95 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:35:50 -0800 Subject: [PATCH 019/232] Fix menubar icon invisible in light mode (#741) Set isTemplate = true on the menu bar icon image so macOS automatically renders it black in light mode and white in dark mode. Changed the glyph fill from white to black per template image convention. Closes https://github.com/manaflow-ai/cmux/issues/737 --- Sources/AppDelegate.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 2c1d7c2d..6bd619e9 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7750,6 +7750,7 @@ enum MenuBarIconRenderer { drawBadge(text: text, in: config.badgeRect, config: config) } + image.isTemplate = true return image } @@ -7778,7 +7779,7 @@ enum MenuBarIconRenderer { path.line(to: map(384.0, 369.0)) path.close() - NSColor.white.setFill() + NSColor.black.setFill() path.fill() } From c6e8d8484bcf61c7d7ff2b9957069e7512a74db8 Mon Sep 17 00:00:00 2001 From: Haruki Tosa <44115752+harukitosa@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:38:23 +0900 Subject: [PATCH 020/232] Fix exclusive-access crash in WindowDragHandleView hitTest (#736) Add early return for non-leftMouseDown events in DraggableView.hitTest to prevent re-entering SwiftUI view state during mouseMoved layout passes, which caused fatal exclusive-access violations. --- Sources/WindowDragHandleView.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/WindowDragHandleView.swift b/Sources/WindowDragHandleView.swift index d8d23f7c..15f406e9 100644 --- a/Sources/WindowDragHandleView.swift +++ b/Sources/WindowDragHandleView.swift @@ -359,6 +359,13 @@ struct WindowDragHandleView: NSViewRepresentable { override func hitTest(_ point: NSPoint) -> NSView? { let currentEvent = NSApp.currentEvent + // Fast bail-out: only claim hits for left-mouse-down events. + // For mouseMoved / mouseEntered / etc., return nil immediately + // to avoid re-entering SwiftUI view state during layout passes, + // which causes exclusive-access crashes. + guard currentEvent?.type == .leftMouseDown else { + return nil + } let shouldCapture = windowDragHandleShouldCaptureHit( point, in: self, From bd3ee68e0c387f999153f730234742235eb79da0 Mon Sep 17 00:00:00 2001 From: Morten Trydal Date: Mon, 2 Mar 2026 06:38:51 +0100 Subject: [PATCH 021/232] Fix display-pass crash from isHidden toggle in debug hit test (#698) Toggling isHidden during a display cycle calls _setHidden:setNeedsDisplay:, which posts another window-needs-display and pushes the pass count past AppKit's per-cycle limit, causing an NSException crash near resize handles. Remove the isHidden toggle from debugTopHitViewForCurrentEvent(); the hit test now returns the overlay itself when it is the topmost view, which is acceptable for debug logging purposes. --- Sources/ContentView.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index e17b67c0..944426a2 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -653,9 +653,8 @@ final class FileDropOverlayView: NSView { let themeFrame = contentView.superview else { return "-" } let pointInTheme = themeFrame.convert(currentEvent.locationInWindow, from: nil) - isHidden = true - defer { isHidden = false } - + // Don't toggle isHidden here — it triggers setNeedsDisplay which can + // exceed AppKit's display-pass limit during cursor-update display cycles. guard let hit = themeFrame.hitTest(pointInTheme) else { return "nil" } var chain: [String] = [] var current: NSView? = hit From 4d9587c3b0b0f9c7321d56f3ed9cd7cd4408870b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:40:58 -0800 Subject: [PATCH 022/232] Fix window.open() and target=_blank not opening in new tab (#693) Always open programmatic navigation (window.open, target=_blank) in a new tab. Fix insecure HTTP path to treat nil targetFrame as new-tab intent. Closes #606 Co-authored-by: lark --- Sources/Panels/BrowserPanel.swift | 38 +++++++++++-------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 24a7150a..1f4b72ad 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -3149,7 +3149,7 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { navigationAction.targetFrame?.isMainFrame != false, shouldBlockInsecureHTTPNavigation?(url) == true { let intent: BrowserInsecureHTTPNavigationIntent - if shouldOpenInNewTab { + if shouldOpenInNewTab || navigationAction.targetFrame == nil { intent = .newTab } else { intent = .currentTab @@ -3192,14 +3192,13 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { return } - // target=_blank or window.open() without explicit new-tab intent — navigate in-place. + // target=_blank or window.open() — open in a new tab. if navigationAction.targetFrame == nil, - navigationAction.request.url != nil { + let url = navigationAction.request.url { #if DEBUG - let targetURL = navigationAction.request.url?.absoluteString ?? "nil" - dlog("browser.nav.decidePolicy.action kind=loadInPlaceFromNilTarget url=\(targetURL)") + dlog("browser.nav.decidePolicy.action kind=openInNewTabFromNilTarget url=\(url.absoluteString)") #endif - browserLoadRequest(navigationAction.request, in: webView) + openInNewTab?(url) decisionHandler(.cancel) return } @@ -3306,20 +3305,16 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { } /// Returning nil tells WebKit not to open a new window. - /// Cmd+click and middle-click open in a new tab; regular target=_blank navigates in-place. + /// createWebViewWith is only called when the page requests a new window + /// (window.open(), target=_blank, etc.). Always open in a new tab. func webView( _ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures ) -> WKWebView? { - let hasRecentMiddleClickIntent = CmuxWebView.hasRecentMiddleClickIntent(for: webView) - let shouldOpenInNewTab = browserNavigationShouldOpenInNewTab( - navigationType: navigationAction.navigationType, - modifierFlags: navigationAction.modifierFlags, - buttonNumber: navigationAction.buttonNumber, - hasRecentMiddleClickIntent: hasRecentMiddleClickIntent - ) + // createWebViewWith is only called when the page requests a new window, + // so always treat as new-tab intent regardless of modifiers/button. #if DEBUG let currentEventType = NSApp.currentEvent.map { String(describing: $0.type) } ?? "nil" let currentEventButton = NSApp.currentEvent.map { String($0.buttonNumber) } ?? "nil" @@ -3328,8 +3323,7 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { "browser.nav.createWebView navType=\(navType) button=\(navigationAction.buttonNumber) " + "mods=\(navigationAction.modifierFlags.rawValue) targetNil=\(navigationAction.targetFrame == nil ? 1 : 0) " + "eventType=\(currentEventType) eventButton=\(currentEventButton) " + - "recentMiddleIntent=\(hasRecentMiddleClickIntent ? 1 : 0) " + - "openInNewTab=\(shouldOpenInNewTab ? 1 : 0)" + "openInNewTab=1" ) #endif if let url = navigationAction.request.url { @@ -3344,25 +3338,19 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { return nil } if let requestNavigation { - let intent: BrowserInsecureHTTPNavigationIntent = - shouldOpenInNewTab ? .newTab : .currentTab + let intent: BrowserInsecureHTTPNavigationIntent = .newTab #if DEBUG dlog( - "browser.nav.createWebView.action kind=requestNavigation intent=\(intent == .newTab ? "newTab" : "currentTab") " + + "browser.nav.createWebView.action kind=requestNavigation intent=newTab " + "url=\(url.absoluteString)" ) #endif requestNavigation(navigationAction.request, intent) - } else if shouldOpenInNewTab { + } else { #if DEBUG dlog("browser.nav.createWebView.action kind=openInNewTab url=\(url.absoluteString)") #endif openInNewTab?(url) - } else { -#if DEBUG - dlog("browser.nav.createWebView.action kind=loadInPlace url=\(url.absoluteString)") -#endif - browserLoadRequest(navigationAction.request, in: webView) } } return nil From a6f6485e3c3694ad5ead0fdf13a5067b590ee76d Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Mon, 2 Mar 2026 16:46:00 -0800 Subject: [PATCH 023/232] Fix Cmd+Tab activation ordering for cmux windows (#744) (#766) --- Sources/AppDelegate.swift | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 6bd619e9..9054dd74 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1496,6 +1496,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private var commandPaletteVisibilityByWindowId: [UUID: Bool] = [:] private var commandPaletteSelectionByWindowId: [UUID: Int] = [:] private var commandPaletteSnapshotByWindowId: [UUID: CommandPaletteDebugSnapshot] = [:] + private weak var lastActiveMainWindow: NSWindow? var updateViewModel: UpdateViewModel { updateController.viewModel @@ -1554,6 +1555,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } func applicationDidFinishLaunching(_ notification: Notification) { + if NSApp.activationPolicy() != .regular { + NSApp.setActivationPolicy(.regular) + } + let env = ProcessInfo.processInfo.environment let isRunningUnderXCTest = isRunningUnderXCTest(env) let telemetryEnabled = TelemetrySettings.enabledForCurrentLaunch @@ -1719,6 +1724,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tab.triggerNotificationFocusFlash(panelId: surfaceId, requiresSplit: false, shouldFocus: false) } notificationStore.markRead(forTabId: tabId, surfaceId: surfaceId) + bringMainWindowToFrontOnActivationIfNeeded() } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -6782,6 +6788,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent queue: .main ) { [weak self] note in guard let self, let window = note.object as? NSWindow else { return } + self.lastActiveMainWindow = window self.setActiveMainWindow(window) } } @@ -7141,6 +7148,36 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + private func bringMainWindowToFrontOnActivationIfNeeded() { + guard let window = frontmostKnownMainWindow() else { return } + bringToFront(window) + } + + private func frontmostKnownMainWindow() -> NSWindow? { + let directCandidates: [NSWindow?] = [lastActiveMainWindow, NSApp.keyWindow, NSApp.mainWindow] + for candidate in directCandidates where candidate != nil { + if let window = candidate, isMainTerminalWindow(window) { + return window + } + } + + if let activeManager = tabManager, + let context = mainWindowContexts.values.first(where: { $0.tabManager === activeManager }), + let window = context.window ?? windowForMainWindowId(context.windowId) { + return window + } + + if let visible = mainWindowContexts.values + .compactMap({ $0.window ?? windowForMainWindowId($0.windowId) }) + .first(where: { $0.isVisible }) { + return visible + } + + return mainWindowContexts.values + .compactMap({ $0.window ?? windowForMainWindowId($0.windowId) }) + .first + } + #if DEBUG private func recordMultiWindowNotificationOpenFailureIfNeeded( tabId: UUID, From fdc38a332643e1d7fcbd1918c36105e95a7e3ae0 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:50:34 -0800 Subject: [PATCH 024/232] Add external URL bypass rules for embedded browser opens (#768) * Add external URL bypass rules for embedded browser opens * Align open-wrapper external regex handling with app-side matcher --- CLI/cmux.swift | 14 +++ Resources/bin/open | 5 +- Sources/GhosttyTerminalView.swift | 5 + Sources/Panels/BrowserPanel.swift | 47 ++++++++ Sources/TerminalController.swift | 29 +++++ Sources/cmuxApp.swift | 28 +++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 50 +++++++++ tests/test_open_wrapper.py | 106 ++++++++++++++++++ 8 files changed, 283 insertions(+), 1 deletion(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index e83a0121..f6f0d760 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -2390,6 +2390,17 @@ struct CMUXCLI { let (workspaceOpt, argsAfterWorkspace) = parseOption(subArgs, name: "--workspace") let (windowOpt, urlArgs) = parseOption(argsAfterWorkspace, name: "--window") let url = urlArgs.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) + let respectExternalOpenRules: Bool = { + guard let raw = ProcessInfo.processInfo.environment["CMUX_RESPECT_EXTERNAL_OPEN_RULES"] else { + return false + } + switch raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { + case "1", "true", "yes", "on": + return true + default: + return false + } + }() if surfaceRaw != nil, subcommand == "open" { // Treat `browser open ` as navigate for agent-browser ergonomics. @@ -2415,6 +2426,9 @@ struct CMUXCLI { params["workspace_id"] = workspace } } + if respectExternalOpenRules { + params["respect_external_open_rules"] = true + } if let windowRaw = windowOpt { if let window = try normalizeWindowHandle(windowRaw, client: client) { params["window_id"] = window diff --git a/Resources/bin/open b/Resources/bin/open index 0b0ab639..203ba1db 100755 --- a/Resources/bin/open +++ b/Resources/bin/open @@ -437,6 +437,7 @@ if [[ -n "$settings_domain" ]]; then if [[ -n "$whitelist_raw" ]]; then load_whitelist_patterns "$whitelist_raw" fi + fi # Find cmux CLI (same directory as this script). @@ -448,13 +449,15 @@ if [[ ! -x "$CMUX_CLI" ]]; then fi # Open each URL in cmux's in-app browser; track failures individually. +# External-open pattern rules are evaluated in-app (NSRegularExpression) so +# terminal link clicks and intercepted `open` commands share one regex dialect. failed_urls=() for url in "${cmux_targets[@]}"; do if is_http_url "$url" && ! host_matches_whitelist "$url"; then failed_urls+=("$url") continue fi - "$CMUX_CLI" browser open "$url" 2>/dev/null || failed_urls+=("$url") + CMUX_RESPECT_EXTERNAL_OPEN_RULES=1 "$CMUX_CLI" browser open "$url" 2>/dev/null || failed_urls+=("$url") done # Fall back to system open for unmatched args and URLs that failed. diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index cd120bb1..303b4e44 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -1368,6 +1368,11 @@ class GhosttyApp { NSWorkspace.shared.open(url) } case let .embeddedBrowser(url): + if BrowserLinkOpenSettings.shouldOpenExternally(url) { + return performOnMain { + NSWorkspace.shared.open(url) + } + } guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return performOnMain { NSWorkspace.shared.open(url) diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 1f4b72ad..37d38fd7 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -186,6 +186,8 @@ enum BrowserLinkOpenSettings { static let browserHostWhitelistKey = "browserHostWhitelist" static let defaultBrowserHostWhitelist: String = "" + static let browserExternalOpenPatternsKey = "browserExternalOpenPatterns" + static let defaultBrowserExternalOpenPatterns: String = "" static func openTerminalLinksInCmuxBrowser(defaults: UserDefaults = .standard) -> Bool { if defaults.object(forKey: openTerminalLinksInCmuxBrowserKey) == nil { @@ -226,6 +228,38 @@ enum BrowserLinkOpenSettings { .filter { !$0.isEmpty } } + static func externalOpenPatterns(defaults: UserDefaults = .standard) -> [String] { + let raw = defaults.string(forKey: browserExternalOpenPatternsKey) ?? defaultBrowserExternalOpenPatterns + return raw + .components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty && !$0.hasPrefix("#") } + } + + static func shouldOpenExternally(_ url: URL, defaults: UserDefaults = .standard) -> Bool { + shouldOpenExternally(url.absoluteString, defaults: defaults) + } + + static func shouldOpenExternally(_ rawURL: String, defaults: UserDefaults = .standard) -> Bool { + let target = rawURL.trimmingCharacters(in: .whitespacesAndNewlines) + guard !target.isEmpty else { return false } + + for rawPattern in externalOpenPatterns(defaults: defaults) { + guard let (isRegex, value) = parseExternalPattern(rawPattern) else { continue } + if isRegex { + guard let regex = try? NSRegularExpression(pattern: value, options: [.caseInsensitive]) else { continue } + let range = NSRange(target.startIndex.. (isRegex: Bool, value: String)? { + let trimmed = rawPattern.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + + if trimmed.lowercased().hasPrefix("re:") { + let regexPattern = String(trimmed.dropFirst(3)).trimmingCharacters(in: .whitespacesAndNewlines) + guard !regexPattern.isEmpty else { return nil } + return (isRegex: true, value: regexPattern) + } + + return (isRegex: false, value: trimmed) + } } enum BrowserInsecureHTTPSettings { diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 9eefcd40..116c293c 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -5252,6 +5252,7 @@ class TerminalController { } let urlStr = v2String(params, "url") let url = urlStr.flatMap { URL(string: $0) } + let respectExternalOpenRules = v2Bool(params, "respect_external_open_rules") ?? false var result: V2CallResult = .err(code: "internal_error", message: "Failed to create browser", data: nil) v2MainSync { @@ -5259,6 +5260,34 @@ class TerminalController { result = .err(code: "not_found", message: "Workspace not found", data: nil) return } + if let url, + respectExternalOpenRules, + BrowserLinkOpenSettings.shouldOpenExternally(url) { + guard NSWorkspace.shared.open(url) else { + result = .err( + code: "external_open_failed", + message: "Failed to open URL externally", + data: ["url": url.absoluteString] + ) + return + } + let windowId = v2ResolveWindowId(tabManager: tabManager) + result = .ok([ + "window_id": v2OrNull(windowId?.uuidString), + "window_ref": v2Ref(kind: .window, uuid: windowId), + "workspace_id": ws.id.uuidString, + "workspace_ref": v2Ref(kind: .workspace, uuid: ws.id), + "pane_id": v2OrNull(nil), + "pane_ref": v2Ref(kind: .pane, uuid: nil), + "surface_id": v2OrNull(nil), + "surface_ref": v2Ref(kind: .surface, uuid: nil), + "created_split": false, + "placement_strategy": "external", + "opened_externally": true, + "url": url.absoluteString + ]) + return + } v2MaybeFocusWindow(for: tabManager) v2MaybeSelectWorkspace(tabManager, workspace: ws) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 41b8f160..9936536a 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2730,6 +2730,8 @@ struct SettingsView: View { @AppStorage(BrowserLinkOpenSettings.interceptTerminalOpenCommandInCmuxBrowserKey) private var interceptTerminalOpenCommandInCmuxBrowser = BrowserLinkOpenSettings.initialInterceptTerminalOpenCommandInCmuxBrowserValue() @AppStorage(BrowserLinkOpenSettings.browserHostWhitelistKey) private var browserHostWhitelist = BrowserLinkOpenSettings.defaultBrowserHostWhitelist + @AppStorage(BrowserLinkOpenSettings.browserExternalOpenPatternsKey) + private var browserExternalOpenPatterns = BrowserLinkOpenSettings.defaultBrowserExternalOpenPatterns @AppStorage(BrowserInsecureHTTPSettings.allowlistKey) private var browserInsecureHTTPAllowlist = BrowserInsecureHTTPSettings.defaultAllowlistText @AppStorage(NotificationBadgeSettings.dockBadgeEnabledKey) private var notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled @AppStorage(QuitWarningSettings.warnBeforeQuitKey) private var warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit @@ -3354,6 +3356,31 @@ struct SettingsView: View { .padding(.horizontal, 16) .padding(.bottom, 12) } + + SettingsCardDivider() + + VStack(alignment: .leading, spacing: 6) { + SettingsCardRow( + "URLs to Always Open Externally", + subtitle: "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\.example\\.com/(billing|usage))." + ) { + EmptyView() + } + + TextEditor(text: $browserExternalOpenPatterns) + .font(.system(.body, design: .monospaced)) + .frame(minHeight: 60, maxHeight: 120) + .scrollContentBackground(.hidden) + .padding(6) + .background(Color(nsColor: .controlBackgroundColor)) + .cornerRadius(6) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Color(nsColor: .separatorColor), lineWidth: 0.5) + ) + .padding(.horizontal, 16) + .padding(.bottom, 12) + } } SettingsCardDivider() @@ -3602,6 +3629,7 @@ struct SettingsView: View { openTerminalLinksInCmuxBrowser = BrowserLinkOpenSettings.defaultOpenTerminalLinksInCmuxBrowser interceptTerminalOpenCommandInCmuxBrowser = BrowserLinkOpenSettings.defaultInterceptTerminalOpenCommandInCmuxBrowser browserHostWhitelist = BrowserLinkOpenSettings.defaultBrowserHostWhitelist + browserExternalOpenPatterns = BrowserLinkOpenSettings.defaultBrowserExternalOpenPatterns browserInsecureHTTPAllowlist = BrowserInsecureHTTPSettings.defaultAllowlistText browserInsecureHTTPAllowlistDraft = BrowserInsecureHTTPSettings.defaultAllowlistText notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 547fda50..7f5ddb4c 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -7935,6 +7935,56 @@ final class BrowserLinkOpenSettingsTests: XCTestCase { defaults.set(true, forKey: BrowserLinkOpenSettings.openTerminalLinksInCmuxBrowserKey) XCTAssertTrue(BrowserLinkOpenSettings.initialInterceptTerminalOpenCommandInCmuxBrowserValue(defaults: defaults)) } + + func testExternalOpenPatternsDefaultToEmpty() { + XCTAssertTrue(BrowserLinkOpenSettings.externalOpenPatterns(defaults: defaults).isEmpty) + } + + func testExternalOpenLiteralPatternMatchesCaseInsensitively() { + defaults.set("openai.com/account/usage", forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey) + XCTAssertTrue( + BrowserLinkOpenSettings.shouldOpenExternally( + "https://platform.OPENAI.com/account/usage", + defaults: defaults + ) + ) + } + + func testExternalOpenRegexPatternMatchesCaseInsensitively() { + defaults.set( + "re:^https?://[^/]*\\.example\\.com/(billing|usage)", + forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey + ) + XCTAssertTrue( + BrowserLinkOpenSettings.shouldOpenExternally( + "https://FOO.example.com/BILLING", + defaults: defaults + ) + ) + } + + func testExternalOpenRegexPatternSupportsDigitCharacterClass() { + defaults.set( + "re:^https://example\\.com/usage/\\d+$", + forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey + ) + XCTAssertTrue( + BrowserLinkOpenSettings.shouldOpenExternally( + "https://example.com/usage/42", + defaults: defaults + ) + ) + } + + func testExternalOpenPatternsIgnoreInvalidRegexEntries() { + defaults.set("re:(\nexample.com", forKey: BrowserLinkOpenSettings.browserExternalOpenPatternsKey) + XCTAssertTrue( + BrowserLinkOpenSettings.shouldOpenExternally( + "https://example.com/path", + defaults: defaults + ) + ) + } } final class TerminalOpenURLTargetResolutionTests: XCTestCase { diff --git a/tests/test_open_wrapper.py b/tests/test_open_wrapper.py index e54f134f..b2b98a51 100755 --- a/tests/test_open_wrapper.py +++ b/tests/test_open_wrapper.py @@ -33,6 +33,7 @@ def run_wrapper( intercept_setting: str | None, legacy_open_setting: str | None = None, whitelist: str | None, + external_patterns: str | None = None, fail_urls: list[str] | None = None, local_files: list[str] | None = None, python_bin: str | None = None, @@ -87,6 +88,13 @@ case "$key" in fi exit 1 ;; + browserExternalOpenPatterns) + if [[ "${FAKE_DEFAULTS_EXTERNAL_PATTERNS+x}" == "x" ]]; then + printf '%s' "$FAKE_DEFAULTS_EXTERNAL_PATTERNS" + exit 0 + fi + exit 1 + ;; *) exit 1 ;; @@ -148,6 +156,11 @@ exit 0 else: env["FAKE_DEFAULTS_WHITELIST"] = whitelist + if external_patterns is None: + env.pop("FAKE_DEFAULTS_EXTERNAL_PATTERNS", None) + else: + env["FAKE_DEFAULTS_EXTERNAL_PATTERNS"] = external_patterns + if fail_urls: env["FAKE_CMUX_FAIL_URLS"] = ",".join(fail_urls) else: @@ -226,6 +239,95 @@ def test_whitelist_match_routes_to_cmux(failures: list[str]) -> None: expect(cmux_log == [f"browser open {url}"], f"whitelist match: unexpected cmux log {cmux_log}", failures) +def test_external_literal_pattern_is_deferred_to_app(failures: list[str]) -> None: + url = "https://platform.openai.com/account/usage" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="", + external_patterns="platform.openai.com/account/usage", + ) + expect(code == 0, f"external literal deferred: wrapper exited {code}: {stderr}", failures) + expect( + cmux_log == [f"browser open {url}"], + f"external literal deferred: expected wrapper to pass URL to cmux, got {cmux_log}", + failures, + ) + expect( + open_log == [], + f"external literal deferred: system open should not be called by wrapper, got {open_log}", + failures, + ) + + +def test_external_regex_pattern_is_deferred_to_app(failures: list[str]) -> None: + url = "https://foo.example.com/billing" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="*.example.com", + external_patterns=r"re:^https?://[^/]*\.example\.com/(billing|usage)", + ) + expect(code == 0, f"external regex deferred: wrapper exited {code}: {stderr}", failures) + expect( + cmux_log == [f"browser open {url}"], + f"external regex deferred: expected wrapper to pass URL to cmux, got {cmux_log}", + failures, + ) + expect( + open_log == [], + f"external regex deferred: system open should not be called by wrapper, got {open_log}", + failures, + ) + + +def test_external_regex_with_icu_features_is_deferred_to_app(failures: list[str]) -> None: + url = "https://example.com/usage/42" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="example.com", + external_patterns=r"re:^https://example\.com/usage/\d+$", + ) + expect(code == 0, f"external regex icu deferred: wrapper exited {code}: {stderr}", failures) + expect( + cmux_log == [f"browser open {url}"], + f"external regex icu deferred: expected wrapper to pass URL to cmux, got {cmux_log}", + failures, + ) + expect( + open_log == [], + f"external regex icu deferred: system open should not be called by wrapper, got {open_log}", + failures, + ) + + +def test_external_invalid_regex_is_ignored_silently(failures: list[str]) -> None: + url = "https://example.com/path" + open_log, cmux_log, code, stderr = run_wrapper( + args=[url], + intercept_setting="1", + whitelist="", + external_patterns=r"re:[unclosed", + ) + expect(code == 0, f"external invalid regex: wrapper exited {code}: {stderr}", failures) + expect( + cmux_log == [f"browser open {url}"], + f"external invalid regex: expected cmux open for {url}, got {cmux_log}", + failures, + ) + expect( + open_log == [], + f"external invalid regex: expected no system open calls, got {open_log}", + failures, + ) + expect( + "invalid regular expression" not in stderr.lower(), + f"external invalid regex: stderr should stay clean, got {stderr!r}", + failures, + ) + + def test_partial_failures_only_fallback_failed_urls(failures: list[str]) -> None: good = "https://api.example.com" failed = "https://fail.example.com" @@ -468,6 +570,10 @@ def main() -> int: test_toggle_disabled_case_insensitive_passthrough(failures) test_whitelist_miss_passthrough(failures) test_whitelist_match_routes_to_cmux(failures) + test_external_literal_pattern_is_deferred_to_app(failures) + test_external_regex_pattern_is_deferred_to_app(failures) + test_external_regex_with_icu_features_is_deferred_to_app(failures) + test_external_invalid_regex_is_ignored_silently(failures) test_partial_failures_only_fallback_failed_urls(failures) test_legacy_toggle_fallback_passthrough(failures) test_legacy_toggle_fallback_case_insensitive_passthrough(failures) From b6163ccfad704b23d2ed674e2017c695683fa895 Mon Sep 17 00:00:00 2001 From: Jose Masri <58571583+josemasri@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:00:00 -0600 Subject: [PATCH 025/232] Support pasting clipboard images in terminal (#562) * Support pasting clipboard images as file paths in terminal When the macOS clipboard contains only image data (e.g. from Cmd+Ctrl+Shift+4 screenshot) and no text, Cmd+V now saves the image as a temporary PNG file and pastes the file path into the terminal. This allows CLI tools like Claude Code to receive pasted images. The pasteboard heuristic only intercepts when there is image data (TIFF/PNG) and no text/HTML/RTF, so normal text paste is unaffected. Images over 10 MB are skipped and fall through to default behavior. Closes #457 Co-Authored-By: Claude Opus 4.6 * Fix clipboard image paste: collision-free filenames and pasteAsPlainText validation - Add UUID suffix to temp filenames to prevent overwrites when pasting images multiple times in the same second - Only enable Paste menu (not Paste as Plain Text) for image-only clipboard, since pasteAsPlainText has no image-path handling Co-Authored-By: Claude Opus 4.6 * Shell-escape pasted image path before sending to terminal Use escapeDropForShell on the clipboard image temp path, consistent with how drag/drop paths are escaped, to avoid issues with shell-special characters in the path. Co-Authored-By: Claude Opus 4.6 * Add docstrings to paste and validation functions Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Jose Masri Co-authored-by: Claude Opus 4.6 Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> --- Sources/GhosttyTerminalView.swift | 78 ++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 303b4e44..dba744af 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2708,20 +2708,96 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { _ = performBindingAction("copy_to_clipboard") } + // MARK: - Clipboard image paste + + private static let maxClipboardImageSize = 10 * 1024 * 1024 // 10 MB + + /// Quick check: does the clipboard have image data and no text? + private static func clipboardHasImageOnly() -> Bool { + let pb = NSPasteboard.general + let types = pb.types ?? [] + let hasText = types.contains(.string) || types.contains(.html) + || types.contains(.rtf) || types.contains(.rtfd) + if hasText { return false } + return types.contains(.tiff) || types.contains(.png) + } + + /// When the clipboard contains only image data (no text/HTML), saves it as + /// a temporary PNG file and returns the file path. Returns nil if the + /// clipboard contains text or no image. + private static func saveClipboardImageIfNeeded() -> String? { + let pb = NSPasteboard.general + let types = pb.types ?? [] + + // If pasteboard has text/HTML, this is a normal copy — let Ghostty handle it. + let hasText = types.contains(.string) || types.contains(.html) + || types.contains(.rtf) || types.contains(.rtfd) + if hasText { return nil } + + // Check for image types (TIFF from screenshots, PNG from some tools). + guard types.contains(.tiff) || types.contains(.png) else { return nil } + guard let image = NSImage(pasteboard: pb), + let tiffData = image.tiffRepresentation, + let bitmap = NSBitmapImageRep(data: tiffData), + let pngData = bitmap.representation(using: .png, properties: [:]) else { return nil } + + guard pngData.count <= maxClipboardImageSize else { +#if DEBUG + dlog("terminal.paste.image.rejected reason=tooLarge bytes=\(pngData.count)") +#endif + return nil + } + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd-HHmmss" + formatter.locale = Locale(identifier: "en_US_POSIX") + let timestamp = formatter.string(from: Date()) + let filename = "clipboard-\(timestamp)-\(UUID().uuidString.prefix(8)).png" + let path = (NSTemporaryDirectory() as NSString).appendingPathComponent(filename) + + do { + try pngData.write(to: URL(fileURLWithPath: path)) + } catch { +#if DEBUG + dlog("terminal.paste.image.writeFailed error=\(error.localizedDescription)") +#endif + return nil + } + + return path + } + + /// Pastes clipboard content into the terminal. If the clipboard contains only + /// image data, saves it as a temporary PNG and pastes the shell-escaped file path. @IBAction func paste(_ sender: Any?) { + // When the clipboard contains only image data (e.g. from Cmd+Ctrl+Shift+4 + // screenshot), save it as a temporary PNG and paste the file path so that + // CLI tools like Claude Code can accept the image. + if let path = Self.saveClipboardImageIfNeeded() { +#if DEBUG + dlog("terminal.paste.image path=\(path)") +#endif + terminalSurface?.sendText(Self.escapeDropForShell(path)) + return + } _ = performBindingAction("paste_from_clipboard") } + /// Pastes clipboard text as plain text, stripping any rich formatting. @IBAction func pasteAsPlainText(_ sender: Any?) { _ = performBindingAction("paste_from_clipboard") } + /// Validates whether edit menu items (copy, paste, split) should be enabled. func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { switch item.action { case #selector(copy(_:)): guard let surface = surface else { return false } return ghostty_surface_has_selection(surface) - case #selector(paste(_:)), #selector(pasteAsPlainText(_:)): + case #selector(paste(_:)): + return GhosttyPasteboardHelper.hasString(for: GHOSTTY_CLIPBOARD_STANDARD) + || Self.clipboardHasImageOnly() + case #selector(pasteAsPlainText(_:)): return GhosttyPasteboardHelper.hasString(for: GHOSTTY_CLIPBOARD_STANDARD) case #selector(splitHorizontally(_:)), #selector(splitVertically(_:)): return canSplitCurrentSurface() From b3f6f8cfd705e64e69c604674ed5d94fa4f3fe53 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:50:27 -0800 Subject: [PATCH 026/232] Add macOS compatibility CI: unit tests + smoke test on macos-14/15 (#769) * Add macOS compatibility CI: unit tests + smoke test on macos-14/15 New workflow runs on GitHub-hosted macos-14 and macos-15 runners (matrix strategy). Each run: unit tests via cmux-unit scheme, then a smoke test that builds the app, launches it, sends a command via the socket, and verifies it stays alive for 15 seconds. * Select latest Xcode on runner (fix macos-14 Swift tools version) macos-14 runners default to Xcode 15.4, but sentry-cocoa needs Swift tools version 6.0 (Xcode 16+). Pick the latest Xcode_*.app instead of the default symlink. * Launch app binary directly in smoke test for better CI compatibility Using `open` can fail silently on CI runners. Launch the binary directly with env vars set, capture stdout/stderr, and add process health checks with diagnostic output (debug log tail, crash reports) on failure. --- .github/workflows/ci-macos-compat.yml | 165 ++++++++++++++++++++++++++ scripts/smoke-test-ci.sh | 144 ++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 .github/workflows/ci-macos-compat.yml create mode 100755 scripts/smoke-test-ci.sh diff --git a/.github/workflows/ci-macos-compat.yml b/.github/workflows/ci-macos-compat.yml new file mode 100644 index 00000000..4bcab6b0 --- /dev/null +++ b/.github/workflows/ci-macos-compat.yml @@ -0,0 +1,165 @@ +name: macOS Compatibility + +on: + push: + branches: + - main + pull_request: + +jobs: + compat-tests: + # Only run for the repo itself, not forks (GhosttyKit download needs repo access). + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + strategy: + fail-fast: false + matrix: + os: [macos-14, macos-15] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + submodules: recursive + + - name: Select Xcode + run: | + set -euo pipefail + # Pick the latest Xcode installed on the runner. GitHub-hosted macos-14 + # defaults to Xcode 15.4, but the project needs Xcode 16+ (Swift tools + # version 6.0 required by sentry-cocoa). + XCODE_APP="$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort | tail -n 1)" + if [ -z "$XCODE_APP" ]; then + XCODE_APP="/Applications/Xcode.app" + fi + XCODE_DIR="$XCODE_APP/Contents/Developer" + if [ ! -d "$XCODE_DIR" ]; then + echo "No Xcode found under /Applications" >&2 + exit 1 + fi + echo "Selected: $XCODE_APP" + echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" + export DEVELOPER_DIR="$XCODE_DIR" + xcodebuild -version + xcrun --sdk macosx --show-sdk-path + sw_vers + + - name: Download pre-built GhosttyKit.xcframework + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 + exit 1 + fi + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz + test -d GhosttyKit.xcframework + + - name: Clean DerivedData + run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + + - name: Resolve Swift packages + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + rm -rf "$SOURCE_PACKAGES_DIR" + mkdir -p "$SOURCE_PACKAGES_DIR" + + for attempt in 1 2 3; do + if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -resolvePackageDependencies; then + exit 0 + fi + if [ "$attempt" -eq 3 ]; then + echo "Failed to resolve Swift packages after 3 attempts" >&2 + exit 1 + fi + echo "Package resolution failed on attempt $attempt, retrying..." + sleep $((attempt * 5)) + done + + - name: Run unit tests + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + run_unit_tests() { + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" test 2>&1 + } + + set +e + OUTPUT=$(run_unit_tests) + EXIT_CODE=$? + set -e + + # SwiftPM binary artifact resolution can occasionally fail on ephemeral + # runners. Retry once after clearing caches. + if [ "$EXIT_CODE" -ne 0 ] && echo "$OUTPUT" | grep -q "Could not resolve package dependencies"; then + echo "SwiftPM package resolution failed, clearing caches and retrying once" + rm -rf ~/Library/Caches/org.swift.swiftpm + mkdir -p ~/Library/Caches/org.swift.swiftpm + rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + set +e + OUTPUT=$(run_unit_tests) + EXIT_CODE=$? + set -e + fi + + echo "$OUTPUT" + if [ "$EXIT_CODE" -ne 0 ]; then + SUMMARY=$(echo "$OUTPUT" | grep "Executed.*tests.*with.*failures" | tail -1) + if echo "$SUMMARY" | grep -q "(0 unexpected)"; then + echo "All failures are expected, treating as pass" + else + echo "Unexpected test failures detected" + exit 1 + fi + fi + + - name: Create virtual display + run: | + set -euo pipefail + echo "=== Display before ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(no display info)" + echo "" + clang -framework Foundation -framework CoreGraphics \ + -o /tmp/create-virtual-display scripts/create-virtual-display.m + /tmp/create-virtual-display & + VDISPLAY_PID=$! + echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" + sleep 3 + echo "=== Display after ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(no display info)" + + - name: Build app for smoke test + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" build + + - name: Smoke test + run: | + set -euo pipefail + chmod +x scripts/smoke-test-ci.sh + scripts/smoke-test-ci.sh diff --git a/scripts/smoke-test-ci.sh b/scripts/smoke-test-ci.sh new file mode 100755 index 00000000..60583bc5 --- /dev/null +++ b/scripts/smoke-test-ci.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# Smoke test for CI: launch the app, send a command, verify it stays alive for 15 seconds. +set -euo pipefail + +SOCKET_PATH="/tmp/cmux-debug.sock" +STABILITY_WAIT=15 + +echo "=== Smoke Test ===" + +# --- Find the built app --- +APP=$(find ~/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmux DEV.app" -print -quit 2>/dev/null || true) +if [ -z "$APP" ]; then + echo "ERROR: Built app not found in DerivedData" + exit 1 +fi +echo "App: $APP" +BINARY="$APP/Contents/MacOS/cmux DEV" +if [ ! -x "$BINARY" ]; then + echo "ERROR: App binary not found or not executable: $BINARY" + exit 1 +fi + +# --- Clean up stale socket and any existing instances --- +rm -f "$SOCKET_PATH" +pkill -x "cmux DEV" 2>/dev/null || true +sleep 1 + +# --- Launch the app directly (not via `open`, which can silently fail on CI) --- +echo "Launching app..." +CMUX_SOCKET_MODE=allowAll CMUX_UI_TEST_MODE=1 "$BINARY" > /tmp/cmux-smoke-stdout.log 2>&1 & +APP_PID=$! +echo "App PID: $APP_PID" + +# --- Verify process is alive after 2s --- +sleep 2 +if ! kill -0 "$APP_PID" 2>/dev/null; then + echo "ERROR: App exited immediately after launch" + echo "--- stdout/stderr ---" + cat /tmp/cmux-smoke-stdout.log 2>/dev/null | tail -50 || true + echo "--- debug log ---" + tail -50 /tmp/cmux-debug.log 2>/dev/null || true + echo "--- crash reports ---" + ls -lt ~/Library/Logs/DiagnosticReports/*cmux* 2>/dev/null | head -5 || echo "(none)" + exit 1 +fi + +# --- Wait for socket (up to 30s) --- +echo "Waiting for socket at $SOCKET_PATH..." +SOCKET_READY=false +for i in $(seq 1 60); do + if [ -S "$SOCKET_PATH" ]; then + echo "Socket ready after $((i / 2))s" + SOCKET_READY=true + break + fi + # Check if process died while waiting + if ! kill -0 "$APP_PID" 2>/dev/null; then + echo "ERROR: App crashed while waiting for socket" + echo "--- stdout/stderr ---" + cat /tmp/cmux-smoke-stdout.log 2>/dev/null | tail -50 || true + echo "--- debug log ---" + tail -50 /tmp/cmux-debug.log 2>/dev/null || true + exit 1 + fi + sleep 0.5 +done +if [ "$SOCKET_READY" != "true" ]; then + echo "ERROR: Socket not ready after 30s" + echo "--- stdout/stderr ---" + cat /tmp/cmux-smoke-stdout.log 2>/dev/null | tail -30 || true + echo "--- debug log ---" + tail -30 /tmp/cmux-debug.log 2>/dev/null || true + ls -la /tmp/cmux-debug* 2>/dev/null || true + pgrep -la "cmux" || echo "No cmux processes found" + exit 1 +fi + +# --- Ping the socket --- +echo "Pinging socket..." +PING_RESPONSE=$(python3 -c " +import socket +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.connect('$SOCKET_PATH') +s.settimeout(5.0) +s.sendall(b'ping\n') +data = s.recv(1024).decode().strip() +s.close() +print(data) +") +echo "Ping response: $PING_RESPONSE" +if [ "$PING_RESPONSE" != "PONG" ]; then + echo "ERROR: Expected PONG, got: $PING_RESPONSE" + exit 1 +fi + +# --- Send a command to the terminal --- +echo "Sending 'time' command to terminal..." +SEND_RESPONSE=$(python3 -c " +import socket +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.connect('$SOCKET_PATH') +s.settimeout(5.0) +s.sendall(b'send time\\\n\n') +data = s.recv(1024).decode().strip() +s.close() +print(data) +") +echo "Send response: $SEND_RESPONSE" + +# --- Wait and verify stability --- +echo "Waiting ${STABILITY_WAIT}s to verify stability..." +sleep "$STABILITY_WAIT" + +if ! kill -0 "$APP_PID" 2>/dev/null; then + echo "ERROR: App crashed during ${STABILITY_WAIT}s stability check" + echo "--- stdout/stderr ---" + cat /tmp/cmux-smoke-stdout.log 2>/dev/null | tail -30 || true + echo "--- debug log ---" + tail -30 /tmp/cmux-debug.log 2>/dev/null || true + exit 1 +fi + +# --- Final ping --- +FINAL_PING=$(python3 -c " +import socket +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.connect('$SOCKET_PATH') +s.settimeout(5.0) +s.sendall(b'ping\n') +data = s.recv(1024).decode().strip() +s.close() +print(data) +") +echo "Final ping: $FINAL_PING" +if [ "$FINAL_PING" != "PONG" ]; then + echo "ERROR: App not responsive after ${STABILITY_WAIT}s" + exit 1 +fi + +echo "=== Smoke test passed ===" + +# --- Cleanup --- +kill "$APP_PID" 2>/dev/null || true +wait "$APP_PID" 2>/dev/null || true From 682a57d7db1afe5d5c5a6d088b325147b77ee590 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:06:50 -0800 Subject: [PATCH 027/232] Add workspace-churn typing lag regression and fix (#767) * Add workspace-churn typing lag regression and fix * Fix CI build for debug stress split calls * Stabilize lag regression gate for low baseline latency --- .github/workflows/ci.yml | 42 ++ Sources/AppDelegate.swift | 329 ++++++++++++++- tests/test_workspace_churn_up_arrow_lag.py | 466 +++++++++++++++++++++ 3 files changed, 828 insertions(+), 9 deletions(-) create mode 100755 tests/test_workspace_churn_up_arrow_lag.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c51b29a3..7c822148 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,3 +217,45 @@ jobs: exit 1 fi fi + + - name: Run workspace churn typing-lag regression + run: | + set -euo pipefail + + APP="$(find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Debug/cmux DEV.app" -print -quit)" + if [ -z "${APP:-}" ] || [ ! -d "$APP" ]; then + echo "cmux DEV.app not found in DerivedData" >&2 + exit 1 + fi + + TAG="ci-lag" + SOCK="/tmp/cmux-debug-${TAG}.sock" + BUNDLE_ID="$( + /usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Contents/Info.plist" 2>/dev/null \ + || echo 'com.cmuxterm.app.debug' + )" + + pkill -x "cmux DEV" || true + rm -f "$SOCK" "/tmp/cmux-${TAG}.sock" || true + defaults write "$BUNDLE_ID" socketControlMode -string full >/dev/null 2>&1 || true + + CMUX_TAG="$TAG" CMUX_SOCKET_PATH="$SOCK" CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/tmp/cmux-ci-lag.log 2>&1 & + APP_PID=$! + trap 'kill "$APP_PID" >/dev/null 2>&1 || true' EXIT + + for _ in {1..240}; do + [ -S "$SOCK" ] && break + sleep 0.25 + done + [ -S "$SOCK" ] || { echo "Socket not ready at $SOCK" >&2; exit 1; } + + CMUX_SOCKET_PATH="$SOCK" \ + CMUX_LAG_MAX_P95_RATIO=1.70 \ + CMUX_LAG_MAX_AVG_RATIO=1.70 \ + CMUX_LAG_MIN_BASELINE_P95_MS_FOR_RATIO=6.0 \ + CMUX_LAG_MIN_BASELINE_AVG_MS_FOR_RATIO=4.0 \ + CMUX_LAG_MAX_P95_DELTA_MS=20.0 \ + CMUX_LAG_MAX_AVG_DELTA_MS=12.0 \ + CMUX_LAG_MAX_CHURN_P95_MS=35 \ + CMUX_LAG_KEY_EVENTS=180 \ + python3 tests/test_workspace_churn_up_arrow_lag.py diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 9054dd74..b2bd485e 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -3684,6 +3684,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) #endif guard let context else { return tabManager } + let alreadyActive = + tabManager === context.tabManager + && sidebarState === context.sidebarState + && sidebarSelectionState === context.sidebarSelectionState + if alreadyActive { +#if DEBUG + dlog( + "shortcut.sync.post source=\(source) beforeMgr=\(beforeManagerToken) afterMgr=\(debugManagerToken(tabManager)) chosen={\(debugContextToken(context))} nochange=1 \(debugShortcutRouteSnapshot())" + ) +#endif + return context.tabManager + } if let window = context.window ?? windowForMainWindowId(context.windowId) { setActiveMainWindow(window) } else { @@ -4483,6 +4495,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent #if DEBUG private let debugColorWorkspaceTitlePrefix = "Debug Color - " + private let debugPerfWorkspaceTitlePrefix = "Debug Perf - " + private var debugStressWorkspaceCreationInProgress = false + private var debugStressLagProbeEnabled = false + private let debugStressWorkspaceCount = 20 + private let debugStressPaneCount = 4 + private let debugStressTabsPerPane = 4 + private let debugStressYieldInterval = 4 @objc func openDebugScrollbackTab(_ sender: Any?) { guard let tabManager else { return } @@ -4533,6 +4552,280 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + @objc func openDebugStressWorkspacesWithLoadedSurfaces(_ sender: Any?) { + guard !debugStressWorkspaceCreationInProgress else { return } + guard let tabManager else { return } + + debugStressLagProbeEnabled = true + debugStressWorkspaceCreationInProgress = true + Task { @MainActor [weak self] in + guard let self else { return } + defer { self.debugStressWorkspaceCreationInProgress = false } + + let totalStart = ProcessInfo.processInfo.systemUptime + let originalSelectedWorkspaceId = tabManager.selectedTabId + var created: [Workspace] = [] + created.reserveCapacity(self.debugStressWorkspaceCount) + var layoutFailures = 0 + var cumulativeWorkspaceMs: Double = 0 + var slowWorkspaceCount = 0 + var worstWorkspaceMs: Double = 0 + + dlog( + "stress.setup.start workspaces=\(self.debugStressWorkspaceCount) panes=\(self.debugStressPaneCount) " + + "tabsPerPane=\(self.debugStressTabsPerPane) lagProbe=1" + ) + + for index in 0..= 35 { + slowWorkspaceCount += 1 + } + + if workspaceMs >= 35 || ((index + 1) % 5 == 0) { + let pending = self.pendingDebugTerminalSurfaceCount(in: created) + dlog( + "stress.setup.workspace idx=\(index + 1)/\(self.debugStressWorkspaceCount) " + + "ms=\(String(format: "%.2f", workspaceMs)) failures=\(layoutFailures) pending=\(pending)" + ) + } + + if ((index + 1) % self.debugStressYieldInterval) == 0 { + await Task.yield() + } + } + + let creationElapsedMs = (ProcessInfo.processInfo.systemUptime - totalStart) * 1000.0 + let primeStats = await self.primeDebugStressWorkspacesForSurfaceLoad(created) + // Avoid synchronous "load all surfaces" waiting in this command path. + // Waiting for every background surface to be ready creates sustained + // main-actor churn and can starve typing responsiveness. + let loadStats = DebugStressSurfaceLoadStats( + pendingSurfaces: self.pendingDebugTerminalSurfaceCount(in: created), + attempts: 0, + elapsedMs: 0 + ) + let totalElapsedMs = (ProcessInfo.processInfo.systemUptime - totalStart) * 1000.0 + let avgWorkspaceMs = created.isEmpty ? 0 : (cumulativeWorkspaceMs / Double(created.count)) + let expectedSurfaceCount = self.debugStressWorkspaceCount + * self.debugStressPaneCount + * self.debugStressTabsPerPane + if let originalSelectedWorkspaceId, + tabManager.tabs.contains(where: { $0.id == originalSelectedWorkspaceId }) { + tabManager.selectedTabId = originalSelectedWorkspaceId + } + + dlog( + "stress.setup.done createMs=\(String(format: "%.2f", creationElapsedMs)) " + + "primeMs=\(String(format: "%.2f", primeStats.elapsedMs)) primedTabs=\(primeStats.activatedTabs) " + + "waitMs=\(String(format: "%.2f", loadStats.elapsedMs)) totalMs=\(String(format: "%.2f", totalElapsedMs)) " + + "workspaceAvgMs=\(String(format: "%.2f", avgWorkspaceMs)) workspaceWorstMs=\(String(format: "%.2f", worstWorkspaceMs)) " + + "workspaceSlowCount=\(slowWorkspaceCount) waitAttempts=\(loadStats.attempts) " + + "pendingSurfaces=\(loadStats.pendingSurfaces) expectedSurfaces=\(expectedSurfaceCount)" + ) + + NSLog( + "Debug stress workspaces: created=%d panesPerWorkspace=%d tabsPerPane=%d expectedSurfaces=%d layoutFailures=%d pendingSurfaces=%d createMs=%.2f primeMs=%.2f primedTabs=%d waitMs=%.2f totalMs=%.2f workspaceAvgMs=%.2f workspaceWorstMs=%.2f waitAttempts=%d", + self.debugStressWorkspaceCount, + self.debugStressPaneCount, + self.debugStressTabsPerPane, + expectedSurfaceCount, + layoutFailures, + loadStats.pendingSurfaces, + creationElapsedMs, + primeStats.elapsedMs, + primeStats.activatedTabs, + loadStats.elapsedMs, + totalElapsedMs, + avgWorkspaceMs, + worstWorkspaceMs, + loadStats.attempts + ) + } + } + + private struct DebugStressSurfacePrimeStats { + let activatedTabs: Int + let elapsedMs: Double + } + + private func primeDebugStressWorkspacesForSurfaceLoad( + _ workspaces: [Workspace] + ) async -> DebugStressSurfacePrimeStats { + guard !workspaces.isEmpty else { + return DebugStressSurfacePrimeStats(activatedTabs: 0, elapsedMs: 0) + } + + let primeStart = ProcessInfo.processInfo.systemUptime + var activatedTabs = 0 + + for (index, workspace) in workspaces.enumerated() { + activatedTabs += workspace.panels.values.reduce(into: 0) { count, panel in + if panel is TerminalPanel { + count += 1 + } + } + + if (index + 1) % debugStressYieldInterval == 0 || index == workspaces.count - 1 { + dlog( + "stress.setup.mount idx=\(index + 1)/\(workspaces.count) activatedTabs=\(activatedTabs)" + ) + await Task.yield() + } + } + + let elapsedMs = (ProcessInfo.processInfo.systemUptime - primeStart) * 1000.0 + return DebugStressSurfacePrimeStats(activatedTabs: activatedTabs, elapsedMs: elapsedMs) + } + + private func configureDebugStressWorkspaceLayout( + _ workspace: Workspace, + paneCount: Int, + tabsPerPane: Int + ) async -> Bool { + guard let topLeftPanelId = workspace.focusedTerminalPanel?.id ?? workspace.focusedPanelId else { + return false + } + guard let topRight = workspace.newTerminalSplit( + from: topLeftPanelId, + orientation: .horizontal, + focus: false + ) else { + return false + } + await Task.yield() + guard workspace.newTerminalSplit( + from: topLeftPanelId, + orientation: .vertical, + focus: false + ) != nil else { + return false + } + await Task.yield() + guard workspace.newTerminalSplit( + from: topRight.id, + orientation: .vertical, + focus: false + ) != nil else { + return false + } + await Task.yield() + + let paneIds = workspace.bonsplitController.allPaneIds + guard paneIds.count == paneCount else { return false } + + let additionalTabsPerPane = max(0, tabsPerPane - 1) + if additionalTabsPerPane > 0 { + for (paneIndex, paneId) in paneIds.enumerated() { + for tabOffset in 0.. Int { + var pending = 0 + for workspace in workspaces { + for panel in workspace.panels.values { + guard let terminalPanel = panel as? TerminalPanel else { continue } + if terminalPanel.surface.surface == nil { + pending += 1 + } + } + } + return pending + } + + private func debugStressLagSnapshot() -> ( + workspaceCount: Int, + terminalPanelCount: Int, + loadedSurfaceCount: Int, + selectedWorkspace: String + ) { + guard let tabManager else { + return (0, 0, 0, "nil") + } + var terminalPanelCount = 0 + var loadedSurfaceCount = 0 + for workspace in tabManager.tabs { + for panel in workspace.panels.values { + guard let terminalPanel = panel as? TerminalPanel else { continue } + terminalPanelCount += 1 + if terminalPanel.surface.surface != nil { + loadedSurfaceCount += 1 + } + } + } + let selectedWorkspace = tabManager.selectedTabId.map { String($0.uuidString.prefix(5)) } ?? "nil" + return ( + tabManager.tabs.count, + terminalPanelCount, + loadedSurfaceCount, + selectedWorkspace + ) + } + + private func logSlowShortcutMonitorLatencyIfNeeded( + event: NSEvent, + handledByShortcut: Bool, + elapsedMs: Double + ) { + guard debugStressLagProbeEnabled else { return } + guard event.type == .keyDown else { return } + + let normalizedFlags = event.modifierFlags + .intersection(.deviceIndependentFlagsMask) + .subtracting([.numericPad, .function, .capsLock]) + let isPlainTyping = normalizedFlags.isDisjoint(with: [.command, .control, .option]) + let thresholdMs: Double = event.isARepeat ? 1.5 : (isPlainTyping ? 2.5 : 6.0) + guard elapsedMs >= thresholdMs else { return } + + let snapshot = debugStressLagSnapshot() + dlog( + "stress.inputLag path=appMonitor ms=\(String(format: "%.2f", elapsedMs)) " + + "threshold=\(String(format: "%.2f", thresholdMs)) handled=\(handledByShortcut ? 1 : 0) " + + "plain=\(isPlainTyping ? 1 : 0) repeat=\(event.isARepeat ? 1 : 0) keyCode=\(event.keyCode) " + + "mods=\(event.modifierFlags.rawValue) workspaces=\(snapshot.workspaceCount) " + + "terminals=\(snapshot.terminalPanelCount) surfacesReady=\(snapshot.loadedSurfaceCount) " + + "selected=\(snapshot.selectedWorkspace)" + ) + } + private func sendTextWhenReady(_ text: String, to tab: Tab, attempt: Int = 0) { let maxAttempts = 60 if let terminalPanel = tab.focusedTerminalPanel, terminalPanel.surface.surface != nil { @@ -5132,24 +5425,35 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let delayText = String(format: "%.2f", delayMs) dlog("key.latency path=appMonitor ms=\(delayText) keyCode=\(event.keyCode) mods=\(event.modifierFlags.rawValue) repeat=\(event.isARepeat ? 1 : 0)") } - let frType = NSApp.keyWindow?.firstResponder.map { String(describing: type(of: $0)) } ?? "nil" - dlog( - "monitor.keyDown: \(NSWindow.keyDescription(event)) fr=\(frType) addrBarId=\(self.browserAddressBarFocusedPanelId?.uuidString.prefix(8) ?? "nil") \(self.debugShortcutRouteSnapshot(event: event))" - ) + let shortcutMonitorTraceEnabled = + ProcessInfo.processInfo.environment["CMUX_SHORTCUT_MONITOR_TRACE"] == "1" + || UserDefaults.standard.bool(forKey: "cmuxShortcutMonitorTrace") + if shortcutMonitorTraceEnabled { + let frType = NSApp.keyWindow?.firstResponder.map { String(describing: type(of: $0)) } ?? "nil" + dlog( + "monitor.keyDown: \(NSWindow.keyDescription(event)) fr=\(frType) addrBarId=\(self.browserAddressBarFocusedPanelId?.uuidString.prefix(8) ?? "nil") \(self.debugShortcutRouteSnapshot(event: event))" + ) + } if let probeKind = self.developerToolsShortcutProbeKind(event: event) { self.logDeveloperToolsShortcutSnapshot(phase: "monitor.pre.\(probeKind)", event: event) } #endif - if self.handleCustomShortcut(event: event) { + let shortcutStart = ProcessInfo.processInfo.systemUptime + let handledByShortcut = self.handleCustomShortcut(event: event) +#if DEBUG + let shortcutElapsedMs = (ProcessInfo.processInfo.systemUptime - shortcutStart) * 1000.0 + self.logSlowShortcutMonitorLatencyIfNeeded( + event: event, + handledByShortcut: handledByShortcut, + elapsedMs: shortcutElapsedMs + ) +#endif + if handledByShortcut { #if DEBUG dlog(" → consumed by handleCustomShortcut") - DebugEventLog.shared.dump() #endif return nil // Consume the event } -#if DEBUG - DebugEventLog.shared.dump() -#endif return event // Pass through } self.handleBrowserOmnibarSelectionRepeatLifecycleEvent(event) @@ -5573,6 +5877,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } + // Fast path for normal typing and terminal navigation keys (for example Up-arrow + // history): after command-palette/notification handling and browser omnibar + // arrow navigation above, plain key events have no app-level shortcut behavior. + if normalizedFlags.isEmpty { + return false + } + // Let omnibar-local Emacs navigation (Cmd/Ctrl+N/P) win while the browser // address bar is focused. Without this, app-level Cmd+N can steal focus. if shouldBypassAppShortcutForFocusedBrowserAddressBar(flags: flags, chars: chars) { diff --git a/tests/test_workspace_churn_up_arrow_lag.py b/tests/test_workspace_churn_up_arrow_lag.py new file mode 100755 index 00000000..3cadc43d --- /dev/null +++ b/tests/test_workspace_churn_up_arrow_lag.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python3 +""" +Regression harness: compare typing latency before and after workspace churn. + +Scenario A (baseline): +1) Keep only the first workspace. +2) Seed shell history. +3) Measure per-key latency for repeated Up-arrow shortcuts. + +Scenario B (churn): +1) Keep only the first workspace. +2) Create N workspaces. +3) Visit every workspace (simulates clicking each tab), then return to the first. +4) Seed shell history. +5) Measure Up-arrow latency again. + +The test fails when churn latency regresses too far relative to baseline. +""" + +from __future__ import annotations + +import os +import select +import socket +import statistics +import subprocess +import sys +import threading +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Callable, Optional + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from cmux import cmux, cmuxError + +NEW_WORKSPACES = int(os.environ.get("CMUX_LAG_NEW_WORKSPACES", "20")) +SWITCH_PASSES = int(os.environ.get("CMUX_LAG_SWITCH_PASSES", "1")) +SWITCH_DELAY_S = float(os.environ.get("CMUX_LAG_SWITCH_DELAY_S", "0.06")) +HISTORY_SEED_LINES = int(os.environ.get("CMUX_LAG_HISTORY_LINES", "120")) +KEY_EVENTS = int(os.environ.get("CMUX_LAG_KEY_EVENTS", "180")) +KEY_DELAY_S = float(os.environ.get("CMUX_LAG_KEY_DELAY_S", "0.0")) +KEY_COMBO = os.environ.get("CMUX_LAG_KEY_COMBO", "up") + +MAX_P95_RATIO = float(os.environ.get("CMUX_LAG_MAX_P95_RATIO", "1.70")) +MAX_AVG_RATIO = float(os.environ.get("CMUX_LAG_MAX_AVG_RATIO", "1.70")) +MAX_CHURN_P95_MS = float(os.environ.get("CMUX_LAG_MAX_CHURN_P95_MS", "35.0")) +MAX_P95_DELTA_MS = float(os.environ.get("CMUX_LAG_MAX_P95_DELTA_MS", "20.0")) +MAX_AVG_DELTA_MS = float(os.environ.get("CMUX_LAG_MAX_AVG_DELTA_MS", "12.0")) +MIN_BASELINE_P95_MS_FOR_RATIO = float(os.environ.get("CMUX_LAG_MIN_BASELINE_P95_MS_FOR_RATIO", "6.0")) +MIN_BASELINE_AVG_MS_FOR_RATIO = float(os.environ.get("CMUX_LAG_MIN_BASELINE_AVG_MS_FOR_RATIO", "4.0")) +MAX_CPU_PERCENT = float(os.environ.get("CMUX_LAG_MAX_CPU_PERCENT", "180.0")) +ENFORCE_CPU = os.environ.get("CMUX_LAG_ENFORCE_CPU", "0") == "1" +ALLOW_MAIN_SOCKET = os.environ.get("CMUX_LAG_ALLOW_MAIN_SOCKET", "0") == "1" + + +@dataclass +class LatencyStats: + n: int + avg_ms: float + p50_ms: float + p95_ms: float + p99_ms: float + max_ms: float + + +class RawSocketClient: + def __init__(self, socket_path: str): + self.socket_path = socket_path + self.sock: Optional[socket.socket] = None + self.recv_buffer = "" + + def connect(self) -> None: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(3.0) + sock.connect(self.socket_path) + self.sock = sock + + def close(self) -> None: + if self.sock is not None: + try: + self.sock.close() + finally: + self.sock = None + + def __enter__(self) -> RawSocketClient: + self.connect() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.close() + + def command(self, command: str, timeout_s: float = 2.0) -> str: + if self.sock is None: + raise cmuxError("Raw socket client not connected") + + self.sock.sendall((command + "\n").encode("utf-8")) + deadline = time.time() + timeout_s + + while True: + if "\n" in self.recv_buffer: + line, self.recv_buffer = self.recv_buffer.split("\n", 1) + return line + + remaining = deadline - time.time() + if remaining <= 0: + raise cmuxError(f"Timed out waiting for response to: {command}") + + ready, _, _ = select.select([self.sock], [], [], remaining) + if not ready: + raise cmuxError(f"Timed out waiting for response to: {command}") + + chunk = self.sock.recv(8192) + if not chunk: + raise cmuxError("Socket closed while waiting for response") + self.recv_buffer += chunk.decode("utf-8", errors="replace") + + +def wait_for(predicate: Callable[[], bool], timeout_s: float, step_s: float = 0.05) -> None: + start = time.time() + while time.time() - start < timeout_s: + if predicate(): + return + time.sleep(step_s) + raise cmuxError("Timed out waiting for condition") + + +def percentile(values: list[float], p: float) -> float: + if not values: + return 0.0 + if len(values) == 1: + return values[0] + sorted_values = sorted(values) + idx = (len(sorted_values) - 1) * p + lower = int(idx) + upper = min(lower + 1, len(sorted_values) - 1) + fraction = idx - lower + return sorted_values[lower] * (1 - fraction) + sorted_values[upper] * fraction + + +def compute_stats(values_ms: list[float]) -> LatencyStats: + return LatencyStats( + n=len(values_ms), + avg_ms=statistics.mean(values_ms) if values_ms else 0.0, + p50_ms=percentile(values_ms, 0.50), + p95_ms=percentile(values_ms, 0.95), + p99_ms=percentile(values_ms, 0.99), + max_ms=max(values_ms) if values_ms else 0.0, + ) + + +def get_cmux_pid_for_socket(socket_path: Optional[str]) -> Optional[int]: + if socket_path and os.path.exists(socket_path): + result = subprocess.run(["lsof", "-t", socket_path], capture_output=True, text=True) + if result.returncode == 0: + for line in result.stdout.strip().split("\n"): + line = line.strip() + if not line: + continue + try: + pid = int(line) + except ValueError: + continue + if pid != os.getpid(): + return pid + + result = subprocess.run( + ["pgrep", "-f", r"cmux DEV.*\.app/Contents/MacOS/cmux DEV"], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None + lines = [line.strip() for line in result.stdout.splitlines() if line.strip()] + return int(lines[0]) if lines else None + + +def resolve_target_socket() -> str: + socket_path = os.environ.get("CMUX_SOCKET_PATH") + if not socket_path: + raise cmuxError( + "CMUX_SOCKET_PATH is required. Point it to a tagged dev socket (for example /tmp/cmux-debug-.sock)." + ) + base = os.path.basename(socket_path) + if not ALLOW_MAIN_SOCKET and base in {"cmux.sock", "cmux-debug.sock"}: + raise cmuxError( + f"Refusing to run against main socket '{socket_path}'. Set CMUX_SOCKET_PATH to a tagged dev instance." + ) + return socket_path + + +def get_cpu(pid: int) -> float: + result = subprocess.run(["ps", "-p", str(pid), "-o", "%cpu="], capture_output=True, text=True) + if result.returncode != 0: + return 0.0 + try: + return float(result.stdout.strip()) + except ValueError: + return 0.0 + + +class CPUMonitor: + def __init__(self, pid: int, interval_s: float = 0.2): + self.pid = pid + self.interval_s = interval_s + self._stop = threading.Event() + self._thread = threading.Thread(target=self._run, daemon=True) + self.samples: list[float] = [] + + def _run(self) -> None: + while not self._stop.is_set(): + self.samples.append(get_cpu(self.pid)) + time.sleep(self.interval_s) + + def start(self) -> None: + self._thread.start() + + def stop(self) -> None: + self._stop.set() + self._thread.join(timeout=2.0) + + +def keep_only_first_workspace(client: cmux) -> str: + workspaces = sorted(client.list_workspaces(), key=lambda row: row[0]) + if not workspaces: + first_id = client.new_workspace() + client.select_workspace(first_id) + return first_id + + first_id = workspaces[0][1] + client.select_workspace(first_id) + for _index, wid, _title, _selected in reversed(workspaces[1:]): + if wid == first_id: + continue + client.close_workspace(wid) + + def only_first() -> bool: + current = sorted(client.list_workspaces(), key=lambda row: row[0]) + return len(current) == 1 and current[0][1] == first_id + + wait_for(only_first, timeout_s=6.0) + return first_id + + +def create_workspaces(client: cmux, count: int) -> list[str]: + created: list[str] = [] + for _ in range(count): + wid = client.new_workspace() + created.append(wid) + time.sleep(0.04) + return created + + +def cycle_all_workspaces(client: cmux, passes: int, delay_s: float) -> list[str]: + ids = [wid for _idx, wid, _title, _selected in sorted(client.list_workspaces(), key=lambda row: row[0])] + for _ in range(passes): + for wid in ids: + client.select_workspace(wid) + time.sleep(delay_s) + return ids + + +def focused_terminal_panel(client: cmux) -> str: + surfaces = client.list_surfaces() + if not surfaces: + raise cmuxError("No surfaces available in selected workspace") + focused = next(((idx, sid) for idx, sid, is_focused in surfaces if is_focused), None) + if focused is None: + idx, sid, _ = surfaces[0] + client.focus_surface(idx) + return sid + return focused[1] + + +def seed_history(client: cmux, lines: int) -> None: + for i in range(lines): + client.send_line(f"echo cmux-lag-seed-{i}") + + +def run_shortcut_latency_burst( + socket_path: str, + combo: str, + count: int, + delay_s: float, +) -> list[float]: + latencies_ms: list[float] = [] + with RawSocketClient(socket_path) as raw: + # Warm up the command path and responder chain. + for _ in range(5): + response = raw.command(f"simulate_shortcut {combo}") + if not response.startswith("OK"): + raise cmuxError(response) + + for _ in range(count): + start = time.perf_counter() + response = raw.command(f"simulate_shortcut {combo}") + elapsed_ms = (time.perf_counter() - start) * 1000.0 + if not response.startswith("OK"): + raise cmuxError(response) + latencies_ms.append(elapsed_ms) + if delay_s > 0: + time.sleep(delay_s) + + return latencies_ms + + +def maybe_write_sample(pid: Optional[int], prefix: str) -> Optional[Path]: + if pid is None: + return None + out = Path(f"/tmp/{prefix}_{pid}.txt") + result = subprocess.run(["sample", str(pid), "2"], capture_output=True, text=True) + out.write_text(result.stdout + result.stderr) + return out + + +def print_stats(label: str, stats: LatencyStats) -> None: + print(f"\n{label}") + print(f" events: {stats.n}") + print(f" avg_ms: {stats.avg_ms:.2f}") + print(f" p50_ms: {stats.p50_ms:.2f}") + print(f" p95_ms: {stats.p95_ms:.2f}") + print(f" p99_ms: {stats.p99_ms:.2f}") + print(f" max_ms: {stats.max_ms:.2f}") + + +def run_baseline_scenario(client: cmux, socket_path: str) -> tuple[str, LatencyStats]: + first_workspace_id = keep_only_first_workspace(client) + client.select_workspace(first_workspace_id) + panel_id = focused_terminal_panel(client) + seed_history(client, HISTORY_SEED_LINES) + latencies = run_shortcut_latency_burst( + socket_path=socket_path, + combo=KEY_COMBO, + count=KEY_EVENTS, + delay_s=KEY_DELAY_S, + ) + return panel_id, compute_stats(latencies) + + +def run_churn_scenario(client: cmux, socket_path: str, first_workspace_id: str) -> tuple[str, LatencyStats]: + first_workspace_id = keep_only_first_workspace(client) + _ = create_workspaces(client, NEW_WORKSPACES) + ordered_ids = cycle_all_workspaces(client, SWITCH_PASSES, SWITCH_DELAY_S) + + if first_workspace_id in ordered_ids: + client.select_workspace(first_workspace_id) + elif ordered_ids: + client.select_workspace(ordered_ids[0]) + + panel_id = focused_terminal_panel(client) + seed_history(client, HISTORY_SEED_LINES) + latencies = run_shortcut_latency_burst( + socket_path=socket_path, + combo=KEY_COMBO, + count=KEY_EVENTS, + delay_s=KEY_DELAY_S, + ) + return panel_id, compute_stats(latencies) + + +def main() -> int: + print("=" * 64) + print("Workspace Churn + Up-Arrow Latency Regression") + print("=" * 64) + + client: Optional[cmux] = None + pid: Optional[int] = None + first_workspace_id: Optional[str] = None + + try: + target_socket = resolve_target_socket() + client = cmux(socket_path=target_socket) + client.connect() + print(f"Using socket: {client.socket_path}") + + pid = get_cmux_pid_for_socket(client.socket_path) + if pid is None: + print("SKIP: cmux process not found for socket") + return 0 + + cpu_monitor = CPUMonitor(pid) + cpu_monitor.start() + + first_workspace_id = keep_only_first_workspace(client) + baseline_panel_id, baseline = run_baseline_scenario(client, client.socket_path) + print(f"Baseline panel: {baseline_panel_id}") + + churn_panel_id, churn = run_churn_scenario(client, client.socket_path, first_workspace_id) + print(f"Churn panel: {churn_panel_id}") + + cpu_monitor.stop() + cpu_samples = cpu_monitor.samples + cpu_avg = statistics.mean(cpu_samples) if cpu_samples else 0.0 + cpu_max = max(cpu_samples) if cpu_samples else 0.0 + + print_stats("Baseline", baseline) + print_stats("After workspace churn", churn) + + p95_ratio = churn.p95_ms / max(baseline.p95_ms, 0.001) + avg_ratio = churn.avg_ms / max(baseline.avg_ms, 0.001) + p95_delta_ms = churn.p95_ms - baseline.p95_ms + avg_delta_ms = churn.avg_ms - baseline.avg_ms + enforce_p95_ratio = baseline.p95_ms >= MIN_BASELINE_P95_MS_FOR_RATIO + enforce_avg_ratio = baseline.avg_ms >= MIN_BASELINE_AVG_MS_FOR_RATIO + + print("\nComparison") + print( + f" p95_ratio: {p95_ratio:.2f}x (max {MAX_P95_RATIO:.2f}x, " + f"enabled when baseline p95 >= {MIN_BASELINE_P95_MS_FOR_RATIO:.2f}ms)" + ) + print( + f" avg_ratio: {avg_ratio:.2f}x (max {MAX_AVG_RATIO:.2f}x, " + f"enabled when baseline avg >= {MIN_BASELINE_AVG_MS_FOR_RATIO:.2f}ms)" + ) + print(f" churn_p95_ms: {churn.p95_ms:.2f} (max {MAX_CHURN_P95_MS:.2f})") + print(f" p95_delta_ms: {p95_delta_ms:.2f} (max {MAX_P95_DELTA_MS:.2f})") + print(f" avg_delta_ms: {avg_delta_ms:.2f} (max {MAX_AVG_DELTA_MS:.2f})") + print(f" cpu_avg_pct: {cpu_avg:.2f}") + print(f" cpu_max_pct: {cpu_max:.2f}") + + failures: list[str] = [] + if enforce_p95_ratio and p95_ratio > MAX_P95_RATIO: + failures.append(f"p95 ratio {p95_ratio:.2f}x > {MAX_P95_RATIO:.2f}x") + if enforce_avg_ratio and avg_ratio > MAX_AVG_RATIO: + failures.append(f"avg ratio {avg_ratio:.2f}x > {MAX_AVG_RATIO:.2f}x") + if p95_delta_ms > MAX_P95_DELTA_MS: + failures.append(f"p95 delta {p95_delta_ms:.2f}ms > {MAX_P95_DELTA_MS:.2f}ms") + if avg_delta_ms > MAX_AVG_DELTA_MS: + failures.append(f"avg delta {avg_delta_ms:.2f}ms > {MAX_AVG_DELTA_MS:.2f}ms") + if churn.p95_ms > MAX_CHURN_P95_MS: + failures.append(f"churn p95 {churn.p95_ms:.2f}ms > {MAX_CHURN_P95_MS:.2f}ms") + if ENFORCE_CPU and cpu_max > MAX_CPU_PERCENT: + failures.append(f"cpu max {cpu_max:.2f}% > {MAX_CPU_PERCENT:.2f}%") + + if failures: + print("\nFAIL") + for item in failures: + print(f" - {item}") + sample_path = maybe_write_sample(pid, "cmux_workspace_churn_up_arrow_lag") + if sample_path: + print(f" sample_path: {sample_path}") + return 1 + + print("\nPASS") + return 0 + + except cmuxError as e: + print(f"FAIL: {e}") + sample_path = maybe_write_sample(pid, "cmux_workspace_churn_up_arrow_error") + if sample_path: + print(f"sample_path: {sample_path}") + return 1 + + finally: + if client is not None: + try: + if first_workspace_id: + client.select_workspace(first_workspace_id) + keep_only_first_workspace(client) + except Exception: + pass + client.close() + + +if __name__ == "__main__": + raise SystemExit(main()) From 5bbdd87c294d2271dcdb69f7411d48b58ae1c7b5 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:20:14 -0800 Subject: [PATCH 028/232] Fix re-entrant exclusive-access crash in drag handle hit test (#771) When sibling.hitTest() triggers a SwiftUI layout pass during the drag handle's sibling walk, AppKit can call back into windowDragHandleShouldCaptureHit before the outer invocation finishes. This re-entry accesses SwiftUI view state that is already held exclusively, causing a Swift runtime SIGABRT. Add a module-level re-entrancy guard that bails out (returns false) on nested calls to the sibling walk. Since hitTest is always called on the main thread, a simple Bool flag is sufficient. Crash was reproduced on macOS Sequoia 15.1.1 (24B91) in a UTM VM. The crash stack: DraggableView.hitTest -> windowDragHandleShouldCaptureHit -> sibling.hitTest -> SwiftUI body evaluation -> hitTest (re-entry) -> exclusive-access violation -> SIGABRT. --- Sources/WindowDragHandleView.swift | 20 +++++++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 41 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/Sources/WindowDragHandleView.swift b/Sources/WindowDragHandleView.swift index 15f406e9..3aa5f16d 100644 --- a/Sources/WindowDragHandleView.swift +++ b/Sources/WindowDragHandleView.swift @@ -218,6 +218,12 @@ func windowDragHandleShouldTreatTopHitAsPassiveHost(_ view: NSView) -> Bool { return false } +/// Re-entrancy guard for the sibling hit-test walk. When `sibling.hitTest()` +/// triggers SwiftUI view-body evaluation, AppKit can call back into this +/// function before the outer invocation finishes, causing a Swift +/// exclusive-access violation (SIGABRT). Main-thread only, no lock needed. +private var _windowDragHandleIsResolvingSiblingHits = false + /// Returns whether the titlebar drag handle should capture a hit at `point`. /// We only claim the hit when no sibling view already handles it, so interactive /// controls layered in the titlebar (e.g. proxy folder icon) keep their gestures. @@ -295,6 +301,20 @@ func windowDragHandleShouldCaptureHit( return true } + // Bail out if we're already inside a sibling hit-test walk. This happens + // when sibling.hitTest() re-enters SwiftUI layout, which calls hitTest on + // this drag handle again. Proceeding would trigger an exclusive-access + // violation in the Swift runtime. + guard !_windowDragHandleIsResolvingSiblingHits else { + #if DEBUG + dlog("titlebar.dragHandle.hitTest capture=false reason=reentrant point=\(windowDragHandleFormatPoint(point))") + #endif + return false + } + + _windowDragHandleIsResolvingSiblingHits = true + defer { _windowDragHandleIsResolvingSiblingHits = false } + let siblingSnapshot = Array(superview.subviews.reversed()) #if DEBUG diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 7f5ddb4c..d116a4e9 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -6521,6 +6521,24 @@ final class WindowDragHandleHitTests: XCTestCase { } } + /// A sibling view whose hitTest re-enters windowDragHandleShouldCaptureHit, + /// simulating the crash path where sibling.hitTest triggers a SwiftUI layout + /// pass that calls back into the drag handle's hit resolution. + private final class ReentrantSiblingView: NSView { + weak var dragHandle: NSView? + var reenteredResult: Bool? + + override func hitTest(_ point: NSPoint) -> NSView? { + guard bounds.contains(point), let dragHandle else { return nil } + // Simulate the re-entry: during sibling hit test, SwiftUI layout + // calls windowDragHandleShouldCaptureHit on the drag handle again. + reenteredResult = windowDragHandleShouldCaptureHit( + point, in: dragHandle, eventType: .leftMouseDown, eventWindow: dragHandle.window + ) + return nil + } + } + func testDragHandleCapturesHitWhenNoSiblingClaimsPoint() { let container = NSView(frame: NSRect(x: 0, y: 0, width: 220, height: 36)) let dragHandle = NSView(frame: container.bounds) @@ -6751,6 +6769,29 @@ final class WindowDragHandleHitTests: XCTestCase { ) } + func testDragHandleSiblingHitTestReentrancyDoesNotCrash() { + let container = NSView(frame: NSRect(x: 0, y: 0, width: 220, height: 36)) + let dragHandle = NSView(frame: container.bounds) + container.addSubview(dragHandle) + + let reentrantSibling = ReentrantSiblingView(frame: container.bounds) + reentrantSibling.dragHandle = dragHandle + container.addSubview(reentrantSibling) + + // The outer call enters the sibling walk, which calls + // reentrantSibling.hitTest(), which re-enters + // windowDragHandleShouldCaptureHit. Without the re-entrancy guard + // this would trigger a Swift exclusive-access violation (SIGABRT). + let outerResult = windowDragHandleShouldCaptureHit( + NSPoint(x: 110, y: 18), in: dragHandle, eventType: .leftMouseDown + ) + XCTAssertTrue(outerResult, "Outer call should still capture when sibling returns nil") + XCTAssertEqual( + reentrantSibling.reenteredResult, false, + "Re-entrant call should bail out (return false) instead of crashing" + ) + } + func testDragHandleTopHitResolutionSurvivesSameWindowReentrancy() { let point = NSPoint(x: 180, y: 18) let window = NSWindow( From 919f77b6dc38707e93e242452bc6311136dcd580 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:56:27 -0800 Subject: [PATCH 029/232] Add setting to hide Cmd-hold shortcut hints (#765) * Add setting to hide Cmd-hold shortcut hints * Bump bonsplit for Cmd-hold pane hint toggle * Document tagged app link format in agent notes * Disable Ctrl pane hints when hold-hints toggle is off --- CLAUDE.md | 8 ++ Sources/ContentView.swift | 22 +++- Sources/cmuxApp.swift | 17 +++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 103 ++++++++++++++---- vendor/bonsplit | 2 +- 5 files changed, 125 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2d1710c7..0c147fc9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,14 @@ After making code changes, always run the reload script with a tag to launch the ./scripts/reload.sh --tag fix-zsh-autosuggestions ``` +When reporting a tagged reload result in chat, use this exact clickable format: + +```markdown +======================================================= +[: file:///tmp/cmux-.app](file:///tmp/cmux-.app) +======================================================= +``` + After making code changes, always run the build: ```bash diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 944426a2..dd19fc85 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -5810,8 +5810,12 @@ struct VerticalTabsSidebar: View { enum SidebarCommandHintPolicy { static let intentionalHoldDelay: TimeInterval = 0.30 - static func shouldShowHints(for modifierFlags: NSEvent.ModifierFlags) -> Bool { - modifierFlags.intersection(.deviceIndependentFlagsMask) == [.command] + static func shouldShowHints( + for modifierFlags: NSEvent.ModifierFlags, + defaults: UserDefaults = .standard + ) -> Bool { + ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults) && + modifierFlags.intersection(.deviceIndependentFlagsMask) == [.command] } static func isCurrentWindow( @@ -5832,9 +5836,10 @@ enum SidebarCommandHintPolicy { hostWindowNumber: Int?, hostWindowIsKey: Bool, eventWindowNumber: Int?, - keyWindowNumber: Int? + keyWindowNumber: Int?, + defaults: UserDefaults = .standard ) -> Bool { - shouldShowHints(for: modifierFlags) && + shouldShowHints(for: modifierFlags, defaults: defaults) && isCurrentWindow( hostWindowNumber: hostWindowNumber, hostWindowIsKey: hostWindowIsKey, @@ -5852,6 +5857,7 @@ enum ShortcutHintDebugSettings { static let paneHintXKey = "shortcutHintPaneTabXOffset" static let paneHintYKey = "shortcutHintPaneTabYOffset" static let alwaysShowHintsKey = "shortcutHintAlwaysShow" + static let showHintsOnCommandHoldKey = "shortcutHintShowOnCommandHold" static let defaultSidebarHintX = 0.0 static let defaultSidebarHintY = 0.0 @@ -5860,12 +5866,20 @@ enum ShortcutHintDebugSettings { static let defaultPaneHintX = 0.0 static let defaultPaneHintY = 0.0 static let defaultAlwaysShowHints = false + static let defaultShowHintsOnCommandHold = true static let offsetRange: ClosedRange = -20...20 static func clamped(_ value: Double) -> Double { min(max(value, offsetRange.lowerBound), offsetRange.upperBound) } + + static func showHintsOnCommandHoldEnabled(defaults: UserDefaults = .standard) -> Bool { + guard defaults.object(forKey: showHintsOnCommandHoldKey) != nil else { + return defaultShowHintsOnCommandHold + } + return defaults.bool(forKey: showHintsOnCommandHoldKey) + } } enum SidebarDragLifecycleNotification { diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 9936536a..4f7cea7d 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -1313,6 +1313,7 @@ private enum DebugWindowConfigSnapshot { shortcutHintPaneTabXOffset=\(String(format: "%.1f", doubleValue(defaults, key: ShortcutHintDebugSettings.paneHintXKey, fallback: ShortcutHintDebugSettings.defaultPaneHintX))) shortcutHintPaneTabYOffset=\(String(format: "%.1f", doubleValue(defaults, key: ShortcutHintDebugSettings.paneHintYKey, fallback: ShortcutHintDebugSettings.defaultPaneHintY))) shortcutHintAlwaysShow=\(boolValue(defaults, key: ShortcutHintDebugSettings.alwaysShowHintsKey, fallback: ShortcutHintDebugSettings.defaultAlwaysShowHints)) + shortcutHintShowOnCommandHold=\(boolValue(defaults, key: ShortcutHintDebugSettings.showHintsOnCommandHoldKey, fallback: ShortcutHintDebugSettings.defaultShowHintsOnCommandHold)) """ let backgroundPayload = """ @@ -2746,6 +2747,8 @@ struct SettingsView: View { @AppStorage("sidebarShowPullRequest") private var sidebarShowPullRequest = true @AppStorage(BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowserKey) private var openSidebarPullRequestLinksInCmuxBrowser = BrowserLinkOpenSettings.defaultOpenSidebarPullRequestLinksInCmuxBrowser + @AppStorage(ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + private var showShortcutHintsOnCommandHold = ShortcutHintDebugSettings.defaultShowHintsOnCommandHold @AppStorage("sidebarShowPorts") private var sidebarShowPorts = true @AppStorage("sidebarShowLog") private var sidebarShowLog = true @AppStorage("sidebarShowProgress") private var sidebarShowProgress = true @@ -3460,6 +3463,19 @@ struct SettingsView: View { SettingsSectionHeader(title: "Keyboard Shortcuts") SettingsCard { + SettingsCardRow( + "Show Cmd/Ctrl-Hold Shortcut Hints", + subtitle: showShortcutHintsOnCommandHold + ? "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills." + : "Holding Cmd or Ctrl keeps shortcut hint pills hidden." + ) { + Toggle("", isOn: $showShortcutHintsOnCommandHold) + .labelsHidden() + .controlSize(.small) + } + + SettingsCardDivider() + let actions = KeyboardShortcutSettings.Action.allCases ForEach(Array(actions.enumerated()), id: \.element.id) { index, action in ShortcutSettingRow(action: action) @@ -3642,6 +3658,7 @@ struct SettingsView: View { sidebarShowBranchDirectory = true sidebarShowPullRequest = true openSidebarPullRequestLinksInCmuxBrowser = BrowserLinkOpenSettings.defaultOpenSidebarPullRequestLinksInCmuxBrowser + showShortcutHintsOnCommandHold = ShortcutHintDebugSettings.defaultShowHintsOnCommandHold sidebarShowPorts = true sidebarShowLog = true sidebarShowProgress = true diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index d116a4e9..3e0d1c72 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2554,11 +2554,31 @@ final class CommandPaletteSelectionScrollBehaviorTests: XCTestCase { final class SidebarCommandHintPolicyTests: XCTestCase { func testCommandHintRequiresCommandOnlyModifier() { - XCTAssertTrue(SidebarCommandHintPolicy.shouldShowHints(for: [.command])) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [])) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .shift])) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .option])) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .control])) + withDefaultsSuite { defaults in + defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + + XCTAssertTrue(SidebarCommandHintPolicy.shouldShowHints(for: [.command], defaults: defaults)) + XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [], defaults: defaults)) + XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .shift], defaults: defaults)) + XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .option], defaults: defaults)) + XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .control], defaults: defaults)) + } + } + + func testCommandHintCanBeDisabledInSettings() { + withDefaultsSuite { defaults in + defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + + XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command], defaults: defaults)) + } + } + + func testCommandHintDefaultsToEnabledWhenSettingMissing() { + withDefaultsSuite { defaults in + defaults.removeObject(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + + XCTAssertTrue(SidebarCommandHintPolicy.shouldShowHints(for: [.command], defaults: defaults)) + } } func testCommandHintUsesIntentionalHoldDelay() { @@ -2595,25 +2615,43 @@ final class SidebarCommandHintPolicyTests: XCTestCase { } func testWindowScopedCommandHintsUseKeyWindowWhenNoEventWindowIsAvailable() { - XCTAssertTrue( - SidebarCommandHintPolicy.shouldShowHints( - for: [.command], - hostWindowNumber: 42, - hostWindowIsKey: true, - eventWindowNumber: nil, - keyWindowNumber: 42 - ) - ) + withDefaultsSuite { defaults in + defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) - XCTAssertFalse( - SidebarCommandHintPolicy.shouldShowHints( - for: [.command], - hostWindowNumber: 42, - hostWindowIsKey: true, - eventWindowNumber: nil, - keyWindowNumber: 7 + XCTAssertTrue( + SidebarCommandHintPolicy.shouldShowHints( + for: [.command], + hostWindowNumber: 42, + hostWindowIsKey: true, + eventWindowNumber: nil, + keyWindowNumber: 42, + defaults: defaults + ) ) - ) + + XCTAssertFalse( + SidebarCommandHintPolicy.shouldShowHints( + for: [.command], + hostWindowNumber: 42, + hostWindowIsKey: true, + eventWindowNumber: nil, + keyWindowNumber: 7, + defaults: defaults + ) + ) + } + } + + private func withDefaultsSuite(_ body: (UserDefaults) -> Void) { + let suiteName = "SidebarCommandHintPolicyTests-\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create defaults suite") + return + } + + defaults.removePersistentDomain(forName: suiteName) + body(defaults) + defaults.removePersistentDomain(forName: suiteName) } } @@ -2633,6 +2671,27 @@ final class ShortcutHintDebugSettingsTests: XCTestCase { XCTAssertEqual(ShortcutHintDebugSettings.defaultPaneHintX, 0.0) XCTAssertEqual(ShortcutHintDebugSettings.defaultPaneHintY, 0.0) XCTAssertFalse(ShortcutHintDebugSettings.defaultAlwaysShowHints) + XCTAssertTrue(ShortcutHintDebugSettings.defaultShowHintsOnCommandHold) + } + + func testShowHintsOnCommandHoldSettingRespectsStoredValue() { + let suiteName = "ShortcutHintDebugSettingsTests-\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create defaults suite") + return + } + + defaults.removePersistentDomain(forName: suiteName) + defer { defaults.removePersistentDomain(forName: suiteName) } + + defaults.removeObject(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + XCTAssertTrue(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults)) + + defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + XCTAssertFalse(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults)) + + defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + XCTAssertTrue(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults)) } } diff --git a/vendor/bonsplit b/vendor/bonsplit index 335facd9..89a4fd12 160000 --- a/vendor/bonsplit +++ b/vendor/bonsplit @@ -1 +1 @@ -Subproject commit 335facd9fd1d81a3c71fea69345af30f7e3601f9 +Subproject commit 89a4fd1288a706ae4b766f323191d6570b7123aa From 0cad7e012643343f140c176a024918969c24b960 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:11:47 -0800 Subject: [PATCH 030/232] Reduce spacing under Changelog heading (#774) Remove top padding on the first article and reduce the gap between the heading and version list from 32px to 16px. --- web/app/docs/changelog/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/docs/changelog/page.tsx b/web/app/docs/changelog/page.tsx index 43760e74..ac660226 100644 --- a/web/app/docs/changelog/page.tsx +++ b/web/app/docs/changelog/page.tsx @@ -246,8 +246,8 @@ export default function ChangelogPage() {

Changelog

-
- {versions.map((v) => { +
+ {versions.map((v, vi) => { const media = changelogMedia[v.version]; return ( @@ -255,7 +255,7 @@ export default function ChangelogPage() { key={v.version} id={`v${v.version}`} className="border-t border-border first:border-t-0" - style={{ display: "flex", flexDirection: "column", padding: "40px 0" }} + style={{ display: "flex", flexDirection: "column", paddingTop: vi === 0 ? 0 : 40, paddingBottom: 40 }} >
Date: Mon, 2 Mar 2026 21:31:39 -0800 Subject: [PATCH 031/232] Split CI: GitHub runners for tests, Depot for perf regression (#773) * Split CI: GitHub runners for tests, Depot for perf regression Unit/UI tests move to macos-15 (no queue wait, fast enough for test suites). Typing-lag regression stays on Depot in a new tests-depot job (needs stronger hardware). No duplicated test work between the two. * Fix Xcode selection: add pipefail guard, use sort|tail for consistency Address review comments: - tests job: add || true to ls pipeline so fallback works under pipefail - tests-depot job: use sort | tail -n 1 instead of head -n 1 * Move XCUITests from GitHub runner to Depot tests (macos-15) now runs unit tests only. tests-depot (Depot) runs UI tests and the typing-lag regression, reusing the same build. --- .github/workflows/ci.yml | 133 ++++++++++++++++++++++------- tests/test_ci_self_hosted_guard.sh | 6 +- 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c822148..6bac7a85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,9 +44,7 @@ jobs: run: bun tsc --noEmit tests: - # Never run Depot jobs for fork pull requests (avoid billing on external PRs). - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: depot-macos-latest + runs-on: macos-15 steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -56,17 +54,16 @@ jobs: - name: Select Xcode run: | set -euo pipefail - if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then - XCODE_DIR="/Applications/Xcode.app/Contents/Developer" - else - XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)" - if [ -n "$XCODE_APP" ]; then - XCODE_DIR="$XCODE_APP/Contents/Developer" - else - echo "No Xcode.app found under /Applications" >&2 - exit 1 - fi + XCODE_APP="$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort | tail -n 1 || true)" + if [ -z "$XCODE_APP" ]; then + XCODE_APP="/Applications/Xcode.app" fi + XCODE_DIR="$XCODE_APP/Contents/Developer" + if [ ! -d "$XCODE_DIR" ]; then + echo "No Xcode found under /Applications" >&2 + exit 1 + fi + echo "Selected: $XCODE_APP" echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" export DEVELOPER_DIR="$XCODE_DIR" xcodebuild -version @@ -99,21 +96,6 @@ jobs: rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework - - name: Create virtual display - run: | - set -euo pipefail - echo "=== Display before ===" - system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" - echo "" - clang -framework Foundation -framework CoreGraphics \ - -o /tmp/create-virtual-display scripts/create-virtual-display.m - /tmp/create-virtual-display & - VDISPLAY_PID=$! - echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" - sleep 3 - echo "=== Display after ===" - system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" - - name: Clean DerivedData run: | # Remove stale build cache to avoid incremental build errors @@ -183,13 +165,102 @@ jobs: fi fi + tests-depot: + # Never run Depot jobs for fork pull requests (avoid billing on external PRs). + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: depot-macos-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + submodules: recursive + + - name: Select Xcode + run: | + set -euo pipefail + if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then + XCODE_DIR="/Applications/Xcode.app/Contents/Developer" + else + XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | sort | tail -n 1 || true)" + if [ -n "$XCODE_APP" ]; then + XCODE_DIR="$XCODE_APP/Contents/Developer" + else + echo "No Xcode.app found under /Applications" >&2 + exit 1 + fi + fi + echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" + export DEVELOPER_DIR="$XCODE_DIR" + xcodebuild -version + + - name: Download pre-built GhosttyKit.xcframework + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 + exit 1 + fi + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz + test -d GhosttyKit.xcframework + + - name: Clean DerivedData + run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + + - name: Resolve Swift packages + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + rm -rf "$SOURCE_PACKAGES_DIR" + mkdir -p "$SOURCE_PACKAGES_DIR" + + for attempt in 1 2 3; do + if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -resolvePackageDependencies; then + exit 0 + fi + if [ "$attempt" -eq 3 ]; then + echo "Failed to resolve Swift packages after 3 attempts" >&2 + exit 1 + fi + echo "Package resolution failed on attempt $attempt, retrying..." + sleep $((attempt * 5)) + done + + - name: Create virtual display + run: | + set -euo pipefail + clang -framework Foundation -framework CoreGraphics \ + -o /tmp/create-virtual-display scripts/create-virtual-display.m + /tmp/create-virtual-display & + VDISPLAY_PID=$! + echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" + sleep 3 + - name: Run UI tests run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - # SidebarResizeUITests hangs on headless Depot runners (mouse drag - # simulation doesn't work without a physical display, even with virtual - # display). Skip it in CI; it runs fine on local machines. + # SidebarResizeUITests hangs on headless runners (mouse drag simulation + # doesn't work without a physical display, even with virtual display). + # Skip it in CI; it runs fine on local machines. run_ui_tests() { xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ diff --git a/tests/test_ci_self_hosted_guard.sh b/tests/test_ci_self_hosted_guard.sh index a05ee529..3b4f7f65 100755 --- a/tests/test_ci_self_hosted_guard.sh +++ b/tests/test_ci_self_hosted_guard.sh @@ -16,14 +16,14 @@ if ! grep -Fq "$EXPECTED_IF" "$WORKFLOW_FILE"; then fi if ! awk ' - /^ tests:/ { in_tests=1; next } + /^ tests-depot:/ { in_tests=1; next } in_tests && /^ [^[:space:]]/ { in_tests=0 } in_tests && /runs-on: depot-macos-latest/ { saw_depot=1 } in_tests && /github.event.pull_request.head.repo.full_name == github.repository/ { saw_guard=1 } END { exit !(saw_depot && saw_guard) } ' "$WORKFLOW_FILE"; then - echo "FAIL: tests block must keep both depot-macos-latest runner and fork guard" + echo "FAIL: tests-depot block must keep both depot-macos-latest runner and fork guard" exit 1 fi -echo "PASS: tests Depot runner fork guard is present" +echo "PASS: tests-depot Depot runner fork guard is present" From 56f184d02ebdd916cbbdc5e0e5b888a433cb3662 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:47:24 -0800 Subject: [PATCH 032/232] Add debug logging for cmd+click link handling (#777) * Add debug logging for cmd+click link handling Logs every decision point in the link opening pipeline: - mouseDown/mouseUp: modifier keys, click count, position - resolveTerminalOpenURLTarget: input URL, classification (external/embedded/fallback) - OPEN_URL action handler: routing decision (cmuxBrowser disabled, external, whitelist miss, embedded browser pane) All logs are #if DEBUG only via dlog(). * Update tagged build link format for Claude Code cmd+clickability Claude Code gets a markdown link with the real derived-data path (file:///tmp/cmux-/Build/Products/Debug/cmux%20DEV%20.app) which is cmd+clickable in cmux. Codex keeps the original format. * Add equal sign separators to Claude Code build link format * Fix cmd+click URL buffer overread in OPEN_URL handler cmux was using String(cString:) to decode the URL from GHOSTTY_ACTION_OPEN_URL, which reads until a null terminator. The C struct provides both a pointer and a length field (url + len), and the pointer is not guaranteed to be null-terminated. This caused cmux to read past the URL bytes into adjacent memory, producing corrupted URLs like "https://example.comcom" and multi-URL concatenations. Fix: use Data(bytes:count:) with the length field, matching how vanilla Ghostty decodes the same struct in Ghostty.Action.swift. * Address review feedback: extract debugModifierString helper --- CLAUDE.md | 10 ++- Sources/GhosttyTerminalView.swift | 104 ++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0c147fc9..297a5855 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,10 +16,18 @@ After making code changes, always run the reload script with a tag to launch the ./scripts/reload.sh --tag fix-zsh-autosuggestions ``` -When reporting a tagged reload result in chat, use this exact clickable format: +When reporting a tagged reload result in chat, use the format for your agent type: +**Claude Code** (markdown link with correct derived-data path, cmd+clickable): ```markdown ======================================================= +[cmux DEV .app](file:///tmp/cmux-/Build/Products/Debug/cmux%20DEV%20.app) +======================================================= +``` + +**Codex** (plain text format): +``` +======================================================= [: file:///tmp/cmux-.app](file:///tmp/cmux-.app) ======================================================= ``` diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index dba744af..d1085f97 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -209,9 +209,20 @@ final class GhosttyDefaultBackgroundNotificationDispatcher { func resolveTerminalOpenURLTarget(_ rawValue: String) -> TerminalOpenURLTarget? { let trimmed = rawValue.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { return nil } + #if DEBUG + dlog("link.resolve input=\(trimmed)") + #endif + guard !trimmed.isEmpty else { + #if DEBUG + dlog("link.resolve result=nil (empty)") + #endif + return nil + } if NSString(string: trimmed).isAbsolutePath { + #if DEBUG + dlog("link.resolve result=external(absolutePath) url=\(trimmed)") + #endif return .external(URL(fileURLWithPath: trimmed)) } @@ -219,21 +230,44 @@ func resolveTerminalOpenURLTarget(_ rawValue: String) -> TerminalOpenURLTarget? let scheme = parsed.scheme?.lowercased() { if scheme == "http" || scheme == "https" { guard BrowserInsecureHTTPSettings.normalizeHost(parsed.host ?? "") != nil else { + #if DEBUG + dlog("link.resolve result=external(invalidHost) url=\(parsed)") + #endif return .external(parsed) } + #if DEBUG + dlog("link.resolve result=embeddedBrowser url=\(parsed)") + #endif return .embeddedBrowser(parsed) } + #if DEBUG + dlog("link.resolve result=external(scheme=\(scheme)) url=\(parsed)") + #endif return .external(parsed) } if let webURL = resolveBrowserNavigableURL(trimmed) { guard BrowserInsecureHTTPSettings.normalizeHost(webURL.host ?? "") != nil else { + #if DEBUG + dlog("link.resolve result=external(bareHost-invalidHost) url=\(webURL)") + #endif return .external(webURL) } + #if DEBUG + dlog("link.resolve result=embeddedBrowser(bareHost) url=\(webURL)") + #endif return .embeddedBrowser(webURL) } - guard let fallback = URL(string: trimmed) else { return nil } + guard let fallback = URL(string: trimmed) else { + #if DEBUG + dlog("link.resolve result=nil (unparseable)") + #endif + return nil + } + #if DEBUG + dlog("link.resolve result=external(fallback) url=\(fallback)") + #endif return .external(fallback) } @@ -1355,25 +1389,48 @@ class GhosttyApp { case GHOSTTY_ACTION_OPEN_URL: let openUrl = action.action.open_url guard let cstr = openUrl.url else { return false } - let urlString = String(cString: cstr) - guard let target = resolveTerminalOpenURLTarget(urlString) else { return false } + let urlString = String( + data: Data(bytes: cstr, count: Int(openUrl.len)), + encoding: .utf8 + ) ?? "" + #if DEBUG + dlog("link.openURL raw=\(urlString)") + #endif + guard let target = resolveTerminalOpenURLTarget(urlString) else { + #if DEBUG + dlog("link.openURL resolve failed, returning false") + #endif + return false + } if !BrowserLinkOpenSettings.openTerminalLinksInCmuxBrowser() { + #if DEBUG + dlog("link.openURL cmuxBrowser=disabled, opening externally url=\(target.url)") + #endif return performOnMain { NSWorkspace.shared.open(target.url) } } switch target { case let .external(url): + #if DEBUG + dlog("link.openURL target=external, opening externally url=\(url)") + #endif return performOnMain { NSWorkspace.shared.open(url) } case let .embeddedBrowser(url): if BrowserLinkOpenSettings.shouldOpenExternally(url) { + #if DEBUG + dlog("link.openURL target=embedded but shouldOpenExternally=true url=\(url)") + #endif return performOnMain { NSWorkspace.shared.open(url) } } guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { + #if DEBUG + dlog("link.openURL target=embedded but normalizeHost=nil host=\(url.host ?? "nil") url=\(url)") + #endif return performOnMain { NSWorkspace.shared.open(url) } @@ -1381,21 +1438,41 @@ class GhosttyApp { // If a host whitelist is configured and this host isn't in it, open externally. if !BrowserLinkOpenSettings.hostMatchesWhitelist(host) { + #if DEBUG + dlog("link.openURL target=embedded but hostWhitelist miss host=\(host) url=\(url)") + #endif return performOnMain { NSWorkspace.shared.open(url) } } guard let tabId = surfaceView.tabId, - let surfaceId = surfaceView.terminalSurface?.id else { return false } + let surfaceId = surfaceView.terminalSurface?.id else { + #if DEBUG + dlog("link.openURL target=embedded but tabId/surfaceId=nil") + #endif + return false + } + #if DEBUG + dlog("link.openURL target=embedded, opening in browser pane host=\(host) url=\(url) tabId=\(tabId) surfaceId=\(surfaceId)") + #endif return performOnMain { guard let app = AppDelegate.shared, let tabManager = app.tabManagerFor(tabId: tabId) ?? app.tabManager, let workspace = tabManager.tabs.first(where: { $0.id == tabId }) else { + #if DEBUG + dlog("link.openURL embedded but workspace lookup failed tabId=\(tabId)") + #endif return false } if let targetPane = workspace.preferredBrowserTargetPane(fromPanelId: surfaceId) { + #if DEBUG + dlog("link.openURL opening in existing browser pane=\(targetPane)") + #endif return workspace.newBrowserSurface(inPane: targetPane, url: url, focus: true) != nil } else { + #if DEBUG + dlog("link.openURL opening as new browser split from surface=\(surfaceId)") + #endif return workspace.newBrowserSplit(from: surfaceId, orientation: .horizontal, url: url) != nil } } @@ -3431,9 +3508,21 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { // MARK: - Mouse Handling + #if DEBUG + private func debugModifierString(_ flags: NSEvent.ModifierFlags) -> String { + [ + flags.contains(.command) ? "cmd" : nil, + flags.contains(.shift) ? "shift" : nil, + flags.contains(.control) ? "ctrl" : nil, + flags.contains(.option) ? "opt" : nil, + ].compactMap { $0 }.joined(separator: "+") + } + #endif + override func mouseDown(with event: NSEvent) { #if DEBUG - dlog("terminal.mouseDown surface=\(terminalSurface?.id.uuidString.prefix(5) ?? "nil")") + let debugPoint = convert(event.locationInWindow, from: nil) + dlog("terminal.mouseDown surface=\(terminalSurface?.id.uuidString.prefix(5) ?? "nil") mods=[\(debugModifierString(event.modifierFlags))] clickCount=\(event.clickCount) point=(\(String(format: "%.0f", debugPoint.x)),\(String(format: "%.0f", debugPoint.y)))") #endif window?.makeFirstResponder(self) guard let surface = surface else { return } @@ -3443,6 +3532,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } override func mouseUp(with event: NSEvent) { + #if DEBUG + dlog("terminal.mouseUp surface=\(terminalSurface?.id.uuidString.prefix(5) ?? "nil") mods=[\(debugModifierString(event.modifierFlags))]") + #endif guard let surface = surface else { return } _ = ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, modsFromEvent(event)) } From fe3e2d06d9823d2817e41eebac75c2ef51d99d8e Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:13:03 -0800 Subject: [PATCH 033/232] Trigger nightly on push to main, switch to GitHub macos-15 runner (#779) Build immediately on merge instead of waiting for the hourly cron. Concurrency group cancels in-progress builds when new commits land. Depot macos runner replaced with GitHub macos-15 (similar perf, simpler). --- .github/workflows/nightly.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 64b3fd35..702c5d8f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,6 +1,8 @@ name: Nightly macOS build on: + push: + branches: [main] schedule: # Every hour at :30. The 'decide' job skips if main has no new commits. - cron: "30 * * * *" @@ -12,6 +14,10 @@ on: default: false type: boolean +concurrency: + group: nightly-build + cancel-in-progress: true + permissions: contents: write @@ -81,7 +87,7 @@ jobs: build-sign-notarize-nightly: needs: decide if: needs.decide.outputs.should_build == 'true' - runs-on: depot-macos-latest + runs-on: macos-15 steps: - name: Checkout main uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 From cd0c3cfa93fc3809b980be11e4e2354e5f101e9f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:16:38 -0800 Subject: [PATCH 034/232] Add E2E test workflow with video recording (#778) * Add E2E test workflow with video recording and issue posting New workflow_dispatch workflow (test-e2e.yml) that runs XCUITests on GitHub-hosted macos-15 runners, records the virtual display, uploads the video as an artifact, and posts results as an issue on cmux-dev-artifacts. Includes scripts/run-e2e.sh for convenient triggering from the terminal. * Print issue URL in workflow annotation and run-e2e.sh output - Capture gh issue create output URL, print as ::notice annotation - Search issues by run ID instead of grabbing most recent --- .github/workflows/test-e2e.yml | 272 +++++++++++++++++++++++++++++++++ scripts/run-e2e.sh | 101 ++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 .github/workflows/test-e2e.yml create mode 100755 scripts/run-e2e.sh diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml new file mode 100644 index 00000000..4d8483eb --- /dev/null +++ b/.github/workflows/test-e2e.yml @@ -0,0 +1,272 @@ +name: E2E test with video recording + +on: + workflow_dispatch: + inputs: + ref: + description: Branch or SHA to test + required: false + default: "" + test_filter: + description: "Test class or class/method (e.g. UpdatePillUITests or UpdatePillUITests/testSomething)" + required: true + test_timeout: + description: "Per-test timeout in seconds" + required: false + default: "120" + record_video: + description: Record the virtual display during tests + required: false + default: true + type: boolean + +jobs: + e2e: + runs-on: macos-15 + env: + TEST_REF: ${{ inputs.ref || github.ref }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ inputs.ref || github.ref }} + submodules: recursive + + - name: Capture SHA + id: sha + run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Select Xcode + run: | + set -euo pipefail + if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then + XCODE_DIR="/Applications/Xcode.app/Contents/Developer" + else + XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)" + if [ -n "$XCODE_APP" ]; then + XCODE_DIR="$XCODE_APP/Contents/Developer" + else + echo "No Xcode.app found under /Applications" >&2 + exit 1 + fi + fi + echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" + export DEVELOPER_DIR="$XCODE_DIR" + xcodebuild -version + xcrun --sdk macosx --show-sdk-path + + - name: Download pre-built GhosttyKit.xcframework + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + GHOSTTY_SHA=$(git -C ghostty rev-parse HEAD) + TAG="xcframework-$GHOSTTY_SHA" + URL="https://github.com/manaflow-ai/ghostty/releases/download/$TAG/GhosttyKit.xcframework.tar.gz" + echo "Downloading xcframework for ghostty $GHOSTTY_SHA" + MAX_RETRIES=30 + RETRY_DELAY=20 + for i in $(seq 1 $MAX_RETRIES); do + if curl -fSL -o GhosttyKit.xcframework.tar.gz "$URL"; then + echo "Download succeeded on attempt $i" + break + fi + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "Failed to download xcframework after $MAX_RETRIES attempts" >&2 + exit 1 + fi + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + done + tar xzf GhosttyKit.xcframework.tar.gz + rm GhosttyKit.xcframework.tar.gz + test -d GhosttyKit.xcframework + + - name: Create virtual display + run: | + set -euo pipefail + echo "=== Display before ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" + echo "" + clang -framework Foundation -framework CoreGraphics \ + -o /tmp/create-virtual-display scripts/create-virtual-display.m + /tmp/create-virtual-display & + VDISPLAY_PID=$! + echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" + sleep 3 + echo "=== Display after ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" + + - name: Grant TCC screen recording permission + continue-on-error: true + run: | + # Ephemeral CI runner, TCC database is writable + TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db" + if [ -f "$TCC_DB" ]; then + sqlite3 "$TCC_DB" "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version) VALUES ('kTCCServiceScreenCapture', '/usr/sbin/screencapture', 1, 2, 4, 1);" 2>/dev/null || true + fi + + - name: Start screen recording + if: ${{ inputs.record_video }} + run: | + screencapture -v -D 1 -x /tmp/test-recording.mov & + RECORD_PID=$! + echo "RECORD_PID=$RECORD_PID" >> "$GITHUB_ENV" + sleep 2 + echo "Recording started (PID $RECORD_PID)" + + - name: Clean DerivedData + run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + + - name: Resolve Swift packages + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + rm -rf "$SOURCE_PACKAGES_DIR" + mkdir -p "$SOURCE_PACKAGES_DIR" + for attempt in 1 2 3; do + if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -resolvePackageDependencies; then + exit 0 + fi + if [ "$attempt" -eq 3 ]; then + echo "Failed to resolve Swift packages after 3 attempts" >&2 + exit 1 + fi + echo "Package resolution failed on attempt $attempt, retrying..." + sleep $((attempt * 5)) + done + + - name: Run UI tests + id: tests + env: + TEST_FILTER: ${{ inputs.test_filter }} + TEST_TIMEOUT: ${{ inputs.test_timeout || '120' }} + run: | + set -euo pipefail + SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" + + ONLY_TESTING="-only-testing:cmuxUITests/$TEST_FILTER" + + set +e + OUTPUT=$(xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ + -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ + -disableAutomaticPackageResolution \ + -destination "platform=macOS" \ + -maximum-test-execution-time-allowance "$TEST_TIMEOUT" \ + $ONLY_TESTING test 2>&1) + EXIT_CODE=$? + set -e + + echo "$OUTPUT" + + # Save summary for the issue + SUMMARY=$(echo "$OUTPUT" | grep -E "(Test Suite|Executed|FAIL|PASS)" | tail -20) + { + echo "test_summary<> "$GITHUB_OUTPUT" + + if [ "$EXIT_CODE" -eq 0 ]; then + echo "test_result=passed" >> "$GITHUB_OUTPUT" + else + echo "test_result=failed" >> "$GITHUB_OUTPUT" + # Save full output for the issue body + { + echo "test_output<> "$GITHUB_OUTPUT" + exit 1 + fi + + - name: Stop screen recording + if: ${{ always() && inputs.record_video && env.RECORD_PID != '' }} + run: | + kill -INT "$RECORD_PID" 2>/dev/null || true + # Wait for screencapture to finalize the .mov file + for i in $(seq 1 30); do + if ! kill -0 "$RECORD_PID" 2>/dev/null; then + echo "Recording stopped after ${i}s" + break + fi + sleep 1 + done + kill -9 "$RECORD_PID" 2>/dev/null || true + ls -lh /tmp/test-recording.mov 2>/dev/null || echo "No recording file found" + + - name: Upload recording artifact + if: ${{ always() && inputs.record_video }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: test-recording + path: /tmp/test-recording.mov + if-no-files-found: warn + + - name: Post results to cmux-dev-artifacts + if: always() + env: + GH_TOKEN: ${{ secrets.DEV_ARTIFACTS_TOKEN }} + TEST_RESULT: ${{ steps.tests.outputs.test_result || 'failed' }} + TEST_SUMMARY: ${{ steps.tests.outputs.test_summary }} + TEST_OUTPUT: ${{ steps.tests.outputs.test_output }} + TEST_FILTER: ${{ inputs.test_filter }} + COMMIT_SHA: ${{ steps.sha.outputs.sha }} + RUN_ID: ${{ github.run_id }} + RECORD_VIDEO: ${{ inputs.record_video }} + run: | + set -euo pipefail + + LABEL="$TEST_RESULT" + if [ "$TEST_RESULT" = "passed" ]; then + STATUS_EMOJI="PASSED" + else + STATUS_EMOJI="FAILED" + fi + + REF_DISPLAY="${{ inputs.ref || github.ref_name }}" + RUN_URL="https://github.com/${{ github.repository }}/actions/runs/$RUN_ID" + ARTIFACT_URL="$RUN_URL#artifacts" + + # Build issue body (no leading whitespace) + BODY="**Status:** $STATUS_EMOJI + **Ref:** \`$REF_DISPLAY\` + **SHA:** [\`${COMMIT_SHA:0:12}\`](https://github.com/${{ github.repository }}/commit/$COMMIT_SHA) + **Test:** \`$TEST_FILTER\` + **Workflow run:** $RUN_URL" + + if [ "$RECORD_VIDEO" = "true" ]; then + BODY="$BODY + **Recording:** [Download from artifacts]($ARTIFACT_URL)" + fi + + if [ -n "$TEST_OUTPUT" ]; then + BODY="$BODY + +
Test output (last 200 lines) + + \`\`\` + $TEST_OUTPUT + \`\`\` + +
" + fi + + if [ -n "$TEST_SUMMARY" ]; then + BODY="$BODY + + \`\`\` + $TEST_SUMMARY + \`\`\`" + fi + + ISSUE_URL=$(gh issue create \ + --repo manaflow-ai/cmux-dev-artifacts \ + --title "[$STATUS_EMOJI] $TEST_FILTER @ ${COMMIT_SHA:0:7} ($REF_DISPLAY)" \ + --body "$BODY" \ + --label "$LABEL") + + echo "Issue posted: $ISSUE_URL" + echo "::notice title=Test Result Issue::$ISSUE_URL" diff --git a/scripts/run-e2e.sh b/scripts/run-e2e.sh new file mode 100755 index 00000000..4d26c416 --- /dev/null +++ b/scripts/run-e2e.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Trigger the test-e2e.yml workflow and optionally wait for results. +# +# Usage: +# ./scripts/run-e2e.sh UpdatePillUITests +# ./scripts/run-e2e.sh UpdatePillUITests --wait +# ./scripts/run-e2e.sh UpdatePillUITests/testFoo --ref my-branch +# ./scripts/run-e2e.sh UpdatePillUITests --no-video --timeout 300 +set -euo pipefail + +REPO="manaflow-ai/cmux" +WORKFLOW="test-e2e.yml" + +# Defaults +REF="" +WAIT=false +RECORD_VIDEO=true +TIMEOUT=120 + +usage() { + cat < [options] + +Arguments: + test_filter Test class or class/method (e.g. UpdatePillUITests) + +Options: + --ref Branch or SHA to test (default: current branch) + --wait Wait for the run to complete and print result + --no-video Disable video recording + --timeout Per-test timeout in seconds (default: 120) + -h, --help Show this help +EOF + exit 0 +} + +if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + usage +fi + +TEST_FILTER="$1" +shift + +while [ $# -gt 0 ]; do + case "$1" in + --ref) + REF="$2" + shift 2 + ;; + --wait) + WAIT=true + shift + ;; + --no-video) + RECORD_VIDEO=false + shift + ;; + --timeout) + TIMEOUT="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" >&2 + usage + ;; + esac +done + +# Build workflow dispatch fields +FIELDS=(-f "test_filter=$TEST_FILTER" -f "record_video=$RECORD_VIDEO" -f "test_timeout=$TIMEOUT") +if [ -n "$REF" ]; then + FIELDS+=(-f "ref=$REF") +fi + +echo "Triggering $WORKFLOW with test_filter=$TEST_FILTER ref=${REF:-} video=$RECORD_VIDEO timeout=$TIMEOUT" +gh workflow run "$WORKFLOW" --repo "$REPO" "${FIELDS[@]}" + +# Wait a moment for the run to register +sleep 3 + +# Get the latest run ID +RUN_ID=$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --limit 1 --json databaseId --jq '.[0].databaseId') +RUN_URL="https://github.com/$REPO/actions/runs/$RUN_ID" + +echo "Run: $RUN_URL" + +if [ "$WAIT" = true ]; then + echo "Waiting for run to complete..." + gh run watch --repo "$REPO" "$RUN_ID" --exit-status || true + + STATUS=$(gh run view --repo "$REPO" "$RUN_ID" --json conclusion --jq '.conclusion') + echo "" + echo "Result: $STATUS" + echo "Run: $RUN_URL" + + # Find the issue created for this run (search by run ID in body) + ISSUE_URL=$(gh search issues "$RUN_ID" --repo manaflow-ai/cmux-dev-artifacts --limit 1 --json url --jq '.[0].url' 2>/dev/null || true) + if [ -n "$ISSUE_URL" ]; then + echo "Issue: $ISSUE_URL" + fi +fi From 3cb101f1c83c9f52287a1ff296817187124fd9d9 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:36:07 -0800 Subject: [PATCH 035/232] Switch screen recording from screencapture to ffmpeg AVFoundation (#780) screencapture -v produces a 0-second file on GitHub Actions M-series runners (IOServiceMatchingfailed for AppleM2ScalerParavirtDriver). ffmpeg with avfoundation input handles virtual displays correctly. --- .github/workflows/test-e2e.yml | 37 ++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 4d8483eb..bae601dd 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -100,20 +100,38 @@ jobs: - name: Grant TCC screen recording permission continue-on-error: true run: | - # Ephemeral CI runner, TCC database is writable TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db" if [ -f "$TCC_DB" ]; then - sqlite3 "$TCC_DB" "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version) VALUES ('kTCCServiceScreenCapture', '/usr/sbin/screencapture', 1, 2, 4, 1);" 2>/dev/null || true + for client in /usr/sbin/screencapture /opt/homebrew/bin/ffmpeg /usr/local/bin/ffmpeg; do + sqlite3 "$TCC_DB" "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version) VALUES ('kTCCServiceScreenCapture', '$client', 1, 2, 4, 1);" 2>/dev/null || true + done fi - name: Start screen recording if: ${{ inputs.record_video }} run: | - screencapture -v -D 1 -x /tmp/test-recording.mov & + # List available AVFoundation devices for debugging + ffmpeg -f avfoundation -list_devices true -i "" 2>&1 || true + + # Find the screen capture device index (usually "Capture screen 0") + SCREEN_INDEX=$(ffmpeg -f avfoundation -list_devices true -i "" 2>&1 \ + | grep -n "Capture screen" | head -1 | sed 's/.*\[\([0-9]*\)\].*/\1/' || echo "1") + echo "Using AVFoundation screen device index: $SCREEN_INDEX" + + ffmpeg -f avfoundation -framerate 10 -capture_cursor 1 \ + -i "${SCREEN_INDEX}:none" \ + -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ + /tmp/test-recording.mp4 /tmp/ffmpeg.log 2>&1 & RECORD_PID=$! echo "RECORD_PID=$RECORD_PID" >> "$GITHUB_ENV" sleep 2 - echo "Recording started (PID $RECORD_PID)" + + if kill -0 "$RECORD_PID" 2>/dev/null; then + echo "Recording started (PID $RECORD_PID)" + else + echo "::warning::ffmpeg failed to start recording" + cat /tmp/ffmpeg.log + fi - name: Clean DerivedData run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* @@ -185,9 +203,9 @@ jobs: - name: Stop screen recording if: ${{ always() && inputs.record_video && env.RECORD_PID != '' }} run: | + # Send quit signal to ffmpeg for clean finalization kill -INT "$RECORD_PID" 2>/dev/null || true - # Wait for screencapture to finalize the .mov file - for i in $(seq 1 30); do + for i in $(seq 1 15); do if ! kill -0 "$RECORD_PID" 2>/dev/null; then echo "Recording stopped after ${i}s" break @@ -195,14 +213,17 @@ jobs: sleep 1 done kill -9 "$RECORD_PID" 2>/dev/null || true - ls -lh /tmp/test-recording.mov 2>/dev/null || echo "No recording file found" + echo "=== ffmpeg log ===" + cat /tmp/ffmpeg.log 2>/dev/null || true + echo "=== recording file ===" + ls -lh /tmp/test-recording.mp4 2>/dev/null || echo "No recording file found" - name: Upload recording artifact if: ${{ always() && inputs.record_video }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-recording - path: /tmp/test-recording.mov + path: /tmp/test-recording.mp4 if-no-files-found: warn - name: Post results to cmux-dev-artifacts From 63e7cc7faa14083f3d3218d4ab04b270dd7f29ba Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:45:22 -0800 Subject: [PATCH 036/232] Install ffmpeg via brew for screen recording (#781) macos-15 GitHub runners don't have ffmpeg pre-installed. --- .github/workflows/test-e2e.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index bae601dd..82341ae9 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -97,12 +97,21 @@ jobs: echo "=== Display after ===" system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" + - name: Install ffmpeg + if: ${{ inputs.record_video }} + run: | + brew install --quiet ffmpeg + FFMPEG_PATH=$(which ffmpeg) + echo "ffmpeg: $FFMPEG_PATH" + ffmpeg -version | head -1 + echo "FFMPEG_PATH=$FFMPEG_PATH" >> "$GITHUB_ENV" + - name: Grant TCC screen recording permission continue-on-error: true run: | TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db" if [ -f "$TCC_DB" ]; then - for client in /usr/sbin/screencapture /opt/homebrew/bin/ffmpeg /usr/local/bin/ffmpeg; do + for client in /usr/sbin/screencapture "${FFMPEG_PATH:-/opt/homebrew/bin/ffmpeg}" /opt/homebrew/bin/ffmpeg /usr/local/bin/ffmpeg; do sqlite3 "$TCC_DB" "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version) VALUES ('kTCCServiceScreenCapture', '$client', 1, 2, 4, 1);" 2>/dev/null || true done fi From d77299c220928a2f7c5065a6d703b2b4fdd20837 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:55:03 -0800 Subject: [PATCH 037/232] Clean up E2E ffmpeg device detection (#782) * Install ffmpeg via brew for screen recording macos-15 GitHub runners don't have ffmpeg pre-installed. * Clean up ffmpeg device detection and add fallback Suppress noisy device listing errors, add fallback to index 1 if detected index fails, upgrade warning to error on total failure. --- .github/workflows/test-e2e.yml | 42 +++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 82341ae9..fd10a563 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -119,28 +119,44 @@ jobs: - name: Start screen recording if: ${{ inputs.record_video }} run: | - # List available AVFoundation devices for debugging - ffmpeg -f avfoundation -list_devices true -i "" 2>&1 || true + # Detect screen capture device index. ffmpeg -list_devices always + # exits non-zero; redirect noise to a temp file and parse it. + DEVLIST=$( ffmpeg -f avfoundation -list_devices true -i "" 2>&1 || true ) + echo "Available devices:" + echo "$DEVLIST" | grep -E "AVFoundation|Capture screen" - # Find the screen capture device index (usually "Capture screen 0") - SCREEN_INDEX=$(ffmpeg -f avfoundation -list_devices true -i "" 2>&1 \ - | grep -n "Capture screen" | head -1 | sed 's/.*\[\([0-9]*\)\].*/\1/' || echo "1") - echo "Using AVFoundation screen device index: $SCREEN_INDEX" + SCREEN_INDEX=$( echo "$DEVLIST" | grep "Capture screen" | head -1 \ + | sed 's/.*\[\([0-9]*\)\].*/\1/' ) + SCREEN_INDEX="${SCREEN_INDEX:-0}" + echo "Using screen device index: $SCREEN_INDEX" - ffmpeg -f avfoundation -framerate 10 -capture_cursor 1 \ - -i "${SCREEN_INDEX}:none" \ - -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ - /tmp/test-recording.mp4 /tmp/ffmpeg.log 2>&1 & - RECORD_PID=$! - echo "RECORD_PID=$RECORD_PID" >> "$GITHUB_ENV" + # Start recording. Try detected index, fall back to 1 if it dies immediately. + start_recording() { + ffmpeg -f avfoundation -framerate 10 -capture_cursor 1 \ + -i "$1:none" \ + -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ + /tmp/test-recording.mp4 /tmp/ffmpeg.log 2>&1 & + echo $! + } + + RECORD_PID=$(start_recording "$SCREEN_INDEX") sleep 2 + if ! kill -0 "$RECORD_PID" 2>/dev/null; then + echo "Index $SCREEN_INDEX failed, trying index 1" + cat /tmp/ffmpeg.log + rm -f /tmp/test-recording.mp4 + RECORD_PID=$(start_recording 1) + sleep 2 + fi + if kill -0 "$RECORD_PID" 2>/dev/null; then echo "Recording started (PID $RECORD_PID)" else - echo "::warning::ffmpeg failed to start recording" + echo "::error::ffmpeg screen recording failed to start" cat /tmp/ffmpeg.log fi + echo "RECORD_PID=$RECORD_PID" >> "$GITHUB_ENV" - name: Clean DerivedData run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* From 37dc43a6de994d3cd9a9fe04382235d7d28f0a6b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:14:27 -0800 Subject: [PATCH 038/232] Fix TCC dialog, trim black frames, add macos-26 runner option (#784) - Grant kTCCServiceScreenCapture in system-level TCC database (sudo) and pre-date ScreenCaptureApprovals.plist to suppress Sequoia's private window picker dialog - Move recording start to right before xcodebuild test (skip build time) - Trim leading black frames from video using ffmpeg blackdetect - Add runner input: macos-15 (Sequoia) or macos-26 (Tahoe) --- .github/workflows/test-e2e.yml | 141 +++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 51 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index fd10a563..48b5e4de 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -19,10 +19,18 @@ on: required: false default: true type: boolean + runner: + description: "Runner OS (macos-15 or macos-26)" + required: false + default: "macos-15" + type: choice + options: + - macos-15 + - macos-26 jobs: e2e: - runs-on: macos-15 + runs-on: ${{ inputs.runner || 'macos-15' }} env: TEST_REF: ${{ inputs.ref || github.ref }} steps: @@ -107,56 +115,38 @@ jobs: echo "FFMPEG_PATH=$FFMPEG_PATH" >> "$GITHUB_ENV" - name: Grant TCC screen recording permission + if: ${{ inputs.record_video }} continue-on-error: true run: | - TCC_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db" - if [ -f "$TCC_DB" ]; then - for client in /usr/sbin/screencapture "${FFMPEG_PATH:-/opt/homebrew/bin/ffmpeg}" /opt/homebrew/bin/ffmpeg /usr/local/bin/ffmpeg; do - sqlite3 "$TCC_DB" "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version) VALUES ('kTCCServiceScreenCapture', '$client', 1, 2, 4, 1);" 2>/dev/null || true + FFMPEG_BIN="${FFMPEG_PATH:-/opt/homebrew/bin/ffmpeg}" + + # System-level TCC database (where kTCCServiceScreenCapture lives) + SYS_TCC="/Library/Application Support/com.apple.TCC/TCC.db" + if [ -f "$SYS_TCC" ]; then + echo "Granting screen capture in system TCC database" + for client in "$FFMPEG_BIN" /opt/homebrew/bin/ffmpeg /usr/local/bin/ffmpeg /usr/sbin/screencapture; do + sudo sqlite3 "$SYS_TCC" \ + "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version, csreq, policy_id, indirect_object_identifier_type, indirect_object_identifier, indirect_object_code_identity, flags, last_modified) VALUES ('kTCCServiceScreenCapture', '$client', 1, 2, 4, 1, NULL, NULL, 0, 'UNUSED', NULL, 0, $(date +%s));" 2>&1 || echo " (failed for $client)" done fi - - name: Start screen recording - if: ${{ inputs.record_video }} - run: | - # Detect screen capture device index. ffmpeg -list_devices always - # exits non-zero; redirect noise to a temp file and parse it. - DEVLIST=$( ffmpeg -f avfoundation -list_devices true -i "" 2>&1 || true ) - echo "Available devices:" - echo "$DEVLIST" | grep -E "AVFoundation|Capture screen" - - SCREEN_INDEX=$( echo "$DEVLIST" | grep "Capture screen" | head -1 \ - | sed 's/.*\[\([0-9]*\)\].*/\1/' ) - SCREEN_INDEX="${SCREEN_INDEX:-0}" - echo "Using screen device index: $SCREEN_INDEX" - - # Start recording. Try detected index, fall back to 1 if it dies immediately. - start_recording() { - ffmpeg -f avfoundation -framerate 10 -capture_cursor 1 \ - -i "$1:none" \ - -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ - /tmp/test-recording.mp4 /tmp/ffmpeg.log 2>&1 & - echo $! - } - - RECORD_PID=$(start_recording "$SCREEN_INDEX") - sleep 2 - - if ! kill -0 "$RECORD_PID" 2>/dev/null; then - echo "Index $SCREEN_INDEX failed, trying index 1" - cat /tmp/ffmpeg.log - rm -f /tmp/test-recording.mp4 - RECORD_PID=$(start_recording 1) - sleep 2 + # User-level TCC database (fallback) + USER_TCC="$HOME/Library/Application Support/com.apple.TCC/TCC.db" + if [ -f "$USER_TCC" ]; then + echo "Granting screen capture in user TCC database" + for client in "$FFMPEG_BIN" /opt/homebrew/bin/ffmpeg /usr/local/bin/ffmpeg; do + sqlite3 "$USER_TCC" \ + "INSERT OR REPLACE INTO access (service, client, client_type, auth_value, auth_reason, auth_version, csreq, policy_id, indirect_object_identifier_type, indirect_object_identifier, indirect_object_code_identity, flags, last_modified) VALUES ('kTCCServiceScreenCapture', '$client', 1, 2, 4, 1, NULL, NULL, 0, 'UNUSED', NULL, 0, $(date +%s));" 2>&1 || echo " (failed for $client)" + done fi - if kill -0 "$RECORD_PID" 2>/dev/null; then - echo "Recording started (PID $RECORD_PID)" - else - echo "::error::ffmpeg screen recording failed to start" - cat /tmp/ffmpeg.log + # Suppress Sequoia's ScreenCaptureApprovals prompt by pre-dating approval + APPROVALS_PLIST="$HOME/Library/Group Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist" + if [ -d "$(dirname "$APPROVALS_PLIST")" ]; then + echo "Pre-dating ScreenCaptureApprovals" + # Set approval date far in the future so the monthly prompt never fires + defaults write "$APPROVALS_PLIST" "$FFMPEG_BIN" -date "3000-01-01T00:00:00Z" 2>&1 || echo " (failed)" fi - echo "RECORD_PID=$RECORD_PID" >> "$GITHUB_ENV" - name: Clean DerivedData run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* @@ -186,12 +176,39 @@ jobs: env: TEST_FILTER: ${{ inputs.test_filter }} TEST_TIMEOUT: ${{ inputs.test_timeout || '120' }} + RECORD_VIDEO: ${{ inputs.record_video }} run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - ONLY_TESTING="-only-testing:cmuxUITests/$TEST_FILTER" + # Start recording right before the test (after build/resolve) + if [ "$RECORD_VIDEO" = "true" ]; then + DEVLIST=$( ffmpeg -f avfoundation -list_devices true -i "" 2>&1 || true ) + echo "Available devices:" + echo "$DEVLIST" | grep -E "AVFoundation|Capture screen" + + SCREEN_INDEX=$( echo "$DEVLIST" | grep "Capture screen" | head -1 \ + | sed 's/.*\[\([0-9]*\)\].*/\1/' ) + SCREEN_INDEX="${SCREEN_INDEX:-0}" + echo "Using screen device index: $SCREEN_INDEX" + + ffmpeg -f avfoundation -framerate 10 -capture_cursor 1 \ + -i "${SCREEN_INDEX}:none" \ + -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ + /tmp/test-recording-raw.mp4 /tmp/ffmpeg.log 2>&1 & + RECORD_PID=$! + echo "RECORD_PID=$RECORD_PID" >> "$GITHUB_ENV" + sleep 2 + + if kill -0 "$RECORD_PID" 2>/dev/null; then + echo "Recording started (PID $RECORD_PID)" + else + echo "::warning::ffmpeg screen recording failed to start" + cat /tmp/ffmpeg.log + fi + fi + set +e OUTPUT=$(xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug \ -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ @@ -225,10 +242,10 @@ jobs: exit 1 fi - - name: Stop screen recording + - name: Stop recording and trim if: ${{ always() && inputs.record_video && env.RECORD_PID != '' }} run: | - # Send quit signal to ffmpeg for clean finalization + # Stop ffmpeg cleanly kill -INT "$RECORD_PID" 2>/dev/null || true for i in $(seq 1 15); do if ! kill -0 "$RECORD_PID" 2>/dev/null; then @@ -238,10 +255,33 @@ jobs: sleep 1 done kill -9 "$RECORD_PID" 2>/dev/null || true - echo "=== ffmpeg log ===" - cat /tmp/ffmpeg.log 2>/dev/null || true - echo "=== recording file ===" - ls -lh /tmp/test-recording.mp4 2>/dev/null || echo "No recording file found" + + echo "=== raw recording ===" + ls -lh /tmp/test-recording-raw.mp4 2>/dev/null || { echo "No recording file"; exit 0; } + + # Trim: detect first non-black frame and cut from there + BLACK_END=$(ffmpeg -i /tmp/test-recording-raw.mp4 \ + -vf "blackdetect=d=0.3:pic_th=0.95:pix_th=0.1" \ + -an -f null - 2>&1 \ + | grep "black_end" | tail -1 \ + | sed 's/.*black_end:\([0-9.]*\).*/\1/' || true) + + if [ -n "$BLACK_END" ] && [ "$BLACK_END" != "0" ]; then + echo "Trimming ${BLACK_END}s of black frames from start" + ffmpeg -y -i /tmp/test-recording-raw.mp4 -ss "$BLACK_END" \ + -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ + /tmp/test-recording.mp4 2>/dev/null + else + echo "No black frames detected, using raw recording" + mv /tmp/test-recording-raw.mp4 /tmp/test-recording.mp4 + fi + + echo "=== final recording ===" + ls -lh /tmp/test-recording.mp4 + # Print duration + ffprobe -v error -show_entries format=duration \ + -of default=noprint_wrappers=1:nokey=1 /tmp/test-recording.mp4 2>/dev/null \ + | xargs -I{} echo "Duration: {}s" - name: Upload recording artifact if: ${{ always() && inputs.record_video }} @@ -276,7 +316,6 @@ jobs: RUN_URL="https://github.com/${{ github.repository }}/actions/runs/$RUN_ID" ARTIFACT_URL="$RUN_URL#artifacts" - # Build issue body (no leading whitespace) BODY="**Status:** $STATUS_EMOJI **Ref:** \`$REF_DISPLAY\` **SHA:** [\`${COMMIT_SHA:0:12}\`](https://github.com/${{ github.repository }}/commit/$COMMIT_SHA) From a086ebc0f391b81024f75076c4d005123030ee49 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:48:01 -0800 Subject: [PATCH 039/232] Add @tonkotsuboy_com testimonial to wall of love (#785) --- web/app/testimonials.tsx | 10 ++++++++++ web/public/avatars/tonkotsuboy_com.jpg | Bin 0 -> 24790 bytes 2 files changed, 10 insertions(+) create mode 100644 web/public/avatars/tonkotsuboy_com.jpg diff --git a/web/app/testimonials.tsx b/web/app/testimonials.tsx index fa9619f7..1cae3b08 100644 --- a/web/app/testimonials.tsx +++ b/web/app/testimonials.tsx @@ -133,6 +133,16 @@ export const testimonials = [ url: "https://x.com/connorelsea/status/2026867085750440390", platform: "x" as const, }, + { + name: "鹿野 壮 Takeshi Kano", + handle: "@tonkotsuboy_com", + avatar: "/avatars/tonkotsuboy_com.jpg", + text: "年初にWarpからGhosttyに乗り換えたけど、今はcmuxに乗り換えた\uD83D\uDCBB 垂直タブが便利で、Claude Codeのタスクの終了が通知されるのがありがたい。Ghosttyベースだから爆速動作はそのまま。ghosttyでやったブランチ表示や補完もそのまま使える", + translation: + "I switched from Warp to Ghostty at the start of the year, but now I've switched to cmux. The vertical tabs are convenient, and I appreciate getting notified when Claude Code tasks finish. It's Ghostty-based so the blazing fast performance carries over. Branch display and completions I set up in Ghostty still work too.", + url: "https://x.com/tonkotsuboy_com/status/2028458464801108212", + platform: "x" as const, + }, ]; export type Testimonial = (typeof testimonials)[number]; diff --git a/web/public/avatars/tonkotsuboy_com.jpg b/web/public/avatars/tonkotsuboy_com.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3ea9461043960b6e91c4f53087cd2d687502508 GIT binary patch literal 24790 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

A-x=GKRet+qd)0E0;LuEh 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 HRdoN^#+iM5 literal 0 HcmV?d00001 From 5d463af1227af7019eb7b3328dc94f96ec067ee5 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:20:42 -0800 Subject: [PATCH 040/232] Fix ghost terminal surface rebind after close (#808) * Fix ghost terminal lifecycle rebind race * Address review feedback on portal regression checks * Address follow-up review feedback --- Sources/AppDelegate.swift | 39 ++- Sources/GhosttyTerminalView.swift | 124 +++++++- Sources/Panels/TerminalPanel.swift | 19 ++ Sources/TabManager.swift | 55 +++- Sources/TerminalController.swift | 10 + Sources/TerminalWindowPortal.swift | 173 +++++++++- Sources/Workspace.swift | 105 +++++- ...it_cmd_shift_d_ctrl_d_no_portal_orphans.py | 301 ++++++++++++++++++ 8 files changed, 811 insertions(+), 15 deletions(-) create mode 100644 tests_v2/test_split_cmd_shift_d_ctrl_d_no_portal_orphans.py diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index b2bd485e..607fdb56 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -6179,11 +6179,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .splitBrowserRight)) { +#if DEBUG + dlog("shortcut.action name=splitBrowserRight \(debugShortcutRouteSnapshot(event: event))") +#endif _ = performBrowserSplitShortcut(direction: .right) return true } if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .splitBrowserDown)) { +#if DEBUG + dlog("shortcut.action name=splitBrowserDown \(debugShortcutRouteSnapshot(event: event))") +#endif _ = performBrowserSplitShortcut(direction: .down) return true } @@ -6697,7 +6703,38 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent func performBrowserSplitShortcut(direction: SplitDirection) -> Bool { _ = synchronizeActiveMainWindowContext(preferredWindow: NSApp.keyWindow ?? NSApp.mainWindow) - guard let panelId = tabManager?.createBrowserSplit(direction: direction) else { return false } + #if DEBUG + let directionLabel: String + switch direction { + case .left: directionLabel = "left" + case .right: directionLabel = "right" + case .up: directionLabel = "up" + case .down: directionLabel = "down" + } + let selectedTabBefore = tabManager?.selectedTabId?.uuidString.prefix(5) ?? "nil" + let focusedPanelBefore = tabManager?.selectedWorkspace?.focusedPanelId?.uuidString.prefix(5) ?? "nil" + dlog( + "split.browser.shortcut pre dir=\(directionLabel) " + + "tab=\(selectedTabBefore) focusedPanel=\(focusedPanelBefore)" + ) + #endif + + guard let panelId = tabManager?.createBrowserSplit(direction: direction) else { + #if DEBUG + dlog("split.browser.shortcut failed dir=\(directionLabel)") + #endif + return false + } + + #if DEBUG + let selectedTabAfter = tabManager?.selectedTabId?.uuidString.prefix(5) ?? "nil" + let focusedPanelAfter = tabManager?.selectedWorkspace?.focusedPanelId?.uuidString.prefix(5) ?? "nil" + dlog( + "split.browser.shortcut post dir=\(directionLabel) " + + "created=\(panelId.uuidString.prefix(5)) tab=\(selectedTabAfter) focusedPanel=\(focusedPanelAfter)" + ) + #endif + _ = focusBrowserAddressBar(panelId: panelId) return true } diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index d1085f97..6688cbfc 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -1613,6 +1613,13 @@ final class TerminalSurface: Identifiable, ObservableObject { private let maxPendingTextBytes = 1_048_576 private var backgroundSurfaceStartQueued = false private var surfaceCallbackContext: Unmanaged? + private enum PortalLifecycleState: String { + case live + case closing + case closed + } + private var portalLifecycleState: PortalLifecycleState = .live + private var portalLifecycleGeneration: UInt64 = 1 @Published var searchState: SearchState? = nil { didSet { if let searchState { @@ -1672,6 +1679,52 @@ final class TerminalSurface: Identifiable, ObservableObject { attachedView?.tabId = newTabId surfaceView.tabId = newTabId } + + func portalBindingGeneration() -> UInt64 { + portalLifecycleGeneration + } + + func portalBindingStateLabel() -> String { + portalLifecycleState.rawValue + } + + func canAcceptPortalBinding(expectedSurfaceId: UUID?, expectedGeneration: UInt64?) -> Bool { + guard portalLifecycleState == .live else { return false } + if let expectedSurfaceId, expectedSurfaceId != id { + return false + } + if let expectedGeneration, expectedGeneration != portalLifecycleGeneration { + return false + } + return true + } + + func beginPortalCloseLifecycle(reason: String) { + guard portalLifecycleState != .closed else { return } + guard portalLifecycleState != .closing else { return } + portalLifecycleState = .closing + portalLifecycleGeneration &+= 1 +#if DEBUG + dlog( + "surface.lifecycle.close.begin surface=\(id.uuidString.prefix(5)) " + + "workspace=\(tabId.uuidString.prefix(5)) reason=\(reason) " + + "generation=\(portalLifecycleGeneration)" + ) +#endif + } + + private func markPortalLifecycleClosed(reason: String) { + guard portalLifecycleState != .closed else { return } + portalLifecycleState = .closed + portalLifecycleGeneration &+= 1 +#if DEBUG + dlog( + "surface.lifecycle.close.sealed surface=\(id.uuidString.prefix(5)) " + + "workspace=\(tabId.uuidString.prefix(5)) reason=\(reason) " + + "generation=\(portalLifecycleGeneration)" + ) +#endif + } #if DEBUG private static let surfaceLogPath = "/tmp/cmux-ghostty-surface.log" private static let sizeLogPath = "/tmp/cmux-ghostty-size.log" @@ -2288,6 +2341,8 @@ final class TerminalSurface: Identifiable, ObservableObject { #endif deinit { + markPortalLifecycleClosed(reason: "deinit") + let callbackContext = surfaceCallbackContext surfaceCallbackContext = nil @@ -2299,16 +2354,38 @@ final class TerminalSurface: Identifiable, ObservableObject { surface = nil guard let surfaceToFree else { +#if DEBUG + dlog( + "surface.lifecycle.deinit.skip surface=\(id.uuidString.prefix(5)) " + + "workspace=\(tabId.uuidString.prefix(5)) reason=noRuntimeSurface" + ) +#endif callbackContext?.release() return } +#if DEBUG + let surfaceToken = String(id.uuidString.prefix(5)) + let workspaceToken = String(tabId.uuidString.prefix(5)) + dlog( + "surface.lifecycle.deinit.begin surface=\(surfaceToken) " + + "workspace=\(workspaceToken) hasAttachedView=\(attachedView != nil ? 1 : 0) " + + "hostedInWindow=\(hostedView.window != nil ? 1 : 0)" + ) +#endif + // Keep teardown asynchronous to avoid re-entrant close/deinit loops, but retain // callback userdata until surface free completes so callbacks never dereference // a deallocated view pointer. Task { @MainActor in ghostty_surface_free(surfaceToFree) callbackContext?.release() +#if DEBUG + dlog( + "surface.lifecycle.deinit.end surface=\(surfaceToken) " + + "workspace=\(workspaceToken) freed=1" + ) +#endif } } } @@ -3764,6 +3841,13 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { deinit { // Surface lifecycle is managed by TerminalSurface, not the view +#if DEBUG + dlog( + "surface.view.deinit view=\(Unmanaged.passUnretained(self).toOpaque()) " + + "surface=\(terminalSurface?.id.uuidString.prefix(5) ?? "nil") " + + "inWindow=\(window != nil ? 1 : 0) hasSuperview=\(superview != nil ? 1 : 0)" + ) +#endif if let eventMonitor { NSEvent.removeMonitor(eventMonitor) } @@ -4111,6 +4195,25 @@ final class GhosttySurfaceScrollView: NSView { } #endif + func portalBindingGuardState() -> (surfaceId: UUID?, generation: UInt64?, state: String) { + guard let terminalSurface = surfaceView.terminalSurface else { + return (surfaceId: nil, generation: nil, state: "missingSurface") + } + return ( + surfaceId: terminalSurface.id, + generation: terminalSurface.portalBindingGeneration(), + state: terminalSurface.portalBindingStateLabel() + ) + } + + func canAcceptPortalBinding(expectedSurfaceId: UUID?, expectedGeneration: UInt64?) -> Bool { + guard let terminalSurface = surfaceView.terminalSurface else { return false } + return terminalSurface.canAcceptPortalBinding( + expectedSurfaceId: expectedSurfaceId, + expectedGeneration: expectedGeneration + ) + } + init(surfaceView: GhosttyNSView) { self.surfaceView = surfaceView backgroundView = NSView(frame: .zero) @@ -4249,6 +4352,13 @@ final class GhosttySurfaceScrollView: NSView { } deinit { +#if DEBUG + dlog( + "surface.hosted.deinit surface=\(debugSurfaceId?.uuidString.prefix(5) ?? "nil") " + + "inWindow=\(window != nil ? 1 : 0) hasSuperview=\(superview != nil ? 1 : 0) " + + "hidden=\(isHidden ? 1 : 0) frame=\(String(format: "%.1fx%.1f", frame.width, frame.height))" + ) +#endif observers.forEach { NotificationCenter.default.removeObserver($0) } windowObservers.forEach { NotificationCenter.default.removeObserver($0) } cancelFocusRequest() @@ -5660,6 +5770,8 @@ struct GhosttyTerminalView: NSViewRepresentable { hostedView.setSearchOverlay(searchState: searchState) hostedView.setFocusHandler { onFocus?(terminalSurface.id) } hostedView.setTriggerFlashHandler(onTriggerFlash) + let portalExpectedSurfaceId = terminalSurface.id + let portalExpectedGeneration = terminalSurface.portalBindingGeneration() let forwardedDropZone = isVisibleInUI ? paneDropZone : nil #if DEBUG if coordinator.lastPaneDropZone != paneDropZone { @@ -5695,7 +5807,9 @@ struct GhosttyTerminalView: NSViewRepresentable { hostedView: hostedView, to: host, visibleInUI: coordinator.desiredIsVisibleInUI, - zPriority: coordinator.desiredPortalZPriority + zPriority: coordinator.desiredPortalZPriority, + expectedSurfaceId: portalExpectedSurfaceId, + expectedGeneration: portalExpectedGeneration ) coordinator.lastBoundHostId = ObjectIdentifier(host) hostedView.setVisibleInUI(coordinator.desiredIsVisibleInUI) @@ -5719,7 +5833,9 @@ struct GhosttyTerminalView: NSViewRepresentable { hostedView: hostedView, to: host, visibleInUI: coordinator.desiredIsVisibleInUI, - zPriority: coordinator.desiredPortalZPriority + zPriority: coordinator.desiredPortalZPriority, + expectedSurfaceId: portalExpectedSurfaceId, + expectedGeneration: portalExpectedGeneration ) coordinator.lastBoundHostId = ObjectIdentifier(host) hostedView.setVisibleInUI(coordinator.desiredIsVisibleInUI) @@ -5742,7 +5858,9 @@ struct GhosttyTerminalView: NSViewRepresentable { hostedView: hostedView, to: host, visibleInUI: coordinator.desiredIsVisibleInUI, - zPriority: coordinator.desiredPortalZPriority + zPriority: coordinator.desiredPortalZPriority, + expectedSurfaceId: portalExpectedSurfaceId, + expectedGeneration: portalExpectedGeneration ) coordinator.lastBoundHostId = hostId } diff --git a/Sources/Panels/TerminalPanel.swift b/Sources/Panels/TerminalPanel.swift index c8ba8507..cee42f2e 100644 --- a/Sources/Panels/TerminalPanel.swift +++ b/Sources/Panels/TerminalPanel.swift @@ -1,6 +1,7 @@ import Foundation import Combine import AppKit +import Bonsplit /// TerminalPanel wraps an existing TerminalSurface and conforms to the Panel protocol. /// This allows TerminalSurface to be used within the bonsplit-based layout system. @@ -139,9 +140,27 @@ final class TerminalPanel: Panel, ObservableObject { // The surface will be cleaned up by its deinit // Detach from the window portal on real close so stale hosted views // cannot remain above browser panes after split close. + surface.beginPortalCloseLifecycle(reason: "panel.close") +#if DEBUG + let frame = String(format: "%.1fx%.1f", hostedView.frame.width, hostedView.frame.height) + let bounds = String(format: "%.1fx%.1f", hostedView.bounds.width, hostedView.bounds.height) + dlog( + "surface.panel.close.begin panel=\(id.uuidString.prefix(5)) " + + "workspace=\(workspaceId.uuidString.prefix(5)) runtimeSurface=\(surface.surface != nil ? 1 : 0) " + + "inWindow=\(hostedView.window != nil ? 1 : 0) hasSuperview=\(hostedView.superview != nil ? 1 : 0) " + + "hidden=\(hostedView.isHidden ? 1 : 0) frame=\(frame) bounds=\(bounds)" + ) +#endif unfocus() hostedView.setVisibleInUI(false) TerminalWindowPortalRegistry.detach(hostedView: hostedView) +#if DEBUG + dlog( + "surface.panel.close.end panel=\(id.uuidString.prefix(5)) " + + "inWindow=\(hostedView.window != nil ? 1 : 0) hasSuperview=\(hostedView.superview != nil ? 1 : 0) " + + "hidden=\(hostedView.isHidden ? 1 : 0)" + ) +#endif } func requestViewReattach() { diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index e9ffa00d..39b26771 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1866,9 +1866,24 @@ class TabManager: ObservableObject { guard let selectedTabId, let tab = tabs.first(where: { $0.id == selectedTabId }), let focusedPanelId = tab.focusedPanelId else { return } +#if DEBUG + let directionLabel = direction.debugLabel + dlog( + "split.create.request kind=terminal dir=\(directionLabel) " + + "tab=\(selectedTabId.uuidString.prefix(5)) panel=\(focusedPanelId.uuidString.prefix(5)) " + + "panels=\(tab.panels.count) panes=\(tab.bonsplitController.allPaneIds.count)" + ) +#endif tab.clearSplitZoom() sentryBreadcrumb("split.create", data: ["direction": String(describing: direction)]) - _ = newSplit(tabId: selectedTabId, surfaceId: focusedPanelId, direction: direction) + let createdPanelId = newSplit(tabId: selectedTabId, surfaceId: focusedPanelId, direction: direction) +#if DEBUG + dlog( + "split.create.result kind=terminal dir=\(directionLabel) " + + "created=\(createdPanelId?.uuidString.prefix(5) ?? "nil") " + + "panels=\(tab.panels.count) panes=\(tab.bonsplitController.allPaneIds.count)" + ) +#endif } /// Create a new browser split from the currently focused panel. @@ -1877,14 +1892,30 @@ class TabManager: ObservableObject { guard let selectedTabId, let tab = tabs.first(where: { $0.id == selectedTabId }), let focusedPanelId = tab.focusedPanelId else { return nil } +#if DEBUG + let directionLabel = direction.debugLabel + dlog( + "split.create.request kind=browser dir=\(directionLabel) " + + "tab=\(selectedTabId.uuidString.prefix(5)) panel=\(focusedPanelId.uuidString.prefix(5)) " + + "panels=\(tab.panels.count) panes=\(tab.bonsplitController.allPaneIds.count)" + ) +#endif tab.clearSplitZoom() - return newBrowserSplit( + let createdPanelId = newBrowserSplit( tabId: selectedTabId, fromPanelId: focusedPanelId, orientation: direction.orientation, insertFirst: direction.insertFirst, url: url ) +#if DEBUG + dlog( + "split.create.result kind=browser dir=\(directionLabel) " + + "created=\(createdPanelId?.uuidString.prefix(5) ?? "nil") " + + "panels=\(tab.panels.count) panes=\(tab.bonsplitController.allPaneIds.count)" + ) +#endif + return createdPanelId } /// Refresh Bonsplit right-side action button tooltips for all workspaces. @@ -1985,12 +2016,21 @@ class TabManager: ObservableObject { /// Returns the new panel's ID (which is also the surface ID for terminals) func newSplit(tabId: UUID, surfaceId: UUID, direction: SplitDirection, focus: Bool = true) -> UUID? { guard let tab = tabs.first(where: { $0.id == tabId }) else { return nil } - return tab.newTerminalSplit( + let createdPanel = tab.newTerminalSplit( from: surfaceId, orientation: direction.orientation, insertFirst: direction.insertFirst, focus: focus )?.id +#if DEBUG + let directionLabel = direction.debugLabel + dlog( + "split.newSurface result dir=\(directionLabel) " + + "tab=\(tabId.uuidString.prefix(5)) source=\(surfaceId.uuidString.prefix(5)) " + + "created=\(createdPanel?.uuidString.prefix(5) ?? "nil") focus=\(focus ? 1 : 0)" + ) +#endif + return createdPanel } /// Move focus in the specified direction @@ -3569,6 +3609,15 @@ enum SplitDirection { var insertFirst: Bool { self == .left || self == .up } + + var debugLabel: String { + switch self { + case .left: return "left" + case .right: return "right" + case .up: return "up" + case .down: return "down" + } + } } /// Resize direction for backwards compatibility diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 116c293c..aaf34f86 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -1521,6 +1521,8 @@ class TerminalController { return v2Result(id: id, self.v2DebugRenderStats(params: params)) case "debug.layout": return v2Result(id: id, self.v2DebugLayout()) + case "debug.portal.stats": + return v2Result(id: id, self.v2DebugPortalStats()) case "debug.bonsplit_underflow.count": return v2Result(id: id, self.v2DebugBonsplitUnderflowCount()) case "debug.bonsplit_underflow.reset": @@ -1725,6 +1727,7 @@ class TerminalController { "debug.terminal.read_text", "debug.terminal.render_stats", "debug.layout", + "debug.portal.stats", "debug.bonsplit_underflow.count", "debug.bonsplit_underflow.reset", "debug.empty_panel.count", @@ -8526,6 +8529,13 @@ class TerminalController { return .ok(["layout": obj]) } + private func v2DebugPortalStats() -> V2CallResult { + let payload: [String: Any] = v2MainSync { + TerminalWindowPortalRegistry.debugPortalStats() + } + return .ok(payload) + } + private func v2DebugBonsplitUnderflowCount() -> V2CallResult { let resp = bonsplitUnderflowCount() guard resp.hasPrefix("OK ") else { return .err(code: "internal_error", message: resp, data: nil) } diff --git a/Sources/TerminalWindowPortal.swift b/Sources/TerminalWindowPortal.swift index 8e8dc306..0c466aca 100644 --- a/Sources/TerminalWindowPortal.swift +++ b/Sources/TerminalWindowPortal.swift @@ -1488,6 +1488,55 @@ final class WindowTerminalPortal: NSObject { } #if DEBUG + struct DebugStats { + let windowNumber: Int + let entryCount: Int + let hostSubviewCount: Int + let terminalSubviewCount: Int + let mappedTerminalSubviewCount: Int + let orphanTerminalSubviewCount: Int + let visibleOrphanTerminalSubviewCount: Int + let staleEntryCount: Int + } + + func debugStats() -> DebugStats { + let terminalSubviews = hostView.subviews.compactMap { $0 as? GhosttySurfaceScrollView } + var mappedTerminalSubviewCount = 0 + var orphanTerminalSubviewCount = 0 + var visibleOrphanTerminalSubviewCount = 0 + + for hostedView in terminalSubviews { + let hostedId = ObjectIdentifier(hostedView) + if entriesByHostedId[hostedId] != nil { + mappedTerminalSubviewCount += 1 + } else { + orphanTerminalSubviewCount += 1 + if hostedView.window != nil, + !hostedView.isHidden, + hostedView.frame.width > Self.tinyHideThreshold, + hostedView.frame.height > Self.tinyHideThreshold { + visibleOrphanTerminalSubviewCount += 1 + } + } + } + + let staleEntryCount = entriesByHostedId.values.reduce(0) { partialResult, entry in + guard let hostedView = entry.hostedView else { return partialResult + 1 } + return hostedView.superview === hostView ? partialResult : partialResult + 1 + } + + return DebugStats( + windowNumber: window?.windowNumber ?? -1, + entryCount: entriesByHostedId.count, + hostSubviewCount: hostView.subviews.count, + terminalSubviewCount: terminalSubviews.count, + mappedTerminalSubviewCount: mappedTerminalSubviewCount, + orphanTerminalSubviewCount: orphanTerminalSubviewCount, + visibleOrphanTerminalSubviewCount: visibleOrphanTerminalSubviewCount, + staleEntryCount: staleEntryCount + ) + } + func debugEntryCount() -> Int { entriesByHostedId.count } @@ -1540,6 +1589,30 @@ final class WindowTerminalPortal: NSObject { enum TerminalWindowPortalRegistry { private static var portalsByWindowId: [ObjectIdentifier: WindowTerminalPortal] = [:] private static var hostedToWindowId: [ObjectIdentifier: ObjectIdentifier] = [:] +#if DEBUG + private static var blockedBindCount: Int = 0 + private static var blockedBindReasons: [String: Int] = [:] +#endif + + private static func bindBlockReason( + expectedSurfaceId: UUID?, + expectedGeneration: UInt64?, + actual: (surfaceId: UUID?, generation: UInt64?, state: String) + ) -> String { + if actual.surfaceId == nil { + return "missingSurface" + } + if actual.state != "live" { + return "state_\(actual.state)" + } + if let expectedSurfaceId, actual.surfaceId != expectedSurfaceId { + return "surfaceMismatch" + } + if let expectedGeneration, actual.generation != expectedGeneration { + return "generationMismatch" + } + return "guardRejected" + } private static func installWindowCloseObserverIfNeeded(for window: NSWindow) { guard objc_getAssociatedObject(window, &cmuxWindowTerminalPortalCloseObserverKey) == nil else { return } @@ -1603,11 +1676,46 @@ enum TerminalWindowPortalRegistry { return portal } - static func bind(hostedView: GhosttySurfaceScrollView, to anchorView: NSView, visibleInUI: Bool, zPriority: Int = 0) { + static func bind( + hostedView: GhosttySurfaceScrollView, + to anchorView: NSView, + visibleInUI: Bool, + zPriority: Int = 0, + expectedSurfaceId: UUID? = nil, + expectedGeneration: UInt64? = nil + ) { guard let window = anchorView.window else { return } let windowId = ObjectIdentifier(window) let hostedId = ObjectIdentifier(hostedView) + let guardState = hostedView.portalBindingGuardState() + guard hostedView.canAcceptPortalBinding( + expectedSurfaceId: expectedSurfaceId, + expectedGeneration: expectedGeneration + ) else { + if let oldWindowId = hostedToWindowId.removeValue(forKey: hostedId) { + portalsByWindowId[oldWindowId]?.detachHostedView(withId: hostedId) + } +#if DEBUG + let reason = bindBlockReason( + expectedSurfaceId: expectedSurfaceId, + expectedGeneration: expectedGeneration, + actual: guardState + ) + blockedBindCount += 1 + blockedBindReasons[reason, default: 0] += 1 + dlog( + "portal.bind.blocked hosted=\(portalDebugToken(hostedView)) " + + "reason=\(reason) expectedSurface=\(expectedSurfaceId?.uuidString.prefix(5) ?? "nil") " + + "expectedGeneration=\(expectedGeneration.map { String($0) } ?? "nil") " + + "actualSurface=\(guardState.surfaceId?.uuidString.prefix(5) ?? "nil") " + + "actualGeneration=\(guardState.generation.map { String($0) } ?? "nil") " + + "actualState=\(guardState.state)" + ) +#endif + return + } + let nextPortal = portal(for: window) if let oldWindowId = hostedToWindowId[hostedId], @@ -1674,5 +1782,68 @@ enum TerminalWindowPortalRegistry { static func debugPortalCount() -> Int { portalsByWindowId.count } + + static func debugPortalStats() -> [String: Any] { + var portals: [[String: Any]] = [] + var totals: [String: Int] = [ + "entry_count": 0, + "host_subview_count": 0, + "terminal_subview_count": 0, + "mapped_terminal_subview_count": 0, + "orphan_terminal_subview_count": 0, + "visible_orphan_terminal_subview_count": 0, + "stale_entry_count": 0, + "mapped_hosted_count": 0, + ] + + for (windowId, portal) in portalsByWindowId { + let stats = portal.debugStats() + let mappedHostedCount = hostedToWindowId.values.reduce(0) { partialResult, mappedWindowId in + partialResult + (mappedWindowId == windowId ? 1 : 0) + } + let integrityOK = + stats.orphanTerminalSubviewCount == 0 && + stats.visibleOrphanTerminalSubviewCount == 0 && + stats.staleEntryCount == 0 && + mappedHostedCount == stats.entryCount + + portals.append([ + "window_number": stats.windowNumber, + "entry_count": stats.entryCount, + "mapped_hosted_count": mappedHostedCount, + "host_subview_count": stats.hostSubviewCount, + "terminal_subview_count": stats.terminalSubviewCount, + "mapped_terminal_subview_count": stats.mappedTerminalSubviewCount, + "orphan_terminal_subview_count": stats.orphanTerminalSubviewCount, + "visible_orphan_terminal_subview_count": stats.visibleOrphanTerminalSubviewCount, + "stale_entry_count": stats.staleEntryCount, + "integrity_ok": integrityOK, + ]) + + totals["entry_count", default: 0] += stats.entryCount + totals["host_subview_count", default: 0] += stats.hostSubviewCount + totals["terminal_subview_count", default: 0] += stats.terminalSubviewCount + totals["mapped_terminal_subview_count", default: 0] += stats.mappedTerminalSubviewCount + totals["orphan_terminal_subview_count", default: 0] += stats.orphanTerminalSubviewCount + totals["visible_orphan_terminal_subview_count", default: 0] += stats.visibleOrphanTerminalSubviewCount + totals["stale_entry_count", default: 0] += stats.staleEntryCount + totals["mapped_hosted_count", default: 0] += mappedHostedCount + } + + portals.sort { + let lhs = ($0["window_number"] as? Int) ?? Int.min + let rhs = ($1["window_number"] as? Int) ?? Int.min + return lhs < rhs + } + + return [ + "portal_count": portals.count, + "hosted_mapping_count": hostedToWindowId.count, + "guarded_bind_blocked_count": blockedBindCount, + "guarded_bind_blocked_reasons": blockedBindReasons, + "portals": portals, + "totals": totals, + ] + } #endif } diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 4417cb13..533f6580 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -2152,12 +2152,29 @@ final class Workspace: Identifiable, ObservableObject { /// Close a panel. /// Returns true when a bonsplit tab close request was issued. func closePanel(_ panelId: UUID, force: Bool = false) -> Bool { +#if DEBUG + let mappedTabIdBeforeClose = surfaceIdFromPanelId(panelId) + dlog( + "surface.close.request panel=\(panelId.uuidString.prefix(5)) " + + "force=\(force ? 1 : 0) mappedTab=\(mappedTabIdBeforeClose.map { String(String(describing: $0).prefix(5)) } ?? "nil") " + + "focusedPanel=\(focusedPanelId?.uuidString.prefix(5) ?? "nil") " + + "focusedPane=\(bonsplitController.focusedPaneId?.id.uuidString.prefix(5) ?? "nil") " + + "\(debugPanelLifecycleState(panelId: panelId, panel: panels[panelId]))" + ) +#endif if let tabId = surfaceIdFromPanelId(panelId) { if force { forceCloseTabIds.insert(tabId) } // Close the tab in bonsplit (this triggers delegate callback) - return bonsplitController.closeTab(tabId) + let closed = bonsplitController.closeTab(tabId) +#if DEBUG + dlog( + "surface.close.request.done panel=\(panelId.uuidString.prefix(5)) " + + "tab=\(String(describing: tabId).prefix(5)) closed=\(closed ? 1 : 0) force=\(force ? 1 : 0)" + ) +#endif + return closed } // Mapping can transiently drift during split-tree mutations. If the target panel is @@ -2189,12 +2206,38 @@ final class Workspace: Identifiable, ObservableObject { dlog( "surface.close.fallback panel=\(panelId.uuidString.prefix(5)) " + "selectedTab=\(String(describing: selected.id).prefix(5)) " + - "closed=\(closed ? 1 : 0)" + "closed=\(closed ? 1 : 0) " + + "\(debugPanelLifecycleState(panelId: panelId, panel: panels[panelId]))" ) #endif return closed } +#if DEBUG + private func debugPanelLifecycleState(panelId: UUID, panel: (any Panel)?) -> String { + guard let panel else { return "panelState=missing" } + if let terminal = panel as? TerminalPanel { + let hosted = terminal.hostedView + let frame = String(format: "%.1fx%.1f", hosted.frame.width, hosted.frame.height) + let bounds = String(format: "%.1fx%.1f", hosted.bounds.width, hosted.bounds.height) + let hasRuntimeSurface = terminal.surface.surface != nil ? 1 : 0 + return + "panelState=terminal panel=\(panelId.uuidString.prefix(5)) " + + "surface=\(terminal.id.uuidString.prefix(5)) runtimeSurface=\(hasRuntimeSurface) " + + "inWindow=\(hosted.window != nil ? 1 : 0) hasSuperview=\(hosted.superview != nil ? 1 : 0) " + + "hidden=\(hosted.isHidden ? 1 : 0) frame=\(frame) bounds=\(bounds)" + } + if let browser = panel as? BrowserPanel { + let webView = browser.webView + let frame = String(format: "%.1fx%.1f", webView.frame.width, webView.frame.height) + return + "panelState=browser panel=\(panelId.uuidString.prefix(5)) " + + "webInWindow=\(webView.window != nil ? 1 : 0) webHasSuperview=\(webView.superview != nil ? 1 : 0) frame=\(frame)" + } + return "panelState=\(String(describing: type(of: panel))) panel=\(panelId.uuidString.prefix(5))" + } +#endif + func paneId(forPanelId panelId: UUID) -> PaneID? { guard let tabId = surfaceIdFromPanelId(panelId) else { return nil } return bonsplitController.allPaneIds.first { paneId in @@ -3731,7 +3774,11 @@ extension Workspace: BonsplitDelegate { // Clean up our panel guard let panelId = panelIdFromSurfaceId(tabId) else { #if DEBUG - NSLog("[Workspace] didCloseTab: no panelId for tabId") + dlog( + "surface.didCloseTab.skip tab=\(String(describing: tabId).prefix(5)) " + + "pane=\(pane.id.uuidString.prefix(5)) reason=missingPanelMapping " + + "panels=\(panels.count) panes=\(controller.allPaneIds.count)" + ) #endif scheduleTerminalGeometryReconcile() if !isDetaching { @@ -3740,11 +3787,15 @@ extension Workspace: BonsplitDelegate { return } - #if DEBUG - NSLog("[Workspace] didCloseTab panelId=\(panelId) remainingPanels=\(panels.count - 1) remainingPanes=\(controller.allPaneIds.count)") - #endif - let panel = panels[panelId] +#if DEBUG + dlog( + "surface.didCloseTab.begin tab=\(String(describing: tabId).prefix(5)) " + + "pane=\(pane.id.uuidString.prefix(5)) panel=\(panelId.uuidString.prefix(5)) " + + "isDetaching=\(isDetaching ? 1 : 0) selectAfter=\(selectTabId.map { String(String(describing: $0).prefix(5)) } ?? "nil") " + + "\(debugPanelLifecycleState(panelId: panelId, panel: panel))" + ) +#endif if isDetaching, let panel { let browserPanel = panel as? BrowserPanel @@ -3795,6 +3846,12 @@ extension Workspace: BonsplitDelegate { // prune the source workspace/window after the tab is attached elsewhere. if panels.isEmpty { if isDetaching { +#if DEBUG + dlog( + "surface.didCloseTab.end tab=\(String(describing: tabId).prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) mode=detachingEmptyWorkspace" + ) +#endif scheduleTerminalGeometryReconcile() return } @@ -3808,6 +3865,13 @@ extension Workspace: BonsplitDelegate { } scheduleTerminalGeometryReconcile() scheduleFocusReconcile() +#if DEBUG + dlog( + "surface.didCloseTab.end tab=\(String(describing: tabId).prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) mode=replacementCreated " + + "replacement=\(replacement.id.uuidString.prefix(5)) panels=\(panels.count)" + ) +#endif return } @@ -3829,6 +3893,15 @@ extension Workspace: BonsplitDelegate { if bonsplitController.allPaneIds.contains(pane) { normalizePinnedTabs(in: pane) } +#if DEBUG + let focusedPaneAfter = bonsplitController.focusedPaneId?.id.uuidString.prefix(5) ?? "nil" + let focusedPanelAfter = focusedPanelId?.uuidString.prefix(5) ?? "nil" + dlog( + "surface.didCloseTab.end tab=\(String(describing: tabId).prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) panels=\(panels.count) panes=\(controller.allPaneIds.count) " + + "focusedPane=\(focusedPaneAfter) focusedPanel=\(focusedPanelAfter)" + ) +#endif scheduleTerminalGeometryReconcile() if !isDetaching { scheduleFocusReconcile() @@ -3913,9 +3986,21 @@ extension Workspace: BonsplitDelegate { func splitTabBar(_ controller: BonsplitController, didClosePane paneId: PaneID) { let closedPanelIds = pendingPaneClosePanelIds.removeValue(forKey: paneId.id) ?? [] let shouldScheduleFocusReconcile = !isDetachingCloseTransaction +#if DEBUG + dlog( + "surface.didClosePane.begin pane=\(paneId.id.uuidString.prefix(5)) " + + "closedPanels=\(closedPanelIds.count) detaching=\(isDetachingCloseTransaction ? 1 : 0)" + ) +#endif if !closedPanelIds.isEmpty { for panelId in closedPanelIds { +#if DEBUG + dlog( + "surface.didClosePane.panel pane=\(paneId.id.uuidString.prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) \(debugPanelLifecycleState(panelId: panelId, panel: panels[panelId]))" + ) +#endif panels[panelId]?.close() panels.removeValue(forKey: panelId) panelDirectories.removeValue(forKey: panelId) @@ -3948,6 +4033,12 @@ extension Workspace: BonsplitDelegate { if shouldScheduleFocusReconcile { scheduleFocusReconcile() } +#if DEBUG + dlog( + "surface.didClosePane.end pane=\(paneId.id.uuidString.prefix(5)) " + + "remainingPanels=\(panels.count) remainingPanes=\(bonsplitController.allPaneIds.count)" + ) +#endif } func splitTabBar(_ controller: BonsplitController, shouldClosePane pane: PaneID) -> Bool { diff --git a/tests_v2/test_split_cmd_shift_d_ctrl_d_no_portal_orphans.py b/tests_v2/test_split_cmd_shift_d_ctrl_d_no_portal_orphans.py new file mode 100644 index 00000000..7f5257e2 --- /dev/null +++ b/tests_v2/test_split_cmd_shift_d_ctrl_d_no_portal_orphans.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +""" +Regression: a Ctrl-D closed terminal must never become visible again before deinit. + +This targets the "ghost terminal" race: + 1) close starts (`surface.close.childExited`) + 2) panel is detached + 3) stale host callback re-binds the same surface + 4) it flips visible/active again (`ws.term.visible transition=0->1`) + 5) deinit only happens later + +Old behavior can pass steady-state orphan counts while still showing this transient bug. +""" + +import os +import re +import sys +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmux, cmuxError + + +SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") +LOG_PATH_OVERRIDE = os.environ.get("CMUX_DEBUG_LOG") +ITERATIONS = int(os.environ.get("CMUX_PORTAL_ORPHAN_ITERS", "16")) +PANE_TIMEOUT_S = float(os.environ.get("CMUX_PORTAL_ORPHAN_PANE_TIMEOUT_S", "3.0")) +INTEGRITY_TIMEOUT_S = float(os.environ.get("CMUX_PORTAL_ORPHAN_INTEGRITY_TIMEOUT_S", "1.5")) +POLL_S = float(os.environ.get("CMUX_PORTAL_ORPHAN_POLL_S", "0.02")) +CTRL_D_RETRY_INTERVAL_S = float(os.environ.get("CMUX_PORTAL_ORPHAN_CTRL_D_RETRY_INTERVAL_S", "0.20")) +CTRL_D_MAX_EXTRA = int(os.environ.get("CMUX_PORTAL_ORPHAN_CTRL_D_MAX_EXTRA", "3")) +POST_CLOSE_SETTLE_S = float(os.environ.get("CMUX_PORTAL_ORPHAN_POST_CLOSE_SETTLE_S", "0.08")) +LOG_FLUSH_S = float(os.environ.get("CMUX_PORTAL_ORPHAN_LOG_FLUSH_S", "0.15")) + +RE_CLOSE = re.compile(r"surface\.close\.childExited .* surface=([0-9A-F]{5})\b") +RE_DEINIT_BEGIN = re.compile(r"surface\.lifecycle\.deinit\.begin surface=([0-9A-F]{5})\b") +RE_DEINIT_END = re.compile(r"surface\.lifecycle\.deinit\.end surface=([0-9A-F]{5})\b") +RE_VISIBLE_ON = re.compile(r"ws\.term\.visible .* surface=([0-9A-F]{5}) .* transition=0->1\b") + + +def _derive_log_path(socket_path: str) -> str: + if LOG_PATH_OVERRIDE: + return LOG_PATH_OVERRIDE + base = os.path.basename(socket_path) + if base.startswith("cmux-debug-") and base.endswith(".sock"): + slug = base[len("cmux-debug-") : -len(".sock")] + return f"/tmp/cmux-debug-{slug}.log" + return "/tmp/cmux-debug.log" + + +def _read_new_lines(log_path: str, offset: int) -> tuple[list[str], int]: + if not os.path.exists(log_path): + raise cmuxError(f"debug log not found at {log_path}") + with open(log_path, "rb") as f: + f.seek(offset) + data = f.read() + new_offset = f.tell() + if not data: + return [], new_offset + return data.decode("utf-8", errors="replace").splitlines(), new_offset + + +def _pane_count(layout_payload: dict) -> int: + return len((layout_payload.get("layout") or {}).get("panes") or []) + + +def _selected_panel_by_pane(layout_payload: dict) -> dict[str, str]: + out: dict[str, str] = {} + for row in layout_payload.get("selectedPanels") or []: + pane_id = str(row.get("paneId") or "") + panel_id = str(row.get("panelId") or "") + if pane_id and panel_id: + out[pane_id] = panel_id + return out + + +def _pane_sort_key(pane: dict) -> tuple[float, float]: + frame = pane.get("frame") or {} + x = float(frame.get("x", 0.0)) + y = float(frame.get("y", 0.0)) + return (x, y) + + +def _panel_for_pane(layout_payload: dict, pane: dict) -> str: + pane_id = str(pane.get("paneId") or "") + selected = _selected_panel_by_pane(layout_payload) + panel_id = str(selected.get(pane_id) or "") + if not panel_id: + raise cmuxError(f"missing selected panel for pane: pane_id={pane_id} selected={selected}") + return panel_id + + +def _rightmost_panel(layout_payload: dict) -> str: + panes = (layout_payload.get("layout") or {}).get("panes") or [] + if len(panes) < 2: + raise cmuxError(f"expected >=2 panes to find rightmost panel, got {len(panes)}") + rightmost = max(panes, key=_pane_sort_key) + return _panel_for_pane(layout_payload, rightmost) + + +def _bottom_right_panel(layout_payload: dict) -> str: + panes = (layout_payload.get("layout") or {}).get("panes") or [] + if len(panes) < 3: + raise cmuxError(f"expected >=3 panes to find bottom-right panel, got {len(panes)}") + bottom_right = max(panes, key=_pane_sort_key) + return _panel_for_pane(layout_payload, bottom_right) + + +def _wait_for_panes(c: cmux, target_panes: int, *, timeout_s: float, context: str) -> dict: + deadline = time.time() + timeout_s + last = None + while time.time() < deadline: + last = c.layout_debug() + if _pane_count(last) == target_panes: + return last + time.sleep(POLL_S) + raise cmuxError( + f"timed out waiting for {target_panes} panes ({context}); " + f"last_panes={_pane_count(last or {})} last_layout={last}" + ) + + +def _portal_stats(c: cmux, *, timeout_s: float) -> dict: + stats = c._call("debug.portal.stats", timeout_s=timeout_s) or {} + if not isinstance(stats, dict): + raise cmuxError(f"debug.portal.stats returned non-dict payload: {stats!r}") + return stats + + +def _portal_integrity_error(stats: dict) -> str | None: + totals = stats.get("totals") or {} + if not isinstance(totals, dict): + return f"portal totals payload is not a dict: {totals!r}" + + required_keys = ( + "orphan_terminal_subview_count", + "visible_orphan_terminal_subview_count", + "stale_entry_count", + ) + missing = [key for key in required_keys if key not in totals] + if missing: + return f"portal totals missing required counters: {', '.join(missing)}" + + try: + orphan = int(totals["orphan_terminal_subview_count"]) + visible_orphan = int(totals["visible_orphan_terminal_subview_count"]) + stale = int(totals["stale_entry_count"]) + except (TypeError, ValueError): + return ( + "portal totals contains non-integer counters " + f"(orphan={totals.get('orphan_terminal_subview_count')!r}, " + f"visible_orphan={totals.get('visible_orphan_terminal_subview_count')!r}, " + f"stale={totals.get('stale_entry_count')!r})" + ) + + if orphan != 0 or visible_orphan != 0 or stale != 0: + return ( + "portal totals show orphan/stale entries " + f"(orphan={orphan}, visible_orphan={visible_orphan}, stale={stale})" + ) + return None + + +def _wait_for_portal_integrity(c: cmux, *, timeout_s: float, context: str) -> None: + deadline = time.time() + timeout_s + last = None + error = None + while time.time() < deadline: + remaining = deadline - time.time() + if remaining <= 0: + break + last = _portal_stats(c, timeout_s=min(remaining, 0.5)) + error = _portal_integrity_error(last) + if error is None: + return + time.sleep(POLL_S) + raise cmuxError(f"{context}: {error}; stats={last}") + + +def _close_bottom_right_via_ctrl_d(c: cmux, *, bottom_right_panel_id: str, context: str) -> dict: + c.send_key_surface(bottom_right_panel_id, "ctrl-d") + next_retry_at = time.time() + CTRL_D_RETRY_INTERVAL_S + extra = 0 + deadline = time.time() + PANE_TIMEOUT_S + last = None + + while time.time() < deadline: + last = c.layout_debug() + if _pane_count(last) == 2: + return last + + if extra < CTRL_D_MAX_EXTRA and time.time() >= next_retry_at: + c.send_key_surface(bottom_right_panel_id, "ctrl-d") + extra += 1 + next_retry_at = time.time() + CTRL_D_RETRY_INTERVAL_S + time.sleep(POLL_S) + + raise cmuxError( + f"{context}: timed out collapsing back to 2 panes after ctrl-d " + f"(extra_ctrl_d={extra}, panel={bottom_right_panel_id}); last_layout={last}" + ) + + +def _find_close_rebind_violations(lines: list[str]) -> tuple[int, list[str]]: + close_pending: set[str] = set() + deinit_started: set[str] = set() + close_count = 0 + violations: list[str] = [] + + for line in lines: + m = RE_CLOSE.search(line) + if m: + sid = m.group(1) + close_pending.add(sid) + close_count += 1 + continue + + m = RE_DEINIT_BEGIN.search(line) + if m: + sid = m.group(1) + deinit_started.add(sid) + continue + + m = RE_DEINIT_END.search(line) + if m: + sid = m.group(1) + close_pending.discard(sid) + deinit_started.discard(sid) + continue + + m = RE_VISIBLE_ON.search(line) + if m: + sid = m.group(1) + if sid in close_pending: + violations.append(line) + + return close_count, violations + + +def main() -> int: + log_path = _derive_log_path(SOCKET_PATH) + if not os.path.exists(log_path): + raise cmuxError(f"debug log not found at {log_path} for socket={SOCKET_PATH}") + log_offset = os.path.getsize(log_path) + + with cmux(SOCKET_PATH) as c: + c.activate_app() + workspace_id = c.new_workspace() + c.select_workspace(workspace_id) + time.sleep(0.2) + + c.new_split("right") + layout = _wait_for_panes(c, 2, timeout_s=PANE_TIMEOUT_S, context="initial right split") + _wait_for_portal_integrity(c, timeout_s=INTEGRITY_TIMEOUT_S, context="after initial right split") + + for iteration in range(1, ITERATIONS + 1): + right_panel_id = _rightmost_panel(layout) + c.focus_surface_by_panel(right_panel_id) + c.new_split("down") + layout = _wait_for_panes( + c, + 3, + timeout_s=PANE_TIMEOUT_S, + context=f"iter={iteration} after split down", + ) + + bottom_right_panel_id = _bottom_right_panel(layout) + layout = _close_bottom_right_via_ctrl_d( + c, + bottom_right_panel_id=bottom_right_panel_id, + context=f"iter={iteration}", + ) + _wait_for_portal_integrity(c, timeout_s=INTEGRITY_TIMEOUT_S, context=f"iter={iteration} integrity") + if POST_CLOSE_SETTLE_S > 0: + time.sleep(POST_CLOSE_SETTLE_S) + + c.close_workspace(workspace_id) + + if LOG_FLUSH_S > 0: + time.sleep(LOG_FLUSH_S) + lines, _ = _read_new_lines(log_path, log_offset) + close_count, violations = _find_close_rebind_violations(lines) + if close_count == 0: + raise cmuxError("no surface.close.childExited events captured; test did not exercise close path") + if violations: + sample = "\n".join(violations[:5]) + raise cmuxError( + "detected close->visible rebind race (closed surface became visible before deinit):\n" + f"{sample}" + ) + + print( + "PASS: no close->visible rebind races during split-down + ctrl-d churn " + f"(iters={ITERATIONS}, closes={close_count})" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 4af2e6be30e2f663ed79fc20954bbbb990efacdc Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:42:09 -0800 Subject: [PATCH 041/232] Remove hourly cron and skipped job from nightly workflow (#817) Every merge to main already triggers a nightly build, making the hourly cron redundant. The skipped job was cosmetic (just echoed a message) and caused confusing red X statuses when cancel-in-progress kicked in. --- .github/workflows/nightly.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 702c5d8f..da320e73 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -3,9 +3,6 @@ name: Nightly macOS build on: push: branches: [main] - schedule: - # Every hour at :30. The 'decide' job skips if main has no new commits. - - cron: "30 * * * *" workflow_dispatch: inputs: force: @@ -367,12 +364,3 @@ jobs: run: | security delete-keychain build.keychain >/dev/null 2>&1 || true rm -f /tmp/cert.p12 - - skipped: - needs: decide - if: needs.decide.outputs.should_build != 'true' - runs-on: ubuntu-latest - steps: - - name: No nightly build needed - run: | - echo "No changes on main since last nightly tag; skipping build." From a3681ede5bb63f84e418fb15f609cb8e93ddc40d Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 3 Mar 2026 15:53:39 -0800 Subject: [PATCH 042/232] ok (#717) --- Sources/AppDelegate.swift | 44 +++++++++++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 65 +++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 607fdb56..e0cabdaa 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1176,6 +1176,33 @@ func shouldRouteTerminalFontZoomShortcutToGhostty( ) != nil } +func shouldRouteTerminalCommandShortcutToGhostty( + flags: NSEvent.ModifierFlags, + chars: String, + keyCode: UInt16, + terminalHasSelection: Bool +) -> Bool { + let normalizedFlags = flags + .intersection(.deviceIndependentFlagsMask) + .subtracting([.numericPad, .function, .capsLock]) + guard normalizedFlags.contains(.command) else { return false } + + let normalizedChars = chars.lowercased() + if normalizedFlags == [.command] { + // Keep Preferences (Cmd+,) menu-routed even when a terminal is focused. + if normalizedChars == "," || keyCode == 43 { + return false + } + + // Preserve standard copy behavior when text is selected in the terminal. + if (normalizedChars == "c" || keyCode == 8), terminalHasSelection { + return false + } + } + + return true +} + func cmuxOwningGhosttyView(for responder: NSResponder?) -> GhosttyNSView? { guard let responder else { return nil } if let ghosttyView = responder as? GhosttyNSView { @@ -8428,6 +8455,23 @@ private extension NSWindow { return true } + // Support custom tmux prefixes (for example Cmd+C): when the terminal is focused + // and no app-level shortcut matched, prefer forwarding Command-key input to the + // terminal rather than consuming it as a menu key equivalent. + if let ghosttyView = firstResponderGhosttyView, + shouldRouteTerminalCommandShortcutToGhostty( + flags: event.modifierFlags, + chars: event.charactersIgnoringModifiers ?? "", + keyCode: event.keyCode, + terminalHasSelection: ghosttyView.terminalSurface?.hasSelection() ?? false + ) { + ghosttyView.keyDown(with: event) +#if DEBUG + dlog(" → ghostty command passthrough") +#endif + return true + } + // When the terminal is focused, skip the full NSWindow.performKeyEquivalent // (which walks the SwiftUI content view hierarchy) and dispatch Command-key // events directly to the main menu. This avoids the broken SwiftUI focus path. diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 3e0d1c72..dc2ecb6f 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2228,6 +2228,71 @@ final class BrowserZoomShortcutRoutingPolicyTests: XCTestCase { } } +final class TerminalCommandShortcutRoutingPolicyTests: XCTestCase { + func testRoutesCommandCToTerminalWhenNoSelection() { + XCTAssertTrue( + shouldRouteTerminalCommandShortcutToGhostty( + flags: [.command], + chars: "c", + keyCode: 8, // kVK_ANSI_C + terminalHasSelection: false + ) + ) + } + + func testKeepsCommandCCopyMenuRoutedWhenSelectionExists() { + XCTAssertFalse( + shouldRouteTerminalCommandShortcutToGhostty( + flags: [.command], + chars: "c", + keyCode: 8, // kVK_ANSI_C + terminalHasSelection: true + ) + ) + } + + func testKeepsCommandCommaMenuRoutedForPreferences() { + XCTAssertFalse( + shouldRouteTerminalCommandShortcutToGhostty( + flags: [.command], + chars: ",", + keyCode: 43, // kVK_ANSI_Comma + terminalHasSelection: false + ) + ) + } + + func testRequiresCommandModifier() { + XCTAssertFalse( + shouldRouteTerminalCommandShortcutToGhostty( + flags: [.control], + chars: "c", + keyCode: 8, + terminalHasSelection: false + ) + ) + } + + func testRoutesOtherCommandShortcutsToTerminal() { + XCTAssertTrue( + shouldRouteTerminalCommandShortcutToGhostty( + flags: [.command, .option], + chars: "c", + keyCode: 8, + terminalHasSelection: false + ) + ) + XCTAssertTrue( + shouldRouteTerminalCommandShortcutToGhostty( + flags: [.command], + chars: "v", + keyCode: 9, // kVK_ANSI_V + terminalHasSelection: false + ) + ) + } +} + final class GhosttyResponderResolutionTests: XCTestCase { private final class FocusProbeView: NSView { override var acceptsFirstResponder: Bool { true } From bdfcc74df3b4a29411530a798863358547bf0170 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 3 Mar 2026 15:57:36 -0800 Subject: [PATCH 043/232] Fix Shift+backquote input regression in Ghostty key path (#815) --- Sources/GhosttyTerminalView.swift | 24 +++++++++++- cmuxTests/CJKIMEInputTests.swift | 62 +++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 6688cbfc..bc7329cc 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -3481,10 +3481,22 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { guard let chars = event.characters, !chars.isEmpty else { return nil } if chars.count == 1, let scalar = chars.unicodeScalars.first { + let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + // If we have a single control character, return the character without // the control modifier so Ghostty's KeyEncoder can handle it. if scalar.value < 0x20 { - return event.characters(byApplyingModifiers: event.modifierFlags.subtracting(.control)) + if flags.contains(.control) { + return event.characters(byApplyingModifiers: event.modifierFlags.subtracting(.control)) + } + + // Some AppKit key paths can report Shift+` as a bare ESC control + // character even though the physical key should produce "~". + if scalar.value == 0x1B, + flags == [.shift], + event.charactersIgnoringModifiers == "`" { + return "~" + } } // Private Use Area characters (function keys) should not be sent if scalar.value >= 0xF700 && scalar.value <= 0xF8FF { @@ -3497,7 +3509,15 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { /// Get the unshifted codepoint for the key event private func unshiftedCodepointFromEvent(_ event: NSEvent) -> UInt32 { - guard let chars = event.characters(byApplyingModifiers: []), + if let layoutChars = KeyboardLayout.character(forKeyCode: event.keyCode), + layoutChars.count == 1, + let layoutScalar = layoutChars.unicodeScalars.first, + layoutScalar.value >= 0x20, + !(layoutScalar.value >= 0xF700 && layoutScalar.value <= 0xF8FF) { + return layoutScalar.value + } + + guard let chars = (event.characters(byApplyingModifiers: []) ?? event.charactersIgnoringModifiers ?? event.characters), let scalar = chars.unicodeScalars.first else { return 0 } return scalar.value } diff --git a/cmuxTests/CJKIMEInputTests.swift b/cmuxTests/CJKIMEInputTests.swift index 6ef9aa03..86ed191e 100644 --- a/cmuxTests/CJKIMEInputTests.swift +++ b/cmuxTests/CJKIMEInputTests.swift @@ -882,3 +882,65 @@ final class GhosttySpaceReleaseRegressionTests: XCTestCase { XCTAssertNil(releaseEvent.text) } } + +final class GhosttyBackquoteRegressionTests: XCTestCase { + func testShiftBackquoteEscFallbackSendsLiteralTilde() { + _ = NSApplication.shared + + let surface = TerminalSurface( + tabId: UUID(), + context: GHOSTTY_SURFACE_CONTEXT_SPLIT, + configTemplate: nil, + workingDirectory: nil + ) + let hostedView = surface.hostedView + + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 360, height: 240), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { + GhosttyNSView.debugGhosttySurfaceKeyEventObserver = nil + window.orderOut(nil) + } + + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + hostedView.frame = contentView.bounds + hostedView.autoresizingMask = [.width, .height] + contentView.addSubview(hostedView) + + window.makeKeyAndOrderFront(nil) + window.displayIfNeeded() + contentView.layoutSubtreeIfNeeded() + hostedView.setVisibleInUI(true) + hostedView.setActive(true) + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + + var pressText: String? + var pressUnshiftedCodepoint: UInt32? + GhosttyNSView.debugGhosttySurfaceKeyEventObserver = { keyEvent in + guard keyEvent.action == GHOSTTY_ACTION_PRESS, keyEvent.keycode == 50 else { return } + pressUnshiftedCodepoint = keyEvent.unshifted_codepoint + if let text = keyEvent.text { + pressText = String(cString: text) + } else { + pressText = nil + } + } + + let sent = hostedView.debugSendSyntheticKeyPressAndReleaseForUITest( + characters: "\u{1B}", + charactersIgnoringModifiers: "`", + keyCode: 50, + modifierFlags: [.shift] + ) + XCTAssertTrue(sent, "Expected synthetic Shift+backquote event to be dispatched") + XCTAssertEqual(pressText, "~") + XCTAssertEqual(pressUnshiftedCodepoint, "`".unicodeScalars.first?.value) + } +} From 34c6250a376f3eb5c053031ae0cc76be387df46e Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 3 Mar 2026 16:22:38 -0800 Subject: [PATCH 044/232] Respect shell-integration=none in zsh wrapper (#816) --- Resources/shell-integration/.zshenv | 8 +- ...ue_734_shell_integration_none_respected.py | 132 ++++++++++++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 tests/test_issue_734_shell_integration_none_respected.py diff --git a/Resources/shell-integration/.zshenv b/Resources/shell-integration/.zshenv index 21570241..68925a2f 100644 --- a/Resources/shell-integration/.zshenv +++ b/Resources/shell-integration/.zshenv @@ -13,7 +13,9 @@ # - CMUX_ZSH_ZDOTDIR (set by cmux when it overwrote a user-provided ZDOTDIR) # - unset (zsh treats unset ZDOTDIR as $HOME) +builtin typeset _cmux_had_ghostty_zdotdir=0 if [[ -n "${GHOSTTY_ZSH_ZDOTDIR+X}" ]]; then + _cmux_had_ghostty_zdotdir=1 builtin export ZDOTDIR="$GHOSTTY_ZSH_ZDOTDIR" builtin unset GHOSTTY_ZSH_ZDOTDIR elif [[ -n "${CMUX_ZSH_ZDOTDIR+X}" ]]; then @@ -31,7 +33,9 @@ fi if [[ -o interactive ]]; then # We overwrote GhosttyKit's injected ZDOTDIR, so manually load Ghostty's # zsh integration if available. - if [[ -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then + # Guard on GHOSTTY_ZSH_ZDOTDIR being set by Ghostty. When users configure + # shell-integration=none, Ghostty does not set this and we must skip. + if [[ "$_cmux_had_ghostty_zdotdir" == "1" && -n "${GHOSTTY_RESOURCES_DIR:-}" ]]; then builtin typeset _cmux_ghostty="$GHOSTTY_RESOURCES_DIR/shell-integration/zsh/ghostty-integration" [[ -r "$_cmux_ghostty" ]] && builtin source -- "$_cmux_ghostty" fi @@ -43,5 +47,5 @@ fi fi fi - builtin unset _cmux_file _cmux_ghostty _cmux_integ + builtin unset _cmux_file _cmux_ghostty _cmux_integ _cmux_had_ghostty_zdotdir } diff --git a/tests/test_issue_734_shell_integration_none_respected.py b/tests/test_issue_734_shell_integration_none_respected.py new file mode 100644 index 00000000..3fe6836c --- /dev/null +++ b/tests/test_issue_734_shell_integration_none_respected.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +""" +Regression for issue #734: +cmux wrapper .zshenv should only source Ghostty zsh integration when Ghostty +actually enabled shell integration (signaled by GHOSTTY_ZSH_ZDOTDIR being set). +""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + + +def _run_case( + *, + wrapper_dir: Path, + home: Path, + orig_zdotdir: Path, + ghostty_resources: Path, + out_path: Path, + ghostty_enabled: bool, +) -> tuple[int, str]: + env = dict(os.environ) + env["HOME"] = str(home) + env["ZDOTDIR"] = str(wrapper_dir) + env["GHOSTTY_RESOURCES_DIR"] = str(ghostty_resources) + env["CMUX_SHELL_INTEGRATION"] = "0" + env["CMUX_TEST_OUT"] = str(out_path) + + # Keep input deterministic and local to this test. + for key in ( + "GHOSTTY_ZSH_ZDOTDIR", + "CMUX_ZSH_ZDOTDIR", + "CMUX_ORIGINAL_ZDOTDIR", + "GHOSTTY_SHELL_FEATURES", + "GHOSTTY_BIN_DIR", + ): + env.pop(key, None) + + if ghostty_enabled: + env["GHOSTTY_ZSH_ZDOTDIR"] = str(orig_zdotdir) + else: + env["CMUX_ZSH_ZDOTDIR"] = str(orig_zdotdir) + + result = subprocess.run( + ["zsh", "-d", "-i", "-c", "true"], + env=env, + capture_output=True, + text=True, + timeout=8, + ) + return (result.returncode, (result.stdout or "") + (result.stderr or "")) + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + wrapper_dir = root / "Resources" / "shell-integration" + if not (wrapper_dir / ".zshenv").exists(): + print(f"SKIP: missing wrapper .zshenv at {wrapper_dir}") + return 0 + + base = Path("/tmp") / f"cmux_issue_734_{os.getpid()}" + try: + shutil.rmtree(base, ignore_errors=True) + base.mkdir(parents=True, exist_ok=True) + + home = base / "home" + orig = base / "orig-zdotdir" + resources = base / "ghostty-resources" + home.mkdir(parents=True, exist_ok=True) + orig.mkdir(parents=True, exist_ok=True) + (resources / "shell-integration" / "zsh").mkdir(parents=True, exist_ok=True) + + # Keep user startup files inert and local. + for filename in (".zshenv", ".zprofile", ".zshrc"): + (orig / filename).write_text("", encoding="utf-8") + + marker = base / "ghostty-sourced.txt" + (resources / "shell-integration" / "zsh" / "ghostty-integration").write_text( + 'echo "sourced" >> "$CMUX_TEST_OUT"\n', + encoding="utf-8", + ) + + rc, out = _run_case( + wrapper_dir=wrapper_dir, + home=home, + orig_zdotdir=orig, + ghostty_resources=resources, + out_path=marker, + ghostty_enabled=False, + ) + if rc != 0: + print(f"FAIL: zsh exited non-zero when ghostty_enabled=False rc={rc}") + if out.strip(): + print(out.strip()) + return 1 + if marker.exists(): + print("FAIL: ghostty integration sourced when Ghostty shell integration was disabled") + return 1 + + rc, out = _run_case( + wrapper_dir=wrapper_dir, + home=home, + orig_zdotdir=orig, + ghostty_resources=resources, + out_path=marker, + ghostty_enabled=True, + ) + if rc != 0: + print(f"FAIL: zsh exited non-zero when ghostty_enabled=True rc={rc}") + if out.strip(): + print(out.strip()) + return 1 + if not marker.exists(): + print("FAIL: ghostty integration not sourced when Ghostty shell integration was enabled") + return 1 + + contents = marker.read_text(encoding="utf-8") + if "sourced" not in contents: + print("FAIL: expected marker output missing after enabled run") + return 1 + + print("PASS: wrapper respects Ghostty shell-integration=none via GHOSTTY_ZSH_ZDOTDIR gate") + return 0 + finally: + shutil.rmtree(base, ignore_errors=True) + + +if __name__ == "__main__": + raise SystemExit(main()) From a12fb563ff196e155186c1b34bab40fbf62d37fc Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 3 Mar 2026 16:28:51 -0800 Subject: [PATCH 045/232] Fix Ghostty config reload theme refresh (#812) (#818) --- Sources/AppDelegate.swift | 33 +++++++++++++++++++++++++++++++ Sources/GhosttyTerminalView.swift | 9 +++++++++ 2 files changed, 42 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index e0cabdaa..155ecfc9 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -3372,6 +3372,39 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return nil } + func refreshTerminalSurfacesAfterGhosttyConfigReload(source: String) { + var refreshedCount = 0 + forEachTerminalPanel { terminalPanel in + terminalPanel.hostedView.reconcileGeometryNow() + terminalPanel.surface.forceRefresh() + refreshedCount += 1 + } +#if DEBUG + dlog("reload.config.surfaceRefresh source=\(source) count=\(refreshedCount)") +#endif + } + + private func forEachTerminalPanel(_ body: (TerminalPanel) -> Void) { + var seenManagers: Set = [] + + func visitManager(_ manager: TabManager?) { + guard let manager else { return } + let managerId = ObjectIdentifier(manager) + guard seenManagers.insert(managerId).inserted else { return } + for workspace in manager.tabs { + for panel in workspace.panels.values { + guard let terminalPanel = panel as? TerminalPanel else { continue } + body(terminalPanel) + } + } + } + + visitManager(tabManager) + for context in mainWindowContexts.values { + visitManager(context.tabManager) + } + } + func focusMainWindow(windowId: UUID) -> Bool { guard let window = windowForMainWindowId(windowId) else { return false } if TerminalController.shouldSuppressSocketCommandActivation() { diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index bc7329cc..0a6b2489 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -660,6 +660,7 @@ class GhosttyApp { private func loadDefaultConfigFilesWithLegacyFallback(_ config: ghostty_config_t) { ghostty_config_load_default_files(config) loadLegacyGhosttyConfigIfNeeded(config) + ghostty_config_load_recursive_files(config) ghostty_config_finalize(config) } @@ -767,6 +768,7 @@ class GhosttyApp { ghostty_app_update_config(app, config) lastAppearanceColorScheme = GhosttyConfig.currentColorSchemePreference() NotificationCenter.default.post(name: .ghosttyConfigDidReload, object: nil) + scheduleSurfaceRefreshAfterConfigurationReload(source: source) logThemeAction("reload end source=\(source) soft=\(soft) mode=soft") return } @@ -791,9 +793,16 @@ class GhosttyApp { config = newConfig lastAppearanceColorScheme = GhosttyConfig.currentColorSchemePreference() NotificationCenter.default.post(name: .ghosttyConfigDidReload, object: nil) + scheduleSurfaceRefreshAfterConfigurationReload(source: source) logThemeAction("reload end source=\(source) soft=\(soft) mode=full") } + private func scheduleSurfaceRefreshAfterConfigurationReload(source: String) { + DispatchQueue.main.async { + AppDelegate.shared?.refreshTerminalSurfacesAfterGhosttyConfigReload(source: source) + } + } + func synchronizeThemeWithAppearance(_ appearance: NSAppearance?, source: String) { let currentColorScheme = GhosttyConfig.currentColorSchemePreference( appAppearance: appearance ?? NSApp?.effectiveAppearance From d1f4c663783b8e435ff1964b9ab34e86eb4d763c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:42:10 -0800 Subject: [PATCH 046/232] Move UNUserNotificationCenter remove calls off main thread (#820) * Move UNUserNotificationCenter remove calls off main thread removeDeliveredNotifications(withIdentifiers:) and removePendingNotificationRequests(withIdentifiers:) perform synchronous XPC to usernoted. When usernoted is slow or overwhelmed, this blocks the main thread indefinitely, freezing the entire UI (confirmed via sample showing __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ for the full sample duration on both cmux and cmux NIGHTLY). Add extension methods on UNUserNotificationCenter that dispatch these calls to a background queue. All 13 call sites in TerminalNotificationStore are fire-and-forget (void, no data flows back), so moving them off-main is safe. The @Published property mutations and dock badge updates remain on @MainActor. * Use dedicated serial queue for notification removal dispatch Replaces DispatchQueue.global(qos: .utility) with a private static serial queue. If usernoted stalls, concurrent dispatches to the global pool could exhaust threads. A dedicated queue caps concurrency at 1. --- Sources/TerminalNotificationStore.swift | 60 ++++++++++++++++++------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index bc3272b1..5a34be3c 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -2,6 +2,32 @@ import AppKit import Foundation import UserNotifications +// UNUserNotificationCenter.removeDeliveredNotifications(withIdentifiers:) and +// removePendingNotificationRequests(withIdentifiers:) perform synchronous XPC to +// usernoted under the hood. When usernoted is slow, this blocks the calling thread +// indefinitely. These helpers dispatch the calls off the main thread so they never +// freeze the UI. +extension UNUserNotificationCenter { + private static let removalQueue = DispatchQueue( + label: "com.cmuxterm.notification-removal", + qos: .utility + ) + + func removeDeliveredNotificationsOffMain(withIdentifiers ids: [String]) { + guard !ids.isEmpty else { return } + Self.removalQueue.async { + self.removeDeliveredNotifications(withIdentifiers: ids) + } + } + + func removePendingNotificationRequestsOffMain(withIdentifiers ids: [String]) { + guard !ids.isEmpty else { return } + Self.removalQueue.async { + self.removePendingNotificationRequests(withIdentifiers: ids) + } + } +} + enum NotificationBadgeSettings { static let dockBadgeEnabledKey = "notificationDockBadgeEnabled" static let defaultDockBadgeEnabled = true @@ -190,8 +216,8 @@ final class TerminalNotificationStore: ObservableObject { if isAppFocused && isFocusedPanel { if !idsToClear.isEmpty { notifications = updated - center.removeDeliveredNotifications(withIdentifiers: idsToClear) - center.removePendingNotificationRequests(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } return } @@ -213,8 +239,8 @@ final class TerminalNotificationStore: ObservableObject { updated.insert(notification, at: 0) notifications = updated if !idsToClear.isEmpty { - center.removeDeliveredNotifications(withIdentifiers: idsToClear) - center.removePendingNotificationRequests(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } scheduleUserNotification(notification) } @@ -225,7 +251,7 @@ final class TerminalNotificationStore: ObservableObject { guard !updated[index].isRead else { return } updated[index].isRead = true notifications = updated - center.removeDeliveredNotifications(withIdentifiers: [id.uuidString]) + center.removeDeliveredNotificationsOffMain(withIdentifiers: [id.uuidString]) } func markRead(forTabId tabId: UUID) { @@ -239,7 +265,7 @@ final class TerminalNotificationStore: ObservableObject { } if !idsToClear.isEmpty { notifications = updated - center.removeDeliveredNotifications(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) } } @@ -256,8 +282,8 @@ final class TerminalNotificationStore: ObservableObject { } if !idsToClear.isEmpty { notifications = updated - center.removeDeliveredNotifications(withIdentifiers: idsToClear) - center.removePendingNotificationRequests(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } } @@ -286,8 +312,8 @@ final class TerminalNotificationStore: ObservableObject { } if !idsToClear.isEmpty { notifications = updated - center.removeDeliveredNotifications(withIdentifiers: idsToClear) - center.removePendingNotificationRequests(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } } @@ -297,15 +323,15 @@ final class TerminalNotificationStore: ObservableObject { updated.removeAll { $0.id == id } guard updated.count != originalCount else { return } notifications = updated - center.removeDeliveredNotifications(withIdentifiers: [id.uuidString]) + center.removeDeliveredNotificationsOffMain(withIdentifiers: [id.uuidString]) } func clearAll() { guard !notifications.isEmpty else { return } let ids = notifications.map { $0.id.uuidString } notifications.removeAll() - center.removeDeliveredNotifications(withIdentifiers: ids) - center.removePendingNotificationRequests(withIdentifiers: ids) + center.removeDeliveredNotificationsOffMain(withIdentifiers: ids) + center.removePendingNotificationRequestsOffMain(withIdentifiers: ids) } func clearNotifications(forTabId tabId: UUID, surfaceId: UUID?) { @@ -321,8 +347,8 @@ final class TerminalNotificationStore: ObservableObject { } guard !idsToClear.isEmpty else { return } notifications = updated - center.removeDeliveredNotifications(withIdentifiers: idsToClear) - center.removePendingNotificationRequests(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } func clearNotifications(forTabId tabId: UUID) { @@ -338,8 +364,8 @@ final class TerminalNotificationStore: ObservableObject { } guard !idsToClear.isEmpty else { return } notifications = updated - center.removeDeliveredNotifications(withIdentifiers: idsToClear) - center.removePendingNotificationRequests(withIdentifiers: idsToClear) + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } private func scheduleUserNotification(_ notification: TerminalNotification) { From fd31210ea486183aa932b3faf19fe8ab250b896b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:21:02 -0800 Subject: [PATCH 047/232] Fix settings dropdown for Sidebar Branch Layout taking full width (#825) The Picker was missing the controlWidth parameter that all other picker rows in settings use, causing it to expand and hide its label. --- Sources/cmuxApp.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 4f7cea7d..3471ca26 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2985,7 +2985,8 @@ struct SettingsView: View { "Sidebar Branch Layout", subtitle: sidebarBranchVerticalLayout ? "Vertical: each branch appears on its own line." - : "Inline: all branches share one line." + : "Inline: all branches share one line.", + controlWidth: pickerColumnWidth ) { Picker("", selection: $sidebarBranchVerticalLayout) { Text("Vertical").tag(true) From a139c346f2c4ddbb4a465c0ee76207d07220a3df Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:37:46 -0800 Subject: [PATCH 048/232] Fix Cmd+Shift+Enter pane zoom regression in browser focus (#826) --- Sources/AppDelegate.swift | 12 +++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 62 +++++++++++++++++-- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 155ecfc9..63db90ac 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -991,10 +991,15 @@ func browserOmnibarShouldSubmitOnReturn(flags: NSEvent.ModifierFlags) -> Bool { func shouldDispatchBrowserReturnViaFirstResponderKeyDown( keyCode: UInt16, - firstResponderIsBrowser: Bool + firstResponderIsBrowser: Bool, + flags: NSEvent.ModifierFlags ) -> Bool { guard firstResponderIsBrowser else { return false } - return keyCode == 36 || keyCode == 76 + guard keyCode == 36 || keyCode == 76 else { return false } + // Keep browser Return forwarding narrow: only plain/Shift Return should be + // treated as submit-intent. Command-modified Return is reserved for app shortcuts + // like Toggle Pane Zoom (Cmd+Shift+Enter). + return browserOmnibarShouldSubmitOnReturn(flags: flags) } func shouldToggleMainWindowFullScreenForCommandControlFShortcut( @@ -8462,7 +8467,8 @@ private extension NSWindow { // mark handled to avoid the AppKit alert sound path. if shouldDispatchBrowserReturnViaFirstResponderKeyDown( keyCode: event.keyCode, - firstResponderIsBrowser: firstResponderWebView != nil + firstResponderIsBrowser: firstResponderWebView != nil, + flags: event.modifierFlags ) { // Forwarding keyDown can re-enter performKeyEquivalent in WebKit/AppKit internals. // On re-entry, fall back to normal dispatch to avoid an infinite loop. diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index dc2ecb6f..cc75f384 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -1988,7 +1988,8 @@ final class BrowserReturnKeyDownRoutingTests: XCTestCase { XCTAssertTrue( shouldDispatchBrowserReturnViaFirstResponderKeyDown( keyCode: 36, - firstResponderIsBrowser: true + firstResponderIsBrowser: true, + flags: [] ) ) } @@ -1997,7 +1998,8 @@ final class BrowserReturnKeyDownRoutingTests: XCTestCase { XCTAssertTrue( shouldDispatchBrowserReturnViaFirstResponderKeyDown( keyCode: 76, - firstResponderIsBrowser: true + firstResponderIsBrowser: true, + flags: [] ) ) } @@ -2006,7 +2008,8 @@ final class BrowserReturnKeyDownRoutingTests: XCTestCase { XCTAssertFalse( shouldDispatchBrowserReturnViaFirstResponderKeyDown( keyCode: 13, - firstResponderIsBrowser: true + firstResponderIsBrowser: true, + flags: [] ) ) } @@ -2015,7 +2018,58 @@ final class BrowserReturnKeyDownRoutingTests: XCTestCase { XCTAssertFalse( shouldDispatchBrowserReturnViaFirstResponderKeyDown( keyCode: 36, - firstResponderIsBrowser: false + firstResponderIsBrowser: false, + flags: [] + ) + ) + } + + func testRoutesForShiftReturnWhenBrowserFirstResponder() { + XCTAssertTrue( + shouldDispatchBrowserReturnViaFirstResponderKeyDown( + keyCode: 36, + firstResponderIsBrowser: true, + flags: [.shift] + ) + ) + } + + func testDoesNotRouteForCommandShiftReturnWhenBrowserFirstResponder() { + XCTAssertFalse( + shouldDispatchBrowserReturnViaFirstResponderKeyDown( + keyCode: 36, + firstResponderIsBrowser: true, + flags: [.command, .shift] + ) + ) + } + + func testDoesNotRouteForCommandReturnWhenBrowserFirstResponder() { + XCTAssertFalse( + shouldDispatchBrowserReturnViaFirstResponderKeyDown( + keyCode: 36, + firstResponderIsBrowser: true, + flags: [.command] + ) + ) + } + + func testDoesNotRouteForOptionReturnWhenBrowserFirstResponder() { + XCTAssertFalse( + shouldDispatchBrowserReturnViaFirstResponderKeyDown( + keyCode: 36, + firstResponderIsBrowser: true, + flags: [.option] + ) + ) + } + + func testDoesNotRouteForControlReturnWhenBrowserFirstResponder() { + XCTAssertFalse( + shouldDispatchBrowserReturnViaFirstResponderKeyDown( + keyCode: 36, + firstResponderIsBrowser: true, + flags: [.control] ) ) } From 355012b25217fe0463cac58c2f1abd1604c34ab7 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 3 Mar 2026 18:38:24 -0800 Subject: [PATCH 049/232] Revert "Fix Cmd+Tab activation ordering for cmux windows (#744) (#766)" (#827) This reverts commit a6f6485e3c3694ad5ead0fdf13a5067b590ee76d. --- Sources/AppDelegate.swift | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 63db90ac..64150724 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1528,7 +1528,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private var commandPaletteVisibilityByWindowId: [UUID: Bool] = [:] private var commandPaletteSelectionByWindowId: [UUID: Int] = [:] private var commandPaletteSnapshotByWindowId: [UUID: CommandPaletteDebugSnapshot] = [:] - private weak var lastActiveMainWindow: NSWindow? var updateViewModel: UpdateViewModel { updateController.viewModel @@ -1587,10 +1586,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } func applicationDidFinishLaunching(_ notification: Notification) { - if NSApp.activationPolicy() != .regular { - NSApp.setActivationPolicy(.regular) - } - let env = ProcessInfo.processInfo.environment let isRunningUnderXCTest = isRunningUnderXCTest(env) let telemetryEnabled = TelemetrySettings.enabledForCurrentLaunch @@ -1756,7 +1751,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tab.triggerNotificationFocusFlash(panelId: surfaceId, requiresSplit: false, shouldFocus: false) } notificationStore.markRead(forTabId: tabId, surfaceId: surfaceId) - bringMainWindowToFrontOnActivationIfNeeded() } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -7201,7 +7195,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent queue: .main ) { [weak self] note in guard let self, let window = note.object as? NSWindow else { return } - self.lastActiveMainWindow = window self.setActiveMainWindow(window) } } @@ -7561,36 +7554,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } - private func bringMainWindowToFrontOnActivationIfNeeded() { - guard let window = frontmostKnownMainWindow() else { return } - bringToFront(window) - } - - private func frontmostKnownMainWindow() -> NSWindow? { - let directCandidates: [NSWindow?] = [lastActiveMainWindow, NSApp.keyWindow, NSApp.mainWindow] - for candidate in directCandidates where candidate != nil { - if let window = candidate, isMainTerminalWindow(window) { - return window - } - } - - if let activeManager = tabManager, - let context = mainWindowContexts.values.first(where: { $0.tabManager === activeManager }), - let window = context.window ?? windowForMainWindowId(context.windowId) { - return window - } - - if let visible = mainWindowContexts.values - .compactMap({ $0.window ?? windowForMainWindowId($0.windowId) }) - .first(where: { $0.isVisible }) { - return visible - } - - return mainWindowContexts.values - .compactMap({ $0.window ?? windowForMainWindowId($0.windowId) }) - .first - } - #if DEBUG private func recordMultiWindowNotificationOpenFailureIfNeeded( tabId: UUID, From bfe843f0bd1dbfe77cac84bec3a98c8c08e25f71 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:48:32 -0800 Subject: [PATCH 050/232] Clear sidebar notification when user submits prompt (#821) * Clear sidebar notification when user submits prompt in Claude Code Add UserPromptSubmit hook to the Claude Code wrapper that calls `cmux claude-hook prompt-submit`. This clears the workspace notification and sets status back to "Running" when the user addresses Claude's question, so the "waiting for input" preview in the sidebar goes away. Also adds --tab support to clear_notifications socket command and --workspace support to the clear-notifications CLI command for per-workspace notification clearing. Closes https://github.com/manaflow-ai/cmux/issues/799 * Address review feedback: stricter error handling - clear-notifications CLI: error on explicit --workspace failure instead of falling back to global clear. Env var still gracefully degrades. - prompt-submit hook: propagate sendV1Command errors instead of swallowing with try?. - clear_notifications socket: validate --tab flag is present before resolving, reject malformed args instead of falling back to selected tab. * Gate env workspace fallback on windowId == nil in clear-notifications Matches the pattern used by other CLI commands to avoid using CMUX_WORKSPACE_ID from the caller shell when --window targets a different window. --- CLI/cmux.swift | 32 ++++++++++++++++++++++++++++++-- Resources/bin/claude | 2 +- Sources/TerminalController.swift | 29 +++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index f6f0d760..3108185a 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -1305,7 +1305,16 @@ struct CMUXCLI { } case "clear-notifications": - let response = try sendV1Command("clear_notifications", client: client) + var socketCmd = "clear_notifications" + if let wsFlag = optionValue(commandArgs, name: "--workspace") { + let wsId = try resolveWorkspaceId(wsFlag, client: client) + socketCmd += " --tab=\(wsId)" + } else if windowId == nil, + let envWs = ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"], + let wsId = try? resolveWorkspaceId(envWs, client: client) { + socketCmd += " --tab=\(wsId)" + } + let response = try sendV1Command(socketCmd, client: client) print(response) case "claude-hook": @@ -4441,7 +4450,7 @@ struct CMUXCLI { """ case "claude-hook": return """ - Usage: cmux claude-hook [flags] + Usage: cmux claude-hook [flags] Hook for Claude Code integration. Reads JSON from stdin. @@ -4452,6 +4461,7 @@ struct CMUXCLI { idle Alias for stop notification Forward a Claude notification notify Alias for notification + prompt-submit Clear notification and set Running on user prompt Flags: --workspace Target workspace (default: $CMUX_WORKSPACE_ID) @@ -5700,6 +5710,24 @@ struct CMUXCLI { print("OK") } + case "prompt-submit": + telemetry.breadcrumb("claude-hook.prompt-submit") + var workspaceId = fallbackWorkspaceId + if let sessionId = parsedInput.sessionId, + let mapped = try? sessionStore.lookup(sessionId: sessionId), + let mappedWorkspace = try? resolveWorkspaceIdForClaudeHook(mapped.workspaceId, client: client) { + workspaceId = mappedWorkspace + } + _ = try sendV1Command("clear_notifications --tab=\(workspaceId)", client: client) + try setClaudeStatus( + client: client, + workspaceId: workspaceId, + value: "Running", + icon: "bolt.fill", + color: "#4C8DFF" + ) + print("OK") + case "notification", "notify": telemetry.breadcrumb("claude-hook.notification") let summary = summarizeClaudeHookNotification(rawInput: rawInput) diff --git a/Resources/bin/claude b/Resources/bin/claude index d722b9c7..2205fe3c 100755 --- a/Resources/bin/claude +++ b/Resources/bin/claude @@ -50,7 +50,7 @@ done # Build hooks settings JSON. # Claude Code merges --settings additively with the user's own settings.json. -HOOKS_JSON='{"hooks":{"SessionStart":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook session-start","timeout":10}]}],"Stop":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook stop","timeout":10}]}],"Notification":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook notification","timeout":10}]}]}}' +HOOKS_JSON='{"hooks":{"SessionStart":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook session-start","timeout":10}]}],"Stop":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook stop","timeout":10}]}],"Notification":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook notification","timeout":10}]}],"UserPromptSubmit":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook prompt-submit","timeout":10}]}]}}' if [[ "$SKIP_SESSION_ID" == true ]]; then exec "$REAL_CLAUDE" --settings "$HOOKS_JSON" "$@" diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index aaf34f86..99ca19f6 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -878,7 +878,7 @@ class TerminalController { return listNotifications() case "clear_notifications": - return clearNotifications() + return clearNotifications(args) case "set_app_focus": return setAppFocusOverride(args) @@ -8790,7 +8790,7 @@ class TerminalController { notify_surface - Notify a specific surface notify_target - Notify by workspace+surface list_notifications - List all notifications - clear_notifications - Clear all notifications + clear_notifications [--tab=X] - Clear notifications (all or per-tab) set_app_focus - Override app focus state simulate_app_active - Trigger app active handler set_status [--icon=X] [--color=#hex] [--url=X] [--priority=N] [--format=plain|markdown] [--tab=X] - Set a status entry @@ -10124,9 +10124,30 @@ class TerminalController { return result.isEmpty ? "No notifications" : result } - private func clearNotifications() -> String { + private func clearNotifications(_ args: String) -> String { + let trimmed = args.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { + DispatchQueue.main.sync { + TerminalNotificationStore.shared.clearAll() + } + return "OK" + } + let parsed = parseOptions(trimmed) + guard let tabOption = parsed.options["tab"], + !tabOption.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return "ERROR: Usage: clear_notifications [--tab=X]" + } + var tabId: UUID? DispatchQueue.main.sync { - TerminalNotificationStore.shared.clearAll() + if let tab = resolveTabForReport(trimmed) { + tabId = tab.id + } + } + guard let tabId else { + return "ERROR: Tab not found" + } + DispatchQueue.main.sync { + TerminalNotificationStore.shared.clearNotifications(forTabId: tabId) } return "OK" } From 2f6cb6ff38b39bfcfce107c8a7ac4e5ef5696a4f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:01:21 -0800 Subject: [PATCH 051/232] Add keyboard copy mode for terminal scrollback (#792) * Add keyboard copy mode for terminal scrollback * Show vim copy mode indicator in terminal * Fix vi copy-mode symbol keys and pending yank handling * Refine copy-mode badge wording and font * Rename keyboard copy-mode badge to VI MODE * Address PR feedback for copy-mode routing and keyup handling * Refresh copy-mode viewport row after scrolling --- Sources/AppDelegate.swift | 13 + Sources/GhosttyTerminalView.swift | 547 ++++++++++++++++++ Sources/KeyboardShortcutSettings.swift | 5 + Sources/TabManager.swift | 6 + .../AppDelegateShortcutRoutingTests.swift | 29 + cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 489 ++++++++++++++++ ghostty | 2 +- ghostty.h | 2 + 8 files changed, 1092 insertions(+), 1 deletion(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 64150724..10e1c47b 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -6030,6 +6030,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } + if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .toggleTerminalCopyMode)) { + let handled = tabManager?.toggleFocusedTerminalCopyMode() ?? false +#if DEBUG + dlog( + "shortcut.action name=toggleTerminalCopyMode handled=\(handled ? 1 : 0) " + + "\(debugShortcutRouteSnapshot(event: event))" + ) +#endif + // Only consume when a focused terminal actually handled the toggle. + // Otherwise allow the event to continue through the responder chain. + return handled + } + // Workspace navigation: Cmd+Ctrl+] / Cmd+Ctrl+[ if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .nextSidebarTab)) { #if DEBUG diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 0a6b2489..da2dda28 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -271,6 +271,259 @@ func resolveTerminalOpenURLTarget(_ rawValue: String) -> TerminalOpenURLTarget? return .external(fallback) } +enum TerminalKeyboardCopyModeSelectionMove: String, Equatable { + case left + case right + case up + case down + case pageUp = "page_up" + case pageDown = "page_down" + case home + case end + case beginningOfLine = "beginning_of_line" + case endOfLine = "end_of_line" +} + +enum TerminalKeyboardCopyModeAction: Equatable { + case exit + case startSelection + case clearSelection + case copyAndExit + case copyLineAndExit + case scrollLines(Int) + case scrollPage(Int) + case scrollToTop + case scrollToBottom + case jumpToPrompt(Int) + case startSearch + case searchNext + case searchPrevious + case adjustSelection(TerminalKeyboardCopyModeSelectionMove) +} + +struct TerminalKeyboardCopyModeInputState: Equatable { + var countPrefix: Int? + var pendingYankLine = false + + mutating func reset() { + countPrefix = nil + pendingYankLine = false + } +} + +enum TerminalKeyboardCopyModeResolution: Equatable { + case perform(TerminalKeyboardCopyModeAction, count: Int) + case consume +} + +private let terminalKeyboardCopyModeMaxCount = 9_999 + +private func terminalKeyboardCopyModeClampCount(_ value: Int) -> Int { + min(max(value, 1), terminalKeyboardCopyModeMaxCount) +} + +func terminalKeyboardCopyModeInitialViewportRow( + rows: Int, + imePointY: Double, + imeCellHeight: Double, + topPadding: Double = 0 +) -> Int { + let clampedRows = max(rows, 1) + guard imeCellHeight > 0 else { return clampedRows - 1 } + + // `ghostty_surface_ime_point` returns a top-origin Y coordinate at the + // cursor baseline plus one cell-height. Convert that to a zero-based row. + let estimatedRow = Int(floor(((imePointY - topPadding) / imeCellHeight) - 1)) + return max(0, min(clampedRows - 1, estimatedRow)) +} + +private func terminalKeyboardCopyModeNormalizedModifiers( + _ modifierFlags: NSEvent.ModifierFlags +) -> NSEvent.ModifierFlags { + modifierFlags + .intersection(.deviceIndependentFlagsMask) + .subtracting([.numericPad, .function, .capsLock]) +} + +private func terminalKeyboardCopyModeChars( + _ charactersIgnoringModifiers: String? +) -> String { + guard let scalar = charactersIgnoringModifiers?.unicodeScalars.first else { + return "" + } + return String(scalar).lowercased() +} + +func terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: NSEvent.ModifierFlags) -> Bool { + let normalized = terminalKeyboardCopyModeNormalizedModifiers(modifierFlags) + return normalized.contains(.command) +} + +func terminalKeyboardCopyModeAction( + keyCode: UInt16, + charactersIgnoringModifiers: String?, + modifierFlags: NSEvent.ModifierFlags, + hasSelection: Bool +) -> TerminalKeyboardCopyModeAction? { + let normalized = terminalKeyboardCopyModeNormalizedModifiers(modifierFlags) + let chars = terminalKeyboardCopyModeChars(charactersIgnoringModifiers) + + if keyCode == 53 { // Escape + return .exit + } + + switch keyCode { + case 126: // Up + return hasSelection ? .adjustSelection(.up) : .scrollLines(-1) + case 125: // Down + return hasSelection ? .adjustSelection(.down) : .scrollLines(1) + case 123: // Left + return hasSelection ? .adjustSelection(.left) : nil + case 124: // Right + return hasSelection ? .adjustSelection(.right) : nil + case 116: // Page Up + return hasSelection ? .adjustSelection(.pageUp) : .scrollPage(-1) + case 121: // Page Down + return hasSelection ? .adjustSelection(.pageDown) : .scrollPage(1) + case 115: // Home + return hasSelection ? .adjustSelection(.home) : .scrollToTop + case 119: // End + return hasSelection ? .adjustSelection(.end) : .scrollToBottom + default: + break + } + + if normalized == [.control] { + if chars == "u" || chars == "\u{15}" { + return hasSelection ? .adjustSelection(.pageUp) : .scrollPage(-1) + } + if chars == "d" || chars == "\u{04}" { + return hasSelection ? .adjustSelection(.pageDown) : .scrollPage(1) + } + if chars == "b" || chars == "\u{02}" { + return hasSelection ? .adjustSelection(.pageUp) : .scrollPage(-1) + } + if chars == "f" || chars == "\u{06}" { + return hasSelection ? .adjustSelection(.pageDown) : .scrollPage(1) + } + if chars == "y" || chars == "\u{19}" { + return hasSelection ? .adjustSelection(.up) : .scrollLines(-1) + } + if chars == "e" || chars == "\u{05}" { + return hasSelection ? .adjustSelection(.down) : .scrollLines(1) + } + return nil + } + + guard normalized.isEmpty || normalized == [.shift] else { return nil } + + switch chars { + case "q": + return .exit + case "v": + return hasSelection ? .clearSelection : .startSelection + case "y": + if normalized == [.shift], !hasSelection { + return .copyLineAndExit + } + return hasSelection ? .copyAndExit : nil + case "j": + return hasSelection ? .adjustSelection(.down) : .scrollLines(1) + case "k": + return hasSelection ? .adjustSelection(.up) : .scrollLines(-1) + case "h": + return hasSelection ? .adjustSelection(.left) : nil + case "l": + return hasSelection ? .adjustSelection(.right) : nil + case "g": + if normalized == [.shift] { + return hasSelection ? .adjustSelection(.end) : .scrollToBottom + } + return hasSelection ? .adjustSelection(.home) : .scrollToTop + case "0", "^": + return hasSelection ? .adjustSelection(.beginningOfLine) : nil + case "$", "4": + guard chars == "$" || normalized == [.shift] else { return nil } + return hasSelection ? .adjustSelection(.endOfLine) : nil + case "{", "[": + guard chars == "{" || normalized == [.shift] else { return nil } + return .jumpToPrompt(-1) + case "}", "]": + guard chars == "}" || normalized == [.shift] else { return nil } + return .jumpToPrompt(1) + case "/": + return .startSearch + case "n": + return normalized == [.shift] ? .searchPrevious : .searchNext + default: + return nil + } +} + +func terminalKeyboardCopyModeResolve( + keyCode: UInt16, + charactersIgnoringModifiers: String?, + modifierFlags: NSEvent.ModifierFlags, + hasSelection: Bool, + state: inout TerminalKeyboardCopyModeInputState +) -> TerminalKeyboardCopyModeResolution { + let normalized = terminalKeyboardCopyModeNormalizedModifiers(modifierFlags) + let chars = terminalKeyboardCopyModeChars(charactersIgnoringModifiers) + + if keyCode == 53 { // Escape + state.reset() + return .perform(.exit, count: 1) + } + + if state.pendingYankLine { + if chars == "y", normalized.isEmpty || normalized == [.shift] { + let count = terminalKeyboardCopyModeClampCount(state.countPrefix ?? 1) + state.reset() + return .perform(.copyLineAndExit, count: count) + } + // Only `yy`/`Y` are supported as line-yank operators, so cancel the + // pending yank and treat this key as a fresh command. + state.pendingYankLine = false + } + + if normalized.isEmpty, + let scalar = chars.unicodeScalars.first, + scalar.isASCII, + scalar.value >= 48, + scalar.value <= 57 { + let digit = Int(scalar.value - 48) + if digit == 0 { + if let currentCount = state.countPrefix { + state.countPrefix = terminalKeyboardCopyModeClampCount(currentCount * 10) + return .consume + } + } else { + let currentCount = state.countPrefix ?? 0 + state.countPrefix = terminalKeyboardCopyModeClampCount((currentCount * 10) + digit) + return .consume + } + } + + if !hasSelection, chars == "y", normalized.isEmpty { + state.pendingYankLine = true + return .consume + } + + guard let action = terminalKeyboardCopyModeAction( + keyCode: keyCode, + charactersIgnoringModifiers: charactersIgnoringModifiers, + modifierFlags: modifierFlags, + hasSelection: hasSelection + ) else { + state.reset() + return .consume + } + + let count = terminalKeyboardCopyModeClampCount(state.countPrefix ?? 1) + state.reset() + return .perform(action, count: count) +} + private final class GhosttySurfaceCallbackContext { weak var surfaceView: GhosttyNSView? weak var terminalSurface: TerminalSurface? @@ -1657,6 +1910,7 @@ final class TerminalSurface: Identifiable, ObservableObject { } } } + @Published private(set) var keyboardCopyModeActive: Bool = false private var searchNeedleCancellable: AnyCancellable? init( @@ -2326,6 +2580,29 @@ final class TerminalSurface: Identifiable, ObservableObject { } } + @discardableResult + func toggleKeyboardCopyMode() -> Bool { + let handled = surfaceView.toggleKeyboardCopyMode() + if handled { + setKeyboardCopyModeActive(surfaceView.isKeyboardCopyModeActive) + } + return handled + } + + func setKeyboardCopyModeActive(_ active: Bool) { + if !Thread.isMainThread { + DispatchQueue.main.async { [weak self] in + self?.setKeyboardCopyModeActive(active) + } + return + } + + if keyboardCopyModeActive != active { + keyboardCopyModeActive = active + } + hostedView.setKeyboardCopyModeIndicator(visible: active) + } + func hasSelection() -> Bool { guard let surface = surface else { return false } return ghostty_surface_has_selection(surface) @@ -2435,6 +2712,11 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { private var lastLoggedWindowBackgroundSignature: String? private var keySequence: [ghostty_input_trigger_s] = [] private var keyTables: [String] = [] + fileprivate private(set) var keyboardCopyModeActive = false + private var keyboardCopyModeConsumedKeyUps: Set = [] + private var keyboardCopyModeInputState = TerminalKeyboardCopyModeInputState() + private var keyboardCopyModeViewportRow: Int? + fileprivate var isKeyboardCopyModeActive: Bool { keyboardCopyModeActive } #if DEBUG private static let keyLatencyProbeEnabled: Bool = { if ProcessInfo.processInfo.environment["CMUX_KEY_LATENCY_PROBE"] == "1" { @@ -2597,6 +2879,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { terminalSurface = surface tabId = surface.tabId surface.attachToView(self) + surface.setKeyboardCopyModeActive(keyboardCopyModeActive) updateSurfaceSize() applySurfaceBackground() applySurfaceColorScheme(force: true) @@ -2865,6 +3148,195 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } } + @discardableResult + func toggleKeyboardCopyMode() -> Bool { + guard surface != nil else { return false } + setKeyboardCopyModeActive(!keyboardCopyModeActive) + if !keyboardCopyModeActive, let surface { + _ = ghostty_surface_clear_selection(surface) + } + return true + } + + private func setKeyboardCopyModeActive(_ active: Bool) { + keyboardCopyModeInputState.reset() + keyboardCopyModeActive = active + if active, let surface { + keyboardCopyModeViewportRow = keyboardCopyModeSelectionAnchor(surface: surface)?.row + _ = ghostty_surface_clear_selection(surface) + if keyboardCopyModeViewportRow == nil { + keyboardCopyModeViewportRow = keyboardCopyModeImeViewportRow(surface: surface) + } + } else { + keyboardCopyModeViewportRow = nil + } + terminalSurface?.setKeyboardCopyModeActive(active) + } + + private func performBindingAction(_ action: String, repeatCount: Int) { + let count = terminalKeyboardCopyModeClampCount(repeatCount) + for _ in 0 ..< count { + _ = performBindingAction(action) + } + } + + private func currentKeyboardCopyModeViewportRow(surface: ghostty_surface_t) -> Int { + let rows = max(Int(ghostty_surface_size(surface).rows), 1) + let fallback = rows - 1 + return max(0, min(rows - 1, keyboardCopyModeViewportRow ?? fallback)) + } + + private func keyboardCopyModeImeViewportRow(surface: ghostty_surface_t) -> Int { + let rows = max(Int(ghostty_surface_size(surface).rows), 1) + var x: Double = 0 + var y: Double = 0 + var width: Double = 0 + var height: Double = 0 + ghostty_surface_ime_point(surface, &x, &y, &width, &height) + return terminalKeyboardCopyModeInitialViewportRow( + rows: rows, + imePointY: y, + imeCellHeight: height + ) + } + + private func keyboardCopyModeSelectionAnchor(surface: ghostty_surface_t) -> (row: Int, y: Double)? { + let size = ghostty_surface_size(surface) + guard size.rows > 0, size.columns > 0 else { return nil } + guard ghostty_surface_select_cursor_cell(surface) else { return nil } + + var text = ghostty_text_s() + guard ghostty_surface_read_selection(surface, &text) else { return nil } + defer { ghostty_surface_free_text(surface, &text) } + + let rows = max(Int(size.rows), 1) + let cols = max(Int(size.columns), 1) + let rawRow = Int(text.offset_start) / cols + let clampedRow = max(0, min(rows - 1, rawRow)) + return (row: clampedRow, y: text.tl_px_y) + } + + private func refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: ghostty_surface_t) { + guard !ghostty_surface_has_selection(surface) else { return } + guard let anchor = keyboardCopyModeSelectionAnchor(surface: surface) else { return } + keyboardCopyModeViewportRow = anchor.row + _ = ghostty_surface_clear_selection(surface) + } + + private func copyCurrentViewportLinesToClipboard( + surface: ghostty_surface_t, + startRow: Int, + lineCount: Int + ) -> Bool { + let clampedCount = terminalKeyboardCopyModeClampCount(lineCount) + let rows = max(Int(ghostty_surface_size(surface).rows), 1) + let targetRow = max(0, min(rows - 1, startRow)) + let endRow = min(rows - 1, targetRow + clampedCount - 1) + guard let anchor = keyboardCopyModeSelectionAnchor(surface: surface) else { + return false + } + _ = ghostty_surface_clear_selection(surface) + + var imeX: Double = 0 + var imeY: Double = 0 + var imeWidth: Double = 0 + var imeHeight: Double = 0 + ghostty_surface_ime_point(surface, &imeX, &imeY, &imeWidth, &imeHeight) + let cellHeight = imeHeight > 0 ? imeHeight : max(bounds.height / Double(rows), 1) + let yMax = max(bounds.height - 1, 0) + + let startRawY = anchor.y + (Double(targetRow - anchor.row) * cellHeight) + let endRawY = anchor.y + (Double(endRow - anchor.row) * cellHeight) + let startY = max(0, min(startRawY, yMax)) + let endY = max(0, min(endRawY, yMax)) + let xMax = max(bounds.width - 1, 0) + let startX = min(1, xMax) + let endX = xMax + + let mods = ghostty_input_mods_e(rawValue: GHOSTTY_MODS_NONE.rawValue) ?? GHOSTTY_MODS_NONE + ghostty_surface_mouse_pos(surface, startX, startY, mods) + guard ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_LEFT, mods) else { + return false + } + defer { + _ = ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_LEFT, mods) + } + ghostty_surface_mouse_pos(surface, endX, endY, mods) + guard ghostty_surface_has_selection(surface) else { return false } + + return performBindingAction("copy_to_clipboard") + } + + private func handleKeyboardCopyModeIfNeeded(_ event: NSEvent, surface: ghostty_surface_t) -> Bool { + guard keyboardCopyModeActive else { return false } + + if terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: event.modifierFlags) { + keyboardCopyModeInputState.reset() + return false + } + + let hasSelection = ghostty_surface_has_selection(surface) + let resolution = terminalKeyboardCopyModeResolve( + keyCode: event.keyCode, + charactersIgnoringModifiers: event.charactersIgnoringModifiers, + modifierFlags: event.modifierFlags, + hasSelection: hasSelection, + state: &keyboardCopyModeInputState + ) + guard case let .perform(action, count) = resolution else { + return true + } + + switch action { + case .exit: + _ = ghostty_surface_clear_selection(surface) + setKeyboardCopyModeActive(false) + case .startSelection: + _ = ghostty_surface_select_cursor_cell(surface) + case .clearSelection: + _ = ghostty_surface_clear_selection(surface) + case .copyAndExit: + _ = performBindingAction("copy_to_clipboard") + _ = ghostty_surface_clear_selection(surface) + setKeyboardCopyModeActive(false) + case .copyLineAndExit: + let startRow = currentKeyboardCopyModeViewportRow(surface: surface) + _ = copyCurrentViewportLinesToClipboard( + surface: surface, + startRow: startRow, + lineCount: count + ) + _ = ghostty_surface_clear_selection(surface) + setKeyboardCopyModeActive(false) + case let .scrollLines(delta): + _ = performBindingAction("scroll_page_lines:\(delta * count)") + refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) + case let .scrollPage(delta): + performBindingAction(delta > 0 ? "scroll_page_down" : "scroll_page_up", repeatCount: count) + refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) + case .scrollToTop: + keyboardCopyModeViewportRow = 0 + _ = performBindingAction("scroll_to_top") + case .scrollToBottom: + keyboardCopyModeViewportRow = max(Int(ghostty_surface_size(surface).rows) - 1, 0) + _ = performBindingAction("scroll_to_bottom") + case let .jumpToPrompt(delta): + _ = performBindingAction("jump_to_prompt:\(delta * count)") + refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) + case .startSearch: + _ = performBindingAction("start_search") + case .searchNext: + performBindingAction("navigate_search:next", repeatCount: count) + refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) + case .searchPrevious: + performBindingAction("navigate_search:previous", repeatCount: count) + refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) + case let .adjustSelection(direction): + performBindingAction("adjust_selection:\(direction.rawValue)", repeatCount: count) + } + return true + } + // MARK: - Input Handling @IBAction func copy(_ sender: Any?) { @@ -3227,6 +3699,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { super.keyDown(with: event) return } + if handleKeyboardCopyModeIfNeeded(event, surface: surface) { + keyboardCopyModeConsumedKeyUps.insert(event.keyCode) + return + } #if DEBUG recordKeyLatency(path: "keyDown", event: event) #endif @@ -3436,6 +3912,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { return } + if keyboardCopyModeConsumedKeyUps.remove(event.keyCode) != nil { + return + } + // Build release events from the same translation path as keyDown so // consumers that depend on precise key identity (for example Space // hold/release flows) receive consistent metadata. @@ -4088,6 +4568,14 @@ private final class GhosttyFlashOverlayView: NSView { } } +private final class GhosttyPassthroughVisualEffectView: NSVisualEffectView { + override var acceptsFirstResponder: Bool { false } + + override func hitTest(_ point: NSPoint) -> NSView? { + nil + } +} + final class GhosttySurfaceScrollView: NSView { private let backgroundView: NSView private let scrollView: GhosttyScrollView @@ -4099,6 +4587,8 @@ final class GhosttySurfaceScrollView: NSView { private let notificationRingLayer: CAShapeLayer private let flashOverlayView: GhosttyFlashOverlayView private let flashLayer: CAShapeLayer + private let keyboardCopyModeBadgeView: GhosttyPassthroughVisualEffectView + private let keyboardCopyModeBadgeLabel: NSTextField private var searchOverlayHostingView: NSHostingView? private var observers: [NSObjectProtocol] = [] private var windowObservers: [NSObjectProtocol] = [] @@ -4253,6 +4743,8 @@ final class GhosttySurfaceScrollView: NSView { notificationRingLayer = CAShapeLayer() flashOverlayView = GhosttyFlashOverlayView(frame: .zero) flashLayer = CAShapeLayer() + keyboardCopyModeBadgeView = GhosttyPassthroughVisualEffectView(frame: .zero) + keyboardCopyModeBadgeLabel = NSTextField(labelWithString: "VI MODE") scrollView.hasVerticalScroller = true scrollView.hasHorizontalScroller = false scrollView.autohidesScrollers = false @@ -4325,6 +4817,32 @@ final class GhosttySurfaceScrollView: NSView { flashLayer.opacity = 0 flashOverlayView.layer?.addSublayer(flashLayer) addSubview(flashOverlayView) + keyboardCopyModeBadgeView.translatesAutoresizingMaskIntoConstraints = false + keyboardCopyModeBadgeView.wantsLayer = true + keyboardCopyModeBadgeView.material = .hudWindow + keyboardCopyModeBadgeView.blendingMode = .withinWindow + keyboardCopyModeBadgeView.state = .active + keyboardCopyModeBadgeView.layer?.cornerRadius = 7 + keyboardCopyModeBadgeView.layer?.masksToBounds = true + keyboardCopyModeBadgeView.layer?.borderWidth = 1 + keyboardCopyModeBadgeView.layer?.borderColor = cmuxAccentNSColor().withAlphaComponent(0.45).cgColor + keyboardCopyModeBadgeView.alphaValue = 0.97 + keyboardCopyModeBadgeLabel.translatesAutoresizingMaskIntoConstraints = false + keyboardCopyModeBadgeLabel.textColor = NSColor.labelColor + keyboardCopyModeBadgeLabel.lineBreakMode = .byClipping + keyboardCopyModeBadgeView.addSubview(keyboardCopyModeBadgeLabel) + NSLayoutConstraint.activate([ + keyboardCopyModeBadgeLabel.leadingAnchor.constraint(equalTo: keyboardCopyModeBadgeView.leadingAnchor, constant: 8), + keyboardCopyModeBadgeLabel.trailingAnchor.constraint(equalTo: keyboardCopyModeBadgeView.trailingAnchor, constant: -8), + keyboardCopyModeBadgeLabel.topAnchor.constraint(equalTo: keyboardCopyModeBadgeView.topAnchor, constant: 4), + keyboardCopyModeBadgeLabel.bottomAnchor.constraint(equalTo: keyboardCopyModeBadgeView.bottomAnchor, constant: -4), + ]) + keyboardCopyModeBadgeView.isHidden = true + addSubview(keyboardCopyModeBadgeView) + NSLayoutConstraint.activate([ + keyboardCopyModeBadgeView.topAnchor.constraint(equalTo: topAnchor, constant: 8), + keyboardCopyModeBadgeView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), + ]) scrollView.contentView.postsBoundsChangedNotifications = true observers.append(NotificationCenter.default.addObserver( @@ -4579,6 +5097,9 @@ final class GhosttySurfaceScrollView: NSView { overlay.trailingAnchor.constraint(equalTo: trailingAnchor), ]) } + if !keyboardCopyModeBadgeView.isHidden { + addSubview(keyboardCopyModeBadgeView, positioned: .above, relativeTo: overlay) + } return } @@ -4591,9 +5112,30 @@ final class GhosttySurfaceScrollView: NSView { overlay.leadingAnchor.constraint(equalTo: leadingAnchor), overlay.trailingAnchor.constraint(equalTo: trailingAnchor), ]) + if !keyboardCopyModeBadgeView.isHidden { + addSubview(keyboardCopyModeBadgeView, positioned: .above, relativeTo: overlay) + } searchOverlayHostingView = overlay } + func setKeyboardCopyModeIndicator(visible: Bool) { + if !Thread.isMainThread { + DispatchQueue.main.async { [weak self] in + self?.setKeyboardCopyModeIndicator(visible: visible) + } + return + } + + keyboardCopyModeBadgeView.isHidden = !visible + if visible { + if let overlay = searchOverlayHostingView { + addSubview(keyboardCopyModeBadgeView, positioned: .above, relativeTo: overlay) + } else { + addSubview(keyboardCopyModeBadgeView, positioned: .above, relativeTo: nil) + } + } + } + private func dropZoneOverlayFrame(for zone: DropZone, in size: CGSize) -> CGRect { let padding: CGFloat = 4 switch zone { @@ -4916,6 +5458,10 @@ final class GhosttySurfaceScrollView: NSView { return overlay.superview === self && !overlay.isHidden } + func debugHasKeyboardCopyModeIndicator() -> Bool { + keyboardCopyModeBadgeView.superview === self && !keyboardCopyModeBadgeView.isHidden + } + #endif /// Handle file/URL drops, forwarding to the terminal as shell-escaped paths. @@ -5797,6 +6343,7 @@ struct GhosttyTerminalView: NSViewRepresentable { ) hostedView.setNotificationRing(visible: showsUnreadNotificationRing) hostedView.setSearchOverlay(searchState: searchState) + hostedView.setKeyboardCopyModeIndicator(visible: terminalSurface.keyboardCopyModeActive) hostedView.setFocusHandler { onFocus?(terminalSurface.id) } hostedView.setTriggerFlashHandler(onTriggerFlash) let portalExpectedSurfaceId = terminalSurface.id diff --git a/Sources/KeyboardShortcutSettings.swift b/Sources/KeyboardShortcutSettings.swift index 13095d90..922fe5f2 100644 --- a/Sources/KeyboardShortcutSettings.swift +++ b/Sources/KeyboardShortcutSettings.swift @@ -23,6 +23,7 @@ enum KeyboardShortcutSettings { case renameWorkspace case closeWorkspace case newSurface + case toggleTerminalCopyMode // Panes / splits case focusLeft @@ -60,6 +61,7 @@ enum KeyboardShortcutSettings { case .renameWorkspace: return "Rename Workspace" case .closeWorkspace: return "Close Workspace" case .newSurface: return "New Surface" + case .toggleTerminalCopyMode: return "Toggle Terminal Copy Mode" case .focusLeft: return "Focus Pane Left" case .focusRight: return "Focus Pane Right" case .focusUp: return "Focus Pane Up" @@ -102,6 +104,7 @@ enum KeyboardShortcutSettings { case .nextSurface: return "shortcut.nextSurface" case .prevSurface: return "shortcut.prevSurface" case .newSurface: return "shortcut.newSurface" + case .toggleTerminalCopyMode: return "shortcut.toggleTerminalCopyMode" case .openBrowser: return "shortcut.openBrowser" case .toggleBrowserDeveloperTools: return "shortcut.toggleBrowserDeveloperTools" case .showBrowserJavaScriptConsole: return "shortcut.showBrowserJavaScriptConsole" @@ -160,6 +163,8 @@ enum KeyboardShortcutSettings { return StoredShortcut(key: "[", command: true, shift: true, option: false, control: false) case .newSurface: return StoredShortcut(key: "t", command: true, shift: false, option: false, control: false) + case .toggleTerminalCopyMode: + return StoredShortcut(key: "m", command: true, shift: true, option: false, control: false) case .openBrowser: return StoredShortcut(key: "l", command: true, shift: true, option: false, control: false) case .toggleBrowserDeveloperTools: diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 39b26771..aaeac1fe 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -753,6 +753,12 @@ class TabManager: ObservableObject { _ = selectedTerminalPanel?.performBindingAction("search:previous") } + @discardableResult + func toggleFocusedTerminalCopyMode() -> Bool { + guard let panel = selectedTerminalPanel else { return false } + return panel.surface.toggleKeyboardCopyMode() + } + func hideFind() { selectedTerminalPanel?.searchState = nil } diff --git a/cmuxTests/AppDelegateShortcutRoutingTests.swift b/cmuxTests/AppDelegateShortcutRoutingTests.swift index eaf8fb61..41689662 100644 --- a/cmuxTests/AppDelegateShortcutRoutingTests.swift +++ b/cmuxTests/AppDelegateShortcutRoutingTests.swift @@ -476,6 +476,35 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { XCTAssertTrue(appDelegate.tabManager === firstManager, "Unresolved event window should not retarget active manager") } + func testCmdShiftMReturnsFalseWhenNoFocusedTerminalCanHandle() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + // Force unresolved shortcut routing context and no active manager. + appDelegate.tabManager = nil + + guard let event = makeKeyDownEvent( + key: "m", + modifiers: [.command, .shift], + keyCode: 46, // kVK_ANSI_M + windowNumber: Int.max + ) else { + XCTFail("Failed to construct Cmd+Shift+M event") + return + } + +#if DEBUG + XCTAssertFalse( + appDelegate.debugHandleCustomShortcut(event: event), + "Cmd+Shift+M should not be consumed when no terminal can toggle copy mode" + ) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + func testPresentPreferencesWindowShowsCustomSettingsWindowAndActivates() { var showFallbackSettingsWindowCallCount = 0 var activateApplicationCallCount = 0 diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index cc75f384..0dd21b27 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -1217,6 +1217,21 @@ final class WorkspaceRenameShortcutDefaultsTests: XCTestCase { XCTAssertTrue(prevShortcut.eventModifiers.contains(.control)) } + func testToggleTerminalCopyModeShortcutDefaultsAndMetadata() { + XCTAssertEqual(KeyboardShortcutSettings.Action.toggleTerminalCopyMode.label, "Toggle Terminal Copy Mode") + XCTAssertEqual( + KeyboardShortcutSettings.Action.toggleTerminalCopyMode.defaultsKey, + "shortcut.toggleTerminalCopyMode" + ) + + let shortcut = KeyboardShortcutSettings.Action.toggleTerminalCopyMode.defaultShortcut + XCTAssertEqual(shortcut.key, "m") + XCTAssertTrue(shortcut.command) + XCTAssertTrue(shortcut.shift) + XCTAssertFalse(shortcut.option) + XCTAssertFalse(shortcut.control) + } + func testMenuItemKeyEquivalentHandlesArrowAndTabKeys() { XCTAssertNotNil(StoredShortcut(key: "←", command: true, shift: false, option: false, control: false).menuItemKeyEquivalent) XCTAssertNotNil(StoredShortcut(key: "→", command: true, shift: false, option: false, control: false).menuItemKeyEquivalent) @@ -1234,6 +1249,463 @@ final class WorkspaceRenameShortcutDefaultsTests: XCTestCase { } } +final class TerminalKeyboardCopyModeActionTests: XCTestCase { + func testCopyModeBypassAllowsOnlyCommandShortcuts() { + XCTAssertTrue(terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: [.command])) + XCTAssertTrue(terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: [.command, .shift])) + XCTAssertTrue(terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: [.command, .option])) + XCTAssertFalse(terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: [.option])) + XCTAssertFalse(terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: [.option, .shift])) + XCTAssertFalse(terminalKeyboardCopyModeShouldBypassForShortcut(modifierFlags: [.control])) + } + + func testJKWithoutSelectionScrollByLine() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 38, + charactersIgnoringModifiers: "j", + modifierFlags: [], + hasSelection: false + ), + .scrollLines(1) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 40, + charactersIgnoringModifiers: "k", + modifierFlags: [], + hasSelection: false + ), + .scrollLines(-1) + ) + } + + func testCapsLockDoesNotBlockLetterMappings() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 38, + charactersIgnoringModifiers: "j", + modifierFlags: [.capsLock], + hasSelection: false + ), + .scrollLines(1) + ) + } + + func testJKWithSelectionAdjustSelection() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 38, + charactersIgnoringModifiers: "j", + modifierFlags: [], + hasSelection: true + ), + .adjustSelection(.down) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 40, + charactersIgnoringModifiers: "k", + modifierFlags: [], + hasSelection: true + ), + .adjustSelection(.up) + ) + } + + func testControlPagingSupportsPrintableAndControlCharacters() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 0, + charactersIgnoringModifiers: "\u{15}", + modifierFlags: [.control], + hasSelection: false + ), + .scrollPage(-1) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 0, + charactersIgnoringModifiers: "\u{04}", + modifierFlags: [.control], + hasSelection: true + ), + .adjustSelection(.pageDown) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 0, + charactersIgnoringModifiers: "\u{02}", + modifierFlags: [.control], + hasSelection: false + ), + .scrollPage(-1) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 0, + charactersIgnoringModifiers: "\u{06}", + modifierFlags: [.control], + hasSelection: true + ), + .adjustSelection(.pageDown) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 0, + charactersIgnoringModifiers: "\u{19}", + modifierFlags: [.control], + hasSelection: false + ), + .scrollLines(-1) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 0, + charactersIgnoringModifiers: "\u{05}", + modifierFlags: [.control], + hasSelection: true + ), + .adjustSelection(.down) + ) + } + + func testVGYMapping() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 9, + charactersIgnoringModifiers: "v", + modifierFlags: [], + hasSelection: false + ), + .startSelection + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 9, + charactersIgnoringModifiers: "v", + modifierFlags: [], + hasSelection: true + ), + .clearSelection + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 16, + charactersIgnoringModifiers: "y", + modifierFlags: [], + hasSelection: true + ), + .copyAndExit + ) + } + + func testGAndShiftGMapping() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 5, + charactersIgnoringModifiers: "g", + modifierFlags: [], + hasSelection: false + ), + .scrollToTop + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 5, + charactersIgnoringModifiers: "g", + modifierFlags: [.shift], + hasSelection: false + ), + .scrollToBottom + ) + } + + func testLineBoundaryPromptAndSearchMappings() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 29, + charactersIgnoringModifiers: "0", + modifierFlags: [], + hasSelection: true + ), + .adjustSelection(.beginningOfLine) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 20, + charactersIgnoringModifiers: "^", + modifierFlags: [.shift], + hasSelection: true + ), + .adjustSelection(.beginningOfLine) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 21, + charactersIgnoringModifiers: "4", + modifierFlags: [.shift], + hasSelection: true + ), + .adjustSelection(.endOfLine) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 33, + charactersIgnoringModifiers: "[", + modifierFlags: [.shift], + hasSelection: false + ), + .jumpToPrompt(-1) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 30, + charactersIgnoringModifiers: "]", + modifierFlags: [.shift], + hasSelection: false + ), + .jumpToPrompt(1) + ) + XCTAssertNil( + terminalKeyboardCopyModeAction( + keyCode: 21, + charactersIgnoringModifiers: "4", + modifierFlags: [], + hasSelection: true + ) + ) + XCTAssertNil( + terminalKeyboardCopyModeAction( + keyCode: 33, + charactersIgnoringModifiers: "[", + modifierFlags: [], + hasSelection: false + ) + ) + XCTAssertNil( + terminalKeyboardCopyModeAction( + keyCode: 30, + charactersIgnoringModifiers: "]", + modifierFlags: [], + hasSelection: false + ) + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 44, + charactersIgnoringModifiers: "/", + modifierFlags: [], + hasSelection: false + ), + .startSearch + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 45, + charactersIgnoringModifiers: "n", + modifierFlags: [], + hasSelection: false + ), + .searchNext + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 45, + charactersIgnoringModifiers: "n", + modifierFlags: [.shift], + hasSelection: false + ), + .searchPrevious + ) + } + + func testShiftVMatchesVisualToggleBehavior() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 9, + charactersIgnoringModifiers: "v", + modifierFlags: [.shift], + hasSelection: false + ), + .startSelection + ) + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 9, + charactersIgnoringModifiers: "v", + modifierFlags: [.shift], + hasSelection: true + ), + .clearSelection + ) + } + + func testEscapeAlwaysExits() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 53, + charactersIgnoringModifiers: "", + modifierFlags: [], + hasSelection: false + ), + .exit + ) + } + + func testQAlwaysExits() { + XCTAssertEqual( + terminalKeyboardCopyModeAction( + keyCode: 12, // kVK_ANSI_Q + charactersIgnoringModifiers: "q", + modifierFlags: [], + hasSelection: false + ), + .exit + ) + } +} + +final class TerminalKeyboardCopyModeResolveTests: XCTestCase { + private func resolve( + _ keyCode: UInt16, + chars: String, + modifiers: NSEvent.ModifierFlags = [], + hasSelection: Bool, + state: inout TerminalKeyboardCopyModeInputState + ) -> TerminalKeyboardCopyModeResolution { + terminalKeyboardCopyModeResolve( + keyCode: keyCode, + charactersIgnoringModifiers: chars, + modifierFlags: modifiers, + hasSelection: hasSelection, + state: &state + ) + } + + func testCountPrefixAppliesToMotion() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(20, chars: "3", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(38, chars: "j", hasSelection: false, state: &state), .perform(.scrollLines(1), count: 3)) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } + + func testZeroAppendsCountOrActsAsMotion() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(19, chars: "2", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(29, chars: "0", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(40, chars: "k", hasSelection: false, state: &state), .perform(.scrollLines(-1), count: 20)) + + var selectionState = TerminalKeyboardCopyModeInputState() + XCTAssertEqual( + resolve(29, chars: "0", hasSelection: true, state: &selectionState), + .perform(.adjustSelection(.beginningOfLine), count: 1) + ) + } + + func testYankLineOperatorSupportsYYAndYWithCounts() { + var yyState = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(16, chars: "y", hasSelection: false, state: &yyState), .consume) + XCTAssertEqual(resolve(16, chars: "y", hasSelection: false, state: &yyState), .perform(.copyLineAndExit, count: 1)) + + var countedState = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(21, chars: "4", hasSelection: false, state: &countedState), .consume) + XCTAssertEqual(resolve(16, chars: "y", hasSelection: false, state: &countedState), .consume) + XCTAssertEqual(resolve(16, chars: "y", hasSelection: false, state: &countedState), .perform(.copyLineAndExit, count: 4)) + + var shiftYState = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(20, chars: "3", hasSelection: false, state: &shiftYState), .consume) + XCTAssertEqual( + resolve(16, chars: "y", modifiers: [.shift], hasSelection: false, state: &shiftYState), + .perform(.copyLineAndExit, count: 3) + ) + } + + func testPendingYankLineDoesNotSwallowNextCommand() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(16, chars: "y", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(38, chars: "j", hasSelection: false, state: &state), .perform(.scrollLines(1), count: 1)) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } + + func testSearchAndPromptMotionsUseCounts() { + var promptState = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(20, chars: "3", hasSelection: false, state: &promptState), .consume) + XCTAssertEqual( + resolve(30, chars: "]", modifiers: [.shift], hasSelection: false, state: &promptState), + .perform(.jumpToPrompt(1), count: 3) + ) + + var searchState = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(18, chars: "2", hasSelection: false, state: &searchState), .consume) + XCTAssertEqual(resolve(45, chars: "n", hasSelection: false, state: &searchState), .perform(.searchNext, count: 2)) + } + + func testInvalidKeyClearsPendingState() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(18, chars: "2", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(7, chars: "x", hasSelection: false, state: &state), .consume) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } +} + +final class TerminalKeyboardCopyModeViewportRowTests: XCTestCase { + func testInitialViewportRowUsesImePointBaseline() { + XCTAssertEqual( + terminalKeyboardCopyModeInitialViewportRow( + rows: 24, + imePointY: 24, + imeCellHeight: 24 + ), + 0 + ) + XCTAssertEqual( + terminalKeyboardCopyModeInitialViewportRow( + rows: 24, + imePointY: 240, + imeCellHeight: 24 + ), + 9 + ) + XCTAssertEqual( + terminalKeyboardCopyModeInitialViewportRow( + rows: 24, + imePointY: 48, + imeCellHeight: 24, + topPadding: 24 + ), + 0 + ) + } + + func testInitialViewportRowClampsBoundsAndFallsBackWhenHeightMissing() { + XCTAssertEqual( + terminalKeyboardCopyModeInitialViewportRow( + rows: 24, + imePointY: 0, + imeCellHeight: 24 + ), + 0 + ) + XCTAssertEqual( + terminalKeyboardCopyModeInitialViewportRow( + rows: 24, + imePointY: 9999, + imeCellHeight: 24 + ), + 23 + ) + XCTAssertEqual( + terminalKeyboardCopyModeInitialViewportRow( + rows: 24, + imePointY: 123, + imeCellHeight: 0 + ), + 23 + ) + } +} + @MainActor final class BrowserDeveloperToolsConfigurationTests: XCTestCase { func testBrowserPanelEnablesInspectableWebViewAndDeveloperExtras() { @@ -7432,6 +7904,23 @@ final class GhosttySurfaceOverlayTests: XCTestCase { XCTAssertFalse(hostedView.debugHasSearchOverlay()) } + func testKeyboardCopyModeIndicatorMountsAndUnmounts() { + let surface = TerminalSurface( + tabId: UUID(), + context: GHOSTTY_SURFACE_CONTEXT_SPLIT, + configTemplate: nil, + workingDirectory: nil + ) + let hostedView = surface.hostedView + XCTAssertFalse(hostedView.debugHasKeyboardCopyModeIndicator()) + + hostedView.setKeyboardCopyModeIndicator(visible: true) + XCTAssertTrue(hostedView.debugHasKeyboardCopyModeIndicator()) + + hostedView.setKeyboardCopyModeIndicator(visible: false) + XCTAssertFalse(hostedView.debugHasKeyboardCopyModeIndicator()) + } + func testForceRefreshNoopsAfterSurfaceReleaseDuringGeometryReconcile() throws { #if DEBUG let window = NSWindow( diff --git a/ghostty b/ghostty index 80d3fa07..7dd58982 160000 --- a/ghostty +++ b/ghostty @@ -1 +1 @@ -Subproject commit 80d3fa07ff8ae86fe6089083371f71ac7634648f +Subproject commit 7dd589824d4c9bda8265355718800cccaf7189a0 diff --git a/ghostty.h b/ghostty.h index 3d397308..b54e84f1 100644 --- a/ghostty.h +++ b/ghostty.h @@ -1108,6 +1108,8 @@ void ghostty_surface_complete_clipboard_request(ghostty_surface_t, void*, bool); bool ghostty_surface_has_selection(ghostty_surface_t); +bool ghostty_surface_select_cursor_cell(ghostty_surface_t); +bool ghostty_surface_clear_selection(ghostty_surface_t); bool ghostty_surface_read_selection(ghostty_surface_t, ghostty_text_s*); bool ghostty_surface_read_text(ghostty_surface_t, ghostty_selection_s, From c7bdd92df93f5009265643eb54d52132630a8ebf Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Tue, 3 Mar 2026 19:14:55 -0800 Subject: [PATCH 052/232] Fix Ghostty theme loading in debug builds (#830) * Revert "Fix Cmd+Tab activation ordering for cmux windows (#744) (#766)" This reverts commit a6f6485e3c3694ad5ead0fdf13a5067b590ee76d. * Fix debug Ghostty theme loading fallback --- Sources/GhosttyTerminalView.swift | 69 ++++++++++++++++++++++++++++++ cmuxTests/GhosttyConfigTests.swift | 46 ++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index da2dda28..cc1b94ff 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -551,6 +551,7 @@ private final class GhosttySurfaceCallbackContext { class GhosttyApp { static let shared = GhosttyApp() + private static let releaseBundleIdentifier = "com.cmuxterm.app" private static let backgroundLogTimestampFormatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] @@ -912,6 +913,7 @@ class GhosttyApp { private func loadDefaultConfigFilesWithLegacyFallback(_ config: ghostty_config_t) { ghostty_config_load_default_files(config) + loadReleaseAppSupportGhosttyConfigIfNeeded(config) loadLegacyGhosttyConfigIfNeeded(config) ghostty_config_load_recursive_files(config) ghostty_config_finalize(config) @@ -926,6 +928,22 @@ class GhosttyApp { return true } + static func shouldLoadReleaseAppSupportGhosttyConfig( + currentBundleIdentifier: String?, + currentConfigFileSize: Int?, + currentLegacyConfigFileSize: Int?, + releaseConfigFileSize: Int?, + releaseLegacyConfigFileSize: Int? + ) -> Bool { + guard SocketControlSettings.isDebugLikeBundleIdentifier(currentBundleIdentifier) else { return false } + + let hasCurrentAppSupportConfig = (currentConfigFileSize ?? 0) > 0 || (currentLegacyConfigFileSize ?? 0) > 0 + guard !hasCurrentAppSupportConfig else { return false } + + let hasReleaseAppSupportConfig = (releaseConfigFileSize ?? 0) > 0 || (releaseLegacyConfigFileSize ?? 0) > 0 + return hasReleaseAppSupportConfig + } + static func shouldApplyDefaultBackgroundUpdate( currentScope: GhosttyDefaultBackgroundUpdateScope, incomingScope: GhosttyDefaultBackgroundUpdateScope @@ -963,6 +981,57 @@ class GhosttyApp { return true } + private func loadReleaseAppSupportGhosttyConfigIfNeeded(_ config: ghostty_config_t) { + #if os(macOS) + let fm = FileManager.default + guard let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { return } + guard let currentBundleIdentifier = Bundle.main.bundleIdentifier, + !currentBundleIdentifier.isEmpty else { return } + + let currentAppSupportDir = appSupport.appendingPathComponent(currentBundleIdentifier, isDirectory: true) + let releaseAppSupportDir = appSupport.appendingPathComponent(Self.releaseBundleIdentifier, isDirectory: true) + let currentConfig = currentAppSupportDir.appendingPathComponent("config.ghostty", isDirectory: false) + let currentLegacyConfig = currentAppSupportDir.appendingPathComponent("config", isDirectory: false) + let releaseConfig = releaseAppSupportDir.appendingPathComponent("config.ghostty", isDirectory: false) + let releaseLegacyConfig = releaseAppSupportDir.appendingPathComponent("config", isDirectory: false) + + func fileSize(_ url: URL) -> Int? { + guard let attrs = try? fm.attributesOfItem(atPath: url.path), + let size = attrs[.size] as? NSNumber else { return nil } + return size.intValue + } + + let releaseConfigSize = fileSize(releaseConfig) + let releaseLegacyConfigSize = fileSize(releaseLegacyConfig) + + guard Self.shouldLoadReleaseAppSupportGhosttyConfig( + currentBundleIdentifier: currentBundleIdentifier, + currentConfigFileSize: fileSize(currentConfig), + currentLegacyConfigFileSize: fileSize(currentLegacyConfig), + releaseConfigFileSize: releaseConfigSize, + releaseLegacyConfigFileSize: releaseLegacyConfigSize + ) else { return } + + if let releaseLegacyConfigSize, releaseLegacyConfigSize > 0 { + releaseLegacyConfig.path.withCString { path in + ghostty_config_load_file(config, path) + } + } + + if let releaseConfigSize, releaseConfigSize > 0 { + releaseConfig.path.withCString { path in + ghostty_config_load_file(config, path) + } + } + + #if DEBUG + Self.initLog( + "loaded release app support ghostty config fallback from: \(releaseAppSupportDir.path)" + ) + #endif + #endif + } + private func loadLegacyGhosttyConfigIfNeeded(_ config: ghostty_config_t) { #if os(macOS) // Ghostty 1.3+ prefers `config.ghostty`, but some users still have their real diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index 65fe2841..ac9d68b7 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -258,6 +258,52 @@ final class GhosttyConfigTests: XCTestCase { ) } + func testReleaseAppSupportFallbackLoadsForDebugWhenOnlyReleaseConfigExists() { + XCTAssertTrue( + GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + currentBundleIdentifier: "com.cmuxterm.app.debug", + currentConfigFileSize: nil, + currentLegacyConfigFileSize: nil, + releaseConfigFileSize: 128, + releaseLegacyConfigFileSize: nil + ) + ) + } + + func testReleaseAppSupportFallbackSkipsWhenDebugConfigAlreadyExists() { + XCTAssertFalse( + GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + currentBundleIdentifier: "com.cmuxterm.app.debug.issue-829", + currentConfigFileSize: nil, + currentLegacyConfigFileSize: 64, + releaseConfigFileSize: 128, + releaseLegacyConfigFileSize: nil + ) + ) + } + + func testReleaseAppSupportFallbackSkipsForNonDebugBundleOrMissingReleaseConfig() { + XCTAssertFalse( + GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + currentBundleIdentifier: "com.cmuxterm.app", + currentConfigFileSize: nil, + currentLegacyConfigFileSize: nil, + releaseConfigFileSize: 128, + releaseLegacyConfigFileSize: nil + ) + ) + + XCTAssertFalse( + GhosttyApp.shouldLoadReleaseAppSupportGhosttyConfig( + currentBundleIdentifier: "com.cmuxterm.app.debug", + currentConfigFileSize: nil, + currentLegacyConfigFileSize: nil, + releaseConfigFileSize: nil, + releaseLegacyConfigFileSize: 0 + ) + ) + } + func testDefaultBackgroundUpdateScopePrioritizesSurfaceOverAppAndUnscoped() { XCTAssertTrue( GhosttyApp.shouldApplyDefaultBackgroundUpdate( From 044a3dbb648ef3bbb284fb5f61eea99ff2fb976c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:56:01 -0800 Subject: [PATCH 053/232] Vi mode: half-page scroll, visible cursor, gg fix (#851) * Vi mode P0 improvements: half-page scroll, visible cursor, gg fix Three changes to keyboard copy mode (vi mode): 1. Ctrl+U/D now scroll half-page (was full-page). Ctrl+B/F remain full-page. Uses Ghostty's scroll_page_fractional binding. 2. Entering copy mode now creates a 1-cell selection at the terminal cursor via select_cursor_cell, giving the user a visible cursor indicator. Visual mode (v) is tracked separately from Ghostty's has_selection so the cursor selection doesn't make every motion behave as visual. Exiting visual mode (v again) collapses back to the cursor cell. 3. Single 'g' is now a prefix key requiring 'gg' to scroll to top, matching standard vim behavior. Uses the same pendingG state machine pattern as pendingYankLine for 'yy'. Part of https://github.com/manaflow-ai/cmux/issues/846 * Fix viewport row refresh with persistent cursor selection The 1-cell cursor selection made refreshKeyboardCopyModeViewportRowFromVisibleAnchor always bail (has_selection was always true). Guard on keyboardCopyModeVisualActive instead so viewport row updates work after scrolling in non-visual mode. Also re-creates the cursor cell after refresh to preserve visibility. Additionally: use performBindingAction(_:repeatCount:) for scrollHalfPage, add state-reset assertion to testGGWithSelectionAdjustsToHome. Addresses review feedback from CodeRabbit, Cubic, Codex, and Greptile. --- Sources/GhosttyTerminalView.swift | 56 +++++++++++-- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 82 ++++++++++++++++++- 2 files changed, 127 insertions(+), 11 deletions(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index cc1b94ff..03b8c376 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -292,6 +292,7 @@ enum TerminalKeyboardCopyModeAction: Equatable { case copyLineAndExit case scrollLines(Int) case scrollPage(Int) + case scrollHalfPage(Int) case scrollToTop case scrollToBottom case jumpToPrompt(Int) @@ -304,10 +305,12 @@ enum TerminalKeyboardCopyModeAction: Equatable { struct TerminalKeyboardCopyModeInputState: Equatable { var countPrefix: Int? var pendingYankLine = false + var pendingG = false mutating func reset() { countPrefix = nil pendingYankLine = false + pendingG = false } } @@ -395,10 +398,10 @@ func terminalKeyboardCopyModeAction( if normalized == [.control] { if chars == "u" || chars == "\u{15}" { - return hasSelection ? .adjustSelection(.pageUp) : .scrollPage(-1) + return hasSelection ? .adjustSelection(.pageUp) : .scrollHalfPage(-1) } if chars == "d" || chars == "\u{04}" { - return hasSelection ? .adjustSelection(.pageDown) : .scrollPage(1) + return hasSelection ? .adjustSelection(.pageDown) : .scrollHalfPage(1) } if chars == "b" || chars == "\u{02}" { return hasSelection ? .adjustSelection(.pageUp) : .scrollPage(-1) @@ -439,7 +442,8 @@ func terminalKeyboardCopyModeAction( if normalized == [.shift] { return hasSelection ? .adjustSelection(.end) : .scrollToBottom } - return hasSelection ? .adjustSelection(.home) : .scrollToTop + // Bare "g" is a prefix key (e.g. gg); handled in resolve. + return nil case "0", "^": return hasSelection ? .adjustSelection(.beginningOfLine) : nil case "$", "4": @@ -486,6 +490,17 @@ func terminalKeyboardCopyModeResolve( state.pendingYankLine = false } + if state.pendingG { + if chars == "g", normalized.isEmpty { + let count = terminalKeyboardCopyModeClampCount(state.countPrefix ?? 1) + let action: TerminalKeyboardCopyModeAction = hasSelection ? .adjustSelection(.home) : .scrollToTop + state.reset() + return .perform(action, count: count) + } + // Not `gg`, cancel and treat as fresh command. + state.pendingG = false + } + if normalized.isEmpty, let scalar = chars.unicodeScalars.first, scalar.isASCII, @@ -509,6 +524,11 @@ func terminalKeyboardCopyModeResolve( return .consume } + if chars == "g", normalized.isEmpty { + state.pendingG = true + return .consume + } + guard let action = terminalKeyboardCopyModeAction( keyCode: keyCode, charactersIgnoringModifiers: charactersIgnoringModifiers, @@ -2785,6 +2805,11 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { private var keyboardCopyModeConsumedKeyUps: Set = [] private var keyboardCopyModeInputState = TerminalKeyboardCopyModeInputState() private var keyboardCopyModeViewportRow: Int? + /// Tracks whether the user has explicitly entered visual selection mode (v). + /// Separate from Ghostty's `has_selection` because copy mode always maintains + /// a 1-cell selection as a visible cursor. This flag determines whether + /// movements should extend the selection (visual) or scroll the viewport. + private var keyboardCopyModeVisualActive = false fileprivate var isKeyboardCopyModeActive: Bool { keyboardCopyModeActive } #if DEBUG private static let keyLatencyProbeEnabled: Bool = { @@ -3229,6 +3254,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { private func setKeyboardCopyModeActive(_ active: Bool) { keyboardCopyModeInputState.reset() + keyboardCopyModeVisualActive = false keyboardCopyModeActive = active if active, let surface { keyboardCopyModeViewportRow = keyboardCopyModeSelectionAnchor(surface: surface)?.row @@ -3236,6 +3262,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { if keyboardCopyModeViewportRow == nil { keyboardCopyModeViewportRow = keyboardCopyModeImeViewportRow(surface: surface) } + // Create a 1-cell selection at the terminal cursor to serve as a + // visible cursor indicator in copy mode. + _ = ghostty_surface_select_cursor_cell(surface) } else { keyboardCopyModeViewportRow = nil } @@ -3286,10 +3315,14 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } private func refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: ghostty_surface_t) { - guard !ghostty_surface_has_selection(surface) else { return } + // In visual mode the user owns the selection range; don't disturb it. + // Outside visual mode we keep a 1-cell cursor selection for visibility, + // so we still need to refresh the viewport row after scrolling. + guard !keyboardCopyModeVisualActive else { return } guard let anchor = keyboardCopyModeSelectionAnchor(surface: surface) else { return } keyboardCopyModeViewportRow = anchor.row - _ = ghostty_surface_clear_selection(surface) + // Preserve the visible cursor indicator. + _ = ghostty_surface_select_cursor_cell(surface) } private func copyCurrentViewportLinesToClipboard( @@ -3344,7 +3377,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { return false } - let hasSelection = ghostty_surface_has_selection(surface) + // Use the visual-mode flag instead of raw has_selection so that the + // 1-cell cursor selection doesn't make every motion behave as visual. + let hasSelection = keyboardCopyModeVisualActive let resolution = terminalKeyboardCopyModeResolve( keyCode: event.keyCode, charactersIgnoringModifiers: event.charactersIgnoringModifiers, @@ -3361,9 +3396,12 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { _ = ghostty_surface_clear_selection(surface) setKeyboardCopyModeActive(false) case .startSelection: - _ = ghostty_surface_select_cursor_cell(surface) + keyboardCopyModeVisualActive = true case .clearSelection: + keyboardCopyModeVisualActive = false _ = ghostty_surface_clear_selection(surface) + // Re-create 1-cell cursor at terminal cursor position. + _ = ghostty_surface_select_cursor_cell(surface) case .copyAndExit: _ = performBindingAction("copy_to_clipboard") _ = ghostty_surface_clear_selection(surface) @@ -3383,6 +3421,10 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { case let .scrollPage(delta): performBindingAction(delta > 0 ? "scroll_page_down" : "scroll_page_up", repeatCount: count) refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) + case let .scrollHalfPage(delta): + let fraction = delta > 0 ? 0.5 : -0.5 + performBindingAction("scroll_page_fractional:\(fraction)", repeatCount: count) + refreshKeyboardCopyModeViewportRowFromVisibleAnchor(surface: surface) case .scrollToTop: keyboardCopyModeViewportRow = 0 _ = performBindingAction("scroll_to_top") diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 0dd21b27..2d7609b5 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -1314,6 +1314,7 @@ final class TerminalKeyboardCopyModeActionTests: XCTestCase { } func testControlPagingSupportsPrintableAndControlCharacters() { + // Ctrl+U = half-page up (vim standard). XCTAssertEqual( terminalKeyboardCopyModeAction( keyCode: 0, @@ -1321,7 +1322,7 @@ final class TerminalKeyboardCopyModeActionTests: XCTestCase { modifierFlags: [.control], hasSelection: false ), - .scrollPage(-1) + .scrollHalfPage(-1) ) XCTAssertEqual( terminalKeyboardCopyModeAction( @@ -1401,14 +1402,14 @@ final class TerminalKeyboardCopyModeActionTests: XCTestCase { } func testGAndShiftGMapping() { - XCTAssertEqual( + // Bare "g" is a prefix key (gg), not an immediate action. + XCTAssertNil( terminalKeyboardCopyModeAction( keyCode: 5, charactersIgnoringModifiers: "g", modifierFlags: [], hasSelection: false - ), - .scrollToTop + ) ) XCTAssertEqual( terminalKeyboardCopyModeAction( @@ -1647,6 +1648,79 @@ final class TerminalKeyboardCopyModeResolveTests: XCTestCase { XCTAssertEqual(resolve(7, chars: "x", hasSelection: false, state: &state), .consume) XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) } + + // MARK: - gg (scroll to top via two-key sequence) + + func testGGScrollsToTop() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(5, chars: "g", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(5, chars: "g", hasSelection: false, state: &state), .perform(.scrollToTop, count: 1)) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } + + func testGGWithSelectionAdjustsToHome() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(5, chars: "g", hasSelection: true, state: &state), .consume) + XCTAssertEqual(resolve(5, chars: "g", hasSelection: true, state: &state), .perform(.adjustSelection(.home), count: 1)) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } + + func testCountedGG() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(22, chars: "5", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(5, chars: "g", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(5, chars: "g", hasSelection: false, state: &state), .perform(.scrollToTop, count: 5)) + } + + func testPendingGCancelledByOtherKey() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual(resolve(5, chars: "g", hasSelection: false, state: &state), .consume) + XCTAssertEqual(resolve(38, chars: "j", hasSelection: false, state: &state), .perform(.scrollLines(1), count: 1)) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } + + func testShiftGStillWorksImmediately() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual( + resolve(5, chars: "g", modifiers: [.shift], hasSelection: false, state: &state), + .perform(.scrollToBottom, count: 1) + ) + XCTAssertEqual(state, TerminalKeyboardCopyModeInputState()) + } + + // MARK: - Ctrl+U/D half-page scroll + + func testCtrlUHalfPage() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual( + resolve(32, chars: "u", modifiers: [.control], hasSelection: false, state: &state), + .perform(.scrollHalfPage(-1), count: 1) + ) + } + + func testCtrlDHalfPage() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual( + resolve(2, chars: "d", modifiers: [.control], hasSelection: false, state: &state), + .perform(.scrollHalfPage(1), count: 1) + ) + } + + func testCtrlBFullPage() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual( + resolve(11, chars: "b", modifiers: [.control], hasSelection: false, state: &state), + .perform(.scrollPage(-1), count: 1) + ) + } + + func testCtrlFFullPage() { + var state = TerminalKeyboardCopyModeInputState() + XCTAssertEqual( + resolve(3, chars: "f", modifiers: [.control], hasSelection: false, state: &state), + .perform(.scrollPage(1), count: 1) + ) + } } final class TerminalKeyboardCopyModeViewportRowTests: XCTestCase { From a5de92e9d6ff458283a47f7f7f2b1fca850f6098 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:12:05 -0800 Subject: [PATCH 054/232] Add customizable notification sound (#839) * Add customizable notification sound setting Adds a "Notification Sound" picker in Settings > App that lets users choose from macOS system sounds (Default, Basso, Blow, Glass, etc.) or silence notifications entirely with "None". Closes https://github.com/manaflow-ai/cmux/issues/608 * Add custom notification command with env vars and sound preview Users can set a shell command in Settings > App > Notification Command that runs on every notification. CMUX_NOTIFICATION_TITLE, CMUX_NOTIFICATION_SUBTITLE, and CMUX_NOTIFICATION_BODY env vars are set. Also adds a play button to preview system sounds and docs. --- Sources/AppDelegate.swift | 6 +- Sources/TerminalNotificationStore.swift | 89 ++++++++++++++++++++++++- Sources/cmuxApp.swift | 40 +++++++++++ web/app/docs/notifications/page.tsx | 42 ++++++++++++ 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 10e1c47b..b70ed1d5 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7156,7 +7156,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { - completionHandler([.banner, .sound, .list]) + var options: UNNotificationPresentationOptions = [.banner, .list] + if !NotificationSoundSettings.isSilent() { + options.insert(.sound) + } + completionHandler(options) } private func handleNotificationResponse(_ response: UNNotificationResponse) { diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 5a34be3c..8985b6ce 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -28,6 +28,87 @@ extension UNUserNotificationCenter { } } +enum NotificationSoundSettings { + static let key = "notificationSound" + static let defaultValue = "default" + static let customCommandKey = "notificationCustomCommand" + static let defaultCustomCommand = "" + + static let systemSounds: [(label: String, value: String)] = [ + ("Default", "default"), + ("Basso", "Basso"), + ("Blow", "Blow"), + ("Bottle", "Bottle"), + ("Frog", "Frog"), + ("Funk", "Funk"), + ("Glass", "Glass"), + ("Hero", "Hero"), + ("Morse", "Morse"), + ("Ping", "Ping"), + ("Pop", "Pop"), + ("Purr", "Purr"), + ("Sosumi", "Sosumi"), + ("Submarine", "Submarine"), + ("Tink", "Tink"), + ("None", "none"), + ] + + static func sound(defaults: UserDefaults = .standard) -> UNNotificationSound? { + let value = defaults.string(forKey: key) ?? defaultValue + switch value { + case "default": + return .default + case "none": + return nil + default: + return UNNotificationSound(named: UNNotificationSoundName(rawValue: value)) + } + } + + static func isSilent(defaults: UserDefaults = .standard) -> Bool { + return (defaults.string(forKey: key) ?? defaultValue) == "none" + } + + static func previewSound(value: String) { + switch value { + case "default": + NSSound.beep() + case "none": + break + default: + NSSound(named: NSSound.Name(value))?.play() + } + } + + private static let customCommandQueue = DispatchQueue( + label: "com.cmuxterm.notification-custom-command", + qos: .utility + ) + + static func runCustomCommand(title: String, subtitle: String, body: String, defaults: UserDefaults = .standard) { + let command = (defaults.string(forKey: customCommandKey) ?? defaultCustomCommand) + .trimmingCharacters(in: .whitespacesAndNewlines) + guard !command.isEmpty else { return } + customCommandQueue.async { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/bin/sh") + process.arguments = ["-c", command] + var env = ProcessInfo.processInfo.environment + env["CMUX_NOTIFICATION_TITLE"] = title + env["CMUX_NOTIFICATION_SUBTITLE"] = subtitle + env["CMUX_NOTIFICATION_BODY"] = body + process.environment = env + process.standardOutput = FileHandle.nullDevice + process.standardError = FileHandle.nullDevice + do { + try process.run() + } catch { + NSLog("Notification command failed to launch: \(error)") + } + } + } +} + enum NotificationBadgeSettings { static let dockBadgeEnabledKey = "notificationDockBadgeEnabled" static let defaultDockBadgeEnabled = true @@ -379,7 +460,7 @@ final class TerminalNotificationStore: ObservableObject { content.title = notification.title.isEmpty ? appName : notification.title content.subtitle = notification.subtitle content.body = notification.body - content.sound = UNNotificationSound.default + content.sound = NotificationSoundSettings.sound() content.categoryIdentifier = Self.categoryIdentifier content.userInfo = [ "tabId": notification.tabId.uuidString, @@ -398,6 +479,12 @@ final class TerminalNotificationStore: ObservableObject { self.center.add(request) { error in if let error { NSLog("Failed to schedule notification: \(error)") + } else { + NotificationSoundSettings.runCustomCommand( + title: content.title, + subtitle: content.subtitle, + body: content.body + ) } } } diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 3471ca26..80556abb 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2734,6 +2734,8 @@ struct SettingsView: View { @AppStorage(BrowserLinkOpenSettings.browserExternalOpenPatternsKey) private var browserExternalOpenPatterns = BrowserLinkOpenSettings.defaultBrowserExternalOpenPatterns @AppStorage(BrowserInsecureHTTPSettings.allowlistKey) private var browserInsecureHTTPAllowlist = BrowserInsecureHTTPSettings.defaultAllowlistText + @AppStorage(NotificationSoundSettings.key) private var notificationSound = NotificationSoundSettings.defaultValue + @AppStorage(NotificationSoundSettings.customCommandKey) private var notificationCustomCommand = NotificationSoundSettings.defaultCustomCommand @AppStorage(NotificationBadgeSettings.dockBadgeEnabledKey) private var notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled @AppStorage(QuitWarningSettings.warnBeforeQuitKey) private var warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit @AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey) @@ -2942,6 +2944,42 @@ struct SettingsView: View { SettingsCardDivider() + SettingsCardRow( + "Notification Sound", + subtitle: "Sound played when a notification arrives." + ) { + HStack(spacing: 6) { + Picker("", selection: $notificationSound) { + ForEach(NotificationSoundSettings.systemSounds, id: \.value) { sound in + Text(sound.label).tag(sound.value) + } + } + .labelsHidden() + Button { + NotificationSoundSettings.previewSound(value: notificationSound) + } label: { + Image(systemName: "play.fill") + .font(.system(size: 9)) + } + .buttonStyle(.bordered) + .controlSize(.small) + .disabled(notificationSound == "none") + } + } + + SettingsCardDivider() + + SettingsCardRow( + "Notification Command", + subtitle: "Run a shell command when a notification arrives. $CMUX_NOTIFICATION_TITLE, $CMUX_NOTIFICATION_SUBTITLE, $CMUX_NOTIFICATION_BODY are set." + ) { + TextField("say \"done\"", text: $notificationCustomCommand) + .textFieldStyle(.roundedBorder) + .frame(width: 200) + } + + SettingsCardDivider() + SettingsCardRow( "Send anonymous telemetry", subtitle: sendAnonymousTelemetry != telemetryValueAtLaunch @@ -3649,6 +3687,8 @@ struct SettingsView: View { browserExternalOpenPatterns = BrowserLinkOpenSettings.defaultBrowserExternalOpenPatterns browserInsecureHTTPAllowlist = BrowserInsecureHTTPSettings.defaultAllowlistText browserInsecureHTTPAllowlistDraft = BrowserInsecureHTTPSettings.defaultAllowlistText + notificationSound = NotificationSoundSettings.defaultValue + notificationCustomCommand = NotificationSoundSettings.defaultCustomCommand notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus diff --git a/web/app/docs/notifications/page.tsx b/web/app/docs/notifications/page.tsx index b7738f2c..032af4b3 100644 --- a/web/app/docs/notifications/page.tsx +++ b/web/app/docs/notifications/page.tsx @@ -49,6 +49,48 @@ export default function NotificationsPage() { directly to the workspace with the most recent unread notification.

+

Custom command

+

+ Run a shell command every time a notification is scheduled. Set it in{" "} + Settings → App → Notification Command. The command + runs via /bin/sh -c with these environment variables: +

+ + + + + + + + + + + + + + + + + + + + + +
VariableDescription
CMUX_NOTIFICATION_TITLENotification title (workspace name or app name)
CMUX_NOTIFICATION_SUBTITLENotification subtitle
CMUX_NOTIFICATION_BODYNotification body text
+ {`# Text-to-speech +say "$CMUX_NOTIFICATION_TITLE" + +# Custom sound file +afplay /path/to/sound.aiff + +# Log to file +echo "$CMUX_NOTIFICATION_TITLE: $CMUX_NOTIFICATION_BODY" >> ~/notifications.log`} +

+ The command runs independently of the system sound picker. Set the + picker to "None" to use only the custom command, or keep both for a + system sound plus a custom action. +

+

Sending notifications

CLI

From 1f62d770c7e903d81f05fd1a65d70f6b7ff32ce6 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:15:04 -0800 Subject: [PATCH 055/232] Skip keychain migration for DEV/staging builds (#845) * Skip keychain migration for DEV/staging builds Each tagged DEV build gets a unique bundle ID (com.cmuxterm.app.debug.) with its own UserDefaults domain. This means the migration version key is never set, so migrateLegacyKeychainPasswordIfNeeded runs on every launch. The SecItemCopyMatching call then triggers a macOS keychain access prompt because the ad-hoc re-signed binary doesn't match the ACL on the legacy keychain item. Guard the migration call so it only runs for production bundle IDs. * Suppress keychain UI prompt in legacy password lookup Add kSecUseAuthenticationUIFail to the SecItemCopyMatching query so macOS fails silently instead of showing a keychain access dialog when the app's code signing identity doesn't match the item's ACL. This closes the remaining code path (lazy keychain fallback in configuredPassword) that could trigger a prompt in DEV builds at runtime. * Revert "Suppress keychain UI prompt in legacy password lookup" This reverts commit 6453b7b5d70e713b324b0ffff7ecc4b22cc9fb5f. --- Sources/cmuxApp.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 80556abb..9ae5d3a5 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -57,7 +57,15 @@ struct cmuxApp: App { defaults.set(legacy ? SocketControlMode.cmuxOnly.rawValue : SocketControlMode.off.rawValue, forKey: SocketControlSettings.appStorageKey) } - SocketControlPasswordStore.migrateLegacyKeychainPasswordIfNeeded(defaults: defaults) + // Skip keychain migration for DEV/staging builds. Each tagged build gets a + // unique bundle ID with its own UserDefaults domain, so migration would run + // on every launch and trigger a macOS keychain access prompt (the legacy + // keychain item was created by a differently-signed app). + let bundleID = Bundle.main.bundleIdentifier + if !SocketControlSettings.isDebugLikeBundleIdentifier(bundleID) + && !SocketControlSettings.isStagingBundleIdentifier(bundleID) { + SocketControlPasswordStore.migrateLegacyKeychainPasswordIfNeeded(defaults: defaults) + } migrateSidebarAppearanceDefaultsIfNeeded(defaults: defaults) // UI tests depend on AppDelegate wiring happening even if SwiftUI view appearance From dad52b09d9664466aaf3730d5dbe68e040721136 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:19:01 -0800 Subject: [PATCH 056/232] Cmd+P: show workspaces only (#844) * Limit Cmd+P switcher to workspaces * Fix command palette Enter/Escape handling and add regression test * Scope command palette key handling to event window --- Sources/AppDelegate.swift | 68 ++++++++- Sources/ContentView.swift | 134 ++++++------------ Sources/TabManager.swift | 2 + Sources/cmuxApp.swift | 2 +- ...t_browser_new_tab_surface_focus_omnibar.py | 103 +++++++++++--- 5 files changed, 196 insertions(+), 113 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index b70ed1d5..1c88b6cc 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1080,6 +1080,43 @@ func shouldConsumeShortcutWhileCommandPaletteVisible( return true } +func shouldSubmitCommandPaletteWithReturn( + keyCode: UInt16, + flags: NSEvent.ModifierFlags +) -> Bool { + guard keyCode == 36 || keyCode == 76 else { return false } + let normalizedFlags = flags + .intersection(.deviceIndependentFlagsMask) + .subtracting([.numericPad, .function, .capsLock]) + return normalizedFlags == [] || normalizedFlags == [.shift] +} + +func commandPaletteFieldEditorHasMarkedText(in window: NSWindow) -> Bool { + guard let editor = window.firstResponder as? NSTextView, + editor.isFieldEditor else { + return false + } + return editor.hasMarkedText() +} + +func shouldHandleCommandPaletteShortcutEvent( + _ event: NSEvent, + paletteWindow: NSWindow? +) -> Bool { + guard let paletteWindow else { return false } + if let eventWindow = event.window { + return eventWindow === paletteWindow + } + let eventWindowNumber = event.windowNumber + if eventWindowNumber > 0 { + return eventWindowNumber == paletteWindow.windowNumber + } + if let keyWindow = NSApp.keyWindow { + return keyWindow === paletteWindow + } + return false +} + enum BrowserZoomShortcutAction: Equatable { case zoomIn case zoomOut @@ -5787,7 +5824,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let normalizedFlags = flags.subtracting([.numericPad, .function, .capsLock]) let commandPaletteTargetWindow = commandPaletteWindowForShortcutEvent(event) - let commandPaletteVisibleInTargetWindow = commandPaletteTargetWindow.map { + let commandPaletteShortcutWindow = shouldHandleCommandPaletteShortcutEvent( + event, + paletteWindow: commandPaletteTargetWindow + ) ? commandPaletteTargetWindow : nil + let commandPaletteVisibleInTargetWindow = commandPaletteShortcutWindow.map { isCommandPaletteVisible(for: $0) } ?? false @@ -5797,7 +5838,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent keyCode: event.keyCode ), commandPaletteVisibleInTargetWindow, - let paletteWindow = commandPaletteTargetWindow { + let paletteWindow = commandPaletteShortcutWindow { NotificationCenter.default.post( name: .commandPaletteMoveSelection, object: paletteWindow, @@ -5806,6 +5847,29 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } + if commandPaletteVisibleInTargetWindow, + let paletteWindow = commandPaletteShortcutWindow { + let paletteFieldEditorHasMarkedText = commandPaletteFieldEditorHasMarkedText(in: paletteWindow) + if normalizedFlags.isEmpty, event.keyCode == 53 { + if paletteFieldEditorHasMarkedText { + return false + } + NotificationCenter.default.post(name: .commandPaletteDismissRequested, object: paletteWindow) + return true + } + + if shouldSubmitCommandPaletteWithReturn( + keyCode: event.keyCode, + flags: event.modifierFlags + ) { + if paletteFieldEditorHasMarkedText { + return false + } + NotificationCenter.default.post(name: .commandPaletteSubmitRequested, object: paletteWindow) + return true + } + } + // Guard against stale browserAddressBarFocusedPanelId after focus transitions // (e.g., split that doesn't properly blur the address bar). If the first responder // is a terminal surface, the address bar can't be focused. diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index dd19fc85..2b57e7fa 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -2282,6 +2282,30 @@ struct ContentView: View { openCommandPaletteSwitcher() }) + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .commandPaletteSubmitRequested)) { notification in + guard isCommandPalettePresented else { return } + let requestedWindow = notification.object as? NSWindow + guard Self.shouldHandleCommandPaletteRequest( + observedWindow: observedWindow, + requestedWindow: requestedWindow, + keyWindow: NSApp.keyWindow, + mainWindow: NSApp.mainWindow + ) else { return } + handleCommandPaletteSubmitRequest() + }) + + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .commandPaletteDismissRequested)) { notification in + guard isCommandPalettePresented else { return } + let requestedWindow = notification.object as? NSWindow + guard Self.shouldHandleCommandPaletteRequest( + observedWindow: observedWindow, + requestedWindow: requestedWindow, + keyWindow: NSApp.keyWindow, + mainWindow: NSApp.mainWindow + ) else { return } + dismissCommandPalette() + }) + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .commandPaletteRenameTabRequested)) { notification in let requestedWindow = notification.object as? NSWindow guard Self.shouldHandleCommandPaletteRequest( @@ -2963,7 +2987,7 @@ struct ContentView: View { case .commands: return "Type a command" case .switcher: - return "Search workspaces and tabs" + return "Search workspaces" } } @@ -2972,7 +2996,7 @@ struct ContentView: View { case .commands: return "No commands match your search." case .switcher: - return "No workspaces or tabs match your search." + return "No workspaces match your search." } } @@ -3067,9 +3091,6 @@ struct ContentView: View { if command.id.hasPrefix("switcher.workspace.") { return CommandPaletteTrailingLabel(text: "Workspace", style: .kind) } - if command.id.hasPrefix("switcher.surface.") { - return CommandPaletteTrailingLabel(text: "Surface", style: .kind) - } return nil } @@ -3079,7 +3100,7 @@ struct ContentView: View { var entries: [CommandPaletteCommand] = [] let estimatedCount = windowContexts.reduce(0) { partial, context in - partial + max(1, context.tabManager.tabs.count) * 4 + partial + context.tabManager.tabs.count } entries.reserveCapacity(estimatedCount) var nextRank = 0 @@ -3126,62 +3147,12 @@ struct ContentView: View { focusCommandPaletteSwitcherTarget( windowId: windowId, tabManager: windowTabManager, - workspaceId: workspaceId, - panelId: nil + workspaceId: workspaceId ) } ) ) nextRank += 1 - - var orderedPanelIds = workspace.sidebarOrderedPanelIds() - if let focusedPanelId = workspace.focusedPanelId, - let focusedIndex = orderedPanelIds.firstIndex(of: focusedPanelId) { - orderedPanelIds.remove(at: focusedIndex) - orderedPanelIds.insert(focusedPanelId, at: 0) - } - - for panelId in orderedPanelIds { - guard let panel = workspace.panels[panelId] else { continue } - let panelTitle = panelDisplayName(workspace: workspace, panelId: panelId, fallback: panel.displayTitle) - let typeLabel: String = (panel.panelType == .browser) ? "Browser" : "Terminal" - let panelKeywords = CommandPaletteSwitcherSearchIndexer.keywords( - baseKeywords: [ - "tab", - "surface", - "panel", - "switch", - "go", - workspaceName, - panelTitle, - typeLabel.lowercased() - ] + windowKeywords, - metadata: commandPalettePanelSearchMetadata(in: workspace, panelId: panelId) - ) - entries.append( - CommandPaletteCommand( - id: "switcher.surface.\(workspace.id.uuidString.lowercased()).\(panelId.uuidString.lowercased())", - rank: nextRank, - title: panelTitle, - subtitle: commandPaletteSwitcherSubtitle( - base: "\(typeLabel) • \(workspaceName)", - windowLabel: context.windowLabel - ), - shortcutHint: nil, - keywords: panelKeywords, - dismissOnRun: true, - action: { - focusCommandPaletteSwitcherTarget( - windowId: windowId, - tabManager: windowTabManager, - workspaceId: workspaceId, - panelId: panelId - ) - } - ) - ) - nextRank += 1 - } } } @@ -3250,24 +3221,19 @@ struct ContentView: View { private func focusCommandPaletteSwitcherTarget( windowId: UUID, tabManager: TabManager, - workspaceId: UUID, - panelId: UUID? + workspaceId: UUID ) { // Switcher commands dismiss the palette after action dispatch. // Defer focus mutation one turn so browser omnibar autofocus can run // without being blocked by the palette-visibility guard. DispatchQueue.main.async { _ = AppDelegate.shared?.focusMainWindow(windowId: windowId) - if let panelId { - tabManager.focusTab(workspaceId, surfaceId: panelId, suppressFlash: true) - } else { - tabManager.focusTab(workspaceId, suppressFlash: true) - } + tabManager.focusTab(workspaceId, suppressFlash: true) } } private func commandPaletteWorkspaceSearchMetadata(for workspace: Workspace) -> CommandPaletteSwitcherSearchMetadata { - // Keep workspace rows coarse so surface rows win for directory/branch-specific queries. + // Keep workspace rows coarse and stable for predictable workspace switching queries. let directories = [workspace.currentDirectory] let branches = [workspace.gitBranch?.branch].compactMap { $0 } let ports = workspace.listeningPorts @@ -3278,33 +3244,6 @@ struct ContentView: View { ) } - private func commandPalettePanelSearchMetadata(in workspace: Workspace, panelId: UUID) -> CommandPaletteSwitcherSearchMetadata { - var directories: [String] = [] - if let directory = workspace.panelDirectories[panelId] { - directories.append(directory) - } else if workspace.focusedPanelId == panelId { - directories.append(workspace.currentDirectory) - } - - var branches: [String] = [] - if let branch = workspace.panelGitBranches[panelId]?.branch { - branches.append(branch) - } else if workspace.focusedPanelId == panelId, let branch = workspace.gitBranch?.branch { - branches.append(branch) - } - - var ports = workspace.surfaceListeningPorts[panelId] ?? [] - if ports.isEmpty, workspace.panels.count == 1 { - ports = workspace.listeningPorts - } - - return CommandPaletteSwitcherSearchMetadata( - directories: directories, - branches: branches, - ports: ports - ) - } - private func commandPaletteCommands() -> [CommandPaletteCommand] { let context = commandPaletteContextSnapshot() let contributions = commandPaletteCommandContributions() @@ -4543,6 +4482,17 @@ struct ContentView: View { runCommandPaletteCommand(visibleResults[index].command) } + private func handleCommandPaletteSubmitRequest() { + switch commandPaletteMode { + case .commands: + runSelectedCommandPaletteResult() + case .renameInput(let target): + continueRenameFlow(target: target) + case .renameConfirm(let target, let proposedName): + applyRenameFlow(target: target, proposedName: proposedName) + } + } + private func runCommandPaletteCommand(_ command: CommandPaletteCommand) { #if DEBUG dlog("palette.run commandId=\(command.id) dismissOnRun=\(command.dismissOnRun ? 1 : 0)") diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index aaeac1fe..64fc28dc 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -3635,6 +3635,8 @@ extension Notification.Name { static let commandPaletteToggleRequested = Notification.Name("cmux.commandPaletteToggleRequested") static let commandPaletteRequested = Notification.Name("cmux.commandPaletteRequested") static let commandPaletteSwitcherRequested = Notification.Name("cmux.commandPaletteSwitcherRequested") + static let commandPaletteSubmitRequested = Notification.Name("cmux.commandPaletteSubmitRequested") + static let commandPaletteDismissRequested = Notification.Name("cmux.commandPaletteDismissRequested") static let commandPaletteRenameTabRequested = Notification.Name("cmux.commandPaletteRenameTabRequested") static let commandPaletteRenameWorkspaceRequested = Notification.Name("cmux.commandPaletteRenameWorkspaceRequested") static let commandPaletteMoveSelection = Notification.Name("cmux.commandPaletteMoveSelection") diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 9ae5d3a5..3fff7055 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -418,7 +418,7 @@ struct cmuxApp: App { // Close tab/workspace CommandGroup(after: .newItem) { - Button("Go to Workspace or Tab…") { + Button("Go to Workspace…") { let targetWindow = NSApp.keyWindow ?? NSApp.mainWindow NotificationCenter.default.post(name: .commandPaletteSwitcherRequested, object: targetWindow) } diff --git a/tests/test_browser_new_tab_surface_focus_omnibar.py b/tests/test_browser_new_tab_surface_focus_omnibar.py index b66bb505..cea196c5 100644 --- a/tests/test_browser_new_tab_surface_focus_omnibar.py +++ b/tests/test_browser_new_tab_surface_focus_omnibar.py @@ -4,7 +4,8 @@ Regression test: 1. Focusing a blank browser surface should focus the omnibar. 2. Focusing a pane that contains a blank browser should focus the omnibar. 3. If command palette is open, focusing that blank browser surface must not steal input. -4. Cmd+P switcher focusing an existing blank browser surface should focus the omnibar. +4. Cmd+P switcher should list only workspaces, then switching to a workspace with a + focused blank browser should focus the omnibar. """ import json @@ -281,24 +282,47 @@ def main() -> int: workspace_ids.remove(workspace_id) time.sleep(0.3) - # Scenario 4: Cmd+P switcher selecting an existing blank browser surface should focus omnibar. - workspace_id = client.new_workspace() - workspace_ids.append(workspace_id) - client.select_workspace(workspace_id) + # Scenario 4: Cmd+P switcher should only list workspaces, and switching to a workspace + # that has a focused blank browser should focus the omnibar. + target_workspace_id = client.new_workspace() + workspace_ids.append(target_workspace_id) + client.select_workspace(target_workspace_id) time.sleep(0.4) window_id = current_window_id(client) if not set_command_palette_visible(client, window_id, False): - raise cmuxError("Failed to reset command palette before scenario 4") + raise cmuxError("Failed to reset command palette before scenario 4 (target setup)") switcher_browser_id = client.new_surface(panel_type="browser") time.sleep(0.3) + client.focus_surface_by_panel(switcher_browser_id) - switcher_surfaces = client.list_surfaces() - switcher_terminal_id = next((surface_id for _, surface_id, _ in switcher_surfaces if surface_id != switcher_browser_id), None) - if not switcher_terminal_id: - raise cmuxError("Missing terminal surface for Cmd+P switcher scenario") + did_focus_target_browser = wait_for( + lambda: bool( + browser_address_bar_focus_state( + client, + surface_id=switcher_browser_id, + request_id="browser-focus-switcher-target-setup" + ).get("focused") + ), + timeout_s=3.0, + interval_s=0.1 + ) + if not did_focus_target_browser: + raise cmuxError("Failed to focus omnibar on target workspace browser before Cmd+P switch") - client.focus_surface_by_panel(switcher_terminal_id) + source_workspace_id = client.new_workspace() + workspace_ids.append(source_workspace_id) + client.select_workspace(source_workspace_id) + time.sleep(0.4) + window_id = current_window_id(client) + if not set_command_palette_visible(client, window_id, False): + raise cmuxError("Failed to reset command palette before scenario 4 (source setup)") + + source_surfaces = client.list_surfaces() + source_terminal_id = next((surface_id for _, surface_id, _ in source_surfaces), None) + if not source_terminal_id: + raise cmuxError("Missing terminal surface for Cmd+P workspace switcher scenario") + client.focus_surface_by_panel(source_terminal_id) time.sleep(0.2) client.simulate_shortcut("cmd+p") @@ -316,11 +340,13 @@ def main() -> int: ): raise cmuxError("Cmd+P did not open command palette switcher") - client.simulate_type("new tab") - time.sleep(0.2) + switcher_results = command_palette_results(client, window_id, limit=100) + switcher_ids = [row.get("command_id") for row in switcher_results if isinstance(row.get("command_id"), str)] + has_surface_rows = any(command_id.startswith("switcher.surface.") for command_id in switcher_ids) + if has_surface_rows: + raise cmuxError("Cmd+P switcher listed unexpected surface rows; expected workspace-only results") - target_command_id = f"switcher.surface.{workspace_id.lower()}.{switcher_browser_id.lower()}" - switcher_results = command_palette_results(client, window_id, limit=50) + target_command_id = f"switcher.workspace.{target_workspace_id.lower()}" target_index = next( ( idx for idx, row in enumerate(switcher_results) @@ -329,7 +355,7 @@ def main() -> int: None ) if target_index is None: - raise cmuxError(f"Cmd+P switcher did not list target surface command {target_command_id}") + raise cmuxError(f"Cmd+P switcher did not list target workspace command {target_command_id}") if not move_command_palette_selection_to_index(client, window_id, target_index): raise cmuxError(f"Failed to move Cmd+P selection to result index {target_index}") @@ -358,9 +384,50 @@ def main() -> int: interval_s=0.1 ) if not did_focus_switcher_target: - raise cmuxError("Cmd+P switcher focus to blank browser did not focus omnibar") + raise cmuxError("Cmd+P workspace switch did not restore blank browser omnibar focus") - print("PASS: blank-browser focus paths (surface, pane, and Cmd+P switcher) drive omnibar, while command palette visibility blocks focus stealing") + # Scenario 5: Cmd+P switcher should dismiss on Escape reliably. + client.select_workspace(source_workspace_id) + time.sleep(0.4) + window_id = current_window_id(client) + if not set_command_palette_visible(client, window_id, False): + raise cmuxError("Failed to reset command palette before scenario 5") + + client.focus_surface_by_panel(source_terminal_id) + time.sleep(0.2) + + client.simulate_shortcut("cmd+p") + if not wait_for( + lambda: bool( + v2_call( + client, + "debug.command_palette.visible", + {"window_id": window_id}, + request_id="palette-visible-switcher-open-escape" + ).get("visible") + ), + timeout_s=2.0, + interval_s=0.1 + ): + raise cmuxError("Cmd+P did not open command palette switcher before Escape scenario") + + client.simulate_shortcut("escape") + did_dismiss_switcher_on_escape = wait_for( + lambda: not bool( + v2_call( + client, + "debug.command_palette.visible", + {"window_id": window_id}, + request_id="palette-visible-switcher-after-escape" + ).get("visible") + ), + timeout_s=3.0, + interval_s=0.1 + ) + if not did_dismiss_switcher_on_escape: + raise cmuxError("Cmd+P Escape did not dismiss command palette switcher") + + print("PASS: blank-browser focus paths (surface, pane, Cmd+P Enter switcher, and Cmd+P Escape dismiss) drive omnibar, while command palette visibility blocks focus stealing") return 0 except cmuxError as exc: From e65bc65ac77de69c4c18a8b3ae41f3a4922b0e8b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:33:52 -0800 Subject: [PATCH 057/232] Fix Cmd+V clipboard image paste not working (#853) Ghostty's keybinding system intercepts Cmd+V and routes it through read_clipboard_cb, which only reads text. The paste(_:) NSResponder method with image handling was never reached. Move clipboard image save logic into GhosttyPasteboardHelper and call it from read_clipboard_cb when the clipboard has no text but has image data. This makes image paste work regardless of whether paste is triggered via Ghostty keybinding (Cmd+V) or menu action (Edit > Paste). Fixes regression from https://github.com/manaflow-ai/cmux/pull/562 --- Sources/GhosttyTerminalView.swift | 142 ++++++++++++++---------------- 1 file changed, 68 insertions(+), 74 deletions(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 03b8c376..f1e8856f 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -104,7 +104,8 @@ private enum GhosttyPasteboardHelper { static func hasString(for location: ghostty_clipboard_e) -> Bool { guard let pasteboard = pasteboard(for: location) else { return false } - return (stringContents(from: pasteboard) ?? "").isEmpty == false + if let text = stringContents(from: pasteboard), !text.isEmpty { return true } + return clipboardHasImageOnly() } static func writeString(_ string: String, to location: ghostty_clipboard_e) { @@ -113,13 +114,70 @@ private enum GhosttyPasteboardHelper { pasteboard.setString(string, forType: .string) } - private static func escapeForShell(_ value: String) -> String { + static func escapeForShell(_ value: String) -> String { var result = value for char in shellEscapeCharacters { result = result.replacingOccurrences(of: String(char), with: "\\\(char)") } return result } + + private static let maxClipboardImageSize = 10 * 1024 * 1024 // 10 MB + + /// Quick check: does the clipboard have image data and no text? + static func clipboardHasImageOnly() -> Bool { + let pb = NSPasteboard.general + let types = pb.types ?? [] + let hasText = types.contains(.string) || types.contains(.html) + || types.contains(.rtf) || types.contains(.rtfd) + if hasText { return false } + return types.contains(.tiff) || types.contains(.png) + } + + /// When the clipboard contains only image data (no text/HTML), saves it as + /// a temporary PNG file and returns the shell-escaped file path. Returns nil + /// if the clipboard contains text or no image. + static func saveClipboardImageIfNeeded() -> String? { + let pb = NSPasteboard.general + let types = pb.types ?? [] + + // If pasteboard has text/HTML, this is a normal copy. + let hasText = types.contains(.string) || types.contains(.html) + || types.contains(.rtf) || types.contains(.rtfd) + if hasText { return nil } + + // Check for image types (TIFF from screenshots, PNG from some tools). + guard types.contains(.tiff) || types.contains(.png) else { return nil } + guard let image = NSImage(pasteboard: pb), + let tiffData = image.tiffRepresentation, + let bitmap = NSBitmapImageRep(data: tiffData), + let pngData = bitmap.representation(using: .png, properties: [:]) else { return nil } + + guard pngData.count <= maxClipboardImageSize else { +#if DEBUG + dlog("terminal.paste.image.rejected reason=tooLarge bytes=\(pngData.count)") +#endif + return nil + } + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd-HHmmss" + formatter.locale = Locale(identifier: "en_US_POSIX") + let timestamp = formatter.string(from: Date()) + let filename = "clipboard-\(timestamp)-\(UUID().uuidString.prefix(8)).png" + let path = (NSTemporaryDirectory() as NSString).appendingPathComponent(filename) + + do { + try pngData.write(to: URL(fileURLWithPath: path)) + } catch { +#if DEBUG + dlog("terminal.paste.image.writeFailed error=\(error.localizedDescription)") +#endif + return nil + } + + return escapeForShell(path) + } } enum TerminalOpenURLTarget: Equatable { @@ -786,7 +844,13 @@ class GhosttyApp { let surface = callbackContext.runtimeSurface else { return } let pasteboard = GhosttyPasteboardHelper.pasteboard(for: location) - let value = pasteboard.flatMap { GhosttyPasteboardHelper.stringContents(from: $0) } ?? "" + var value = pasteboard.flatMap { GhosttyPasteboardHelper.stringContents(from: $0) } ?? "" + + // When clipboard has only image data (e.g. screenshot), save as temp + // PNG and paste the file path so CLI tools can receive images. + if value.isEmpty, let imagePath = GhosttyPasteboardHelper.saveClipboardImageIfNeeded() { + value = imagePath + } value.withCString { ptr in ghostty_surface_complete_clipboard_request(surface, ptr, state, false) @@ -3454,78 +3518,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { _ = performBindingAction("copy_to_clipboard") } - // MARK: - Clipboard image paste + // MARK: - Clipboard paste - private static let maxClipboardImageSize = 10 * 1024 * 1024 // 10 MB - - /// Quick check: does the clipboard have image data and no text? - private static func clipboardHasImageOnly() -> Bool { - let pb = NSPasteboard.general - let types = pb.types ?? [] - let hasText = types.contains(.string) || types.contains(.html) - || types.contains(.rtf) || types.contains(.rtfd) - if hasText { return false } - return types.contains(.tiff) || types.contains(.png) - } - - /// When the clipboard contains only image data (no text/HTML), saves it as - /// a temporary PNG file and returns the file path. Returns nil if the - /// clipboard contains text or no image. - private static func saveClipboardImageIfNeeded() -> String? { - let pb = NSPasteboard.general - let types = pb.types ?? [] - - // If pasteboard has text/HTML, this is a normal copy — let Ghostty handle it. - let hasText = types.contains(.string) || types.contains(.html) - || types.contains(.rtf) || types.contains(.rtfd) - if hasText { return nil } - - // Check for image types (TIFF from screenshots, PNG from some tools). - guard types.contains(.tiff) || types.contains(.png) else { return nil } - guard let image = NSImage(pasteboard: pb), - let tiffData = image.tiffRepresentation, - let bitmap = NSBitmapImageRep(data: tiffData), - let pngData = bitmap.representation(using: .png, properties: [:]) else { return nil } - - guard pngData.count <= maxClipboardImageSize else { -#if DEBUG - dlog("terminal.paste.image.rejected reason=tooLarge bytes=\(pngData.count)") -#endif - return nil - } - - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd-HHmmss" - formatter.locale = Locale(identifier: "en_US_POSIX") - let timestamp = formatter.string(from: Date()) - let filename = "clipboard-\(timestamp)-\(UUID().uuidString.prefix(8)).png" - let path = (NSTemporaryDirectory() as NSString).appendingPathComponent(filename) - - do { - try pngData.write(to: URL(fileURLWithPath: path)) - } catch { -#if DEBUG - dlog("terminal.paste.image.writeFailed error=\(error.localizedDescription)") -#endif - return nil - } - - return path - } - - /// Pastes clipboard content into the terminal. If the clipboard contains only - /// image data, saves it as a temporary PNG and pastes the shell-escaped file path. @IBAction func paste(_ sender: Any?) { - // When the clipboard contains only image data (e.g. from Cmd+Ctrl+Shift+4 - // screenshot), save it as a temporary PNG and paste the file path so that - // CLI tools like Claude Code can accept the image. - if let path = Self.saveClipboardImageIfNeeded() { -#if DEBUG - dlog("terminal.paste.image path=\(path)") -#endif - terminalSurface?.sendText(Self.escapeDropForShell(path)) - return - } _ = performBindingAction("paste_from_clipboard") } @@ -3542,7 +3537,6 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { return ghostty_surface_has_selection(surface) case #selector(paste(_:)): return GhosttyPasteboardHelper.hasString(for: GHOSTTY_CLIPBOARD_STANDARD) - || Self.clipboardHasImageOnly() case #selector(pasteAsPlainText(_:)): return GhosttyPasteboardHelper.hasString(for: GHOSTTY_CLIPBOARD_STANDARD) case #selector(splitHorizontally(_:)), #selector(splitVertically(_:)): From 9b78ef47268fbf45c222a29948403edd9bdd2977 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 01:35:11 -0800 Subject: [PATCH 058/232] Add blog post: My Favorite Feature: Cmd+Shift+U (#852) * Add blog post about Cmd+Shift+U (Jump to Latest Unread) * Rewrite blog post to remove AI rhetorical patterns * Add video, trim post to two paragraphs --- web/app/blog/cmd-shift-u/page.tsx | 80 ++++++++++++++++++++++++++++++ web/app/blog/page.tsx | 7 +++ web/public/blog/cmd-shift-u.mp4 | Bin 0 -> 16881456 bytes 3 files changed, 87 insertions(+) create mode 100644 web/app/blog/cmd-shift-u/page.tsx create mode 100644 web/public/blog/cmd-shift-u.mp4 diff --git a/web/app/blog/cmd-shift-u/page.tsx b/web/app/blog/cmd-shift-u/page.tsx new file mode 100644 index 00000000..772de010 --- /dev/null +++ b/web/app/blog/cmd-shift-u/page.tsx @@ -0,0 +1,80 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +export const metadata: Metadata = { + title: "Cmd+Shift+U", + description: + "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.", + keywords: [ + "cmux", + "terminal", + "macOS", + "notifications", + "AI coding agents", + "keyboard shortcuts", + "developer tools", + "workflow", + ], + openGraph: { + title: "Cmd+Shift+U", + description: + "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.", + type: "article", + publishedTime: "2026-03-04T00:00:00Z", + url: "https://cmux.dev/blog/cmd-shift-u", + }, + twitter: { + card: "summary", + title: "Cmd+Shift+U", + description: + "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.", + }, + alternates: { + canonical: "https://cmux.dev/blog/cmd-shift-u", + }, +}; + +export default function CmdShiftUPage() { + return ( + <> +
+ + ← Back to blog + +
+ +

Cmd+Shift+U

+ + +

+ My favorite cmux feature is Cmd+Shift+U. I have 17 + workspaces open right now, each running an agent. I used to click + through tabs and the notification panel to figure out what completed. + Typing is faster. +

+ +

lDsF#g#~TJP*2}{FkUJ`?den|c;7?N#!xLf zVR=fBNc6x3G0~ncYKVOs$%$}EWPw2Zp#b@d9{tahM5EKXQtDSIB)Gc#QPxxg?@124 zr}sX(GA(~b7-DILbpZ^CAngWzYC8|pfe-Harb)WAGzAWlhSO#B$qXX)T}D#Ot#!$- zXg?G?ZqV#hq)JSpvra8cV|LX$hBN*>uMghPL6%5kOS!C83wt*#K8&c=APP%Pm=81? z_Xj3kb453=s-)=#f~_?$cav$#NVmM1pZu|#?;z~$_C;)_6TMZ7%~rLq?~fpEhm&-l zG)xuT3KZdyoUTAfsZrlP+D0f94xLA$Uk-rBDP@{>2GR@DWO{yLRJ>pW9_g`VqhTi^ z%9fP4W~yqzc6A_YvZ;aR7_I91lO}rV5Q2GsQgi8Fm{IjOVx|UJML5XNL9SUZp_mgV zBu0{)Lu!dU2oyEQa95V2#3^57Im(=!r1`HIj?Z$_<-5>dug+`iBbC4BvXhj=Sz3nL zhpdTl`3aO6frcStAw;?K0?9&BC^|9klhRuXY3Kr$sw*}GOkHA}PAbRAIeHR-H}zKD zv`l;!LpAT66Bs!xEtCL&`>NGQn#j(L#eyI)kl8m(ebu2wA7%~)eB5VF;SiuY$GKQC z^~Cxi{)|e0RU=>cKe9qA#^ckGwt-S zo`gQfO?1gm=y@ zZdvnsh%M*U3U2M2`|05lzA|O|Db1+nyUyFy$)p=mH0Z7`)fkJIhIg$u+7*vTNZdoO zMHy(pkSRn_J9Ut<;Z}r24GWA}7RfmVC-rkUbY$vqALA|EwJ~jy&aqRygA0-3fx^Mj z7mPr{Pw%>XWex;~O0%u;qQpi3O*%FTq_PI=?)&BI$B!?IyOq}LppNe<>HOcZ36h}| zwC)DPD8Ibj8$3^yZWlfB_1U``PftYzQCq3|Wke#bKPU6^wXL>yUY%W*HLDfv-7x#1o;c zHd@|Db*Lxufm z-P}+m-M9cNm}RbyYI&&uWCWLqB$D=#l9y3aH4GSXJbxb+qMfYURO9*zLe3=XX=*)% z6lXDMSOG|@Nz0*`Kw{|U(Fv0{^_xMqXd1UEGN4pXVPmLZYPvU`gFx>~)@OlX#KVmH zrJr!hbwY7ieu8uNs2o6$BVA$>XBk&9S5Ks=Dc|0$u&*xxZ&S(hTK9D*s5dWn&-9jM zrKm8-n+{fxX-Juw_QCMLz#J>3kDn-{q?sFMiP+-bShVS^47zGxShzq@OLp0(^Q;d2EuS>QgMx3K0E2@L06HyTWO; zb=G7q0L1ydI$a`g^0tOPj;$c?rG1&AyB8t8Ob$KGjsDoCaTv>SxXAL1pU)aj1kArj z8o={q-!+S|DasqIT*!nhq9x-RmO%@AYP<21wkcOwD>AtGyJz!Cno=+Af0#1`4PS&tg1D;ZP6yC>pMf!=4V3k=a!QrC?eWh^k5%sH%u za;N&!8w%7?j$|)H=D_`Vp~Ef!k+U8c+G4=t4)=@q{TJ}@pB5;Y=cMFxVX`P~0hNqO zkvflb?q4fKFJ%0%JsM_)ysL&=PV1vv&rEzdi~`UuEmh$$^yPVu{==8 zr>5ltY1v;f$Uk#W8l+}PSwG5l)a$nTyQyl&iARMhBXy;S*w`aAkR$H8(DE<(Q+5vJ zM;FSm6#RboZbpvY6T}!o(9CjzaF-pT=j4Y-WWhHixAe*Um-t{h`&U$c@M zzi3TWnkY6^iAr0|Esu%AIem{Lfdo96?vyasTFS+(fOsaoRjN-1W0FZ2%#*5=Et=zZ zPteotM&6-_&u(PS=gYJG{))N5PwFp5PoM-gja6WaRwGHqtTbABdiCIy+BWC4SjrP9 z<)?-JALVh{u!~rGDEr8u3nL4}SldNEhQL8eshP<8u4+a>4FdDfA(aeTAp!RsATr;; z)S+VZiqxN=by}u>UGK1{sm;*9mf}fA6e9%bF!MSytl)AnN~(ikKCBcL;16JSXDWDn z6ICp^qo#ubpu;k|COpcY%nt`m&PFk=3;X`_XG-4#d~KcSQ7VO$Mamhul6C!i9z#`5sO1Nq?7YTFVfwoHgU?}C@ z4clHmQ92qblIikp+)h(|X|yK|<7Yp(_oD>Fw?L_gFn2_vjhr}in;%ho$MLONTbLzj zN)A6JGi?&1o3zilCN9I9D(sSPnD!WULi5g^SwU|f%UEKBaeWj%riQK1-eq&^bwkRR zC?LC*g;lQU2zmkGT~+#$3<6IdHPC)_+ZHqb@nt3bt$PKmN;Y)?pg2D<3>sW1Q?BP{ z-+(@_)@uzxjlr$@b$ek>MyFh-|5QV`+`6DPy32yBe!E4es$(Rj0uWxeJo+#@_obQa zvhNKF_xPo3b@9261n9c?ort3((N;R@|o(7;Q z9tvrcQ4M#{TD=baRsCOj3=W33bN4~UzLwUDD8>P=U8^dmBb7w#sV#i?UHzV6<(+Q$ zAO*VV>be(2wtFCVS}q=r22wr~*NCj^h^+0Zav(W#krgIMztBDsk|?5Qf7E#Rs8p$- z2HxIh+cQ%Tb+Bz8-AYUS=Q%%rG_LAWa6=Sl`}BDJt#p+?Oqz+r5U`oICz#GEbovvA zu6|g1(J2La!GQ4!DM72x%P!H1aETN2$8}E)GGmKipopgBSM&6#g2-I)<@BQJrn@CS z1FovS6oldOj{C-V!O2;Z+lakP;kVm&A-<^n?5oI8iZNh@Pq~K1o=MMQ-yd=;o$;+n zYCFWk!~&TpEbOA;p>S?|s=5W$|MrVVMCv7=wK&P( z)a#B!+zn#E>Uf^D4ES)O9z3nwGi@SL7JLW5s2S2LVM9^cay|l%xguP={YNNq3^kH9 z_W4s2)t<&30#Rn^9Yl)6iICGhsF+=dlMq`2^#8g=?AHciaTQK|+mn`ypCTqg2q!86 zN(Aud&{*~Un2$QD7%E;6eRG!Dt#P1pFt9rem!S14Zw%EL3!0-+R#>G<9S zcOSYiY8Wz0B3<^V!vrZQ0}DBBbjt98@QT`<@aGrA?%w}Eqq<9ntqYgODEjyvkuNT$ z;vSx5(Mu5{RTx@|u=62<~sGcyp-|{QeK|jtPvH>9B zLFTF@+9}zZ7ybo9;^~Oe>a3C+PH`_!%?TTE##;F9`Oz#bjjbC%o!Y=+0)}PbK7lEe zPt3|PgX*o;Aw6UG#~UTQYDk}Uq!3A$rt&x+Wyl}O;G zlY78WIgZ@}=%dDB*6TUNA7A(E4^0k3^6R8K_gdeTmrmsD)01?&o}NK-6`K7$wG#DH z*aFHUE1_FpfMd$HAv}gGK-}NXS#~e9 zMP&QK=QYocWVn?wfaR8cGo*jS5+v#QrwelREZEsirL1zf3s1D#^RaO=XEGc7ULNe6 zPV!eO2Sf}+8BhG@nE9z$6E98UZ~9!mJLMpAq56mm@ehgvmLS+(^L2;Qol6_?AyaF5ePN@ zzq^_5o%|0r0MQ#Q^K~h-ynmXhjV;O1nv-FoR1U7 zzanbyEq%MSikSS+S6IbZU?c9Nk$dQC>wV#PwHOLJ(Dr)f?8ONEBUeG%K#-bKjk>O* zy|1DU$iOYT0009300RI30{{R6000936N8a69U{-;T2zl{Wa8JI&hnm3mDI`tc3y|u zV{NnqF_F)f0DDx|OMOucPo2;y80IU^Ebj68sEx*piHy5x;vU0Vjl`3eAydX+!aZq_ zV)<a1#yH-_exLNM)tlQA-o`_JN3|Vr~4@^f}=fYZ5`G{?#y=3nC_!y3BV!tDOiDa{QH7@$x6@hliha8mirY*8N-wEGv{m zrV*gL|1$Ce=~u`~PD>W@L;vpz*35*Ta-*>|S0(KKjYZaIn3|4uF56>c!)OamY{a$q z(n95cDAiB=)HF#|jMuyE%pfK&m~{2BMEE?D6yW)-dQAZ-*TY=a;9ITu_b|RHf+^Dj zVfA2Vmej#&lixIpqhS2KZnCx&6ibyM5Jcp-%2i)wqw{T6-ddv(<0f6g;1aR0V$9OI z!fnYYP0TN=e*H7O3*O6fn%tOQk?w1yc`w?*dyI^$_FQSnLiZ-{uybsAp4|&D>G~B= z>*|IVRb}EK3Kxwf7GbVs`b<7EuCCxP38)iGz75S7mTIcVB5f&Q8|~$NAcRy@l)52W zCyihiGexrRq%@^0yvZ2vlf#&)y#2k{+>^XcRpb58!Lm0h)#|Y#vdRXXi4QQt4yb>< zToe;yE$EVAP*aQMbaye+E-jYhiX9iI@34)Krh+PR5Irl2dm$EEJ~^hw1ukFDp=;OI zk;uMe$7D3Rz5+L$Z85rB{ZA11Su%~$HanFm_Ehd;eqok4QI^bJ6j8sNAYjEs{M|R! znQ{hdMKGcc11Db0nhaH~wFlg>0O}0Ul4Fx&$%`zGN6J(QrTNysZ^-tIxOuTflzsmN zW%hUJ4-%0ET9?g_aRCB#Y0P?dnrros~yx$?%%nD3S zXl&J=NgdTj!o@efUHV5Ch)m+Pkl}oBC%6UFXm?}5(wp!`oAy!(i7~a*QP(dDF*xfg z#bT;xOqu|{kU0DnILHcBBTYcM%N6vJr1`NJ{bnr!;dUHdSZp$2!d&{*QmZ~P+86N4 zCqyYx8Kd&c0sw-XAiF*T_cn5HR)n!4JOw?*r?W!;O!cw4{5 zZ|k?t7}4-Zanv6<bnT9ZqGi6p+NTnORGOZK;NHRG+SY2H%349H&;EYP4a0N1+VHvsVfoPQe3f}*!n zIrWOLbvA@iRm)y*2L|GXc-!_zjnl8mkXtT9Y!DLu=v;cH%mW2ESS+(|I#+%Zl+E=g zACAk^Z5-Sx2^Oz%?}+^|+*^4|Yck3sHeWlRvk;?zl5#U7a_`;>`nGq6ha4J}(y4d+ zmsgdjk_%P`$L4`qeAO*fh<;>5I70Ayye%16)VjdcvKt}r;s7hYRFA-sX(F&ZE!htS zev$2nN1y;W|T~K$NvQ9>e4ZD_~O^fEs~1G)<3nKTPYmm z!}Z+}VL7w#j$-q+OY`$4A{va5s4jf=;IOXN2f0 zwFlUDu=&3`;o>(Pv4aMae6(*#jV%-g{mEe^CJOqaVNR$$x z?Ho!s@Gz0#*iExnH_k>P2L93zON|)}LG0MCt^!o+gj#%-=g21)s#K&4so_-U!OLpJ z3+Zten$0P1Rxc)1`p}r{%L0r)C`VE3y7}Aax7xcDlKBjlCSe+MH1#P2U#$$>I;dES^EYnkX&)bhA=bPBp$JTG~y2n2oP88_G_LSm^V)iKu z7pg=aZ=uWq6NqAH19K^om9YZ{YWU*EW>`$_NDZ7Al?}=cVabPa$tjXbd*!v6DgwZZ z#YN#~S$lGH?_r2=a708bg%qNEJg?DPD*icv58{EiFeFOFjoF++X#qs9YA&*N?rX~* z*R+|LxYhe(f~kQ0-Dg6vU+y6Z58Jwpc1INY3plr~`^U#3l2kUUfTKUGN9(+u1Ii7` z{{ODCx+?8lN3-(Dv-j-!T)2wwQpyGf$W-j->(89o-B#-J$$$Op%ru!Q8L_dgX>);I zmxMY)T%{xn%qilo2&2x%Kyzz3-DXU{%i>xM9z@z=?|GfN^Pbdr#3GAoVAH6z5lH(rJas#abKKkSFWftVkV0mZw396I$p zA0e)?l{|6<<>XyEJNbFE#MyX43-o%&!Y$(gP!uQO$nf=VIH^}3cv7aBi);Z%Bal9x zQDv7l%iA~$usM0OU|gP8Z!?_d9%H}T$Pp^aSX}jsI5MoA&qm6jySEx*h`)M{XKRX% zF*yYD{`x6Q3xSKwYD-6<1AeuYt-Vbs=ld)p*yKa#SOqHr=5E2g=YNjlQ20c30r9JSCxu$A zsjZ#|gh~bHyBlc2ks*-Vxs}AD4JldL_pm%7-?wlw$q*5E42t)7Qb}yJ>8e8l^Pk2> zT!=PX0lt2od`4^?d3pfgP2cQ2U!b})m6?%0DQmL$Wm*l7B0{_kid{}?PO&wdS|K`0 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

); diff --git a/web/app/blog/page.tsx b/web/app/blog/page.tsx index 81a0a6ab..0462e1a2 100644 --- a/web/app/blog/page.tsx +++ b/web/app/blog/page.tsx @@ -1,48 +1,18 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { blogPosts } from "../components/blog-posts"; export const metadata: Metadata = { title: "Blog", description: "News and updates from the cmux team", }; -const posts = [ - { - slug: "cmd-shift-u", - title: "Cmd+Shift+U", - date: "2026-03-04", - summary: - "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.", - }, - { - slug: "zen-of-cmux", - title: "The Zen of cmux", - date: "2026-02-27", - summary: - "cmux is a primitive, not a solution. It gives you composable pieces and your workflow is up to you.", - }, - { - slug: "show-hn-launch", - title: "Launching cmux on Show HN", - date: "2026-02-21", - summary: - "cmux hit #2 on Hacker News, got shared by Mitchell Hashimoto, and went viral in Japan.", - }, - { - slug: "introducing-cmux", - title: "Introducing cmux", - date: "2026-02-12", - summary: - "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side.", - }, -]; - export default function BlogPage() { return ( <>

Blog

-
- {posts.map((post) => ( +
+ {blogPosts.map((post) => (
-
- - -
); } diff --git a/web/app/components/blog-cta.tsx b/web/app/components/blog-cta.tsx new file mode 100644 index 00000000..2c57459e --- /dev/null +++ b/web/app/components/blog-cta.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import { DownloadButton } from "./download-button"; +import { GitHubButton } from "./github-button"; + +export function BlogCTA() { + const pathname = usePathname(); + if (pathname === "/blog") return null; + + const slug = pathname.replace("/blog/", ""); + const location = `blog-${slug}`; + + return ( +
+ + +
+ ); +} diff --git a/web/app/components/blog-pager.tsx b/web/app/components/blog-pager.tsx new file mode 100644 index 00000000..b895a75c --- /dev/null +++ b/web/app/components/blog-pager.tsx @@ -0,0 +1,43 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { blogPosts } from "./blog-posts"; + +export function BlogPager() { + const pathname = usePathname(); + const index = blogPosts.findIndex( + (post) => `/blog/${post.slug}` === pathname + ); + const prev = index > 0 ? blogPosts[index - 1] : null; + const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null; + + if (!prev && !next) return null; + + return ( + + ); +} diff --git a/web/app/components/blog-posts.ts b/web/app/components/blog-posts.ts new file mode 100644 index 00000000..3cfdff15 --- /dev/null +++ b/web/app/components/blog-posts.ts @@ -0,0 +1,30 @@ +export const blogPosts = [ + { + slug: "cmd-shift-u", + title: "Cmd+Shift+U", + date: "2026-03-04", + summary: + "How Cmd+Shift+U navigates between finished agents across workspaces in cmux.", + }, + { + slug: "zen-of-cmux", + title: "The Zen of cmux", + date: "2026-02-27", + summary: + "cmux is a primitive, not a solution. It gives you composable pieces and your workflow is up to you.", + }, + { + slug: "show-hn-launch", + title: "Launching cmux on Show HN", + date: "2026-02-21", + summary: + "cmux hit #2 on Hacker News, got shared by Mitchell Hashimoto, and went viral in Japan.", + }, + { + slug: "introducing-cmux", + title: "Introducing cmux", + date: "2026-02-12", + summary: + "A native macOS terminal built on Ghostty, designed for running multiple AI coding agents side by side.", + }, +]; diff --git a/web/app/components/github-button.tsx b/web/app/components/github-button.tsx index 9cee9aaa..585e9747 100644 --- a/web/app/components/github-button.tsx +++ b/web/app/components/github-button.tsx @@ -2,13 +2,13 @@ import posthog from "posthog-js"; -export function GitHubButton() { +export function GitHubButton({ location = "hero" }: { location?: string }) { return (
posthog.capture("cmuxterm_github_clicked", { location: "hero" })} + onClick={() => posthog.capture("cmuxterm_github_clicked", { location })} className="inline-flex items-center whitespace-nowrap gap-2 rounded-full border border-border px-5 py-2.5 text-[15px] font-medium text-foreground hover:bg-code-bg transition-colors" > From ace539326f486f1eb61f53fe701e339edc67575a Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:30:49 -0800 Subject: [PATCH 061/232] Add debug logs for Cmd+F find bar refocus (#840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add debug logs for Cmd+F find bar focus/refocus state machine Traces the full lifecycle: menu action, startSearch, overlay mount/unmount, focus changes, window key/resign, applyFirstResponderIfNeeded guards, and moveFocus calls. Helps reproduce the bug where Cmd+F fails to reopen after switching away and back to the terminal window. * Fix Cmd+F find bar focus loss after window switch When the find bar is open and the user switches away and back, the window's first responder was left as the NSWindow itself because applyFirstResponderIfNeeded bailed on the searchState guard and nothing refocused the find bar. This caused a dead state where neither the search field nor the terminal accepted keyboard input. Add a SearchFocusTarget state machine (.searchField / .terminal) to GhosttySurfaceScrollView that tracks user intent. On window-become-key, restoreSearchFocus() makes the correct view first responder based on the target. Pressing Escape with a non-empty needle sets target to .terminal so window reactivation preserves that intent. Cmd+F and .ghosttySearchFocus notifications reset target to .searchField. * Fix multi-surface focus stealing and NSHostingView responder issue Two bugs found from debug logs: 1. Other surfaces in the same window (without search active) were calling applyFirstResponderIfNeeded and stealing focus from the find bar's surface. Added a check: if current first responder is inside a search overlay NSHostingView, don't steal it. 2. window.makeFirstResponder(overlay) on the NSHostingView was wrong. It made the hosting view itself the responder, which ate keystrokes as performKeyEquivalent instead of routing them to the SwiftUI TextField inside. Removed that call, now only posting the .ghosttySearchFocus notification to let SwiftUI handle internal focus via @FocusState. * Use AppKit NSTextField focus instead of SwiftUI @FocusState for search restore The notification-only approach fails because SwiftUI @FocusState can't propagate to AppKit when the first responder is the NSWindow itself (no view in the responder chain to anchor the change). And making the NSHostingView first responder eats keys as performKeyEquivalent. Now walks the hosting view's subview tree to find the actual editable NSTextField backing the SwiftUI TextField, and calls window.makeFirstResponder directly on it. Falls back to notification if the text field isn't found. * Two-phase focus restore: AppKit + SwiftUI sync, click-to-terminal fix restoreSearchFocus now does both: 1. AppKit: makeFirstResponder(nsTextField) so typing works immediately 2. SwiftUI: post .ghosttySearchFocus so @FocusState syncs and .onExitCommand (Escape) and .onKeyPress (Return) still work Also: clicking the terminal while find bar is open now sets searchFocusTarget to .terminal, so window reactivation correctly restores terminal focus instead of jumping back to the search field. * Replace SwiftUI TextField with NSViewRepresentable for find bar The core issue: SwiftUI @FocusState does not sync with AppKit's first responder after window resign/become-key cycles. This caused the find bar to lose all keyboard input after switching windows. Previous attempts to bridge SwiftUI and AppKit focus (notifications, makeFirstResponder on the backing NSTextField, belt-and-suspenders approaches) all failed because SwiftUI event handlers (.onExitCommand for Escape, .onKeyPress for Return) require @FocusState to be set. Fix: replace the SwiftUI TextField with an NSViewRepresentable-wrapped NSTextField (SearchTextFieldRepresentable), following the proven OmnibarNativeTextField pattern already in BrowserPanelView.swift. - Escape and Return handled via control(_:textView:doCommandBy:) at the AppKit delegate level, no @FocusState needed - Focus restored via .ghosttySearchFocus notification observed directly by the Coordinator, calling makeFirstResponder immediately - hasMarkedText() guard preserves CJK IME composition (issue #118) - isProgrammaticMutation guard prevents text binding cursor reset - Removes findTextField(in:) subview walk hack * Explicitly unfocus terminal surface when find bar takes focus The Ghostty cursor kept blinking even when the search field was focused because ghostty_surface_set_focus(false) was only called via surfaceView.resignFirstResponder. After window switching, the surface view may not have been the first responder, so resign was never called. Fix: call surface.setFocus(false) in both the .ghosttySearchFocus notification observer and directly in restoreSearchFocus. This ensures the cursor stops blinking regardless of previous first-responder state. * Address review findings: field-editor guard, NSLog→dlog, stale focus 1. isSearchOverlayOrDescendant now accepts NSResponder and follows the field-editor delegate chain back to the owning NSTextField. Previously, when the search field was being edited, the shared NSTextView field editor was the first responder (outside the overlay hierarchy), so the guard missed it and other surfaces could steal focus. 2. Converted all NSLog calls in TabManager (startSearch, hideFind, searchSelection), cmuxApp (Find menu), and GhosttyTerminalView (searchState didSet) to dlog() wrapped in #if DEBUG. Avoids leaking search needle text to system logs in release builds. 3. Added isFocused re-check inside the deferred focus block in SearchTextFieldRepresentable to prevent stale focus requests from stealing focus back after intent has changed. * Guard against re-focusing already-focused search field Every keystroke updated searchState.needle (@Published), which triggered a SwiftUI re-render → ensureFocus → restoreSearchFocus → posted .ghosttySearchFocus notification → Coordinator called makeFirstResponder unconditionally. makeFirstResponder on an already-editing NSTextField ends the editing session and restarts with all text selected, so the next typed character replaced the previous one ("hi" → "i"). Fix: check if the field is already first responder before calling makeFirstResponder in the notification handler. * Address review findings: stale focus target, IME guard, tab/pane gating - Add onFieldDidFocus callback so clicking back into the search field after Escape updates searchFocusTarget = .searchField, fixing stale focus restoration after window switches. - Guard updateNSView text sync with !editor.hasMarkedText() to prevent stomping active CJK IME composition. - Move ensureFocus search state check after tab/pane selection guards so search focus isn't restored on non-active tabs/panes. - Clear surfaceView.onFocus when setFocusHandler(nil) is called. --- Sources/Find/SurfaceSearchOverlay.swift | 255 ++++++++++++++++++++---- Sources/GhosttyTerminalView.swift | 153 +++++++++++++- Sources/TabManager.swift | 21 +- Sources/cmuxApp.swift | 3 + 4 files changed, 385 insertions(+), 47 deletions(-) diff --git a/Sources/Find/SurfaceSearchOverlay.swift b/Sources/Find/SurfaceSearchOverlay.swift index 0900b2ce..ae2c63c8 100644 --- a/Sources/Find/SurfaceSearchOverlay.swift +++ b/Sources/Find/SurfaceSearchOverlay.swift @@ -1,3 +1,4 @@ +import AppKit import Bonsplit import SwiftUI @@ -7,27 +8,47 @@ struct SurfaceSearchOverlay: View { @ObservedObject var searchState: TerminalSurface.SearchState let onMoveFocusToTerminal: () -> Void let onNavigateSearch: (_ action: String) -> Void + let onFieldDidFocus: () -> Void let onClose: () -> Void @State private var corner: Corner = .topRight @State private var dragOffset: CGSize = .zero @State private var barSize: CGSize = .zero - @FocusState private var isSearchFieldFocused: Bool + @State private var isSearchFieldFocused: Bool = true private let padding: CGFloat = 8 var body: some View { GeometryReader { geo in HStack(spacing: 4) { - TextField("Search", text: $searchState.needle) - .textFieldStyle(.plain) - .frame(width: 180) - .padding(.leading, 8) - .padding(.trailing, 50) - .padding(.vertical, 6) - .background(Color.primary.opacity(0.1)) - .cornerRadius(6) - .focused($isSearchFieldFocused) - .overlay(alignment: .trailing) { + SearchTextFieldRepresentable( + text: $searchState.needle, + isFocused: $isSearchFieldFocused, + surfaceId: surfaceId, + onFieldDidFocus: onFieldDidFocus, + onEscape: { + #if DEBUG + dlog("find.nativeField.escape surface=\(surfaceId.uuidString.prefix(5)) needleEmpty=\(searchState.needle.isEmpty)") + #endif + if searchState.needle.isEmpty { + onClose() + } else { + onMoveFocusToTerminal() + } + }, + onReturn: { isShift in + let action = isShift + ? "navigate_search:previous" + : "navigate_search:next" + onNavigateSearch(action) + } + ) + .frame(width: 180) + .padding(.leading, 8) + .padding(.trailing, 50) + .padding(.vertical, 6) + .background(Color.primary.opacity(0.1)) + .cornerRadius(6) + .overlay(alignment: .trailing) { if let selected = searchState.selected { let totalText = searchState.total.map { String($0) } ?? "?" Text("\(selected + 1)/\(totalText)") @@ -43,20 +64,6 @@ struct SurfaceSearchOverlay: View { .padding(.trailing, 8) } } - .onExitCommand { - if searchState.needle.isEmpty { - onClose() - } else { - onMoveFocusToTerminal() - } - } - .backport.onKeyPress(.return) { modifiers in - let action = modifiers.contains(.shift) - ? "navigate_search:previous" - : "navigate_search:next" - onNavigateSearch(action) - return .handled - } Button(action: { #if DEBUG @@ -96,17 +103,11 @@ struct SurfaceSearchOverlay: View { .clipShape(clipShape) .shadow(radius: 4) .onAppear { - NSLog("Find: overlay appear tab=%@ surface=%@", tabId.uuidString, surfaceId.uuidString) + #if DEBUG + dlog("find.overlay.appear tab=\(tabId.uuidString.prefix(5)) surface=\(surfaceId.uuidString.prefix(5))") + #endif isSearchFieldFocused = true } - .onReceive(NotificationCenter.default.publisher(for: .ghosttySearchFocus)) { notification in - guard let focusedSurface = notification.object as? TerminalSurface, - focusedSurface.id == surfaceId else { return } - NSLog("Find: overlay focus tab=%@ surface=%@", tabId.uuidString, surfaceId.uuidString) - DispatchQueue.main.async { - isSearchFieldFocused = true - } - } .background( GeometryReader { barGeo in Color.clear.onAppear { @@ -185,6 +186,192 @@ struct SurfaceSearchOverlay: View { } } +// MARK: - Native Search Text Field (AppKit) + +/// NSTextField subclass for the terminal find bar. +/// Strips visual chrome so SwiftUI handles the background/border appearance. +private final class SearchNativeTextField: NSTextField { + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + isBordered = false + isBezeled = false + drawsBackground = false + focusRingType = .none + usesSingleLineMode = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +/// NSViewRepresentable wrapping SearchNativeTextField. +/// Handles Escape and Return at the AppKit delegate level, eliminating the +/// SwiftUI @FocusState / AppKit first-responder mismatch that broke focus +/// after window switching. +private struct SearchTextFieldRepresentable: NSViewRepresentable { + @Binding var text: String + @Binding var isFocused: Bool + let surfaceId: UUID + let onFieldDidFocus: () -> Void + let onEscape: () -> Void + let onReturn: (_ isShift: Bool) -> Void + + final class Coordinator: NSObject, NSTextFieldDelegate { + var parent: SearchTextFieldRepresentable + var isProgrammaticMutation = false + weak var parentField: SearchNativeTextField? + var pendingFocusRequest: Bool? + var searchFocusObserver: NSObjectProtocol? + + init(parent: SearchTextFieldRepresentable) { + self.parent = parent + } + + deinit { + if let searchFocusObserver { + NotificationCenter.default.removeObserver(searchFocusObserver) + } + } + + func controlTextDidChange(_ obj: Notification) { + guard !isProgrammaticMutation else { return } + guard let field = obj.object as? NSTextField else { return } + parent.text = field.stringValue + } + + func controlTextDidBeginEditing(_ obj: Notification) { + #if DEBUG + dlog("find.nativeField.beginEditing surface=\(parent.surfaceId.uuidString.prefix(5))") + #endif + parent.onFieldDidFocus() + if !parent.isFocused { + DispatchQueue.main.async { + self.parent.isFocused = true + } + } + } + + func controlTextDidEndEditing(_ obj: Notification) { + #if DEBUG + dlog("find.nativeField.endEditing surface=\(parent.surfaceId.uuidString.prefix(5))") + #endif + if parent.isFocused { + DispatchQueue.main.async { + self.parent.isFocused = false + } + } + } + + func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + switch commandSelector { + case #selector(NSResponder.cancelOperation(_:)): + // Don't intercept Escape during CJK IME composition (issue #118) + if textView.hasMarkedText() { return false } + parent.onEscape() + return true + case #selector(NSResponder.insertNewline(_:)): + if textView.hasMarkedText() { return false } + let isShift = NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false + parent.onReturn(isShift) + return true + default: + return false + } + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + func makeNSView(context: Context) -> SearchNativeTextField { + let field = SearchNativeTextField(frame: .zero) + field.font = .systemFont(ofSize: NSFont.systemFontSize) + field.placeholderString = "Search" + field.delegate = context.coordinator + field.stringValue = text + context.coordinator.parentField = field + + // Observe .ghosttySearchFocus to immediately focus from AppKit level. + // This is the primary mechanism for restoring focus after window switches. + context.coordinator.searchFocusObserver = NotificationCenter.default.addObserver( + forName: .ghosttySearchFocus, + object: nil, + queue: .main + ) { [weak field, weak coordinator = context.coordinator] notification in + guard let field, let coordinator else { return } + guard let surface = notification.object as? TerminalSurface, + surface.id == coordinator.parent.surfaceId else { return } + guard let window = field.window else { return } + // Don't re-focus if already first responder. makeFirstResponder on an + // already-editing NSTextField ends the editing session and restarts it + // with all text selected, causing typed characters to replace each other. + let fr = window.firstResponder + let alreadyFocused = fr === field || + field.currentEditor() != nil || + ((fr as? NSTextView)?.delegate as? NSTextField) === field + #if DEBUG + dlog("find.nativeField.searchFocusNotification surface=\(coordinator.parent.surfaceId.uuidString.prefix(5)) alreadyFocused=\(alreadyFocused)") + #endif + guard !alreadyFocused else { return } + window.makeFirstResponder(field) + } + + return field + } + + func updateNSView(_ nsView: SearchNativeTextField, context: Context) { + context.coordinator.parent = self + context.coordinator.parentField = nsView + + // Sync text from binding to field (skip during active IME composition) + if let editor = nsView.currentEditor() as? NSTextView { + if editor.string != text, !editor.hasMarkedText() { + context.coordinator.isProgrammaticMutation = true + editor.string = text + nsView.stringValue = text + context.coordinator.isProgrammaticMutation = false + } + } else if nsView.stringValue != text { + nsView.stringValue = text + } + + // Sync focus from binding to AppKit + if let window = nsView.window { + let fr = window.firstResponder + let isFirstResponder = + fr === nsView || + nsView.currentEditor() != nil || + ((fr as? NSTextView)?.delegate as? NSTextField) === nsView + + if isFocused, !isFirstResponder, context.coordinator.pendingFocusRequest != true { + context.coordinator.pendingFocusRequest = true + DispatchQueue.main.async { [weak nsView, weak coordinator = context.coordinator] in + coordinator?.pendingFocusRequest = nil + guard let coordinator, coordinator.parent.isFocused else { return } + guard let nsView, let window = nsView.window else { return } + let fr = window.firstResponder + let alreadyFocused = fr === nsView || + nsView.currentEditor() != nil || + ((fr as? NSTextView)?.delegate as? NSTextField) === nsView + guard !alreadyFocused else { return } + window.makeFirstResponder(nsView) + } + } + } + } + + static func dismantleNSView(_ nsView: SearchNativeTextField, coordinator: Coordinator) { + if let observer = coordinator.searchFocusObserver { + NotificationCenter.default.removeObserver(observer) + coordinator.searchFocusObserver = nil + } + nsView.delegate = nil + coordinator.parentField = nil + } +} + struct SearchButtonStyle: ButtonStyle { @State private var isHovered = false diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index f1e8856f..3de0ee2a 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2039,7 +2039,9 @@ final class TerminalSurface: Identifiable, ObservableObject { didSet { if let searchState { hostedView.cancelFocusRequest() - NSLog("Find: search state created tab=%@ surface=%@", tabId.uuidString, id.uuidString) +#if DEBUG + dlog("find.searchState created tab=\(tabId.uuidString.prefix(5)) surface=\(id.uuidString.prefix(5))") +#endif searchNeedleCancellable = searchState.$needle .removeDuplicates() .map { needle -> AnyPublisher in @@ -2053,12 +2055,16 @@ final class TerminalSurface: Identifiable, ObservableObject { } .switchToLatest() .sink { [weak self] needle in - NSLog("Find: needle updated tab=%@ surface=%@ needle=%@", self?.tabId.uuidString ?? "unknown", self?.id.uuidString ?? "unknown", needle) +#if DEBUG + dlog("find.needle updated tab=\(self?.tabId.uuidString.prefix(5) ?? "?") surface=\(self?.id.uuidString.prefix(5) ?? "?") chars=\(needle.count)") +#endif _ = self?.performBindingAction("search:\(needle)") } } else if oldValue != nil { searchNeedleCancellable = nil - NSLog("Find: search state cleared tab=%@ surface=%@", tabId.uuidString, id.uuidString) +#if DEBUG + dlog("find.searchState cleared tab=\(tabId.uuidString.prefix(5)) surface=\(id.uuidString.prefix(5))") +#endif _ = performBindingAction("end_search") } } @@ -4705,6 +4711,14 @@ final class GhosttySurfaceScrollView: NSView { private var dropZoneOverlayAnimationGeneration: UInt64 = 0 // Intentionally no focus retry loops: rely on AppKit first-responder and bonsplit selection. + /// Tracks whether keyboard focus should go to the search field or the terminal + /// when the window becomes key while the find bar is open. + enum SearchFocusTarget { + case searchField + case terminal + } + private(set) var searchFocusTarget: SearchFocusTarget = .searchField + private static func panelBackgroundFillColor(for terminalBackgroundColor: NSColor) -> NSColor { // The Ghostty renderer already draws translucent terminal backgrounds. If we paint an // additional translucent layer here, alpha stacks and appears effectively opaque. @@ -4990,6 +5004,20 @@ final class GhosttySurfaceScrollView: NSView { self?.handleScrollbarUpdate(notification) }) + observers.append(NotificationCenter.default.addObserver( + forName: .ghosttySearchFocus, + object: nil, + queue: .main + ) { [weak self] notification in + guard let self, + let surface = notification.object as? TerminalSurface, + surface === self.surfaceView.terminalSurface else { return } + self.searchFocusTarget = .searchField + // Explicitly unfocus the terminal so the cursor stops blinking + // when the search field takes over. + surface.setFocus(false) + }) + observers.append(NotificationCenter.default.addObserver( forName: .ghosttyDidUpdateCellSize, object: surfaceView, @@ -5091,7 +5119,12 @@ final class GhosttySurfaceScrollView: NSView { object: window, queue: .main ) { [weak self] _ in - self?.applyFirstResponderIfNeeded() + guard let self else { return } + let searchActive = self.surfaceView.terminalSurface?.searchState != nil +#if DEBUG + dlog("find.window.didBecomeKey surface=\(self.surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil") searchActive=\(searchActive) focusTarget=\(self.searchFocusTarget) firstResponder=\(String(describing: self.window?.firstResponder))") +#endif + self.applyFirstResponderIfNeeded() }) windowObservers.append(NotificationCenter.default.addObserver( forName: NSWindow.didResignKeyNotification, @@ -5099,11 +5132,19 @@ final class GhosttySurfaceScrollView: NSView { queue: .main ) { [weak self] _ in guard let self, let window = self.window else { return } + let searchActive = self.surfaceView.terminalSurface?.searchState != nil // Losing key window does not always trigger first-responder resignation, so force // the focused terminal view to yield responder to keep Ghostty cursor/focus state in sync. if let fr = window.firstResponder as? NSView, fr === self.surfaceView || fr.isDescendant(of: self.surfaceView) { +#if DEBUG + dlog("find.window.didResignKey surface=\(self.surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil") searchActive=\(searchActive) resigningFirstResponder") +#endif window.makeFirstResponder(nil) + } else { +#if DEBUG + dlog("find.window.didResignKey surface=\(self.surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil") searchActive=\(searchActive) firstResponder=\(String(describing: window.firstResponder)) (not terminal, skipping)") +#endif } }) if window.isKeyWindow { applyFirstResponderIfNeeded() } @@ -5114,7 +5155,18 @@ final class GhosttySurfaceScrollView: NSView { } func setFocusHandler(_ handler: (() -> Void)?) { - surfaceView.onFocus = handler + guard let handler else { + surfaceView.onFocus = nil + return + } + surfaceView.onFocus = { [weak self] in + // When the terminal surface gains focus (click, tab, etc.), update the + // search focus target so window reactivation restores terminal focus. + if self?.surfaceView.terminalSurface?.searchState != nil { + self?.searchFocusTarget = .terminal + } + handler() + } } func setTriggerFlashHandler(_ handler: (() -> Void)?) { @@ -5167,11 +5219,21 @@ final class GhosttySurfaceScrollView: NSView { // SwiftUI panel-level overlays can fall behind portal-hosted terminal surfaces. guard let terminalSurface = surfaceView.terminalSurface, let searchState else { + let hadOverlay = searchOverlayHostingView != nil +#if DEBUG + dlog("find.setSearchOverlay REMOVE surface=\(surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil") hadOverlay=\(hadOverlay)") +#endif searchOverlayHostingView?.removeFromSuperview() searchOverlayHostingView = nil + searchFocusTarget = .searchField return } + let hadOverlay = searchOverlayHostingView != nil +#if DEBUG + dlog("find.setSearchOverlay MOUNT surface=\(terminalSurface.id.uuidString.prefix(5)) existingOverlay=\(hadOverlay ? "yes(update)" : "no(create)")") +#endif + let tabId = terminalSurface.tabId let surfaceId = terminalSurface.id let rootView = SurfaceSearchOverlay( @@ -5179,11 +5241,16 @@ final class GhosttySurfaceScrollView: NSView { surfaceId: surfaceId, searchState: searchState, onMoveFocusToTerminal: { [weak self] in + self?.searchFocusTarget = .terminal self?.moveFocus() }, onNavigateSearch: { [weak terminalSurface] action in _ = terminalSurface?.performBindingAction(action) }, + onFieldDidFocus: { [weak self, weak terminalSurface] in + self?.searchFocusTarget = .searchField + terminalSurface?.setFocus(false) + }, onClose: { [weak self, weak terminalSurface] in terminalSurface?.searchState = nil self?.moveFocus() @@ -5208,6 +5275,7 @@ final class GhosttySurfaceScrollView: NSView { return } + searchFocusTarget = .searchField let overlay = NSHostingView(rootView: rootView) overlay.translatesAutoresizingMaskIntoConstraints = false addSubview(overlay) @@ -5512,7 +5580,9 @@ final class GhosttySurfaceScrollView: NSView { func moveFocus(from previous: GhosttySurfaceScrollView? = nil, delay: TimeInterval? = nil) { #if DEBUG - dlog("focus.moveFocus to=\(self.surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil")") + let surfaceShort = self.surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil" + let searchActive = self.surfaceView.terminalSurface?.searchState != nil + dlog("find.moveFocus to=\(surfaceShort) searchState=\(searchActive ? "active" : "nil")") #endif let work = { [weak self] in guard let self else { return } @@ -5662,7 +5732,6 @@ final class GhosttySurfaceScrollView: NSView { let isHiddenForFocus = isHiddenOrHasHiddenAncestor || surfaceView.isHiddenOrHasHiddenAncestor guard isActive else { return } - guard surfaceView.terminalSurface?.searchState == nil else { return } guard let window else { return } guard surfaceView.isVisibleInUI else { retry() @@ -5702,6 +5771,12 @@ final class GhosttySurfaceScrollView: NSView { return } + // Search focus restoration — only after confirming this is the active tab/pane. + if surfaceView.terminalSurface?.searchState != nil { + restoreSearchFocus(window: window) + return + } + if let fr = window.firstResponder as? NSView, fr === surfaceView || fr.isDescendant(of: surfaceView) { return @@ -5741,27 +5816,87 @@ final class GhosttySurfaceScrollView: NSView { return size.width > 1 && size.height > 1 }() let isHiddenForFocus = isHiddenOrHasHiddenAncestor || surfaceView.isHiddenOrHasHiddenAncestor + let surfaceShort = surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil" guard isActive else { return } guard surfaceView.isVisibleInUI else { return } guard !isHiddenForFocus, hasUsablePortalGeometry else { #if DEBUG dlog( - "focus.apply.skip surface=\(surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil") " + + "focus.apply.skip surface=\(surfaceShort) " + "reason=hidden_or_tiny hidden=\(isHiddenForFocus ? 1 : 0) frame=\(String(format: "%.1fx%.1f", bounds.width, bounds.height))" ) #endif return } - guard surfaceView.terminalSurface?.searchState == nil else { return } guard let window, window.isKeyWindow else { return } + if surfaceView.terminalSurface?.searchState != nil { + // Find bar is open. Restore focus based on what the user last intended. + restoreSearchFocus(window: window) + return + } if let fr = window.firstResponder as? NSView, fr === surfaceView || fr.isDescendant(of: surfaceView) { return } + // Don't steal focus from a search overlay on another surface in this window. + if let fr = window.firstResponder, isSearchOverlayOrDescendant(fr) { +#if DEBUG + dlog("find.applyFirstResponder SKIP surface=\(surfaceShort) reason=searchOverlayFocused") +#endif + return + } +#if DEBUG + dlog("find.applyFirstResponder APPLY surface=\(surfaceShort) prevFirstResponder=\(String(describing: window.firstResponder))") +#endif window.makeFirstResponder(surfaceView) } + /// Restore focus when window becomes key and the find bar is open. + /// Respects `searchFocusTarget` so Escape-to-terminal intent is preserved across window switches. + private func restoreSearchFocus(window: NSWindow) { + let surfaceShort = surfaceView.terminalSurface?.id.uuidString.prefix(5) ?? "nil" + switch searchFocusTarget { + case .searchField: + // Explicitly unfocus the terminal so cursor stops blinking immediately. + // The notification observer also does this, but it runs async when posted from main. + surfaceView.terminalSurface?.setFocus(false) + // Post notification — SearchTextFieldRepresentable's Coordinator + // observes it and calls makeFirstResponder on the native NSTextField. + if let terminalSurface = surfaceView.terminalSurface { + NotificationCenter.default.post(name: .ghosttySearchFocus, object: terminalSurface) + } +#if DEBUG + dlog("find.restoreSearchFocus surface=\(surfaceShort) target=searchField via=notification") +#endif + case .terminal: + window.makeFirstResponder(surfaceView) +#if DEBUG + dlog("find.restoreSearchFocus surface=\(surfaceShort) target=terminal") +#endif + } + } + + /// Check if a responder is inside a search overlay hosting view. + /// Handles the AppKit field-editor case: when an NSTextField is being edited, + /// window.firstResponder is the shared NSTextView field editor, not the text field. + private func isSearchOverlayOrDescendant(_ responder: NSResponder) -> Bool { + // If the responder is a field editor, follow its delegate back to the owning control. + if let editor = responder as? NSTextView, + editor.isFieldEditor, + let editedView = editor.delegate as? NSView { + return isSearchOverlayOrDescendant(editedView) + } + + guard let view = responder as? NSView else { return false } + var current: NSView? = view + while let v = current { + if v is NSHostingView { return true } + current = v.superview + } + return false + } + #if DEBUG struct DebugRenderStats { let drawCount: Int diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 64fc28dc..c4581c62 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -726,11 +726,19 @@ class TabManager: ObservableObject { } func startSearch() { - guard let panel = selectedTerminalPanel else { return } - if panel.searchState == nil { + guard let panel = selectedTerminalPanel else { +#if DEBUG + dlog("find.startSearch SKIPPED no selectedTerminalPanel") +#endif + return + } + let wasNil = panel.searchState == nil + if wasNil { panel.searchState = TerminalSurface.SearchState() } - NSLog("Find: startSearch workspace=%@ panel=%@", panel.workspaceId.uuidString, panel.id.uuidString) +#if DEBUG + dlog("find.startSearch workspace=\(panel.workspaceId.uuidString.prefix(5)) panel=\(panel.id.uuidString.prefix(5)) created=\(wasNil ? "yes" : "no(reuse)") firstResponder=\(String(describing: panel.surface.hostedView.window?.firstResponder))") +#endif NotificationCenter.default.post(name: .ghosttySearchFocus, object: panel.surface) _ = panel.performBindingAction("start_search") } @@ -740,7 +748,9 @@ class TabManager: ObservableObject { if panel.searchState == nil { panel.searchState = TerminalSurface.SearchState() } - NSLog("Find: searchSelection workspace=%@ panel=%@", panel.workspaceId.uuidString, panel.id.uuidString) +#if DEBUG + dlog("find.searchSelection workspace=\(panel.workspaceId.uuidString.prefix(5)) panel=\(panel.id.uuidString.prefix(5))") +#endif NotificationCenter.default.post(name: .ghosttySearchFocus, object: panel.surface) _ = panel.performBindingAction("search_selection") } @@ -760,6 +770,9 @@ class TabManager: ObservableObject { } func hideFind() { +#if DEBUG + dlog("find.hideFind panel=\(selectedTerminalPanel?.id.uuidString.prefix(5) ?? "nil")") +#endif selectedTerminalPanel?.searchState = nil } diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 3fff7055..6c77ea29 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -462,6 +462,9 @@ struct cmuxApp: App { CommandGroup(after: .textEditing) { Menu("Find") { Button("Find…") { +#if DEBUG + dlog("find.menu Cmd+F fired") +#endif activeTabManager.startSearch() } .keyboardShortcut("f", modifiers: .command) From 79cfe2d1682443703671b03e4eedf6ea869e9c9f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:48:06 -0800 Subject: [PATCH 062/232] Fix cross-window theme background gating after jump-to-unread (#861) * Fix cross-window theme background gating * Handle owning-manager nil-selection theme edge case * Simplify window background gating helper --- Sources/GhosttyTerminalView.swift | 31 +++++++++- cmuxTests/GhosttyConfigTests.swift | 90 ++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 3de0ee2a..020d4ad7 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2981,9 +2981,38 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } } + // Theme/background application is window-local. During cross-window workspace + // switches (e.g. jump-to-unread), the global active tab manager can lag behind. + // Prefer the owning window's selected workspace when available. + static func shouldApplyWindowBackground( + surfaceTabId: UUID?, + owningManagerExists: Bool, + owningSelectedTabId: UUID?, + activeSelectedTabId: UUID? + ) -> Bool { + guard let surfaceTabId else { return true } + if owningManagerExists { + guard let owningSelectedTabId else { return true } + return owningSelectedTabId == surfaceTabId + } + if let activeSelectedTabId { + return activeSelectedTabId == surfaceTabId + } + return true + } + func applyWindowBackgroundIfActive() { guard let window else { return } - if let tabId, let selectedId = AppDelegate.shared?.tabManager?.selectedTabId, tabId != selectedId { + let appDelegate = AppDelegate.shared + let owningManager = tabId.flatMap { appDelegate?.tabManagerFor(tabId: $0) } + let owningSelectedTabId = owningManager?.selectedTabId + let activeSelectedTabId = owningManager == nil ? appDelegate?.tabManager?.selectedTabId : nil + guard Self.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: owningManager != nil, + owningSelectedTabId: owningSelectedTabId, + activeSelectedTabId: activeSelectedTabId + ) else { return } applySurfaceBackground() diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index ac9d68b7..98bff922 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -659,6 +659,96 @@ final class WindowTransparencyDecisionTests: XCTestCase { } } +final class WindowBackgroundSelectionGateTests: XCTestCase { + func testShouldApplyWindowBackgroundUsesOwningWindowSelectionWhenAvailable() { + let tabId = UUID() + let activeSelectedTabId = UUID() + + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: true, + owningSelectedTabId: tabId, + activeSelectedTabId: activeSelectedTabId + ) + ) + } + + func testShouldApplyWindowBackgroundRejectsWhenOwningSelectionDiffers() { + let tabId = UUID() + + XCTAssertFalse( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: true, + owningSelectedTabId: UUID(), + activeSelectedTabId: tabId + ) + ) + } + + func testShouldApplyWindowBackgroundAllowsWhenOwningManagerSelectionIsTemporarilyNil() { + let tabId = UUID() + + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: true, + owningSelectedTabId: nil, + activeSelectedTabId: UUID() + ) + ) + } + + func testShouldApplyWindowBackgroundFallsBackToActiveSelection() { + let tabId = UUID() + + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: tabId + ) + ) + XCTAssertFalse( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: UUID() + ) + ) + } + + func testShouldApplyWindowBackgroundAllowsWhenNoSelectionContext() { + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: UUID(), + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: nil + ) + ) + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: nil, + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: nil + ) + ) + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: nil, + owningManagerExists: true, + owningSelectedTabId: UUID(), + activeSelectedTabId: UUID() + ) + ) + } +} + final class NotificationBurstCoalescerTests: XCTestCase { func testSignalsInSameBurstFlushOnce() { let coalescer = NotificationBurstCoalescer(delay: 0.01) From e0ec4487010c616e7829e3fd36940a0671837c09 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 03:27:54 -0800 Subject: [PATCH 063/232] Fix Escape propagation when command palette is visible (#847) * Fix command palette Escape propagation and add regressions * Respect IME marked text for command palette Escape * Harden command palette escape pending-open routing --- Sources/AppDelegate.swift | 423 ++++++++++- .../AppDelegateShortcutRoutingTests.swift | 716 +++++++++++++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 11 + 3 files changed, 1138 insertions(+), 12 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 1c88b6cc..254381a6 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1051,6 +1051,13 @@ func shouldConsumeShortcutWhileCommandPaletteVisible( keyCode: UInt16 ) -> Bool { guard isCommandPaletteVisible else { return false } + + // Escape dismisses the palette, and must not leak through to the + // underlying terminal or browser content. + if normalizedFlags.isEmpty, keyCode == 53 { + return true + } + guard normalizedFlags.contains(.command) else { return false } let normalizedChars = chars.lowercased() @@ -1563,8 +1570,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private var didInstallLifecycleSnapshotObservers = false private var didDisableSuddenTermination = false private var commandPaletteVisibilityByWindowId: [UUID: Bool] = [:] + private var commandPalettePendingOpenByWindowId: [UUID: Bool] = [:] + private var commandPaletteRecentRequestAtByWindowId: [UUID: TimeInterval] = [:] + private var commandPaletteEscapeSuppressionByWindowId: Set = [] + private var commandPaletteEscapeSuppressionStartedAtByWindowId: [UUID: TimeInterval] = [:] private var commandPaletteSelectionByWindowId: [UUID: Int] = [:] private var commandPaletteSnapshotByWindowId: [UUID: CommandPaletteDebugSnapshot] = [:] + private static let commandPaletteRequestGraceInterval: TimeInterval = 1.25 + private static let commandPalettePendingOpenMaxAge: TimeInterval = 8.0 var updateViewModel: UpdateViewModel { updateController.viewModel @@ -3281,9 +3294,212 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent windowForMainWindowId(windowId) } + private func markCommandPaletteOpenRequested(for window: NSWindow?) { + guard let window, + let windowId = mainWindowId(for: window) else { return } + commandPalettePendingOpenByWindowId[windowId] = true + commandPaletteRecentRequestAtByWindowId[windowId] = ProcessInfo.processInfo.systemUptime + } + + private func postCommandPaletteRequest( + name: Notification.Name, + preferredWindow: NSWindow?, + source: String, + markPending: Bool + ) { + let targetWindow = preferredWindow ?? NSApp.keyWindow ?? NSApp.mainWindow + if markPending { + markCommandPaletteOpenRequested(for: targetWindow) + } + NotificationCenter.default.post(name: name, object: targetWindow) +#if DEBUG + dlog( + "shortcut.palette.request source=\(source) " + + "target={\(debugWindowToken(targetWindow))} " + + "pendingMarked=\(markPending ? 1 : 0)" + ) +#endif + } + + func requestCommandPaletteCommands(preferredWindow: NSWindow? = nil, source: String = "api.commandPalette") { + postCommandPaletteRequest( + name: .commandPaletteRequested, + preferredWindow: preferredWindow, + source: source, + markPending: true + ) + } + + func requestCommandPaletteSwitcher(preferredWindow: NSWindow? = nil, source: String = "api.commandPaletteSwitcher") { + postCommandPaletteRequest( + name: .commandPaletteSwitcherRequested, + preferredWindow: preferredWindow, + source: source, + markPending: true + ) + } + + func requestCommandPaletteRenameTab(preferredWindow: NSWindow? = nil, source: String = "api.commandPaletteRenameTab") { + postCommandPaletteRequest( + name: .commandPaletteRenameTabRequested, + preferredWindow: preferredWindow, + source: source, + markPending: true + ) + } + + func requestCommandPaletteRenameWorkspace( + preferredWindow: NSWindow? = nil, + source: String = "api.commandPaletteRenameWorkspace" + ) { + postCommandPaletteRequest( + name: .commandPaletteRenameWorkspaceRequested, + preferredWindow: preferredWindow, + source: source, + markPending: true + ) + } + + private func clearCommandPalettePendingOpen(for window: NSWindow?) { + guard let window, + let windowId = mainWindowId(for: window) else { return } + commandPalettePendingOpenByWindowId.removeValue(forKey: windowId) + commandPaletteRecentRequestAtByWindowId.removeValue(forKey: windowId) + } + + private func pruneExpiredCommandPalettePendingOpenStates( + now: TimeInterval = ProcessInfo.processInfo.systemUptime + ) { + for windowId in Array(commandPalettePendingOpenByWindowId.keys) { + guard commandPalettePendingOpenByWindowId[windowId] == true else { continue } + guard let requestedAt = commandPaletteRecentRequestAtByWindowId[windowId] else { + commandPalettePendingOpenByWindowId.removeValue(forKey: windowId) +#if DEBUG + dlog("shortcut.palette.pendingPrune windowId=\(windowId.uuidString.prefix(8)) reason=missingTimestamp") +#endif + continue + } + let age = now - requestedAt + guard age > Self.commandPalettePendingOpenMaxAge else { continue } + commandPalettePendingOpenByWindowId.removeValue(forKey: windowId) + commandPaletteRecentRequestAtByWindowId.removeValue(forKey: windowId) +#if DEBUG + dlog( + "shortcut.palette.pendingPrune windowId=\(windowId.uuidString.prefix(8)) " + + "reason=stale ageMs=\(Int(age * 1000))" + ) +#endif + } + } + + private func isCommandPalettePendingOpen(for window: NSWindow) -> Bool { + guard let windowId = mainWindowId(for: window) else { return false } + pruneExpiredCommandPalettePendingOpenStates() + return commandPalettePendingOpenByWindowId[windowId] == true + } + + private func beginCommandPaletteEscapeSuppression(for window: NSWindow?) { + guard let window, + let windowId = mainWindowId(for: window) else { return } + commandPaletteEscapeSuppressionByWindowId.insert(windowId) + commandPaletteEscapeSuppressionStartedAtByWindowId[windowId] = ProcessInfo.processInfo.systemUptime + } + + private func endCommandPaletteEscapeSuppression(for window: NSWindow?) { + guard let window, + let windowId = mainWindowId(for: window) else { return } + commandPaletteEscapeSuppressionByWindowId.remove(windowId) + commandPaletteEscapeSuppressionStartedAtByWindowId.removeValue(forKey: windowId) + } + + private func shouldConsumeSuppressedEscape(event: NSEvent, window: NSWindow?) -> Bool { + guard let window, + let windowId = mainWindowId(for: window), + commandPaletteEscapeSuppressionByWindowId.contains(windowId) else { + return false + } + if event.isARepeat { + return true + } + let startedAt = commandPaletteEscapeSuppressionStartedAtByWindowId[windowId] ?? 0 + if ProcessInfo.processInfo.systemUptime - startedAt <= 0.35 { + return true + } + // Fallback cleanup when keyUp is lost for any reason. + endCommandPaletteEscapeSuppression(for: window) + return false + } + + private func recentCommandPaletteRequestAge(for window: NSWindow?) -> TimeInterval? { + guard let window, + let windowId = mainWindowId(for: window) else { + return nil + } + let now = ProcessInfo.processInfo.systemUptime + pruneExpiredCommandPalettePendingOpenStates(now: now) + guard commandPalettePendingOpenByWindowId[windowId] == true else { + commandPaletteRecentRequestAtByWindowId.removeValue(forKey: windowId) + return nil + } + guard let startedAt = commandPaletteRecentRequestAtByWindowId[windowId] else { + commandPalettePendingOpenByWindowId.removeValue(forKey: windowId) + return nil + } + let age = now - startedAt + if age <= Self.commandPaletteRequestGraceInterval { + return age + } + return nil + } + + private func escapeSuppressionWindow(for event: NSEvent) -> NSWindow? { + commandPaletteWindowForShortcutEvent(event) ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow + } + + @discardableResult + private func clearEscapeSuppressionForKeyUp(event: NSEvent, consumeIfSuppressed: Bool = false) -> Bool { + guard event.type == .keyUp, event.keyCode == 53 else { return false } + let suppressionWindow = escapeSuppressionWindow(for: event) + let didConsume = consumeIfSuppressed && shouldConsumeSuppressedEscape(event: event, window: suppressionWindow) + if let window = suppressionWindow { + endCommandPaletteEscapeSuppression(for: window) +#if DEBUG + dlog( + "shortcut.escape suppressionClear target={\(debugWindowToken(window))} " + + "keyUpConsumed=\(didConsume ? 1 : 0)" + ) +#endif + return didConsume + } + commandPaletteEscapeSuppressionByWindowId.removeAll() + commandPaletteEscapeSuppressionStartedAtByWindowId.removeAll() +#if DEBUG + dlog("shortcut.escape suppressionClear target={nil} clearedAll=1 keyUpConsumed=\(didConsume ? 1 : 0)") +#endif + return didConsume + } + func setCommandPaletteVisible(_ visible: Bool, for window: NSWindow) { guard let windowId = mainWindowId(for: window) else { return } + let wasVisible = commandPaletteVisibilityByWindowId[windowId] ?? false commandPaletteVisibilityByWindowId[windowId] = visible + // Opening (false -> true) always resolves pending-open. + // Closing (true -> false) also clears stale pending state. + // Ignore repeated false updates so a stale sync cannot erase an in-flight open request. + if visible || wasVisible { + commandPalettePendingOpenByWindowId.removeValue(forKey: windowId) + commandPaletteRecentRequestAtByWindowId.removeValue(forKey: windowId) + } +#if DEBUG + if !visible, + !wasVisible, + commandPalettePendingOpenByWindowId[windowId] == true { + dlog( + "palette.visibility.retainPending " + + "window={\(debugWindowToken(window))} visible=0 wasVisible=0 pending=1" + ) + } +#endif } func isCommandPaletteVisible(windowId: UUID) -> Bool { @@ -3656,20 +3872,82 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return UUID(uuidString: idPart) } + private func commandPaletteOverlayContainer(in window: NSWindow) -> NSView? { + guard let searchRoot = window.contentView?.superview ?? window.contentView else { return nil } + var stack: [NSView] = [searchRoot] + while let candidate = stack.popLast() { + if candidate.identifier == commandPaletteOverlayContainerIdentifier { + return candidate + } + stack.append(contentsOf: candidate.subviews) + } + return nil + } + + private func isCommandPaletteOverlayPresented(in window: NSWindow) -> Bool { + guard let container = commandPaletteOverlayContainer(in: window) else { return false } + return !container.isHidden && container.alphaValue > 0.001 + } + + private func isCommandPaletteResponderActive(in window: NSWindow) -> Bool { + guard let responder = window.firstResponder else { return false } + if let textView = responder as? NSTextView, + textView.isFieldEditor, + !(textView.delegate is NSView) { + // Field-editor delegates can be non-view responders. Confirm the overlay is + // mounted and visible to avoid treating unrelated editors as palette input. + return isCommandPaletteOverlayPresented(in: window) + } + return isCommandPaletteResponder(responder) + } + + private func commandPaletteMarkedTextInput(in window: NSWindow) -> NSTextView? { + if let textView = window.firstResponder as? NSTextView, + isCommandPaletteResponder(textView), + textView.hasMarkedText() { + return textView + } + + if let textField = window.firstResponder as? NSTextField, + let editor = textField.currentEditor() as? NSTextView, + isCommandPaletteResponder(editor), + editor.hasMarkedText() { + return editor + } + + return nil + } + + private func isCommandPaletteEffectivelyVisible(in window: NSWindow) -> Bool { + isCommandPaletteVisible(for: window) + || isCommandPalettePendingOpen(for: window) + || isCommandPaletteOverlayPresented(in: window) + || isCommandPaletteResponderActive(in: window) + } + private func activeCommandPaletteWindow() -> NSWindow? { + pruneExpiredCommandPalettePendingOpenStates() if let keyWindow = NSApp.keyWindow, - let windowId = mainWindowId(for: keyWindow), - commandPaletteVisibilityByWindowId[windowId] == true { + isMainTerminalWindow(keyWindow), + isCommandPaletteEffectivelyVisible(in: keyWindow) { return keyWindow } if let mainWindow = NSApp.mainWindow, - let windowId = mainWindowId(for: mainWindow), - commandPaletteVisibilityByWindowId[windowId] == true { + isMainTerminalWindow(mainWindow), + isCommandPaletteEffectivelyVisible(in: mainWindow) { return mainWindow } + if let orderedWindow = NSApp.orderedWindows.first(where: { window in + isMainTerminalWindow(window) && isCommandPaletteEffectivelyVisible(in: window) + }) { + return orderedWindow + } if let visibleWindowId = commandPaletteVisibilityByWindowId.first(where: { $0.value })?.key { return windowForMainWindowId(visibleWindowId) } + if let pendingWindowId = commandPalettePendingOpenByWindowId.first(where: { $0.value })?.key { + return windowForMainWindowId(pendingWindowId) + } return nil } @@ -5553,6 +5831,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return event // Pass through } self.handleBrowserOmnibarSelectionRepeatLifecycleEvent(event) + if self.clearEscapeSuppressionForKeyUp(event: event, consumeIfSuppressed: true) { + return nil + } return event } } @@ -5831,6 +6112,102 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let commandPaletteVisibleInTargetWindow = commandPaletteShortcutWindow.map { isCommandPaletteVisible(for: $0) } ?? false + let commandPalettePendingOpenInTargetWindow = commandPaletteTargetWindow.map { + isCommandPalettePendingOpen(for: $0) + } ?? false + let commandPaletteOverlayVisibleInTargetWindow = commandPaletteTargetWindow.map { + isCommandPaletteOverlayPresented(in: $0) + } ?? false + let commandPaletteResponderActiveInTargetWindow = commandPaletteTargetWindow.map { + isCommandPaletteResponderActive(in: $0) + } ?? false + let commandPaletteEffectiveInTargetWindow = + commandPaletteVisibleInTargetWindow + || commandPalettePendingOpenInTargetWindow + || commandPaletteOverlayVisibleInTargetWindow + || commandPaletteResponderActiveInTargetWindow + + if normalizedFlags.isEmpty, event.keyCode == 53 { + let activePaletteWindow = activeCommandPaletteWindow() + let escapePaletteWindow: NSWindow? = { + if let targetWindow = commandPaletteTargetWindow { + guard commandPaletteEffectiveInTargetWindow else { + return nil + } + return targetWindow + } + return activePaletteWindow + }() +#if DEBUG + dlog( + "shortcut.escape route target={\(debugWindowToken(commandPaletteTargetWindow))} " + + "active={\(debugWindowToken(activePaletteWindow))} " + + "visibleTarget=\(commandPaletteVisibleInTargetWindow ? 1 : 0) " + + "pendingTarget=\(commandPalettePendingOpenInTargetWindow ? 1 : 0) " + + "overlayTarget=\(commandPaletteOverlayVisibleInTargetWindow ? 1 : 0) " + + "responderTarget=\(commandPaletteResponderActiveInTargetWindow ? 1 : 0) " + + "effectiveTarget=\(commandPaletteEffectiveInTargetWindow ? 1 : 0) " + + "\(debugShortcutRouteSnapshot(event: event))" + ) + if commandPaletteTargetWindow != nil, + !commandPaletteVisibleInTargetWindow, + !commandPalettePendingOpenInTargetWindow, + (commandPaletteOverlayVisibleInTargetWindow || commandPaletteResponderActiveInTargetWindow) { + dlog( + "shortcut.escape stateMismatch target={\(debugWindowToken(commandPaletteTargetWindow))} " + + "overlayTarget=\(commandPaletteOverlayVisibleInTargetWindow ? 1 : 0) " + + "responderTarget=\(commandPaletteResponderActiveInTargetWindow ? 1 : 0)" + ) + } +#endif + if let paletteWindow = escapePaletteWindow, + isCommandPaletteEffectivelyVisible(in: paletteWindow) { + if commandPaletteMarkedTextInput(in: paletteWindow) != nil { +#if DEBUG + dlog( + "shortcut.escape imeMarkedTextBypass consumed=0 target={\(debugWindowToken(paletteWindow))}" + ) +#endif + return false + } + clearCommandPalettePendingOpen(for: paletteWindow) + beginCommandPaletteEscapeSuppression(for: paletteWindow) + NotificationCenter.default.post(name: .commandPaletteToggleRequested, object: paletteWindow) +#if DEBUG + dlog("shortcut.escape paletteDismiss consumed=1 target={\(debugWindowToken(paletteWindow))}") +#endif + return true + } + let suppressionWindow = commandPaletteTargetWindow + ?? event.window + ?? NSApp.keyWindow + ?? NSApp.mainWindow + if shouldConsumeSuppressedEscape(event: event, window: suppressionWindow) { +#if DEBUG + dlog( + "shortcut.escape suppressionConsume consumed=1 target={\(debugWindowToken(suppressionWindow))} " + + "repeat=\(event.isARepeat ? 1 : 0)" + ) +#endif + return true + } + if let requestAge = recentCommandPaletteRequestAge(for: suppressionWindow) { + beginCommandPaletteEscapeSuppression(for: suppressionWindow) +#if DEBUG + dlog( + "shortcut.escape requestGraceConsume consumed=1 target={\(debugWindowToken(suppressionWindow))} " + + "ageMs=\(Int(requestAge * 1000)) repeat=\(event.isARepeat ? 1 : 0)" + ) +#endif + return true + } +#if DEBUG + dlog( + "shortcut.escape paletteDismiss consumed=0 target={\(debugWindowToken(commandPaletteTargetWindow))} " + + "active={\(debugWindowToken(activePaletteWindow))}" + ) +#endif + } if let delta = commandPaletteSelectionDeltaForKeyboardNavigation( flags: event.modifierFlags, @@ -5892,19 +6269,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent && (chars == "p" || event.keyCode == 35) if isCommandP { let targetWindow = commandPaletteTargetWindow ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow - NotificationCenter.default.post(name: .commandPaletteSwitcherRequested, object: targetWindow) + requestCommandPaletteSwitcher(preferredWindow: targetWindow, source: "shortcut.cmdP") return true } let isCommandShiftP = normalizedFlags == [.command, .shift] && (chars == "p" || event.keyCode == 35) if isCommandShiftP { let targetWindow = commandPaletteTargetWindow ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow - NotificationCenter.default.post(name: .commandPaletteRequested, object: targetWindow) + requestCommandPaletteCommands(preferredWindow: targetWindow, source: "shortcut.cmdShiftP") return true } if shouldConsumeShortcutWhileCommandPaletteVisible( - isCommandPaletteVisible: commandPaletteVisibleInTargetWindow, + isCommandPaletteVisible: commandPaletteEffectiveInTargetWindow, normalizedFlags: normalizedFlags, chars: chars, keyCode: event.keyCode @@ -6206,7 +6583,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return false } let targetWindow = commandPaletteTargetWindow ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow - NotificationCenter.default.post(name: .commandPaletteRenameTabRequested, object: targetWindow) + requestCommandPaletteRenameTab(preferredWindow: targetWindow, source: "shortcut.renameTab") return true } @@ -6885,7 +7262,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent @discardableResult func requestRenameWorkspaceViaCommandPalette(preferredWindow: NSWindow? = nil) -> Bool { let targetWindow = preferredWindow ?? NSApp.keyWindow ?? NSApp.mainWindow - NotificationCenter.default.post(name: .commandPaletteRenameWorkspaceRequested, object: targetWindow) + requestCommandPaletteRenameWorkspace( + preferredWindow: targetWindow, + source: "shortcut.renameWorkspace" + ) return true } @@ -6897,6 +7277,27 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent handleCustomShortcut(event: event) } + // Debug/test hook: mirrors local monitor routing (keyDown + keyUp lifecycle). + func debugHandleShortcutMonitorEvent(event: NSEvent) -> Bool { + if event.type == .keyDown { + return handleCustomShortcut(event: event) + } + handleBrowserOmnibarSelectionRepeatLifecycleEvent(event) + return clearEscapeSuppressionForKeyUp(event: event, consumeIfSuppressed: true) + } + + func debugMarkCommandPaletteOpenPending(window: NSWindow) { + markCommandPaletteOpenRequested(for: window) + } + + @discardableResult + func debugSetCommandPalettePendingOpenAge(window: NSWindow, age: TimeInterval) -> Bool { + guard let windowId = mainWindowId(for: window) else { return false } + commandPalettePendingOpenByWindowId[windowId] = true + commandPaletteRecentRequestAtByWindowId[windowId] = ProcessInfo.processInfo.systemUptime - max(age, 0) + return true + } + // Test hook: remap a window context under a detached window key so direct // ObjectIdentifier(window) lookups fail and fallback logic is exercised. @discardableResult @@ -7342,6 +7743,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent persistWindowGeometry(from: window) guard let removed = unregisterMainWindowContext(for: window) else { return } commandPaletteVisibilityByWindowId.removeValue(forKey: removed.windowId) + commandPalettePendingOpenByWindowId.removeValue(forKey: removed.windowId) + commandPaletteRecentRequestAtByWindowId.removeValue(forKey: removed.windowId) + commandPaletteEscapeSuppressionByWindowId.remove(removed.windowId) + commandPaletteEscapeSuppressionStartedAtByWindowId.removeValue(forKey: removed.windowId) commandPaletteSelectionByWindowId.removeValue(forKey: removed.windowId) commandPaletteSnapshotByWindowId.removeValue(forKey: removed.windowId) diff --git a/cmuxTests/AppDelegateShortcutRoutingTests.swift b/cmuxTests/AppDelegateShortcutRoutingTests.swift index 41689662..49dfa9ab 100644 --- a/cmuxTests/AppDelegateShortcutRoutingTests.swift +++ b/cmuxTests/AppDelegateShortcutRoutingTests.swift @@ -329,11 +329,14 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { let workspaceExpectation = expectation(description: "Expected command palette rename workspace notification") var observedWorkspaceWindow: NSWindow? + var didObserveWorkspaceNotification = false let workspaceToken = NotificationCenter.default.addObserver( forName: .commandPaletteRenameWorkspaceRequested, object: nil, queue: nil ) { notification in + guard !didObserveWorkspaceNotification else { return } + didObserveWorkspaceNotification = true observedWorkspaceWindow = notification.object as? NSWindow workspaceExpectation.fulfill() } @@ -370,6 +373,626 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { XCTAssertEqual(observedWorkspaceWindow?.windowNumber, window.windowNumber) } + func testEscapeDismissesVisibleCommandPaletteAndIsConsumed() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + appDelegate.setCommandPaletteVisible(true, for: window) + defer { + appDelegate.setCommandPaletteVisible(false, for: window) + } + + let dismissExpectation = expectation(description: "Expected command palette toggle notification for Escape dismiss") + var observedDismissWindow: NSWindow? + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { notification in + observedDismissWindow = notification.object as? NSWindow + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let event = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, // kVK_Escape + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 1.0) + XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) + } + + func testEscapeDoesNotDismissCommandPaletteWhenInputHasMarkedText() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let fieldEditor = CommandPaletteMarkedTextFieldEditor(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) + fieldEditor.isFieldEditor = true + fieldEditor.hasMarkedTextForTesting = true + window.contentView?.addSubview(fieldEditor) + XCTAssertTrue(window.makeFirstResponder(fieldEditor)) + + appDelegate.setCommandPaletteVisible(true, for: window) + defer { + appDelegate.setCommandPaletteVisible(false, for: window) + fieldEditor.removeFromSuperview() + } + + let dismissExpectation = expectation( + description: "Escape should not dismiss command palette while IME marked text is active" + ) + dismissExpectation.isInverted = true + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { notification in + guard let dismissWindow = notification.object as? NSWindow, + dismissWindow.windowNumber == window.windowNumber else { return } + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertFalse( + appDelegate.debugHandleCustomShortcut(event: escapeEvent), + "Escape should pass through to IME composition instead of dismissing command palette" + ) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 0.2) + } + + func testEscapeDismissesCommandPaletteWhenVisibilitySyncLagsAfterOpenRequest() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let dismissExpectation = expectation(description: "Expected command palette dismiss notification for Escape") + var observedDismissWindow: NSWindow? + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { notification in + observedDismissWindow = notification.object as? NSWindow + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + +#if DEBUG + appDelegate.debugMarkCommandPaletteOpenPending(window: window) +#else + XCTFail("debugMarkCommandPaletteOpenPending is only available in DEBUG") +#endif + + // Simulate a visibility sync lag/race where AppDelegate does not yet know the palette is open. + appDelegate.setCommandPaletteVisible(false, for: window) + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 1.0) + XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) + } + + func testEscapeDismissesCommandPaletteWhenVisibilityStateStaysStalePastInitialPendingWindow() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + +#if DEBUG + XCTAssertTrue( + appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 1.3), + "Expected to backdate pending-open age for stale visibility test" + ) +#else + XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") +#endif + + // Simulate stale app-level visibility bookkeeping. + appDelegate.setCommandPaletteVisible(false, for: window) + + let dismissExpectation = expectation(description: "Escape should dismiss stale-state command palette after delay") + var observedDismissWindow: NSWindow? + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { notification in + observedDismissWindow = notification.object as? NSWindow + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 1.0) + XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) + } + + func testEscapeDismissesCommandPaletteWhenVisibilityStateRemainsStaleForExtendedDelay() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + +#if DEBUG + XCTAssertTrue( + appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 6.25), + "Expected to backdate pending-open age for extended stale visibility test" + ) +#else + XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") +#endif + + // Simulate stale app-level visibility bookkeeping for a longer user delay. + appDelegate.setCommandPaletteVisible(false, for: window) + + let dismissExpectation = expectation(description: "Escape should dismiss stale-state command palette after extended delay") + var observedDismissWindow: NSWindow? + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { notification in + observedDismissWindow = notification.object as? NSWindow + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 1.0) + XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) + } + + func testEscapeDoesNotConsumeWhenMenuTriggeredPendingOpenStateExpires() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + +#if DEBUG + XCTAssertTrue( + appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 20.0), + "Expected to seed an expired pending-open request state" + ) +#else + XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") +#endif + + appDelegate.setCommandPaletteVisible(false, for: window) + + let dismissExpectation = expectation(description: "No dismiss notification for expired pending-open state") + dismissExpectation.isInverted = true + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { _ in + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertFalse( + appDelegate.debugHandleCustomShortcut(event: escapeEvent), + "Escape should pass through once pending-open grace has expired" + ) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 0.2) + } + + func testEscapeDismissesMenuTriggeredCommandPaletteWhenVisibilitySyncIsStale() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + // Reproduce the menu-command path (Cmd+Shift+P/Cmd+P) routed via AppDelegate. + appDelegate.requestCommandPaletteCommands( + preferredWindow: window, + source: "test.menuCommandPalette" + ) + // Simulate delayed/stale visibility sync from SwiftUI overlay state. + appDelegate.setCommandPaletteVisible(false, for: window) +#if DEBUG + XCTAssertTrue( + appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 0.1), + "Expected deterministic pending-open state for menu-triggered stale-visibility path" + ) +#else + XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") +#endif + + let dismissExpectation = expectation(description: "Expected command palette dismiss notification for menu-triggered stale visibility") + var observedDismissWindow: NSWindow? + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { notification in + observedDismissWindow = notification.object as? NSWindow + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertTrue( + appDelegate.debugHandleCustomShortcut(event: escapeEvent), + "Escape should still be consumed for menu-triggered command palette opens" + ) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 1.0) + XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) + } + + func testEscapeRepeatIsConsumedImmediatelyAfterPaletteDismiss() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + appDelegate.setCommandPaletteVisible(true, for: window) + defer { + appDelegate.setCommandPaletteVisible(false, for: window) + } + + guard let firstEscape = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct first Escape event") + return + } + + guard let repeatedEscape = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber, + isARepeat: true + ) else { + XCTFail("Failed to construct repeated Escape event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: firstEscape)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + // Simulate the palette overlay synchronizing to closed state while the Escape key is still held. + appDelegate.setCommandPaletteVisible(false, for: window) + +#if DEBUG + XCTAssertTrue( + appDelegate.debugHandleCustomShortcut(event: repeatedEscape), + "Repeated Escape immediately after dismiss should be consumed to prevent terminal passthrough" + ) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + + func testEscapeKeyUpIsConsumedAfterPaletteDismissToPreventTerminalLeak() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + appDelegate.setCommandPaletteVisible(true, for: window) + defer { + appDelegate.setCommandPaletteVisible(false, for: window) + } + + guard let escapeKeyDown = makeKeyEvent( + type: .keyDown, + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape keyDown event") + return + } + + guard let escapeKeyUp = makeKeyEvent( + type: .keyUp, + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape keyUp event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyDown)) +#else + XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG") +#endif + + // Simulate the palette overlay synchronizing to closed state before Escape key-up arrives. + appDelegate.setCommandPaletteVisible(false, for: window) + +#if DEBUG + XCTAssertTrue( + appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyUp), + "Escape keyUp after palette dismiss should be consumed to prevent terminal passthrough" + ) +#else + XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG") +#endif + } + + func testEscapeKeyUpIsConsumedAfterCmdPSwitcherDismiss() { + assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest { appDelegate, window in + appDelegate.requestCommandPaletteSwitcher( + preferredWindow: window, + source: "test.cmdP" + ) + } + } + + func testEscapeKeyUpIsConsumedAfterCmdShiftPCommandsDismiss() { + assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest { appDelegate, window in + appDelegate.requestCommandPaletteCommands( + preferredWindow: window, + source: "test.cmdShiftP" + ) + } + } + + func testEscapeDoesNotDismissPaletteInDifferentWindow() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let paletteWindowId = appDelegate.createMainWindow() + let eventWindowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: paletteWindowId) + closeWindow(withId: eventWindowId) + } + + guard let paletteWindow = window(withId: paletteWindowId), + let eventWindow = window(withId: eventWindowId) else { + XCTFail("Expected both test windows") + return + } + + appDelegate.setCommandPaletteVisible(true, for: paletteWindow) + defer { + appDelegate.setCommandPaletteVisible(false, for: paletteWindow) + } + + let dismissExpectation = expectation(description: "Escape in another window should not dismiss palette") + dismissExpectation.isInverted = true + let dismissToken = NotificationCenter.default.addObserver( + forName: .commandPaletteToggleRequested, + object: nil, + queue: nil + ) { _ in + dismissExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(dismissToken) } + + guard let escapeEvent = makeKeyDownEvent( + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: eventWindow.windowNumber + ) else { + XCTFail("Failed to construct Escape event") + return + } + +#if DEBUG + XCTAssertFalse( + appDelegate.debugHandleCustomShortcut(event: escapeEvent), + "Escape should remain scoped to the event window" + ) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [dismissExpectation], timeout: 0.2) + } + func testCmdDigitDoesNotFallbackToOtherWindowWhenEventWindowContextIsMissing() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") @@ -552,10 +1175,29 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { key: String, modifiers: NSEvent.ModifierFlags, keyCode: UInt16, - windowNumber: Int + windowNumber: Int, + isARepeat: Bool = false + ) -> NSEvent? { + makeKeyEvent( + type: .keyDown, + key: key, + modifiers: modifiers, + keyCode: keyCode, + windowNumber: windowNumber, + isARepeat: isARepeat + ) + } + + private func makeKeyEvent( + type: NSEvent.EventType, + key: String, + modifiers: NSEvent.ModifierFlags, + keyCode: UInt16, + windowNumber: Int, + isARepeat: Bool = false ) -> NSEvent? { NSEvent.keyEvent( - with: .keyDown, + with: type, location: .zero, modifierFlags: modifiers, timestamp: ProcessInfo.processInfo.systemUptime, @@ -563,11 +1205,71 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { context: nil, characters: key, charactersIgnoringModifiers: key, - isARepeat: false, + isARepeat: isARepeat, keyCode: keyCode ) } + private func assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest( + _ openRequest: (_ appDelegate: AppDelegate, _ window: NSWindow) -> Void, + file: StaticString = #filePath, + line: UInt = #line + ) { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared", file: file, line: line) + return + } + + let windowId = appDelegate.createMainWindow() + defer { + closeWindow(withId: windowId) + } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window", file: file, line: line) + return + } + + openRequest(appDelegate, window) + appDelegate.setCommandPaletteVisible(true, for: window) + + guard let escapeKeyDown = makeKeyEvent( + type: .keyDown, + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ), let escapeKeyUp = makeKeyEvent( + type: .keyUp, + key: "\u{1b}", + modifiers: [], + keyCode: 53, + windowNumber: window.windowNumber + ) else { + XCTFail("Failed to construct Escape key events", file: file, line: line) + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyDown), file: file, line: line) +#else + XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG", file: file, line: line) +#endif + + appDelegate.setCommandPaletteVisible(false, for: window) + +#if DEBUG + XCTAssertTrue( + appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyUp), + "Escape keyUp should be consumed after dismiss for command palette open requests", + file: file, + line: line + ) +#else + XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG", file: file, line: line) +#endif + } + private func window(withId windowId: UUID) -> NSWindow? { let identifier = "cmux.main.\(windowId.uuidString)" return NSApp.windows.first(where: { $0.identifier?.rawValue == identifier }) @@ -579,3 +1281,11 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) } } + +private final class CommandPaletteMarkedTextFieldEditor: NSTextView { + var hasMarkedTextForTesting = false + + override func hasMarkedText() -> Bool { + hasMarkedTextForTesting + } +} diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 2d7609b5..64bbf44a 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -3120,6 +3120,17 @@ final class CommandPaletteOpenShortcutConsumptionTests: XCTestCase { ) ) } + + func testConsumesEscapeWhenPaletteIsVisible() { + XCTAssertTrue( + shouldConsumeShortcutWhileCommandPaletteVisible( + isCommandPaletteVisible: true, + normalizedFlags: [], + chars: "", + keyCode: 53 + ) + ) + } } final class CommandPaletteRestoreFocusStateMachineTests: XCTestCase { From a4bf2214feea369cb948408c5343e3d43c719483 Mon Sep 17 00:00:00 2001 From: Yoshiki Agatsuma Date: Thu, 5 Mar 2026 07:32:52 +0900 Subject: [PATCH 064/232] Fix browser address bar Japanese IME input (#789) (#867) * Fix browser address bar Japanese IME input (#789) * Remove redundant comments --- Sources/Panels/BrowserPanelView.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 1b46904c..a08e17a3 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -2294,6 +2294,10 @@ private final class OmnibarNativeTextField: NSTextField { } override func keyDown(with event: NSEvent) { + if (currentEditor() as? NSTextView)?.hasMarkedText() == true { + super.keyDown(with: event) + return + } if onHandleKeyEvent?(event, currentEditor() as? NSTextView) == true { return } @@ -2301,6 +2305,9 @@ private final class OmnibarNativeTextField: NSTextField { } override func performKeyEquivalent(with event: NSEvent) -> Bool { + if (currentEditor() as? NSTextView)?.hasMarkedText() == true { + return super.performKeyEquivalent(with: event) + } if onHandleKeyEvent?(event, currentEditor() as? NSTextView) == true { return true } @@ -2615,7 +2622,7 @@ private struct OmnibarTextFieldRepresentable: NSViewRepresentable { ) let desiredDisplayText = activeInlineCompletion?.displayText ?? text if let editor = nsView.currentEditor() as? NSTextView { - if editor.string != desiredDisplayText { + if !editor.hasMarkedText(), editor.string != desiredDisplayText { context.coordinator.isProgrammaticMutation = true editor.string = desiredDisplayText nsView.stringValue = desiredDisplayText @@ -2659,7 +2666,7 @@ private struct OmnibarTextFieldRepresentable: NSViewRepresentable { } } - if let editor = nsView.currentEditor() as? NSTextView { + if let editor = nsView.currentEditor() as? NSTextView, !editor.hasMarkedText() { if let activeInlineCompletion { let currentSelection = editor.selectedRange() let desiredSelection = omnibarDesiredSelectionRangeForInlineCompletion( From 422c86e8229563584c759a4f1e0a9a4f774894bf Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:41:04 -0800 Subject: [PATCH 065/232] Fix notification sound picker using wrong style in Settings (#885) The notification sound picker was missing .pickerStyle(.menu) and controlWidth, causing it to render as an expanded picker with a large empty area instead of a compact dropdown menu. Apply the same pattern used by all other settings pickers. --- Sources/cmuxApp.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 6c77ea29..8ef4d5ab 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2957,7 +2957,8 @@ struct SettingsView: View { SettingsCardRow( "Notification Sound", - subtitle: "Sound played when a notification arrives." + subtitle: "Sound played when a notification arrives.", + controlWidth: pickerColumnWidth ) { HStack(spacing: 6) { Picker("", selection: $notificationSound) { @@ -2966,6 +2967,7 @@ struct SettingsView: View { } } .labelsHidden() + .pickerStyle(.menu) Button { NotificationSoundSettings.previewSound(value: notificationSound) } label: { From 2c330efb8aa99f805dc676bd870bf81fd9ecd2cb Mon Sep 17 00:00:00 2001 From: atani Date: Thu, 5 Mar 2026 07:58:28 +0900 Subject: [PATCH 066/232] feat: add Japanese localization with String Catalog (#819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add i18n infrastructure with String Catalog and Japanese translations Introduce String Catalog (.xcstrings) for localization support: - Localizable.xcstrings: 195 UI string entries with en and ja translations - InfoPlist.xcstrings: Info.plist strings (microphone usage, Finder menu items) - project.pbxproj: add xcstrings to build phase and ja to knownRegions * Replace hardcoded UI strings with String(localized:defaultValue:) Migrate all user-facing strings across 11 source files to use String(localized:defaultValue:) API (macOS 13+). Each string references a key in Localizable.xcstrings, with the English text preserved as defaultValue for fallback. Files modified: - KeyboardShortcutSettings: 28 shortcut labels - SocketControlSettings: mode names and descriptions - TabManager: placement labels, color names, close dialogs - BrowserPanel/BrowserPanelView: error pages, context menus, tooltips - UpdateViewModel/UpdatePopoverView/UpdatePill: update UI states - NotificationsPage: notification panel labels - SurfaceSearchOverlay: search bar placeholder and tooltips - AppDelegate: menus, dialogs, command palette items * Fix localization gaps from review feedback Address review comments from CodeRabbit, Greptile, and Cubic Dev AI: - Use interpolated String(localized:) instead of concatenation for version/progress strings in UpdateViewModel - Localize remaining hardcoded strings in AppDelegate: window labels, rename dialog, status menu items, unread notification count - Localize insecure HTTP alert body in BrowserPanel - Add 12 new entries to Localizable.xcstrings with Japanese translations * Fix String(localized:defaultValue:) keys to use StaticString The localized: parameter requires StaticString when defaultValue: is used. Move string interpolation from the key to defaultValue only, and revert maxWidthText to plain strings since they are only used for layout width calculation. * Localize remaining UI strings across all source files Add String(localized:defaultValue:) to all user-facing strings in: - cmuxApp.swift: settings screen, menus, about panel, dialogs (~180 strings) - ContentView.swift: command palette, sidebar context menu, dialogs (~200 strings) - Workspace.swift: rename/move/close tab dialogs, tooltips (~20 strings) - UpdateTitlebarAccessory.swift: titlebar tooltips, notifications popover (~10 strings) - TerminalNotificationStore.swift: notification permission dialog (4 strings) - CmuxWebView.swift: browser context menu items (2 strings) - AppDelegate.swift: CLI install/uninstall alerts (6 strings) Add 418 new entries to Localizable.xcstrings with Japanese translations. Extract sidebar context menu into separate @ViewBuilder to fix Swift type-checker timeout in large body. Fix xcstrings format specifiers for interpolated strings (%lld, %@). Total: 624 localization entries covering the full UI. * Address review feedback: fix missing localizations and terminology - Localize javaScriptDialogTitle URL branch in BrowserPanel - Localize cantReach error message in BrowserPanel - Localize close other tabs dialog message in TabManager - Localize workspace accessibility label in ContentView - Fix unread notification singular/plural (split into two keys) - Fix insecure connection apostrophe inconsistency (unify to U+2019) - Rename socketControl.fullOpen.description to socketControl.allowAll.description - Remove dead code: renameTargetNoun function - Fix terminology inconsistencies in xcstrings: - Unify "Developer Tools" to デベロッパツール - Unify "Jump to Latest Unread" phrasing - Unify "Flash Focused Panel" terminology - Fix dialog.enableNotifications.notNow translation * fix: address remaining PR 819 review feedback * fix: use a single localized key for close-other-tabs * fix: avoid inflection markup in close-other-tabs message * Address review feedback: localize tooltip, fix subtitle concat, unify keys - Localize menubar tooltip unread count (hardcoded English -> localized) - Replace subtitle string concatenation anti-pattern with single localized keys containing interpolation placeholders - Unify workspace fallback key to workspace.displayName.fallback - Remove unused workspace.defaultName key from xcstrings - Add Japanese translations for new tooltip and subtitle keys --- GhosttyTabs.xcodeproj/project.pbxproj | 11 +- Resources/InfoPlist.xcstrings | 57 + Resources/Localizable.xcstrings | 10733 +++++++++++++++++ Sources/AppDelegate.swift | 110 +- Sources/ContentView.swift | 684 +- Sources/Find/SurfaceSearchOverlay.swift | 8 +- Sources/KeyboardShortcutSettings.swift | 60 +- Sources/NotificationsPage.swift | 16 +- Sources/Panels/BrowserPanel.swift | 79 +- Sources/Panels/BrowserPanelView.swift | 18 +- Sources/Panels/CmuxWebView.swift | 6 +- Sources/SocketControlSettings.swift | 22 +- Sources/TabManager.swift | 48 +- Sources/TerminalNotificationStore.swift | 8 +- Sources/Update/UpdatePill.swift | 2 +- Sources/Update/UpdatePopoverView.swift | 54 +- Sources/Update/UpdateTitlebarAccessory.swift | 20 +- Sources/Update/UpdateViewModel.swift | 90 +- Sources/Workspace.swift | 44 +- Sources/cmuxApp.swift | 362 +- 20 files changed, 11643 insertions(+), 789 deletions(-) create mode 100644 Resources/InfoPlist.xcstrings create mode 100644 Resources/Localizable.xcstrings diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index aa0221e6..532ab71b 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -84,6 +84,8 @@ F6000000A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */; }; F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */; }; F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */; }; + DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000001 /* Localizable.xcstrings */; }; + DA7A10CA710E000000000004 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000002 /* InfoPlist.xcstrings */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -215,7 +217,9 @@ F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateShortcutRoutingTests.swift; sourceTree = ""; }; F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentViewVisibilityTests.swift; sourceTree = ""; }; F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlPasswordStoreTests.swift; sourceTree = ""; }; - /* End PBXFileReference section */ + DA7A10CA710E000000000001 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + DA7A10CA710E000000000002 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; +/* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ A5001030 /* Frameworks */ = { @@ -261,6 +265,8 @@ A5001100 /* Assets.xcassets in Resources */, 84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */, A5002000 /* THIRD_PARTY_LICENSES.md in Resources */, + DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */, + DA7A10CA710E000000000004 /* InfoPlist.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -385,6 +391,8 @@ B2E7294509CC42FE9191870E /* xterm-ghostty */, A5002001 /* THIRD_PARTY_LICENSES.md */, C1ADE00001A1B2C3D4E5F719 /* claude */, + DA7A10CA710E000000000001 /* Localizable.xcstrings */, + DA7A10CA710E000000000002 /* InfoPlist.xcstrings */, ); path = Resources; sourceTree = ""; @@ -534,6 +542,7 @@ knownRegions = ( en, Base, + ja, ); mainGroup = A5001040; packageReferences = ( diff --git a/Resources/InfoPlist.xcstrings b/Resources/InfoPlist.xcstrings new file mode 100644 index 00000000..fa672ecd --- /dev/null +++ b/Resources/InfoPlist.xcstrings @@ -0,0 +1,57 @@ +{ + "sourceLanguage" : "en", + "version" : "1.0", + "strings" : { + "NSMicrophoneUsageDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "A program running within cmux would like to use your microphone." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "cmux 内で実行中のプログラムがマイクの使用を求めています。" + } + } + } + }, + "New $(PRODUCT_NAME) Workspace Here" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New $(PRODUCT_NAME) Workspace Here" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ここに新規 $(PRODUCT_NAME) ワークスペースを作成" + } + } + } + }, + "New $(PRODUCT_NAME) Window Here" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New $(PRODUCT_NAME) Window Here" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ここに新規 $(PRODUCT_NAME) ウインドウを作成" + } + } + } + } + } +} diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings new file mode 100644 index 00000000..9ee38bc3 --- /dev/null +++ b/Resources/Localizable.xcstrings @@ -0,0 +1,10733 @@ +{ + "sourceLanguage": "en", + "version": "1.0", + "strings": { + "about.appName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + } + } + }, + "about.build": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + } + } + }, + "about.commit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + } + } + }, + "about.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A Ghostty-based terminal with vertical tabs\\nand a notification panel for macOS." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Ghosttyベースの縦タブ付きターミナルと\nmacOS用通知パネル。" + } + } + } + }, + "about.docs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Docs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ドキュメント" + } + } + } + }, + "about.github": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + } + } + }, + "about.licenses": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Licenses" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライセンス" + } + } + } + }, + "about.licenses.notFound": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Licenses file not found." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライセンスファイルが見つかりません。" + } + } + } + }, + "about.licenses.windowTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Third-Party Licenses" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サードパーティライセンス" + } + } + } + }, + "about.version": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + } + } + }, + "accessibility.workspacePosition": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%1$@, workspace %2$lld of %3$lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%1$@、ワークスペース %3$lld中%2$lld" + } + } + } + }, + "alert.customColor.apply": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apply" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "適用" + } + } + } + }, + "alert.customColor.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "alert.customColor.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a hex color in the format #RRGGBB." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB形式で16進カラーコードを入力してください。" + } + } + } + }, + "alert.customColor.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom Workspace Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムワークスペースカラー" + } + } + } + }, + "alert.invalidColor.emptyMessage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a hex color in the format #RRGGBB." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB形式で16進カラーコードを入力してください。" + } + } + } + }, + "alert.invalidColor.invalidMessage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" is not a valid hex color. Use #RRGGBB." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "「%@」は有効な16進カラーではありません。#RRGGBB形式で入力してください。" + } + } + } + }, + "alert.invalidColor.ok": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "alert.invalidColor.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "無効なカラー" + } + } + } + }, + "alert.renameWorkspace.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "alert.renameWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a custom name for this workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このワークスペースのカスタム名を入力してください。" + } + } + } + }, + "alert.renameWorkspace.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名" + } + } + } + }, + "alert.renameWorkspace.rename": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名称変更" + } + } + } + }, + "alert.renameWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更" + } + } + } + }, + "appIcon.automatic": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatic" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動" + } + } + } + }, + "appIcon.dark": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダーク" + } + } + } + }, + "appIcon.light": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライト" + } + } + } + }, + "appearance.auto": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動" + } + } + } + }, + "appearance.dark": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダーク" + } + } + } + }, + "appearance.light": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライト" + } + } + } + }, + "appearance.system": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システム" + } + } + } + }, + "browser.action.newTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "new tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ" + } + } + } + }, + "browser.addressBar.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search or enter URL" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索またはURLを入力" + } + } + } + }, + "browser.addressBarSuggestions": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Address bar suggestions" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アドレスバーの候補" + } + } + } + }, + "browser.alwaysAllowHost": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Always allow this host in cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このホストを cmux で常に許可" + } + } + } + }, + "browser.contextMenu.openLinkInDefaultBrowser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Link in Default Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デフォルトブラウザでリンクを開く" + } + } + } + }, + "browser.contextMenu.openLinkInNewTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Link in New Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブでリンクを開く" + } + } + } + }, + "browser.dialog.pageSays": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This page says:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このページの内容:" + } + } + } + }, + "browser.dialog.pageSaysAt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The page at %@ says:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ページ %@ のメッセージ:" + } + } + } + }, + "browser.downloadInProgress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Download in progress" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中" + } + } + } + }, + "browser.downloading": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading..." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中..." + } + } + } + }, + "browser.error.cantOpen.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Can't open this page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このページを開けません" + } + } + } + }, + "browser.error.cantReach.messageSite": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The site refused to connect. Check that a server is running on this address." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイトに接続できませんでした。このアドレスでサーバーが実行されていることを確認してください。" + } + } + } + }, + "browser.error.cantReach.messageURL": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ refused to connect. Check that a server is running on this address." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@ に接続できませんでした。このアドレスでサーバーが実行されていることを確認してください。" + } + } + } + }, + "browser.error.cantReach.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Can't reach this page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このページに到達できません" + } + } + } + }, + "browser.error.checkNetwork": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check your network connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ネットワーク接続を確認してもう一度お試しください。" + } + } + } + }, + "browser.error.frameLoadInterrupted": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Frame load interrupted" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フレームの読み込みが中断されました" + } + } + } + }, + "browser.error.insecure.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ uses plain HTTP, so traffic can be read or modified on the network.\n\nOpen this URL in your default browser, or proceed in cmux." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@ は HTTP 接続を使用しているため、通信内容がネットワーク上で読み取られたり改ざんされる可能性があります。\n\nデフォルトブラウザで開くか、cmux で続行してください。" + } + } + } + }, + "browser.error.insecure.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection isn't secure" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "接続は安全ではありません" + } + } + } + }, + "browser.error.invalidCertificate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The certificate for this site is invalid." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このサイトの証明書が無効です。" + } + } + } + }, + "browser.error.noInternet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No internet connection" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インターネット接続がありません" + } + } + } + }, + "browser.error.reload": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再読み込み" + } + } + } + }, + "browser.goBack": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go Back" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "戻る" + } + } + } + }, + "browser.goForward": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go Forward" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "進む" + } + } + } + }, + "browser.goToURL": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "go to URL" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "URLに移動" + } + } + } + }, + "browser.newTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ" + } + } + } + }, + "browser.openInDefaultBrowser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open in Default Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デフォルトブラウザで開く" + } + } + } + }, + "browser.proceedInCmux": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Proceed in cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux で続行" + } + } + } + }, + "browser.reload": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再読み込み" + } + } + } + }, + "browser.search.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索" + } + } + } + }, + "browser.stop": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "停止" + } + } + } + }, + "browser.switchToTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Switch to tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブに切替" + } + } + } + }, + "browser.toggleDevTools": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デベロッパツールを切替" + } + } + } + }, + "cli.install.adminRequired": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Administrator privileges were required to write to /usr/local/bin." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/binへの書き込みに管理者権限が必要でした。" + } + } + } + }, + "cli.install.symlinkCreated": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Created symlink:\n\n%1$@ -> %2$@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シンボリックリンクを作成しました:\n\n%1$@ -> %2$@" + } + } + } + }, + "cli.installFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't Install cmux CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI をインストールできませんでした" + } + } + } + }, + "cli.installed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Installed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI がインストールされました" + } + } + } + }, + "cli.uninstall.adminRequired": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Administrator privileges were required to modify /usr/local/bin." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/binの変更に管理者権限が必要でした。" + } + } + } + }, + "cli.uninstall.notFound": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No cmux CLI symlink was found at %@." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@にcmux CLIのシンボリックリンクが見つかりませんでした。" + } + } + } + }, + "cli.uninstall.removed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removed %@." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@を削除しました。" + } + } + } + }, + "cli.uninstallFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't Uninstall cmux CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI をアンインストールできませんでした" + } + } + } + }, + "cli.uninstalled": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Uninstalled" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI がアンインストールされました" + } + } + } + }, + "command.applyUpdateIfAvailable.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.applyUpdateIfAvailable.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apply Update (If Available)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを適用(利用可能な場合)" + } + } + } + }, + "command.attemptUpdate.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.attemptUpdate.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Attempt Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを試行" + } + } + } + }, + "command.browserBack.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Back" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "戻る" + } + } + } + }, + "command.browserClearHistory.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ" + } + } + } + }, + "command.browserClearHistory.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Browser History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴をクリア" + } + } + } + }, + "command.browserConsole.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show JavaScript Console" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "JavaScriptコンソールを表示" + } + } + } + }, + "command.browserDuplicateRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザレイアウト" + } + } + } + }, + "command.browserDuplicateRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Duplicate Browser to the Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に複製" + } + } + } + }, + "command.browserFocusAddressBar.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Address Bar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アドレスバーにフォーカス" + } + } + } + }, + "command.browserForward.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Forward" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "進む" + } + } + } + }, + "command.browserOpenDefault.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Page in Default Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のページをデフォルトブラウザで開く" + } + } + } + }, + "command.browserReload.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ページを再読み込み" + } + } + } + }, + "command.browserSplitDown.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザレイアウト" + } + } + } + }, + "command.browserSplitDown.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "command.browserSplitRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザレイアウト" + } + } + } + }, + "command.browserSplitRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "command.browserToggleDevTools.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デベロッパツールの切り替え" + } + } + } + }, + "command.browserZoomIn.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom In" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "拡大" + } + } + } + }, + "command.browserZoomOut.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom Out" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縮小" + } + } + } + }, + "command.browserZoomReset.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actual Size" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "実際のサイズ" + } + } + } + }, + "command.checkForUpdates.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.checkForUpdates.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認" + } + } + } + }, + "command.clearTabName.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Tab Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名をクリア" + } + } + } + }, + "command.clearWorkspaceName.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Workspace Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名をクリア" + } + } + } + }, + "command.closeTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "command.closeTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じる" + } + } + } + }, + "command.closeWindow.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ" + } + } + } + }, + "command.closeWindow.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウを閉じる" + } + } + } + }, + "command.closeWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "command.closeWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "command.equalizeSplits.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Equalize Splits" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "分割を均等にする" + } + } + } + }, + "command.installCLI.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + } + } + }, + "command.installCLI.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shell Command: Install 'cmux' in PATH" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シェルコマンド: 'cmux'をPATHにインストール" + } + } + } + }, + "command.jumpUnread.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "command.jumpUnread.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "command.markTabRead.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Tab as Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを既読にする" + } + } + } + }, + "command.markTabUnread.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Tab as Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを未読にする" + } + } + } + }, + "command.newBrowserTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "command.newBrowserTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Tab (Browser)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ(ブラウザ)" + } + } + } + }, + "command.newTerminalTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "command.newTerminalTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Tab (Terminal)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ(ターミナル)" + } + } + } + }, + "command.newWindow.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ" + } + } + } + }, + "command.newWindow.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "command.newWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "command.newWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "command.nextTabInPane.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブナビゲーション" + } + } + } + }, + "command.nextTabInPane.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Tab in Pane" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペイン内の次のタブ" + } + } + } + }, + "command.nextWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースナビゲーション" + } + } + } + }, + "command.nextWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のワークスペース" + } + } + } + }, + "command.openFolder.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "command.openFolder.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く…" + } + } + } + }, + "command.openSettings.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.openSettings.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定を開く" + } + } + } + }, + "command.openWorkspacePRLinks.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open All Workspace PR Links" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのPRリンクをすべて開く" + } + } + } + }, + "command.pinTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブをピンで固定" + } + } + } + }, + "command.pinWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをピンで固定" + } + } + } + }, + "command.previousTabInPane.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブナビゲーション" + } + } + } + }, + "command.previousTabInPane.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Tab in Pane" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペイン内の前のタブ" + } + } + } + }, + "command.previousWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースナビゲーション" + } + } + } + }, + "command.previousWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のワークスペース" + } + } + } + }, + "command.renameTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの名称変更…" + } + } + } + }, + "command.renameWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更…" + } + } + } + }, + "command.reopenClosedBrowserTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ" + } + } + } + }, + "command.reopenClosedBrowserTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reopen Closed Browser Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じたブラウザタブを再度開く" + } + } + } + }, + "command.restartSocketListener.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.restartSocketListener.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart CLI Listener" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CLIリスナーを再起動" + } + } + } + }, + "command.showNotifications.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "command.showNotifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "command.terminalFind.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索…" + } + } + } + }, + "command.terminalFindNext.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Next" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次を検索" + } + } + } + }, + "command.terminalFindPrevious.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Previous" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前を検索" + } + } + } + }, + "command.terminalHideFind.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide Find Bar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索バーを非表示" + } + } + } + }, + "command.terminalSplitBrowserDown.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitBrowserDown.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "command.terminalSplitBrowserRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitBrowserRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "command.terminalSplitDown.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitDown.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "command.terminalSplitRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + }, + "command.terminalUseSelectionForFind.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Selection for Find" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択範囲を検索に使用" + } + } + } + }, + "command.toggleFullScreen.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ" + } + } + } + }, + "command.toggleFullScreen.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Full Screen" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルスクリーンの切り替え" + } + } + } + }, + "command.toggleSidebar.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "レイアウト" + } + } + } + }, + "command.toggleSidebar.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの切り替え" + } + } + } + }, + "command.toggleSplitZoom.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.toggleSplitZoom.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Pane Zoom" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペインズームの切り替え" + } + } + } + }, + "command.triggerFlash.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "表示" + } + } + } + }, + "command.triggerFlash.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash Focused Panel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォーカスペインを強調" + } + } + } + }, + "command.uninstallCLI.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + } + } + }, + "command.uninstallCLI.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shell Command: Uninstall 'cmux' from PATH" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シェルコマンド: 'cmux'をPATHからアンインストール" + } + } + } + }, + "command.unpinTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブのピンを外す" + } + } + } + }, + "command.unpinWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのピンを外す" + } + } + } + }, + "command.vscodeServeWebRestart.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart VS Code Inline Server" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "VS Codeインラインサーバーを再起動" + } + } + } + }, + "command.vscodeServeWebStop.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop VS Code Inline Server" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "VS Codeインラインサーバーを停止" + } + } + } + }, + "commandPalette.kind.workspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "commandPalette.rename.clearCustomName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "(clear custom name)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "(カスタム名をクリア)" + } + } + } + }, + "commandPalette.rename.tabConfirmHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Press Enter to apply this tab name, or Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Enterキーでタブ名を適用、Escapeキーでキャンセルします。" + } + } + } + }, + "commandPalette.rename.tabDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a custom tab name." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブのカスタム名を選択してください。" + } + } + } + }, + "commandPalette.rename.tabInputHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a tab name. Press Enter to rename, Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名を入力してください。Enterで名称変更、Escapeでキャンセルします。" + } + } + } + }, + "commandPalette.rename.tabPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名" + } + } + } + }, + "commandPalette.rename.tabTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの名称変更" + } + } + } + }, + "commandPalette.rename.workspaceConfirmHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Press Enter to apply this workspace name, or Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Enterキーでワークスペース名を適用、Escapeキーでキャンセルします。" + } + } + } + }, + "commandPalette.rename.workspaceDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a custom workspace name." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのカスタム名を選択してください。" + } + } + } + }, + "commandPalette.rename.workspaceInputHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a workspace name. Press Enter to rename, Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名を入力してください。Enterで名称変更、Escapeでキャンセルします。" + } + } + } + }, + "commandPalette.rename.workspacePlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名" + } + } + } + }, + "commandPalette.rename.workspaceTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更" + } + } + } + }, + "commandPalette.search.commandsEmpty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No commands match your search." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索に一致するコマンドがありません。" + } + } + } + }, + "commandPalette.search.commandsPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a command" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドを入力" + } + } + } + }, + "commandPalette.search.switcherEmpty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No workspaces match your search." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索に一致するワークスペースがありません。" + } + } + } + }, + "commandPalette.search.switcherPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを検索" + } + } + } + }, + "commandPalette.subtitle.browserWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ • %@" + } + } + } + }, + "commandPalette.subtitle.tabWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ • %@" + } + } + } + }, + "commandPalette.subtitle.tabFallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "commandPalette.subtitle.terminalWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナル • %@" + } + } + } + }, + "commandPalette.subtitle.workspaceWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース • %@" + } + } + } + }, + "commandPalette.subtitle.workspaceFallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "commandPalette.switcher.windowLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window %lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ %lld" + } + } + } + }, + "commandPalette.switcher.workspaceLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "common.allow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Allow" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "許可" + } + } + } + }, + "common.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "common.close": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じる" + } + } + } + }, + "common.copyDetails": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy Details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細をコピー" + } + } + } + }, + "common.dontSave": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Don't Save" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "保存しない" + } + } + } + }, + "common.installAndRelaunch": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install and Relaunch" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インストールして再起動" + } + } + } + }, + "common.later": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Later" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "後で" + } + } + } + }, + "common.notNow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今はしない" + } + } + } + }, + "common.ok": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "common.rename": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名前を変更" + } + } + } + }, + "common.restartLater": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Later" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "後で再起動" + } + } + } + }, + "common.restartNow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今すぐ再起動" + } + } + } + }, + "common.retry": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再試行" + } + } + } + }, + "common.skip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "スキップ" + } + } + } + }, + "contextMenu.chooseCustomColor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose Custom Color…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムカラーを選択…" + } + } + } + }, + "contextMenu.clearColor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カラーをクリア" + } + } + } + }, + "contextMenu.closeOtherWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Other Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "他のワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspacesAbove": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspaces Above" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上のワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspacesBelow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspaces Below" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下のワークスペースを閉じる" + } + } + } + }, + "contextMenu.markWorkspaceRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspace as Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを既読にする" + } + } + } + }, + "contextMenu.markWorkspaceUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspace as Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを未読にする" + } + } + } + }, + "contextMenu.markWorkspacesRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspaces as Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを既読にする" + } + } + } + }, + "contextMenu.markWorkspacesUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspaces as Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを未読にする" + } + } + } + }, + "contextMenu.moveDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に移動" + } + } + } + }, + "contextMenu.moveToTop": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move to Top" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "一番上に移動" + } + } + } + }, + "contextMenu.moveUp": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上に移動" + } + } + } + }, + "contextMenu.moveWorkspaceToWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Workspace to Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをウインドウに移動" + } + } + } + }, + "contextMenu.moveWorkspacesToWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Workspaces to Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをウインドウに移動" + } + } + } + }, + "contextMenu.newWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "contextMenu.pinWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをピンで固定" + } + } + } + }, + "contextMenu.pinWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをピンで固定" + } + } + } + }, + "contextMenu.removeCustomWorkspaceName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove Custom Workspace Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムワークスペース名を削除" + } + } + } + }, + "contextMenu.renameWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更…" + } + } + } + }, + "contextMenu.unpinWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのピンを外す" + } + } + } + }, + "contextMenu.unpinWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのピンを外す" + } + } + } + }, + "contextMenu.workspaceColor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースカラー" + } + } + } + }, + "dialog.closeLastTabWindow.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the last tab and close the window." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最後のタブを閉じ、ウインドウを閉じます。" + } + } + } + }, + "dialog.closeLastTabWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the last tab and close its workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最後のタブを閉じ、ワークスペースを閉じます。" + } + } + } + }, + "dialog.closeOtherTabs.message.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close 1 tab in this pane:\n%@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このペインの 1 個のタブを閉じます:\n%@" + } + } + } + }, + "dialog.closeOtherTabs.message.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close %1$lld tabs in this pane:\n%2$@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このペインの %1$lld 個のタブを閉じます:\n%2$@" + } + } + } + }, + "dialog.closeOtherTabs.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close other tabs?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "他のタブを閉じますか?" + } + } + } + }, + "dialog.closeTab.close": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じる" + } + } + } + }, + "dialog.closeTab.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the current tab." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のタブを閉じます。" + } + } + } + }, + "dialog.closeTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close tab?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じますか?" + } + } + } + }, + "dialog.closeWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the workspace and all of its panels." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースとそのすべてのパネルを閉じます。" + } + } + } + }, + "dialog.closeWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close workspace?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じますか?" + } + } + } + }, + "dialog.dontWarnCmdQ": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Don't warn again for Cmd+Q" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q の警告を表示しない" + } + } + } + }, + "dialog.enableNotifications.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications are disabled for cmux. Enable them in System Settings to see alerts." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxの通知が無効になっています。アラートを表示するにはシステム設定で有効にしてください。" + } + } + } + }, + "dialog.enableNotifications.notNow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今はしない" + } + } + } + }, + "dialog.enableNotifications.openSettings": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定を開く" + } + } + } + }, + "dialog.enableNotifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Notifications for cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxの通知を有効にする" + } + } + } + }, + "dialog.moveFailed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux could not move this tab to the selected destination." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxはこのタブを選択した移動先に移動できませんでした。" + } + } + } + }, + "dialog.moveFailed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Failed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "移動失敗" + } + } + } + }, + "dialog.moveTab.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a destination for this tab." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このタブの移動先を選択してください。" + } + } + } + }, + "dialog.moveTab.move": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "移動" + } + } + } + }, + "dialog.moveTab.newWorkspaceCurrentWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace in Current Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のウインドウの新規ワークスペース" + } + } + } + }, + "dialog.moveTab.selectedWorkspaceNewWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Selected Workspace in New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択したワークスペースを新規ウインドウに" + } + } + } + }, + "dialog.moveTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの移動" + } + } + } + }, + "dialog.quitCmux.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close all windows and workspaces." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてのウインドウとワークスペースを閉じます。" + } + } + } + }, + "dialog.quitCmux.quit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "終了" + } + } + } + }, + "dialog.quitCmux.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quit cmux?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux を終了しますか?" + } + } + } + }, + "dialog.renameTab.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a custom name for this tab." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このタブのカスタム名を入力してください。" + } + } + } + }, + "dialog.renameTab.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名" + } + } + } + }, + "dialog.renameTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの名称変更" + } + } + } + }, + "dialog.renameWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a custom name for this workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このワークスペースのカスタム名を入力してください。" + } + } + } + }, + "dialog.renameWorkspace.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名" + } + } + } + }, + "dialog.renameWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更" + } + } + } + }, + "error.clipboardFolderPath": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Could not load any folder path from the clipboard." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリップボードからフォルダパスを読み込めませんでした。" + } + } + } + }, + "menu.app.about": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxについて" + } + } + } + }, + "menu.app.checkForUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認…" + } + } + } + }, + "menu.app.ghosttySettings": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ghostty Settings…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Ghostty設定…" + } + } + } + }, + "menu.app.reloadConfiguration": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Configuration" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "構成を再読み込み" + } + } + } + }, + "menu.app.settings": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定…" + } + } + } + }, + "menu.checkForUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認…" + } + } + } + }, + "menu.currentWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Current Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のウインドウ" + } + } + } + }, + "menu.file.closeOtherTabs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Other Tabs in Pane" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペイン内の他のタブを閉じる" + } + } + } + }, + "menu.file.closeTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じる" + } + } + } + }, + "menu.file.closeWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "menu.file.commandPalette": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレット…" + } + } + } + }, + "menu.file.goToWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go to Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースに移動…" + } + } + } + }, + "menu.file.newWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "menu.file.newWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "menu.file.openFolder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く…" + } + } + } + }, + "menu.file.openFolder.panelPrompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "開く" + } + } + } + }, + "menu.file.openFolder.panelTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く" + } + } + } + }, + "menu.file.reopenClosedBrowserPanel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reopen Closed Browser Panel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じたブラウザパネルを再度開く" + } + } + } + }, + "menu.find.find": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索…" + } + } + } + }, + "menu.find.findNext": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Next" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次を検索" + } + } + } + }, + "menu.find.findPrevious": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Previous" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前を検索" + } + } + } + }, + "menu.find.hideFindBar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide Find Bar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索バーを非表示" + } + } + } + }, + "menu.find.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索" + } + } + } + }, + "menu.find.useSelectionForFind": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Selection for Find" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択範囲を検索に使用" + } + } + } + }, + "menu.notifications.clearAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear All" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてクリア" + } + } + } + }, + "menu.notifications.jumpToUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "menu.notifications.markAllRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark All Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべて既読にする" + } + } + } + }, + "menu.notifications.show": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "menu.notifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "menu.openInAndroidStudio": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Android Studio" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Android Studio で開く" + } + } + } + }, + "menu.openInAntigravity": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Antigravity" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Antigravity で開く" + } + } + } + }, + "menu.openInCursor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Cursor" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Cursor で開く" + } + } + } + }, + "menu.openInFinder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Finder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Finder で開く" + } + } + } + }, + "menu.openInGhostty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Ghostty" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Ghostty で開く" + } + } + } + }, + "menu.openInITerm2": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in iTerm2" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを iTerm2 で開く" + } + } + } + }, + "menu.openInTerminal": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリをターミナルで開く" + } + } + } + }, + "menu.openInTower": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Tower" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Tower で開く" + } + } + } + }, + "menu.openInVSCode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in VS Code (Inline)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを VS Code で開く(インライン)" + } + } + } + }, + "menu.openInWarp": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Warp" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Warp で開く" + } + } + } + }, + "menu.openInWindsurf": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Windsurf" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Windsurf で開く" + } + } + } + }, + "menu.openInXcode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Xcode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Xcode で開く" + } + } + } + }, + "menu.openInZed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Zed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Zed で開く" + } + } + } + }, + "menu.preferences": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preferences…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定…" + } + } + } + }, + "menu.quitCmux": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quit cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux を終了" + } + } + } + }, + "menu.showNotifications": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "menu.updateLogs.copyFocusLogs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy Focus Logs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォーカスログをコピー" + } + } + } + }, + "menu.updateLogs.copyUpdateLogs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy Update Logs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートログをコピー" + } + } + } + }, + "menu.updateLogs.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Logs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートログ" + } + } + } + }, + "menu.view.actualSize": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actual Size" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "実際のサイズ" + } + } + } + }, + "menu.view.back": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Back" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "戻る" + } + } + } + }, + "menu.view.clearBrowserHistory": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Browser History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴をクリア" + } + } + } + }, + "menu.view.forward": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Forward" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "進む" + } + } + } + }, + "menu.view.jumpToUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "menu.view.nextSurface": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のサーフェス" + } + } + } + }, + "menu.view.nextWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のワークスペース" + } + } + } + }, + "menu.view.previousSurface": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のサーフェス" + } + } + } + }, + "menu.view.previousWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のワークスペース" + } + } + } + }, + "menu.view.reloadPage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ページを再読み込み" + } + } + } + }, + "menu.view.renameWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更…" + } + } + } + }, + "menu.view.showJSConsole": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show JavaScript Console" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "JavaScriptコンソールを表示" + } + } + } + }, + "menu.view.showNotifications": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "menu.view.splitBrowserDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "menu.view.splitBrowserRight": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "menu.view.splitDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "menu.view.splitRight": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + }, + "menu.view.toggleDevTools": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デベロッパツールの切り替え" + } + } + } + }, + "menu.view.toggleSidebar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの切り替え" + } + } + } + }, + "menu.view.workspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace %lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース %lld" + } + } + } + }, + "menu.view.zoomIn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom In" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "拡大" + } + } + } + }, + "menu.view.zoomOut": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom Out" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縮小" + } + } + } + }, + "menu.windowNumber": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window %lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ %lld" + } + } + } + }, + "notifications.clearAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear All" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてクリア" + } + } + } + }, + "notifications.empty.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Desktop notifications will appear here for quick review." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デスクトップ通知がここに表示されます。" + } + } + } + }, + "notifications.empty.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Desktop notifications will appear here." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デスクトップ通知がここに表示されます。" + } + } + } + }, + "notifications.empty.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No notifications yet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まだ通知はありません" + } + } + } + }, + "notifications.jumpToLatestUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "notifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "panel.displayName.fallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "panel.openFolder.prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "開く" + } + } + } + }, + "panel.openFolder.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く" + } + } + } + }, + "search.close.help": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close (Esc)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じる (Esc)" + } + } + } + }, + "search.nextMatch.help": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next match (Return)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次の一致 (Return)" + } + } + } + }, + "search.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索" + } + } + } + }, + "search.previousMatch.help": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous match (Shift+Return)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前の一致 (Shift+Return)" + } + } + } + }, + "settings.app.appIcon": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Icon" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリアイコン" + } + } + } + }, + "settings.app.dockBadge": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dock Badge" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Dockバッジ" + } + } + } + }, + "settings.app.dockBadge.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show unread count on app icon (Dock and Cmd+Tab)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリアイコン(DockおよびCmd+Tab)に未読数を表示します。" + } + } + } + }, + "settings.app.newWorkspacePlacement": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace Placement" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペースの配置" + } + } + } + }, + "settings.app.openSidebarPRLinks": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Sidebar PR Links in cmux Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーのPRリンクをcmuxブラウザで開く" + } + } + } + }, + "settings.app.openSidebarPRLinks.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicks open in your default browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリックするとデフォルトブラウザで開きます。" + } + } + } + }, + "settings.app.openSidebarPRLinks.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicks open inside cmux browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリックするとcmuxブラウザ内で開きます。" + } + } + } + }, + "settings.app.renameSelectsName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Selects Existing Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名称変更時に既存の名前を選択" + } + } + } + }, + "settings.app.renameSelectsName.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette rename keeps the caret at the end." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレットの名称変更ではキャレットが末尾に置かれます。" + } + } + } + }, + "settings.app.renameSelectsName.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette rename starts with all text selected." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレットの名称変更ではテキスト全体が選択された状態で始まります。" + } + } + } + }, + "settings.app.reorderOnNotification": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reorder on Notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知時に並べ替え" + } + } + } + }, + "settings.app.reorderOnNotification.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move workspaces to the top when they receive a notification. Disable for stable shortcut positions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を受け取ったワークスペースを一番上に移動します。ショートカット位置を固定するには無効にしてください。" + } + } + } + }, + "settings.app.showBranchDirectory": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Branch + Directory in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにブランチ+ディレクトリを表示" + } + } + } + }, + "settings.app.showBranchDirectory.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the built-in git branch and working-directory row." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "組み込みのgitブランチと作業ディレクトリの行を表示します。" + } + } + } + }, + "settings.app.showLog": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Latest Log in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーに最新ログを表示" + } + } + } + }, + "settings.app.showLog.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the latest imperative log/status message." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の命令型ログ/ステータスメッセージを表示します。" + } + } + } + }, + "settings.app.showMetadata": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Custom Metadata in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにカスタムメタデータを表示" + } + } + } + }, + "settings.app.showMetadata.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display custom metadata from report_meta/set_status and report_meta_block." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "report_meta/set_statusおよびreport_meta_blockからのカスタムメタデータを表示します。" + } + } + } + }, + "settings.app.showPorts": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Listening Ports in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにリスニングポートを表示" + } + } + } + }, + "settings.app.showPorts.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display detected listening ports for the active workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブなワークスペースで検出されたリスニングポートを表示します。" + } + } + } + }, + "settings.app.showProgress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Progress in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーに進捗を表示" + } + } + } + }, + "settings.app.showProgress.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the built-in progress bar from set_progress." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "set_progressによる組み込みプログレスバーを表示します。" + } + } + } + }, + "settings.app.showPullRequests": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Pull Requests in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにプルリクエストを表示" + } + } + } + }, + "settings.app.showPullRequests.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display review items (PR/MR/etc.) with status, number, and clickable link." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ステータス、番号、クリック可能なリンク付きのレビュー項目(PR/MRなど)を表示します。" + } + } + } + }, + "settings.app.sidebarBranchLayout": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sidebar Branch Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーのブランチレイアウト" + } + } + } + }, + "settings.app.sidebarBranchLayout.inline": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Inline" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インライン" + } + } + } + }, + "settings.app.sidebarBranchLayout.subtitleInline": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Inline: all branches share one line." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インライン: すべてのブランチが1行に表示されます。" + } + } + } + }, + "settings.app.sidebarBranchLayout.subtitleVertical": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vertical: each branch appears on its own line." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縦: 各ブランチがそれぞれの行に表示されます。" + } + } + } + }, + "settings.app.sidebarBranchLayout.vertical": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vertical" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縦" + } + } + } + }, + "settings.app.telemetry": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send anonymous telemetry" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "匿名のテレメトリを送信" + } + } + } + }, + "settings.app.telemetry.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Share anonymized crash and usage data to help improve cmux." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxの改善に役立てるため、匿名化されたクラッシュおよび使用状況データを共有します。" + } + } + } + }, + "settings.app.telemetry.subtitleChanged": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change takes effect on next launch." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "変更は次回起動時に反映されます。" + } + } + } + }, + "settings.app.theme": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "テーマ" + } + } + } + }, + "settings.app.warnBeforeQuit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Warn Before Quit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "終了前に警告" + } + } + } + }, + "settings.app.warnBeforeQuit.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q quits immediately without confirmation." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Qで確認なしにすぐ終了します。" + } + } + } + }, + "settings.app.warnBeforeQuit.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show a confirmation before quitting with Cmd+Q." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Qで終了する前に確認を表示します。" + } + } + } + }, + "settings.automation.claudeCode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Claude Code Integration" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Claude Code連携" + } + } + } + }, + "settings.automation.claudeCode.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When enabled, cmux wraps the claude command to inject session tracking and notification hooks. Disable if you prefer to manage Claude Code hooks yourself." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "有効にすると、cmuxはclaudeコマンドをラップしてセッション追跡と通知フックを挿入します。Claude Codeのフックを自分で管理する場合は無効にしてください。" + } + } + } + }, + "settings.automation.claudeCode.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Claude Code runs without cmux integration." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Claude Codeはcmux連携なしで実行されます。" + } + } + } + }, + "settings.automation.claudeCode.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sidebar shows Claude session status and notifications." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにClaudeセッションのステータスと通知が表示されます。" + } + } + } + }, + "settings.automation.openAccess.dialog.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "settings.automation.openAccess.dialog.confirm": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Full Open Access" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルオープンアクセスを有効にする" + } + } + } + }, + "settings.automation.openAccess.dialog.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "これにより、祖先プロセスチェックとパスワードチェックが無効になり、すべてのローカルユーザーにソケットが公開されます。リスクを理解した上で有効にしてください。" + } + } + } + }, + "settings.automation.openAccess.dialog.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable full open access?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルオープンアクセスを有効にしますか?" + } + } + } + }, + "settings.automation.openAccessWarning": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Warning: Full open access makes the control socket world-readable/writable on this Mac and disables auth checks. Use only for local debugging." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "警告: フルオープンアクセスにすると、このMac上の制御ソケットが誰でも読み書き可能になり、認証チェックが無効になります。ローカルデバッグ用途にのみ使用してください。" + } + } + } + }, + "settings.automation.port.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Each workspace gets CMUX_PORT and CMUX_PORT_END env vars with a dedicated port range. New terminals inherit these values." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "各ワークスペースにはCMUX_PORTとCMUX_PORT_END環境変数で専用のポート範囲が割り当てられます。新しいターミナルはこれらの値を継承します。" + } + } + } + }, + "settings.automation.portBase": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Port Base" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポートベース" + } + } + } + }, + "settings.automation.portBase.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starting port for CMUX_PORT env var." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CMUX_PORT環境変数の開始ポート。" + } + } + } + }, + "settings.automation.portRange": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Port Range Size" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポート範囲サイズ" + } + } + } + }, + "settings.automation.portRange.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Number of ports per workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースあたりのポート数。" + } + } + } + }, + "settings.automation.socketMode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Socket Control Mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソケット制御モード" + } + } + } + }, + "settings.automation.socketMode.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Controls access to the local Unix socket for programmatic control. Choose a mode that matches your threat model." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "プログラムによる制御のためのローカルUnix socketへのアクセスを制御します。脅威モデルに合ったモードを選択してください。" + } + } + } + }, + "settings.automation.socketOverrides.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Overrides: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, and CMUX_SOCKET_PATH (set CMUX_ALLOW_SOCKET_OVERRIDE=1 for stable/nightly builds)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オーバーライド: CMUX_SOCKET_ENABLE、CMUX_SOCKET_MODE、CMUX_SOCKET_PATH(stable/nightlyビルドではCMUX_ALLOW_SOCKET_OVERRIDE=1を設定)。" + } + } + } + }, + "settings.automation.socketPassword": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Socket Password" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソケットパスワード" + } + } + } + }, + "settings.automation.socketPassword.change": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "変更" + } + } + } + }, + "settings.automation.socketPassword.clear": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリア" + } + } + } + }, + "settings.automation.socketPassword.clearFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to clear password (%@)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードのクリアに失敗しました(%@)。" + } + } + } + }, + "settings.automation.socketPassword.cleared": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password cleared." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードをクリアしました。" + } + } + } + }, + "settings.automation.socketPassword.enterFirst": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a password first." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まずパスワードを入力してください。" + } + } + } + }, + "settings.automation.socketPassword.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワード" + } + } + } + }, + "settings.automation.socketPassword.saveFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to save password (%@)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードの保存に失敗しました(%@)。" + } + } + } + }, + "settings.automation.socketPassword.saved": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password saved." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードを保存しました。" + } + } + } + }, + "settings.automation.socketPassword.set": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Set" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定" + } + } + } + }, + "settings.automation.socketPassword.subtitleSet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stored in Application Support." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Application Supportに保存済み。" + } + } + } + }, + "settings.automation.socketPassword.subtitleUnset": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No password set. External clients will be blocked until one is configured." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードが設定されていません。設定するまで外部クライアントはブロックされます。" + } + } + } + }, + "settings.blendMode.behindWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Behind Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウの背面" + } + } + } + }, + "settings.blendMode.withinWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Within Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ内" + } + } + } + }, + "settings.browser.externalPatterns": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "URLs to Always Open Externally" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "常に外部で開くURL" + } + } + } + }, + "settings.browser.externalPatterns.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルのリンククリックおよびインターセプトされた`open https://...`呼び出しに適用されます。1行に1ルール。プレーンテキストはURL部分文字列に一致し、`re:`プレフィックスで正規表現を使用できます(例: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))。" + } + } + } + }, + "settings.browser.history": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browsing History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴" + } + } + } + }, + "settings.browser.history.clearButton": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear History…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "履歴をクリア…" + } + } + } + }, + "settings.browser.history.clearDialog.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "settings.browser.history.clearDialog.confirm": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "履歴をクリア" + } + } + } + }, + "settings.browser.history.clearDialog.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This removes visited-page suggestions from the browser omnibar." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザのオムニバーから訪問済みページの候補が削除されます。" + } + } + } + }, + "settings.browser.history.clearDialog.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear browser history?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴をクリアしますか?" + } + } + } + }, + "settings.browser.history.subtitleEmpty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No saved pages yet." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "保存済みのページはまだありません。" + } + } + } + }, + "settings.browser.history.subtitleMany": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld saved pages appear in omnibar suggestions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%lld件の保存済みページがオムニバーの候補に表示されます。" + } + } + } + }, + "settings.browser.history.subtitleOne": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 saved page appears in omnibar suggestions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "1件の保存済みページがオムニバーの候補に表示されます。" + } + } + } + }, + "settings.browser.hostWhitelist": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hosts to Open in Embedded Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "内蔵ブラウザで開くホスト" + } + } + } + }, + "settings.browser.hostWhitelist.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to terminal link clicks and intercepted `open https://...` calls. Only these hosts open in cmux. Others open in your default browser. One host or wildcard per line (for example: example.com, *.internal.example). Leave empty to open all hosts in cmux." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルのリンククリックおよびインターセプトされた`open https://...`呼び出しに適用されます。これらのホストのみcmuxで開きます。その他はデフォルトブラウザで開きます。1行に1つのホストまたはワイルドカード(例: example.com, *.internal.example)。空欄にするとすべてのホストをcmuxで開きます。" + } + } + } + }, + "settings.browser.httpAllowlist": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "HTTP Hosts Allowed in Embedded Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "内蔵ブラウザで許可するHTTPホスト" + } + } + } + }, + "settings.browser.httpAllowlist.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Controls which HTTP (non-HTTPS) hosts can open in cmux without a warning prompt. Defaults include localhost, 127.0.0.1, ::1, 0.0.0.0, and *.localtest.me." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "警告プロンプトなしでcmuxで開けるHTTP(非HTTPS)ホストを制御します。デフォルトにはlocalhost、127.0.0.1、::1、0.0.0.0、*.localtest.meが含まれます。" + } + } + } + }, + "settings.browser.httpAllowlist.hint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "1行に1つのホストまたはワイルドカード(例: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)。" + } + } + } + }, + "settings.browser.httpAllowlist.save": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "保存" + } + } + } + }, + "settings.browser.interceptOpen": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Intercept open http(s) in Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルでopen http(s)をインターセプト" + } + } + } + }, + "settings.browser.interceptOpen.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, `open https://...` and `open http://...` always use your default browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オフの場合、`open https://...`および`open http://...`は常にデフォルトブラウザを使用します。" + } + } + } + }, + "settings.browser.openTerminalLinks": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Terminal Links in cmux Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルのリンクをcmuxブラウザで開く" + } + } + } + }, + "settings.browser.openTerminalLinks.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, links clicked in terminal output open in your default browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オフの場合、ターミナル出力のリンクはデフォルトブラウザで開きます。" + } + } + } + }, + "settings.browser.searchEngine": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Default Search Engine" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デフォルト検索エンジン" + } + } + } + }, + "settings.browser.searchEngine.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Used by the browser address bar when input is not a URL." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザのアドレスバーで入力がURLでない場合に使用されます。" + } + } + } + }, + "settings.browser.searchSuggestions": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Search Suggestions" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索候補を表示" + } + } + } + }, + "settings.browser.theme": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Theme" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザテーマ" + } + } + } + }, + "settings.browser.theme.subtitleForced": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ forces that color scheme for compatible pages." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@は対応ページにそのカラースキームを強制します。" + } + } + } + }, + "settings.browser.theme.subtitleSystem": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System follows app and macOS appearance." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システムはアプリとmacOSの外観に従います。" + } + } + } + }, + "settings.material.contentBackground": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Content Background" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コンテンツ背景" + } + } + } + }, + "settings.material.fullScreenUI": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Full Screen UI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルスクリーンUI" + } + } + } + }, + "settings.material.headerView": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Header View" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ヘッダビュー" + } + } + } + }, + "settings.material.hudWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "HUD Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "HUDウインドウ" + } + } + } + }, + "settings.material.liquidGlass": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass(macOS 26以降)" + } + } + } + }, + "settings.material.menu": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "メニュー" + } + } + } + }, + "settings.material.none": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "None" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "なし" + } + } + } + }, + "settings.material.popover": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポップオーバー" + } + } + } + }, + "settings.material.sheet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sheet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シート" + } + } + } + }, + "settings.material.sidebar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバー" + } + } + } + }, + "settings.material.toolTip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tool Tip" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ツールチップ" + } + } + } + }, + "settings.material.underWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ下" + } + } + } + }, + "settings.material.windowBackground": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window Background" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ背景" + } + } + } + }, + "settings.preset.hudGlass": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + } + } + }, + "settings.preset.nativeSidebar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Native Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ネイティブサイドバー" + } + } + } + }, + "settings.preset.popoverGlass": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポップオーバーGlass" + } + } + } + }, + "settings.preset.raycastGray": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + } + } + }, + "settings.preset.softBlur": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソフトブラー" + } + } + } + }, + "settings.preset.underWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ下" + } + } + } + }, + "settings.reset.resetAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset All Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべての設定をリセット" + } + } + } + }, + "settings.section.app": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリ" + } + } + } + }, + "settings.section.automation": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オートメーション" + } + } + } + }, + "settings.section.browser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ" + } + } + } + }, + "settings.section.keyboardShortcuts": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keyboard Shortcuts" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キーボードショートカット" + } + } + } + }, + "settings.section.reset": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リセット" + } + } + } + }, + "settings.section.workspaceColors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Colors" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースカラー" + } + } + } + }, + "settings.shortcuts.recordHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click a shortcut value to record a new shortcut." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ショートカット値をクリックして新しいショートカットを記録します。" + } + } + } + }, + "settings.shortcuts.showHints": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Cmd/Ctrl-Hold Shortcut Hints" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd/Ctrl長押しのショートカットヒントを表示" + } + } + } + }, + "settings.shortcuts.showHints.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Holding Cmd or Ctrl keeps shortcut hint pills hidden." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CmdまたはCtrlを長押ししてもショートカットヒントピルは非表示のままです。" + } + } + } + }, + "settings.shortcuts.showHints.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd(サイドバー/タイトルバー)またはCtrl/Cmd(ペインタブ)を長押しするとショートカットヒントピルが表示されます。" + } + } + } + }, + "settings.state.active": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Active" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブ" + } + } + } + }, + "settings.state.followWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Follow Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウに追従" + } + } + } + }, + "settings.state.inactive": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Inactive" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "非アクティブ" + } + } + } + }, + "settings.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定" + } + } + } + }, + "settings.workspaceColors.base": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Base: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ベース: %@" + } + } + } + }, + "settings.workspaceColors.customColors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom Colors" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムカラー" + } + } + } + }, + "settings.workspaceColors.indicator": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Color Indicator" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースカラーインジケーター" + } + } + } + }, + "settings.workspaceColors.noCustomColors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom colors: none yet. Use \"Choose Custom Color...\" from a workspace context menu." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムカラー: まだありません。ワークスペースのコンテキストメニューから「カスタムカラーを選択…」を使用してください。" + } + } + } + }, + "settings.workspaceColors.paletteNote": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバー > ワークスペースカラーで使用するカラーパレットをカスタマイズできます。「カスタムカラーを選択…」のエントリは以下に保存されます。" + } + } + } + }, + "settings.workspaceColors.remove": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "削除" + } + } + } + }, + "settings.workspaceColors.resetPalette": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset Palette" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パレットをリセット" + } + } + } + }, + "settings.workspaceColors.resetPalette.button": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リセット" + } + } + } + }, + "settings.workspaceColors.resetPalette.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore built-in defaults and clear all custom colors." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "組み込みのデフォルトに戻し、すべてのカスタムカラーをクリアします。" + } + } + } + }, + "shortcut.closeWindow.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウを閉じる" + } + } + } + }, + "shortcut.closeWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "shortcut.flashFocusedPanel.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash Focused Panel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォーカスペインを強調" + } + } + } + }, + "shortcut.focusPaneDown.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下のペインにフォーカス" + } + } + } + }, + "shortcut.focusPaneLeft.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Left" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "左のペインにフォーカス" + } + } + } + }, + "shortcut.focusPaneRight.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右のペインにフォーカス" + } + } + } + }, + "shortcut.focusPaneUp.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上のペインにフォーカス" + } + } + } + }, + "shortcut.jumpToUnread.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "shortcut.newSurface.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規サーフェス" + } + } + } + }, + "shortcut.newWindow.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "shortcut.newWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "shortcut.nextSurface.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のサーフェス" + } + } + } + }, + "shortcut.nextWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のワークスペース" + } + } + } + }, + "shortcut.openBrowser.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを開く" + } + } + } + }, + "shortcut.openFolder.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く" + } + } + } + }, + "shortcut.pressShortcut.prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Press shortcut…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ショートカットを入力…" + } + } + } + }, + "shortcut.previousSurface.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のサーフェス" + } + } + } + }, + "shortcut.previousWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のワークスペース" + } + } + } + }, + "shortcut.renameTab.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名を変更" + } + } + } + }, + "shortcut.renameWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名を変更" + } + } + } + }, + "shortcut.showBrowserJSConsole.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Browser JavaScript Console" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ JavaScript コンソールを表示" + } + } + } + }, + "shortcut.showNotifications.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "shortcut.splitBrowserDown.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "shortcut.splitBrowserRight.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "shortcut.splitDown.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "shortcut.splitRight.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + }, + "shortcut.toggleBrowserDevTools.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Browser Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザデベロッパツールを切替" + } + } + } + }, + "shortcut.togglePaneZoom.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Pane Zoom" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペインズームを切替" + } + } + } + }, + "shortcut.toggleSidebar.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーを切替" + } + } + } + }, + "shortcut.toggleTerminalCopyMode.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Terminal Copy Mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルコピーモードを切替" + } + } + } + }, + "sidebar.closeWorkspace.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "sidebar.folderIcon.dragHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Drag to open in Finder or another app" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ドラッグしてFinderまたは他のアプリで開く" + } + } + } + }, + "sidebar.indicator.leftRail": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Left Rail" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "左レール" + } + } + } + }, + "sidebar.indicator.solidFill": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Solid Fill" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソリッドフィル" + } + } + } + }, + "sidebar.metadata.showLess": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show less" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "表示を減らす" + } + } + } + }, + "sidebar.metadata.showLessDetails": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show less details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細を折りたたむ" + } + } + } + }, + "sidebar.metadata.showMore": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show more" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "さらに表示" + } + } + } + }, + "sidebar.metadata.showMoreDetails": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show more details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細を展開" + } + } + } + }, + "sidebar.pathMenu.macintoshHD": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + } + } + }, + "sidebar.pullRequest.openTooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open %1$@ #%2$lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%1$@ #%2$lldを開く" + } + } + } + }, + "sidebar.pullRequest.statusClosed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "closed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クローズ" + } + } + } + }, + "sidebar.pullRequest.statusMerged": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "merged" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "マージ済み" + } + } + } + }, + "sidebar.pullRequest.statusOpen": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "open" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オープン" + } + } + } + }, + "sidebar.workspace.accessibilityHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activate to focus this workspace. Drag to reorder, or use Move Up and Move Down actions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブにしてこのワークスペースにフォーカスします。ドラッグで並べ替え、または「上に移動」「下に移動」アクションを使用します。" + } + } + } + }, + "sidebar.workspace.moveDownAction": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に移動" + } + } + } + }, + "sidebar.workspace.moveUpAction": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上に移動" + } + } + } + }, + "socketControl.allowAll.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Allow any local process and user to connect with no auth. Unsafe." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "認証なしで任意のローカルプロセスおよびユーザーの接続を許可します。安全ではありません。" + } + } + } + }, + "socketControl.allowAll.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Full open access" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "完全オープンアクセス" + } + } + } + }, + "socketControl.automation.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Allow external local automation clients from this macOS user (no ancestry check)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この macOS ユーザーからの外部ローカル自動化クライアントを許可します(プロセス系譜チェックなし)。" + } + } + } + }, + "socketControl.automation.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automation mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動化モード" + } + } + } + }, + "socketControl.cmuxOnly.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Only processes started inside cmux terminals can send commands." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux ターミナル内で起動したプロセスのみがコマンドを送信できます。" + } + } + } + }, + "socketControl.cmuxOnly.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux processes only" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux プロセスのみ" + } + } + } + }, + "socketControl.error.passwordFilePath": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unable to resolve socket password file path." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソケットパスワードファイルのパスを解決できません。" + } + } + } + }, + "socketControl.off.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disable the local control socket." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ローカルコントロールソケットを無効にします。" + } + } + } + }, + "socketControl.off.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オフ" + } + } + } + }, + "socketControl.password.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Require socket authentication with a password stored in a local file." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ローカルファイルに保存されたパスワードでソケット認証を要求します。" + } + } + } + }, + "socketControl.password.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードモード" + } + } + } + }, + "statusMenu.clearAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear All" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてクリア" + } + } + } + }, + "statusMenu.jumpToLatestUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "statusMenu.markAllRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark All Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべて既読にする" + } + } + } + }, + "statusMenu.noUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読の通知はありません" + } + } + } + }, + "statusMenu.showNotifications": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "statusMenu.unreadCount.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 unread notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 1件" + } + } + } + }, + "statusMenu.unreadCount.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 %lld件" + } + } + } + }, + "statusMenu.tooltip.unread.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 unread notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 1件" + } + } + } + }, + "statusMenu.tooltip.unread.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 %lld件" + } + } + } + }, + "tab.untitled": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Untitled Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名称未設定タブ" + } + } + } + }, + "theme.dark": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダーク" + } + } + } + }, + "theme.light": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライト" + } + } + } + }, + "theme.system": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システム" + } + } + } + }, + "titlebar.newWorkspace.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "titlebar.newWorkspace.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "titlebar.notifications.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "titlebar.notifications.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "titlebar.sidebar.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの切り替え" + } + } + } + }, + "titlebar.sidebar.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show or hide the sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの表示/非表示" + } + } + } + }, + "update.available.short": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートがあります" + } + } + } + }, + "update.available.withVersion": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Available: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートあり: %@" + } + } + } + }, + "update.checking": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checking for Updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認中…" + } + } + } + }, + "update.configureAutoUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Configure automatic update preferences" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動アップデートの設定" + } + } + } + }, + "update.downloadAndInstall": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Download and install the latest version" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新バージョンをダウンロードしてインストール" + } + } + } + }, + "update.downloading.progress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中: %@" + } + } + } + }, + "update.downloading.status": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中…" + } + } + } + }, + "update.downloadingPackage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading the update package" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートパッケージをダウンロード中" + } + } + } + }, + "update.error.appLocation.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Location Issue" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリの場所の問題" + } + } + } + }, + "update.error.connectionLost.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The network connection was lost while checking for updates. Try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートの確認中にネットワーク接続が切れました。もう一度お試しください。" + } + } + } + }, + "update.error.connectionLost.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection Lost" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "接続が切れました" + } + } + } + }, + "update.error.downloadFailed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't Download Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをダウンロードできませんでした" + } + } + } + }, + "update.error.failed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Failed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートに失敗しました" + } + } + } + }, + "update.error.feedDownload.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux couldn't download the update feed. Check your connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はアップデートフィードをダウンロードできませんでした。接続を確認してもう一度お試しください。" + } + } + } + }, + "update.error.feedError.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Feed Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードエラー" + } + } + } + }, + "update.error.feedRead.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update feed could not be read. Please try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードを読み取れませんでした。後でもう一度お試しください。" + } + } + } + }, + "update.error.insecureFeed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update feed is insecure. Please contact support." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードが安全ではありません。サポートにお問い合わせください。" + } + } + } + }, + "update.error.insecureFeed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Insecure Update Feed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "安全でないアップデートフィード" + } + } + } + }, + "update.error.invalidFeed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update feed URL is invalid. Please contact support." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードのURLが無効です。サポートにお問い合わせください。" + } + } + } + }, + "update.error.invalidFeed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid Update Feed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "無効なアップデートフィード" + } + } + } + }, + "update.error.noInternet.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux can't reach the update server. Check your internet connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はアップデートサーバーに接続できません。インターネット接続を確認してもう一度お試しください。" + } + } + } + }, + "update.error.noInternet.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Internet Connection" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インターネット接続がありません" + } + } + } + }, + "update.error.permissionError.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move cmux into Applications and relaunch to enable updates." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを有効にするには、cmux を「アプリケーション」に移動して再起動してください。" + } + } + } + }, + "update.error.permissionError.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updater Permission Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデーター権限エラー" + } + } + } + }, + "update.error.secureConnectionFailed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A secure connection to the update server couldn't be established. Try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートサーバーへのセキュア接続を確立できませんでした。後でもう一度お試しください。" + } + } + } + }, + "update.error.secureConnectionFailed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Secure Connection Failed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "セキュア接続に失敗しました" + } + } + } + }, + "update.error.serverNotFound.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update server can't be found. Check your connection or try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートサーバーが見つかりません。接続を確認するか、後でもう一度お試しください。" + } + } + } + }, + "update.error.serverNotFound.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Server Not Found" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サーバーが見つかりません" + } + } + } + }, + "update.error.serverUnreachable.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux couldn't connect to the update server. Check your connection or try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はアップデートサーバーに接続できませんでした。接続を確認するか、後でもう一度お試しください。" + } + } + } + }, + "update.error.serverUnreachable.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Server Unreachable" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サーバーに接続できません" + } + } + } + }, + "update.error.signatureError.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update's signature could not be verified. Please try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートの署名を検証できませんでした。後でもう一度お試しください。" + } + } + } + }, + "update.error.signatureError.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Signature Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデート署名エラー" + } + } + } + }, + "update.error.timedOut.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update server took too long to respond. Try again in a moment." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートサーバーの応答に時間がかかりすぎました。しばらくしてからもう一度お試しください。" + } + } + } + }, + "update.error.timedOut.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Timed Out" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートがタイムアウトしました" + } + } + } + }, + "update.extracting.progress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preparing: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "準備中: %@" + } + } + } + }, + "update.installAndRelaunch": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install Update and Relaunch" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをインストールして再起動" + } + } + } + }, + "update.installing.status": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インストール中…" + } + } + } + }, + "update.installingAndRestarting": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing update and preparing to restart" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをインストールして再起動を準備中" + } + } + } + }, + "update.noUpdates.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You are running the latest version" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新バージョンを使用しています" + } + } + } + }, + "update.noUpdates.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Updates Available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートはありません" + } + } + } + }, + "update.permissionRequest.text": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Automatic Updates?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動アップデートを有効にしますか?" + } + } + } + }, + "update.pleaseWait": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Please wait while we check for available updates" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認しています。しばらくお待ちください" + } + } + } + }, + "update.popover.autoUpdatesDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux can automatically check for updates in the background." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はバックグラウンドで自動的にアップデートを確認できます。" + } + } + } + }, + "update.popover.checking": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checking for updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認中…" + } + } + } + }, + "update.popover.details": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細" + } + } + } + }, + "update.popover.downloadingUpdate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをダウンロード中" + } + } + } + }, + "update.popover.enableAutoUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable automatic updates?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動アップデートを有効にしますか?" + } + } + } + }, + "update.popover.noUpdatesFound": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Updates Found" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートは見つかりませんでした" + } + } + } + }, + "update.popover.noUpdatesFound.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You're already running the latest version." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すでに最新バージョンを使用しています。" + } + } + } + }, + "update.popover.preparingUpdate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preparing Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを準備中" + } + } + } + }, + "update.popover.released": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Released:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リリース日:" + } + } + } + }, + "update.popover.restartRequired": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Required" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再起動が必要です" + } + } + } + }, + "update.popover.restartRequired.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update is ready. Please restart the application to complete the installation." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートの準備ができました。インストールを完了するにはアプリケーションを再起動してください。" + } + } + } + }, + "update.popover.size": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Size:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイズ:" + } + } + } + }, + "update.popover.updateAvailable": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートがあります" + } + } + } + }, + "update.popover.version": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Version:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "バージョン:" + } + } + } + }, + "update.preparingUpdate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Extracting and preparing the update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを展開して準備中" + } + } + } + }, + "update.restartToComplete": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart to Complete Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを完了するには再起動してください" + } + } + } + }, + "update.viewGitHubCommit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View GitHub Commit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "GitHub コミットを表示" + } + } + } + }, + "update.viewReleaseNotes": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View Release Notes" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リリースノートを表示" + } + } + } + }, + "workspace.displayName.fallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "workspace.placement.afterCurrent": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "After current" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在の後" + } + } + } + }, + "workspace.placement.afterCurrent.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Insert new workspaces directly after the active workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブなワークスペースの直後に新しいワークスペースを挿入します。" + } + } + } + }, + "workspace.placement.end": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "End" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "末尾" + } + } + } + }, + "workspace.placement.end.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Append new workspaces to the bottom of the list." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新しいワークスペースをリストの末尾に追加します。" + } + } + } + }, + "workspace.placement.top": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Top" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "先頭" + } + } + } + }, + "workspace.placement.top.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Insert new workspaces at the top of the list." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新しいワークスペースをリストの先頭に挿入します。" + } + } + } + }, + "workspace.tooltip.newBrowser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ブラウザ" + } + } + } + }, + "workspace.tooltip.newTerminal": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ターミナル" + } + } + } + }, + "workspace.tooltip.splitDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "workspace.tooltip.splitRight": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + } + } +} \ No newline at end of file diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 254381a6..a6f996fb 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -80,31 +80,31 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { var commandPaletteTitle: String { switch self { case .androidStudio: - return "Open Current Directory in Android Studio" + return String(localized: "menu.openInAndroidStudio", defaultValue: "Open Current Directory in Android Studio") case .antigravity: - return "Open Current Directory in Antigravity" + return String(localized: "menu.openInAntigravity", defaultValue: "Open Current Directory in Antigravity") case .cursor: - return "Open Current Directory in Cursor" + return String(localized: "menu.openInCursor", defaultValue: "Open Current Directory in Cursor") case .finder: - return "Open Current Directory in Finder" + return String(localized: "menu.openInFinder", defaultValue: "Open Current Directory in Finder") case .ghostty: - return "Open Current Directory in Ghostty" + return String(localized: "menu.openInGhostty", defaultValue: "Open Current Directory in Ghostty") case .iterm2: - return "Open Current Directory in iTerm2" + return String(localized: "menu.openInITerm2", defaultValue: "Open Current Directory in iTerm2") case .terminal: - return "Open Current Directory in Terminal" + return String(localized: "menu.openInTerminal", defaultValue: "Open Current Directory in Terminal") case .tower: - return "Open Current Directory in Tower" + return String(localized: "menu.openInTower", defaultValue: "Open Current Directory in Tower") case .vscode: - return "Open Current Directory in VS Code (Inline)" + return String(localized: "menu.openInVSCode", defaultValue: "Open Current Directory in VS Code (Inline)") case .warp: - return "Open Current Directory in Warp" + return String(localized: "menu.openInWarp", defaultValue: "Open Current Directory in Warp") case .windsurf: - return "Open Current Directory in Windsurf" + return String(localized: "menu.openInWindsurf", defaultValue: "Open Current Directory in Windsurf") case .xcode: - return "Open Current Directory in Xcode" + return String(localized: "menu.openInXcode", defaultValue: "Open Current Directory in Xcode") case .zed: - return "Open Current Directory in Zed" + return String(localized: "menu.openInZed", defaultValue: "Open Current Directory in Zed") } } @@ -1461,7 +1461,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private lazy var titlebarAccessoryController = UpdateTitlebarAccessoryController(viewModel: updateViewModel) private let windowDecorationsController = WindowDecorationsController() private var menuBarExtraController: MenuBarExtraController? - private static let serviceErrorNoPath = NSString(string: "Could not load any folder path from the clipboard.") + private static let serviceErrorNoPath = NSString(string: String(localized: "error.clipboardFolderPath", defaultValue: "Could not load any folder path from the clipboard.")) private static let didInstallWindowKeyEquivalentSwizzle: Void = { let targetClass: AnyClass = NSWindow.self let originalSelector = #selector(NSWindow.performKeyEquivalent(with:)) @@ -3695,9 +3695,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent var labels: [UUID: String] = [:] for (index, summary) in orderedSummaries.enumerated() { if summary.windowId == referenceWindowId { - labels[summary.windowId] = "Current Window" + labels[summary.windowId] = String(localized: "menu.currentWindow", defaultValue: "Current Window") } else { - labels[summary.windowId] = "Window \(index + 1)" + let number = index + 1 + labels[summary.windowId] = String(localized: "menu.windowNumber", defaultValue: "Window \(number)") } } return labels @@ -3705,7 +3706,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private func workspaceDisplayName(_ workspace: Workspace) -> String { let trimmed = workspace.title.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmed.isEmpty ? "Workspace" : trimmed + return trimmed.isEmpty ? String(localized: "workspace.displayName.fallback", defaultValue: "Workspace") : trimmed } private func rollbackDetachedSurface( @@ -4648,22 +4649,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let installer = CmuxCLIPathInstaller() do { let outcome = try installer.install() - var informativeText = """ - Created symlink: - - \(outcome.destinationURL.path) -> \(outcome.sourceURL.path) - """ + var informativeText = String(localized: "cli.install.symlinkCreated", defaultValue: "Created symlink:\n\n\(outcome.destinationURL.path) -> \(outcome.sourceURL.path)") if outcome.usedAdministratorPrivileges { - informativeText += "\n\nAdministrator privileges were required to write to /usr/local/bin." + informativeText += "\n\n" + String(localized: "cli.install.adminRequired", defaultValue: "Administrator privileges were required to write to /usr/local/bin.") } presentCLIPathAlert( - title: "cmux CLI Installed", + title: String(localized: "cli.installed", defaultValue: "cmux CLI Installed"), informativeText: informativeText, style: .informational ) } catch { presentCLIPathAlert( - title: "Couldn't Install cmux CLI", + title: String(localized: "cli.installFailed", defaultValue: "Couldn't Install cmux CLI"), informativeText: error.localizedDescription, style: .warning ) @@ -4675,20 +4672,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent do { let outcome = try installer.uninstall() let prefix = outcome.removedExistingEntry - ? "Removed \(outcome.destinationURL.path)." - : "No cmux CLI symlink was found at \(outcome.destinationURL.path)." + ? String(localized: "cli.uninstall.removed", defaultValue: "Removed \(outcome.destinationURL.path).") + : String(localized: "cli.uninstall.notFound", defaultValue: "No cmux CLI symlink was found at \(outcome.destinationURL.path).") var informativeText = prefix if outcome.usedAdministratorPrivileges { - informativeText += "\n\nAdministrator privileges were required to modify /usr/local/bin." + informativeText += "\n\n" + String(localized: "cli.uninstall.adminRequired", defaultValue: "Administrator privileges were required to modify /usr/local/bin.") } presentCLIPathAlert( - title: "cmux CLI Uninstalled", + title: String(localized: "cli.uninstalled", defaultValue: "cmux CLI Uninstalled"), informativeText: informativeText, style: .informational ) } catch { presentCLIPathAlert( - title: "Couldn't Uninstall cmux CLI", + title: String(localized: "cli.uninstallFailed", defaultValue: "Couldn't Uninstall cmux CLI"), informativeText: error.localizedDescription, style: .warning ) @@ -4704,7 +4701,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent alert.alertStyle = style alert.messageText = title alert.informativeText = informativeText - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) if let window = NSApp.keyWindow ?? NSApp.mainWindow { alert.beginSheetModal(for: window, completionHandler: nil) @@ -6003,12 +6000,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let alert = NSAlert() alert.alertStyle = .warning - alert.messageText = "Quit cmux?" - alert.informativeText = "This will close all windows and workspaces." - alert.addButton(withTitle: "Quit") - alert.addButton(withTitle: "Cancel") + alert.messageText = String(localized: "dialog.quitCmux.title", defaultValue: "Quit cmux?") + alert.informativeText = String(localized: "dialog.quitCmux.message", defaultValue: "This will close all windows and workspaces.") + alert.addButton(withTitle: String(localized: "dialog.quitCmux.quit", defaultValue: "Quit")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) alert.showsSuppressionButton = true - alert.suppressionButton?.title = "Don't warn again for Cmd+Q" + alert.suppressionButton?.title = String(localized: "dialog.dontWarnCmdQ", defaultValue: "Don't warn again for Cmd+Q") let response = alert.runModal() if alert.suppressionButton?.state == .on { @@ -6030,14 +6027,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } let alert = NSAlert() - alert.messageText = "Rename Workspace" - alert.informativeText = "Enter a custom name for this workspace." + alert.messageText = String(localized: "dialog.renameWorkspace.title", defaultValue: "Rename Workspace") + alert.informativeText = String(localized: "dialog.renameWorkspace.message", defaultValue: "Enter a custom name for this workspace.") let input = NSTextField(string: tab.customTitle ?? tab.title) - input.placeholderString = "Workspace name" + input.placeholderString = String(localized: "dialog.renameWorkspace.placeholder", defaultValue: "Workspace name") input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Rename") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.rename", defaultValue: "Rename")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input DispatchQueue.main.async { @@ -8083,17 +8080,17 @@ final class MenuBarExtraController: NSObject, NSMenuDelegate { private var notificationsCancellable: AnyCancellable? private let buildHintTitle: String? - private let stateHintItem = NSMenuItem(title: "No unread notifications", action: nil, keyEquivalent: "") + private let stateHintItem = NSMenuItem(title: String(localized: "statusMenu.noUnread", defaultValue: "No unread notifications"), action: nil, keyEquivalent: "") private let buildHintItem = NSMenuItem(title: "", action: nil, keyEquivalent: "") private let notificationListSeparator = NSMenuItem.separator() private let notificationSectionSeparator = NSMenuItem.separator() - private let showNotificationsItem = NSMenuItem(title: "Show Notifications", action: nil, keyEquivalent: "") - private let jumpToUnreadItem = NSMenuItem(title: "Jump to Latest Unread", action: nil, keyEquivalent: "") - private let markAllReadItem = NSMenuItem(title: "Mark All Read", action: nil, keyEquivalent: "") - private let clearAllItem = NSMenuItem(title: "Clear All", action: nil, keyEquivalent: "") - private let checkForUpdatesItem = NSMenuItem(title: "Check for Updates…", action: nil, keyEquivalent: "") - private let preferencesItem = NSMenuItem(title: "Preferences…", action: nil, keyEquivalent: "") - private let quitItem = NSMenuItem(title: "Quit cmux", action: nil, keyEquivalent: "") + private let showNotificationsItem = NSMenuItem(title: String(localized: "statusMenu.showNotifications", defaultValue: "Show Notifications"), action: nil, keyEquivalent: "") + private let jumpToUnreadItem = NSMenuItem(title: String(localized: "statusMenu.jumpToLatestUnread", defaultValue: "Jump to Latest Unread"), action: nil, keyEquivalent: "") + private let markAllReadItem = NSMenuItem(title: String(localized: "statusMenu.markAllRead", defaultValue: "Mark All Read"), action: nil, keyEquivalent: "") + private let clearAllItem = NSMenuItem(title: String(localized: "statusMenu.clearAll", defaultValue: "Clear All"), action: nil, keyEquivalent: "") + private let checkForUpdatesItem = NSMenuItem(title: String(localized: "menu.checkForUpdates", defaultValue: "Check for Updates…"), action: nil, keyEquivalent: "") + private let preferencesItem = NSMenuItem(title: String(localized: "menu.preferences", defaultValue: "Preferences…"), action: nil, keyEquivalent: "") + private let quitItem = NSMenuItem(title: String(localized: "menu.quitCmux", defaultValue: "Quit cmux"), action: nil, keyEquivalent: "") private var notificationItems: [NSMenuItem] = [] private let maxInlineNotificationItems = 6 @@ -8222,7 +8219,9 @@ final class MenuBarExtraController: NSObject, NSMenuDelegate { button.image = MenuBarIconRenderer.makeImage(unreadCount: displayedUnreadCount) button.toolTip = displayedUnreadCount == 0 ? "cmux" - : "cmux: \(displayedUnreadCount) unread notification\(displayedUnreadCount == 1 ? "" : "s")" + : displayedUnreadCount == 1 + ? "cmux: " + String(localized: "statusMenu.tooltip.unread.one", defaultValue: "1 unread notification") + : "cmux: " + String(localized: "statusMenu.tooltip.unread.other", defaultValue: "\(displayedUnreadCount) unread notifications") } } @@ -8345,9 +8344,14 @@ enum NotificationMenuSnapshotBuilder { } static func stateHintTitle(unreadCount: Int) -> String { - unreadCount == 0 - ? "No unread notifications" - : "\(unreadCount) unread notification\(unreadCount == 1 ? "" : "s")" + switch unreadCount { + case 0: + return String(localized: "statusMenu.noUnread", defaultValue: "No unread notifications") + case 1: + return String(localized: "statusMenu.unreadCount.one", defaultValue: "1 unread notification") + default: + return String(localized: "statusMenu.unreadCount.other", defaultValue: "\(unreadCount) unread notifications") + } } } diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 2b57e7fa..a4884f4c 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1287,27 +1287,27 @@ struct ContentView: View { var title: String { switch kind { case .workspace: - return "Rename Workspace" + return String(localized: "commandPalette.rename.workspaceTitle", defaultValue: "Rename Workspace") case .tab: - return "Rename Tab" + return String(localized: "commandPalette.rename.tabTitle", defaultValue: "Rename Tab") } } var description: String { switch kind { case .workspace: - return "Choose a custom workspace name." + return String(localized: "commandPalette.rename.workspaceDescription", defaultValue: "Choose a custom workspace name.") case .tab: - return "Choose a custom tab name." + return String(localized: "commandPalette.rename.tabDescription", defaultValue: "Choose a custom tab name.") } } var placeholder: String { switch kind { case .workspace: - return "Workspace name" + return String(localized: "commandPalette.rename.workspacePlaceholder", defaultValue: "Workspace name") case .tab: - return "Tab name" + return String(localized: "commandPalette.rename.tabPlaceholder", defaultValue: "Tab name") } } } @@ -2904,7 +2904,7 @@ struct ContentView: View { Divider() - Text("Enter a \(renameTargetNoun(target)) name. Press Enter to rename, Escape to cancel.") + Text(renameInputHintText(target: target)) .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) @@ -2933,7 +2933,7 @@ struct ContentView: View { proposedName: String ) -> some View { let trimmedName = proposedName.trimmingCharacters(in: .whitespacesAndNewlines) - let nextName = trimmedName.isEmpty ? "(clear custom name)" : trimmedName + let nextName = trimmedName.isEmpty ? String(localized: "commandPalette.rename.clearCustomName", defaultValue: "(clear custom name)") : trimmedName return VStack(spacing: 0) { Text(nextName) @@ -2945,7 +2945,7 @@ struct ContentView: View { Divider() - Text("Press Enter to apply this \(renameTargetNoun(target)) name, or Escape to cancel.") + Text(renameConfirmHintText(target: target)) .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) @@ -2966,12 +2966,21 @@ struct ContentView: View { } } - private func renameTargetNoun(_ target: CommandPaletteRenameTarget) -> String { + private func renameInputHintText(target: CommandPaletteRenameTarget) -> String { switch target.kind { case .workspace: - return "workspace" + return String(localized: "commandPalette.rename.workspaceInputHint", defaultValue: "Enter a workspace name. Press Enter to rename, Escape to cancel.") case .tab: - return "tab" + return String(localized: "commandPalette.rename.tabInputHint", defaultValue: "Enter a tab name. Press Enter to rename, Escape to cancel.") + } + } + + private func renameConfirmHintText(target: CommandPaletteRenameTarget) -> String { + switch target.kind { + case .workspace: + return String(localized: "commandPalette.rename.workspaceConfirmHint", defaultValue: "Press Enter to apply this workspace name, or Escape to cancel.") + case .tab: + return String(localized: "commandPalette.rename.tabConfirmHint", defaultValue: "Press Enter to apply this tab name, or Escape to cancel.") } } @@ -2985,18 +2994,18 @@ struct ContentView: View { private var commandPaletteSearchPlaceholder: String { switch commandPaletteListScope { case .commands: - return "Type a command" + return String(localized: "commandPalette.search.commandsPlaceholder", defaultValue: "Type a command") case .switcher: - return "Search workspaces" + return String(localized: "commandPalette.search.switcherPlaceholder", defaultValue: "Search workspaces") } } private var commandPaletteEmptyStateText: String { switch commandPaletteListScope { case .commands: - return "No commands match your search." + return String(localized: "commandPalette.search.commandsEmpty", defaultValue: "No commands match your search.") case .switcher: - return "No workspaces match your search." + return String(localized: "commandPalette.search.switcherEmpty", defaultValue: "No workspaces match your search.") } } @@ -3089,7 +3098,7 @@ struct ContentView: View { guard commandPaletteListScope == .switcher else { return nil } if command.id.hasPrefix("switcher.workspace.") { - return CommandPaletteTrailingLabel(text: "Workspace", style: .kind) + return CommandPaletteTrailingLabel(text: String(localized: "commandPalette.kind.workspace", defaultValue: "Workspace"), style: .kind) } return nil } @@ -3139,7 +3148,7 @@ struct ContentView: View { id: workspaceCommandId, rank: nextRank, title: workspaceName, - subtitle: commandPaletteSwitcherSubtitle(base: "Workspace", windowLabel: context.windowLabel), + subtitle: commandPaletteSwitcherSubtitle(base: String(localized: "commandPalette.switcher.workspaceLabel", defaultValue: "Workspace"), windowLabel: context.windowLabel), shortcutHint: nil, keywords: workspaceKeywords, dismissOnRun: true, @@ -3183,7 +3192,7 @@ struct ContentView: View { var windowLabelById: [UUID: String] = [:] if orderedSummaries.count > 1 { for (index, summary) in orderedSummaries.enumerated() where summary.windowId != windowId { - windowLabelById[summary.windowId] = "Window \(index + 1)" + windowLabelById[summary.windowId] = String(localized: "commandPalette.switcher.windowLabel", defaultValue: "Window \(index + 1)") } } @@ -3449,23 +3458,23 @@ struct ContentView: View { } func workspaceSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.workspaceName) ?? "Workspace" - return "Workspace • \(name)" + let name = context.string(CommandPaletteContextKeys.workspaceName) ?? String(localized: "commandPalette.subtitle.workspaceFallback", defaultValue: "Workspace") + return String(localized: "commandPalette.subtitle.workspaceWithName", defaultValue: "Workspace • \(name)") } func panelSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.panelName) ?? "Tab" - return "Tab • \(name)" + let name = context.string(CommandPaletteContextKeys.panelName) ?? String(localized: "commandPalette.subtitle.tabFallback", defaultValue: "Tab") + return String(localized: "commandPalette.subtitle.tabWithName", defaultValue: "Tab • \(name)") } func browserPanelSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.panelName) ?? "Tab" - return "Browser • \(name)" + let name = context.string(CommandPaletteContextKeys.panelName) ?? String(localized: "commandPalette.subtitle.tabFallback", defaultValue: "Tab") + return String(localized: "commandPalette.subtitle.browserWithName", defaultValue: "Browser • \(name)") } func terminalPanelSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.panelName) ?? "Tab" - return "Terminal • \(name)" + let name = context.string(CommandPaletteContextKeys.panelName) ?? String(localized: "commandPalette.subtitle.tabFallback", defaultValue: "Tab") + return String(localized: "commandPalette.subtitle.terminalWithName", defaultValue: "Terminal • \(name)") } var contributions: [CommandPaletteCommandContribution] = [] @@ -3473,24 +3482,24 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.newWorkspace", - title: constant("New Workspace"), - subtitle: constant("Workspace"), + title: constant(String(localized: "command.newWorkspace.title", defaultValue: "New Workspace")), + subtitle: constant(String(localized: "command.newWorkspace.subtitle", defaultValue: "Workspace")), keywords: ["create", "new", "workspace"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.newWindow", - title: constant("New Window"), - subtitle: constant("Window"), + title: constant(String(localized: "command.newWindow.title", defaultValue: "New Window")), + subtitle: constant(String(localized: "command.newWindow.subtitle", defaultValue: "Window")), keywords: ["create", "new", "window"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.installCLI", - title: constant("Shell Command: Install 'cmux' in PATH"), - subtitle: constant("CLI"), + title: constant(String(localized: "command.installCLI.title", defaultValue: "Shell Command: Install 'cmux' in PATH")), + subtitle: constant(String(localized: "command.installCLI.subtitle", defaultValue: "CLI")), keywords: ["install", "cli", "path", "shell", "command", "symlink"], when: { _ in !(AppDelegate.shared?.isCmuxCLIInstalledInPATH() ?? false) } ) @@ -3498,8 +3507,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.uninstallCLI", - title: constant("Shell Command: Uninstall 'cmux' from PATH"), - subtitle: constant("CLI"), + title: constant(String(localized: "command.uninstallCLI.title", defaultValue: "Shell Command: Uninstall 'cmux' from PATH")), + subtitle: constant(String(localized: "command.uninstallCLI.subtitle", defaultValue: "CLI")), keywords: ["uninstall", "remove", "cli", "path", "shell", "command", "symlink"], when: { _ in AppDelegate.shared?.isCmuxCLIInstalledInPATH() ?? false } ) @@ -3507,16 +3516,16 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.openFolder", - title: constant("Open Folder…"), - subtitle: constant("Workspace"), + title: constant(String(localized: "command.openFolder.title", defaultValue: "Open Folder…")), + subtitle: constant(String(localized: "command.openFolder.subtitle", defaultValue: "Workspace")), keywords: ["open", "folder", "repository", "project", "directory"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.newTerminalTab", - title: constant("New Tab (Terminal)"), - subtitle: constant("Tab"), + title: constant(String(localized: "command.newTerminalTab.title", defaultValue: "New Tab (Terminal)")), + subtitle: constant(String(localized: "command.newTerminalTab.subtitle", defaultValue: "Tab")), shortcutHint: "⌘T", keywords: ["new", "terminal", "tab"] ) @@ -3524,8 +3533,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.newBrowserTab", - title: constant("New Tab (Browser)"), - subtitle: constant("Tab"), + title: constant(String(localized: "command.newBrowserTab.title", defaultValue: "New Tab (Browser)")), + subtitle: constant(String(localized: "command.newBrowserTab.subtitle", defaultValue: "Tab")), shortcutHint: "⌘⇧L", keywords: ["new", "browser", "tab", "web"] ) @@ -3533,8 +3542,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.closeTab", - title: constant("Close Tab"), - subtitle: constant("Tab"), + title: constant(String(localized: "command.closeTab.title", defaultValue: "Close Tab")), + subtitle: constant(String(localized: "command.closeTab.subtitle", defaultValue: "Tab")), shortcutHint: "⌘W", keywords: ["close", "tab"] ) @@ -3542,8 +3551,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.closeWorkspace", - title: constant("Close Workspace"), - subtitle: constant("Workspace"), + title: constant(String(localized: "command.closeWorkspace.title", defaultValue: "Close Workspace")), + subtitle: constant(String(localized: "command.closeWorkspace.subtitle", defaultValue: "Workspace")), shortcutHint: "⌘⇧W", keywords: ["close", "workspace"] ) @@ -3551,24 +3560,24 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.closeWindow", - title: constant("Close Window"), - subtitle: constant("Window"), + title: constant(String(localized: "command.closeWindow.title", defaultValue: "Close Window")), + subtitle: constant(String(localized: "command.closeWindow.subtitle", defaultValue: "Window")), keywords: ["close", "window"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.toggleFullScreen", - title: constant("Toggle Full Screen"), - subtitle: constant("Window"), + title: constant(String(localized: "command.toggleFullScreen.title", defaultValue: "Toggle Full Screen")), + subtitle: constant(String(localized: "command.toggleFullScreen.subtitle", defaultValue: "Window")), keywords: ["fullscreen", "full", "screen", "window", "toggle"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.reopenClosedBrowserTab", - title: constant("Reopen Closed Browser Tab"), - subtitle: constant("Browser"), + title: constant(String(localized: "command.reopenClosedBrowserTab.title", defaultValue: "Reopen Closed Browser Tab")), + subtitle: constant(String(localized: "command.reopenClosedBrowserTab.subtitle", defaultValue: "Browser")), shortcutHint: "⌘⇧T", keywords: ["reopen", "closed", "browser"] ) @@ -3576,40 +3585,40 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.toggleSidebar", - title: constant("Toggle Sidebar"), - subtitle: constant("Layout"), + title: constant(String(localized: "command.toggleSidebar.title", defaultValue: "Toggle Sidebar")), + subtitle: constant(String(localized: "command.toggleSidebar.subtitle", defaultValue: "Layout")), keywords: ["toggle", "sidebar", "layout"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.triggerFlash", - title: constant("Flash Focused Panel"), - subtitle: constant("View"), + title: constant(String(localized: "command.triggerFlash.title", defaultValue: "Flash Focused Panel")), + subtitle: constant(String(localized: "command.triggerFlash.subtitle", defaultValue: "View")), keywords: ["flash", "highlight", "focus", "panel"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.showNotifications", - title: constant("Show Notifications"), - subtitle: constant("Notifications"), + title: constant(String(localized: "command.showNotifications.title", defaultValue: "Show Notifications")), + subtitle: constant(String(localized: "command.showNotifications.subtitle", defaultValue: "Notifications")), keywords: ["notifications", "inbox"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.jumpUnread", - title: constant("Jump to Latest Unread"), - subtitle: constant("Notifications"), + title: constant(String(localized: "command.jumpUnread.title", defaultValue: "Jump to Latest Unread")), + subtitle: constant(String(localized: "command.jumpUnread.subtitle", defaultValue: "Notifications")), keywords: ["jump", "unread", "notification"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.openSettings", - title: constant("Open Settings"), - subtitle: constant("Global"), + title: constant(String(localized: "command.openSettings.title", defaultValue: "Open Settings")), + subtitle: constant(String(localized: "command.openSettings.subtitle", defaultValue: "Global")), shortcutHint: "⌘,", keywords: ["settings", "preferences"] ) @@ -3617,16 +3626,16 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.checkForUpdates", - title: constant("Check for Updates"), - subtitle: constant("Global"), + title: constant(String(localized: "command.checkForUpdates.title", defaultValue: "Check for Updates")), + subtitle: constant(String(localized: "command.checkForUpdates.subtitle", defaultValue: "Global")), keywords: ["update", "upgrade", "release"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.applyUpdateIfAvailable", - title: constant("Apply Update (If Available)"), - subtitle: constant("Global"), + title: constant(String(localized: "command.applyUpdateIfAvailable.title", defaultValue: "Apply Update (If Available)")), + subtitle: constant(String(localized: "command.applyUpdateIfAvailable.subtitle", defaultValue: "Global")), keywords: ["apply", "install", "update", "available"], when: { $0.bool(CommandPaletteContextKeys.updateHasAvailable) } ) @@ -3634,16 +3643,16 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.attemptUpdate", - title: constant("Attempt Update"), - subtitle: constant("Global"), + title: constant(String(localized: "command.attemptUpdate.title", defaultValue: "Attempt Update")), + subtitle: constant(String(localized: "command.attemptUpdate.subtitle", defaultValue: "Global")), keywords: ["attempt", "check", "update", "upgrade", "release"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.restartSocketListener", - title: constant("Restart CLI Listener"), - subtitle: constant("Global"), + title: constant(String(localized: "command.restartSocketListener.title", defaultValue: "Restart CLI Listener")), + subtitle: constant(String(localized: "command.restartSocketListener.subtitle", defaultValue: "Global")), keywords: ["restart", "socket", "listener", "cli", "cmux", "control"] ) ) @@ -3651,7 +3660,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.renameWorkspace", - title: constant("Rename Workspace…"), + title: constant(String(localized: "command.renameWorkspace.title", defaultValue: "Rename Workspace…")), subtitle: workspaceSubtitle, keywords: ["rename", "workspace", "title"], dismissOnRun: false, @@ -3661,7 +3670,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.clearWorkspaceName", - title: constant("Clear Workspace Name"), + title: constant(String(localized: "command.clearWorkspaceName.title", defaultValue: "Clear Workspace Name")), subtitle: workspaceSubtitle, keywords: ["clear", "workspace", "name"], when: { @@ -3674,7 +3683,7 @@ struct ContentView: View { CommandPaletteCommandContribution( commandId: "palette.toggleWorkspacePin", title: { context in - context.bool(CommandPaletteContextKeys.workspaceShouldPin) ? "Pin Workspace" : "Unpin Workspace" + context.bool(CommandPaletteContextKeys.workspaceShouldPin) ? String(localized: "command.pinWorkspace.title", defaultValue: "Pin Workspace") : String(localized: "command.unpinWorkspace.title", defaultValue: "Unpin Workspace") }, subtitle: workspaceSubtitle, keywords: ["workspace", "pin", "pinned"], @@ -3684,8 +3693,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.nextWorkspace", - title: constant("Next Workspace"), - subtitle: constant("Workspace Navigation"), + title: constant(String(localized: "command.nextWorkspace.title", defaultValue: "Next Workspace")), + subtitle: constant(String(localized: "command.nextWorkspace.subtitle", defaultValue: "Workspace Navigation")), keywords: ["next", "workspace", "navigate"], when: { $0.bool(CommandPaletteContextKeys.hasWorkspace) } ) @@ -3693,8 +3702,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.previousWorkspace", - title: constant("Previous Workspace"), - subtitle: constant("Workspace Navigation"), + title: constant(String(localized: "command.previousWorkspace.title", defaultValue: "Previous Workspace")), + subtitle: constant(String(localized: "command.previousWorkspace.subtitle", defaultValue: "Workspace Navigation")), keywords: ["previous", "workspace", "navigate"], when: { $0.bool(CommandPaletteContextKeys.hasWorkspace) } ) @@ -3703,7 +3712,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.renameTab", - title: constant("Rename Tab…"), + title: constant(String(localized: "command.renameTab.title", defaultValue: "Rename Tab…")), subtitle: panelSubtitle, keywords: ["rename", "tab", "title"], dismissOnRun: false, @@ -3713,7 +3722,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.clearTabName", - title: constant("Clear Tab Name"), + title: constant(String(localized: "command.clearTabName.title", defaultValue: "Clear Tab Name")), subtitle: panelSubtitle, keywords: ["clear", "tab", "name"], when: { @@ -3726,7 +3735,7 @@ struct ContentView: View { CommandPaletteCommandContribution( commandId: "palette.toggleTabPin", title: { context in - context.bool(CommandPaletteContextKeys.panelShouldPin) ? "Pin Tab" : "Unpin Tab" + context.bool(CommandPaletteContextKeys.panelShouldPin) ? String(localized: "command.pinTab.title", defaultValue: "Pin Tab") : String(localized: "command.unpinTab.title", defaultValue: "Unpin Tab") }, subtitle: panelSubtitle, keywords: ["tab", "pin", "pinned"], @@ -3737,7 +3746,7 @@ struct ContentView: View { CommandPaletteCommandContribution( commandId: "palette.toggleTabUnread", title: { context in - context.bool(CommandPaletteContextKeys.panelHasUnread) ? "Mark Tab as Read" : "Mark Tab as Unread" + context.bool(CommandPaletteContextKeys.panelHasUnread) ? String(localized: "command.markTabRead.title", defaultValue: "Mark Tab as Read") : String(localized: "command.markTabUnread.title", defaultValue: "Mark Tab as Unread") }, subtitle: panelSubtitle, keywords: ["tab", "read", "unread", "notification"], @@ -3747,8 +3756,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.nextTabInPane", - title: constant("Next Tab in Pane"), - subtitle: constant("Tab Navigation"), + title: constant(String(localized: "command.nextTabInPane.title", defaultValue: "Next Tab in Pane")), + subtitle: constant(String(localized: "command.nextTabInPane.subtitle", defaultValue: "Tab Navigation")), keywords: ["next", "tab", "pane"], when: { $0.bool(CommandPaletteContextKeys.hasFocusedPanel) } ) @@ -3756,8 +3765,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.previousTabInPane", - title: constant("Previous Tab in Pane"), - subtitle: constant("Tab Navigation"), + title: constant(String(localized: "command.previousTabInPane.title", defaultValue: "Previous Tab in Pane")), + subtitle: constant(String(localized: "command.previousTabInPane.subtitle", defaultValue: "Tab Navigation")), keywords: ["previous", "tab", "pane"], when: { $0.bool(CommandPaletteContextKeys.hasFocusedPanel) } ) @@ -3766,7 +3775,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.openWorkspacePullRequests", - title: constant("Open All Workspace PR Links"), + title: constant(String(localized: "command.openWorkspacePRLinks.title", defaultValue: "Open All Workspace PR Links")), subtitle: workspaceSubtitle, keywords: ["pull", "request", "review", "merge", "pr", "mr", "open", "links", "workspace"], when: { @@ -3778,7 +3787,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserBack", - title: constant("Back"), + title: constant(String(localized: "command.browserBack.title", defaultValue: "Back")), subtitle: browserPanelSubtitle, shortcutHint: "⌘[", keywords: ["browser", "back", "history"], @@ -3788,7 +3797,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserForward", - title: constant("Forward"), + title: constant(String(localized: "command.browserForward.title", defaultValue: "Forward")), subtitle: browserPanelSubtitle, shortcutHint: "⌘]", keywords: ["browser", "forward", "history"], @@ -3798,7 +3807,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserReload", - title: constant("Reload Page"), + title: constant(String(localized: "command.browserReload.title", defaultValue: "Reload Page")), subtitle: browserPanelSubtitle, shortcutHint: "⌘R", keywords: ["browser", "reload", "refresh"], @@ -3808,7 +3817,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserOpenDefault", - title: constant("Open Current Page in Default Browser"), + title: constant(String(localized: "command.browserOpenDefault.title", defaultValue: "Open Current Page in Default Browser")), subtitle: browserPanelSubtitle, keywords: ["open", "default", "external", "browser"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3817,7 +3826,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserFocusAddressBar", - title: constant("Focus Address Bar"), + title: constant(String(localized: "command.browserFocusAddressBar.title", defaultValue: "Focus Address Bar")), subtitle: browserPanelSubtitle, shortcutHint: "⌘L", keywords: ["browser", "address", "omnibar", "url"], @@ -3827,7 +3836,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserToggleDevTools", - title: constant("Toggle Developer Tools"), + title: constant(String(localized: "command.browserToggleDevTools.title", defaultValue: "Toggle Developer Tools")), subtitle: browserPanelSubtitle, keywords: ["browser", "devtools", "inspector"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3836,7 +3845,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserConsole", - title: constant("Show JavaScript Console"), + title: constant(String(localized: "command.browserConsole.title", defaultValue: "Show JavaScript Console")), subtitle: browserPanelSubtitle, keywords: ["browser", "console", "javascript"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3845,7 +3854,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserZoomIn", - title: constant("Zoom In"), + title: constant(String(localized: "command.browserZoomIn.title", defaultValue: "Zoom In")), subtitle: browserPanelSubtitle, keywords: ["browser", "zoom", "in"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3854,7 +3863,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserZoomOut", - title: constant("Zoom Out"), + title: constant(String(localized: "command.browserZoomOut.title", defaultValue: "Zoom Out")), subtitle: browserPanelSubtitle, keywords: ["browser", "zoom", "out"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3863,7 +3872,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserZoomReset", - title: constant("Actual Size"), + title: constant(String(localized: "command.browserZoomReset.title", defaultValue: "Actual Size")), subtitle: browserPanelSubtitle, keywords: ["browser", "zoom", "reset", "actual size"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3872,8 +3881,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserClearHistory", - title: constant("Clear Browser History"), - subtitle: constant("Browser"), + title: constant(String(localized: "command.browserClearHistory.title", defaultValue: "Clear Browser History")), + subtitle: constant(String(localized: "command.browserClearHistory.subtitle", defaultValue: "Browser")), keywords: ["browser", "history", "clear"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3881,8 +3890,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserSplitRight", - title: constant("Split Browser Right"), - subtitle: constant("Browser Layout"), + title: constant(String(localized: "command.browserSplitRight.title", defaultValue: "Split Browser Right")), + subtitle: constant(String(localized: "command.browserSplitRight.subtitle", defaultValue: "Browser Layout")), keywords: ["browser", "split", "right"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3890,8 +3899,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserSplitDown", - title: constant("Split Browser Down"), - subtitle: constant("Browser Layout"), + title: constant(String(localized: "command.browserSplitDown.title", defaultValue: "Split Browser Down")), + subtitle: constant(String(localized: "command.browserSplitDown.subtitle", defaultValue: "Browser Layout")), keywords: ["browser", "split", "down"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3899,8 +3908,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserDuplicateRight", - title: constant("Duplicate Browser to the Right"), - subtitle: constant("Browser Layout"), + title: constant(String(localized: "command.browserDuplicateRight.title", defaultValue: "Duplicate Browser to the Right")), + subtitle: constant(String(localized: "command.browserDuplicateRight.subtitle", defaultValue: "Browser Layout")), keywords: ["browser", "duplicate", "clone", "split"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3923,7 +3932,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.vscodeServeWebStop", - title: constant("Stop VS Code Inline Server"), + title: constant(String(localized: "command.vscodeServeWebStop.title", defaultValue: "Stop VS Code Inline Server")), subtitle: terminalPanelSubtitle, keywords: ["vscode", "inline", "serve-web", "stop", "server"], when: { context in @@ -3935,7 +3944,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.vscodeServeWebRestart", - title: constant("Restart VS Code Inline Server"), + title: constant(String(localized: "command.vscodeServeWebRestart.title", defaultValue: "Restart VS Code Inline Server")), subtitle: terminalPanelSubtitle, keywords: ["vscode", "inline", "serve-web", "restart", "server"], when: { context in @@ -3947,7 +3956,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFind", - title: constant("Find…"), + title: constant(String(localized: "command.terminalFind.title", defaultValue: "Find…")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘F", keywords: ["terminal", "find", "search"], @@ -3957,7 +3966,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFindNext", - title: constant("Find Next"), + title: constant(String(localized: "command.terminalFindNext.title", defaultValue: "Find Next")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘G", keywords: ["terminal", "find", "next", "search"], @@ -3967,7 +3976,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFindPrevious", - title: constant("Find Previous"), + title: constant(String(localized: "command.terminalFindPrevious.title", defaultValue: "Find Previous")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘⇧G", keywords: ["terminal", "find", "previous", "search"], @@ -3977,7 +3986,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalHideFind", - title: constant("Hide Find Bar"), + title: constant(String(localized: "command.terminalHideFind.title", defaultValue: "Hide Find Bar")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘⇧F", keywords: ["terminal", "hide", "find", "search"], @@ -3987,7 +3996,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalUseSelectionForFind", - title: constant("Use Selection for Find"), + title: constant(String(localized: "command.terminalUseSelectionForFind.title", defaultValue: "Use Selection for Find")), subtitle: terminalPanelSubtitle, keywords: ["terminal", "selection", "find"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } @@ -3996,8 +4005,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitRight", - title: constant("Split Right"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitRight.title", defaultValue: "Split Right")), + subtitle: constant(String(localized: "command.terminalSplitRight.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "right"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4005,8 +4014,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitDown", - title: constant("Split Down"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitDown.title", defaultValue: "Split Down")), + subtitle: constant(String(localized: "command.terminalSplitDown.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "down"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4014,8 +4023,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitBrowserRight", - title: constant("Split Browser Right"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitBrowserRight.title", defaultValue: "Split Browser Right")), + subtitle: constant(String(localized: "command.terminalSplitBrowserRight.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "browser", "right"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4023,8 +4032,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitBrowserDown", - title: constant("Split Browser Down"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitBrowserDown.title", defaultValue: "Split Browser Down")), + subtitle: constant(String(localized: "command.terminalSplitBrowserDown.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "browser", "down"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4032,8 +4041,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.toggleSplitZoom", - title: constant("Toggle Pane Zoom"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.toggleSplitZoom.title", defaultValue: "Toggle Pane Zoom")), + subtitle: constant(String(localized: "command.toggleSplitZoom.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "pane", "split", "zoom", "maximize"], when: { context in context.bool(CommandPaletteContextKeys.panelIsTerminal) && @@ -4044,7 +4053,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.equalizeSplits", - title: constant("Equalize Splits"), + title: constant(String(localized: "command.equalizeSplits.title", defaultValue: "Equalize Splits")), subtitle: workspaceSubtitle, keywords: ["split", "equalize", "balance", "divider", "layout"], when: { $0.bool(CommandPaletteContextKeys.workspaceHasSplits) } @@ -4065,8 +4074,8 @@ struct ContentView: View { panel.canChooseFiles = false panel.canChooseDirectories = true panel.allowsMultipleSelection = false - panel.title = "Open Folder" - panel.prompt = "Open" + panel.title = String(localized: "panel.openFolder.title", defaultValue: "Open Folder") + panel.prompt = String(localized: "panel.openFolder.prompt", defaultValue: "Open") if panel.runModal() == .OK, let url = panel.url { tabManager.addWorkspace(workingDirectory: url.path) } @@ -4352,7 +4361,7 @@ struct ContentView: View { return custom } let title = workspace.title.trimmingCharacters(in: .whitespacesAndNewlines) - return title.isEmpty ? "Workspace" : title + return title.isEmpty ? String(localized: "workspace.displayName.fallback", defaultValue: "Workspace") : title } private func panelDisplayName(workspace: Workspace, panelId: UUID, fallback: String) -> String { @@ -4361,7 +4370,7 @@ struct ContentView: View { return title } let trimmedFallback = fallback.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmedFallback.isEmpty ? "Tab" : trimmedFallback + return trimmedFallback.isEmpty ? String(localized: "panel.displayName.fallback", defaultValue: "Tab") : trimmedFallback } private func commandPaletteSelectedIndex(resultCount: Int) -> Int { @@ -6551,6 +6560,10 @@ private struct TabItemView: View { } var body: some View { + let closeWorkspaceTooltip = String(localized: "sidebar.closeWorkspace.tooltip", defaultValue: "Close Workspace") + let accessibilityHintText = String(localized: "sidebar.workspace.accessibilityHint", defaultValue: "Activate to focus this workspace. Drag to reorder, or use Move Up and Move Down actions.") + let moveUpActionText = String(localized: "sidebar.workspace.moveUpAction", defaultValue: "Move Up") + let moveDownActionText = String(localized: "sidebar.workspace.moveDownAction", defaultValue: "Move Down") let latestNotificationSubtitle = latestNotificationText let orderedPanelIds: [UUID]? = (sidebarShowBranchDirectory || sidebarShowPullRequest) ? tab.sidebarOrderedPanelIds() @@ -6630,7 +6643,7 @@ private struct TabItemView: View { .foregroundColor(activeSecondaryColor(0.7)) } .buttonStyle(.plain) - .help(KeyboardShortcutSettings.Action.closeWorkspace.tooltip("Close Workspace")) + .help(KeyboardShortcutSettings.Action.closeWorkspace.tooltip(closeWorkspaceTooltip)) .frame(width: 16, height: 16, alignment: .center) .opacity(showCloseButton && !showsWorkspaceShortcutHint ? 1 : 0) .allowsHitTesting(showCloseButton && !showsWorkspaceShortcutHint) @@ -6803,7 +6816,7 @@ private struct TabItemView: View { .foregroundColor(pullRequestForegroundColor) } .buttonStyle(.plain) - .help("Open \(pullRequest.label) #\(pullRequest.number)") + .help(String(localized: "sidebar.pullRequest.openTooltip", defaultValue: "Open \(pullRequest.label) #\(pullRequest.number)")) } } } @@ -6903,165 +6916,190 @@ private struct TabItemView: View { } .accessibilityElement(children: .combine) .accessibilityLabel(Text(accessibilityTitle)) - .accessibilityHint(Text("Activate to focus this workspace. Drag to reorder, or use Move Up and Move Down actions.")) - .accessibilityAction(named: Text("Move Up")) { + .accessibilityHint(Text(accessibilityHintText)) + .accessibilityAction(named: Text(moveUpActionText)) { moveBy(-1) } - .accessibilityAction(named: Text("Move Down")) { + .accessibilityAction(named: Text(moveDownActionText)) { moveBy(1) } - .contextMenu { - let targetIds = contextTargetIds() - let tabColorPalette = WorkspaceTabColorSettings.palette() - let shouldPin = !tab.isPinned - let pinLabel = targetIds.count > 1 - ? (shouldPin ? "Pin Workspaces" : "Unpin Workspaces") - : (shouldPin ? "Pin Workspace" : "Unpin Workspace") - let closeLabel = targetIds.count > 1 ? "Close Workspaces" : "Close Workspace" - let markReadLabel = targetIds.count > 1 ? "Mark Workspaces as Read" : "Mark Workspace as Read" - let markUnreadLabel = targetIds.count > 1 ? "Mark Workspaces as Unread" : "Mark Workspace as Unread" - let renameWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .renameWorkspace) - let closeWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .closeWorkspace) - Button(pinLabel) { - for id in targetIds { - if let tab = tabManager.tabs.first(where: { $0.id == id }) { - tabManager.setPinned(tab, pinned: shouldPin) - } - } - syncSelectionAfterMutation() - } + .contextMenu { workspaceContextMenu } + } - if let key = renameWorkspaceShortcut.keyEquivalent { - Button("Rename Workspace…") { - promptRename() - } - .keyboardShortcut(key, modifiers: renameWorkspaceShortcut.eventModifiers) - } else { - Button("Rename Workspace…") { - promptRename() + private func contextMenuLabel(multi: String, single: String, isMulti: Bool) -> String { + isMulti ? multi : single + } + + @ViewBuilder + private var workspaceContextMenu: some View { + let targetIds = contextTargetIds() + let isMulti = targetIds.count > 1 + let tabColorPalette = WorkspaceTabColorSettings.palette() + let shouldPin = !tab.isPinned + let pinLabel = shouldPin + ? contextMenuLabel( + multi: String(localized: "contextMenu.pinWorkspaces", defaultValue: "Pin Workspaces"), + single: String(localized: "contextMenu.pinWorkspace", defaultValue: "Pin Workspace"), + isMulti: isMulti) + : contextMenuLabel( + multi: String(localized: "contextMenu.unpinWorkspaces", defaultValue: "Unpin Workspaces"), + single: String(localized: "contextMenu.unpinWorkspace", defaultValue: "Unpin Workspace"), + isMulti: isMulti) + let closeLabel = contextMenuLabel( + multi: String(localized: "contextMenu.closeWorkspaces", defaultValue: "Close Workspaces"), + single: String(localized: "contextMenu.closeWorkspace", defaultValue: "Close Workspace"), + isMulti: isMulti) + let markReadLabel = contextMenuLabel( + multi: String(localized: "contextMenu.markWorkspacesRead", defaultValue: "Mark Workspaces as Read"), + single: String(localized: "contextMenu.markWorkspaceRead", defaultValue: "Mark Workspace as Read"), + isMulti: isMulti) + let markUnreadLabel = contextMenuLabel( + multi: String(localized: "contextMenu.markWorkspacesUnread", defaultValue: "Mark Workspaces as Unread"), + single: String(localized: "contextMenu.markWorkspaceUnread", defaultValue: "Mark Workspace as Unread"), + isMulti: isMulti) + let renameWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .renameWorkspace) + let closeWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .closeWorkspace) + Button(pinLabel) { + for id in targetIds { + if let tab = tabManager.tabs.first(where: { $0.id == id }) { + tabManager.setPinned(tab, pinned: shouldPin) } } - - if tab.hasCustomTitle { - Button("Remove Custom Workspace Name") { - tabManager.clearCustomTitle(tabId: tab.id) - } - } - - Menu("Workspace Color") { - if tab.customColor != nil { - Button { - applyTabColor(nil, targetIds: targetIds) - } label: { - Label("Clear Color", systemImage: "xmark.circle") - } - } - - Button { - promptCustomColor(targetIds: targetIds) - } label: { - Label("Choose Custom Color…", systemImage: "paintpalette") - } - - if !tabColorPalette.isEmpty { - Divider() - } - - ForEach(tabColorPalette, id: \.id) { entry in - Button { - applyTabColor(entry.hex, targetIds: targetIds) - } label: { - Label { - Text(entry.name) - } icon: { - Image(nsImage: coloredCircleImage(color: tabColorSwatchColor(for: entry.hex))) - } - } - } - } - - Divider() - - Button("Move Up") { - moveBy(-1) - } - .disabled(index == 0) - - Button("Move Down") { - moveBy(1) - } - .disabled(index >= tabManager.tabs.count - 1) - - Button("Move to Top") { - tabManager.moveTabsToTop(Set(targetIds)) - syncSelectionAfterMutation() - } - .disabled(targetIds.isEmpty) - - let referenceWindowId = AppDelegate.shared?.windowId(for: tabManager) - let windowMoveTargets = AppDelegate.shared?.windowMoveTargets(referenceWindowId: referenceWindowId) ?? [] - let moveMenuTitle = targetIds.count > 1 ? "Move Workspaces to Window" : "Move Workspace to Window" - Menu(moveMenuTitle) { - Button("New Window") { - moveWorkspacesToNewWindow(targetIds) - } - .disabled(targetIds.isEmpty) - - if !windowMoveTargets.isEmpty { - Divider() - } - - ForEach(windowMoveTargets) { target in - Button(target.label) { - moveWorkspaces(targetIds, toWindow: target.windowId) - } - .disabled(target.isCurrentWindow || targetIds.isEmpty) - } - } - .disabled(targetIds.isEmpty) - - Divider() - - if let key = closeWorkspaceShortcut.keyEquivalent { - Button(closeLabel) { - closeTabs(targetIds, allowPinned: true) - } - .keyboardShortcut(key, modifiers: closeWorkspaceShortcut.eventModifiers) - .disabled(targetIds.isEmpty) - } else { - Button(closeLabel) { - closeTabs(targetIds, allowPinned: true) - } - .disabled(targetIds.isEmpty) - } - - Button("Close Other Workspaces") { - closeOtherTabs(targetIds) - } - .disabled(tabManager.tabs.count <= 1 || targetIds.count == tabManager.tabs.count) - - Button("Close Workspaces Below") { - closeTabsBelow(tabId: tab.id) - } - .disabled(index >= tabManager.tabs.count - 1) - - Button("Close Workspaces Above") { - closeTabsAbove(tabId: tab.id) - } - .disabled(index == 0) - - Divider() - - Button(markReadLabel) { - markTabsRead(targetIds) - } - .disabled(!hasUnreadNotifications(in: targetIds)) - - Button(markUnreadLabel) { - markTabsUnread(targetIds) - } - .disabled(!hasReadNotifications(in: targetIds)) + syncSelectionAfterMutation() } + + if let key = renameWorkspaceShortcut.keyEquivalent { + Button(String(localized: "contextMenu.renameWorkspace", defaultValue: "Rename Workspace…")) { + promptRename() + } + .keyboardShortcut(key, modifiers: renameWorkspaceShortcut.eventModifiers) + } else { + Button(String(localized: "contextMenu.renameWorkspace", defaultValue: "Rename Workspace…")) { + promptRename() + } + } + + if tab.hasCustomTitle { + Button(String(localized: "contextMenu.removeCustomWorkspaceName", defaultValue: "Remove Custom Workspace Name")) { + tabManager.clearCustomTitle(tabId: tab.id) + } + } + + Menu(String(localized: "contextMenu.workspaceColor", defaultValue: "Workspace Color")) { + if tab.customColor != nil { + Button { + applyTabColor(nil, targetIds: targetIds) + } label: { + Label(String(localized: "contextMenu.clearColor", defaultValue: "Clear Color"), systemImage: "xmark.circle") + } + } + + Button { + promptCustomColor(targetIds: targetIds) + } label: { + Label(String(localized: "contextMenu.chooseCustomColor", defaultValue: "Choose Custom Color…"), systemImage: "paintpalette") + } + + if !tabColorPalette.isEmpty { + Divider() + } + + ForEach(tabColorPalette, id: \.id) { entry in + Button { + applyTabColor(entry.hex, targetIds: targetIds) + } label: { + Label { + Text(entry.name) + } icon: { + Image(nsImage: coloredCircleImage(color: tabColorSwatchColor(for: entry.hex))) + } + } + } + } + + Divider() + + Button(String(localized: "contextMenu.moveUp", defaultValue: "Move Up")) { + moveBy(-1) + } + .disabled(index == 0) + + Button(String(localized: "contextMenu.moveDown", defaultValue: "Move Down")) { + moveBy(1) + } + .disabled(index >= tabManager.tabs.count - 1) + + Button(String(localized: "contextMenu.moveToTop", defaultValue: "Move to Top")) { + tabManager.moveTabsToTop(Set(targetIds)) + syncSelectionAfterMutation() + } + .disabled(targetIds.isEmpty) + + let referenceWindowId = AppDelegate.shared?.windowId(for: tabManager) + let windowMoveTargets = AppDelegate.shared?.windowMoveTargets(referenceWindowId: referenceWindowId) ?? [] + let moveMenuTitle = targetIds.count > 1 + ? String(localized: "contextMenu.moveWorkspacesToWindow", defaultValue: "Move Workspaces to Window") + : String(localized: "contextMenu.moveWorkspaceToWindow", defaultValue: "Move Workspace to Window") + Menu(moveMenuTitle) { + Button(String(localized: "contextMenu.newWindow", defaultValue: "New Window")) { + moveWorkspacesToNewWindow(targetIds) + } + .disabled(targetIds.isEmpty) + + if !windowMoveTargets.isEmpty { + Divider() + } + + ForEach(windowMoveTargets) { target in + Button(target.label) { + moveWorkspaces(targetIds, toWindow: target.windowId) + } + .disabled(target.isCurrentWindow || targetIds.isEmpty) + } + } + .disabled(targetIds.isEmpty) + + Divider() + + if let key = closeWorkspaceShortcut.keyEquivalent { + Button(closeLabel) { + closeTabs(targetIds, allowPinned: true) + } + .keyboardShortcut(key, modifiers: closeWorkspaceShortcut.eventModifiers) + .disabled(targetIds.isEmpty) + } else { + Button(closeLabel) { + closeTabs(targetIds, allowPinned: true) + } + .disabled(targetIds.isEmpty) + } + + Button(String(localized: "contextMenu.closeOtherWorkspaces", defaultValue: "Close Other Workspaces")) { + closeOtherTabs(targetIds) + } + .disabled(tabManager.tabs.count <= 1 || targetIds.count == tabManager.tabs.count) + + Button(String(localized: "contextMenu.closeWorkspacesBelow", defaultValue: "Close Workspaces Below")) { + closeTabsBelow(tabId: tab.id) + } + .disabled(index >= tabManager.tabs.count - 1) + + Button(String(localized: "contextMenu.closeWorkspacesAbove", defaultValue: "Close Workspaces Above")) { + closeTabsAbove(tabId: tab.id) + } + .disabled(index == 0) + + Divider() + + Button(markReadLabel) { + markTabsRead(targetIds) + } + .disabled(!hasUnreadNotifications(in: targetIds)) + + Button(markUnreadLabel) { + markTabsUnread(targetIds) + } + .disabled(!hasReadNotifications(in: targetIds)) } private var backgroundColor: Color { @@ -7126,7 +7164,7 @@ private struct TabItemView: View { } private var accessibilityTitle: String { - "\(tab.title), workspace \(index + 1) of \(tabManager.tabs.count)" + String(localized: "accessibility.workspacePosition", defaultValue: "\(tab.title), workspace \(index + 1) of \(tabManager.tabs.count)") } private func moveBy(_ delta: Int) { @@ -7412,9 +7450,9 @@ private struct TabItemView: View { private func pullRequestStatusLabel(_ status: SidebarPullRequestStatus) -> String { switch status { - case .open: return "open" - case .merged: return "merged" - case .closed: return "closed" + case .open: return String(localized: "sidebar.pullRequest.statusOpen", defaultValue: "open") + case .merged: return String(localized: "sidebar.pullRequest.statusMerged", defaultValue: "merged") + case .closed: return String(localized: "sidebar.pullRequest.statusClosed", defaultValue: "closed") } } @@ -7555,16 +7593,16 @@ private struct TabItemView: View { private func promptCustomColor(targetIds: [UUID]) { let alert = NSAlert() - alert.messageText = "Custom Workspace Color" - alert.informativeText = "Enter a hex color in the format #RRGGBB." + alert.messageText = String(localized: "alert.customColor.title", defaultValue: "Custom Workspace Color") + alert.informativeText = String(localized: "alert.customColor.message", defaultValue: "Enter a hex color in the format #RRGGBB.") let seed = tab.customColor ?? WorkspaceTabColorSettings.customColors().first ?? "" let input = NSTextField(string: seed) input.placeholderString = "#1565C0" input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Apply") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "alert.customColor.apply", defaultValue: "Apply")) + alert.addButton(withTitle: String(localized: "alert.customColor.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input @@ -7585,27 +7623,27 @@ private struct TabItemView: View { private func showInvalidColorAlert(_ value: String) { let alert = NSAlert() alert.alertStyle = .warning - alert.messageText = "Invalid Color" + alert.messageText = String(localized: "alert.invalidColor.title", defaultValue: "Invalid Color") let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { - alert.informativeText = "Enter a hex color in the format #RRGGBB." + alert.informativeText = String(localized: "alert.invalidColor.emptyMessage", defaultValue: "Enter a hex color in the format #RRGGBB.") } else { - alert.informativeText = "\"\(trimmed)\" is not a valid hex color. Use #RRGGBB." + alert.informativeText = String(localized: "alert.invalidColor.invalidMessage", defaultValue: "\"\(trimmed)\" is not a valid hex color. Use #RRGGBB.") } - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: String(localized: "alert.invalidColor.ok", defaultValue: "OK")) _ = alert.runModal() } private func promptRename() { let alert = NSAlert() - alert.messageText = "Rename Workspace" - alert.informativeText = "Enter a custom name for this workspace." + alert.messageText = String(localized: "alert.renameWorkspace.title", defaultValue: "Rename Workspace") + alert.informativeText = String(localized: "alert.renameWorkspace.message", defaultValue: "Enter a custom name for this workspace.") let input = NSTextField(string: tab.customTitle ?? tab.title) - input.placeholderString = "Workspace name" + input.placeholderString = String(localized: "alert.renameWorkspace.placeholder", defaultValue: "Workspace name") input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Rename") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "alert.renameWorkspace.rename", defaultValue: "Rename")) + alert.addButton(withTitle: String(localized: "alert.renameWorkspace.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input DispatchQueue.main.async { @@ -7633,7 +7671,7 @@ private struct SidebarMetadataRows: View { } if shouldShowToggle { - Button(isExpanded ? "Show less" : "Show more") { + Button(isExpanded ? String(localized: "sidebar.metadata.showLess", defaultValue: "Show less") : String(localized: "sidebar.metadata.showMore", defaultValue: "Show more")) { onFocus() withAnimation(.easeInOut(duration: 0.15)) { isExpanded.toggle() @@ -7786,7 +7824,7 @@ private struct SidebarMetadataMarkdownBlocks: View { } if shouldShowToggle { - Button(isExpanded ? "Show less details" : "Show more details") { + Button(isExpanded ? String(localized: "sidebar.metadata.showLessDetails", defaultValue: "Show less details") : String(localized: "sidebar.metadata.showMoreDetails", defaultValue: "Show more details")) { onFocus() withAnimation(.easeInOut(duration: 0.15)) { isExpanded.toggle() @@ -8479,7 +8517,7 @@ private struct DraggableFolderIcon: View { var body: some View { DraggableFolderIconRepresentable(directory: directory) .frame(width: 16, height: 16) - .help("Drag to open in Finder or another app") + .help(String(localized: "sidebar.folderIcon.dragHint", defaultValue: "Drag to open in Finder or another app")) .onTapGesture(count: 2) { NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: directory) } @@ -8644,7 +8682,7 @@ final class DraggableFolderNSView: NSView, NSDraggingSource { if let volumeName = try? URL(fileURLWithPath: "/").resourceValues(forKeys: [.volumeNameKey]).volumeName { displayName = volumeName } else { - displayName = "Macintosh HD" + displayName = String(localized: "sidebar.pathMenu.macintoshHD", defaultValue: "Macintosh HD") } } else { displayName = FileManager.default.displayName(atPath: pathURL.path) @@ -8908,19 +8946,19 @@ enum SidebarMaterialOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .none: return "None" - case .liquidGlass: return "Liquid Glass (macOS 26+)" - case .sidebar: return "Sidebar" - case .hudWindow: return "HUD Window" - case .menu: return "Menu" - case .popover: return "Popover" - case .underWindowBackground: return "Under Window" - case .windowBackground: return "Window Background" - case .contentBackground: return "Content Background" - case .fullScreenUI: return "Full Screen UI" - case .sheet: return "Sheet" - case .headerView: return "Header View" - case .toolTip: return "Tool Tip" + case .none: return String(localized: "settings.material.none", defaultValue: "None") + case .liquidGlass: return String(localized: "settings.material.liquidGlass", defaultValue: "Liquid Glass (macOS 26+)") + case .sidebar: return String(localized: "settings.material.sidebar", defaultValue: "Sidebar") + case .hudWindow: return String(localized: "settings.material.hudWindow", defaultValue: "HUD Window") + case .menu: return String(localized: "settings.material.menu", defaultValue: "Menu") + case .popover: return String(localized: "settings.material.popover", defaultValue: "Popover") + case .underWindowBackground: return String(localized: "settings.material.underWindow", defaultValue: "Under Window") + case .windowBackground: return String(localized: "settings.material.windowBackground", defaultValue: "Window Background") + case .contentBackground: return String(localized: "settings.material.contentBackground", defaultValue: "Content Background") + case .fullScreenUI: return String(localized: "settings.material.fullScreenUI", defaultValue: "Full Screen UI") + case .sheet: return String(localized: "settings.material.sheet", defaultValue: "Sheet") + case .headerView: return String(localized: "settings.material.headerView", defaultValue: "Header View") + case .toolTip: return String(localized: "settings.material.toolTip", defaultValue: "Tool Tip") } } @@ -8956,8 +8994,8 @@ enum SidebarBlendModeOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .behindWindow: return "Behind Window" - case .withinWindow: return "Within Window" + case .behindWindow: return String(localized: "settings.blendMode.behindWindow", defaultValue: "Behind Window") + case .withinWindow: return String(localized: "settings.blendMode.withinWindow", defaultValue: "Within Window") } } @@ -8978,9 +9016,9 @@ enum SidebarStateOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .active: return "Active" - case .inactive: return "Inactive" - case .followWindow: return "Follow Window" + case .active: return String(localized: "settings.state.active", defaultValue: "Active") + case .inactive: return String(localized: "settings.state.inactive", defaultValue: "Inactive") + case .followWindow: return String(localized: "settings.state.followWindow", defaultValue: "Follow Window") } } @@ -9005,12 +9043,12 @@ enum SidebarPresetOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .nativeSidebar: return "Native Sidebar" - case .glassBehind: return "Raycast Gray" - case .softBlur: return "Soft Blur" - case .popoverGlass: return "Popover Glass" - case .hudGlass: return "HUD Glass" - case .underWindow: return "Under Window" + case .nativeSidebar: return String(localized: "settings.preset.nativeSidebar", defaultValue: "Native Sidebar") + case .glassBehind: return String(localized: "settings.preset.raycastGray", defaultValue: "Raycast Gray") + case .softBlur: return String(localized: "settings.preset.softBlur", defaultValue: "Soft Blur") + case .popoverGlass: return String(localized: "settings.preset.popoverGlass", defaultValue: "Popover Glass") + case .hudGlass: return String(localized: "settings.preset.hudGlass", defaultValue: "HUD Glass") + case .underWindow: return String(localized: "settings.preset.underWindow", defaultValue: "Under Window") } } diff --git a/Sources/Find/SurfaceSearchOverlay.swift b/Sources/Find/SurfaceSearchOverlay.swift index ae2c63c8..44b37433 100644 --- a/Sources/Find/SurfaceSearchOverlay.swift +++ b/Sources/Find/SurfaceSearchOverlay.swift @@ -74,7 +74,7 @@ struct SurfaceSearchOverlay: View { Image(systemName: "chevron.up") } .buttonStyle(SearchButtonStyle()) - .help("Next match (Return)") + .help(String(localized: "search.nextMatch.help", defaultValue: "Next match (Return)")) Button(action: { #if DEBUG @@ -85,7 +85,7 @@ struct SurfaceSearchOverlay: View { Image(systemName: "chevron.down") } .buttonStyle(SearchButtonStyle()) - .help("Previous match (Shift+Return)") + .help(String(localized: "search.previousMatch.help", defaultValue: "Previous match (Shift+Return)")) Button(action: { #if DEBUG @@ -96,7 +96,7 @@ struct SurfaceSearchOverlay: View { Image(systemName: "xmark") } .buttonStyle(SearchButtonStyle()) - .help("Close (Esc)") + .help(String(localized: "search.close.help", defaultValue: "Close (Esc)")) } .padding(8) .background(.background) @@ -288,7 +288,7 @@ private struct SearchTextFieldRepresentable: NSViewRepresentable { func makeNSView(context: Context) -> SearchNativeTextField { let field = SearchNativeTextField(frame: .zero) field.font = .systemFont(ofSize: NSFont.systemFontSize) - field.placeholderString = "Search" + field.placeholderString = String(localized: "search.placeholder", defaultValue: "Search") field.delegate = context.coordinator field.stringValue = text context.coordinator.parentField = field diff --git a/Sources/KeyboardShortcutSettings.swift b/Sources/KeyboardShortcutSettings.swift index 922fe5f2..dae65482 100644 --- a/Sources/KeyboardShortcutSettings.swift +++ b/Sources/KeyboardShortcutSettings.swift @@ -45,35 +45,35 @@ enum KeyboardShortcutSettings { var label: String { switch self { - case .toggleSidebar: return "Toggle Sidebar" - case .newTab: return "New Workspace" - case .newWindow: return "New Window" - case .closeWindow: return "Close Window" - case .openFolder: return "Open Folder" - case .showNotifications: return "Show Notifications" - case .jumpToUnread: return "Jump to Latest Unread" - case .triggerFlash: return "Flash Focused Panel" - case .nextSurface: return "Next Surface" - case .prevSurface: return "Previous Surface" - case .nextSidebarTab: return "Next Workspace" - case .prevSidebarTab: return "Previous Workspace" - case .renameTab: return "Rename Tab" - case .renameWorkspace: return "Rename Workspace" - case .closeWorkspace: return "Close Workspace" - case .newSurface: return "New Surface" - case .toggleTerminalCopyMode: return "Toggle Terminal Copy Mode" - case .focusLeft: return "Focus Pane Left" - case .focusRight: return "Focus Pane Right" - case .focusUp: return "Focus Pane Up" - case .focusDown: return "Focus Pane Down" - case .splitRight: return "Split Right" - case .splitDown: return "Split Down" - case .toggleSplitZoom: return "Toggle Pane Zoom" - case .splitBrowserRight: return "Split Browser Right" - case .splitBrowserDown: return "Split Browser Down" - case .openBrowser: return "Open Browser" - case .toggleBrowserDeveloperTools: return "Toggle Browser Developer Tools" - case .showBrowserJavaScriptConsole: return "Show Browser JavaScript Console" + case .toggleSidebar: return String(localized: "shortcut.toggleSidebar.label", defaultValue: "Toggle Sidebar") + case .newTab: return String(localized: "shortcut.newWorkspace.label", defaultValue: "New Workspace") + case .newWindow: return String(localized: "shortcut.newWindow.label", defaultValue: "New Window") + case .closeWindow: return String(localized: "shortcut.closeWindow.label", defaultValue: "Close Window") + case .openFolder: return String(localized: "shortcut.openFolder.label", defaultValue: "Open Folder") + case .showNotifications: return String(localized: "shortcut.showNotifications.label", defaultValue: "Show Notifications") + case .jumpToUnread: return String(localized: "shortcut.jumpToUnread.label", defaultValue: "Jump to Latest Unread") + case .triggerFlash: return String(localized: "shortcut.flashFocusedPanel.label", defaultValue: "Flash Focused Panel") + case .nextSurface: return String(localized: "shortcut.nextSurface.label", defaultValue: "Next Surface") + case .prevSurface: return String(localized: "shortcut.previousSurface.label", defaultValue: "Previous Surface") + case .nextSidebarTab: return String(localized: "shortcut.nextWorkspace.label", defaultValue: "Next Workspace") + case .prevSidebarTab: return String(localized: "shortcut.previousWorkspace.label", defaultValue: "Previous Workspace") + case .renameTab: return String(localized: "shortcut.renameTab.label", defaultValue: "Rename Tab") + case .renameWorkspace: return String(localized: "shortcut.renameWorkspace.label", defaultValue: "Rename Workspace") + case .closeWorkspace: return String(localized: "shortcut.closeWorkspace.label", defaultValue: "Close Workspace") + case .newSurface: return String(localized: "shortcut.newSurface.label", defaultValue: "New Surface") + case .toggleTerminalCopyMode: return String(localized: "shortcut.toggleTerminalCopyMode.label", defaultValue: "Toggle Terminal Copy Mode") + case .focusLeft: return String(localized: "shortcut.focusPaneLeft.label", defaultValue: "Focus Pane Left") + case .focusRight: return String(localized: "shortcut.focusPaneRight.label", defaultValue: "Focus Pane Right") + case .focusUp: return String(localized: "shortcut.focusPaneUp.label", defaultValue: "Focus Pane Up") + case .focusDown: return String(localized: "shortcut.focusPaneDown.label", defaultValue: "Focus Pane Down") + case .splitRight: return String(localized: "shortcut.splitRight.label", defaultValue: "Split Right") + case .splitDown: return String(localized: "shortcut.splitDown.label", defaultValue: "Split Down") + case .toggleSplitZoom: return String(localized: "shortcut.togglePaneZoom.label", defaultValue: "Toggle Pane Zoom") + case .splitBrowserRight: return String(localized: "shortcut.splitBrowserRight.label", defaultValue: "Split Browser Right") + case .splitBrowserDown: return String(localized: "shortcut.splitBrowserDown.label", defaultValue: "Split Browser Down") + case .openBrowser: return String(localized: "shortcut.openBrowser.label", defaultValue: "Open Browser") + case .toggleBrowserDeveloperTools: return String(localized: "shortcut.toggleBrowserDevTools.label", defaultValue: "Toggle Browser Developer Tools") + case .showBrowserJavaScriptConsole: return String(localized: "shortcut.showBrowserJSConsole.label", defaultValue: "Show Browser JavaScript Console") } } @@ -474,7 +474,7 @@ private class ShortcutRecorderNSButton: NSButton { func updateTitle() { if isRecording { - title = "Press shortcut…" + title = String(localized: "shortcut.pressShortcut.prompt", defaultValue: "Press shortcut…") } else { title = shortcut.displayString } diff --git a/Sources/NotificationsPage.swift b/Sources/NotificationsPage.swift index 53cc8737..370477f0 100644 --- a/Sources/NotificationsPage.swift +++ b/Sources/NotificationsPage.swift @@ -67,7 +67,7 @@ struct NotificationsPage: View { private var header: some View { HStack { - Text("Notifications") + Text(String(localized: "notifications.title", defaultValue: "Notifications")) .font(.title2) .fontWeight(.semibold) @@ -76,7 +76,7 @@ struct NotificationsPage: View { if !notificationStore.notifications.isEmpty { jumpToUnreadButton - Button("Clear All") { + Button(String(localized: "notifications.clearAll", defaultValue: "Clear All")) { notificationStore.clearAll() } .buttonStyle(.bordered) @@ -91,9 +91,9 @@ struct NotificationsPage: View { Image(systemName: "bell.slash") .font(.system(size: 32)) .foregroundColor(.secondary) - Text("No notifications yet") + Text(String(localized: "notifications.empty.title", defaultValue: "No notifications yet")) .font(.headline) - Text("Desktop notifications will appear here for quick review.") + Text(String(localized: "notifications.empty.description", defaultValue: "Desktop notifications will appear here for quick review.")) .font(.subheadline) .foregroundColor(.secondary) } @@ -107,25 +107,25 @@ struct NotificationsPage: View { AppDelegate.shared?.jumpToLatestUnread() }) { HStack(spacing: 6) { - Text("Jump to Latest Unread") + Text(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread")) ShortcutAnnotation(text: jumpToUnreadShortcut.displayString) } } .buttonStyle(.bordered) .keyboardShortcut(key, modifiers: jumpToUnreadShortcut.eventModifiers) - .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip("Jump to Latest Unread")) + .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread"))) .disabled(!hasUnreadNotifications) } else { Button(action: { AppDelegate.shared?.jumpToLatestUnread() }) { HStack(spacing: 6) { - Text("Jump to Latest Unread") + Text(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread")) ShortcutAnnotation(text: jumpToUnreadShortcut.displayString) } } .buttonStyle(.bordered) - .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip("Jump to Latest Unread")) + .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread"))) .disabled(!hasUnreadNotifications) } } diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 37d38fd7..41da6553 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -125,11 +125,11 @@ enum BrowserThemeMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .system: - return "System" + return String(localized: "theme.system", defaultValue: "System") case .light: - return "Light" + return String(localized: "theme.light", defaultValue: "Light") case .dark: - return "Dark" + return String(localized: "theme.dark", defaultValue: "Dark") } } @@ -1474,7 +1474,7 @@ final class BrowserPanel: Panel, ObservableObject { if let url = currentURL { return url.host ?? url.absoluteString } - return "New tab" + return String(localized: "browser.newTab", defaultValue: "New tab") } var displayIcon: String? { @@ -2074,17 +2074,13 @@ final class BrowserPanel: Panel, ObservableObject { let alert = insecureHTTPAlertFactory() alert.alertStyle = .warning - alert.messageText = "Connection isn't secure" - alert.informativeText = """ - \(host) uses plain HTTP, so traffic can be read or modified on the network. - - Open this URL in your default browser, or proceed in cmux. - """ - alert.addButton(withTitle: "Open in Default Browser") - alert.addButton(withTitle: "Proceed in cmux") - alert.addButton(withTitle: "Cancel") + alert.messageText = String(localized: "browser.error.insecure.title", defaultValue: "Connection isn\u{2019}t secure") + alert.informativeText = String(localized: "browser.error.insecure.message", defaultValue: "\(host) uses plain HTTP, so traffic can be read or modified on the network.\n\nOpen this URL in your default browser, or proceed in cmux.") + alert.addButton(withTitle: String(localized: "browser.openInDefaultBrowser", defaultValue: "Open in Default Browser")) + alert.addButton(withTitle: String(localized: "browser.proceedInCmux", defaultValue: "Proceed in cmux")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) alert.showsSuppressionButton = true - alert.suppressionButton?.title = "Always allow this host in cmux" + alert.suppressionButton?.title = String(localized: "browser.alwaysAllowHost", defaultValue: "Always allow this host in cmux") let handleResponse: (NSApplication.ModalResponse) -> Void = { [weak self, weak alert] response in self?.handleInsecureHTTPAlertResponse( @@ -3098,29 +3094,40 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { case (NSURLErrorDomain, NSURLErrorCannotConnectToHost), (NSURLErrorDomain, NSURLErrorCannotFindHost), (NSURLErrorDomain, NSURLErrorTimedOut): - title = "Can\u{2019}t reach this page" - message = "\(failedURL.isEmpty ? "The site" : failedURL) refused to connect. Check that a server is running on this address." + title = String(localized: "browser.error.cantReach.title", defaultValue: "Can\u{2019}t reach this page") + if failedURL.isEmpty { + message = String(localized: "browser.error.cantReach.messageSite", defaultValue: "The site refused to connect. Check that a server is running on this address.") + } else { + message = String(localized: "browser.error.cantReach.messageURL", defaultValue: "\(failedURL) refused to connect. Check that a server is running on this address.") + } case (NSURLErrorDomain, NSURLErrorNotConnectedToInternet), (NSURLErrorDomain, NSURLErrorNetworkConnectionLost): - title = "No internet connection" - message = "Check your network connection and try again." + title = String(localized: "browser.error.noInternet", defaultValue: "No internet connection") + message = String(localized: "browser.error.checkNetwork", defaultValue: "Check your network connection and try again.") case (NSURLErrorDomain, NSURLErrorSecureConnectionFailed), (NSURLErrorDomain, NSURLErrorServerCertificateUntrusted), (NSURLErrorDomain, NSURLErrorServerCertificateHasUnknownRoot), (NSURLErrorDomain, NSURLErrorServerCertificateHasBadDate), (NSURLErrorDomain, NSURLErrorServerCertificateNotYetValid): - title = "Connection isn\u{2019}t secure" - message = "The certificate for this site is invalid." + title = String(localized: "browser.error.insecure.title", defaultValue: "Connection isn\u{2019}t secure") + message = String(localized: "browser.error.invalidCertificate", defaultValue: "The certificate for this site is invalid.") default: - title = "Can\u{2019}t open this page" + title = String(localized: "browser.error.cantOpen.title", defaultValue: "Can\u{2019}t open this page") message = error.localizedDescription } - let escapedURL = failedURL - .replacingOccurrences(of: "&", with: "&") - .replacingOccurrences(of: "<", with: "<") - .replacingOccurrences(of: ">", with: ">") - .replacingOccurrences(of: "\"", with: """) + let escapeHTML: (String) -> String = { value in + value + .replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "<", with: "<") + .replacingOccurrences(of: ">", with: ">") + .replacingOccurrences(of: "\"", with: """) + } + + let escapedTitle = escapeHTML(title) + let escapedMessage = escapeHTML(message) + let escapedURL = escapeHTML(failedURL) + let escapedReloadLabel = escapeHTML(String(localized: "browser.error.reload", defaultValue: "Reload")) let html = """ @@ -3156,10 +3163,10 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate {
-

\(title)

-

\(message)

+

\(escapedTitle)

+

\(escapedMessage)

\(escapedURL)
- +
@@ -3334,9 +3341,9 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { private func javaScriptDialogTitle(for webView: WKWebView) -> String { if let absolute = webView.url?.absoluteString, !absolute.isEmpty { - return "The page at \(absolute) says:" + return String(localized: "browser.dialog.pageSaysAt", defaultValue: "The page at \(absolute) says:") } - return "This page says:" + return String(localized: "browser.dialog.pageSays", defaultValue: "This page says:") } private func presentDialog( @@ -3429,7 +3436,7 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { alert.alertStyle = .informational alert.messageText = javaScriptDialogTitle(for: webView) alert.informativeText = message - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) presentDialog(alert, for: webView) { _ in completionHandler() } } @@ -3443,8 +3450,8 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { alert.alertStyle = .informational alert.messageText = javaScriptDialogTitle(for: webView) alert.informativeText = message - alert.addButton(withTitle: "OK") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) presentDialog(alert, for: webView) { response in completionHandler(response == .alertFirstButtonReturn) } @@ -3461,8 +3468,8 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { alert.alertStyle = .informational alert.messageText = javaScriptDialogTitle(for: webView) alert.informativeText = prompt - alert.addButton(withTitle: "OK") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) let field = NSTextField(frame: NSRect(x: 0, y: 0, width: 320, height: 24)) field.stringValue = defaultText ?? "" diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index a08e17a3..eae96a00 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -504,7 +504,7 @@ struct BrowserPanelView: View { .buttonStyle(OmnibarAddressButtonStyle()) .disabled(!panel.canGoBack) .opacity(panel.canGoBack ? 1.0 : 0.4) - .help("Go Back") + .help(String(localized: "browser.goBack", defaultValue: "Go Back")) Button(action: { #if DEBUG @@ -520,7 +520,7 @@ struct BrowserPanelView: View { .buttonStyle(OmnibarAddressButtonStyle()) .disabled(!panel.canGoForward) .opacity(panel.canGoForward ? 1.0 : 0.4) - .help("Go Forward") + .help(String(localized: "browser.goForward", defaultValue: "Go Forward")) Button(action: { if panel.isLoading { @@ -541,18 +541,18 @@ struct BrowserPanelView: View { .contentShape(Rectangle()) } .buttonStyle(OmnibarAddressButtonStyle()) - .help(panel.isLoading ? "Stop" : "Reload") + .help(panel.isLoading ? String(localized: "browser.stop", defaultValue: "Stop") : String(localized: "browser.reload", defaultValue: "Reload")) if panel.isDownloading { HStack(spacing: 4) { ProgressView() .controlSize(.small) - Text("Downloading...") + Text(String(localized: "browser.downloading", defaultValue: "Downloading...")) .font(.system(size: 11)) .foregroundStyle(.secondary) } .padding(.leading, 6) - .help("Download in progress") + .help(String(localized: "browser.downloadInProgress", defaultValue: "Download in progress")) } } } @@ -570,7 +570,7 @@ struct BrowserPanelView: View { } .buttonStyle(OmnibarAddressButtonStyle()) .frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center) - .help(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.tooltip("Toggle Developer Tools")) + .help(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.tooltip(String(localized: "browser.toggleDevTools", defaultValue: "Toggle Developer Tools"))) .accessibilityIdentifier("BrowserToggleDevToolsButton") } @@ -651,7 +651,7 @@ struct BrowserPanelView: View { ), isFocused: $addressBarFocused, inlineCompletion: inlineCompletion, - placeholder: "Search or enter URL", + placeholder: String(localized: "browser.addressBar.placeholder", defaultValue: "Search or enter URL"), onTap: { handleOmnibarTap() }, @@ -2146,7 +2146,7 @@ struct OmnibarSuggestion: Identifiable, Hashable { var trailingBadgeText: String? { switch kind { case .switchToTab: - return "Switch to tab" + return String(localized: "browser.switchToTab", defaultValue: "Switch to tab") default: return nil } @@ -2983,7 +2983,7 @@ private struct OmnibarSuggestionsView: View { .accessibilityElement(children: .contain) .accessibilityRespondsToUserInteraction(true) .accessibilityIdentifier("BrowserOmnibarSuggestions") - .accessibilityLabel("Address bar suggestions") + .accessibilityLabel(String(localized: "browser.addressBarSuggestions", defaultValue: "Address bar suggestions")) } } diff --git a/Sources/Panels/CmuxWebView.swift b/Sources/Panels/CmuxWebView.swift index c330f9ea..a8a9e144 100644 --- a/Sources/Panels/CmuxWebView.swift +++ b/Sources/Panels/CmuxWebView.swift @@ -1133,7 +1133,7 @@ final class CmuxWebView: WKWebView { debugLogContextMenuDownloadCandidate(item, index: index) if !hasDefaultBrowserOpenLinkItem, (item.action == #selector(contextMenuOpenLinkInDefaultBrowser(_:)) - || item.title == "Open Link in Default Browser") { + || item.title == String(localized: "browser.contextMenu.openLinkInDefaultBrowser", defaultValue: "Open Link in Default Browser")) { hasDefaultBrowserOpenLinkItem = true } @@ -1148,7 +1148,7 @@ final class CmuxWebView: WKWebView { // by opening the link as a new surface in the same pane. if item.identifier?.rawValue == "WKMenuItemIdentifierOpenLinkInNewWindow" || item.title.contains("Open Link in New Window") { - item.title = "Open Link in New Tab" + item.title = String(localized: "browser.contextMenu.openLinkInNewTab", defaultValue: "Open Link in New Tab") } if isDownloadImageMenuItem(item) { @@ -1188,7 +1188,7 @@ final class CmuxWebView: WKWebView { if let openLinkInsertionIndex, !hasDefaultBrowserOpenLinkItem { let item = NSMenuItem( - title: "Open Link in Default Browser", + title: String(localized: "browser.contextMenu.openLinkInDefaultBrowser", defaultValue: "Open Link in Default Browser"), action: #selector(contextMenuOpenLinkInDefaultBrowser(_:)), keyEquivalent: "" ) diff --git a/Sources/SocketControlSettings.swift b/Sources/SocketControlSettings.swift index f5a6825f..6a12a955 100644 --- a/Sources/SocketControlSettings.swift +++ b/Sources/SocketControlSettings.swift @@ -18,30 +18,30 @@ enum SocketControlMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .off: - return "Off" + return String(localized: "socketControl.off.name", defaultValue: "Off") case .cmuxOnly: - return "cmux processes only" + return String(localized: "socketControl.cmuxOnly.name", defaultValue: "cmux processes only") case .automation: - return "Automation mode" + return String(localized: "socketControl.automation.name", defaultValue: "Automation mode") case .password: - return "Password mode" + return String(localized: "socketControl.password.name", defaultValue: "Password mode") case .allowAll: - return "Full open access" + return String(localized: "socketControl.allowAll.name", defaultValue: "Full open access") } } var description: String { switch self { case .off: - return "Disable the local control socket." + return String(localized: "socketControl.off.description", defaultValue: "Disable the local control socket.") case .cmuxOnly: - return "Only processes started inside cmux terminals can send commands." + return String(localized: "socketControl.cmuxOnly.description", defaultValue: "Only processes started inside cmux terminals can send commands.") case .automation: - return "Allow external local automation clients from this macOS user (no ancestry check)." + return String(localized: "socketControl.automation.description", defaultValue: "Allow external local automation clients from this macOS user (no ancestry check).") case .password: - return "Require socket authentication with a password stored in a local file." + return String(localized: "socketControl.password.description", defaultValue: "Require socket authentication with a password stored in a local file.") case .allowAll: - return "Allow any local process and user to connect with no auth. Unsafe." + return String(localized: "socketControl.allowAll.description", defaultValue: "Allow any local process and user to connect with no auth. Unsafe.") } } @@ -183,7 +183,7 @@ enum SocketControlPasswordStore { throw NSError( domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, - userInfo: [NSLocalizedDescriptionKey: "Unable to resolve socket password file path."] + userInfo: [NSLocalizedDescriptionKey: String(localized: "socketControl.error.passwordFilePath", defaultValue: "Unable to resolve socket password file path.")] ) } let directory = fileURL.deletingLastPathComponent() diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index c4581c62..b89a4d84 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -19,22 +19,22 @@ enum NewWorkspacePlacement: String, CaseIterable, Identifiable { var displayName: String { switch self { case .top: - return "Top" + return String(localized: "workspace.placement.top", defaultValue: "Top") case .afterCurrent: - return "After current" + return String(localized: "workspace.placement.afterCurrent", defaultValue: "After current") case .end: - return "End" + return String(localized: "workspace.placement.end", defaultValue: "End") } } var description: String { switch self { case .top: - return "Insert new workspaces at the top of the list." + return String(localized: "workspace.placement.top.description", defaultValue: "Insert new workspaces at the top of the list.") case .afterCurrent: - return "Insert new workspaces directly after the active workspace." + return String(localized: "workspace.placement.afterCurrent.description", defaultValue: "Insert new workspaces directly after the active workspace.") case .end: - return "Append new workspaces to the bottom of the list." + return String(localized: "workspace.placement.end.description", defaultValue: "Append new workspaces to the bottom of the list.") } } } @@ -72,9 +72,9 @@ enum SidebarActiveTabIndicatorStyle: String, CaseIterable, Identifiable { var displayName: String { switch self { case .leftRail: - return "Left Rail" + return String(localized: "sidebar.indicator.leftRail", defaultValue: "Left Rail") case .solidFill: - return "Solid Fill" + return String(localized: "sidebar.indicator.solidFill", defaultValue: "Solid Fill") } } } @@ -1078,9 +1078,13 @@ class TabManager: ObservableObject { let count = plan.panelIds.count let titleLines = plan.titles.map { "• \($0)" }.joined(separator: "\n") - let message = "This is about to close \(count) tab\(count == 1 ? "" : "s") in this pane:\n\(titleLines)" + let message = if count == 1 { + String(localized: "dialog.closeOtherTabs.message.one", defaultValue: "This will close 1 tab in this pane:\n\(titleLines)") + } else { + String(localized: "dialog.closeOtherTabs.message.other", defaultValue: "This will close \(count) tabs in this pane:\n\(titleLines)") + } guard confirmClose( - title: "Close other tabs?", + title: String(localized: "dialog.closeOtherTabs.title", defaultValue: "Close other tabs?"), message: message, acceptCmdD: false ) else { return } @@ -1120,8 +1124,8 @@ class TabManager: ObservableObject { alert.messageText = title alert.informativeText = message alert.alertStyle = .warning - alert.addButton(withTitle: "Close") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.close", defaultValue: "Close")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) // macOS convention: Cmd+D = confirm destructive close (e.g. "Don't Save"). // We only opt into this for the "close last workspace => close window" path to avoid @@ -1182,15 +1186,15 @@ class TabManager: ObservableObject { if let collapsed, !collapsed.isEmpty { return collapsed } - return "Untitled Tab" + return String(localized: "tab.untitled", defaultValue: "Untitled Tab") } private func closeWorkspaceIfRunningProcess(_ workspace: Workspace) { let willCloseWindow = tabs.count <= 1 if workspaceNeedsConfirmClose(workspace), !confirmClose( - title: "Close workspace?", - message: "This will close the workspace and all of its panels.", + title: String(localized: "dialog.closeWorkspace.title", defaultValue: "Close workspace?"), + message: String(localized: "dialog.closeWorkspace.message", defaultValue: "This will close the workspace and all of its panels."), acceptCmdD: willCloseWindow ) { return @@ -1231,8 +1235,8 @@ class TabManager: ObservableObject { let needsConfirm = workspaceNeedsConfirmClose(tab) if needsConfirm { let message = willCloseWindow - ? "This will close the last tab and close the window." - : "This will close the last tab and close its workspace." + ? String(localized: "dialog.closeLastTabWindow.message", defaultValue: "This will close the last tab and close the window.") + : String(localized: "dialog.closeLastTabWorkspace.message", defaultValue: "This will close the last tab and close its workspace.") #if DEBUG dlog( "surface.close.shortcut.confirm tab=\(tab.id.uuidString.prefix(5)) " + @@ -1240,7 +1244,7 @@ class TabManager: ObservableObject { ) #endif guard confirmClose( - title: "Close tab?", + title: String(localized: "dialog.closeTab.title", defaultValue: "Close tab?"), message: message, acceptCmdD: willCloseWindow ) else { @@ -1272,8 +1276,8 @@ class TabManager: ObservableObject { ) #endif guard confirmClose( - title: "Close tab?", - message: "This will close the current tab.", + title: String(localized: "dialog.closeTab.title", defaultValue: "Close tab?"), + message: String(localized: "dialog.closeTab.message", defaultValue: "This will close the current tab."), acceptCmdD: false ) else { #if DEBUG @@ -1311,8 +1315,8 @@ class TabManager: ObservableObject { if let terminalPanel = tab.terminalPanel(for: surfaceId), terminalPanel.needsConfirmClose() { guard confirmClose( - title: "Close tab?", - message: "This will close the current tab.", + title: String(localized: "dialog.closeTab.title", defaultValue: "Close tab?"), + message: String(localized: "dialog.closeTab.message", defaultValue: "This will close the current tab."), acceptCmdD: false ) else { return } } diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 8985b6ce..06d8a97d 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -545,10 +545,10 @@ final class TerminalNotificationStore: ObservableObject { } let alert = notificationSettingsAlertFactory() - alert.messageText = "Enable Notifications for cmux" - alert.informativeText = "Notifications are disabled for cmux. Enable them in System Settings to see alerts." - alert.addButton(withTitle: "Open Settings") - alert.addButton(withTitle: "Not Now") + alert.messageText = String(localized: "dialog.enableNotifications.title", defaultValue: "Enable Notifications for cmux") + alert.informativeText = String(localized: "dialog.enableNotifications.message", defaultValue: "Notifications are disabled for cmux. Enable them in System Settings to see alerts.") + alert.addButton(withTitle: String(localized: "dialog.enableNotifications.openSettings", defaultValue: "Open Settings")) + alert.addButton(withTitle: String(localized: "dialog.enableNotifications.notNow", defaultValue: "Not Now")) alert.beginSheetModal(for: window) { [weak self] response in guard response == .alertFirstButtonReturn, let url = URL(string: "x-apple.systempreferences:com.apple.preference.notifications") else { diff --git a/Sources/Update/UpdatePill.swift b/Sources/Update/UpdatePill.swift index a976fee5..f8d69aff 100644 --- a/Sources/Update/UpdatePill.swift +++ b/Sources/Update/UpdatePill.swift @@ -72,7 +72,7 @@ struct InstallUpdateMenuItem: View { var body: some View { if model.state.isInstallable { - Button("Install Update and Relaunch") { + Button(String(localized: "update.installAndRelaunch", defaultValue: "Install Update and Relaunch")) { model.state.confirm() } } diff --git a/Sources/Update/UpdatePopoverView.swift b/Sources/Update/UpdatePopoverView.swift index 2b1fc3b1..2361775d 100644 --- a/Sources/Update/UpdatePopoverView.swift +++ b/Sources/Update/UpdatePopoverView.swift @@ -49,17 +49,17 @@ fileprivate struct PermissionRequestView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("Enable automatic updates?") + Text(String(localized: "update.popover.enableAutoUpdates", defaultValue: "Enable automatic updates?")) .font(.system(size: 13, weight: .semibold)) - Text("cmux can automatically check for updates in the background.") + Text(String(localized: "update.popover.autoUpdatesDescription", defaultValue: "cmux can automatically check for updates in the background.")) .font(.system(size: 11)) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) } HStack(spacing: 8) { - Button("Not Now") { + Button(String(localized: "common.notNow", defaultValue: "Not Now")) { request.reply(SUUpdatePermissionResponse( automaticUpdateChecks: false, sendSystemProfile: false)) @@ -69,7 +69,7 @@ fileprivate struct PermissionRequestView: View { Spacer() - Button("Allow") { + Button(String(localized: "common.allow", defaultValue: "Allow")) { request.reply(SUUpdatePermissionResponse( automaticUpdateChecks: true, sendSystemProfile: false)) @@ -92,13 +92,13 @@ fileprivate struct CheckingView: View { HStack(spacing: 10) { ProgressView() .controlSize(.small) - Text("Checking for updates…") + Text(String(localized: "update.popover.checking", defaultValue: "Checking for updates…")) .font(.system(size: 13)) } HStack { Spacer() - Button("Cancel") { + Button(String(localized: "common.cancel", defaultValue: "Cancel")) { checking.cancel() dismiss() } @@ -120,12 +120,12 @@ fileprivate struct UpdateAvailableView: View { VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 8) { - Text("Update Available") + Text(String(localized: "update.popover.updateAvailable", defaultValue: "Update Available")) .font(.system(size: 13, weight: .semibold)) VStack(alignment: .leading, spacing: 4) { HStack(spacing: 6) { - Text("Version:") + Text(String(localized: "update.popover.version", defaultValue: "Version:")) .foregroundColor(.secondary) .frame(width: labelWidth, alignment: .trailing) Text(update.appcastItem.displayVersionString) @@ -134,7 +134,7 @@ fileprivate struct UpdateAvailableView: View { if update.appcastItem.contentLength > 0 { HStack(spacing: 6) { - Text("Size:") + Text(String(localized: "update.popover.size", defaultValue: "Size:")) .foregroundColor(.secondary) .frame(width: labelWidth, alignment: .trailing) Text(ByteCountFormatter.string(fromByteCount: Int64(update.appcastItem.contentLength), countStyle: .file)) @@ -144,7 +144,7 @@ fileprivate struct UpdateAvailableView: View { if let date = update.appcastItem.date { HStack(spacing: 6) { - Text("Released:") + Text(String(localized: "update.popover.released", defaultValue: "Released:")) .foregroundColor(.secondary) .frame(width: labelWidth, alignment: .trailing) Text(date.formatted(date: .abbreviated, time: .omitted)) @@ -156,13 +156,13 @@ fileprivate struct UpdateAvailableView: View { } HStack(spacing: 8) { - Button("Skip") { + Button(String(localized: "common.skip", defaultValue: "Skip")) { update.reply(.skip) dismiss() } .controlSize(.small) - Button("Later") { + Button(String(localized: "common.later", defaultValue: "Later")) { update.reply(.dismiss) dismiss() } @@ -171,7 +171,7 @@ fileprivate struct UpdateAvailableView: View { Spacer() - Button("Install and Relaunch") { + Button(String(localized: "common.installAndRelaunch", defaultValue: "Install and Relaunch")) { update.reply(.install) dismiss() } @@ -214,7 +214,7 @@ fileprivate struct DownloadingView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("Downloading Update") + Text(String(localized: "update.popover.downloadingUpdate", defaultValue: "Downloading Update")) .font(.system(size: 13, weight: .semibold)) if let expectedLength = download.expectedLength, expectedLength > 0 { @@ -233,7 +233,7 @@ fileprivate struct DownloadingView: View { HStack { Spacer() - Button("Cancel") { + Button(String(localized: "common.cancel", defaultValue: "Cancel")) { download.cancel() dismiss() } @@ -250,7 +250,7 @@ fileprivate struct ExtractingView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text("Preparing Update") + Text(String(localized: "update.popover.preparingUpdate", defaultValue: "Preparing Update")) .font(.system(size: 13, weight: .semibold)) VStack(alignment: .leading, spacing: 6) { @@ -271,17 +271,17 @@ fileprivate struct InstallingView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("Restart Required") + Text(String(localized: "update.popover.restartRequired", defaultValue: "Restart Required")) .font(.system(size: 13, weight: .semibold)) - Text("The update is ready. Please restart the application to complete the installation.") + Text(String(localized: "update.popover.restartRequired.message", defaultValue: "The update is ready. Please restart the application to complete the installation.")) .font(.system(size: 11)) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) } HStack { - Button("Restart Later") { + Button(String(localized: "common.restartLater", defaultValue: "Restart Later")) { installing.dismiss() dismiss() } @@ -290,7 +290,7 @@ fileprivate struct InstallingView: View { Spacer() - Button("Restart Now") { + Button(String(localized: "common.restartNow", defaultValue: "Restart Now")) { installing.retryTerminatingApplication() dismiss() } @@ -310,10 +310,10 @@ fileprivate struct NotFoundView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("No Updates Found") + Text(String(localized: "update.popover.noUpdatesFound", defaultValue: "No Updates Found")) .font(.system(size: 13, weight: .semibold)) - Text("You're already running the latest version.") + Text(String(localized: "update.popover.noUpdatesFound.message", defaultValue: "You're already running the latest version.")) .font(.system(size: 11)) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -321,7 +321,7 @@ fileprivate struct NotFoundView: View { HStack { Spacer() - Button("OK") { + Button(String(localized: "common.ok", defaultValue: "OK")) { notFound.acknowledgement() dismiss() } @@ -363,7 +363,7 @@ fileprivate struct UpdateErrorView: View { } VStack(alignment: .leading, spacing: 6) { - Text("Details") + Text(String(localized: "update.popover.details", defaultValue: "Details")) .font(.system(size: 11, weight: .semibold)) Text(details) .font(.system(size: 10, design: .monospaced)) @@ -373,14 +373,14 @@ fileprivate struct UpdateErrorView: View { } HStack(spacing: 8) { - Button("Copy Details") { + Button(String(localized: "common.copyDetails", defaultValue: "Copy Details")) { let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString(details, forType: .string) } .controlSize(.small) - Button("OK") { + Button(String(localized: "common.ok", defaultValue: "OK")) { error.dismiss() dismiss() } @@ -389,7 +389,7 @@ fileprivate struct UpdateErrorView: View { Spacer() - Button("Retry") { + Button(String(localized: "common.retry", defaultValue: "Retry")) { error.retry() dismiss() } diff --git a/Sources/Update/UpdateTitlebarAccessory.swift b/Sources/Update/UpdateTitlebarAccessory.swift index 7ff7d03b..c7a96e0e 100644 --- a/Sources/Update/UpdateTitlebarAccessory.swift +++ b/Sources/Update/UpdateTitlebarAccessory.swift @@ -320,8 +320,8 @@ struct TitlebarControlsView: View { iconLabel(systemName: "sidebar.left", config: config) } .accessibilityIdentifier("titlebarControl.toggleSidebar") - .accessibilityLabel("Toggle Sidebar") - .help(KeyboardShortcutSettings.Action.toggleSidebar.tooltip("Show or hide the sidebar")) + .accessibilityLabel(String(localized: "titlebar.sidebar.accessibilityLabel", defaultValue: "Toggle Sidebar")) + .help(KeyboardShortcutSettings.Action.toggleSidebar.tooltip(String(localized: "titlebar.sidebar.tooltip", defaultValue: "Show or hide the sidebar"))) TitlebarControlButton(config: config, action: { #if DEBUG @@ -347,8 +347,8 @@ struct TitlebarControlsView: View { } .accessibilityIdentifier("titlebarControl.showNotifications") .background(NotificationsAnchorView { viewModel.notificationsAnchorView = $0 }) - .accessibilityLabel("Notifications") - .help(KeyboardShortcutSettings.Action.showNotifications.tooltip("Show notifications")) + .accessibilityLabel(String(localized: "titlebar.notifications.accessibilityLabel", defaultValue: "Notifications")) + .help(KeyboardShortcutSettings.Action.showNotifications.tooltip(String(localized: "titlebar.notifications.tooltip", defaultValue: "Show notifications"))) TitlebarControlButton(config: config, action: { #if DEBUG @@ -359,8 +359,8 @@ struct TitlebarControlsView: View { iconLabel(systemName: "plus", config: config) } .accessibilityIdentifier("titlebarControl.newTab") - .accessibilityLabel("New Workspace") - .help(KeyboardShortcutSettings.Action.newTab.tooltip("New workspace")) + .accessibilityLabel(String(localized: "titlebar.newWorkspace.accessibilityLabel", defaultValue: "New Workspace")) + .help(KeyboardShortcutSettings.Action.newTab.tooltip(String(localized: "titlebar.newWorkspace.tooltip", defaultValue: "New workspace"))) } let paddedContent = content.padding(config.groupPadding) @@ -901,11 +901,11 @@ private struct NotificationsPopoverView: View { var body: some View { VStack(spacing: 0) { HStack { - Text("Notifications") + Text(String(localized: "notifications.title", defaultValue: "Notifications")) .font(.headline) Spacer() if !notificationStore.notifications.isEmpty { - Button("Clear All") { + Button(String(localized: "notifications.clearAll", defaultValue: "Clear All")) { notificationStore.clearAll() } .buttonStyle(.bordered) @@ -921,9 +921,9 @@ private struct NotificationsPopoverView: View { Image(systemName: "bell.slash") .font(.system(size: 28)) .foregroundColor(.secondary) - Text("No notifications yet") + Text(String(localized: "notifications.empty.title", defaultValue: "No notifications yet")) .font(.headline) - Text("Desktop notifications will appear here.") + Text(String(localized: "notifications.empty.subtitle", defaultValue: "Desktop notifications will appear here.")) .font(.subheadline) .foregroundColor(.secondary) } diff --git a/Sources/Update/UpdateViewModel.swift b/Sources/Update/UpdateViewModel.swift index dd8a6697..4bdb9ad2 100644 --- a/Sources/Update/UpdateViewModel.swift +++ b/Sources/Update/UpdateViewModel.swift @@ -22,27 +22,29 @@ class UpdateViewModel: ObservableObject { case .idle: return "" case .permissionRequest: - return "Enable Automatic Updates?" + return String(localized: "update.permissionRequest.text", defaultValue: "Enable Automatic Updates?") case .checking: - return "Checking for Updates…" + return String(localized: "update.checking", defaultValue: "Checking for Updates…") case .updateAvailable(let update): let version = update.appcastItem.displayVersionString if !version.isEmpty { - return "Update Available: \(version)" + return String(localized: "update.available.withVersion", defaultValue: "Update Available: \(version)") } - return "Update Available" + return String(localized: "update.available.short", defaultValue: "Update Available") case .downloading(let download): if let expectedLength = download.expectedLength, expectedLength > 0 { let progress = Double(download.progress) / Double(expectedLength) - return String(format: "Downloading: %.0f%%", progress * 100) + let percent = String(format: "%.0f%%", progress * 100) + return String(localized: "update.downloading.progress", defaultValue: "Downloading: \(percent)") } - return "Downloading…" + return String(localized: "update.downloading.status", defaultValue: "Downloading…") case .extracting(let extracting): - return String(format: "Preparing: %.0f%%", extracting.progress * 100) + let percent = String(format: "%.0f%%", extracting.progress * 100) + return String(localized: "update.extracting.progress", defaultValue: "Preparing: \(percent)") case .installing(let install): - return install.isAutoUpdate ? "Restart to Complete Update" : "Installing…" + return install.isAutoUpdate ? String(localized: "update.restartToComplete", defaultValue: "Restart to Complete Update") : String(localized: "update.installing.status", defaultValue: "Installing…") case .notFound: - return "No Updates Available" + return String(localized: "update.noUpdates.title", defaultValue: "No Updates Available") case .error(let err): return Self.userFacingErrorTitle(for: err.error) } @@ -87,19 +89,19 @@ class UpdateViewModel: ObservableObject { case .idle: return "" case .permissionRequest: - return "Configure automatic update preferences" + return String(localized: "update.configureAutoUpdates", defaultValue: "Configure automatic update preferences") case .checking: - return "Please wait while we check for available updates" + return String(localized: "update.pleaseWait", defaultValue: "Please wait while we check for available updates") case .updateAvailable(let update): - return update.releaseNotes?.label ?? "Download and install the latest version" + return update.releaseNotes?.label ?? String(localized: "update.downloadAndInstall", defaultValue: "Download and install the latest version") case .downloading: - return "Downloading the update package" + return String(localized: "update.downloadingPackage", defaultValue: "Downloading the update package") case .extracting: - return "Extracting and preparing the update" + return String(localized: "update.preparingUpdate", defaultValue: "Extracting and preparing the update") case let .installing(install): - return install.isAutoUpdate ? "Restart to Complete Update" : "Installing update and preparing to restart" + return install.isAutoUpdate ? String(localized: "update.restartToComplete", defaultValue: "Restart to Complete Update") : String(localized: "update.installingAndRestarting", defaultValue: "Installing update and preparing to restart") case .notFound: - return "You are running the latest version" + return String(localized: "update.noUpdates.message", defaultValue: "You are running the latest version") case .error(let err): return Self.userFacingErrorMessage(for: err.error) } @@ -177,21 +179,21 @@ class UpdateViewModel: ObservableObject { if let networkError = networkError(from: nsError) { switch networkError.code { case NSURLErrorNotConnectedToInternet: - return "No Internet Connection" + return String(localized: "update.error.noInternet.title", defaultValue: "No Internet Connection") case NSURLErrorTimedOut: - return "Update Timed Out" + return String(localized: "update.error.timedOut.title", defaultValue: "Update Timed Out") case NSURLErrorCannotFindHost: - return "Server Not Found" + return String(localized: "update.error.serverNotFound.title", defaultValue: "Server Not Found") case NSURLErrorCannotConnectToHost: - return "Server Unreachable" + return String(localized: "update.error.serverUnreachable.title", defaultValue: "Server Unreachable") case NSURLErrorNetworkConnectionLost: - return "Connection Lost" + return String(localized: "update.error.connectionLost.title", defaultValue: "Connection Lost") case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasBadDate, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorServerCertificateNotYetValid: - return "Secure Connection Failed" + return String(localized: "update.error.secureConnectionFailed.title", defaultValue: "Secure Connection Failed") default: break } @@ -199,24 +201,24 @@ class UpdateViewModel: ObservableObject { if nsError.domain == SUSparkleErrorDomain { switch nsError.code { case 4005: - return "Updater Permission Error" + return String(localized: "update.error.permissionError.title", defaultValue: "Updater Permission Error") case 2001: - return "Couldn't Download Update" + return String(localized: "update.error.downloadFailed.title", defaultValue: "Couldn't Download Update") case 1000, 1002: - return "Update Feed Error" + return String(localized: "update.error.feedError.title", defaultValue: "Update Feed Error") case 4: - return "Invalid Update Feed" + return String(localized: "update.error.invalidFeed.title", defaultValue: "Invalid Update Feed") case 3: - return "Insecure Update Feed" + return String(localized: "update.error.insecureFeed.title", defaultValue: "Insecure Update Feed") case 1, 2, 3001, 3002: - return "Update Signature Error" + return String(localized: "update.error.signatureError.title", defaultValue: "Update Signature Error") case 1003, 1005: - return "App Location Issue" + return String(localized: "update.error.appLocation.title", defaultValue: "App Location Issue") default: break } } - return "Update Failed" + return String(localized: "update.error.failed.title", defaultValue: "Update Failed") } static func userFacingErrorMessage(for error: Swift.Error) -> String { @@ -224,21 +226,21 @@ class UpdateViewModel: ObservableObject { if let networkError = networkError(from: nsError) { switch networkError.code { case NSURLErrorNotConnectedToInternet: - return "cmux can’t reach the update server. Check your internet connection and try again." + return String(localized: "update.error.noInternet.message", defaultValue: "cmux can’t reach the update server. Check your internet connection and try again.") case NSURLErrorTimedOut: - return "The update server took too long to respond. Try again in a moment." + return String(localized: "update.error.timedOut.message", defaultValue: "The update server took too long to respond. Try again in a moment.") case NSURLErrorCannotFindHost: - return "The update server can’t be found. Check your connection or try again later." + return String(localized: "update.error.serverNotFound.message", defaultValue: "The update server can’t be found. Check your connection or try again later.") case NSURLErrorCannotConnectToHost: - return "cmux couldn’t connect to the update server. Check your connection or try again later." + return String(localized: "update.error.serverUnreachable.message", defaultValue: "cmux couldn’t connect to the update server. Check your connection or try again later.") case NSURLErrorNetworkConnectionLost: - return "The network connection was lost while checking for updates. Try again." + return String(localized: "update.error.connectionLost.message", defaultValue: "The network connection was lost while checking for updates. Try again.") case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasBadDate, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorServerCertificateNotYetValid: - return "A secure connection to the update server couldn’t be established. Try again later." + return String(localized: "update.error.secureConnectionFailed.message", defaultValue: "A secure connection to the update server couldn’t be established. Try again later.") default: break } @@ -246,17 +248,17 @@ class UpdateViewModel: ObservableObject { if nsError.domain == SUSparkleErrorDomain { switch nsError.code { case 2001: - return "cmux couldn't download the update feed. Check your connection and try again." + return String(localized: "update.error.feedDownload.message", defaultValue: "cmux couldn't download the update feed. Check your connection and try again.") case 1000, 1002: - return "The update feed could not be read. Please try again later." + return String(localized: "update.error.feedRead.message", defaultValue: "The update feed could not be read. Please try again later.") case 4: - return "The update feed URL is invalid. Please contact support." + return String(localized: "update.error.invalidFeed.message", defaultValue: "The update feed URL is invalid. Please contact support.") case 3: - return "The update feed is insecure. Please contact support." + return String(localized: "update.error.insecureFeed.message", defaultValue: "The update feed is insecure. Please contact support.") case 1, 2, 3001, 3002: - return "The update's signature could not be verified. Please try again later." + return String(localized: "update.error.signatureError.message", defaultValue: "The update's signature could not be verified. Please try again later.") case 1003, 1005, 4005: - return "Move cmux into Applications and relaunch to enable updates." + return String(localized: "update.error.permissionError.message", defaultValue: "Move cmux into Applications and relaunch to enable updates.") default: break } @@ -487,8 +489,8 @@ enum UpdateState: Equatable { var label: String { switch self { - case .commit: return "View GitHub Commit" - case .tagged: return "View Release Notes" + case .commit: return String(localized: "update.viewGitHubCommit", defaultValue: "View GitHub Commit") + case .tagged: return String(localized: "update.viewReleaseNotes", defaultValue: "View Release Notes") } } } diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 533f6580..010310b4 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -990,10 +990,10 @@ final class Workspace: Identifiable, ObservableObject { private static func currentSplitButtonTooltips() -> BonsplitConfiguration.SplitButtonTooltips { BonsplitConfiguration.SplitButtonTooltips( - newTerminal: KeyboardShortcutSettings.Action.newSurface.tooltip("New Terminal"), - newBrowser: KeyboardShortcutSettings.Action.openBrowser.tooltip("New Browser"), - splitRight: KeyboardShortcutSettings.Action.splitRight.tooltip("Split Right"), - splitDown: KeyboardShortcutSettings.Action.splitDown.tooltip("Split Down") + newTerminal: KeyboardShortcutSettings.Action.newSurface.tooltip(String(localized: "workspace.tooltip.newTerminal", defaultValue: "New Terminal")), + newBrowser: KeyboardShortcutSettings.Action.openBrowser.tooltip(String(localized: "workspace.tooltip.newBrowser", defaultValue: "New Browser")), + splitRight: KeyboardShortcutSettings.Action.splitRight.tooltip(String(localized: "workspace.tooltip.splitRight", defaultValue: "Split Right")), + splitDown: KeyboardShortcutSettings.Action.splitDown.tooltip(String(localized: "workspace.tooltip.splitDown", defaultValue: "Split Down")) ) } @@ -3331,15 +3331,15 @@ final class Workspace: Identifiable, ObservableObject { let panel = panels[panelId] else { return } let alert = NSAlert() - alert.messageText = "Rename Tab" - alert.informativeText = "Enter a custom name for this tab." + alert.messageText = String(localized: "dialog.renameTab.title", defaultValue: "Rename Tab") + alert.informativeText = String(localized: "dialog.renameTab.message", defaultValue: "Enter a custom name for this tab.") let currentTitle = panelCustomTitles[panelId] ?? panelTitles[panelId] ?? panel.displayTitle let input = NSTextField(string: currentTitle) - input.placeholderString = "Tab name" + input.placeholderString = String(localized: "dialog.renameTab.placeholder", defaultValue: "Tab name") input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Rename") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.rename", defaultValue: "Rename")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input DispatchQueue.main.async { @@ -3368,24 +3368,24 @@ final class Workspace: Identifiable, ObservableObject { ) var options: [(title: String, destination: PanelMoveDestination)] = [ - ("New Workspace in Current Window", .newWorkspaceInCurrentWindow), - ("Selected Workspace in New Window", .selectedWorkspaceInNewWindow), + (String(localized: "dialog.moveTab.newWorkspaceCurrentWindow", defaultValue: "New Workspace in Current Window"), .newWorkspaceInCurrentWindow), + (String(localized: "dialog.moveTab.selectedWorkspaceNewWindow", defaultValue: "Selected Workspace in New Window"), .selectedWorkspaceInNewWindow), ] options.append(contentsOf: workspaceTargets.map { target in (target.label, .existingWorkspace(target.workspaceId)) }) let alert = NSAlert() - alert.messageText = "Move Tab" - alert.informativeText = "Choose a destination for this tab." + alert.messageText = String(localized: "dialog.moveTab.title", defaultValue: "Move Tab") + alert.informativeText = String(localized: "dialog.moveTab.message", defaultValue: "Choose a destination for this tab.") let popup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 320, height: 26), pullsDown: false) for option in options { popup.addItem(withTitle: option.title) } popup.selectItem(at: 0) alert.accessoryView = popup - alert.addButton(withTitle: "Move") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "dialog.moveTab.move", defaultValue: "Move")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) guard alert.runModal() == .alertFirstButtonReturn else { return } let selectedIndex = max(0, min(popup.indexOfSelectedItem, options.count - 1)) @@ -3431,9 +3431,9 @@ final class Workspace: Identifiable, ObservableObject { if !moved { let failure = NSAlert() failure.alertStyle = .warning - failure.messageText = "Move Failed" - failure.informativeText = "cmux could not move this tab to the selected destination." - failure.addButton(withTitle: "OK") + failure.messageText = String(localized: "dialog.moveFailed.title", defaultValue: "Move Failed") + failure.informativeText = String(localized: "dialog.moveFailed.message", defaultValue: "cmux could not move this tab to the selected destination.") + failure.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) _ = failure.runModal() } } @@ -3500,11 +3500,11 @@ extension Workspace: BonsplitDelegate { @MainActor private func confirmClosePanel(for tabId: TabID) async -> Bool { let alert = NSAlert() - alert.messageText = "Close tab?" - alert.informativeText = "This will close the current tab." + alert.messageText = String(localized: "dialog.closeTab.title", defaultValue: "Close tab?") + alert.informativeText = String(localized: "dialog.closeTab.message", defaultValue: "This will close the current tab.") alert.alertStyle = .warning - alert.addButton(withTitle: "Close") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "dialog.closeTab.close", defaultValue: "Close")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) // Prefer a sheet if we can find a window, otherwise fall back to modal. if let window = NSApp.keyWindow ?? NSApp.mainWindow { diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 8ef4d5ab..f6ca3ebf 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -220,25 +220,25 @@ struct cmuxApp: App { .windowStyle(.hiddenTitleBar) .commands { CommandGroup(replacing: .appSettings) { - Button("Settings…") { + Button(String(localized: "menu.app.settings", defaultValue: "Settings…")) { appDelegate.openPreferencesWindow(debugSource: "menu.cmdComma") } .keyboardShortcut(",", modifiers: .command) } CommandGroup(replacing: .appInfo) { - Button("About cmux") { + Button(String(localized: "menu.app.about", defaultValue: "About cmux")) { showAboutPanel() } - Button("Ghostty Settings…") { + Button(String(localized: "menu.app.ghosttySettings", defaultValue: "Ghostty Settings…")) { GhosttyApp.shared.openConfigurationInTextEdit() } - Button("Reload Configuration") { + Button(String(localized: "menu.app.reloadConfiguration", defaultValue: "Reload Configuration")) { GhosttyApp.shared.reloadConfiguration(source: "menu.reload_configuration") } .keyboardShortcut(",", modifiers: [.command, .shift]) Divider() - Button("Check for Updates…") { + Button(String(localized: "menu.app.checkForUpdates", defaultValue: "Check for Updates…")) { appDelegate.checkForUpdates(nil) } InstallUpdateMenuItem(model: appDelegate.updateViewModel) @@ -264,16 +264,16 @@ struct cmuxApp: App { } #endif - CommandMenu("Update Logs") { - Button("Copy Update Logs") { + CommandMenu(String(localized: "menu.updateLogs.title", defaultValue: "Update Logs")) { + Button(String(localized: "menu.updateLogs.copyUpdateLogs", defaultValue: "Copy Update Logs")) { appDelegate.copyUpdateLogs(nil) } - Button("Copy Focus Logs") { + Button(String(localized: "menu.updateLogs.copyFocusLogs", defaultValue: "Copy Focus Logs")) { appDelegate.copyFocusLogs(nil) } } - CommandMenu("Notifications") { + CommandMenu(String(localized: "menu.notifications.title", defaultValue: "Notifications")) { let snapshot = notificationMenuSnapshot Button(snapshot.stateHintTitle) {} @@ -291,21 +291,21 @@ struct cmuxApp: App { Divider() } - splitCommandButton(title: "Show Notifications", shortcut: showNotificationsMenuShortcut) { + splitCommandButton(title: String(localized: "menu.notifications.show", defaultValue: "Show Notifications"), shortcut: showNotificationsMenuShortcut) { showNotificationsPopover() } - splitCommandButton(title: "Jump to Latest Unread", shortcut: jumpToUnreadMenuShortcut) { + splitCommandButton(title: String(localized: "menu.notifications.jumpToUnread", defaultValue: "Jump to Latest Unread"), shortcut: jumpToUnreadMenuShortcut) { appDelegate.jumpToLatestUnread() } .disabled(!snapshot.hasUnreadNotifications) - Button("Mark All Read") { + Button(String(localized: "menu.notifications.markAllRead", defaultValue: "Mark All Read")) { notificationStore.markAllRead() } .disabled(!snapshot.hasUnreadNotifications) - Button("Clear All") { + Button(String(localized: "menu.notifications.clearAll", defaultValue: "Clear All")) { notificationStore.clearAll() } .disabled(!snapshot.hasNotifications) @@ -375,11 +375,11 @@ struct cmuxApp: App { // New tab commands CommandGroup(replacing: .newItem) { - splitCommandButton(title: "New Window", shortcut: newWindowMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.newWindow", defaultValue: "New Window"), shortcut: newWindowMenuShortcut) { appDelegate.openNewMainWindow(nil) } - splitCommandButton(title: "New Workspace", shortcut: newWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.newWorkspace", defaultValue: "New Workspace"), shortcut: newWorkspaceMenuShortcut) { if let appDelegate = AppDelegate.shared { if appDelegate.addWorkspaceInPreferredMainWindow(debugSource: "menu.newWorkspace") == nil { #if DEBUG @@ -394,13 +394,13 @@ struct cmuxApp: App { } } - splitCommandButton(title: "Open Folder…", shortcut: openFolderMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.openFolder", defaultValue: "Open Folder…"), shortcut: openFolderMenuShortcut) { let panel = NSOpenPanel() panel.canChooseFiles = false panel.canChooseDirectories = true panel.allowsMultipleSelection = false - panel.title = "Open Folder" - panel.prompt = "Open" + panel.title = String(localized: "menu.file.openFolder.panelTitle", defaultValue: "Open Folder") + panel.prompt = String(localized: "menu.file.openFolder.panelPrompt", defaultValue: "Open") if panel.runModal() == .OK, let url = panel.url { if let appDelegate = AppDelegate.shared { if appDelegate.addWorkspaceInPreferredMainWindow( @@ -418,13 +418,13 @@ struct cmuxApp: App { // Close tab/workspace CommandGroup(after: .newItem) { - Button("Go to Workspace…") { + Button(String(localized: "menu.file.goToWorkspace", defaultValue: "Go to Workspace…")) { let targetWindow = NSApp.keyWindow ?? NSApp.mainWindow NotificationCenter.default.post(name: .commandPaletteSwitcherRequested, object: targetWindow) } .keyboardShortcut("p", modifiers: [.command]) - Button("Command Palette…") { + Button(String(localized: "menu.file.commandPalette", defaultValue: "Command Palette…")) { let targetWindow = NSApp.keyWindow ?? NSApp.mainWindow NotificationCenter.default.post(name: .commandPaletteRequested, object: targetWindow) } @@ -435,12 +435,12 @@ struct cmuxApp: App { // Terminal semantics: // Cmd+W closes the focused tab (with confirmation if needed). If this is the last // tab in the last workspace, it closes the window. - Button("Close Tab") { + Button(String(localized: "menu.file.closeTab", defaultValue: "Close Tab")) { closePanelOrWindow() } .keyboardShortcut("w", modifiers: .command) - Button("Close Other Tabs in Pane") { + Button(String(localized: "menu.file.closeOtherTabs", defaultValue: "Close Other Tabs in Pane")) { closeOtherTabsInFocusedPane() } .keyboardShortcut("t", modifiers: [.command, .option]) @@ -448,11 +448,11 @@ struct cmuxApp: App { // Cmd+Shift+W closes the current workspace (with confirmation if needed). If this // is the last workspace, it closes the window. - splitCommandButton(title: "Close Workspace", shortcut: closeWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.closeWorkspace", defaultValue: "Close Workspace"), shortcut: closeWorkspaceMenuShortcut) { closeTabOrWindow() } - Button("Reopen Closed Browser Panel") { + Button(String(localized: "menu.file.reopenClosedBrowserPanel", defaultValue: "Reopen Closed Browser Panel")) { _ = activeTabManager.reopenMostRecentlyClosedBrowserPanel() } .keyboardShortcut("t", modifiers: [.command, .shift]) @@ -460,8 +460,8 @@ struct cmuxApp: App { // Find CommandGroup(after: .textEditing) { - Menu("Find") { - Button("Find…") { + Menu(String(localized: "menu.find.title", defaultValue: "Find")) { + Button(String(localized: "menu.find.find", defaultValue: "Find…")) { #if DEBUG dlog("find.menu Cmd+F fired") #endif @@ -469,19 +469,19 @@ struct cmuxApp: App { } .keyboardShortcut("f", modifiers: .command) - Button("Find Next") { + Button(String(localized: "menu.find.findNext", defaultValue: "Find Next")) { activeTabManager.findNext() } .keyboardShortcut("g", modifiers: .command) - Button("Find Previous") { + Button(String(localized: "menu.find.findPrevious", defaultValue: "Find Previous")) { activeTabManager.findPrevious() } .keyboardShortcut("g", modifiers: [.command, .shift]) Divider() - Button("Hide Find Bar") { + Button(String(localized: "menu.find.hideFindBar", defaultValue: "Hide Find Bar")) { activeTabManager.hideFind() } .keyboardShortcut("f", modifiers: [.command, .shift]) @@ -489,7 +489,7 @@ struct cmuxApp: App { Divider() - Button("Use Selection for Find") { + Button(String(localized: "menu.find.useSelectionForFind", defaultValue: "Use Selection for Find")) { activeTabManager.searchSelection() } .keyboardShortcut("e", modifiers: .command) @@ -499,7 +499,7 @@ struct cmuxApp: App { // Tab navigation CommandGroup(after: .toolbar) { - splitCommandButton(title: "Toggle Sidebar", shortcut: toggleSidebarMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.toggleSidebar", defaultValue: "Toggle Sidebar"), shortcut: toggleSidebarMenuShortcut) { if AppDelegate.shared?.toggleSidebarInActiveMainWindow() != true { sidebarState.toggle() } @@ -507,89 +507,89 @@ struct cmuxApp: App { Divider() - splitCommandButton(title: "Next Surface", shortcut: nextSurfaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.nextSurface", defaultValue: "Next Surface"), shortcut: nextSurfaceMenuShortcut) { activeTabManager.selectNextSurface() } - splitCommandButton(title: "Previous Surface", shortcut: prevSurfaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.previousSurface", defaultValue: "Previous Surface"), shortcut: prevSurfaceMenuShortcut) { activeTabManager.selectPreviousSurface() } - Button("Back") { + Button(String(localized: "menu.view.back", defaultValue: "Back")) { activeTabManager.focusedBrowserPanel?.goBack() } .keyboardShortcut("[", modifiers: .command) - Button("Forward") { + Button(String(localized: "menu.view.forward", defaultValue: "Forward")) { activeTabManager.focusedBrowserPanel?.goForward() } .keyboardShortcut("]", modifiers: .command) - Button("Reload Page") { + Button(String(localized: "menu.view.reloadPage", defaultValue: "Reload Page")) { activeTabManager.focusedBrowserPanel?.reload() } .keyboardShortcut("r", modifiers: .command) - splitCommandButton(title: "Toggle Developer Tools", shortcut: toggleBrowserDeveloperToolsMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.toggleDevTools", defaultValue: "Toggle Developer Tools"), shortcut: toggleBrowserDeveloperToolsMenuShortcut) { let manager = activeTabManager if !manager.toggleDeveloperToolsFocusedBrowser() { NSSound.beep() } } - splitCommandButton(title: "Show JavaScript Console", shortcut: showBrowserJavaScriptConsoleMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.showJSConsole", defaultValue: "Show JavaScript Console"), shortcut: showBrowserJavaScriptConsoleMenuShortcut) { let manager = activeTabManager if !manager.showJavaScriptConsoleFocusedBrowser() { NSSound.beep() } } - Button("Zoom In") { + Button(String(localized: "menu.view.zoomIn", defaultValue: "Zoom In")) { _ = activeTabManager.zoomInFocusedBrowser() } .keyboardShortcut("=", modifiers: .command) - Button("Zoom Out") { + Button(String(localized: "menu.view.zoomOut", defaultValue: "Zoom Out")) { _ = activeTabManager.zoomOutFocusedBrowser() } .keyboardShortcut("-", modifiers: .command) - Button("Actual Size") { + Button(String(localized: "menu.view.actualSize", defaultValue: "Actual Size")) { _ = activeTabManager.resetZoomFocusedBrowser() } .keyboardShortcut("0", modifiers: .command) - Button("Clear Browser History") { + Button(String(localized: "menu.view.clearBrowserHistory", defaultValue: "Clear Browser History")) { BrowserHistoryStore.shared.clearHistory() } - splitCommandButton(title: "Next Workspace", shortcut: nextWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.nextWorkspace", defaultValue: "Next Workspace"), shortcut: nextWorkspaceMenuShortcut) { activeTabManager.selectNextTab() } - splitCommandButton(title: "Previous Workspace", shortcut: prevWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.previousWorkspace", defaultValue: "Previous Workspace"), shortcut: prevWorkspaceMenuShortcut) { activeTabManager.selectPreviousTab() } - splitCommandButton(title: "Rename Workspace…", shortcut: renameWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.renameWorkspace", defaultValue: "Rename Workspace…"), shortcut: renameWorkspaceMenuShortcut) { _ = AppDelegate.shared?.requestRenameWorkspaceViaCommandPalette() } Divider() - splitCommandButton(title: "Split Right", shortcut: splitRightMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitRight", defaultValue: "Split Right"), shortcut: splitRightMenuShortcut) { performSplitFromMenu(direction: .right) } - splitCommandButton(title: "Split Down", shortcut: splitDownMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitDown", defaultValue: "Split Down"), shortcut: splitDownMenuShortcut) { performSplitFromMenu(direction: .down) } - splitCommandButton(title: "Split Browser Right", shortcut: splitBrowserRightMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitBrowserRight", defaultValue: "Split Browser Right"), shortcut: splitBrowserRightMenuShortcut) { performBrowserSplitFromMenu(direction: .right) } - splitCommandButton(title: "Split Browser Down", shortcut: splitBrowserDownMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitBrowserDown", defaultValue: "Split Browser Down"), shortcut: splitBrowserDownMenuShortcut) { performBrowserSplitFromMenu(direction: .down) } @@ -597,7 +597,7 @@ struct cmuxApp: App { // Cmd+1 through Cmd+9 for workspace selection (9 = last workspace) ForEach(1...9, id: \.self) { number in - Button("Workspace \(number)") { + Button(String(localized: "menu.view.workspace", defaultValue: "Workspace \(number)")) { let manager = activeTabManager if let targetIndex = WorkspaceShortcutMapper.workspaceIndex(forCommandDigit: number, workspaceCount: manager.tabs.count) { manager.selectTab(at: targetIndex) @@ -608,11 +608,11 @@ struct cmuxApp: App { Divider() - splitCommandButton(title: "Jump to Latest Unread", shortcut: jumpToUnreadMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.jumpToUnread", defaultValue: "Jump to Latest Unread"), shortcut: jumpToUnreadMenuShortcut) { AppDelegate.shared?.jumpToLatestUnread() } - splitCommandButton(title: "Show Notifications", shortcut: showNotificationsMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.showNotifications", defaultValue: "Show Notifications"), shortcut: showNotificationsMenuShortcut) { showNotificationsPopover() } } @@ -1707,7 +1707,7 @@ private final class AcknowledgmentsWindowController: NSWindowController, NSWindo defer: false ) window.isReleasedWhenClosed = false - window.title = "Third-Party Licenses" + window.title = String(localized: "about.licenses.windowTitle", defaultValue: "Third-Party Licenses") window.identifier = NSUserInterfaceItemIdentifier("cmux.licenses") window.center() window.contentView = NSHostingView(rootView: AcknowledgmentsView()) @@ -1732,7 +1732,7 @@ private struct AcknowledgmentsView: View { let text = try? String(contentsOf: url) { return text } - return "Licenses file not found." + return String(localized: "about.licenses.notFound", defaultValue: "Licenses file not found.") }() var body: some View { @@ -1848,10 +1848,10 @@ private struct AboutPanelView: View { VStack(alignment: .center, spacing: 32) { VStack(alignment: .center, spacing: 8) { - Text("cmux") + Text(String(localized: "about.appName", defaultValue: "cmux")) .bold() .font(.title) - Text("A Ghostty-based terminal with vertical tabs\nand a notification panel for macOS.") + Text(String(localized: "about.description", defaultValue: "A Ghostty-based terminal with vertical tabs\nand a notification panel for macOS.")) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .font(.caption) @@ -1862,31 +1862,31 @@ private struct AboutPanelView: View { VStack(spacing: 2) { if let version { - AboutPropertyRow(label: "Version", text: version) + AboutPropertyRow(label: String(localized: "about.version", defaultValue: "Version"), text: version) } if let build { - AboutPropertyRow(label: "Build", text: build) + AboutPropertyRow(label: String(localized: "about.build", defaultValue: "Build"), text: build) } let commitText = commit ?? "—" let commitURL = commit.flatMap { hash in URL(string: "https://github.com/manaflow-ai/cmux/commit/\(hash)") } - AboutPropertyRow(label: "Commit", text: commitText, url: commitURL) + AboutPropertyRow(label: String(localized: "about.commit", defaultValue: "Commit"), text: commitText, url: commitURL) } .frame(maxWidth: .infinity) HStack(spacing: 8) { if let url = docsURL { - Button("Docs") { + Button(String(localized: "about.docs", defaultValue: "Docs")) { openURL(url) } } if let url = githubURL { - Button("GitHub") { + Button(String(localized: "about.github", defaultValue: "GitHub")) { openURL(url) } } - Button("Licenses") { + Button(String(localized: "about.licenses", defaultValue: "Licenses")) { AcknowledgmentsWindowController.shared.show() } } @@ -2577,13 +2577,13 @@ enum AppearanceMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .system: - return "System" + return String(localized: "appearance.system", defaultValue: "System") case .light: - return "Light" + return String(localized: "appearance.light", defaultValue: "Light") case .dark: - return "Dark" + return String(localized: "appearance.dark", defaultValue: "Dark") case .auto: - return "Auto" + return String(localized: "appearance.auto", defaultValue: "Auto") } } } @@ -2622,9 +2622,9 @@ enum AppIconMode: String, CaseIterable, Identifiable { var displayName: String { switch self { - case .automatic: return "Automatic" - case .light: return "Light" - case .dark: return "Dark" + case .automatic: return String(localized: "appIcon.automatic", defaultValue: "Automatic") + case .light: return String(localized: "appIcon.light", defaultValue: "Light") + case .dark: return String(localized: "appIcon.dark", defaultValue: "Dark") } } @@ -2840,11 +2840,11 @@ struct SettingsView: View { private var browserHistorySubtitle: String { switch browserHistoryEntryCount { case 0: - return "No saved pages yet." + return String(localized: "settings.browser.history.subtitleEmpty", defaultValue: "No saved pages yet.") case 1: - return "1 saved page appears in omnibar suggestions." + return String(localized: "settings.browser.history.subtitleOne", defaultValue: "1 saved page appears in omnibar suggestions.") default: - return "\(browserHistoryEntryCount) saved pages appear in omnibar suggestions." + return String(localized: "settings.browser.history.subtitleMany", defaultValue: "\(browserHistoryEntryCount) saved pages appear in omnibar suggestions.") } } @@ -2861,7 +2861,7 @@ struct SettingsView: View { private func saveSocketPassword() { let trimmed = socketPasswordDraft.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - socketPasswordStatusMessage = "Enter a password first." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.enterFirst", defaultValue: "Enter a password first.") socketPasswordStatusIsError = true return } @@ -2869,10 +2869,10 @@ struct SettingsView: View { do { try SocketControlPasswordStore.savePassword(trimmed) socketPasswordDraft = "" - socketPasswordStatusMessage = "Password saved." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.saved", defaultValue: "Password saved.") socketPasswordStatusIsError = false } catch { - socketPasswordStatusMessage = "Failed to save password (\(error.localizedDescription))." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.saveFailed", defaultValue: "Failed to save password (\(error.localizedDescription)).") socketPasswordStatusIsError = true } } @@ -2881,10 +2881,10 @@ struct SettingsView: View { do { try SocketControlPasswordStore.clearPassword() socketPasswordDraft = "" - socketPasswordStatusMessage = "Password cleared." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.cleared", defaultValue: "Password cleared.") socketPasswordStatusIsError = false } catch { - socketPasswordStatusMessage = "Failed to clear password (\(error.localizedDescription))." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.clearFailed", defaultValue: "Failed to clear password (\(error.localizedDescription)).") socketPasswordStatusIsError = true } } @@ -2893,9 +2893,9 @@ struct SettingsView: View { ZStack(alignment: .top) { ScrollView { VStack(alignment: .leading, spacing: 14) { - SettingsSectionHeader(title: "App") + SettingsSectionHeader(title: String(localized: "settings.section.app", defaultValue: "App")) SettingsCard { - SettingsCardRow("Theme", controlWidth: pickerColumnWidth) { + SettingsCardRow(String(localized: "settings.app.theme", defaultValue: "Theme"), controlWidth: pickerColumnWidth) { Picker("", selection: $appearanceMode) { ForEach(AppearanceMode.visibleCases) { mode in Text(mode.displayName).tag(mode.rawValue) @@ -2918,7 +2918,7 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "New Workspace Placement", + String(localized: "settings.app.newWorkspacePlacement", defaultValue: "New Workspace Placement"), subtitle: selectedWorkspacePlacement.description, controlWidth: pickerColumnWidth ) { @@ -2934,8 +2934,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Reorder on Notification", - subtitle: "Move workspaces to the top when they receive a notification. Disable for stable shortcut positions." + String(localized: "settings.app.reorderOnNotification", defaultValue: "Reorder on Notification"), + subtitle: String(localized: "settings.app.reorderOnNotification.subtitle", defaultValue: "Move workspaces to the top when they receive a notification. Disable for stable shortcut positions.") ) { Toggle("", isOn: $workspaceAutoReorder) .labelsHidden() @@ -2945,8 +2945,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Dock Badge", - subtitle: "Show unread count on app icon (Dock and Cmd+Tab)." + String(localized: "settings.app.dockBadge", defaultValue: "Dock Badge"), + subtitle: String(localized: "settings.app.dockBadge.subtitle", defaultValue: "Show unread count on app icon (Dock and Cmd+Tab).") ) { Toggle("", isOn: $notificationDockBadgeEnabled) .labelsHidden() @@ -2994,10 +2994,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Send anonymous telemetry", + String(localized: "settings.app.telemetry", defaultValue: "Send anonymous telemetry"), subtitle: sendAnonymousTelemetry != telemetryValueAtLaunch - ? "Change takes effect on next launch." - : "Share anonymized crash and usage data to help improve cmux." + ? String(localized: "settings.app.telemetry.subtitleChanged", defaultValue: "Change takes effect on next launch.") + : String(localized: "settings.app.telemetry.subtitle", defaultValue: "Share anonymized crash and usage data to help improve cmux.") ) { Toggle("", isOn: $sendAnonymousTelemetry) .labelsHidden() @@ -3007,10 +3007,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Warn Before Quit", + String(localized: "settings.app.warnBeforeQuit", defaultValue: "Warn Before Quit"), subtitle: warnBeforeQuitShortcut - ? "Show a confirmation before quitting with Cmd+Q." - : "Cmd+Q quits immediately without confirmation." + ? String(localized: "settings.app.warnBeforeQuit.subtitleOn", defaultValue: "Show a confirmation before quitting with Cmd+Q.") + : String(localized: "settings.app.warnBeforeQuit.subtitleOff", defaultValue: "Cmd+Q quits immediately without confirmation.") ) { Toggle("", isOn: $warnBeforeQuitShortcut) .labelsHidden() @@ -3020,10 +3020,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Rename Selects Existing Name", + String(localized: "settings.app.renameSelectsName", defaultValue: "Rename Selects Existing Name"), subtitle: commandPaletteRenameSelectAllOnFocus - ? "Command Palette rename starts with all text selected." - : "Command Palette rename keeps the caret at the end." + ? String(localized: "settings.app.renameSelectsName.subtitleOn", defaultValue: "Command Palette rename starts with all text selected.") + : String(localized: "settings.app.renameSelectsName.subtitleOff", defaultValue: "Command Palette rename keeps the caret at the end.") ) { Toggle("", isOn: $commandPaletteRenameSelectAllOnFocus) .labelsHidden() @@ -3033,15 +3033,15 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Sidebar Branch Layout", + String(localized: "settings.app.sidebarBranchLayout", defaultValue: "Sidebar Branch Layout"), subtitle: sidebarBranchVerticalLayout - ? "Vertical: each branch appears on its own line." - : "Inline: all branches share one line.", + ? String(localized: "settings.app.sidebarBranchLayout.subtitleVertical", defaultValue: "Vertical: each branch appears on its own line.") + : String(localized: "settings.app.sidebarBranchLayout.subtitleInline", defaultValue: "Inline: all branches share one line."), controlWidth: pickerColumnWidth ) { Picker("", selection: $sidebarBranchVerticalLayout) { - Text("Vertical").tag(true) - Text("Inline").tag(false) + Text(String(localized: "settings.app.sidebarBranchLayout.vertical", defaultValue: "Vertical")).tag(true) + Text(String(localized: "settings.app.sidebarBranchLayout.inline", defaultValue: "Inline")).tag(false) } .labelsHidden() .pickerStyle(.menu) @@ -3050,8 +3050,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Branch + Directory in Sidebar", - subtitle: "Display the built-in git branch and working-directory row." + String(localized: "settings.app.showBranchDirectory", defaultValue: "Show Branch + Directory in Sidebar"), + subtitle: String(localized: "settings.app.showBranchDirectory.subtitle", defaultValue: "Display the built-in git branch and working-directory row.") ) { Toggle("", isOn: $sidebarShowBranchDirectory) .labelsHidden() @@ -3061,8 +3061,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Pull Requests in Sidebar", - subtitle: "Display review items (PR/MR/etc.) with status, number, and clickable link." + String(localized: "settings.app.showPullRequests", defaultValue: "Show Pull Requests in Sidebar"), + subtitle: String(localized: "settings.app.showPullRequests.subtitle", defaultValue: "Display review items (PR/MR/etc.) with status, number, and clickable link.") ) { Toggle("", isOn: $sidebarShowPullRequest) .labelsHidden() @@ -3072,10 +3072,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Open Sidebar PR Links in cmux Browser", + String(localized: "settings.app.openSidebarPRLinks", defaultValue: "Open Sidebar PR Links in cmux Browser"), subtitle: openSidebarPullRequestLinksInCmuxBrowser - ? "Clicks open inside cmux browser." - : "Clicks open in your default browser." + ? String(localized: "settings.app.openSidebarPRLinks.subtitleOn", defaultValue: "Clicks open inside cmux browser.") + : String(localized: "settings.app.openSidebarPRLinks.subtitleOff", defaultValue: "Clicks open in your default browser.") ) { Toggle("", isOn: $openSidebarPullRequestLinksInCmuxBrowser) .labelsHidden() @@ -3085,8 +3085,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Listening Ports in Sidebar", - subtitle: "Display detected listening ports for the active workspace." + String(localized: "settings.app.showPorts", defaultValue: "Show Listening Ports in Sidebar"), + subtitle: String(localized: "settings.app.showPorts.subtitle", defaultValue: "Display detected listening ports for the active workspace.") ) { Toggle("", isOn: $sidebarShowPorts) .labelsHidden() @@ -3096,8 +3096,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Latest Log in Sidebar", - subtitle: "Display the latest imperative log/status message." + String(localized: "settings.app.showLog", defaultValue: "Show Latest Log in Sidebar"), + subtitle: String(localized: "settings.app.showLog.subtitle", defaultValue: "Display the latest imperative log/status message.") ) { Toggle("", isOn: $sidebarShowLog) .labelsHidden() @@ -3107,8 +3107,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Progress in Sidebar", - subtitle: "Display the built-in progress bar from set_progress." + String(localized: "settings.app.showProgress", defaultValue: "Show Progress in Sidebar"), + subtitle: String(localized: "settings.app.showProgress.subtitle", defaultValue: "Display the built-in progress bar from set_progress.") ) { Toggle("", isOn: $sidebarShowProgress) .labelsHidden() @@ -3118,8 +3118,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Custom Metadata in Sidebar", - subtitle: "Display custom metadata from report_meta/set_status and report_meta_block." + String(localized: "settings.app.showMetadata", defaultValue: "Show Custom Metadata in Sidebar"), + subtitle: String(localized: "settings.app.showMetadata.subtitle", defaultValue: "Display custom metadata from report_meta/set_status and report_meta_block.") ) { Toggle("", isOn: $sidebarShowMetadata) .labelsHidden() @@ -3127,10 +3127,10 @@ struct SettingsView: View { } } - SettingsSectionHeader(title: "Workspace Colors") + SettingsSectionHeader(title: String(localized: "settings.section.workspaceColors", defaultValue: "Workspace Colors")) SettingsCard { SettingsCardRow( - "Workspace Color Indicator", + String(localized: "settings.workspaceColors.indicator", defaultValue: "Workspace Color Indicator"), controlWidth: pickerColumnWidth ) { Picker("", selection: sidebarIndicatorStyleSelection) { @@ -3144,7 +3144,7 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below.") + SettingsCardNote(String(localized: "settings.workspaceColors.paletteNote", defaultValue: "Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below.")) ForEach(Array(workspaceTabDefaultEntries.enumerated()), id: \.element.name) { index, entry in if index > 0 { @@ -3152,7 +3152,7 @@ struct SettingsView: View { } SettingsCardRow( entry.name, - subtitle: "Base: \(baseTabColorHex(for: entry.name))" + subtitle: String(localized: "settings.workspaceColors.base", defaultValue: "Base: \(baseTabColorHex(for: entry.name))") ) { HStack(spacing: 8) { ColorPicker( @@ -3174,10 +3174,10 @@ struct SettingsView: View { SettingsCardDivider() if workspaceTabCustomColors.isEmpty { - SettingsCardNote("Custom colors: none yet. Use \"Choose Custom Color...\" from a workspace context menu.") + SettingsCardNote(String(localized: "settings.workspaceColors.noCustomColors", defaultValue: "Custom colors: none yet. Use \"Choose Custom Color...\" from a workspace context menu.")) } else { VStack(alignment: .leading, spacing: 8) { - Text("Custom Colors") + Text(String(localized: "settings.workspaceColors.customColors", defaultValue: "Custom Colors")) .font(.system(size: 13, weight: .semibold)) ForEach(workspaceTabCustomColors, id: \.self) { hex in @@ -3192,7 +3192,7 @@ struct SettingsView: View { Spacer(minLength: 8) - Button("Remove") { + Button(String(localized: "settings.workspaceColors.remove", defaultValue: "Remove")) { removeWorkspaceCustomColor(hex) } .buttonStyle(.bordered) @@ -3207,10 +3207,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Reset Palette", - subtitle: "Restore built-in defaults and clear all custom colors." + String(localized: "settings.workspaceColors.resetPalette", defaultValue: "Reset Palette"), + subtitle: String(localized: "settings.workspaceColors.resetPalette.subtitle", defaultValue: "Restore built-in defaults and clear all custom colors.") ) { - Button("Reset") { + Button(String(localized: "settings.workspaceColors.resetPalette.button", defaultValue: "Reset")) { resetWorkspaceTabColors() } .buttonStyle(.bordered) @@ -3218,10 +3218,10 @@ struct SettingsView: View { } } - SettingsSectionHeader(title: "Automation") + SettingsSectionHeader(title: String(localized: "settings.section.automation", defaultValue: "Automation")) SettingsCard { SettingsCardRow( - "Socket Control Mode", + String(localized: "settings.automation.socketMode", defaultValue: "Socket Control Mode"), subtitle: selectedSocketControlMode.description, controlWidth: pickerColumnWidth ) { @@ -3237,27 +3237,27 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("Controls access to the local Unix socket for programmatic control. Choose a mode that matches your threat model.") + SettingsCardNote(String(localized: "settings.automation.socketMode.note", defaultValue: "Controls access to the local Unix socket for programmatic control. Choose a mode that matches your threat model.")) if selectedSocketControlMode == .password { SettingsCardDivider() SettingsCardRow( - "Socket Password", + String(localized: "settings.automation.socketPassword", defaultValue: "Socket Password"), subtitle: hasSocketPasswordConfigured - ? "Stored in Application Support." - : "No password set. External clients will be blocked until one is configured." + ? String(localized: "settings.automation.socketPassword.subtitleSet", defaultValue: "Stored in Application Support.") + : String(localized: "settings.automation.socketPassword.subtitleUnset", defaultValue: "No password set. External clients will be blocked until one is configured.") ) { HStack(spacing: 8) { - SecureField("Password", text: $socketPasswordDraft) + SecureField(String(localized: "settings.automation.socketPassword.placeholder", defaultValue: "Password"), text: $socketPasswordDraft) .textFieldStyle(.roundedBorder) .frame(width: 170) - Button(hasSocketPasswordConfigured ? "Change" : "Set") { + Button(hasSocketPasswordConfigured ? String(localized: "settings.automation.socketPassword.change", defaultValue: "Change") : String(localized: "settings.automation.socketPassword.set", defaultValue: "Set")) { saveSocketPassword() } .buttonStyle(.bordered) .controlSize(.small) .disabled(socketPasswordDraft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) if hasSocketPasswordConfigured { - Button("Clear") { + Button(String(localized: "settings.automation.socketPassword.clear", defaultValue: "Clear")) { clearSocketPassword() } .buttonStyle(.bordered) @@ -3275,21 +3275,21 @@ struct SettingsView: View { } if selectedSocketControlMode == .allowAll { SettingsCardDivider() - Text("Warning: Full open access makes the control socket world-readable/writable on this Mac and disables auth checks. Use only for local debugging.") + Text(String(localized: "settings.automation.openAccessWarning", defaultValue: "Warning: Full open access makes the control socket world-readable/writable on this Mac and disables auth checks. Use only for local debugging.")) .font(.caption) .foregroundStyle(.red) .padding(.horizontal, 14) .padding(.vertical, 8) } - SettingsCardNote("Overrides: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, and CMUX_SOCKET_PATH (set CMUX_ALLOW_SOCKET_OVERRIDE=1 for stable/nightly builds).") + SettingsCardNote(String(localized: "settings.automation.socketOverrides.note", defaultValue: "Overrides: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, and CMUX_SOCKET_PATH (set CMUX_ALLOW_SOCKET_OVERRIDE=1 for stable/nightly builds).")) } SettingsCard { SettingsCardRow( - "Claude Code Integration", + String(localized: "settings.automation.claudeCode", defaultValue: "Claude Code Integration"), subtitle: claudeCodeHooksEnabled - ? "Sidebar shows Claude session status and notifications." - : "Claude Code runs without cmux integration." + ? String(localized: "settings.automation.claudeCode.subtitleOn", defaultValue: "Sidebar shows Claude session status and notifications.") + : String(localized: "settings.automation.claudeCode.subtitleOff", defaultValue: "Claude Code runs without cmux integration.") ) { Toggle("", isOn: $claudeCodeHooksEnabled) .labelsHidden() @@ -3299,11 +3299,11 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("When enabled, cmux wraps the claude command to inject session tracking and notification hooks. Disable if you prefer to manage Claude Code hooks yourself.") + SettingsCardNote(String(localized: "settings.automation.claudeCode.note", defaultValue: "When enabled, cmux wraps the claude command to inject session tracking and notification hooks. Disable if you prefer to manage Claude Code hooks yourself.")) } SettingsCard { - SettingsCardRow("Port Base", subtitle: "Starting port for CMUX_PORT env var.", controlWidth: pickerColumnWidth) { + SettingsCardRow(String(localized: "settings.automation.portBase", defaultValue: "Port Base"), subtitle: String(localized: "settings.automation.portBase.subtitle", defaultValue: "Starting port for CMUX_PORT env var."), controlWidth: pickerColumnWidth) { TextField("", value: $cmuxPortBase, format: .number) .textFieldStyle(.roundedBorder) .multilineTextAlignment(.trailing) @@ -3311,7 +3311,7 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow("Port Range Size", subtitle: "Number of ports per workspace.", controlWidth: pickerColumnWidth) { + SettingsCardRow(String(localized: "settings.automation.portRange", defaultValue: "Port Range Size"), subtitle: String(localized: "settings.automation.portRange.subtitle", defaultValue: "Number of ports per workspace."), controlWidth: pickerColumnWidth) { TextField("", value: $cmuxPortRange, format: .number) .textFieldStyle(.roundedBorder) .multilineTextAlignment(.trailing) @@ -3319,14 +3319,14 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("Each workspace gets CMUX_PORT and CMUX_PORT_END env vars with a dedicated port range. New terminals inherit these values.") + SettingsCardNote(String(localized: "settings.automation.port.note", defaultValue: "Each workspace gets CMUX_PORT and CMUX_PORT_END env vars with a dedicated port range. New terminals inherit these values.")) } - SettingsSectionHeader(title: "Browser") + SettingsSectionHeader(title: String(localized: "settings.section.browser", defaultValue: "Browser")) SettingsCard { SettingsCardRow( - "Default Search Engine", - subtitle: "Used by the browser address bar when input is not a URL.", + String(localized: "settings.browser.searchEngine", defaultValue: "Default Search Engine"), + subtitle: String(localized: "settings.browser.searchEngine.subtitle", defaultValue: "Used by the browser address bar when input is not a URL."), controlWidth: pickerColumnWidth ) { Picker("", selection: $browserSearchEngine) { @@ -3340,7 +3340,7 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow("Show Search Suggestions") { + SettingsCardRow(String(localized: "settings.browser.searchSuggestions", defaultValue: "Show Search Suggestions")) { Toggle("", isOn: $browserSearchSuggestionsEnabled) .labelsHidden() .controlSize(.small) @@ -3349,10 +3349,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Browser Theme", + String(localized: "settings.browser.theme", defaultValue: "Browser Theme"), subtitle: selectedBrowserThemeMode == .system - ? "System follows app and macOS appearance." - : "\(selectedBrowserThemeMode.displayName) forces that color scheme for compatible pages.", + ? String(localized: "settings.browser.theme.subtitleSystem", defaultValue: "System follows app and macOS appearance.") + : String(localized: "settings.browser.theme.subtitleForced", defaultValue: "\(selectedBrowserThemeMode.displayName) forces that color scheme for compatible pages."), controlWidth: pickerColumnWidth ) { Picker("", selection: browserThemeModeSelection) { @@ -3367,8 +3367,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Open Terminal Links in cmux Browser", - subtitle: "When off, links clicked in terminal output open in your default browser." + String(localized: "settings.browser.openTerminalLinks", defaultValue: "Open Terminal Links in cmux Browser"), + subtitle: String(localized: "settings.browser.openTerminalLinks.subtitle", defaultValue: "When off, links clicked in terminal output open in your default browser.") ) { Toggle("", isOn: $openTerminalLinksInCmuxBrowser) .labelsHidden() @@ -3378,8 +3378,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Intercept open http(s) in Terminal", - subtitle: "When off, `open https://...` and `open http://...` always use your default browser." + String(localized: "settings.browser.interceptOpen", defaultValue: "Intercept open http(s) in Terminal"), + subtitle: String(localized: "settings.browser.interceptOpen.subtitle", defaultValue: "When off, `open https://...` and `open http://...` always use your default browser.") ) { Toggle("", isOn: $interceptTerminalOpenCommandInCmuxBrowser) .labelsHidden() @@ -3391,8 +3391,8 @@ struct SettingsView: View { VStack(alignment: .leading, spacing: 6) { SettingsCardRow( - "Hosts to Open in Embedded Browser", - subtitle: "Applies to terminal link clicks and intercepted `open https://...` calls. Only these hosts open in cmux. Others open in your default browser. One host or wildcard per line (for example: example.com, *.internal.example). Leave empty to open all hosts in cmux." + String(localized: "settings.browser.hostWhitelist", defaultValue: "Hosts to Open in Embedded Browser"), + subtitle: String(localized: "settings.browser.hostWhitelist.subtitle", defaultValue: "Applies to terminal link clicks and intercepted `open https://...` calls. Only these hosts open in cmux. Others open in your default browser. One host or wildcard per line (for example: example.com, *.internal.example). Leave empty to open all hosts in cmux.") ) { EmptyView() } @@ -3416,8 +3416,8 @@ struct SettingsView: View { VStack(alignment: .leading, spacing: 6) { SettingsCardRow( - "URLs to Always Open Externally", - subtitle: "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\.example\\.com/(billing|usage))." + String(localized: "settings.browser.externalPatterns", defaultValue: "URLs to Always Open Externally"), + subtitle: String(localized: "settings.browser.externalPatterns.subtitle", defaultValue: "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\.example\\.com/(billing|usage)).") ) { EmptyView() } @@ -3441,10 +3441,10 @@ struct SettingsView: View { SettingsCardDivider() VStack(alignment: .leading, spacing: 8) { - Text("HTTP Hosts Allowed in Embedded Browser") + Text(String(localized: "settings.browser.httpAllowlist", defaultValue: "HTTP Hosts Allowed in Embedded Browser")) .font(.system(size: 13, weight: .semibold)) - Text("Controls which HTTP (non-HTTPS) hosts can open in cmux without a warning prompt. Defaults include localhost, 127.0.0.1, ::1, 0.0.0.0, and *.localtest.me.") + Text(String(localized: "settings.browser.httpAllowlist.description", defaultValue: "Controls which HTTP (non-HTTPS) hosts can open in cmux without a warning prompt. Defaults include localhost, 127.0.0.1, ::1, 0.0.0.0, and *.localtest.me.")) .font(.caption) .foregroundStyle(.secondary) @@ -3464,14 +3464,14 @@ struct SettingsView: View { ViewThatFits(in: .horizontal) { HStack(alignment: .center, spacing: 10) { - Text("One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).") + Text(String(localized: "settings.browser.httpAllowlist.hint", defaultValue: "One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).")) .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) Spacer(minLength: 0) - Button("Save") { + Button(String(localized: "settings.browser.httpAllowlist.save", defaultValue: "Save")) { saveBrowserInsecureHTTPAllowlist() } .buttonStyle(.bordered) @@ -3481,13 +3481,13 @@ struct SettingsView: View { } VStack(alignment: .leading, spacing: 8) { - Text("One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).") + Text(String(localized: "settings.browser.httpAllowlist.hint", defaultValue: "One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).")) .font(.caption) .foregroundStyle(.secondary) HStack { Spacer(minLength: 0) - Button("Save") { + Button(String(localized: "settings.browser.httpAllowlist.save", defaultValue: "Save")) { saveBrowserInsecureHTTPAllowlist() } .buttonStyle(.bordered) @@ -3503,8 +3503,8 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow("Browsing History", subtitle: browserHistorySubtitle) { - Button("Clear History…") { + SettingsCardRow(String(localized: "settings.browser.history", defaultValue: "Browsing History"), subtitle: browserHistorySubtitle) { + Button(String(localized: "settings.browser.history.clearButton", defaultValue: "Clear History…")) { showClearBrowserHistoryConfirmation = true } .buttonStyle(.bordered) @@ -3513,13 +3513,13 @@ struct SettingsView: View { } } - SettingsSectionHeader(title: "Keyboard Shortcuts") + SettingsSectionHeader(title: String(localized: "settings.section.keyboardShortcuts", defaultValue: "Keyboard Shortcuts")) SettingsCard { SettingsCardRow( - "Show Cmd/Ctrl-Hold Shortcut Hints", + String(localized: "settings.shortcuts.showHints", defaultValue: "Show Cmd/Ctrl-Hold Shortcut Hints"), subtitle: showShortcutHintsOnCommandHold - ? "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills." - : "Holding Cmd or Ctrl keeps shortcut hint pills hidden." + ? String(localized: "settings.shortcuts.showHints.subtitleOn", defaultValue: "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills.") + : String(localized: "settings.shortcuts.showHints.subtitleOff", defaultValue: "Holding Cmd or Ctrl keeps shortcut hint pills hidden.") ) { Toggle("", isOn: $showShortcutHintsOnCommandHold) .labelsHidden() @@ -3540,16 +3540,16 @@ struct SettingsView: View { } .id(shortcutResetToken) - Text("Click a shortcut value to record a new shortcut.") + Text(String(localized: "settings.shortcuts.recordHint", defaultValue: "Click a shortcut value to record a new shortcut.")) .font(.caption) .foregroundColor(.secondary) .padding(.leading, 2) - SettingsSectionHeader(title: "Reset") + SettingsSectionHeader(title: String(localized: "settings.section.reset", defaultValue: "Reset")) SettingsCard { HStack { Spacer(minLength: 0) - Button("Reset All Settings") { + Button(String(localized: "settings.reset.resetAll", defaultValue: "Reset All Settings")) { resetAllSettings() } .buttonStyle(.bordered) @@ -3615,7 +3615,7 @@ struct SettingsView: View { .opacity(0.14 + (topBlurOpacity * 0.86)) HStack { - Text("Settings") + Text(String(localized: "settings.title", defaultValue: "Settings")) .font(.system(size: 16, weight: .semibold)) .foregroundColor(.primary.opacity(0.92)) Spacer(minLength: 0) @@ -3656,31 +3656,31 @@ struct SettingsView: View { reloadWorkspaceTabColorSettings() } .confirmationDialog( - "Clear browser history?", + String(localized: "settings.browser.history.clearDialog.title", defaultValue: "Clear browser history?"), isPresented: $showClearBrowserHistoryConfirmation, titleVisibility: .visible ) { - Button("Clear History", role: .destructive) { + Button(String(localized: "settings.browser.history.clearDialog.confirm", defaultValue: "Clear History"), role: .destructive) { BrowserHistoryStore.shared.clearHistory() } - Button("Cancel", role: .cancel) {} + Button(String(localized: "settings.browser.history.clearDialog.cancel", defaultValue: "Cancel"), role: .cancel) {} } message: { - Text("This removes visited-page suggestions from the browser omnibar.") + Text(String(localized: "settings.browser.history.clearDialog.message", defaultValue: "This removes visited-page suggestions from the browser omnibar.")) } .confirmationDialog( - "Enable full open access?", + String(localized: "settings.automation.openAccess.dialog.title", defaultValue: "Enable full open access?"), isPresented: $showOpenAccessConfirmation, titleVisibility: .visible ) { - Button("Enable Full Open Access", role: .destructive) { + Button(String(localized: "settings.automation.openAccess.dialog.confirm", defaultValue: "Enable Full Open Access"), role: .destructive) { socketControlMode = (pendingOpenAccessMode ?? .allowAll).rawValue pendingOpenAccessMode = nil } - Button("Cancel", role: .cancel) { + Button(String(localized: "settings.automation.openAccess.dialog.cancel", defaultValue: "Cancel"), role: .cancel) { pendingOpenAccessMode = nil } } message: { - Text("This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk.") + Text(String(localized: "settings.automation.openAccess.dialog.message", defaultValue: "This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk.")) } } @@ -3915,7 +3915,7 @@ private struct AppIconPickerRow: View { var body: some View { VStack(alignment: .leading, spacing: 10) { - Text("App Icon") + Text(String(localized: "settings.app.appIcon", defaultValue: "App Icon")) .font(.system(size: 13, weight: .medium)) HStack(spacing: 12) { From bd6fa9e8bc9aeb2edc14df4a9b3ecdf154db51b8 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:04:59 -0800 Subject: [PATCH 067/232] Extract SettingsPickerRow to enforce correct picker style (#887) Every settings picker needs .pickerStyle(.menu), controlWidth, and .labelsHidden(). Missing any of these causes layout bugs (like the notification sound picker rendering as an expanded control). This new SettingsPickerRow component bakes in the correct modifiers so future pickers get them by construction. Migrates all 8 existing settings pickers. --- Sources/cmuxApp.swift | 192 +++++++++++++++++++++++++----------------- 1 file changed, 117 insertions(+), 75 deletions(-) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index f6ca3ebf..c69ec4af 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2895,14 +2895,10 @@ struct SettingsView: View { VStack(alignment: .leading, spacing: 14) { SettingsSectionHeader(title: String(localized: "settings.section.app", defaultValue: "App")) SettingsCard { - SettingsCardRow(String(localized: "settings.app.theme", defaultValue: "Theme"), controlWidth: pickerColumnWidth) { - Picker("", selection: $appearanceMode) { - ForEach(AppearanceMode.visibleCases) { mode in - Text(mode.displayName).tag(mode.rawValue) - } + SettingsPickerRow(String(localized: "settings.app.theme", defaultValue: "Theme"), controlWidth: pickerColumnWidth, selection: $appearanceMode) { + ForEach(AppearanceMode.visibleCases) { mode in + Text(mode.displayName).tag(mode.rawValue) } - .labelsHidden() - .pickerStyle(.menu) } SettingsCardDivider() @@ -2917,18 +2913,15 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow( + SettingsPickerRow( String(localized: "settings.app.newWorkspacePlacement", defaultValue: "New Workspace Placement"), subtitle: selectedWorkspacePlacement.description, - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: $newWorkspacePlacement ) { - Picker("", selection: $newWorkspacePlacement) { - ForEach(NewWorkspacePlacement.allCases) { placement in - Text(placement.displayName).tag(placement.rawValue) - } + ForEach(NewWorkspacePlacement.allCases) { placement in + Text(placement.displayName).tag(placement.rawValue) } - .labelsHidden() - .pickerStyle(.menu) } SettingsCardDivider() @@ -2955,29 +2948,25 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow( + SettingsPickerRow( "Notification Sound", subtitle: "Sound played when a notification arrives.", - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: $notificationSound ) { - HStack(spacing: 6) { - Picker("", selection: $notificationSound) { - ForEach(NotificationSoundSettings.systemSounds, id: \.value) { sound in - Text(sound.label).tag(sound.value) - } - } - .labelsHidden() - .pickerStyle(.menu) - Button { - NotificationSoundSettings.previewSound(value: notificationSound) - } label: { - Image(systemName: "play.fill") - .font(.system(size: 9)) - } - .buttonStyle(.bordered) - .controlSize(.small) - .disabled(notificationSound == "none") + ForEach(NotificationSoundSettings.systemSounds, id: \.value) { sound in + Text(sound.label).tag(sound.value) } + } extraTrailing: { + Button { + NotificationSoundSettings.previewSound(value: notificationSound) + } label: { + Image(systemName: "play.fill") + .font(.system(size: 9)) + } + .buttonStyle(.bordered) + .controlSize(.small) + .disabled(notificationSound == "none") } SettingsCardDivider() @@ -3032,19 +3021,16 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow( + SettingsPickerRow( String(localized: "settings.app.sidebarBranchLayout", defaultValue: "Sidebar Branch Layout"), subtitle: sidebarBranchVerticalLayout ? String(localized: "settings.app.sidebarBranchLayout.subtitleVertical", defaultValue: "Vertical: each branch appears on its own line.") : String(localized: "settings.app.sidebarBranchLayout.subtitleInline", defaultValue: "Inline: all branches share one line."), - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: $sidebarBranchVerticalLayout ) { - Picker("", selection: $sidebarBranchVerticalLayout) { - Text(String(localized: "settings.app.sidebarBranchLayout.vertical", defaultValue: "Vertical")).tag(true) - Text(String(localized: "settings.app.sidebarBranchLayout.inline", defaultValue: "Inline")).tag(false) - } - .labelsHidden() - .pickerStyle(.menu) + Text(String(localized: "settings.app.sidebarBranchLayout.vertical", defaultValue: "Vertical")).tag(true) + Text(String(localized: "settings.app.sidebarBranchLayout.inline", defaultValue: "Inline")).tag(false) } SettingsCardDivider() @@ -3129,17 +3115,14 @@ struct SettingsView: View { SettingsSectionHeader(title: String(localized: "settings.section.workspaceColors", defaultValue: "Workspace Colors")) SettingsCard { - SettingsCardRow( + SettingsPickerRow( String(localized: "settings.workspaceColors.indicator", defaultValue: "Workspace Color Indicator"), - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: sidebarIndicatorStyleSelection ) { - Picker("", selection: sidebarIndicatorStyleSelection) { - ForEach(SidebarActiveTabIndicatorStyle.allCases) { style in - Text(style.displayName).tag(style.rawValue) - } + ForEach(SidebarActiveTabIndicatorStyle.allCases) { style in + Text(style.displayName).tag(style.rawValue) } - .labelsHidden() - .pickerStyle(.menu) } SettingsCardDivider() @@ -3220,19 +3203,16 @@ struct SettingsView: View { SettingsSectionHeader(title: String(localized: "settings.section.automation", defaultValue: "Automation")) SettingsCard { - SettingsCardRow( + SettingsPickerRow( String(localized: "settings.automation.socketMode", defaultValue: "Socket Control Mode"), subtitle: selectedSocketControlMode.description, - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: socketModeSelection, + accessibilityId: "AutomationSocketModePicker" ) { - Picker("", selection: socketModeSelection) { - ForEach(SocketControlMode.uiCases) { mode in - Text(mode.displayName).tag(mode.rawValue) - } + ForEach(SocketControlMode.uiCases) { mode in + Text(mode.displayName).tag(mode.rawValue) } - .labelsHidden() - .pickerStyle(.menu) - .accessibilityIdentifier("AutomationSocketModePicker") } SettingsCardDivider() @@ -3324,18 +3304,15 @@ struct SettingsView: View { SettingsSectionHeader(title: String(localized: "settings.section.browser", defaultValue: "Browser")) SettingsCard { - SettingsCardRow( + SettingsPickerRow( String(localized: "settings.browser.searchEngine", defaultValue: "Default Search Engine"), subtitle: String(localized: "settings.browser.searchEngine.subtitle", defaultValue: "Used by the browser address bar when input is not a URL."), - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: $browserSearchEngine ) { - Picker("", selection: $browserSearchEngine) { - ForEach(BrowserSearchEngine.allCases) { engine in - Text(engine.displayName).tag(engine.rawValue) - } + ForEach(BrowserSearchEngine.allCases) { engine in + Text(engine.displayName).tag(engine.rawValue) } - .labelsHidden() - .pickerStyle(.menu) } SettingsCardDivider() @@ -3348,20 +3325,17 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow( + SettingsPickerRow( String(localized: "settings.browser.theme", defaultValue: "Browser Theme"), subtitle: selectedBrowserThemeMode == .system ? String(localized: "settings.browser.theme.subtitleSystem", defaultValue: "System follows app and macOS appearance.") : String(localized: "settings.browser.theme.subtitleForced", defaultValue: "\(selectedBrowserThemeMode.displayName) forces that color scheme for compatible pages."), - controlWidth: pickerColumnWidth + controlWidth: pickerColumnWidth, + selection: browserThemeModeSelection ) { - Picker("", selection: browserThemeModeSelection) { - ForEach(BrowserThemeMode.allCases) { mode in - Text(mode.displayName).tag(mode.rawValue) - } + ForEach(BrowserThemeMode.allCases) { mode in + Text(mode.displayName).tag(mode.rawValue) } - .labelsHidden() - .pickerStyle(.menu) } SettingsCardDivider() @@ -3881,6 +3855,74 @@ private struct SettingsCardRow: View { } } +private struct SettingsPickerRow: View { + let title: String + let subtitle: String? + let controlWidth: CGFloat + @Binding var selection: SelectionValue + let pickerContent: PickerContent + let extraTrailing: ExtraTrailing + let accessibilityId: String? + + init( + _ title: String, + subtitle: String? = nil, + controlWidth: CGFloat, + selection: Binding, + accessibilityId: String? = nil, + @ViewBuilder content: () -> PickerContent, + @ViewBuilder extraTrailing: () -> ExtraTrailing + ) { + self.title = title + self.subtitle = subtitle + self.controlWidth = controlWidth + self._selection = selection + self.pickerContent = content() + self.extraTrailing = extraTrailing() + self.accessibilityId = accessibilityId + } + + var body: some View { + SettingsCardRow(title, subtitle: subtitle, controlWidth: controlWidth) { + HStack(spacing: 6) { + Picker("", selection: $selection) { + pickerContent + } + .labelsHidden() + .pickerStyle(.menu) + .applyIf(accessibilityId != nil) { $0.accessibilityIdentifier(accessibilityId!) } + extraTrailing + } + } + } +} + +extension SettingsPickerRow where ExtraTrailing == EmptyView { + init( + _ title: String, + subtitle: String? = nil, + controlWidth: CGFloat, + selection: Binding, + accessibilityId: String? = nil, + @ViewBuilder content: () -> PickerContent + ) { + self.init(title, subtitle: subtitle, controlWidth: controlWidth, selection: selection, accessibilityId: accessibilityId, content: content) { + EmptyView() + } + } +} + +private extension View { + @ViewBuilder + func applyIf(_ condition: Bool, transform: (Self) -> some View) -> some View { + if condition { + transform(self) + } else { + self + } + } +} + private struct SettingsCardDivider: View { var body: some View { Rectangle() From 102931d9751d736e88a5c9f6786ec1186a017477 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:40:57 -0800 Subject: [PATCH 068/232] Reduce typing lag by hiding invisible views from accessibility tree (#862) Profiling shows the main thread spends ~24% of time in AccessibilityViewGraph.needsUpdate walking invisible SwiftUI views during every layout pass, blocking key event dequeue. - Add .accessibilityHidden on inactive workspaces in the ZStack (opacity-0 but still walked by the accessibility subsystem) - Add .accessibilityHidden on NotificationsPage and the tabs container when their respective selection is not active - Mark GhosttyTerminalView's HostContainerView (empty portal placeholder) as non-accessible since the terminal surface lives in the AppKit portal layer above SwiftUI --- Sources/ContentView.swift | 3 +++ Sources/GhosttyTerminalView.swift | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index a4884f4c..0b9bd263 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1821,15 +1821,18 @@ struct ContentView: View { ) .opacity(isVisible ? 1 : 0) .allowsHitTesting(isSelectedWorkspace) + .accessibilityHidden(!isVisible) .zIndex(isSelectedWorkspace ? 2 : (isRetiringWorkspace ? 1 : 0)) } } .opacity(sidebarSelectionState.selection == .tabs ? 1 : 0) .allowsHitTesting(sidebarSelectionState.selection == .tabs) + .accessibilityHidden(sidebarSelectionState.selection != .tabs) NotificationsPage(selection: $sidebarSelectionState.selection) .opacity(sidebarSelectionState.selection == .notifications ? 1 : 0) .allowsHitTesting(sidebarSelectionState.selection == .notifications) + .accessibilityHidden(sidebarSelectionState.selection != .notifications) } .padding(.top, titlebarPadding) .overlay(alignment: .top) { diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 020d4ad7..5694c57b 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -6562,6 +6562,10 @@ struct GhosttyTerminalView: NSViewRepresentable { func makeNSView(context: Context) -> NSView { let container = HostContainerView() container.wantsLayer = false + // The actual terminal surface lives in the AppKit portal layer above SwiftUI. + // This empty placeholder should not be walked by the accessibility subsystem. + container.setAccessibilityRole(.none) + container.setAccessibilityElement(false) return container } From 80eca0de480c68d2b05df1ad181e83a8a8b195bf Mon Sep 17 00:00:00 2001 From: Connor Callison Date: Wed, 4 Mar 2026 15:48:18 -0800 Subject: [PATCH 069/232] Handle TLS authentication challenges to fix Microsoft device compliance (#806) WKWebView rejects all authentication challenges by default when webView(_:didReceive:completionHandler:) is not implemented, using .rejectProtectionSpace. This silently breaks TLS client-certificate flows like Microsoft Entra ID Conditional Access, which verifies device compliance via a certificate stored in the system keychain by MDM enrollment. By implementing the delegate method and returning .performDefaultHandling, the system's standard URL-loading behaviour takes over: the keychain is searched for matching client identities, MDM-installed root CAs are trusted, and any configured SSO extensions (e.g. Microsoft Enterprise SSO) can intercept the challenge. Co-authored-by: Claude Opus 4.6 (1M context) --- Sources/Panels/BrowserPanel.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 41da6553..00221bb9 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -3081,6 +3081,24 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { loadErrorPage(in: webView, failedURL: failedURL, error: nsError) } + func webView( + _ webView: WKWebView, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) { + // WKWebView rejects all authentication challenges by default when this + // delegate method is not implemented (.rejectProtectionSpace). This + // breaks TLS client-certificate flows such as Microsoft Entra ID + // Conditional Access, which verifies device compliance via a client + // certificate stored in the system keychain by MDM enrollment. + // + // By returning .performDefaultHandling the system's standard URL-loading + // behaviour takes over: the keychain is searched for matching client + // identities, MDM-installed root CAs are trusted, and any configured SSO + // extensions (e.g. Microsoft Enterprise SSO) can intercept the challenge. + completionHandler(.performDefaultHandling, nil) + } + func webView(_ webView: WKWebView, webContentProcessDidTerminate: WKWebView) { NSLog("BrowserPanel web content process terminated, reloading") webView.reload() From b07532c5224988cb4d4a74c738a99fb608d3f1f1 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:03:33 -0800 Subject: [PATCH 070/232] Add Language setting for per-app locale override (#886) * Add Language setting to Settings for per-app locale override Uses UserDefaults AppleLanguages to override locale without changing macOS system language. Picker shows System/English/Japanese with a restart prompt when the selection changes. * Address review feedback: guard relaunch and reset behavior - Guard relaunchApp() against Process launch failure (don't terminate if the new instance didn't start) - Prevent restart dialog from firing during Reset All Settings * Add localization requirement to CLAUDE.md All user-facing strings must use String(localized:) with keys in Localizable.xcstrings and translations for all supported languages. * Fix relaunch: use detached shell so open survives app exit The previous approach spawned open as a child process that could get killed when the parent terminated. Now spawns a shell with sleep+open that outlives the current process. * Fix shell injection risk and SwiftUI state batching in language setting - Pass bundle path via environment variable instead of interpolating into shell command string - Defer isResettingSettings=false to next run loop tick so onChange handler reliably sees the guard during Reset All Settings --- CLAUDE.md | 1 + Resources/Localizable.xcstrings | 240 +++++++++++++++++++++++--------- Sources/cmuxApp.swift | 97 +++++++++++++ 3 files changed, 269 insertions(+), 69 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 297a5855..03591777 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,6 +111,7 @@ tail -f "$(cat /tmp/cmux-last-debug-log-path 2>/dev/null || echo /tmp/cmux-debug - Do not add an app-level display link or manual `ghostty_surface_draw` loop; rely on Ghostty wakeups/renderer to avoid typing lag. - **Terminal find layering contract:** `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers such as `Sources/Panels/TerminalPanelView.swift`. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn. - **Submodule safety:** When modifying a submodule (ghostty, vendor/bonsplit, etc.), always push the submodule commit to its remote `main` branch BEFORE committing the updated pointer in the parent repo. Never commit on a detached HEAD or temporary branch — the commit will be orphaned and lost. Verify with: `cd && git merge-base --is-ancestor HEAD origin/main`. +- **All user-facing strings must be localized.** Use `String(localized: "key.name", defaultValue: "English text")` for every string shown in the UI (labels, buttons, menus, dialogs, tooltips, error messages). Keys go in `Resources/Localizable.xcstrings` with translations for all supported languages (currently English and Japanese). Never use bare string literals in SwiftUI `Text()`, `Button()`, alert titles, etc. ## Socket command threading policy diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 9ee38bc3..3f807bf8 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -3164,23 +3164,6 @@ } } }, - "commandPalette.subtitle.tabWithName": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Tab • %@" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "タブ • %@" - } - } - } - }, "commandPalette.subtitle.tabFallback": { "extractionState": "manual", "localizations": { @@ -3198,6 +3181,23 @@ } } }, + "commandPalette.subtitle.tabWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ • %@" + } + } + } + }, "commandPalette.subtitle.terminalWithName": { "extractionState": "manual", "localizations": { @@ -3215,23 +3215,6 @@ } } }, - "commandPalette.subtitle.workspaceWithName": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Workspace • %@" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ワークスペース • %@" - } - } - } - }, "commandPalette.subtitle.workspaceFallback": { "extractionState": "manual", "localizations": { @@ -3249,6 +3232,23 @@ } } }, + "commandPalette.subtitle.workspaceWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース • %@" + } + } + } + }, "commandPalette.switcher.windowLabel": { "extractionState": "manual", "localizations": { @@ -4473,6 +4473,23 @@ } } }, + "language.system": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システム" + } + } + } + }, "menu.app.about": { "extractionState": "manual", "localizations": { @@ -5952,6 +5969,91 @@ } } }, + "settings.app.language": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Language" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "言語" + } + } + } + }, + "settings.app.language.restartDialog.confirm": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今すぐ再起動" + } + } + } + }, + "settings.app.language.restartDialog.later": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Later" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "後で" + } + } + } + }, + "settings.app.language.restartDialog.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart to apply language change?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "言語変更を適用するために再起動しますか?" + } + } + } + }, + "settings.app.language.restartSubtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart cmux to apply" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxを再起動して適用" + } + } + } + }, "settings.app.newWorkspacePlacement": { "extractionState": "manual", "localizations": { @@ -9284,40 +9386,6 @@ } } }, - "statusMenu.unreadCount.one": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "1 unread notification" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "未読通知 1件" - } - } - } - }, - "statusMenu.unreadCount.other": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "%lld unread notifications" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "未読通知 %lld件" - } - } - } - }, "statusMenu.tooltip.unread.one": { "extractionState": "manual", "localizations": { @@ -9352,6 +9420,40 @@ } } }, + "statusMenu.unreadCount.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 unread notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 1件" + } + } + } + }, + "statusMenu.unreadCount.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 %lld件" + } + } + } + }, "tab.untitled": { "extractionState": "manual", "localizations": { @@ -10730,4 +10832,4 @@ } } } -} \ No newline at end of file +} diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index c69ec4af..5d3d6500 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2613,6 +2613,45 @@ enum AppearanceSettings { } } +enum AppLanguage: String, CaseIterable, Identifiable { + case system + case en + case ja + + var id: String { rawValue } + + var displayName: String { + switch self { + case .system: + return String(localized: "language.system", defaultValue: "System") + case .en: + return Locale.current.localizedString(forLanguageCode: "en") ?? "English" + case .ja: + return Locale.current.localizedString(forLanguageCode: "ja") ?? "Japanese" + } + } +} + +enum LanguageSettings { + static let languageKey = "appLanguage" + static let defaultLanguage: AppLanguage = .system + + static func apply(_ language: AppLanguage) { + switch language { + case .system: + UserDefaults.standard.removeObject(forKey: "AppleLanguages") + case .en, .ja: + UserDefaults.standard.set([language.rawValue], forKey: "AppleLanguages") + } + } + + static var languageAtLaunch: AppLanguage = { + let stored = UserDefaults.standard.string(forKey: languageKey) + guard let stored, let lang = AppLanguage(rawValue: stored) else { return .system } + return lang + }() +} + enum AppIconMode: String, CaseIterable, Identifiable { case automatic case light @@ -2726,6 +2765,7 @@ struct SettingsView: View { private let contentTopInset: CGFloat = 8 private let pickerColumnWidth: CGFloat = 196 + @AppStorage(LanguageSettings.languageKey) private var appLanguage = LanguageSettings.defaultLanguage.rawValue @AppStorage(AppearanceSettings.appearanceModeKey) private var appearanceMode = AppearanceSettings.defaultMode.rawValue @AppStorage(AppIconSettings.modeKey) private var appIconMode = AppIconSettings.defaultMode.rawValue @AppStorage(SocketControlSettings.appStorageKey) private var socketControlMode = SocketControlSettings.defaultMode.rawValue @@ -2779,6 +2819,8 @@ struct SettingsView: View { @State private var socketPasswordStatusMessage: String? @State private var socketPasswordStatusIsError = false @State private var telemetryValueAtLaunch = TelemetrySettings.enabledForCurrentLaunch + @State private var showLanguageRestartAlert = false + @State private var isResettingSettings = false @State private var workspaceTabDefaultEntries = WorkspaceTabColorSettings.defaultPaletteWithOverrides() @State private var workspaceTabCustomColors = WorkspaceTabColorSettings.customColors() @@ -2903,6 +2945,33 @@ struct SettingsView: View { SettingsCardDivider() + SettingsCardRow( + String(localized: "settings.app.language", defaultValue: "Language"), + subtitle: appLanguage != LanguageSettings.languageAtLaunch.rawValue + ? String(localized: "settings.app.language.restartSubtitle", defaultValue: "Restart cmux to apply") + : nil, + controlWidth: pickerColumnWidth + ) { + Picker("", selection: $appLanguage) { + ForEach(AppLanguage.allCases) { lang in + Text(lang.displayName).tag(lang.rawValue) + } + } + .labelsHidden() + .pickerStyle(.menu) + .onChange(of: appLanguage) { newValue in + guard !isResettingSettings else { return } + if let lang = AppLanguage(rawValue: newValue) { + LanguageSettings.apply(lang) + if newValue != LanguageSettings.languageAtLaunch.rawValue { + showLanguageRestartAlert = true + } + } + } + } + + SettingsCardDivider() + AppIconPickerRow( selectedMode: appIconMode, onSelect: { mode in @@ -3656,9 +3725,36 @@ struct SettingsView: View { } message: { Text(String(localized: "settings.automation.openAccess.dialog.message", defaultValue: "This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk.")) } + .confirmationDialog( + String(localized: "settings.app.language.restartDialog.title", defaultValue: "Restart to apply language change?"), + isPresented: $showLanguageRestartAlert, + titleVisibility: .visible + ) { + Button(String(localized: "settings.app.language.restartDialog.confirm", defaultValue: "Restart Now")) { + relaunchApp() + } + Button(String(localized: "settings.app.language.restartDialog.later", defaultValue: "Later"), role: .cancel) {} + } + } + + private func relaunchApp() { + let bundlePath = Bundle.main.bundlePath + let task = Process() + task.executableURL = URL(fileURLWithPath: "/bin/sh") + task.arguments = ["-c", "sleep 1 && open -n -- \"$RELAUNCH_PATH\""] + task.environment = ["RELAUNCH_PATH": bundlePath] + do { + try task.run() + } catch { + return + } + NSApplication.shared.terminate(nil) } private func resetAllSettings() { + isResettingSettings = true + appLanguage = LanguageSettings.defaultLanguage.rawValue + LanguageSettings.apply(.system) appearanceMode = AppearanceSettings.defaultMode.rawValue appIconMode = AppIconSettings.defaultMode.rawValue AppIconSettings.applyIcon(.automatic) @@ -3700,6 +3796,7 @@ struct SettingsView: View { WorkspaceTabColorSettings.reset() reloadWorkspaceTabColorSettings() shortcutResetToken = UUID() + DispatchQueue.main.async { isResettingSettings = false } } private func defaultTabColorBinding(for name: String) -> Binding { From 5baf0d1a3b2089ecef625cdf1a5dfbbf76d5e966 Mon Sep 17 00:00:00 2001 From: Orkhan Rzazade Date: Thu, 5 Mar 2026 04:05:43 +0400 Subject: [PATCH 071/232] fix: prevent crash in parseNotificationPayload when fields are empty (#881) Swift's split(separator:) omits empty subsequences by default, so a payload like "||" or "||body" produces an empty or misaligned array. Accessing parts[0] unconditionally then triggers an out-of-bounds trap (EXC_BREAKPOINT / SIGTRAP). Two changes: 1. Pass omittingEmptySubsequences: false to preserve field positions across the pipe delimiters, so "title||body" correctly yields ["title", "", "body"] instead of ["title", "body"]. 2. Guard parts[0] with a bounds check, consistent with how parts[1] and parts[2] are already accessed. Reproduces when cmux notify is called with empty --title or via Claude Code's Notification hook where env vars may be empty. --- Sources/TerminalController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 99ca19f6..3052e9f9 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -10837,8 +10837,8 @@ class TerminalController { private func parseNotificationPayload(_ args: String) -> (String, String, String) { let trimmed = args.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return ("Notification", "", "") } - let parts = trimmed.split(separator: "|", maxSplits: 2).map(String.init) - let title = parts[0].trimmingCharacters(in: .whitespacesAndNewlines) + let parts = trimmed.split(separator: "|", maxSplits: 2, omittingEmptySubsequences: false).map(String.init) + let title = parts.count > 0 ? parts[0].trimmingCharacters(in: .whitespacesAndNewlines) : "" let subtitle = parts.count > 2 ? parts[1].trimmingCharacters(in: .whitespacesAndNewlines) : "" let body = parts.count > 2 ? parts[2].trimmingCharacters(in: .whitespacesAndNewlines) From 76bdf7631af72f7525232afdde25817dbdb9ade4 Mon Sep 17 00:00:00 2001 From: Yoshiki Agatsuma Date: Thu, 5 Mar 2026 09:15:15 +0900 Subject: [PATCH 072/232] Add find-in-page (Cmd+F) for browser panels (#837) (#875) JavaScript-based find using TreeWalker + highlights with match counter, next/previous navigation, and drag-to-corner overlay matching the existing terminal find bar. - BrowserFindJavaScript: JS generation for search/next/prev/clear - BrowserSearchOverlay: SwiftUI overlay with IME-safe onSubmit - BrowserSearchState: Observable state (needle/selected/total) - TabManager routing: Cmd+F/G dispatches to browser when focused - Visibility filter: skips script/style/hidden/aria-hidden elements - Stale DOM guard: isConnected check in next/previous scripts - Navigation cleanup: clears find on didFinish and didFailNavigation Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> --- GhosttyTabs.xcodeproj/project.pbxproj | 12 ++ Sources/Find/BrowserFindJavaScript.swift | 207 +++++++++++++++++++++ Sources/Find/BrowserSearchOverlay.swift | 183 ++++++++++++++++++ Sources/GhosttyTerminalView.swift | 1 + Sources/Panels/BrowserPanel.swift | 122 ++++++++++++ Sources/Panels/BrowserPanelView.swift | 11 ++ Sources/TabManager.swift | 23 ++- cmuxTests/BrowserFindJavaScriptTests.swift | 116 ++++++++++++ 8 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 Sources/Find/BrowserFindJavaScript.swift create mode 100644 Sources/Find/BrowserSearchOverlay.swift create mode 100644 cmuxTests/BrowserFindJavaScriptTests.swift diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 532ab71b..56f76d80 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ B9000024A1B2C3D4E5F60719 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = A5001251 /* Sentry */; }; A5001270 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = A5001271 /* PostHog */; }; A5001303 /* SurfaceSearchOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001301 /* SurfaceSearchOverlay.swift */; }; + A5008371 /* BrowserSearchOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008370 /* BrowserSearchOverlay.swift */; }; + A5008373 /* BrowserFindJavaScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008372 /* BrowserFindJavaScript.swift */; }; A50012F1 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50012F0 /* Backport.swift */; }; A50012F3 /* KeyboardShortcutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50012F2 /* KeyboardShortcutSettings.swift */; }; A50012F5 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50012F4 /* KeyboardLayout.swift */; }; @@ -84,6 +86,7 @@ F6000000A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */; }; F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */; }; F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */; }; + A5008381 /* BrowserFindJavaScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008380 /* BrowserFindJavaScriptTests.swift */; }; DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000001 /* Localizable.xcstrings */; }; DA7A10CA710E000000000004 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000002 /* InfoPlist.xcstrings */; }; /* End PBXBuildFile section */ @@ -173,6 +176,8 @@ A5001091 /* NotificationsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPage.swift; sourceTree = ""; }; A5001092 /* TerminalNotificationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalNotificationStore.swift; sourceTree = ""; }; A5001301 /* SurfaceSearchOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Find/SurfaceSearchOverlay.swift; sourceTree = ""; }; + A5008370 /* BrowserSearchOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Find/BrowserSearchOverlay.swift; sourceTree = ""; }; + A5008372 /* BrowserFindJavaScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Find/BrowserFindJavaScript.swift; sourceTree = ""; }; A50012F0 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; A50012F2 /* KeyboardShortcutSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardShortcutSettings.swift; sourceTree = ""; }; A50012F4 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = ""; }; @@ -217,6 +222,7 @@ F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateShortcutRoutingTests.swift; sourceTree = ""; }; F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentViewVisibilityTests.swift; sourceTree = ""; }; F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlPasswordStoreTests.swift; sourceTree = ""; }; + A5008380 /* BrowserFindJavaScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserFindJavaScriptTests.swift; sourceTree = ""; }; DA7A10CA710E000000000001 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; DA7A10CA710E000000000002 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -350,6 +356,8 @@ A5001091 /* NotificationsPage.swift */, A5001092 /* TerminalNotificationStore.swift */, A5001301 /* SurfaceSearchOverlay.swift */, + A5008370 /* BrowserSearchOverlay.swift */, + A5008372 /* BrowserFindJavaScript.swift */, A5001410 /* Panel.swift */, A5001411 /* TerminalPanel.swift */, A5001412 /* BrowserPanel.swift */, @@ -436,6 +444,7 @@ F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */, F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */, F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */, + A5008380 /* BrowserFindJavaScriptTests.swift */, ); path = cmuxTests; sourceTree = ""; @@ -592,6 +601,8 @@ A5001094 /* NotificationsPage.swift in Sources */, A5001095 /* TerminalNotificationStore.swift in Sources */, A5001303 /* SurfaceSearchOverlay.swift in Sources */, + A5008371 /* BrowserSearchOverlay.swift in Sources */, + A5008373 /* BrowserFindJavaScript.swift in Sources */, A5001400 /* Panel.swift in Sources */, A5001401 /* TerminalPanel.swift in Sources */, A5001402 /* BrowserPanel.swift in Sources */, @@ -647,6 +658,7 @@ F6000000A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift in Sources */, F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */, F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */, + A5008381 /* BrowserFindJavaScriptTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/Find/BrowserFindJavaScript.swift b/Sources/Find/BrowserFindJavaScript.swift new file mode 100644 index 00000000..c664bdc6 --- /dev/null +++ b/Sources/Find/BrowserFindJavaScript.swift @@ -0,0 +1,207 @@ +import Foundation + +/// JavaScript snippets for find-in-page in WKWebView. +/// +/// Uses TreeWalker to scan text nodes and wraps matches with `` elements. +/// The current match gets an additional `.current` class and is scrolled into view. +enum BrowserFindJavaScript { + + // MARK: - Public API + + /// Returns JS that highlights all occurrences of `query` in the document body. + /// The script evaluates to a JSON string `{"total":N,"current":0}`. + static func searchScript(query: String) -> String { + let escaped = jsStringEscape(query) + return """ + (() => { + const MARK_CLASS = '__cmux-find'; + const CURRENT_CLASS = '__cmux-find-current'; + + // Remove previous highlights first. + \(clearBody) + + const query = "\(escaped)"; + if (!query) return JSON.stringify({total: 0, current: 0}); + + const lowerQuery = query.toLowerCase(); + const SKIP_TAGS = new Set(['SCRIPT','STYLE','NOSCRIPT','TEMPLATE','IFRAME','SVG']); + const isVisible = (el) => { + while (el && el !== document.body) { + if (SKIP_TAGS.has(el.tagName)) return false; + if (el.getAttribute('aria-hidden') === 'true') return false; + const st = getComputedStyle(el); + if (st.display === 'none' || st.visibility === 'hidden') return false; + el = el.parentElement; + } + return true; + }; + const walker = document.createTreeWalker( + document.body, + NodeFilter.SHOW_TEXT, + { acceptNode(node) { return isVisible(node.parentElement) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } } + ); + const matches = []; + const textNodes = []; + while (walker.nextNode()) textNodes.push(walker.currentNode); + + for (const node of textNodes) { + const text = node.textContent || ''; + const lowerText = text.toLowerCase(); + let startIndex = 0; + const parts = []; + let lastEnd = 0; + while (true) { + const idx = lowerText.indexOf(lowerQuery, startIndex); + if (idx === -1) break; + parts.push({ start: idx, end: idx + query.length }); + startIndex = idx + query.length; + } + if (parts.length === 0) continue; + + const parent = node.parentNode; + if (!parent) continue; + const frag = document.createDocumentFragment(); + let pos = 0; + for (const part of parts) { + if (part.start > pos) { + frag.appendChild(document.createTextNode(text.substring(pos, part.start))); + } + const mark = document.createElement('mark'); + mark.className = MARK_CLASS; + mark.textContent = text.substring(part.start, part.end); + frag.appendChild(mark); + matches.push(mark); + pos = part.end; + } + if (pos < text.length) { + frag.appendChild(document.createTextNode(text.substring(pos))); + } + parent.replaceChild(frag, node); + } + + window.__cmuxFindMatches = matches; + window.__cmuxFindIndex = 0; + + if (matches.length > 0) { + matches[0].classList.add(CURRENT_CLASS); + matches[0].scrollIntoView({ block: 'center', behavior: 'smooth' }); + } + + // Inject highlight styles if not already present. + if (!document.getElementById('__cmux-find-style')) { + const style = document.createElement('style'); + style.id = '__cmux-find-style'; + style.textContent = ` + mark.__cmux-find { background: #facc15; color: #000; border-radius: 2px; } + mark.__cmux-find.__cmux-find-current { background: #f97316; color: #fff; } + `; + document.head.appendChild(style); + } + + return JSON.stringify({ total: matches.length, current: 0 }); + })() + """ + } + + /// Returns JS that moves to the next match. Evaluates to `{"total":N,"current":M}`. + static func nextScript() -> String { + """ + (() => { + const matches = window.__cmuxFindMatches || []; + if (matches.length === 0) return JSON.stringify({ total: 0, current: 0 }); + let idx = window.__cmuxFindIndex || 0; + if (!matches[idx] || !matches[idx].isConnected) { + window.__cmuxFindMatches = []; + window.__cmuxFindIndex = 0; + return JSON.stringify({ total: 0, current: 0 }); + } + matches[idx].classList.remove('__cmux-find-current'); + idx = (idx + 1) % matches.length; + if (!matches[idx] || !matches[idx].isConnected) { + window.__cmuxFindMatches = []; + window.__cmuxFindIndex = 0; + return JSON.stringify({ total: 0, current: 0 }); + } + matches[idx].classList.add('__cmux-find-current'); + matches[idx].scrollIntoView({ block: 'center', behavior: 'smooth' }); + window.__cmuxFindIndex = idx; + return JSON.stringify({ total: matches.length, current: idx }); + })() + """ + } + + /// Returns JS that moves to the previous match. Evaluates to `{"total":N,"current":M}`. + static func previousScript() -> String { + """ + (() => { + const matches = window.__cmuxFindMatches || []; + if (matches.length === 0) return JSON.stringify({ total: 0, current: 0 }); + let idx = window.__cmuxFindIndex || 0; + if (!matches[idx] || !matches[idx].isConnected) { + window.__cmuxFindMatches = []; + window.__cmuxFindIndex = 0; + return JSON.stringify({ total: 0, current: 0 }); + } + matches[idx].classList.remove('__cmux-find-current'); + idx = (idx - 1 + matches.length) % matches.length; + if (!matches[idx] || !matches[idx].isConnected) { + window.__cmuxFindMatches = []; + window.__cmuxFindIndex = 0; + return JSON.stringify({ total: 0, current: 0 }); + } + matches[idx].classList.add('__cmux-find-current'); + matches[idx].scrollIntoView({ block: 'center', behavior: 'smooth' }); + window.__cmuxFindIndex = idx; + return JSON.stringify({ total: matches.length, current: idx }); + })() + """ + } + + /// Returns JS that removes all find highlights and restores the DOM. + static func clearScript() -> String { + """ + (() => { + \(clearBody) + window.__cmuxFindMatches = []; + window.__cmuxFindIndex = 0; + const style = document.getElementById('__cmux-find-style'); + if (style) style.remove(); + return 'ok'; + })() + """ + } + + // MARK: - Internal + + /// JS snippet (no wrapping IIFE) that removes existing mark highlights. + private static let clearBody = """ + document.querySelectorAll('mark.__cmux-find').forEach(mark => { + const parent = mark.parentNode; + if (!parent) return; + const text = document.createTextNode(mark.textContent || ''); + parent.replaceChild(text, mark); + parent.normalize(); + }); + """ + + /// Escape a Swift string for safe embedding inside a JS double-quoted string literal. + static func jsStringEscape(_ string: String) -> String { + var result = "" + result.reserveCapacity(string.count) + for scalar in string.unicodeScalars { + switch scalar { + case "\\": result += "\\\\" + case "\"": result += "\\\"" + case "\n": result += "\\n" + case "\r": result += "\\r" + case "\t": result += "\\t" + case "\0": result += "\\0" + case "\u{2028}": result += "\\u2028" + case "\u{2029}": result += "\\u2029" + default: + result.append(Character(scalar)) + } + } + return result + } +} diff --git a/Sources/Find/BrowserSearchOverlay.swift b/Sources/Find/BrowserSearchOverlay.swift new file mode 100644 index 00000000..635aecdb --- /dev/null +++ b/Sources/Find/BrowserSearchOverlay.swift @@ -0,0 +1,183 @@ +import Bonsplit +import SwiftUI + +struct BrowserSearchOverlay: View { + let panelId: UUID + @ObservedObject var searchState: BrowserSearchState + let onNext: () -> Void + let onPrevious: () -> Void + let onClose: () -> Void + @State private var corner: Corner = .topRight + @State private var dragOffset: CGSize = .zero + @State private var barSize: CGSize = .zero + @FocusState private var isSearchFieldFocused: Bool + + private let padding: CGFloat = 8 + + var body: some View { + GeometryReader { geo in + HStack(spacing: 4) { + TextField("Search", text: $searchState.needle) + .textFieldStyle(.plain) + .frame(width: 180) + .padding(.leading, 8) + .padding(.trailing, 50) + .padding(.vertical, 6) + .background(Color.primary.opacity(0.1)) + .cornerRadius(6) + .focused($isSearchFieldFocused) + .overlay(alignment: .trailing) { + if let selected = searchState.selected { + let totalText = searchState.total.map { String($0) } ?? "?" + Text("\(selected + 1)/\(totalText)") + .font(.caption) + .foregroundColor(.secondary) + .monospacedDigit() + .padding(.trailing, 8) + } else if let total = searchState.total { + Text(total == 0 ? "0/0" : "-/\(total)") + .font(.caption) + .foregroundColor(.secondary) + .monospacedDigit() + .padding(.trailing, 8) + } + } + .onExitCommand { + onClose() + } + .onSubmit { + // onSubmit fires only after IME composition is committed. + if NSEvent.modifierFlags.contains(.shift) { + onPrevious() + } else { + onNext() + } + } + + Button(action: { + #if DEBUG + dlog("browser.findbar.next panel=\(panelId.uuidString.prefix(5))") + #endif + onNext() + }) { + Image(systemName: "chevron.up") + } + .buttonStyle(SearchButtonStyle()) + .help("Next match (Return)") + + Button(action: { + #if DEBUG + dlog("browser.findbar.prev panel=\(panelId.uuidString.prefix(5))") + #endif + onPrevious() + }) { + Image(systemName: "chevron.down") + } + .buttonStyle(SearchButtonStyle()) + .help("Previous match (Shift+Return)") + + Button(action: { + #if DEBUG + dlog("browser.findbar.close panel=\(panelId.uuidString.prefix(5))") + #endif + onClose() + }) { + Image(systemName: "xmark") + } + .buttonStyle(SearchButtonStyle()) + .help("Close (Esc)") + } + .padding(8) + .background(.background) + .clipShape(clipShape) + .shadow(radius: 4) + .onAppear { + #if DEBUG + dlog("browser.findbar.appear panel=\(panelId.uuidString.prefix(5))") + #endif + isSearchFieldFocused = true + } + .onReceive(NotificationCenter.default.publisher(for: .browserSearchFocus)) { notification in + guard let notifiedPanelId = notification.object as? UUID, + notifiedPanelId == panelId else { return } + DispatchQueue.main.async { + isSearchFieldFocused = true + } + } + .background( + GeometryReader { barGeo in + Color.clear.onAppear { + barSize = barGeo.size + } + } + ) + .padding(padding) + .offset(dragOffset) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: corner.alignment) + .gesture( + DragGesture() + .onChanged { value in + dragOffset = value.translation + } + .onEnded { value in + let centerPos = centerPosition(for: corner, in: geo.size, barSize: barSize) + let newCenter = CGPoint( + x: centerPos.x + value.translation.width, + y: centerPos.y + value.translation.height + ) + let newCorner = closestCorner(to: newCenter, in: geo.size) + withAnimation(.easeOut(duration: 0.2)) { + corner = newCorner + dragOffset = .zero + } + } + ) + } + } + + private var clipShape: some Shape { + RoundedRectangle(cornerRadius: 8) + } + + enum Corner { + case topLeft + case topRight + case bottomLeft + case bottomRight + + var alignment: Alignment { + switch self { + case .topLeft: return .topLeading + case .topRight: return .topTrailing + case .bottomLeft: return .bottomLeading + case .bottomRight: return .bottomTrailing + } + } + } + + private func centerPosition(for corner: Corner, in containerSize: CGSize, barSize: CGSize) -> CGPoint { + let halfWidth = barSize.width / 2 + padding + let halfHeight = barSize.height / 2 + padding + + switch corner { + case .topLeft: + return CGPoint(x: halfWidth, y: halfHeight) + case .topRight: + return CGPoint(x: containerSize.width - halfWidth, y: halfHeight) + case .bottomLeft: + return CGPoint(x: halfWidth, y: containerSize.height - halfHeight) + case .bottomRight: + return CGPoint(x: containerSize.width - halfWidth, y: containerSize.height - halfHeight) + } + } + + private func closestCorner(to point: CGPoint, in containerSize: CGSize) -> Corner { + let midX = containerSize.width / 2 + let midY = containerSize.height / 2 + + if point.x < midX { + return point.y < midY ? .topLeft : .bottomLeft + } + return point.y < midY ? .topRight : .bottomRight + } +} diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 5694c57b..2d5c5ecb 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -4673,6 +4673,7 @@ extension Notification.Name { static let ghosttySearchFocus = Notification.Name("ghosttySearchFocus") static let ghosttyConfigDidReload = Notification.Name("ghosttyConfigDidReload") static let ghosttyDefaultBackgroundDidChange = Notification.Name("ghosttyDefaultBackgroundDidChange") + static let browserSearchFocus = Notification.Name("browserSearchFocus") } // MARK: - Scroll View Wrapper (Ghostty-style scrollbar) diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 00221bb9..b39e5fad 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1232,6 +1232,18 @@ private enum BrowserInsecureHTTPNavigationIntent { case newTab } +/// Observable state for browser find-in-page. Mirrors `TerminalSurface.SearchState`. +@MainActor +final class BrowserSearchState: ObservableObject { + @Published var needle: String + @Published var selected: UInt? + @Published var total: UInt? + + init(needle: String = "") { + self.needle = needle + } +} + @MainActor final class BrowserPanel: Panel, ObservableObject { /// Shared process pool for cookie sharing across all browser panels @@ -1436,6 +1448,36 @@ final class BrowserPanel: Panel, ObservableObject { /// cleared only after BrowserPanelView acknowledges handling it. @Published private(set) var pendingAddressBarFocusRequestId: UUID? + /// Find-in-page state. Non-nil when the find bar is visible. + @Published var searchState: BrowserSearchState? = nil { + didSet { + if let searchState { + NSLog("Find: browser search state created panel=%@", id.uuidString) + searchNeedleCancellable = searchState.$needle + .removeDuplicates() + .map { needle -> AnyPublisher in + if needle.isEmpty || needle.count >= 3 { + return Just(needle).eraseToAnyPublisher() + } + return Just(needle) + .delay(for: .milliseconds(300), scheduler: DispatchQueue.main) + .eraseToAnyPublisher() + } + .switchToLatest() + .sink { [weak self] needle in + guard let self else { return } + NSLog("Find: browser needle updated panel=%@ needle=%@", self.id.uuidString, needle) + self.executeFindSearch(needle) + } + } else if oldValue != nil { + searchNeedleCancellable = nil + NSLog("Find: browser search state cleared panel=%@", id.uuidString) + executeFindClear() + } + } + } + private var searchNeedleCancellable: AnyCancellable? + private var cancellables = Set() private var navigationDelegate: BrowserNavigationDelegate? private var uiDelegate: BrowserUIDelegate? @@ -1541,6 +1583,10 @@ final class BrowserPanel: Panel, ObservableObject { Task { @MainActor [weak self] in self?.refreshFavicon(from: webView) self?.applyBrowserThemeModeIfNeeded() + // Clear find-in-page on navigation so stale highlights don't persist. + if self?.searchState != nil { + self?.searchState = nil + } } } navDelegate.didFailNavigation = { [weak self] _, failedURL in @@ -1551,6 +1597,10 @@ final class BrowserPanel: Panel, ObservableObject { self.pageTitle = failedURL.isEmpty ? "" : failedURL self.faviconPNGData = nil self.lastFaviconURLString = nil + // Clear find-in-page so stale highlights don't persist. + if self.searchState != nil { + self.searchState = nil + } } } navDelegate.openInNewTab = { [weak self] url in @@ -2502,6 +2552,78 @@ extension BrowserPanel { try await webView.evaluateJavaScript(script) } + // MARK: - Find in Page + + func startFind() { + if searchState == nil { + searchState = BrowserSearchState() + } + NotificationCenter.default.post(name: .browserSearchFocus, object: id) + } + + func findNext() { + Task { @MainActor [weak self] in + guard let self else { return } + let result = try? await self.webView.evaluateJavaScript(BrowserFindJavaScript.nextScript()) + self.parseFindResult(result) + } + } + + func findPrevious() { + Task { @MainActor [weak self] in + guard let self else { return } + let result = try? await self.webView.evaluateJavaScript(BrowserFindJavaScript.previousScript()) + self.parseFindResult(result) + } + } + + func hideFind() { + searchState = nil + } + + private func executeFindSearch(_ needle: String) { + guard !needle.isEmpty else { + executeFindClear() + searchState?.selected = nil + searchState?.total = nil + return + } + Task { @MainActor [weak self] in + guard let self else { return } + let js = BrowserFindJavaScript.searchScript(query: needle) + do { + let result = try await self.webView.evaluateJavaScript(js) + self.parseFindResult(result) + } catch { + NSLog("Find: browser JS search error: %@", error.localizedDescription) + } + } + } + + private func executeFindClear() { + Task { @MainActor [weak self] in + guard let self else { return } + do { + _ = try await self.webView.evaluateJavaScript(BrowserFindJavaScript.clearScript()) + } catch { + NSLog("Find: browser JS clear error: %@", error.localizedDescription) + } + } + } + + private func parseFindResult(_ result: Any?) { + guard let jsonString = result as? String, + let data = jsonString.data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let total = json["total"] as? Int, + let current = json["current"] as? Int, + total >= 0, current >= 0 else { + return + } + searchState?.total = UInt(total) + searchState?.selected = total > 0 ? UInt(current) : nil + } + func setBrowserThemeMode(_ mode: BrowserThemeMode) { browserThemeMode = mode applyBrowserThemeModeIfNeeded() diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index eae96a00..dc4856b8 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -316,6 +316,17 @@ struct BrowserPanelView: View { .padding(FocusFlashPattern.ringInset) .allowsHitTesting(false) } + .overlay { + if let searchState = panel.searchState { + BrowserSearchOverlay( + panelId: panel.id, + searchState: searchState, + onNext: { panel.findNext() }, + onPrevious: { panel.findPrevious() }, + onClose: { panel.hideFind() } + ) + } + } .overlay(alignment: .topLeading) { if addressBarFocused, !omnibarState.suggestions.isEmpty, omnibarPillFrame.width > 0 { OmnibarSuggestionsView( diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index b89a4d84..5ff9c992 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -718,14 +718,21 @@ class TabManager: ObservableObject { } var isFindVisible: Bool { - selectedTerminalPanel?.searchState != nil + if selectedTerminalPanel?.searchState != nil { return true } + if focusedBrowserPanel?.searchState != nil { return true } + return false } var canUseSelectionForFind: Bool { - selectedTerminalPanel?.hasSelection() == true + if focusedBrowserPanel != nil { return false } + return selectedTerminalPanel?.hasSelection() == true } func startSearch() { + if let browser = focusedBrowserPanel { + browser.startFind() + return + } guard let panel = selectedTerminalPanel else { #if DEBUG dlog("find.startSearch SKIPPED no selectedTerminalPanel") @@ -756,10 +763,18 @@ class TabManager: ObservableObject { } func findNext() { + if let browser = focusedBrowserPanel, browser.searchState != nil { + browser.findNext() + return + } _ = selectedTerminalPanel?.performBindingAction("search:next") } func findPrevious() { + if let browser = focusedBrowserPanel, browser.searchState != nil { + browser.findPrevious() + return + } _ = selectedTerminalPanel?.performBindingAction("search:previous") } @@ -770,6 +785,10 @@ class TabManager: ObservableObject { } func hideFind() { + if let browser = focusedBrowserPanel, browser.searchState != nil { + browser.hideFind() + return + } #if DEBUG dlog("find.hideFind panel=\(selectedTerminalPanel?.id.uuidString.prefix(5) ?? "nil")") #endif diff --git a/cmuxTests/BrowserFindJavaScriptTests.swift b/cmuxTests/BrowserFindJavaScriptTests.swift new file mode 100644 index 00000000..4de1cfb4 --- /dev/null +++ b/cmuxTests/BrowserFindJavaScriptTests.swift @@ -0,0 +1,116 @@ +import XCTest + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +final class BrowserFindJavaScriptTests: XCTestCase { + + // MARK: - searchScript + + func testSearchScriptReturnsNonEmptyJavaScript() { + let js = BrowserFindJavaScript.searchScript(query: "hello") + XCTAssertFalse(js.isEmpty) + XCTAssertTrue(js.contains("hello")) + } + + func testSearchScriptEmptyQueryReturnsEarlyReturn() { + let js = BrowserFindJavaScript.searchScript(query: "") + XCTAssertTrue(js.contains("total: 0")) + } + + // MARK: - nextScript / previousScript + + func testNextScriptReturnsValidJavaScript() { + let js = BrowserFindJavaScript.nextScript() + XCTAssertFalse(js.isEmpty) + XCTAssertTrue(js.contains("__cmuxFindMatches")) + } + + func testPreviousScriptReturnsValidJavaScript() { + let js = BrowserFindJavaScript.previousScript() + XCTAssertFalse(js.isEmpty) + XCTAssertTrue(js.contains("__cmuxFindMatches")) + } + + // MARK: - clearScript + + func testClearScriptReturnsValidJavaScript() { + let js = BrowserFindJavaScript.clearScript() + XCTAssertFalse(js.isEmpty) + XCTAssertTrue(js.contains("__cmux-find")) + } + + // MARK: - jsStringEscape + + func testEscapesDoubleQuotes() { + let result = BrowserFindJavaScript.jsStringEscape(#"say "hello""#) + XCTAssertEqual(result, #"say \"hello\""#) + } + + func testEscapesBackslashes() { + let result = BrowserFindJavaScript.jsStringEscape(#"path\to\file"#) + XCTAssertEqual(result, #"path\\to\\file"#) + } + + func testEscapesNewlines() { + let result = BrowserFindJavaScript.jsStringEscape("line1\nline2") + XCTAssertEqual(result, "line1\\nline2") + } + + func testEscapesCarriageReturns() { + let result = BrowserFindJavaScript.jsStringEscape("line1\rline2") + XCTAssertEqual(result, "line1\\rline2") + } + + func testEscapesTabs() { + let result = BrowserFindJavaScript.jsStringEscape("col1\tcol2") + XCTAssertEqual(result, "col1\\tcol2") + } + + func testPlainTextPassesThrough() { + let result = BrowserFindJavaScript.jsStringEscape("hello world 123") + XCTAssertEqual(result, "hello world 123") + } + + func testJapaneseTextPassesThrough() { + let result = BrowserFindJavaScript.jsStringEscape("こんにちは") + XCTAssertEqual(result, "こんにちは") + } + + func testMixedSpecialCharacters() { + let result = BrowserFindJavaScript.jsStringEscape(#"a\"b\nc"#) + XCTAssertEqual(result, #"a\\\"b\\nc"#) + } + + func testEscapesNullByte() { + let result = BrowserFindJavaScript.jsStringEscape("a\0b") + XCTAssertEqual(result, "a\\0b") + } + + func testEscapesLineSeparator() { + let result = BrowserFindJavaScript.jsStringEscape("a\u{2028}b") + XCTAssertEqual(result, "a\\u2028b") + } + + func testEscapesParagraphSeparator() { + let result = BrowserFindJavaScript.jsStringEscape("a\u{2029}b") + XCTAssertEqual(result, "a\\u2029b") + } + + // MARK: - searchScript escaping integration + + func testSearchScriptEscapesQueryInOutput() { + let js = BrowserFindJavaScript.searchScript(query: #"test"injection"#) + // The double quote should be escaped, not breaking the JS string literal. + XCTAssertTrue(js.contains(#"test\"injection"#)) + XCTAssertFalse(js.contains(#"test"injection"#)) + } + + func testSearchScriptHandlesLineSeparator() { + let js = BrowserFindJavaScript.searchScript(query: "test\u{2028}break") + XCTAssertTrue(js.contains("\\u2028")) + } +} From 28977c8e3b25892f6157c403c28f51cf81782eba Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:23:22 -0800 Subject: [PATCH 073/232] Fix browser panel lifecycle after WebContent process termination (#892) * Fix browser panel webview lifecycle after web content crashes * Fix BrowserPanel observer lifecycle during webview replacement * Fix WebKit termination delegate and harden lifecycle regression check --- Sources/Panels/BrowserPanel.swift | 164 +++++-- Sources/Panels/BrowserPanelView.swift | 410 +----------------- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 77 +++- ...t_browser_portal_lifecycle_architecture.py | 94 ++++ 4 files changed, 300 insertions(+), 445 deletions(-) create mode 100755 tests/test_browser_portal_lifecycle_architecture.py diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index b39e5fad..29e12036 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1389,7 +1389,11 @@ final class BrowserPanel: Panel, ObservableObject { private(set) var workspaceId: UUID /// The underlying web view - let webView: WKWebView + private(set) var webView: WKWebView + + /// Monotonic identity for the current WKWebView instance. + /// Incremented whenever we replace the underlying WKWebView after a process crash. + @Published private(set) var webViewInstanceID: UUID = UUID() /// Prevent the omnibar from auto-focusing for a short window after explicit programmatic focus. /// This avoids races where SwiftUI focus state steals first responder back from WebKit. @@ -1477,8 +1481,7 @@ final class BrowserPanel: Panel, ObservableObject { } } private var searchNeedleCancellable: AnyCancellable? - - private var cancellables = Set() + private var webViewCancellables = Set() private var navigationDelegate: BrowserNavigationDelegate? private var uiDelegate: BrowserUIDelegate? private var downloadDelegate: BrowserDownloadDelegate? @@ -1499,7 +1502,7 @@ final class BrowserPanel: Panel, ObservableObject { private let pageZoomStep: CGFloat = 0.1 private var insecureHTTPBypassHostOnce: String? private var insecureHTTPAlertFactory: () -> NSAlert - private var insecureHTTPAlertWindowProvider: () -> NSWindow? + private var insecureHTTPAlertWindowProvider: () -> NSWindow? = { NSApp.keyWindow ?? NSApp.mainWindow } // Persist user intent across WebKit detach/reattach churn (split/layout updates). private var preferredDeveloperToolsVisible: Bool = false private var forceDeveloperToolsRefreshOnNextAttach: Bool = false @@ -1527,13 +1530,7 @@ final class BrowserPanel: Panel, ObservableObject { false } - init(workspaceId: UUID, initialURL: URL? = nil, bypassInsecureHTTPHostOnce: String? = nil) { - self.id = UUID() - self.workspaceId = workspaceId - self.insecureHTTPBypassHostOnce = BrowserInsecureHTTPSettings.normalizeHost(bypassInsecureHTTPHostOnce ?? "") - self.browserThemeMode = BrowserThemeSettings.mode() - - // Configure web view + private static func makeWebView() -> CmuxWebView { let config = WKWebViewConfiguration() config.processPool = BrowserPanel.sharedProcessPool // Ensure browser cookies/storage persist across navigations and launches. @@ -1554,27 +1551,41 @@ final class BrowserPanel: Panel, ObservableObject { ) ) - // Set up web view let webView = CmuxWebView(frame: .zero, configuration: config) webView.allowsBackForwardNavigationGestures = true - - // Required for Web Inspector support on recent WebKit SDKs. if #available(macOS 13.3, *) { webView.isInspectable = true } - // Match the empty-page background to the terminal theme so newly-created browsers // don't flash white before content loads. webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor() - // Always present as Safari. webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent + return webView + } + private func bindWebView(_ webView: CmuxWebView) { + webView.onContextMenuDownloadStateChanged = { [weak self] downloading in + if downloading { + self?.beginDownloadActivity() + } else { + self?.endDownloadActivity() + } + } + webView.navigationDelegate = navigationDelegate + webView.uiDelegate = uiDelegate + setupObservers(for: webView) + } + + init(workspaceId: UUID, initialURL: URL? = nil, bypassInsecureHTTPHostOnce: String? = nil) { + self.id = UUID() + self.workspaceId = workspaceId + self.insecureHTTPBypassHostOnce = BrowserInsecureHTTPSettings.normalizeHost(bypassInsecureHTTPHostOnce ?? "") + self.browserThemeMode = BrowserThemeSettings.mode() + + let webView = Self.makeWebView() self.webView = webView self.insecureHTTPAlertFactory = { NSAlert() } - self.insecureHTTPAlertWindowProvider = { [weak webView] in - webView?.window ?? NSApp.keyWindow ?? NSApp.mainWindow - } // Set up navigation delegate let navDelegate = BrowserNavigationDelegate() @@ -1612,6 +1623,9 @@ final class BrowserPanel: Panel, ObservableObject { navDelegate.handleBlockedInsecureHTTPNavigation = { [weak self] request, intent in self?.presentInsecureHTTPAlert(for: request, intent: intent, recordTypedNavigation: false) } + navDelegate.didTerminateWebContentProcess = { [weak self] webView in + self?.replaceWebViewAfterContentProcessTermination(for: webView) + } // Set up download delegate for navigation-based downloads. // Downloads save to a temp file synchronously (no NSSavePanel during WebKit // callbacks), then show NSSavePanel after the download completes. @@ -1627,14 +1641,6 @@ final class BrowserPanel: Panel, ObservableObject { } navDelegate.downloadDelegate = dlDelegate self.downloadDelegate = dlDelegate - webView.onContextMenuDownloadStateChanged = { [weak self] downloading in - if downloading { - self?.beginDownloadActivity() - } else { - self?.endDownloadActivity() - } - } - webView.navigationDelegate = navDelegate self.navigationDelegate = navDelegate // Set up UI delegate (handles cmd+click, target=_blank, and context menu) @@ -1646,12 +1652,13 @@ final class BrowserPanel: Panel, ObservableObject { browserUIDelegate.requestNavigation = { [weak self] request, intent in self?.requestNavigation(request, intent: intent) } - webView.uiDelegate = browserUIDelegate self.uiDelegate = browserUIDelegate - // Observe web view properties - setupObservers() + bindWebView(webView) applyBrowserThemeModeIfNeeded() + insecureHTTPAlertWindowProvider = { [weak self] in + self?.webView.window ?? NSApp.keyWindow ?? NSApp.mainWindow + } // Navigate to initial URL if provided if let url = initialURL { @@ -1729,7 +1736,7 @@ final class BrowserPanel: Panel, ObservableObject { refreshNavigationAvailability() } - private func setupObservers() { + private func setupObservers(for webView: WKWebView) { // URL changes let urlObserver = webView.observe(\.url, options: [.new]) { [weak self] webView, _ in Task { @MainActor in @@ -1792,9 +1799,85 @@ final class BrowserPanel: Panel, ObservableObject { guard let self else { return } self.webView.underPageBackgroundColor = GhosttyBackgroundTheme.color(from: notification) } - .store(in: &cancellables) + .store(in: &webViewCancellables) } + private func replaceWebViewAfterContentProcessTermination(for terminatedWebView: WKWebView) { + guard terminatedWebView === webView else { return } + + let wasRenderable = shouldRenderWebView + let restoreURL = terminatedWebView.url ?? currentURL + let restoreURLString = restoreURL?.absoluteString + let shouldRestoreURL = wasRenderable && restoreURLString != nil && restoreURLString != blankURLString + let history = sessionNavigationHistorySnapshot() + let historyCurrentURL = preferredURLStringForOmnibar() + let desiredZoom = max(minPageZoom, min(maxPageZoom, terminatedWebView.pageZoom)) + let restoreDevTools = preferredDeveloperToolsVisible + +#if DEBUG + dlog( + "browser.webview.replace.begin panel=\(id.uuidString.prefix(5)) " + + "renderable=\(wasRenderable ? 1 : 0) restoreURL=\(restoreURLString ?? "nil") " + + "restoreHistoryBack=\(history.backHistoryURLStrings.count) " + + "restoreHistoryForward=\(history.forwardHistoryURLStrings.count)" + ) +#endif + + webViewObservers.removeAll() + webViewCancellables.removeAll() + BrowserWindowPortalRegistry.detach(webView: terminatedWebView) + terminatedWebView.stopLoading() + terminatedWebView.navigationDelegate = nil + terminatedWebView.uiDelegate = nil + if let terminatedCmuxWebView = terminatedWebView as? CmuxWebView { + terminatedCmuxWebView.onContextMenuDownloadStateChanged = nil + } + + let replacement = Self.makeWebView() + replacement.pageZoom = desiredZoom + webView = replacement + webViewInstanceID = UUID() + shouldRenderWebView = wasRenderable + + bindWebView(replacement) + + if !history.backHistoryURLStrings.isEmpty || !history.forwardHistoryURLStrings.isEmpty { + restoreSessionNavigationHistory( + backHistoryURLStrings: history.backHistoryURLStrings, + forwardHistoryURLStrings: history.forwardHistoryURLStrings, + currentURLString: historyCurrentURL + ) + } + + if shouldRestoreURL, let restoreURL { + navigateWithoutInsecureHTTPPrompt( + to: restoreURL, + recordTypedNavigation: false, + preserveRestoredSessionHistory: true + ) + } else { + refreshNavigationAvailability() + } + + if restoreDevTools { + requestDeveloperToolsRefreshAfterNextAttach(reason: "webcontent_process_terminated") + } + +#if DEBUG + dlog( + "browser.webview.replace.end panel=\(id.uuidString.prefix(5)) " + + "instance=\(webViewInstanceID.uuidString.prefix(6)) " + + "restoreURL=\(restoreURLString ?? "nil") shouldRestore=\(shouldRestoreURL ? 1 : 0)" + ) +#endif + } + +#if DEBUG + func debugSimulateWebContentProcessTermination() { + replaceWebViewAfterContentProcessTermination(for: webView) + } +#endif + // MARK: - Panel Protocol func focus() { @@ -1835,7 +1918,7 @@ final class BrowserPanel: Panel, ObservableObject { navigationDelegate = nil uiDelegate = nil webViewObservers.removeAll() - cancellables.removeAll() + webViewCancellables.removeAll() faviconTask?.cancel() faviconTask = nil } @@ -2191,7 +2274,7 @@ final class BrowserPanel: Panel, ObservableObject { BrowserWindowPortalRegistry.detach(webView: webView) } webViewObservers.removeAll() - cancellables.removeAll() + webViewCancellables.removeAll() } } @@ -2871,8 +2954,8 @@ extension BrowserPanel { func resetInsecureHTTPAlertHooksForTesting() { insecureHTTPAlertFactory = { NSAlert() } - insecureHTTPAlertWindowProvider = { [weak weakWebView = self.webView] in - weakWebView?.window ?? NSApp.keyWindow ?? NSApp.mainWindow + insecureHTTPAlertWindowProvider = { [weak self] in + self?.webView.window ?? NSApp.keyWindow ?? NSApp.mainWindow } } @@ -3155,6 +3238,7 @@ func browserNavigationShouldOpenInNewTab( private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { var didFinish: ((WKWebView) -> Void)? var didFailNavigation: ((WKWebView, String) -> Void)? + var didTerminateWebContentProcess: ((WKWebView) -> Void)? var openInNewTab: ((URL) -> Void)? var shouldBlockInsecureHTTPNavigation: ((URL) -> Bool)? var handleBlockedInsecureHTTPNavigation: ((URLRequest, BrowserInsecureHTTPNavigationIntent) -> Void)? @@ -3221,9 +3305,11 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { completionHandler(.performDefaultHandling, nil) } - func webView(_ webView: WKWebView, webContentProcessDidTerminate: WKWebView) { - NSLog("BrowserPanel web content process terminated, reloading") - webView.reload() + func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { +#if DEBUG + dlog("browser.webcontent.terminated panel=\(String(describing: self))") +#endif + didTerminateWebContentProcess?(webView) } private func loadErrorPage(in webView: WKWebView, failedURL: String, error: NSError) { diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index dc4856b8..f811b468 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -739,9 +739,9 @@ struct BrowserPanelView: View { isPanelFocused: isFocused, portalZPriority: portalPriority ) - // Keep the representable identity stable across bonsplit structural updates. - // This reduces WKWebView reparenting churn (and the associated WebKit crashes). - .id(panel.id) + // Keep the host stable for normal pane churn, but force a remount when + // BrowserPanel replaces its underlying WKWebView after process termination. + .id(panel.webViewInstanceID) .contentShape(Rectangle()) .simultaneousGesture(TapGesture().onEnded { // Chrome-like behavior: clicking web content while editing the @@ -3009,10 +3009,7 @@ struct WebViewRepresentable: NSViewRepresentable { final class Coordinator { weak var panel: BrowserPanel? weak var webView: WKWebView? - var attachRetryWorkItem: DispatchWorkItem? - var attachRetryCount: Int = 0 var attachGeneration: Int = 0 - var usesWindowPortal: Bool = false var desiredPortalVisibleInUI: Bool = true var desiredPortalZPriority: Int = 0 var lastPortalHostId: ObjectIdentifier? @@ -3239,350 +3236,29 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "portal.update", generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount, + retryCount: 0, details: Self.attachContext(webView: webView, host: host) ) #endif } - private static func attachWebView(_ webView: WKWebView, to host: NSView) { - // WebKit can crash if a WKWebView (or an internal first-responder object) stays first responder - // while being detached/reparented during bonsplit/SwiftUI structural updates. - if let window = webView.window { - let state = firstResponderResignState(window.firstResponder, webView: webView) - if state.needsResign { - window.makeFirstResponder(nil) - } - } - - // The target host can already be in-window while the source host is tearing down. - // Re-check against the target window too (it can differ during split churn). - if let window = host.window { - let state = firstResponderResignState(window.firstResponder, webView: webView) - if state.needsResign { - window.makeFirstResponder(nil) - } - } - - // Detach from any previous host (bonsplit/SwiftUI may rearrange views). - webView.removeFromSuperview() - host.subviews.forEach { $0.removeFromSuperview() } - host.addSubview(webView) - - // Work around WebKit bug 272474 where Inspect Element can render blank/flicker - // when WKWebView is edge-pinned using Auto Layout constraints. - webView.translatesAutoresizingMaskIntoConstraints = true - webView.autoresizingMask = [.width, .height] - webView.frame = host.bounds - - // Make reparenting resilient: WebKit can occasionally stay visually blank until forced to lay out. - webView.needsLayout = true - webView.layoutSubtreeIfNeeded() - webView.needsDisplay = true - webView.displayIfNeeded() - } - - private static func scheduleAttachRetry( - _ webView: WKWebView, - panel: BrowserPanel, - to host: NSView, - coordinator: Coordinator, - generation: Int - ) { - let retryInterval: TimeInterval = 1.0 / 60.0 - // Don't schedule multiple overlapping retries. - guard coordinator.attachRetryWorkItem == nil else { return } - - let work = DispatchWorkItem { [weak host, weak webView] in - coordinator.attachRetryWorkItem = nil - guard let host, let webView else { return } - guard coordinator.attachGeneration == generation else { return } - - // If already attached, we're done. - if webView.superview === host { - coordinator.attachRetryCount = 0 - return - } - - // Wait until the host is actually in a window. SwiftUI can create a new container before it - // is in a window during bonsplit tree updates; moving the webview too early can be flaky. - guard host.window != nil else { - coordinator.attachRetryCount += 1 - #if DEBUG - if coordinator.attachRetryCount == 1 || coordinator.attachRetryCount % 20 == 0 { - logDevToolsState( - panel, - event: "retry.waitingForWindow", - generation: generation, - retryCount: coordinator.attachRetryCount, - details: attachContext(webView: webView, host: host) - ) - } - #endif - // Be generous here: bonsplit structural updates can keep a representable - // container off-window longer than a few seconds under load. - if coordinator.attachRetryCount < 400 { - DispatchQueue.main.asyncAfter(deadline: .now() + retryInterval) { - scheduleAttachRetry( - webView, - panel: panel, - to: host, - coordinator: coordinator, - generation: generation - ) - } - } - return - } - - coordinator.attachRetryCount = 0 - #if DEBUG - logDevToolsState( - panel, - event: "retry.attach.begin", - generation: generation, - retryCount: 0, - details: attachContext(webView: webView, host: host) - ) - #endif - attachWebView(webView, to: host) - panel.restoreDeveloperToolsAfterAttachIfNeeded() - #if DEBUG - logDevToolsState( - panel, - event: "retry.attached", - generation: generation, - retryCount: 0, - details: attachContext(webView: webView, host: host) - ) - #endif - } - - coordinator.attachRetryWorkItem = work - DispatchQueue.main.asyncAfter(deadline: .now() + retryInterval, execute: work) - } - func updateNSView(_ nsView: NSView, context: Context) { let webView = panel.webView - context.coordinator.panel = panel - context.coordinator.webView = webView + let coordinator = context.coordinator + if let previousWebView = coordinator.webView, previousWebView !== webView { + BrowserWindowPortalRegistry.detach(webView: previousWebView) + coordinator.lastPortalHostId = nil + } + coordinator.panel = panel + coordinator.webView = webView Self.applyWebViewFirstResponderPolicy( panel: panel, webView: webView, isPanelFocused: isPanelFocused ) - let shouldUseWindowPortal = panel.shouldPreserveWebViewAttachmentDuringTransientHide() - if shouldUseWindowPortal { - context.coordinator.usesWindowPortal = true - Self.clearPortalCallbacks(for: nsView) - updateUsingWindowPortal(nsView, context: context, webView: webView) - Self.applyFocus( - panel: panel, - webView: webView, - nsView: nsView, - shouldFocusWebView: shouldFocusWebView, - isPanelFocused: isPanelFocused - ) - return - } - - if context.coordinator.usesWindowPortal { - BrowserWindowPortalRegistry.detach(webView: webView) - context.coordinator.usesWindowPortal = false - context.coordinator.lastPortalHostId = nil - } Self.clearPortalCallbacks(for: nsView) - - // Bonsplit keepAllAlive keeps hidden tabs alive (opacity 0). WKWebView is fragile when left - // in the window hierarchy while hidden and rapidly switching focus between tabs. To reduce - // WebKit crashes, detach the WKWebView when this surface is not the selected tab in its pane. - if !shouldAttachWebView { - // Split/layout churn can briefly create an off-window phase while DevTools is open. - // Detaching here can blank inspector content even when visibility preference stays true. - if nsView.window == nil, - webView.superview != nil, - panel.shouldPreserveWebViewAttachmentDuringTransientHide() { - #if DEBUG - Self.logDevToolsState( - panel, - event: "detach.skipped.offWindowDevTools", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - return - } - - #if DEBUG - Self.logDevToolsState( - panel, - event: "detach.beforeSync", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - panel.syncDeveloperToolsPreferenceFromInspector(preserveVisibleIntent: true) - #if DEBUG - Self.logDevToolsState( - panel, - event: "detach.afterSync", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - context.coordinator.attachRetryWorkItem?.cancel() - context.coordinator.attachRetryWorkItem = nil - context.coordinator.attachRetryCount = 0 - context.coordinator.attachGeneration += 1 - - // Resign focus if WebKit currently owns first responder. - if let window = webView.window ?? nsView.window { - let state = Self.firstResponderResignState(window.firstResponder, webView: webView) - if state.needsResign { - #if DEBUG - Self.logDevToolsState( - panel, - event: "detach.resignFirstResponder", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) + " " + state.flags - ) - #endif - window.makeFirstResponder(nil) - } - } - - if webView.superview != nil { - webView.removeFromSuperview() - } - nsView.subviews.forEach { $0.removeFromSuperview() } - #if DEBUG - Self.logDevToolsState( - panel, - event: "detach.done", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - return - } - - if webView.superview !== nsView { - // Cancel any pending retry; we'll reschedule if needed. - context.coordinator.attachRetryWorkItem?.cancel() - context.coordinator.attachRetryWorkItem = nil - context.coordinator.attachGeneration += 1 - - if let window = webView.window ?? nsView.window { - let state = Self.firstResponderResignState(window.firstResponder, webView: webView) - if state.needsResign { - #if DEBUG - Self.logDevToolsState( - panel, - event: "attach.reparent.resignFirstResponder.begin", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) + " " + state.flags - ) - #endif - let resigned = window.makeFirstResponder(nil) - #if DEBUG - Self.logDevToolsState( - panel, - event: "attach.reparent.resignFirstResponder.end", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) + " " + state.flags + " resigned=\(resigned ? 1 : 0)" - ) - #endif - } - } - - if nsView.window == nil { - // Avoid attaching to off-window containers; during bonsplit structural updates SwiftUI - // can create containers that are never inserted into the window. - if panel.shouldPreserveWebViewAttachmentDuringTransientHide() { - panel.requestDeveloperToolsRefreshAfterNextAttach(reason: "attach.defer.offWindow") - #if DEBUG - Self.logDevToolsState( - panel, - event: "attach.defer.requestRefresh", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - } - #if DEBUG - Self.logDevToolsState( - panel, - event: "attach.defer.offWindow", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - Self.scheduleAttachRetry( - webView, - panel: panel, - to: nsView, - coordinator: context.coordinator, - generation: context.coordinator.attachGeneration - ) - } else { - #if DEBUG - Self.logDevToolsState( - panel, - event: "attach.immediate.begin", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - Self.attachWebView(webView, to: nsView) - panel.restoreDeveloperToolsAfterAttachIfNeeded() - #if DEBUG - Self.logDevToolsState( - panel, - event: "attach.immediate", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - } - } else { - // Already attached; no need for any pending retry. - context.coordinator.attachRetryWorkItem?.cancel() - context.coordinator.attachRetryWorkItem = nil - context.coordinator.attachRetryCount = 0 - context.coordinator.attachGeneration += 1 - let hadPendingRefresh = panel.hasPendingDeveloperToolsRefreshAfterAttach() - panel.restoreDeveloperToolsAfterAttachIfNeeded() - #if DEBUG - if hadPendingRefresh { - Self.logDevToolsState( - panel, - event: "attach.alreadyAttached.consumePendingRefresh", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - } - Self.logDevToolsState( - panel, - event: "attach.alreadyAttached", - generation: context.coordinator.attachGeneration, - retryCount: context.coordinator.attachRetryCount, - details: Self.attachContext(webView: webView, host: nsView) - ) - #endif - } + updateUsingWindowPortal(nsView, context: context, webView: webView) Self.applyFocus( panel: panel, @@ -3639,38 +3315,12 @@ struct WebViewRepresentable: NSViewRepresentable { } static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) { - coordinator.attachRetryWorkItem?.cancel() - coordinator.attachRetryWorkItem = nil - coordinator.attachRetryCount = 0 coordinator.attachGeneration += 1 clearPortalCallbacks(for: nsView) guard let webView = coordinator.webView else { return } let panel = coordinator.panel - if coordinator.usesWindowPortal { - coordinator.usesWindowPortal = false - coordinator.lastPortalHostId = nil - - // During split/layout churn we keep the WKWebView portal-hosted so DevTools - // does not lose state. BrowserPanel deinit explicitly detaches on real teardown. - if let panel, panel.shouldPreserveWebViewAttachmentDuringTransientHide() { - #if DEBUG - logDevToolsState( - panel, - event: "dismantle.portal.keepAttached", - generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount, - details: attachContext(webView: webView, host: nsView) - ) - #endif - return - } - - BrowserWindowPortalRegistry.detach(webView: webView) - return - } - // If we're being torn down while the WKWebView (or one of its subviews) is first responder, // resign it before detaching. let window = webView.window ?? nsView.window @@ -3683,7 +3333,7 @@ struct WebViewRepresentable: NSViewRepresentable { panel, event: "dismantle.resignFirstResponder", generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount, + retryCount: 0, details: attachContext(webView: webView, host: nsView) + " " + state.flags ) } @@ -3691,37 +3341,7 @@ struct WebViewRepresentable: NSViewRepresentable { window.makeFirstResponder(nil) } } - - // During split/layout churn, SwiftUI may tear down a host view while a new one is still - // coming online. When DevTools is intended open, avoid eagerly detaching here. - if let panel, - panel.shouldPreserveWebViewAttachmentDuringTransientHide(), - webView.superview === nsView { - #if DEBUG - logDevToolsState( - panel, - event: "dismantle.skipDetach.devTools", - generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount, - details: attachContext(webView: webView, host: nsView) - ) - #endif - return - } - - if webView.superview === nsView { - webView.removeFromSuperview() - #if DEBUG - if let panel { - logDevToolsState( - panel, - event: "dismantle.detached", - generation: coordinator.attachGeneration, - retryCount: coordinator.attachRetryCount, - details: attachContext(webView: webView, host: nsView) - ) - } - #endif - } + BrowserWindowPortalRegistry.detach(webView: webView) + coordinator.lastPortalHostId = nil } } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 64bbf44a..7da75be5 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2209,6 +2209,31 @@ final class BrowserSessionHistoryRestoreTests: XCTestCase { XCTAssertTrue(panel.canGoBack) XCTAssertTrue(panel.canGoForward) } + + func testWebViewReplacementAfterProcessTerminationUpdatesInstanceIdentity() { + let panel = BrowserPanel( + workspaceId: UUID(), + initialURL: URL(string: "https://example.com") + ) + let oldWebView = panel.webView + let oldInstanceID = panel.webViewInstanceID + + panel.debugSimulateWebContentProcessTermination() + + XCTAssertFalse(panel.webView === oldWebView) + XCTAssertNotEqual(panel.webViewInstanceID, oldInstanceID) + XCTAssertNotNil(panel.webView.navigationDelegate) + XCTAssertNotNil(panel.webView.uiDelegate) + } + + func testWebViewReplacementPreservesEmptyNewTabRenderState() { + let panel = BrowserPanel(workspaceId: UUID()) + XCTAssertFalse(panel.shouldRenderWebView) + + panel.debugSimulateWebContentProcessTermination() + + XCTAssertFalse(panel.shouldRenderWebView) + } } @MainActor @@ -2336,10 +2361,27 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { XCTAssertFalse(panel.shouldPreserveWebViewAttachmentDuringTransientHide()) } - func testWebViewDismantleSkipsDetachWhenDeveloperToolsIntentIsVisible() { + func testWebViewDismantleDetachesPortalHostedWebViewWhenDeveloperToolsIntentIsVisible() { let (panel, _) = makePanelWithInspector() XCTAssertTrue(panel.showDeveloperTools()) + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + let anchor = NSView(frame: NSRect(x: 30, y: 30, width: 180, height: 140)) + window.contentView?.addSubview(anchor) + window.makeKeyAndOrderFront(nil) + window.displayIfNeeded() + window.contentView?.layoutSubtreeIfNeeded() + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + + BrowserWindowPortalRegistry.bind(webView: panel.webView, to: anchor, visibleInUI: true, zPriority: 1) + BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) + XCTAssertNotNil(panel.webView.superview) + let representable = WebViewRepresentable( panel: panel, shouldAttachWebView: true, @@ -2349,18 +2391,33 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { ) let coordinator = representable.makeCoordinator() coordinator.webView = panel.webView - let host = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100)) - host.addSubview(panel.webView) + WebViewRepresentable.dismantleNSView(anchor, coordinator: coordinator) - WebViewRepresentable.dismantleNSView(host, coordinator: coordinator) - - XCTAssertTrue(panel.webView.superview === host) + XCTAssertNil(panel.webView.superview) + window.orderOut(nil) } - func testWebViewDismantleDetachesWhenDeveloperToolsIntentIsHidden() { + func testWebViewDismantleDetachesPortalHostedWebViewWhenDeveloperToolsIntentIsHidden() { let (panel, _) = makePanelWithInspector() XCTAssertFalse(panel.shouldPreserveWebViewAttachmentDuringTransientHide()) + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + let anchor = NSView(frame: NSRect(x: 20, y: 20, width: 200, height: 150)) + window.contentView?.addSubview(anchor) + window.makeKeyAndOrderFront(nil) + window.displayIfNeeded() + window.contentView?.layoutSubtreeIfNeeded() + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + + BrowserWindowPortalRegistry.bind(webView: panel.webView, to: anchor, visibleInUI: true, zPriority: 1) + BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) + XCTAssertNotNil(panel.webView.superview) + let representable = WebViewRepresentable( panel: panel, shouldAttachWebView: true, @@ -2370,12 +2427,10 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { ) let coordinator = representable.makeCoordinator() coordinator.webView = panel.webView - let host = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100)) - host.addSubview(panel.webView) - - WebViewRepresentable.dismantleNSView(host, coordinator: coordinator) + WebViewRepresentable.dismantleNSView(anchor, coordinator: coordinator) XCTAssertNil(panel.webView.superview) + window.orderOut(nil) } } diff --git a/tests/test_browser_portal_lifecycle_architecture.py b/tests/test_browser_portal_lifecycle_architecture.py new file mode 100755 index 00000000..9b286924 --- /dev/null +++ b/tests/test_browser_portal_lifecycle_architecture.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Static regression checks for deterministic browser lifecycle architecture. + +Guards the long-term browser mounting design: +1) BrowserPanelView updateNSView must use a single portal-based mount path. +2) Legacy attach-retry and direct attach/detach churn helpers stay removed. +3) BrowserPanel handles WebContent termination via deterministic webview replacement, + not blind `webView.reload()`. +""" + +from __future__ import annotations + +import subprocess +import shutil +from pathlib import Path + + +def repo_root() -> Path: + git_path = shutil.which("git") + git_command = git_path if git_path else "git" + result = subprocess.run( + [git_command, "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + ) + if result.returncode == 0: + return Path(result.stdout.strip()) + return Path(__file__).resolve().parents[1] + + +def extract_block(source: str, signature: str) -> str: + start = source.find(signature) + if start < 0: + raise ValueError(f"Missing signature: {signature}") + brace_start = source.find("{", start) + if brace_start < 0: + raise ValueError(f"Missing opening brace for: {signature}") + depth = 0 + for idx in range(brace_start, len(source)): + char = source[idx] + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return source[brace_start : idx + 1] + raise ValueError(f"Unbalanced braces for: {signature}") + + +def main() -> int: + root = repo_root() + failures: list[str] = [] + + view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift" + view_source = view_path.read_text(encoding="utf-8") + + if "updateUsingWindowPortal(nsView, context: context, webView: webView)" not in view_source: + failures.append("updateNSView no longer routes through updateUsingWindowPortal") + if "scheduleAttachRetry(" in view_source: + failures.append("Legacy attach retry helper still present in BrowserPanelView") + if "attachRetryWorkItem" in view_source: + failures.append("Legacy attachRetryWorkItem state still present in BrowserPanelView") + if "usesWindowPortal" in view_source: + failures.append("Dual portal/non-portal lifecycle state still present in BrowserPanelView") + + panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift" + panel_source = panel_path.read_text(encoding="utf-8") + + if "@Published private(set) var webViewInstanceID" not in panel_source: + failures.append("BrowserPanel is missing webViewInstanceID for deterministic instance remounting") + if "replaceWebViewAfterContentProcessTermination" not in panel_source: + failures.append("BrowserPanel is missing deterministic WebContent termination replacement path") + + terminate_delegate = extract_block( + panel_source, + "func webViewWebContentProcessDidTerminate(_ webView: WKWebView)", + ) + if "didTerminateWebContentProcess?(webView)" not in terminate_delegate: + failures.append("webContentProcessDidTerminate no longer delegates to deterministic replacement handler") + if "webView.reload()" in terminate_delegate: + failures.append("webContentProcessDidTerminate still does blind webView.reload()") + + if failures: + print("FAIL: browser lifecycle architecture regression guards failed") + for item in failures: + print(f" - {item}") + return 1 + + print("PASS: browser lifecycle architecture regression guards are in place") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 80bbfdf20670143f69dbea9174e68ebad816956b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:18:07 -0800 Subject: [PATCH 074/232] Add custom file support for notification sounds (#869) * Add custom-file notification sound option * Add notification permission controls and test action * Allow notification enable retries from settings * Add notification permission flow debug logging --- Sources/AppDelegate.swift | 6 +- Sources/TerminalNotificationStore.swift | 411 ++++++++++++++++-- Sources/cmuxApp.swift | 183 +++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 183 ++++++++ 4 files changed, 739 insertions(+), 44 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index a6f996fb..ab3f5579 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1791,7 +1791,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent PostHogAnalytics.shared.trackHourlyActive(reason: "didBecomeActive") } - guard let tabManager, let notificationStore else { return } + guard let notificationStore else { return } + notificationStore.handleApplicationDidBecomeActive() + guard let tabManager else { return } guard let tabId = tabManager.selectedTabId else { return } let surfaceId = tabManager.focusedSurfaceId(for: tabId) guard notificationStore.hasUnreadNotification(forTabId: tabId, surfaceId: surfaceId) else { return } @@ -7619,7 +7621,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { var options: UNNotificationPresentationOptions = [.banner, .list] - if !NotificationSoundSettings.isSilent() { + if notification.request.content.sound != nil { options.insert(.sound) } completionHandler(options) diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 06d8a97d..4d5ba1b6 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -1,6 +1,7 @@ import AppKit import Foundation import UserNotifications +import Bonsplit // UNUserNotificationCenter.removeDeliveredNotifications(withIdentifiers:) and // removePendingNotificationRequests(withIdentifiers:) perform synchronous XPC to @@ -31,6 +32,10 @@ extension UNUserNotificationCenter { enum NotificationSoundSettings { static let key = "notificationSound" static let defaultValue = "default" + static let customFileValue = "custom_file" + static let customFilePathKey = "notificationSoundCustomFilePath" + static let defaultCustomFilePath = "" + private static let stagedCustomSoundBaseName = "cmux-custom-notification-sound" static let customCommandKey = "notificationCustomCommand" static let defaultCustomCommand = "" @@ -50,6 +55,7 @@ enum NotificationSoundSettings { ("Sosumi", "Sosumi"), ("Submarine", "Submarine"), ("Tink", "Tink"), + ("Custom File...", customFileValue), ("None", "none"), ] @@ -60,26 +66,157 @@ enum NotificationSoundSettings { return .default case "none": return nil + case customFileValue: + guard let customSoundName = stagedCustomSoundName(defaults: defaults) else { + return nil + } + return UNNotificationSound(named: UNNotificationSoundName(rawValue: customSoundName)) default: return UNNotificationSound(named: UNNotificationSoundName(rawValue: value)) } } + static func usesSystemSound(defaults: UserDefaults = .standard) -> Bool { + let value = defaults.string(forKey: key) ?? defaultValue + switch value { + case "none": + return false + case customFileValue: + return customFileURL(defaults: defaults) != nil + default: + return true + } + } + static func isSilent(defaults: UserDefaults = .standard) -> Bool { return (defaults.string(forKey: key) ?? defaultValue) == "none" } - static func previewSound(value: String) { + static func isCustomFileSelected(defaults: UserDefaults = .standard) -> Bool { + (defaults.string(forKey: key) ?? defaultValue) == customFileValue + } + + static func stagedCustomSoundName(defaults: UserDefaults = .standard) -> String? { + guard let sourceURL = customFileURL(defaults: defaults) else { return nil } + let sourceExtension = sourceURL.pathExtension.trimmingCharacters(in: .whitespacesAndNewlines) + guard !sourceExtension.isEmpty else { + NSLog("Notification custom sound requires a file extension: \(sourceURL.path)") + return nil + } + + let destinationDirectory = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Sounds", isDirectory: true) + let destinationFileName = "\(stagedCustomSoundBaseName).\(sourceExtension.lowercased())" + let destinationURL = destinationDirectory.appendingPathComponent(destinationFileName, isDirectory: false) + let fileManager = FileManager.default + + do { + try fileManager.createDirectory(at: destinationDirectory, withIntermediateDirectories: true) + try copyStagedSoundIfNeeded(from: sourceURL, to: destinationURL, fileManager: fileManager) + try cleanupStaleStagedSoundFiles( + in: destinationDirectory, + keeping: destinationFileName, + preservingSourceURL: sourceURL, + fileManager: fileManager + ) + return destinationFileName + } catch { + NSLog("Failed to stage custom notification sound: \(error)") + return nil + } + } + + static func customFileURL(defaults: UserDefaults = .standard) -> URL? { + guard let path = normalizedCustomFilePath(defaults.string(forKey: customFilePathKey) ?? defaultCustomFilePath) else { + return nil + } + return URL(fileURLWithPath: (path as NSString).expandingTildeInPath) + } + + static func playCustomFileSound(defaults: UserDefaults = .standard) { + guard let url = customFileURL(defaults: defaults) else { return } + playSoundFile(at: url) + } + + static func playCustomFileSound(path: String) { + guard let normalizedPath = normalizedCustomFilePath(path) else { return } + let url = URL(fileURLWithPath: (normalizedPath as NSString).expandingTildeInPath) + playSoundFile(at: url) + } + + static func previewSound(value: String, defaults: UserDefaults = .standard) { switch value { case "default": NSSound.beep() case "none": break + case customFileValue: + playCustomFileSound(defaults: defaults) default: NSSound(named: NSSound.Name(value))?.play() } } + private static func normalizedCustomFilePath(_ rawPath: String) -> String? { + let trimmed = rawPath.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + return trimmed + } + + private static func playSoundFile(at url: URL) { + DispatchQueue.main.async { + guard let sound = NSSound(contentsOf: url, byReference: false) else { + NSLog("Notification custom sound failed to load from path: \(url.path)") + return + } + sound.play() + } + } + + private static func cleanupStaleStagedSoundFiles( + in directoryURL: URL, + keeping fileName: String, + preservingSourceURL: URL, + fileManager: FileManager + ) throws { + let prefix = "\(stagedCustomSoundBaseName)." + let normalizedSource = preservingSourceURL.standardizedFileURL + for fileNameCandidate in try fileManager.contentsOfDirectory(atPath: directoryURL.path) { + guard fileNameCandidate.hasPrefix(prefix), fileNameCandidate != fileName else { continue } + let staleURL = directoryURL.appendingPathComponent(fileNameCandidate, isDirectory: false) + if staleURL.standardizedFileURL == normalizedSource { + continue + } + try? fileManager.removeItem(at: staleURL) + } + } + + private static func copyStagedSoundIfNeeded( + from sourceURL: URL, + to destinationURL: URL, + fileManager: FileManager + ) throws { + let normalizedSource = sourceURL.standardizedFileURL + let normalizedDestination = destinationURL.standardizedFileURL + guard normalizedSource != normalizedDestination else { return } + + if fileManager.fileExists(atPath: normalizedDestination.path) { + let sourceAttributes = try fileManager.attributesOfItem(atPath: normalizedSource.path) + let destinationAttributes = try fileManager.attributesOfItem(atPath: normalizedDestination.path) + let sourceSize = sourceAttributes[.size] as? NSNumber + let destinationSize = destinationAttributes[.size] as? NSNumber + let sourceDate = sourceAttributes[.modificationDate] as? Date + let destinationDate = destinationAttributes[.modificationDate] as? Date + if sourceSize == destinationSize && sourceDate == destinationDate { + return + } + try fileManager.removeItem(at: normalizedDestination) + } + + try fileManager.copyItem(at: normalizedSource, to: normalizedDestination) + } + private static let customCommandQueue = DispatchQueue( label: "com.cmuxterm.notification-custom-command", qos: .utility @@ -165,6 +302,39 @@ enum AppFocusState { } } +enum NotificationAuthorizationState: Equatable { + case unknown + case notDetermined + case authorized + case denied + case provisional + case ephemeral + + var statusLabel: String { + switch self { + case .unknown, .notDetermined: + return "Not Requested" + case .authorized: + return "Allowed" + case .denied: + return "Denied" + case .provisional: + return "Deliver Quietly" + case .ephemeral: + return "Temporary" + } + } + + var allowsDelivery: Bool { + switch self { + case .authorized, .provisional, .ephemeral: + return true + case .unknown, .notDetermined, .denied: + return false + } + } +} + struct TerminalNotification: Identifiable, Hashable { let id: UUID let tabId: UUID @@ -195,6 +365,11 @@ final class TerminalNotificationStore: ObservableObject { static let categoryIdentifier = "com.cmuxterm.app.userNotification" static let actionShowIdentifier = "com.cmuxterm.app.userNotification.show" + private enum AuthorizationRequestOrigin: String { + case notificationDelivery = "notification_delivery" + case settingsButton = "settings_button" + case settingsTest = "settings_test" + } @Published private(set) var notifications: [TerminalNotification] = [] { didSet { @@ -202,9 +377,11 @@ final class TerminalNotificationStore: ObservableObject { refreshDockBadge() } } + @Published private(set) var authorizationState: NotificationAuthorizationState = .unknown private let center = UNUserNotificationCenter.current() - private var hasRequestedAuthorization = false + private var hasRequestedAutomaticAuthorization = false + private var hasDeferredAuthorizationRequest = false private var hasPromptedForSettings = false private var userDefaultsObserver: NSObjectProtocol? private let settingsPromptWindowRetryDelay: TimeInterval = 0.5 @@ -237,6 +414,7 @@ final class TerminalNotificationStore: ObservableObject { self?.refreshDockBadge() } refreshDockBadge() + refreshAuthorizationStatus() } deinit { @@ -268,6 +446,98 @@ final class TerminalNotificationStore: ObservableObject { indexes.unreadCount } + private func logAuthorization(_ message: String) { +#if DEBUG + dlog("notification.auth \(message)") +#endif + NSLog("notification.auth %@", message) + } + + private static func authorizationStatusLabel(_ status: UNAuthorizationStatus) -> String { + switch status { + case .notDetermined: + return "notDetermined" + case .denied: + return "denied" + case .authorized: + return "authorized" + case .provisional: + return "provisional" + case .ephemeral: + return "ephemeral" + @unknown default: + return "unknown(\(status.rawValue))" + } + } + + func refreshAuthorizationStatus() { + center.getNotificationSettings { [weak self] settings in + DispatchQueue.main.async { + guard let self else { return } + self.authorizationState = Self.authorizationState(from: settings.authorizationStatus) + self.logAuthorization( + "refresh status=\(Self.authorizationStatusLabel(settings.authorizationStatus)) mapped=\(self.authorizationState.statusLabel)" + ) + } + } + } + + func requestAuthorizationFromSettings() { + logAuthorization("settings request tapped state=\(authorizationState.statusLabel)") + ensureAuthorization(origin: .settingsButton) { _ in } + } + + func openNotificationSettings() { + guard let url = URL(string: "x-apple.systempreferences:com.apple.preference.notifications") else { + return + } + logAuthorization("open settings url=\(url.absoluteString)") + notificationSettingsURLOpener(url) + } + + func sendSettingsTestNotification() { + logAuthorization("settings test tapped state=\(authorizationState.statusLabel)") + ensureAuthorization(origin: .settingsTest) { [weak self] authorized in + guard let self, authorized else { return } + + let content = UNMutableNotificationContent() + content.title = "cmux test notification" + content.body = "Desktop notifications are enabled." + content.sound = NotificationSoundSettings.sound() + content.categoryIdentifier = Self.categoryIdentifier + + let request = UNNotificationRequest( + identifier: "cmux.settings.test.\(UUID().uuidString)", + content: content, + trigger: nil + ) + + self.center.add(request) { error in + if let error { + NSLog("Failed to schedule test notification: \(error)") + self.logAuthorization("settings test schedule failed error=\(error.localizedDescription)") + } else { + self.logAuthorization("settings test schedule succeeded") + NotificationSoundSettings.runCustomCommand( + title: content.title, + subtitle: content.subtitle, + body: content.body + ) + } + } + } + } + + func handleApplicationDidBecomeActive() { + logAuthorization("app became active deferred=\(hasDeferredAuthorizationRequest)") + if hasDeferredAuthorizationRequest { + hasDeferredAuthorizationRequest = false + ensureAuthorization(origin: .settingsButton) { _ in } + return + } + refreshAuthorizationStatus() + } + func unreadCount(forTabId tabId: UUID) -> Int { indexes.unreadCountByTabId[tabId] ?? 0 } @@ -450,7 +720,7 @@ final class TerminalNotificationStore: ObservableObject { } private func scheduleUserNotification(_ notification: TerminalNotification) { - ensureAuthorization { [weak self] authorized in + ensureAuthorization(origin: .notificationDelivery) { [weak self] authorized in guard let self, authorized else { return } let content = UNMutableNotificationContent() @@ -490,41 +760,90 @@ final class TerminalNotificationStore: ObservableObject { } } - private func ensureAuthorization(_ completion: @escaping (Bool) -> Void) { + private func ensureAuthorization( + origin: AuthorizationRequestOrigin, + _ completion: @escaping (Bool) -> Void + ) { + logAuthorization("ensure start origin=\(origin.rawValue)") center.getNotificationSettings { [weak self] settings in - guard let self else { - completion(false) - return - } + DispatchQueue.main.async { + guard let self else { + completion(false) + return + } - switch settings.authorizationStatus { - case .authorized, .provisional, .ephemeral: - completion(true) - case .denied: - self.promptToEnableNotifications() - completion(false) - case .notDetermined: - self.requestAuthorizationIfNeeded(completion) - @unknown default: - completion(false) + self.authorizationState = Self.authorizationState(from: settings.authorizationStatus) + self.logAuthorization( + "ensure status origin=\(origin.rawValue) status=\(Self.authorizationStatusLabel(settings.authorizationStatus)) mapped=\(self.authorizationState.statusLabel) appActive=\(AppFocusState.isAppActive())" + ) + switch settings.authorizationStatus { + case .authorized, .provisional, .ephemeral: + completion(true) + case .denied: + self.logAuthorization("ensure denied origin=\(origin.rawValue) prompting_settings") + self.promptToEnableNotifications() + completion(false) + case .notDetermined: + if Self.shouldDeferAutomaticAuthorizationRequest( + origin: origin, + status: settings.authorizationStatus, + isAppActive: AppFocusState.isAppActive() + ) { + self.logAuthorization("ensure deferred origin=\(origin.rawValue)") + self.hasDeferredAuthorizationRequest = true + completion(false) + } else { + self.requestAuthorizationIfNeeded(origin: origin, completion) + } + @unknown default: + self.logAuthorization("ensure unknown status origin=\(origin.rawValue)") + completion(false) + } } } } - private func requestAuthorizationIfNeeded(_ completion: @escaping (Bool) -> Void) { - guard !hasRequestedAuthorization else { + private func requestAuthorizationIfNeeded( + origin: AuthorizationRequestOrigin, + _ completion: @escaping (Bool) -> Void + ) { + let isAutomaticRequest = origin == .notificationDelivery + guard Self.shouldRequestAuthorization( + isAutomaticRequest: isAutomaticRequest, + hasRequestedAutomaticAuthorization: hasRequestedAutomaticAuthorization + ) else { + logAuthorization( + "request blocked origin=\(origin.rawValue) automatic=\(isAutomaticRequest) hasRequestedAutomatic=\(hasRequestedAutomaticAuthorization)" + ) completion(false) return } - hasRequestedAuthorization = true - center.requestAuthorization(options: [.alert, .sound]) { granted, _ in - completion(granted) + if isAutomaticRequest { + hasRequestedAutomaticAuthorization = true + } + hasDeferredAuthorizationRequest = false + logAuthorization( + "request starting origin=\(origin.rawValue) automatic=\(isAutomaticRequest) hasRequestedAutomatic=\(hasRequestedAutomaticAuthorization)" + ) + center.requestAuthorization(options: [.alert, .sound]) { granted, error in + DispatchQueue.main.async { + if granted { + self.authorizationState = .authorized + } else { + self.refreshAuthorizationStatus() + } + self.logAuthorization( + "request callback origin=\(origin.rawValue) granted=\(granted) error=\(error?.localizedDescription ?? "nil") mapped=\(self.authorizationState.statusLabel)" + ) + completion(granted) + } } } private func promptToEnableNotifications() { DispatchQueue.main.async { [weak self] in guard let self, !self.hasPromptedForSettings else { return } + self.logAuthorization("prompt settings shown") self.hasPromptedForSettings = true self.presentNotificationSettingsPrompt(attempt: 0) } @@ -550,14 +869,54 @@ final class TerminalNotificationStore: ObservableObject { alert.addButton(withTitle: String(localized: "dialog.enableNotifications.openSettings", defaultValue: "Open Settings")) alert.addButton(withTitle: String(localized: "dialog.enableNotifications.notNow", defaultValue: "Not Now")) alert.beginSheetModal(for: window) { [weak self] response in - guard response == .alertFirstButtonReturn, - let url = URL(string: "x-apple.systempreferences:com.apple.preference.notifications") else { + guard response == .alertFirstButtonReturn else { return } - self?.notificationSettingsURLOpener(url) + self?.openNotificationSettings() } } + static func authorizationState(from status: UNAuthorizationStatus) -> NotificationAuthorizationState { + switch status { + case .authorized: + return .authorized + case .denied: + return .denied + case .notDetermined: + return .notDetermined + case .provisional: + return .provisional + case .ephemeral: + return .ephemeral + @unknown default: + return .unknown + } + } + + static func shouldDeferAutomaticAuthorizationRequest( + status: UNAuthorizationStatus, + isAppActive: Bool + ) -> Bool { + status == .notDetermined && !isAppActive + } + + static func shouldRequestAuthorization( + isAutomaticRequest: Bool, + hasRequestedAutomaticAuthorization: Bool + ) -> Bool { + guard isAutomaticRequest else { return true } + return !hasRequestedAutomaticAuthorization + } + + private static func shouldDeferAutomaticAuthorizationRequest( + origin: AuthorizationRequestOrigin, + status: UNAuthorizationStatus, + isAppActive: Bool + ) -> Bool { + guard origin == .notificationDelivery else { return false } + return shouldDeferAutomaticAuthorizationRequest(status: status, isAppActive: isAppActive) + } + private static func buildIndexes(for notifications: [TerminalNotification]) -> NotificationIndexes { var indexes = NotificationIndexes() for notification in notifications { diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 5d3d6500..8b36f4bc 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2,6 +2,7 @@ import AppKit import SwiftUI import Darwin import Bonsplit +import UniformTypeIdentifiers @main struct cmuxApp: App { @@ -2786,6 +2787,8 @@ struct SettingsView: View { private var browserExternalOpenPatterns = BrowserLinkOpenSettings.defaultBrowserExternalOpenPatterns @AppStorage(BrowserInsecureHTTPSettings.allowlistKey) private var browserInsecureHTTPAllowlist = BrowserInsecureHTTPSettings.defaultAllowlistText @AppStorage(NotificationSoundSettings.key) private var notificationSound = NotificationSoundSettings.defaultValue + @AppStorage(NotificationSoundSettings.customFilePathKey) + private var notificationSoundCustomFilePath = NotificationSoundSettings.defaultCustomFilePath @AppStorage(NotificationSoundSettings.customCommandKey) private var notificationCustomCommand = NotificationSoundSettings.defaultCustomCommand @AppStorage(NotificationBadgeSettings.dockBadgeEnabledKey) private var notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled @AppStorage(QuitWarningSettings.warnBeforeQuitKey) private var warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit @@ -2806,6 +2809,7 @@ struct SettingsView: View { @AppStorage("sidebarShowLog") private var sidebarShowLog = true @AppStorage("sidebarShowProgress") private var sidebarShowProgress = true @AppStorage("sidebarShowStatusPills") private var sidebarShowMetadata = true + @ObservedObject private var notificationStore = TerminalNotificationStore.shared @State private var shortcutResetToken = UUID() @State private var topBlurOpacity: Double = 0 @State private var topBlurBaselineOffset: CGFloat? @@ -2894,12 +2898,109 @@ struct SettingsView: View { browserInsecureHTTPAllowlistDraft != browserInsecureHTTPAllowlist } + private var hasCustomNotificationSoundFilePath: Bool { + !notificationSoundCustomFilePath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + + private var notificationSoundCustomFileDisplayName: String { + guard hasCustomNotificationSoundFilePath else { + return "No file selected" + } + return URL(fileURLWithPath: notificationSoundCustomFilePath).lastPathComponent + } + + private var canPreviewNotificationSound: Bool { + switch notificationSound { + case "none": + return false + case NotificationSoundSettings.customFileValue: + return hasCustomNotificationSoundFilePath + default: + return true + } + } + + private var notificationPermissionStatusText: String { + notificationStore.authorizationState.statusLabel + } + + private var notificationPermissionStatusColor: Color { + switch notificationStore.authorizationState { + case .authorized, .provisional, .ephemeral: + return .green + case .denied: + return .red + case .unknown, .notDetermined: + return .secondary + } + } + + private var notificationPermissionSubtitle: String { + switch notificationStore.authorizationState { + case .unknown, .notDetermined: + return "Desktop notifications are not enabled yet." + case .authorized: + return "Desktop notifications are enabled." + case .denied: + return "Desktop notifications are disabled in System Settings." + case .provisional: + return "Desktop notifications are enabled with quiet delivery." + case .ephemeral: + return "Desktop notifications are temporarily enabled." + } + } + + private var notificationPermissionActionTitle: String { + switch notificationStore.authorizationState { + case .unknown, .notDetermined: + return "Enable" + case .authorized, .denied, .provisional, .ephemeral: + return "Open Settings" + } + } + private func blurOpacity(forContentOffset offset: CGFloat) -> Double { guard let baseline = topBlurBaselineOffset else { return 0 } let reveal = (baseline - offset) / 24 return Double(min(max(reveal, 0), 1)) } + private func previewNotificationSound() { + if notificationSound == NotificationSoundSettings.customFileValue { + NotificationSoundSettings.playCustomFileSound(path: notificationSoundCustomFilePath) + return + } + NotificationSoundSettings.previewSound(value: notificationSound) + } + + private func chooseNotificationSoundFile() { + let panel = NSOpenPanel() + panel.canChooseFiles = true + panel.canChooseDirectories = false + panel.allowsMultipleSelection = false + panel.allowedContentTypes = [.audio] + panel.title = "Choose Notification Sound" + panel.prompt = "Choose" + guard panel.runModal() == .OK, let url = panel.url else { return } + notificationSoundCustomFilePath = url.path + notificationSound = NotificationSoundSettings.customFileValue + previewNotificationSound() + } + + private func handleNotificationPermissionAction() { + let state = notificationStore.authorizationState.statusLabel +#if DEBUG + dlog("notification.ui enableTapped state=\(state)") +#endif + NSLog("notification.ui enableTapped state=%@", state) + switch notificationStore.authorizationState { + case .unknown, .notDetermined: + notificationStore.requestAuthorizationFromSettings() + case .authorized, .denied, .provisional, .ephemeral: + notificationStore.openNotificationSettings() + } + } + private func saveSocketPassword() { let trimmed = socketPasswordDraft.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { @@ -3017,25 +3118,73 @@ struct SettingsView: View { SettingsCardDivider() - SettingsPickerRow( - "Notification Sound", - subtitle: "Sound played when a notification arrives.", - controlWidth: pickerColumnWidth, - selection: $notificationSound + SettingsCardRow( + "Desktop Notifications", + subtitle: notificationPermissionSubtitle ) { - ForEach(NotificationSoundSettings.systemSounds, id: \.value) { sound in - Text(sound.label).tag(sound.value) + HStack(spacing: 6) { + Text(notificationPermissionStatusText) + .font(.system(size: 11, weight: .semibold)) + .foregroundStyle(notificationPermissionStatusColor) + .frame(width: 98, alignment: .trailing) + + Button(notificationPermissionActionTitle) { + handleNotificationPermissionAction() + } + .controlSize(.small) + + Button("Send Test") { + notificationStore.sendSettingsTestNotification() + } + .controlSize(.small) } - } extraTrailing: { - Button { - NotificationSoundSettings.previewSound(value: notificationSound) - } label: { - Image(systemName: "play.fill") - .font(.system(size: 9)) + } + + SettingsCardDivider() + + SettingsCardRow( + "Notification Sound", + subtitle: "Sound played when a notification arrives." + ) { + VStack(alignment: .trailing, spacing: 6) { + HStack(spacing: 6) { + Picker("", selection: $notificationSound) { + ForEach(NotificationSoundSettings.systemSounds, id: \.value) { sound in + Text(sound.label).tag(sound.value) + } + } + .labelsHidden() + Button { + previewNotificationSound() + } label: { + Image(systemName: "play.fill") + .font(.system(size: 9)) + } + .buttonStyle(.bordered) + .controlSize(.small) + .disabled(!canPreviewNotificationSound) + } + + if notificationSound == NotificationSoundSettings.customFileValue { + HStack(spacing: 6) { + Text(notificationSoundCustomFileDisplayName) + .font(.system(size: 11)) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.middle) + .frame(width: 170, alignment: .trailing) + Button("Choose...") { + chooseNotificationSoundFile() + } + .controlSize(.small) + Button("Clear") { + notificationSoundCustomFilePath = NotificationSoundSettings.defaultCustomFilePath + } + .controlSize(.small) + .disabled(!hasCustomNotificationSoundFilePath) + } + } } - .buttonStyle(.bordered) - .controlSize(.small) - .disabled(notificationSound == "none") } SettingsCardDivider() @@ -3681,6 +3830,7 @@ struct SettingsView: View { .toggleStyle(.switch) .onAppear { BrowserHistoryStore.shared.loadIfNeeded() + notificationStore.refreshAuthorizationStatus() browserThemeMode = BrowserThemeSettings.mode(defaults: .standard).rawValue browserHistoryEntryCount = BrowserHistoryStore.shared.entries.count browserInsecureHTTPAllowlistDraft = browserInsecureHTTPAllowlist @@ -3771,6 +3921,7 @@ struct SettingsView: View { browserInsecureHTTPAllowlist = BrowserInsecureHTTPSettings.defaultAllowlistText browserInsecureHTTPAllowlistDraft = BrowserInsecureHTTPSettings.defaultAllowlistText notificationSound = NotificationSoundSettings.defaultValue + notificationSoundCustomFilePath = NotificationSoundSettings.defaultCustomFilePath notificationCustomCommand = NotificationSoundSettings.defaultCustomCommand notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 7da75be5..400ab90f 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -5,6 +5,7 @@ import WebKit import SwiftUI import ObjectiveC.runtime import Bonsplit +import UserNotifications #if canImport(cmux_DEV) @testable import cmux_DEV @@ -6587,6 +6588,188 @@ final class NotificationDockBadgeTests: XCTestCase { XCTAssertTrue(NotificationBadgeSettings.isDockBadgeEnabled(defaults: defaults)) } + func testNotificationSoundUsesSystemSoundForDefaultAndNamedSounds() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + XCTAssertTrue(NotificationSoundSettings.usesSystemSound(defaults: defaults)) + + defaults.set("Ping", forKey: NotificationSoundSettings.key) + XCTAssertTrue(NotificationSoundSettings.usesSystemSound(defaults: defaults)) + XCTAssertNotNil(NotificationSoundSettings.sound(defaults: defaults)) + } + + func testNotificationSoundDisablesSystemSoundForNoneAndCustomFile() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + defaults.set("none", forKey: NotificationSoundSettings.key) + XCTAssertFalse(NotificationSoundSettings.usesSystemSound(defaults: defaults)) + XCTAssertNil(NotificationSoundSettings.sound(defaults: defaults)) + + defaults.set(NotificationSoundSettings.customFileValue, forKey: NotificationSoundSettings.key) + XCTAssertFalse(NotificationSoundSettings.usesSystemSound(defaults: defaults)) + XCTAssertNil(NotificationSoundSettings.sound(defaults: defaults)) + } + + func testNotificationCustomFileURLExpandsTildePath() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + let rawPath = "~/Library/Sounds/my-custom.wav" + defaults.set(rawPath, forKey: NotificationSoundSettings.customFilePathKey) + let expectedPath = (rawPath as NSString).expandingTildeInPath + XCTAssertEqual(NotificationSoundSettings.customFileURL(defaults: defaults)?.path, expectedPath) + } + + func testNotificationCustomFileSelectionMustBeExplicit() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + defaults.set("~/Library/Sounds/my-custom.wav", forKey: NotificationSoundSettings.customFilePathKey) + + defaults.set("none", forKey: NotificationSoundSettings.key) + XCTAssertFalse(NotificationSoundSettings.isCustomFileSelected(defaults: defaults)) + + defaults.set("Ping", forKey: NotificationSoundSettings.key) + XCTAssertFalse(NotificationSoundSettings.isCustomFileSelected(defaults: defaults)) + + defaults.set(NotificationSoundSettings.customFileValue, forKey: NotificationSoundSettings.key) + XCTAssertTrue(NotificationSoundSettings.isCustomFileSelected(defaults: defaults)) + } + + func testNotificationCustomStagingPreservesSourceFileWithCmuxPrefix() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + let fileManager = FileManager.default + let soundsDirectory = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Sounds", isDirectory: true) + do { + try fileManager.createDirectory(at: soundsDirectory, withIntermediateDirectories: true) + } catch { + XCTFail("Failed to create sounds directory: \(error)") + return + } + + let sourceURL = soundsDirectory.appendingPathComponent( + "cmux-custom-notification-sound.source-\(UUID().uuidString).custtest", + isDirectory: false + ) + let stagedURL = soundsDirectory.appendingPathComponent( + "cmux-custom-notification-sound.custtest", + isDirectory: false + ) + defer { + try? fileManager.removeItem(at: sourceURL) + try? fileManager.removeItem(at: stagedURL) + } + + do { + try Data("test".utf8).write(to: sourceURL, options: .atomic) + } catch { + XCTFail("Failed to write source custom sound file: \(error)") + return + } + + defaults.set(NotificationSoundSettings.customFileValue, forKey: NotificationSoundSettings.key) + defaults.set(sourceURL.path, forKey: NotificationSoundSettings.customFilePathKey) + + _ = NotificationSoundSettings.sound(defaults: defaults) + + XCTAssertTrue(fileManager.fileExists(atPath: sourceURL.path)) + XCTAssertTrue(fileManager.fileExists(atPath: stagedURL.path)) + } + + func testNotificationAuthorizationStateMappingCoversKnownUNAuthorizationStatuses() { + XCTAssertEqual(TerminalNotificationStore.authorizationState(from: .notDetermined), .notDetermined) + XCTAssertEqual(TerminalNotificationStore.authorizationState(from: .denied), .denied) + XCTAssertEqual(TerminalNotificationStore.authorizationState(from: .authorized), .authorized) + XCTAssertEqual(TerminalNotificationStore.authorizationState(from: .provisional), .provisional) + } + + func testNotificationAuthorizationStateDeliveryCapability() { + XCTAssertFalse(NotificationAuthorizationState.unknown.allowsDelivery) + XCTAssertFalse(NotificationAuthorizationState.notDetermined.allowsDelivery) + XCTAssertFalse(NotificationAuthorizationState.denied.allowsDelivery) + XCTAssertTrue(NotificationAuthorizationState.authorized.allowsDelivery) + XCTAssertTrue(NotificationAuthorizationState.provisional.allowsDelivery) + XCTAssertTrue(NotificationAuthorizationState.ephemeral.allowsDelivery) + } + + func testNotificationAuthorizationDefersFirstPromptWhileAppIsInactive() { + XCTAssertTrue( + TerminalNotificationStore.shouldDeferAutomaticAuthorizationRequest( + status: .notDetermined, + isAppActive: false + ) + ) + XCTAssertFalse( + TerminalNotificationStore.shouldDeferAutomaticAuthorizationRequest( + status: .notDetermined, + isAppActive: true + ) + ) + XCTAssertFalse( + TerminalNotificationStore.shouldDeferAutomaticAuthorizationRequest( + status: .authorized, + isAppActive: false + ) + ) + } + + func testNotificationAuthorizationRequestGatingAllowsSettingsRetry() { + XCTAssertTrue( + TerminalNotificationStore.shouldRequestAuthorization( + isAutomaticRequest: false, + hasRequestedAutomaticAuthorization: true + ) + ) + XCTAssertTrue( + TerminalNotificationStore.shouldRequestAuthorization( + isAutomaticRequest: true, + hasRequestedAutomaticAuthorization: false + ) + ) + XCTAssertFalse( + TerminalNotificationStore.shouldRequestAuthorization( + isAutomaticRequest: true, + hasRequestedAutomaticAuthorization: true + ) + ) + } + func testNotificationSettingsPromptUsesSheetAndNeverRunsModal() { let store = TerminalNotificationStore.shared let alertSpy = NotificationSettingsAlertSpy() From 6f210dd2c7bcb092a4485c82d9b149f1046f6482 Mon Sep 17 00:00:00 2001 From: Qian Wan Date: Thu, 5 Mar 2026 09:25:39 +0800 Subject: [PATCH 075/232] Fix voice dictation text insertion path in GhosttyNSView. (#857) * Fix voice dictation text insertion path in GhosttyNSView. * Fix AX selected text decoding to use explicit length --- Sources/GhosttyTerminalView.swift | 86 ++++++++++++++++++++++++++++++- cmuxTests/CJKIMEInputTests.swift | 61 +++++++++------------- 2 files changed, 108 insertions(+), 39 deletions(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 2d5c5ecb..1b65a5f6 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -3581,6 +3581,73 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } } + // MARK: - Accessibility + + /// Expose the terminal surface as an editable accessibility element. + /// Voice input tools frequently target AX text areas for text insertion. + override func isAccessibilityElement() -> Bool { + true + } + + override func accessibilityRole() -> NSAccessibility.Role? { + .textArea + } + + override func accessibilityHelp() -> String? { + "Terminal content area" + } + + override func accessibilityValue() -> Any? { + // We don't keep a full terminal text snapshot in this layer. + // Expose selected text when available; otherwise provide an empty value + // so AX clients still treat this as an editable text area. + accessibilitySelectedText() ?? "" + } + + override func setAccessibilityValue(_ value: Any?) { + let content: String + switch value { + case let v as NSAttributedString: + content = v.string + case let v as String: + content = v + default: + return + } + + guard !content.isEmpty else { return } + +#if DEBUG + dlog("ime.ax.setValue len=\(content.count)") +#endif + + let inject = { + self.insertText(content, replacementRange: NSRange(location: NSNotFound, length: 0)) + } + if Thread.isMainThread { + inject() + } else { + DispatchQueue.main.async(execute: inject) + } + } + + override func accessibilitySelectedTextRange() -> NSRange { + selectedRange() + } + + override func accessibilitySelectedText() -> String? { + guard let surface = surface else { return nil } + + var text = ghostty_text_s() + guard ghostty_surface_read_selection(surface, &text) else { return nil } + defer { ghostty_surface_free_text(surface, &text) } + + guard let ptr = text.text, text.text_len > 0 else { return nil } + let selectedData = Data(bytes: ptr, count: Int(text.text_len)) + let selected = String(decoding: selectedData, as: UTF8.self) + return selected.isEmpty ? nil : selected + } + override var acceptsFirstResponder: Bool { true } override func becomeFirstResponder() -> Bool { @@ -3713,6 +3780,13 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { // Intentionally empty - prevents system beep on unhandled key commands } + /// Some third-party voice input apps inject committed text by sending the + /// responder-chain `insertText:` action (single-argument form). + /// Route that into our NSTextInputClient path so text lands in the terminal. + override func insertText(_ insertString: Any) { + insertText(insertString, replacementRange: NSRange(location: NSNotFound, length: 0)) + } + override func performKeyEquivalent(with event: NSEvent) -> Bool { guard event.type == .keyDown else { return false } guard let fr = window?.firstResponder as? NSView, @@ -6458,8 +6532,6 @@ extension GhosttyNSView: NSTextInputClient { } func insertText(_ string: Any, replacementRange: NSRange) { - guard NSApp.currentEvent != nil else { return } - // Get the string value var chars = "" switch string { @@ -6474,6 +6546,16 @@ extension GhosttyNSView: NSTextInputClient { // Clear marked text since we're inserting unmarkText() + // Some IME/input-method paths call insertText with an empty payload to + // flush state. There is no terminal text to send in that case. + guard !chars.isEmpty else { return } + +#if DEBUG + if NSApp.currentEvent == nil { + dlog("ime.insertText.noEvent len=\(chars.count)") + } +#endif + // If we have an accumulator, we're in a keyDown event - accumulate the text if keyTextAccumulator != nil { keyTextAccumulator?.append(chars) diff --git a/cmuxTests/CJKIMEInputTests.swift b/cmuxTests/CJKIMEInputTests.swift index 86ed191e..473b11e7 100644 --- a/cmuxTests/CJKIMEInputTests.swift +++ b/cmuxTests/CJKIMEInputTests.swift @@ -7,43 +7,6 @@ import AppKit @testable import cmux #endif -// MARK: - Test helpers - -/// Helper to make `NSApp.currentEvent` non-nil for insertText calls. -/// NSTextInputClient.insertText guards on currentEvent because it should -/// only fire during actual key event processing. In tests we simulate this -/// by posting and immediately processing a synthetic key event. -private func withSyntheticCurrentEvent(_ body: () -> Void) { - _ = NSApplication.shared // ensure NSApp exists - guard let event = NSEvent.keyEvent( - with: .keyDown, - location: .zero, - modifierFlags: [], - timestamp: ProcessInfo.processInfo.systemUptime, - windowNumber: 0, - context: nil, - characters: "", - charactersIgnoringModifiers: "", - isARepeat: false, - keyCode: 0 - ) else { - body() - return - } - NSApp.postEvent(event, atStart: true) - // Process the event so that currentEvent becomes non-nil. - // Use a short timeout since we just posted the event. - if let posted = NSApp.nextEvent(matching: .keyDown, until: Date(timeIntervalSinceNow: 0.05), inMode: .default, dequeue: true) { - // We're now inside event processing; currentEvent should be set. - // However, currentEvent is only set during sendEvent. We need to - // actually invoke sendEvent. Since we can't do that cleanly in a - // unit test, we use a different approach: call insertText indirectly - // via a direct test of the accumulator + unmarkText path. - _ = posted - } - body() -} - // MARK: - NSTextInputClient protocol: marked text (preedit) lifecycle /// Tests that the GhosttyNSView NSTextInputClient implementation correctly @@ -106,6 +69,30 @@ final class CJKIMEMarkedTextTests: XCTestCase { view.setKeyTextAccumulatorForTesting(nil) } + /// Third-party voice input apps often commit text outside an active keyDown + /// event. `insertText` should still clear marked text in that path. + func testInsertTextWithoutCurrentEventClearsMarkedText() { + let view = GhosttyNSView(frame: .zero) + + view.setMarkedText("한", selectedRange: NSRange(location: 0, length: 1), replacementRange: NSRange(location: NSNotFound, length: 0)) + XCTAssertTrue(view.hasMarkedText()) + + view.insertText("한", replacementRange: NSRange(location: NSNotFound, length: 0)) + XCTAssertFalse(view.hasMarkedText(), "insertText should clear marked text even without an active currentEvent") + } + + /// The responder-chain `insertText:` action (single argument) should route + /// to NSTextInputClient insertion so external text-injection tools work. + func testResponderChainInsertTextSelectorClearsMarkedText() { + let view = GhosttyNSView(frame: .zero) + + view.setMarkedText("ni", selectedRange: NSRange(location: 2, length: 0), replacementRange: NSRange(location: NSNotFound, length: 0)) + XCTAssertTrue(view.hasMarkedText()) + + view.insertText("你") + XCTAssertFalse(view.hasMarkedText(), "single-argument insertText should follow the same commit path") + } + // MARK: - Chinese (中文) pinyin candidate selection /// Chinese pinyin IME types Roman letters as marked text, then the user From d72b014d6de73abcb8a48f2d4ad0e708d33281c9 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Thu, 5 Mar 2026 02:48:28 +0100 Subject: [PATCH 076/232] feat: add markdown viewer panel with live file watching (#883) * Add markdown viewer panel with live file watching Introduce a new PanelType.markdown that renders .md files in a dedicated panel using MarkdownUI (SwiftUI), with live file watching via DispatchSource so content auto-updates when the file changes on disk. - New MarkdownPanel class with file system watcher (write/delete/rename/extend) - New MarkdownPanelView with custom cmux theme (headings, code blocks, tables, blockquotes, inline code, lists, horizontal rules, light/dark mode) - Full workspace integration: SurfaceKind, creation methods, tab subscription - Session persistence: snapshot/restore across app restarts - V2 socket command: markdown.open (validates path, resolves workspace, splits) - CLI command: cmux markdown open with routing flags and help text - Agent skill: skills/cmux-markdown/ with SKILL.md, openai.yaml, and references - Cross-link from skills/cmux/SKILL.md to the new markdown skill - SPM dependency: gonzalezreal/swift-markdown-ui 2.4.1 * Fix unreachable guard in markdown subcommand dispatch Use looksLikePath() to distinguish subcommands from path arguments so the guard can catch unknown subcommands and future subcommands are parsed correctly. * Use .isoLatin1 fallback instead of .ascii for encoding recovery ASCII is a strict subset of UTF-8, so falling back to .ascii after UTF-8 fails is dead code. Use .isoLatin1 which accepts all 256 byte values and covers legacy encodings like Windows-1252. * Mark fileWatchSource as nonisolated(unsafe) for deinit safety deinit is not guaranteed to run on the main actor, so accessing @MainActor-isolated storage is a data race under strict concurrency. DispatchSource.cancel() is thread-safe, so nonisolated(unsafe) is sufficient with a documented invariant that writes only occur on main. * Fix file watcher reattach: retry loop with cancellation guard - Replace one-shot 500ms retry with up to 6 attempts (3s total window) so files that reappear after a slow atomic replace are picked up - Add isClosed flag checked before each retry to prevent restarting the watcher after close()/deinit * Harden path validation in markdown.open command Reject directories and non-absolute paths before panel creation to prevent ambiguous behavior and generic downstream failures. * Always reattach file watcher on delete/rename events After an atomic save (delete old + create new), the DispatchSource still points to the old inode. Previously we only reattached when the file was unreadable, so successful atomic saves left the watcher on a stale inode and live updates silently stopped. Now we always stop and reattach: immediately if the new file is readable, via retry loop if not. * Restore markdown panels even when file is missing at launch MarkdownPanel already handles unavailable files gracefully (shows 'file unavailable' UI and retries via the reattach loop). Dropping the panel on restore lost the user's layout for files that may reappear shortly after (network drives, build artifacts, etc.). * Harden markdown CLI parsing and startup reconnect behavior --------- Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> --- CLI/cmux.swift | 115 +++++++ GhosttyTabs.xcodeproj/project.pbxproj | 25 ++ .../xcshareddata/swiftpm/Package.resolved | 29 +- Sources/Panels/MarkdownPanel.swift | 182 +++++++++++ Sources/Panels/MarkdownPanelView.swift | 285 ++++++++++++++++++ Sources/Panels/Panel.swift | 1 + Sources/Panels/PanelContentView.swift | 10 + Sources/SessionPersistence.swift | 5 + Sources/TerminalController.swift | 95 ++++++ Sources/Workspace.swift | 169 ++++++++++- skills/cmux-markdown/SKILL.md | 125 ++++++++ skills/cmux-markdown/agents/openai.yaml | 4 + skills/cmux-markdown/references/commands.md | 69 +++++ .../cmux-markdown/references/live-reload.md | 53 ++++ skills/cmux/SKILL.md | 1 + tests/test_markdown_open_regressions.py | 126 ++++++++ 16 files changed, 1292 insertions(+), 2 deletions(-) create mode 100644 Sources/Panels/MarkdownPanel.swift create mode 100644 Sources/Panels/MarkdownPanelView.swift create mode 100644 skills/cmux-markdown/SKILL.md create mode 100644 skills/cmux-markdown/agents/openai.yaml create mode 100644 skills/cmux-markdown/references/commands.md create mode 100644 skills/cmux-markdown/references/live-reload.md create mode 100644 tests/test_markdown_open_regressions.py diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 3108185a..17a13f91 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -1511,6 +1511,10 @@ struct CMUXCLI { let bridged = replaceToken(commandArgs, from: "--panel", to: "--surface") try runBrowserCommand(commandArgs: ["is-webview-focused"] + bridged, client: client, jsonOutput: jsonOutput, idFormat: idFormat) + // Markdown commands + case "markdown": + try runMarkdownCommand(commandArgs: commandArgs, client: client, jsonOutput: jsonOutput, idFormat: idFormat) + default: print(usage()) throw CLIError(message: "Unknown command: \(command)") @@ -1524,6 +1528,96 @@ struct CMUXCLI { return (cwd as NSString).appendingPathComponent(expanded) } + // MARK: - Markdown Commands + + private func runMarkdownCommand( + commandArgs: [String], + client: SocketClient, + jsonOutput: Bool, + idFormat: CLIIDFormat + ) throws { + var args = commandArgs + + // Parse routing flags + let (workspaceOpt, argsAfterWorkspace) = parseOption(args, name: "--workspace") + let (windowOpt, argsAfterWindow) = parseOption(argsAfterWorkspace, name: "--window") + let (surfaceOpt, argsAfterSurface) = parseOption(argsAfterWindow, name: "--surface") + args = argsAfterSurface + + // Determine subcommand. Explicit "open" is supported, otherwise treat + // a single positional argument as shorthand path. + let subArgs: [String] + if let first = args.first, first.lowercased() == "open" { + subArgs = Array(args.dropFirst()) + } else if args.count == 1, let first = args.first, !first.hasPrefix("-") { + subArgs = [first] + } else { + // Allow path-like first tokens (e.g. plan.md) with trailing args + // so we can surface specific trailing-arg/flag errors below. + if let first = args.first, first.hasPrefix("-") { + throw CLIError( + message: + "markdown open: unknown flag '\(first)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ]" + ) + } else if let first = args.first, looksLikePath(first) || first.contains(".") { + subArgs = args + } else if let first = args.first { + throw CLIError(message: "Unknown markdown subcommand: \(first). Usage: cmux markdown open ") + } else { + subArgs = [] + } + } + + guard let rawPath = subArgs.first, !rawPath.isEmpty else { + throw CLIError(message: "markdown open requires a file path. Usage: cmux markdown open ") + } + let trailingArgs = Array(subArgs.dropFirst()) + if let unknownFlag = trailingArgs.first(where: { $0.hasPrefix("-") }) { + throw CLIError( + message: + "markdown open: unknown flag '\(unknownFlag)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ]" + ) + } + if let extraArg = trailingArgs.first { + throw CLIError( + message: + "markdown open: unexpected argument '\(extraArg)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ]" + ) + } + + let absolutePath = resolvePath(rawPath) + + // Build params + var params: [String: Any] = ["path": absolutePath] + if let surfaceRaw = surfaceOpt { + if let surface = try normalizeSurfaceHandle(surfaceRaw, client: client) { + params["surface_id"] = surface + } + } + let workspaceRaw = workspaceOpt ?? (windowOpt == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil) + if let workspaceRaw { + if let workspace = try normalizeWorkspaceHandle(workspaceRaw, client: client) { + params["workspace_id"] = workspace + } + } + if let windowRaw = windowOpt { + if let window = try normalizeWindowHandle(windowRaw, client: client) { + params["window_id"] = window + } + } + + let payload = try client.sendV2(method: "markdown.open", params: params) + + if jsonOutput { + print(jsonString(formatIDs(payload, mode: idFormat))) + } else { + let surfaceText = formatHandle(payload, kind: "surface", idFormat: idFormat) ?? "unknown" + let paneText = formatHandle(payload, kind: "pane", idFormat: idFormat) ?? "unknown" + let filePath = (payload["path"] as? String) ?? absolutePath + print("OK surface=\(surfaceText) pane=\(paneText) path=\(filePath)") + } + } + /// Returns true if the argument looks like a filesystem path rather than a CLI command. private func looksLikePath(_ arg: String) -> Bool { if arg == "." || arg == ".." { return true } @@ -4551,6 +4645,25 @@ struct CMUXCLI { return "Legacy alias for 'cmux browser focus-webview'. Run 'cmux browser --help' for details." case "is-webview-focused": return "Legacy alias for 'cmux browser is-webview-focused'. Run 'cmux browser --help' for details." + case "markdown": + return """ + Usage: cmux markdown open [options] + cmux markdown (shorthand for 'open') + + Open a markdown file in a formatted viewer panel with live file watching. + The file is rendered with rich formatting (headings, code blocks, tables, + lists, blockquotes) and automatically updates when the file changes on disk. + + Options: + --workspace Target workspace (default: $CMUX_WORKSPACE_ID) + --surface Source surface to split from (default: focused surface) + --window Target window + + Examples: + cmux markdown open plan.md + cmux markdown ~/project/CHANGELOG.md + cmux markdown open ./docs/design.md --workspace 0 + """ default: return nil } @@ -6459,6 +6572,8 @@ struct CMUXCLI { respawn-pane [--workspace ] [--surface ] [--command ] display-message [-p|--print] + markdown [open] (open markdown file in formatted viewer panel with live reload) + browser [--surface | ] ... browser open [url] (create browser split in caller's workspace; if surface supplied, behaves like navigate) browser open-split [url] diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 56f76d80..0b4326bd 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -28,6 +28,9 @@ A5001402 /* BrowserPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001412 /* BrowserPanel.swift */; }; A5001403 /* TerminalPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001413 /* TerminalPanelView.swift */; }; A5001404 /* BrowserPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001414 /* BrowserPanelView.swift */; }; + A5001420 /* MarkdownPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001418 /* MarkdownPanel.swift */; }; + A5001421 /* MarkdownPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001419 /* MarkdownPanelView.swift */; }; + A5001290 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = A5001291 /* MarkdownUI */; }; A5001405 /* PanelContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001415 /* PanelContentView.swift */; }; A5001406 /* Workspace.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001416 /* Workspace.swift */; }; A5001407 /* WorkspaceContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001417 /* WorkspaceContentView.swift */; }; @@ -170,6 +173,8 @@ A5001413 /* TerminalPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/TerminalPanelView.swift; sourceTree = ""; }; A5001414 /* BrowserPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPanelView.swift; sourceTree = ""; }; A5001415 /* PanelContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/PanelContentView.swift; sourceTree = ""; }; + A5001418 /* MarkdownPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanel.swift; sourceTree = ""; }; + A5001419 /* MarkdownPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanelView.swift; sourceTree = ""; }; A5001416 /* Workspace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workspace.swift; sourceTree = ""; }; A5001417 /* WorkspaceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentView.swift; sourceTree = ""; }; A5001090 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -236,6 +241,7 @@ A5001230 /* Sparkle in Frameworks */, A5001250 /* Sentry in Frameworks */, A5001270 /* PostHog in Frameworks */, + A5001290 /* MarkdownUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -363,6 +369,8 @@ A5001412 /* BrowserPanel.swift */, A5001413 /* TerminalPanelView.swift */, A5001414 /* BrowserPanelView.swift */, + A5001418 /* MarkdownPanel.swift */, + A5001419 /* MarkdownPanelView.swift */, A5001510 /* CmuxWebView.swift */, A5001415 /* PanelContentView.swift */, A5001211 /* UpdateController.swift */, @@ -473,6 +481,7 @@ A5001251 /* Sentry */, A5001271 /* PostHog */, A5001261 /* Bonsplit */, + A5001291 /* MarkdownUI */, ); name = GhosttyTabs; productName = GhosttyTabs; @@ -558,6 +567,7 @@ A5001232 /* XCRemoteSwiftPackageReference "Sparkle" */, A5001252 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, A5001272 /* XCRemoteSwiftPackageReference "posthog-ios" */, + A5001292 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, A5001260 /* XCLocalSwiftPackageReference "bonsplit" */, ); productRefGroup = A5001042 /* Products */; @@ -608,6 +618,8 @@ A5001402 /* BrowserPanel.swift in Sources */, A5001403 /* TerminalPanelView.swift in Sources */, A5001404 /* BrowserPanelView.swift in Sources */, + A5001420 /* MarkdownPanel.swift in Sources */, + A5001421 /* MarkdownPanelView.swift in Sources */, A5001500 /* CmuxWebView.swift in Sources */, A5001405 /* PanelContentView.swift in Sources */, A5001201 /* UpdateController.swift in Sources */, @@ -966,6 +978,14 @@ minimumVersion = 3.41.0; }; }; + A5001292 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.4.1; + }; + }; A5001260 /* XCLocalSwiftPackageReference "bonsplit" */ = { isa = XCLocalSwiftPackageReference; relativePath = vendor/bonsplit; @@ -993,6 +1013,11 @@ package = A5001260 /* XCLocalSwiftPackageReference "bonsplit" */; productName = Bonsplit; }; + A5001291 /* MarkdownUI */ = { + isa = XCSwiftPackageProductDependency; + package = A5001292 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; + productName = MarkdownUI; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCConfigurationList section */ diff --git a/GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 492ab4e9..3bf056ae 100644 --- a/GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "a1df212ee81645b29368e6cc39c83aebbbafb5c592f726afc990bab228304987", + "originHash" : "b66d812c506be67c70b46c63421ab2eb2db013613c74252ad1205f662ada079b", "pins" : [ + { + "identity" : "networkimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/NetworkImage", + "state" : { + "revision" : "2849f5323265386e200484b0d0f896e73c3411b9", + "version" : "6.0.1" + } + }, { "identity" : "posthog-ios", "kind" : "remoteSourceControl", @@ -27,6 +36,24 @@ "revision" : "5581748cef2bae787496fe6d61139aebe0a451f6", "version" : "2.8.1" } + }, + { + "identity" : "swift-cmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-cmark", + "state" : { + "revision" : "5d9bdaa4228b381639fff09403e39a04926e2dbe", + "version" : "0.7.1" + } + }, + { + "identity" : "swift-markdown-ui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/swift-markdown-ui", + "state" : { + "revision" : "5f613358148239d0292c0cef674a3c2314737f9e", + "version" : "2.4.1" + } } ], "version" : 3 diff --git a/Sources/Panels/MarkdownPanel.swift b/Sources/Panels/MarkdownPanel.swift new file mode 100644 index 00000000..74e48b89 --- /dev/null +++ b/Sources/Panels/MarkdownPanel.swift @@ -0,0 +1,182 @@ +import Foundation +import Combine + +/// A panel that renders a markdown file with live file-watching. +/// When the file changes on disk, the content is automatically reloaded. +@MainActor +final class MarkdownPanel: Panel, ObservableObject { + let id: UUID + let panelType: PanelType = .markdown + + /// Absolute path to the markdown file being displayed. + let filePath: String + + /// The workspace this panel belongs to. + private(set) var workspaceId: UUID + + /// Current markdown content read from the file. + @Published private(set) var content: String = "" + + /// Title shown in the tab bar (filename). + @Published private(set) var displayTitle: String = "" + + /// SF Symbol icon for the tab bar. + var displayIcon: String? { "doc.richtext" } + + /// Whether the file has been deleted or is unreadable. + @Published private(set) var isFileUnavailable: Bool = false + + /// Token incremented to trigger focus flash animation. + @Published private(set) var focusFlashToken: Int = 0 + + // MARK: - File watching + + // nonisolated(unsafe) because deinit is not guaranteed to run on the + // main actor, but DispatchSource.cancel() is thread-safe. + private nonisolated(unsafe) var fileWatchSource: DispatchSourceFileSystemObject? + private var fileDescriptor: Int32 = -1 + private var isClosed: Bool = false + private let watchQueue = DispatchQueue(label: "com.cmux.markdown-file-watch", qos: .utility) + + /// Maximum number of reattach attempts after a file delete/rename event. + private static let maxReattachAttempts = 6 + /// Delay between reattach attempts (total window: attempts * delay = 3s). + private static let reattachDelay: TimeInterval = 0.5 + + // MARK: - Init + + init(workspaceId: UUID, filePath: String) { + self.id = UUID() + self.workspaceId = workspaceId + self.filePath = filePath + self.displayTitle = (filePath as NSString).lastPathComponent + + loadFileContent() + startFileWatcher() + if isFileUnavailable && fileWatchSource == nil { + // Session restore can create a panel before the file is recreated. + // Retry briefly so atomic-rename recreations can reconnect. + scheduleReattach(attempt: 1) + } + } + + // MARK: - Panel protocol + + func focus() { + // Markdown panel is read-only; no first responder to manage. + } + + func unfocus() { + // No-op for read-only panel. + } + + func close() { + isClosed = true + stopFileWatcher() + } + + func triggerFlash() { + focusFlashToken += 1 + } + + // MARK: - File I/O + + private func loadFileContent() { + do { + let newContent = try String(contentsOfFile: filePath, encoding: .utf8) + content = newContent + isFileUnavailable = false + } catch { + // Fallback: try ISO Latin-1, which accepts all 256 byte values, + // covering legacy encodings like Windows-1252. + if let data = FileManager.default.contents(atPath: filePath), + let decoded = String(data: data, encoding: .isoLatin1) { + content = decoded + isFileUnavailable = false + } else { + isFileUnavailable = true + } + } + } + + // MARK: - File watcher via DispatchSource + + private func startFileWatcher() { + let fd = open(filePath, O_EVTONLY) + guard fd >= 0 else { return } + fileDescriptor = fd + + let source = DispatchSource.makeFileSystemObjectSource( + fileDescriptor: fd, + eventMask: [.write, .delete, .rename, .extend], + queue: watchQueue + ) + + source.setEventHandler { [weak self] in + guard let self else { return } + let flags = source.data + if flags.contains(.delete) || flags.contains(.rename) { + // File was deleted or renamed. The old file descriptor points to + // a stale inode, so we must always stop and reattach the watcher + // even if the new file is already readable (atomic save case). + DispatchQueue.main.async { + self.stopFileWatcher() + self.loadFileContent() + if self.isFileUnavailable { + // File not yet replaced — retry until it reappears. + self.scheduleReattach(attempt: 1) + } else { + // File already replaced — reattach to the new inode immediately. + self.startFileWatcher() + } + } + } else { + // Content changed — reload. + DispatchQueue.main.async { + self.loadFileContent() + } + } + } + + source.setCancelHandler { + Darwin.close(fd) + } + + source.resume() + fileWatchSource = source + } + + /// Retry reattaching the file watcher up to `maxReattachAttempts` times. + /// Each attempt checks if the file has reappeared. Bails out early if + /// the panel has been closed. + private func scheduleReattach(attempt: Int) { + guard attempt <= Self.maxReattachAttempts else { return } + watchQueue.asyncAfter(deadline: .now() + Self.reattachDelay) { [weak self] in + guard let self else { return } + DispatchQueue.main.async { + guard !self.isClosed else { return } + if FileManager.default.fileExists(atPath: self.filePath) { + self.isFileUnavailable = false + self.loadFileContent() + self.startFileWatcher() + } else { + self.scheduleReattach(attempt: attempt + 1) + } + } + } + } + + private func stopFileWatcher() { + if let source = fileWatchSource { + source.cancel() + fileWatchSource = nil + } + // File descriptor is closed by the cancel handler. + fileDescriptor = -1 + } + + deinit { + // DispatchSource cancel is safe from any thread. + fileWatchSource?.cancel() + } +} diff --git a/Sources/Panels/MarkdownPanelView.swift b/Sources/Panels/MarkdownPanelView.swift new file mode 100644 index 00000000..b3b7a971 --- /dev/null +++ b/Sources/Panels/MarkdownPanelView.swift @@ -0,0 +1,285 @@ +import SwiftUI +import MarkdownUI + +/// SwiftUI view that renders a MarkdownPanel's content using MarkdownUI. +struct MarkdownPanelView: View { + @ObservedObject var panel: MarkdownPanel + let isFocused: Bool + let isVisibleInUI: Bool + let portalPriority: Int + let onRequestPanelFocus: () -> Void + + @State private var focusFlashOpacity: Double = 0.0 + @State private var focusFlashAnimationGeneration: Int = 0 + @Environment(\.colorScheme) private var colorScheme + + var body: some View { + Group { + if panel.isFileUnavailable { + fileUnavailableView + } else { + markdownContentView + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(backgroundColor) + .overlay { + RoundedRectangle(cornerRadius: FocusFlashPattern.ringCornerRadius) + .stroke(cmuxAccentColor().opacity(focusFlashOpacity), lineWidth: 3) + .shadow(color: cmuxAccentColor().opacity(focusFlashOpacity * 0.35), radius: 10) + .padding(FocusFlashPattern.ringInset) + .allowsHitTesting(false) + } + .contentShape(Rectangle()) + .onTapGesture { + onRequestPanelFocus() + } + .onChange(of: panel.focusFlashToken) { _ in + triggerFocusFlashAnimation() + } + } + + // MARK: - Content + + private var markdownContentView: some View { + ScrollView { + VStack(alignment: .leading, spacing: 0) { + // File path breadcrumb + filePathHeader + .padding(.horizontal, 24) + .padding(.top, 16) + .padding(.bottom, 8) + + Divider() + .padding(.horizontal, 16) + + // Rendered markdown + Markdown(panel.content) + .markdownTheme(cmuxMarkdownTheme) + .textSelection(.enabled) + .padding(.horizontal, 24) + .padding(.vertical, 16) + } + } + } + + private var filePathHeader: some View { + HStack(spacing: 6) { + Image(systemName: "doc.richtext") + .foregroundColor(.secondary) + .font(.system(size: 12)) + Text(panel.filePath) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(.secondary) + .lineLimit(1) + .truncationMode(.middle) + Spacer() + } + } + + private var fileUnavailableView: some View { + VStack(spacing: 12) { + Image(systemName: "doc.questionmark") + .font(.system(size: 40)) + .foregroundColor(.secondary) + Text("File unavailable") + .font(.headline) + .foregroundColor(.primary) + Text(panel.filePath) + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(.secondary) + .lineLimit(2) + .multilineTextAlignment(.center) + Text("The file may have been moved or deleted.") + .font(.caption) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + // MARK: - Theme + + private var backgroundColor: Color { + colorScheme == .dark + ? Color(nsColor: NSColor(white: 0.12, alpha: 1.0)) + : Color(nsColor: NSColor(white: 0.98, alpha: 1.0)) + } + + private var cmuxMarkdownTheme: Theme { + let isDark = colorScheme == .dark + + return Theme() + // Text + .text { + ForegroundColor(isDark ? .white.opacity(0.9) : .primary) + FontSize(14) + } + // Headings + .heading1 { configuration in + VStack(alignment: .leading, spacing: 8) { + configuration.label + .markdownTextStyle { + FontWeight(.bold) + FontSize(28) + ForegroundColor(isDark ? .white : .primary) + } + Divider() + } + .markdownMargin(top: 24, bottom: 16) + } + .heading2 { configuration in + VStack(alignment: .leading, spacing: 6) { + configuration.label + .markdownTextStyle { + FontWeight(.bold) + FontSize(22) + ForegroundColor(isDark ? .white : .primary) + } + Divider() + } + .markdownMargin(top: 20, bottom: 12) + } + .heading3 { configuration in + configuration.label + .markdownTextStyle { + FontWeight(.semibold) + FontSize(18) + ForegroundColor(isDark ? .white : .primary) + } + .markdownMargin(top: 16, bottom: 8) + } + .heading4 { configuration in + configuration.label + .markdownTextStyle { + FontWeight(.semibold) + FontSize(16) + ForegroundColor(isDark ? .white : .primary) + } + .markdownMargin(top: 12, bottom: 6) + } + .heading5 { configuration in + configuration.label + .markdownTextStyle { + FontWeight(.medium) + FontSize(14) + ForegroundColor(isDark ? .white : .primary) + } + .markdownMargin(top: 10, bottom: 4) + } + .heading6 { configuration in + configuration.label + .markdownTextStyle { + FontWeight(.medium) + FontSize(13) + ForegroundColor(isDark ? .white.opacity(0.7) : .secondary) + } + .markdownMargin(top: 8, bottom: 4) + } + // Code blocks + .codeBlock { configuration in + ScrollView(.horizontal, showsIndicators: true) { + configuration.label + .markdownTextStyle { + FontFamilyVariant(.monospaced) + FontSize(13) + ForegroundColor(isDark ? Color(red: 0.9, green: 0.9, blue: 0.9) : Color(red: 0.2, green: 0.2, blue: 0.2)) + } + .padding(12) + } + .background(isDark + ? Color(nsColor: NSColor(white: 0.08, alpha: 1.0)) + : Color(nsColor: NSColor(white: 0.93, alpha: 1.0))) + .clipShape(RoundedRectangle(cornerRadius: 6)) + .markdownMargin(top: 8, bottom: 8) + } + // Inline code + .code { + FontFamilyVariant(.monospaced) + FontSize(13) + ForegroundColor(isDark ? Color(red: 0.85, green: 0.6, blue: 0.95) : Color(red: 0.6, green: 0.2, blue: 0.7)) + BackgroundColor(isDark + ? Color(nsColor: NSColor(white: 0.18, alpha: 1.0)) + : Color(nsColor: NSColor(white: 0.92, alpha: 1.0))) + } + // Block quotes + .blockquote { configuration in + HStack(spacing: 0) { + RoundedRectangle(cornerRadius: 1.5) + .fill(isDark ? Color.white.opacity(0.2) : Color.gray.opacity(0.4)) + .frame(width: 3) + configuration.label + .markdownTextStyle { + ForegroundColor(isDark ? .white.opacity(0.6) : .secondary) + FontSize(14) + } + .padding(.leading, 12) + } + .markdownMargin(top: 8, bottom: 8) + } + // Links + .link { + ForegroundColor(Color.accentColor) + } + // Strong + .strong { + FontWeight(.semibold) + } + // Tables + .table { configuration in + configuration.label + .markdownTableBorderStyle(.init(color: isDark ? .white.opacity(0.15) : .gray.opacity(0.3))) + .markdownTableBackgroundStyle( + .alternatingRows( + isDark + ? Color(nsColor: NSColor(white: 0.14, alpha: 1.0)) + : Color(nsColor: NSColor(white: 0.96, alpha: 1.0)), + isDark + ? Color(nsColor: NSColor(white: 0.10, alpha: 1.0)) + : Color(nsColor: NSColor(white: 1.0, alpha: 1.0)) + ) + ) + .markdownMargin(top: 8, bottom: 8) + } + // Thematic break (horizontal rule) + .thematicBreak { + Divider() + .markdownMargin(top: 16, bottom: 16) + } + // List items + .listItem { configuration in + configuration.label + .markdownMargin(top: 4, bottom: 4) + } + // Paragraphs + .paragraph { configuration in + configuration.label + .markdownMargin(top: 4, bottom: 8) + } + } + + // MARK: - Focus Flash + + private func triggerFocusFlashAnimation() { + focusFlashAnimationGeneration &+= 1 + let generation = focusFlashAnimationGeneration + focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + + for segment in FocusFlashPattern.segments { + DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { + guard focusFlashAnimationGeneration == generation else { return } + withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { + focusFlashOpacity = segment.targetOpacity + } + } + } + } + + private func focusFlashAnimation(for curve: FocusFlashCurve, duration: TimeInterval) -> Animation { + switch curve { + case .easeIn: + return .easeIn(duration: duration) + case .easeOut: + return .easeOut(duration: duration) + } + } +} diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index a0a719c4..09ec66b6 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -5,6 +5,7 @@ import Combine public enum PanelType: String, Codable, Sendable { case terminal case browser + case markdown } enum FocusFlashCurve: Equatable { diff --git a/Sources/Panels/PanelContentView.swift b/Sources/Panels/PanelContentView.swift index 1374a5a7..adec500f 100644 --- a/Sources/Panels/PanelContentView.swift +++ b/Sources/Panels/PanelContentView.swift @@ -41,6 +41,16 @@ struct PanelContentView: View { onRequestPanelFocus: onRequestPanelFocus ) } + case .markdown: + if let markdownPanel = panel as? MarkdownPanel { + MarkdownPanelView( + panel: markdownPanel, + isFocused: isFocused, + isVisibleInUI: isVisibleInUI, + portalPriority: portalPriority, + onRequestPanelFocus: onRequestPanelFocus + ) + } } } } diff --git a/Sources/SessionPersistence.swift b/Sources/SessionPersistence.swift index 289909df..53eb995e 100644 --- a/Sources/SessionPersistence.swift +++ b/Sources/SessionPersistence.swift @@ -235,6 +235,10 @@ struct SessionBrowserPanelSnapshot: Codable, Sendable { var forwardHistoryURLStrings: [String]? } +struct SessionMarkdownPanelSnapshot: Codable, Sendable { + var filePath: String +} + struct SessionPanelSnapshot: Codable, Sendable { var id: UUID var type: PanelType @@ -248,6 +252,7 @@ struct SessionPanelSnapshot: Codable, Sendable { var ttyName: String? var terminal: SessionTerminalPanelSnapshot? var browser: SessionBrowserPanelSnapshot? + var markdown: SessionMarkdownPanelSnapshot? } enum SessionSplitOrientation: String, Codable, Sendable { diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 3052e9f9..43995ccb 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -1477,6 +1477,11 @@ class TerminalController { return v2Result(id: id, self.v2BrowserInputKeyboard(params: params)) case "browser.input_touch": return v2Result(id: id, self.v2BrowserInputTouch(params: params)) + + // Markdown + case "markdown.open": + return v2Result(id: id, self.v2MarkdownOpen(params: params)) + case "surface.read_text": return v2Result(id: id, self.v2SurfaceReadText(params: params)) @@ -1621,6 +1626,7 @@ class TerminalController { "notification.clear", "app.focus_override.set", "app.simulate_active", + "markdown.open", "browser.open_split", "browser.navigate", "browser.back", @@ -5249,6 +5255,95 @@ class TerminalController { return rep.representation(using: .png, properties: [:]) } + // MARK: - Markdown + + private func v2MarkdownOpen(params: [String: Any]) -> V2CallResult { + guard let tabManager = v2ResolveTabManager(params: params) else { + return .err(code: "unavailable", message: "TabManager not available", data: nil) + } + guard let rawPath = v2String(params, "path") else { + return .err(code: "invalid_params", message: "Missing 'path' parameter", data: nil) + } + + // Resolve the path (expand ~ and standardize) + let expandedPath = NSString(string: rawPath).expandingTildeInPath + let filePath = NSString(string: expandedPath).standardizingPath + + // Reject paths that aren't absolute after resolution + guard filePath.hasPrefix("/") else { + return .err(code: "invalid_params", message: "Path must be absolute: \(filePath)", data: ["path": filePath]) + } + + // Validate the file exists and is a regular file (not a directory) + var isDir: ObjCBool = false + guard FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir) else { + return .err(code: "not_found", message: "File not found: \(filePath)", data: ["path": filePath]) + } + guard !isDir.boolValue else { + return .err(code: "invalid_params", message: "Path is a directory, not a file: \(filePath)", data: ["path": filePath]) + } + guard FileManager.default.isReadableFile(atPath: filePath) else { + return .err(code: "permission_denied", message: "File not readable: \(filePath)", data: ["path": filePath]) + } + + var result: V2CallResult = .err(code: "internal_error", message: "Failed to create markdown panel", data: nil) + v2MainSync { + guard let ws = v2ResolveWorkspace(params: params, tabManager: tabManager) else { + result = .err(code: "not_found", message: "Workspace not found", data: nil) + return + } + v2MaybeFocusWindow(for: tabManager) + v2MaybeSelectWorkspace(tabManager, workspace: ws) + + let sourceSurfaceId = v2UUID(params, "surface_id") ?? ws.focusedPanelId + guard let sourceSurfaceId else { + result = .err(code: "not_found", message: "No focused surface to split", data: nil) + return + } + guard ws.panels[sourceSurfaceId] != nil else { + result = .err(code: "not_found", message: "Source surface not found", data: ["surface_id": sourceSurfaceId.uuidString]) + return + } + + let sourcePaneUUID = ws.paneId(forPanelId: sourceSurfaceId)?.id + + let createdPanel = ws.newMarkdownSplit( + from: sourceSurfaceId, + orientation: .horizontal, + filePath: filePath, + focus: v2FocusAllowed() + ) + + guard let markdownPanelId = createdPanel?.id else { + result = .err(code: "internal_error", message: "Failed to create markdown panel", data: nil) + return + } + + let targetPaneUUID = ws.paneId(forPanelId: markdownPanelId)?.id + let windowId = v2ResolveWindowId(tabManager: tabManager) + result = .ok([ + "window_id": v2OrNull(windowId?.uuidString), + "window_ref": v2Ref(kind: .window, uuid: windowId), + "workspace_id": ws.id.uuidString, + "workspace_ref": v2Ref(kind: .workspace, uuid: ws.id), + "pane_id": v2OrNull(targetPaneUUID?.uuidString), + "pane_ref": v2Ref(kind: .pane, uuid: targetPaneUUID), + "surface_id": markdownPanelId.uuidString, + "surface_ref": v2Ref(kind: .surface, uuid: markdownPanelId), + "source_surface_id": sourceSurfaceId.uuidString, + "source_surface_ref": v2Ref(kind: .surface, uuid: sourceSurfaceId), + "source_pane_id": v2OrNull(sourcePaneUUID?.uuidString), + "source_pane_ref": v2Ref(kind: .pane, uuid: sourcePaneUUID), + "target_pane_id": v2OrNull(targetPaneUUID?.uuidString), + "target_pane_ref": v2Ref(kind: .pane, uuid: targetPaneUUID), + "path": filePath + ]) + } + return result + } + + // MARK: - Browser + private func v2BrowserOpenSplit(params: [String: Any]) -> V2CallResult { guard let tabManager = v2ResolveTabManager(params: params) else { return .err(code: "unavailable", message: "TabManager not available", data: nil) diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 010310b4..70ff179d 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -307,6 +307,7 @@ extension Workspace { let terminalSnapshot: SessionTerminalPanelSnapshot? let browserSnapshot: SessionBrowserPanelSnapshot? + let markdownSnapshot: SessionMarkdownPanelSnapshot? switch panel.panelType { case .terminal: guard let terminalPanel = panel as? TerminalPanel else { return nil } @@ -327,6 +328,7 @@ extension Workspace { scrollback: resolvedScrollback ) browserSnapshot = nil + markdownSnapshot = nil case .browser: guard let browserPanel = panel as? BrowserPanel else { return nil } terminalSnapshot = nil @@ -339,6 +341,12 @@ extension Workspace { backHistoryURLStrings: historySnapshot.backHistoryURLStrings, forwardHistoryURLStrings: historySnapshot.forwardHistoryURLStrings ) + markdownSnapshot = nil + case .markdown: + guard let mdPanel = panel as? MarkdownPanel else { return nil } + terminalSnapshot = nil + browserSnapshot = nil + markdownSnapshot = SessionMarkdownPanelSnapshot(filePath: mdPanel.filePath) } return SessionPanelSnapshot( @@ -353,7 +361,8 @@ extension Workspace { listeningPorts: listeningPorts, ttyName: ttyName, terminal: terminalSnapshot, - browser: browserSnapshot + browser: browserSnapshot, + markdown: markdownSnapshot ) } @@ -513,6 +522,19 @@ extension Workspace { } applySessionPanelMetadata(snapshot, toPanelId: browserPanel.id) return browserPanel.id + case .markdown: + guard let filePath = snapshot.markdown?.filePath else { + return nil + } + guard let markdownPanel = newMarkdownSurface( + inPane: paneId, + filePath: filePath, + focus: false + ) else { + return nil + } + applySessionPanelMetadata(snapshot, toPanelId: markdownPanel.id) + return markdownPanel.id } } @@ -984,6 +1006,7 @@ final class Workspace: Identifiable, ObservableObject { private enum SurfaceKind { static let terminal = "terminal" static let browser = "browser" + static let markdown = "markdown" } // MARK: - Initialization @@ -1296,6 +1319,31 @@ final class Workspace: Identifiable, ObservableObject { } panelSubscriptions[browserPanel.id] = subscription } + + private func installMarkdownPanelSubscription(_ markdownPanel: MarkdownPanel) { + let subscription = markdownPanel.$displayTitle + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self, weak markdownPanel] newTitle in + guard let self = self, + let markdownPanel = markdownPanel, + let tabId = self.surfaceIdFromPanelId(markdownPanel.id) else { return } + guard let existing = self.bonsplitController.tab(tabId) else { return } + + if self.panelTitles[markdownPanel.id] != newTitle { + self.panelTitles[markdownPanel.id] = newTitle + } + let resolvedTitle = self.resolvedPanelTitle(panelId: markdownPanel.id, fallback: newTitle) + guard existing.title != resolvedTitle else { return } + self.bonsplitController.updateTab( + tabId, + title: resolvedTitle, + hasCustomTitle: self.panelCustomTitles[markdownPanel.id] != nil + ) + } + panelSubscriptions[markdownPanel.id] = subscription + } + // MARK: - Panel Access func panel(for surfaceId: TabID) -> (any Panel)? { @@ -1311,12 +1359,18 @@ final class Workspace: Identifiable, ObservableObject { panels[panelId] as? BrowserPanel } + func markdownPanel(for panelId: UUID) -> MarkdownPanel? { + panels[panelId] as? MarkdownPanel + } + private func surfaceKind(for panel: any Panel) -> String { switch panel.panelType { case .terminal: return SurfaceKind.terminal case .browser: return SurfaceKind.browser + case .markdown: + return SurfaceKind.markdown } } @@ -2149,6 +2203,119 @@ final class Workspace: Identifiable, ObservableObject { return browserPanel } + // MARK: - Markdown Panel Creation + + /// Create a new markdown panel split from an existing panel. + func newMarkdownSplit( + from panelId: UUID, + orientation: SplitOrientation, + insertFirst: Bool = false, + filePath: String, + focus: Bool = true + ) -> MarkdownPanel? { + // Find the pane containing the source panel + guard let sourceTabId = surfaceIdFromPanelId(panelId) else { return nil } + var sourcePaneId: PaneID? + for paneId in bonsplitController.allPaneIds { + let tabs = bonsplitController.tabs(inPane: paneId) + if tabs.contains(where: { $0.id == sourceTabId }) { + sourcePaneId = paneId + break + } + } + + guard let paneId = sourcePaneId else { return nil } + + // Create markdown panel + let markdownPanel = MarkdownPanel(workspaceId: id, filePath: filePath) + panels[markdownPanel.id] = markdownPanel + panelTitles[markdownPanel.id] = markdownPanel.displayTitle + + // Pre-generate the bonsplit tab ID so the mapping exists before the split lands. + let newTab = Bonsplit.Tab( + title: markdownPanel.displayTitle, + icon: markdownPanel.displayIcon, + kind: SurfaceKind.markdown, + isDirty: markdownPanel.isDirty, + isLoading: false, + isPinned: false + ) + surfaceIdToPanelId[newTab.id] = markdownPanel.id + let previousFocusedPanelId = focusedPanelId + + // Create the split with the markdown tab already present in the new pane. + // Mark this split as programmatic so didSplitPane doesn't auto-create a terminal. + isProgrammaticSplit = true + defer { isProgrammaticSplit = false } + guard bonsplitController.splitPane(paneId, orientation: orientation, withTab: newTab, insertFirst: insertFirst) != nil else { + surfaceIdToPanelId.removeValue(forKey: newTab.id) + panels.removeValue(forKey: markdownPanel.id) + panelTitles.removeValue(forKey: markdownPanel.id) + return nil + } + + // Suppress old view's becomeFirstResponder during reparenting. + let previousHostedView = focusedTerminalPanel?.hostedView + if focus { + previousHostedView?.suppressReparentFocus() + focusPanel(markdownPanel.id) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + previousHostedView?.clearSuppressReparentFocus() + } + } else { + preserveFocusAfterNonFocusSplit( + preferredPanelId: previousFocusedPanelId, + splitPanelId: markdownPanel.id, + previousHostedView: previousHostedView + ) + } + + installMarkdownPanelSubscription(markdownPanel) + + return markdownPanel + } + + /// Create a new markdown surface (tab) in the specified pane. + @discardableResult + func newMarkdownSurface( + inPane paneId: PaneID, + filePath: String, + focus: Bool? = nil + ) -> MarkdownPanel? { + let shouldFocusNewTab = focus ?? (bonsplitController.focusedPaneId == paneId) + + let markdownPanel = MarkdownPanel(workspaceId: id, filePath: filePath) + panels[markdownPanel.id] = markdownPanel + panelTitles[markdownPanel.id] = markdownPanel.displayTitle + + guard let newTabId = bonsplitController.createTab( + title: markdownPanel.displayTitle, + icon: markdownPanel.displayIcon, + kind: SurfaceKind.markdown, + isDirty: markdownPanel.isDirty, + isLoading: false, + isPinned: false, + inPane: paneId + ) else { + panels.removeValue(forKey: markdownPanel.id) + panelTitles.removeValue(forKey: markdownPanel.id) + return nil + } + + surfaceIdToPanelId[newTabId] = markdownPanel.id + + // Match terminal behavior: enforce deterministic selection + focus. + if shouldFocusNewTab { + bonsplitController.focusPane(paneId) + bonsplitController.selectTab(newTabId) + applyTabSelection(tabId: newTabId, inPane: paneId) + } + + installMarkdownPanelSubscription(markdownPanel) + + return markdownPanel + } + /// Close a panel. /// Returns true when a bonsplit tab close request was issued. func closePanel(_ panelId: UUID, force: Bool = false) -> Bool { diff --git a/skills/cmux-markdown/SKILL.md b/skills/cmux-markdown/SKILL.md new file mode 100644 index 00000000..8d2aac73 --- /dev/null +++ b/skills/cmux-markdown/SKILL.md @@ -0,0 +1,125 @@ +--- +name: cmux-markdown +description: Open markdown files in a formatted viewer panel with live reload. Use when you need to display plans, documentation, or notes alongside the terminal with rich rendering (headings, code blocks, tables, lists). +--- + +# Markdown Viewer with cmux + +Use this skill to display markdown files in a dedicated panel with rich formatting and live file watching. + +## Core Workflow + +1. Write your plan or notes to a `.md` file. +2. Open it in a markdown panel. +3. The panel auto-updates when the file changes on disk. + +```bash +# Open a markdown file as a split panel next to the current terminal +cmux markdown open plan.md + +# Absolute path +cmux markdown open /path/to/PLAN.md + +# Target a specific workspace +cmux markdown open design.md --workspace workspace:2 +``` + +## When to Use + +- Displaying an agent plan or task list alongside the terminal +- Showing documentation, changelogs, or READMEs while working +- Reviewing notes that update in real-time (e.g., a plan file being written by another process) + +## Live File Watching + +The panel automatically re-renders when the file changes on disk. This works with: + +- Direct writes (`echo "..." >> plan.md`) +- Editor saves (vim, nano, VS Code) +- Atomic file replacement (write to temp, rename over original) +- Agent-generated plan files that are updated progressively + +If the file is deleted, the panel shows a "file unavailable" state. During atomic replace, the panel attempts automatic reconnection within its short retry window. If the file returns later, close and reopen the panel. + +## Agent Integration + +### Opening a plan file + +Write your plan to a file, then open it: + +```bash +cat > plan.md << 'EOF' +# Task Plan + +## Steps +1. Analyze the codebase +2. Implement the feature +3. Write tests +4. Verify the build +EOF + +cmux markdown open plan.md +``` + +### Updating a plan in real-time + +The panel live-reloads, so simply overwrite the file as work progresses: + +```bash +# The markdown panel updates automatically when the file changes +echo "## Step 1: Complete" >> plan.md +``` + +### Recommended AGENTS.md instruction + +Add this to your project's `AGENTS.md` to instruct coding agents to use the markdown viewer: + +```markdown +## Plan Display + +When creating a plan or task list, write it to a `.md` file and open it in cmux: + + cmux markdown open plan.md + +The panel renders markdown with rich formatting and auto-updates when the file changes. +``` + +## Routing + +```bash +# Open in the caller's workspace (default -- uses CMUX_WORKSPACE_ID) +cmux markdown open plan.md + +# Open in a specific workspace +cmux markdown open plan.md --workspace workspace:2 + +# Open splitting from a specific surface +cmux markdown open plan.md --surface surface:5 + +# Open in a specific window +cmux markdown open plan.md --window window:1 +``` + +## Deep-Dive References + +| Reference | When to Use | +|-----------|-------------| +| [references/commands.md](references/commands.md) | Full command syntax and options | +| [references/live-reload.md](references/live-reload.md) | File watching behavior, atomic writes, edge cases | + +## Rendering Support + +The markdown panel renders: + +- Headings (h1-h6) with dividers on h1/h2 +- Fenced code blocks with monospaced font +- Inline code with highlighted background +- Tables with alternating row colors +- Ordered and unordered lists (nested) +- Blockquotes with left border +- Bold, italic, strikethrough +- Links (clickable) +- Horizontal rules +- Images (inline) + +Supports both light and dark mode. diff --git a/skills/cmux-markdown/agents/openai.yaml b/skills/cmux-markdown/agents/openai.yaml new file mode 100644 index 00000000..0ce42fe4 --- /dev/null +++ b/skills/cmux-markdown/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "cmux Markdown Viewer" + short_description: "Open markdown files in a formatted panel with live reload alongside the terminal." + default_prompt: "Use this skill to display markdown plans, docs, or notes in a cmux panel: write to a .md file, run 'cmux markdown open ', and the panel auto-updates when the file changes." diff --git a/skills/cmux-markdown/references/commands.md b/skills/cmux-markdown/references/commands.md new file mode 100644 index 00000000..f40f635d --- /dev/null +++ b/skills/cmux-markdown/references/commands.md @@ -0,0 +1,69 @@ +# Command Reference (cmux Markdown) + +## Opening a Markdown Panel + +```bash +cmux markdown open +cmux markdown # shorthand (implicit "open") +``` + +### Options + +| Flag | Description | Default | +|------|-------------|---------| +| `--workspace ` | Target workspace | `$CMUX_WORKSPACE_ID` | +| `--surface ` | Source surface to split from | Focused surface | +| `--window ` | Target window | Current window | + +### Output + +``` +OK surface=surface:8 pane=pane:3 path=/absolute/path/to/file.md +``` + +With `--json`: + +```json +{ + "window_id": "...", + "workspace_id": "...", + "pane_id": "...", + "surface_id": "...", + "path": "/absolute/path/to/file.md" +} +``` + +## Path Resolution + +- Relative paths are resolved against the caller's current working directory. +- `~` is expanded to the home directory. +- The resolved absolute path is returned in the output. + +```bash +# These are equivalent when run from /Users/me/project +cmux markdown open plan.md +cmux markdown open ./plan.md +cmux markdown open /Users/me/project/plan.md +``` + +## Panel Behavior + +- The panel opens as a **horizontal split** to the right of the source surface. +- The tab title shows the filename (e.g., `plan.md`). +- The tab icon is a document icon. +- Content is **read-only** with text selection enabled. +- The file path is displayed as a breadcrumb at the top of the panel. + +## Session Persistence + +Markdown panels are saved and restored across sessions. On restore, the panel re-reads the file from disk. If the file no longer exists at restore time, the panel is not recreated. + +## Help + +```bash +cmux markdown --help +cmux markdown -h +``` + +See also: +- [live-reload.md](live-reload.md) diff --git a/skills/cmux-markdown/references/live-reload.md b/skills/cmux-markdown/references/live-reload.md new file mode 100644 index 00000000..ca0ba724 --- /dev/null +++ b/skills/cmux-markdown/references/live-reload.md @@ -0,0 +1,53 @@ +# Live Reload Behavior + +The markdown panel watches the file on disk and automatically re-renders when it changes. This enables real-time plan tracking as agents or editors update the file. + +## How It Works + +The panel uses a kernel-level file system watcher (`DispatchSource` with `O_EVTONLY`) that monitors the file for: + +- **Write events** -- content was modified in place +- **Extend events** -- content was appended +- **Delete events** -- file was removed (atomic replace step 1) +- **Rename events** -- file was moved or renamed + +## Supported Write Patterns + +| Pattern | Supported | Notes | +|---------|-----------|-------| +| Direct write (`echo >>`) | Yes | Triggers write/extend event | +| Editor save (vim, nano) | Yes | Most editors use atomic write (see below) | +| Atomic replace (write tmp + rename) | Yes | Handled via delete/rename recovery | +| `sed -i` | Yes | Uses atomic replace internally | +| VS Code / IDE save | Yes | Uses atomic replace | +| Agent progressive writes | Yes | Each write triggers a re-render | + +## Atomic File Replacement + +Many editors and tools write files atomically: write to a temporary file, then rename it over the original. This shows up as a **delete** event followed by a new file appearing at the same path. + +The panel handles this by: + +1. Detecting the delete/rename event +2. Attempting to re-read the file immediately (in case the rename already happened) +3. If the file is missing, wait 500 ms and check again (the new file may not yet be in place) +4. Reconnecting the file watcher to the new inode + +## File Unavailable State + +If the file is deleted and does not reappear within the retry window, the panel shows a "file unavailable" state with the original path. The panel does not close automatically -- the user must close it manually. + +If the file later reappears at the same path (e.g., the user recreates it), the panel does NOT automatically reconnect. Close and reopen the panel to pick up the new file. + +## Performance + +- Re-reads are dispatched to the main thread and run synchronously. +- Large files (100KB+) may cause brief UI hitches during re-render. For extremely large markdown files, consider splitting into smaller documents. +- The file watcher runs on a low-priority background queue and has negligible CPU impact. + +## Tips for Agents + +- **Write the full plan file first, then open it.** This avoids the panel showing a partially written file. +- **Append-style updates work well.** Adding sections to the end of a file triggers a smooth re-render. +- **Overwriting the entire file is fine.** The atomic replace handling ensures no data is lost. +- **Don't delete and recreate rapidly.** If writing a new version, prefer overwriting in place or using atomic replacement. diff --git a/skills/cmux/SKILL.md b/skills/cmux/SKILL.md index 336102d0..515315cc 100644 --- a/skills/cmux/SKILL.md +++ b/skills/cmux/SKILL.md @@ -51,3 +51,4 @@ cmux trigger-flash --surface surface:7 | [references/panes-surfaces.md](references/panes-surfaces.md) | Splits, surfaces, move/reorder, focus routing | | [references/trigger-flash-and-health.md](references/trigger-flash-and-health.md) | Flash cue and surface health checks | | [../cmux-browser/SKILL.md](../cmux-browser/SKILL.md) | Browser automation on surface-backed webviews | +| [../cmux-markdown/SKILL.md](../cmux-markdown/SKILL.md) | Markdown viewer panel with live file watching | diff --git a/tests/test_markdown_open_regressions.py b/tests/test_markdown_open_regressions.py new file mode 100644 index 00000000..88af9c96 --- /dev/null +++ b/tests/test_markdown_open_regressions.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Regression tests for markdown-open CLI parsing/help behavior.""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + + +def get_repo_root() -> Path: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return Path(result.stdout.strip()) + return Path.cwd() + + +def require(content: str, needle: str, message: str, failures: list[str]) -> None: + if needle not in content: + failures.append(message) + + +def main() -> int: + repo_root = get_repo_root() + cli_path = repo_root / "CLI" / "cmux.swift" + panel_path = repo_root / "Sources" / "Panels" / "MarkdownPanel.swift" + + if not cli_path.exists(): + print(f"FAIL: missing expected file: {cli_path}") + return 1 + if not panel_path.exists(): + print(f"FAIL: missing expected file: {panel_path}") + return 1 + + cli_content = cli_path.read_text(encoding="utf-8") + panel_content = panel_path.read_text(encoding="utf-8") + failures: list[str] = [] + + # CLI parser behavior. + require( + cli_content, + 'if let first = args.first, first.lowercased() == "open" {', + "markdown parser should explicitly support the 'open' subcommand", + failures, + ) + require( + cli_content, + "args.count == 1", + "markdown parser should accept single-arg shorthand path", + failures, + ) + require( + cli_content, + "args.count == 1, let first = args.first, !first.hasPrefix(\"-\")", + "markdown parser should reject option-like single args from shorthand path mode", + failures, + ) + require( + cli_content, + "let trailingArgs = Array(subArgs.dropFirst())", + "markdown parser should validate trailing arguments", + failures, + ) + require( + cli_content, + 'trailingArgs.first(where: { $0.hasPrefix("-") })', + "markdown parser should detect unknown trailing flags", + failures, + ) + require( + cli_content, + "markdown open: unexpected argument", + "markdown parser should error on unexpected trailing args", + failures, + ) + + # Help text should document shorthand and full index handle support. + require( + cli_content, + "Usage: cmux markdown open [options]\n cmux markdown (shorthand for 'open')", + "markdown subcommand help should include shorthand usage", + failures, + ) + require( + cli_content, + "--window Target window", + "markdown subcommand help should document window index handles", + failures, + ) + require( + cli_content, + "markdown [open] (open markdown file in formatted viewer panel with live reload)", + "top-level help should include markdown shorthand syntax", + failures, + ) + + # Session restore edge case: file missing at startup should still attempt reconnect. + require( + panel_content, + "if isFileUnavailable && fileWatchSource == nil {", + "MarkdownPanel should schedule reattach when watcher cannot start at init", + failures, + ) + require( + panel_content, + "scheduleReattach(attempt: 1)", + "MarkdownPanel should trigger reattach retries for missing files", + failures, + ) + + if failures: + print("FAIL: markdown-open regression(s) detected") + for failure in failures: + print(f"- {failure}") + return 1 + + print("PASS: markdown-open CLI/help/reattach regression checks are present") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 76835662f582869701ba82dd42b70d314ccd2ae9 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 4 Mar 2026 18:03:25 -0800 Subject: [PATCH 077/232] Fix CLI socket autodiscovery for tagged cmux sockets (#832) --- CLI/cmux.swift | 279 +++++++++++++++++++++---- tests/test_cli_socket_autodiscovery.py | 150 +++++++++++++ 2 files changed, 393 insertions(+), 36 deletions(-) create mode 100755 tests/test_cli_socket_autodiscovery.py diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 17a13f91..8d218262 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -451,9 +451,159 @@ private enum SocketPasswordResolver { } } +private enum CLISocketPathSource { + case explicitFlag + case environment + case implicitDefault +} + +private enum CLISocketPathResolver { + static let defaultSocketPath = "/tmp/cmux.sock" + private static let fallbackSocketPath = "/tmp/cmux-debug.sock" + private static let stagingSocketPath = "/tmp/cmux-staging.sock" + private static let lastSocketPathFile = "/tmp/cmux-last-socket-path" + + static func resolve( + requestedPath: String, + source: CLISocketPathSource, + environment: [String: String] = ProcessInfo.processInfo.environment + ) -> String { + guard source == .implicitDefault else { + return requestedPath + } + + let candidates = dedupe(candidatePaths(requestedPath: requestedPath, environment: environment)) + + // Prefer sockets that are currently accepting connections. + for path in candidates where canConnect(to: path) { + return path + } + + // If the listener is still starting, prefer existing socket files. + for path in candidates where isSocketFile(path) { + return path + } + + return requestedPath + } + + private static func candidatePaths(requestedPath: String, environment: [String: String]) -> [String] { + var candidates: [String] = [] + + if let tag = normalized(environment["CMUX_TAG"]) { + let slug = sanitizeTagSlug(tag) + candidates.append("/tmp/cmux-debug-\(slug).sock") + candidates.append("/tmp/cmux-\(slug).sock") + } + + candidates.append(requestedPath) + candidates.append(fallbackSocketPath) + candidates.append(stagingSocketPath) + candidates.append(contentsOf: discoverTaggedSockets(limit: 12)) + if let last = readLastSocketPath() { + candidates.append(last) + } + return candidates + } + + private static func readLastSocketPath() -> String? { + guard let data = try? String(contentsOfFile: lastSocketPathFile, encoding: .utf8) else { + return nil + } + return normalized(data) + } + + private static func discoverTaggedSockets(limit: Int) -> [String] { + guard let entries = try? FileManager.default.contentsOfDirectory(atPath: "/tmp") else { + return [] + } + + var discovered: [(path: String, mtime: TimeInterval)] = [] + discovered.reserveCapacity(min(limit, entries.count)) + for name in entries where name.hasPrefix("cmux") && name.hasSuffix(".sock") { + let path = "/tmp/\(name)" + var st = stat() + guard lstat(path, &st) == 0 else { continue } + guard (st.st_mode & mode_t(S_IFMT)) == mode_t(S_IFSOCK) else { continue } + if path == defaultSocketPath || path == fallbackSocketPath || path == stagingSocketPath { + continue + } + let modified = TimeInterval(st.st_mtimespec.tv_sec) + TimeInterval(st.st_mtimespec.tv_nsec) / 1_000_000_000 + discovered.append((path: path, mtime: modified)) + } + + discovered.sort { $0.mtime > $1.mtime } + return discovered.prefix(limit).map(\.path) + } + + private static func isSocketFile(_ path: String) -> Bool { + var st = stat() + return lstat(path, &st) == 0 && (st.st_mode & mode_t(S_IFMT)) == mode_t(S_IFSOCK) + } + + private static func canConnect(to path: String) -> Bool { + guard isSocketFile(path) else { return false } + let fd = socket(AF_UNIX, SOCK_STREAM, 0) + guard fd >= 0 else { return false } + defer { Darwin.close(fd) } + + var addr = sockaddr_un() + addr.sun_family = sa_family_t(AF_UNIX) + let maxLength = MemoryLayout.size(ofValue: addr.sun_path) + path.withCString { ptr in + withUnsafeMutablePointer(to: &addr.sun_path) { pathPtr in + let buf = UnsafeMutableRawPointer(pathPtr).assumingMemoryBound(to: CChar.self) + strncpy(buf, ptr, maxLength - 1) + } + } + + let result = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + Darwin.connect(fd, sockaddrPtr, socklen_t(MemoryLayout.size)) + } + } + return result == 0 + } + + private static func sanitizeTagSlug(_ raw: String) -> String { + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + let slug = trimmed + .replacingOccurrences(of: "[^a-z0-9]+", with: "-", options: .regularExpression) + .replacingOccurrences(of: "-+", with: "-", options: .regularExpression) + .trimmingCharacters(in: CharacterSet(charactersIn: "-")) + return slug.isEmpty ? "agent" : slug + } + + private static func normalized(_ value: String?) -> String? { + guard let value else { return nil } + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? nil : trimmed + } + + private static func dedupe(_ paths: [String]) -> [String] { + var seen: Set = [] + var ordered: [String] = [] + ordered.reserveCapacity(paths.count) + for path in paths where !path.isEmpty { + if seen.insert(path).inserted { + ordered.append(path) + } + } + return ordered + } +} + final class SocketClient { private let path: String private var socketFD: Int32 = -1 + private static let connectRetryWindowSeconds: TimeInterval = 2.0 + private static let connectRetryIntervalSeconds: TimeInterval = 0.1 + private static let retriableConnectErrnos: Set = [ + ENOENT, + ECONNREFUSED, + EAGAIN, + EINTR + ] private static let defaultResponseTimeoutSeconds: TimeInterval = 15.0 private static let responseTimeoutSeconds: TimeInterval = { let env = ProcessInfo.processInfo.environment @@ -472,40 +622,66 @@ final class SocketClient { func connect() throws { if socketFD >= 0 { return } - // Verify socket is owned by the current user to prevent fake-socket attacks - var st = stat() - guard stat(path, &st) == 0 else { - throw CLIError(message: "Socket not found at \(path)") - } - guard st.st_uid == getuid() else { - throw CLIError(message: "Socket at \(path) is not owned by the current user — refusing to connect") - } + let deadline = Date().addingTimeInterval(Self.connectRetryWindowSeconds) + var lastError: CLIError? - socketFD = socket(AF_UNIX, SOCK_STREAM, 0) - if socketFD < 0 { - throw CLIError(message: "Failed to create socket") - } - - var addr = sockaddr_un() - addr.sun_family = sa_family_t(AF_UNIX) - let maxLength = MemoryLayout.size(ofValue: addr.sun_path) - path.withCString { ptr in - withUnsafeMutablePointer(to: &addr.sun_path) { pathPtr in - let buf = UnsafeMutableRawPointer(pathPtr).assumingMemoryBound(to: CChar.self) - strncpy(buf, ptr, maxLength - 1) + while true { + // Verify socket is owned by the current user to prevent fake-socket attacks. + var st = stat() + guard stat(path, &st) == 0 else { + let error = CLIError(message: "Socket not found at \(path)") + lastError = error + if errno == ENOENT, Date() < deadline { + Thread.sleep(forTimeInterval: Self.connectRetryIntervalSeconds) + continue + } + throw error } - } - - let result = withUnsafePointer(to: &addr) { ptr in - ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in - Darwin.connect(socketFD, sockaddrPtr, socklen_t(MemoryLayout.size)) + guard (st.st_mode & mode_t(S_IFMT)) == mode_t(S_IFSOCK) else { + throw CLIError(message: "Path exists at \(path) but is not a Unix socket") } - } - if result != 0 { + guard st.st_uid == getuid() else { + throw CLIError(message: "Socket at \(path) is not owned by the current user — refusing to connect") + } + + socketFD = socket(AF_UNIX, SOCK_STREAM, 0) + if socketFD < 0 { + throw CLIError(message: "Failed to create socket") + } + + var addr = sockaddr_un() + addr.sun_family = sa_family_t(AF_UNIX) + let maxLength = MemoryLayout.size(ofValue: addr.sun_path) + path.withCString { ptr in + withUnsafeMutablePointer(to: &addr.sun_path) { pathPtr in + let buf = UnsafeMutableRawPointer(pathPtr).assumingMemoryBound(to: CChar.self) + strncpy(buf, ptr, maxLength - 1) + } + } + + let result = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + Darwin.connect(socketFD, sockaddrPtr, socklen_t(MemoryLayout.size)) + } + } + if result == 0 { + return + } + + let connectErrno = errno Darwin.close(socketFD) socketFD = -1 - throw CLIError(message: "Failed to connect to socket at \(path)") + + let error = CLIError(message: "Failed to connect to socket at \(path)") + lastError = error + if Self.retriableConnectErrnos.contains(connectErrno), Date() < deadline { + Thread.sleep(forTimeInterval: Self.connectRetryIntervalSeconds) + continue + } + throw error } + + throw lastError ?? CLIError(message: "Failed to connect to socket at \(path)") } func close() { @@ -614,7 +790,19 @@ struct CMUXCLI { let args: [String] func run() throws { - var socketPath = ProcessInfo.processInfo.environment["CMUX_SOCKET_PATH"] ?? "/tmp/cmux.sock" + let processEnv = ProcessInfo.processInfo.environment + let envSocketPath: String? = { + guard let raw = processEnv["CMUX_SOCKET_PATH"] else { return nil } + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? nil : trimmed + }() + var socketPath = envSocketPath ?? CLISocketPathResolver.defaultSocketPath + var socketPathSource: CLISocketPathSource + if let envSocketPath { + socketPathSource = envSocketPath == CLISocketPathResolver.defaultSocketPath ? .implicitDefault : .environment + } else { + socketPathSource = .implicitDefault + } var jsonOutput = false var idFormatArg: String? = nil var windowId: String? = nil @@ -628,6 +816,7 @@ struct CMUXCLI { throw CLIError(message: "--socket requires a path") } socketPath = args[index + 1] + socketPathSource = .explicitFlag index += 2 continue } @@ -682,7 +871,12 @@ struct CMUXCLI { command: command, commandArgs: commandArgs, socketPath: socketPath, - processEnv: ProcessInfo.processInfo.environment + processEnv: processEnv + ) + let resolvedSocketPath = CLISocketPathResolver.resolve( + requestedPath: socketPath, + source: socketPathSource, + environment: processEnv ) if command == "version" { @@ -692,7 +886,7 @@ struct CMUXCLI { // If the argument looks like a path (not a known command), open a workspace there. if looksLikePath(command) { - try openPath(command, socketPath: socketPath) + try openPath(command, socketPath: resolvedSocketPath) return } @@ -706,16 +900,28 @@ struct CMUXCLI { return } - let client = SocketClient(path: socketPath) + let client = SocketClient(path: resolvedSocketPath) + if resolvedSocketPath != socketPath { + cliTelemetry.breadcrumb( + "socket.path.autodiscovered", + data: [ + "requested_path": socketPath, + "resolved_path": resolvedSocketPath + ] + ) + } cliTelemetry.breadcrumb( "socket.connect.attempt", - data: ["command": command] + data: [ + "command": command, + "path": resolvedSocketPath + ] ) do { try client.connect() - cliTelemetry.breadcrumb("socket.connect.success") + cliTelemetry.breadcrumb("socket.connect.success", data: ["path": resolvedSocketPath]) } catch { - cliTelemetry.breadcrumb("socket.connect.failure") + cliTelemetry.breadcrumb("socket.connect.failure", data: ["path": resolvedSocketPath]) cliTelemetry.captureError(stage: "socket_connect", error: error) throw error } @@ -6613,7 +6819,8 @@ struct CMUXCLI { ALL commands (send, list-panels, new-split, notify, etc.). CMUX_TAB_ID Optional alias used by `tab-action`/`rename-tab` as default --tab. CMUX_SURFACE_ID Auto-set in cmux terminals. Used as default --surface. - CMUX_SOCKET_PATH Override the default Unix socket path (/tmp/cmux.sock). + CMUX_SOCKET_PATH Override the Unix socket path. Without this, the CLI defaults + to /tmp/cmux.sock and auto-discovers tagged/debug sockets. CMUX_CLI_SENTRY_DISABLED Set to 1 to disable CLI Sentry socket diagnostics. """ diff --git a/tests/test_cli_socket_autodiscovery.py b/tests/test_cli_socket_autodiscovery.py new file mode 100755 index 00000000..6eaa205d --- /dev/null +++ b/tests/test_cli_socket_autodiscovery.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Regression test: CLI should auto-discover tagged debug sockets from CMUX_TAG.""" + +from __future__ import annotations + +import glob +import os +import shutil +import socket +import subprocess +import threading + + +def resolve_cmux_cli() -> str: + explicit = os.environ.get("CMUX_CLI_BIN") or os.environ.get("CMUX_CLI") + if explicit and os.path.exists(explicit) and os.access(explicit, os.X_OK): + return explicit + + candidates: list[str] = [] + candidates.extend(glob.glob(os.path.expanduser("~/Library/Developer/Xcode/DerivedData/*/Build/Products/Debug/cmux"))) + candidates.extend(glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux")) + candidates = [p for p in candidates if os.path.exists(p) and os.access(p, os.X_OK)] + if candidates: + candidates.sort(key=os.path.getmtime, reverse=True) + return candidates[0] + + in_path = shutil.which("cmux") + if in_path: + return in_path + + raise RuntimeError("Unable to find cmux CLI binary. Set CMUX_CLI_BIN.") + + +class PingServer: + def __init__(self, socket_path: str): + self.socket_path = socket_path + self.ready = threading.Event() + self.error: Exception | None = None + self._thread = threading.Thread(target=self._run, daemon=True) + + def start(self) -> None: + self._thread.start() + + def wait_ready(self, timeout: float) -> bool: + return self.ready.wait(timeout) + + def join(self, timeout: float) -> None: + self._thread.join(timeout=timeout) + + def _run(self) -> None: + server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + if os.path.exists(self.socket_path): + os.remove(self.socket_path) + server.bind(self.socket_path) + server.listen(1) + server.settimeout(6.0) + self.ready.set() + + # The CLI may probe candidate sockets with a connect-only check before + # issuing the actual command, so handle more than one connection. + for _ in range(4): + conn, _ = server.accept() + with conn: + conn.settimeout(2.0) + data = b"" + while b"\n" not in data: + chunk = conn.recv(4096) + if not chunk: + break + data += chunk + + if b"ping" in data: + conn.sendall(b"PONG\n") + return + raise RuntimeError("Did not receive ping command on test socket") + except Exception as exc: # pragma: no cover - explicit surface on failure + self.error = exc + self.ready.set() + finally: + server.close() + + +def main() -> int: + try: + cli_path = resolve_cmux_cli() + except Exception as exc: + print(f"FAIL: {exc}") + return 1 + + tag = f"cli-autodiscover-{os.getpid()}" + socket_path = f"/tmp/cmux-debug-{tag}.sock" + server = PingServer(socket_path) + server.start() + + if not server.wait_ready(2.0): + print("FAIL: socket server did not become ready") + return 1 + + if server.error is not None: + print(f"FAIL: socket server failed to start: {server.error}") + return 1 + + env = os.environ.copy() + env["CMUX_SOCKET_PATH"] = "/tmp/cmux.sock" + env["CMUX_TAG"] = tag + env["CMUX_CLI_SENTRY_DISABLED"] = "1" + env["CMUX_CLAUDE_HOOK_SENTRY_DISABLED"] = "1" + + try: + proc = subprocess.run( + [cli_path, "ping"], + text=True, + capture_output=True, + env=env, + timeout=8, + check=False, + ) + except Exception as exc: + print(f"FAIL: invoking cmux ping failed: {exc}") + return 1 + finally: + server.join(timeout=2.0) + try: + os.remove(socket_path) + except OSError: + pass + + if server.error is not None: + print(f"FAIL: socket server error: {server.error}") + return 1 + + if proc.returncode != 0: + print("FAIL: cmux ping returned non-zero status") + print(f"stdout={proc.stdout!r}") + print(f"stderr={proc.stderr!r}") + return 1 + + if proc.stdout.strip() != "PONG": + print("FAIL: cmux ping did not use auto-discovered socket") + print(f"stdout={proc.stdout!r}") + print(f"stderr={proc.stderr!r}") + return 1 + + print("PASS: cmux ping auto-discovers tagged socket from CMUX_TAG") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 895fb802c85af63be3d06de6bece684425195a72 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:04:33 -0800 Subject: [PATCH 078/232] Add 16 new languages to localization (#895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add localization for 16 new languages Add translations for all 637 UI string keys and 3 InfoPlist keys in: ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, zh-Hans, zh-Hant Update AppLanguage enum and knownRegions to include all 18 languages. Total supported languages: en, ja + 16 new = 18. * Reorder languages: English first, rest alphabetical, explicit display names Use "Chinese Simplified" / "Chinese Traditional" naming. Show native script with English name in parentheses for non-Latin languages. * Add Chinese native characters to language display names * Delay language restart dialog until picker dropdown closes * Fix Arabic bidi rendering in language picker with LTR mark * Defer AppleLanguages write to app launch, fix picker animation lag Writing AppleLanguages to UserDefaults triggers synchronous locale recalculation on the main thread, causing the picker dropdown dismiss animation to stutter. Since a restart is already required, move the AppleLanguages write to init() on next launch instead of onChange. * Fix reset path and add apply() back to delayed onChange - resetAllSettings() now calls LanguageSettings.apply(.system) and shows restart alert if language was changed from launch value - onChange also calls apply() inside the 0.3s delay block, so AppleLanguages is set before restart (avoids two-restart issue) - init() still calls apply() as belt-and-suspenders for launch * Fix deferred alert race: re-check current language in closure If user changes language then changes back within 0.3s, the stale closure would fire with the old value. Now reads current appLanguage inside the closure instead of capturing newValue. * Fix Spanish and Danish translations: restore missing diacritics Spanish was missing all áéíóúñ characters (now 315 diacritics). Danish was missing all æøå characters (now 401 diacritics). Both languages fully retranslated with correct orthography. * Fix reset restart alert: compare new value against launch language Was comparing previousLanguage against languageAtLaunch, which would miss the case where user launched in Spanish and reset to System (Spanish != Spanish = false, so no alert). Now compares the new appLanguage (system) against languageAtLaunch. --- GhosttyTabs.xcodeproj/project.pbxproj | 16 + Resources/InfoPlist.xcstrings | 360 +- Resources/Localizable.xcstrings | 61152 ++++++++++++++++++++++++ Sources/cmuxApp.swift | 62 +- 4 files changed, 61542 insertions(+), 48 deletions(-) diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 0b4326bd..9b977a9b 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -561,6 +561,22 @@ en, Base, ja, + ar, + bs, + da, + de, + es, + fr, + it, + ko, + nb, + pl, + pt-BR, + ru, + th, + tr, + zh-Hans, + zh-Hant, ); mainGroup = A5001040; packageReferences = ( diff --git a/Resources/InfoPlist.xcstrings b/Resources/InfoPlist.xcstrings index fa672ecd..00297777 100644 --- a/Resources/InfoPlist.xcstrings +++ b/Resources/InfoPlist.xcstrings @@ -1,54 +1,342 @@ { - "sourceLanguage" : "en", - "version" : "1.0", - "strings" : { - "NSMicrophoneUsageDescription" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A program running within cmux would like to use your microphone." + "sourceLanguage": "en", + "version": "1.0", + "strings": { + "NSMicrophoneUsageDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A program running within cmux would like to use your microphone." } }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux 内で実行中のプログラムがマイクの使用を求めています。" + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux 内で実行中のプログラムがマイクの使用を求めています。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 中运行的程序想要使用您的麦克风。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 中執行的程式想要使用您的麥克風。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 내에서 실행 중인 프로그램이 마이크를 사용하려고 합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ein in cmux ausgeführtes Programm möchte Ihr Mikrofon verwenden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Un programa en ejecución dentro de cmux desea usar tu micrófono." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Un programme s'exécutant dans cmux souhaite utiliser votre microphone." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Un programma in esecuzione in cmux desidera utilizzare il microfono." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Et program, der kører i cmux, vil gerne bruge din mikrofon." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Program działający w cmux chciałby użyć Twojego mikrofonu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Программа, запущенная в cmux, хотела бы использовать ваш микрофон." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Program koji se izvršava unutar cmux želi koristiti vaš mikrofon." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يرغب برنامج يعمل داخل cmux في استخدام الميكروفون." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Et program som kjører i cmux ønsker å bruke mikrofonen din." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Um programa em execução no cmux gostaria de usar seu microfone." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โปรแกรมที่ทำงานภายใน cmux ต้องการใช้ไมโครโฟนของคุณ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux içinde çalışan bir program mikrofonunuzu kullanmak istiyor." } } } }, - "New $(PRODUCT_NAME) Workspace Here" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New $(PRODUCT_NAME) Workspace Here" + "New $(PRODUCT_NAME) Workspace Here": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New $(PRODUCT_NAME) Workspace Here" } }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ここに新規 $(PRODUCT_NAME) ワークスペースを作成" + "ja": { + "stringUnit": { + "state": "translated", + "value": "ここに新規 $(PRODUCT_NAME) ワークスペースを作成" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在此新建 $(PRODUCT_NAME) 工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在此新增 $(PRODUCT_NAME) 工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "여기에 새 $(PRODUCT_NAME) 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer $(PRODUCT_NAME)-Arbeitsbereich hier" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo de $(PRODUCT_NAME) aquí" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail $(PRODUCT_NAME) ici" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro $(PRODUCT_NAME) qui" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt $(PRODUCT_NAME)-arbejdsområde her" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza $(PRODUCT_NAME) tutaj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство $(PRODUCT_NAME) здесь" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi $(PRODUCT_NAME) radni prostor ovdje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل $(PRODUCT_NAME) جديدة هنا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt $(PRODUCT_NAME)-arbeidsområde her" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Área de Trabalho do $(PRODUCT_NAME) Aqui" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ $(PRODUCT_NAME) ใหม่ที่นี่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Buraya Yeni $(PRODUCT_NAME) Çalışma Alanı" } } } }, - "New $(PRODUCT_NAME) Window Here" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New $(PRODUCT_NAME) Window Here" + "New $(PRODUCT_NAME) Window Here": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New $(PRODUCT_NAME) Window Here" } }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ここに新規 $(PRODUCT_NAME) ウインドウを作成" + "ja": { + "stringUnit": { + "state": "translated", + "value": "ここに新規 $(PRODUCT_NAME) ウインドウを作成" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在此新建 $(PRODUCT_NAME) 窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在此新增 $(PRODUCT_NAME) 視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "여기에 새 $(PRODUCT_NAME) 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neues $(PRODUCT_NAME)-Fenster hier" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva ventana de $(PRODUCT_NAME) aquí" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvelle fenêtre $(PRODUCT_NAME) ici" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova finestra $(PRODUCT_NAME) qui" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt $(PRODUCT_NAME)-vindue her" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowe okno $(PRODUCT_NAME) tutaj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое окно $(PRODUCT_NAME) здесь" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi $(PRODUCT_NAME) prozor ovdje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة $(PRODUCT_NAME) جديدة هنا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt $(PRODUCT_NAME)-vindu her" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Janela do $(PRODUCT_NAME) Aqui" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง $(PRODUCT_NAME) ใหม่ที่นี่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Buraya Yeni $(PRODUCT_NAME) Penceresi" } } } diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 3f807bf8..3f711d05 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -16,6 +16,102 @@ "state": "translated", "value": "cmux" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } } } }, @@ -33,6 +129,102 @@ "state": "translated", "value": "Build" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "构建" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "建置版本" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "빌드" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Compilación" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kompilacja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сборка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Verzija" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "البناء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bygg" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "บิลด์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Derleme" + } } } }, @@ -50,6 +242,102 @@ "state": "translated", "value": "Commit" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "提交" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "提交" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "커밋" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Коммит" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإيداع" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คอมมิต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } } } }, @@ -67,6 +355,102 @@ "state": "translated", "value": "Ghosttyベースの縦タブ付きターミナルと\nmacOS用通知パネル。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "基于 Ghostty 的 macOS 终端,\\n支持垂直标签页和通知面板。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "基於 Ghostty 的 macOS 終端機,\\n具備垂直標籤頁與通知面板。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세로 탭과 알림 패널을 갖춘\\nGhostty 기반 macOS 터미널." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ein Ghostty-basiertes Terminal mit vertikalen Tabs\\nund einem Benachrichtigungsfeld für macOS." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Un terminal basado en Ghostty con pestañas verticales\\ny un panel de notificaciones para macOS." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Un terminal basé sur Ghostty avec des onglets verticaux\\net un panneau de notifications pour macOS." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Un terminale basato su Ghostty con schede verticali\\ne un pannello notifiche per macOS." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "En Ghostty-baseret terminal med lodrette faner\nog et notifikationspanel til macOS." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Terminal oparty na Ghostty z pionowymi kartami\ni panelem powiadomień dla macOS." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал на базе Ghostty с вертикальными вкладками\\nи панелью уведомлений для macOS." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Terminal zasnovan na Ghostty sa vertikalnim tabovima\\ni panelom za obavještenja za macOS." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "طرفية مبنية على Ghostty مع ألسنة عمودية\\nولوحة إشعارات لنظام macOS." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "En Ghostty-basert terminal med vertikale faner\\nog et varselpanel for macOS." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Um terminal baseado no Ghostty com abas verticais\\ne um painel de notificações para macOS." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัลบน Ghostty พร้อมแท็บแนวตั้ง\\nและแผงการแจ้งเตือนสำหรับ macOS" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "macOS için dikey sekmeli ve bildirim panelli\\nGhostty tabanlı terminal." + } } } }, @@ -84,6 +468,102 @@ "state": "translated", "value": "ドキュメント" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "文档" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "文件" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "문서" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dokumentation" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Documentación" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Documentation" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Documentazione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dokumentation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dokumentacja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Документация" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dokumentacija" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المستندات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dokumentasjon" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Documentação" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เอกสาร" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Belgeler" + } } } }, @@ -101,6 +581,102 @@ "state": "translated", "value": "GitHub" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } } } }, @@ -118,6 +694,102 @@ "state": "translated", "value": "ライセンス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "许可证" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "授權條款" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "라이선스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Lizenzen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Licencias" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Licences" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Licenze" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Licenser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Licencje" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лицензии" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Licence" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التراخيص" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lisenser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Licenças" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สัญญาอนุญาต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Lisanslar" + } } } }, @@ -135,6 +807,102 @@ "state": "translated", "value": "ライセンスファイルが見つかりません。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未找到许可证文件。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "找不到授權條款檔案。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "라이선스 파일을 찾을 수 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Lizenzdatei nicht gefunden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se encontró el archivo de licencias." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fichier de licences introuvable." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "File delle licenze non trovato." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Licensfilen blev ikke fundet." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie znaleziono pliku licencji." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл лицензий не найден." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Datoteka s licencama nije pronađena." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لم يتم العثور على ملف التراخيص." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lisensfilen ble ikke funnet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Arquivo de licenças não encontrado." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่พบไฟล์สัญญาอนุญาต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Lisans dosyası bulunamadı." + } } } }, @@ -152,6 +920,102 @@ "state": "translated", "value": "サードパーティライセンス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "第三方许可证" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "第三方授權條款" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "서드파티 라이선스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Drittanbieter-Lizenzen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Licencias de terceros" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Licences tierces" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Licenze di terze parti" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tredjepartslicenser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Licencje stron trzecich" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лицензии сторонних компонентов" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Licence trećih strana" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تراخيص الجهات الخارجية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tredjepartslisenser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Licenças de Terceiros" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สัญญาอนุญาตของบุคคลที่สาม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Üçüncü Taraf Lisansları" + } } } }, @@ -169,6 +1033,102 @@ "state": "translated", "value": "Version" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "版本" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "版本" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "버전" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Versión" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Versione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wersja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Версия" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Verzija" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإصدار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Versjon" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Versão" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวอร์ชัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sürüm" + } } } }, @@ -186,6 +1146,102 @@ "state": "translated", "value": "%1$@、ワークスペース %3$lld中%2$lld" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%1$@,工作区 %2$lld / %3$lld" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%1$@,工作區 %2$lld / %3$lld" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%1$@, 작업 공간 %2$lld / %3$lld" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%1$@, Arbeitsbereich %2$lld von %3$lld" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%1$@, espacio de trabajo %2$lld de %3$lld" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%1$@, espace de travail %2$lld sur %3$lld" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%1$@, area di lavoro %2$lld di %3$lld" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%1$@, arbejdsområde %2$lld af %3$lld" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%1$@, przestrzeń robocza %2$lld z %3$lld" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%1$@, рабочее пространство %2$lld из %3$lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%1$@, radni prostor %2$lld od %3$lld" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "%1$@، مساحة العمل %2$lld من %3$lld" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%1$@, arbeidsområde %2$lld av %3$lld" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%1$@, área de trabalho %2$lld de %3$lld" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%1$@, เวิร์กสเปซที่ %2$lld จาก %3$lld" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%1$@, çalışma alanı %3$lld/%2$lld" + } } } }, @@ -203,6 +1259,102 @@ "state": "translated", "value": "適用" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "应用" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "套用" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "적용" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Anwenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Aplicar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Appliquer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Applica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Anvend" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zastosuj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Применить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Primijeni" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تطبيق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bruk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aplicar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "นำไปใช้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygula" + } } } }, @@ -220,6 +1372,102 @@ "state": "translated", "value": "キャンセル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "취소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Annuler" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Annulla" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Annuller" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Anuluj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkaži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avbryt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Vazgeç" + } } } }, @@ -237,6 +1485,102 @@ "state": "translated", "value": "#RRGGBB形式で16進カラーコードを入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请输入 #RRGGBB 格式的十六进制颜色。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請輸入 #RRGGBB 格式的十六進位色碼。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB 형식으로 16진수 색상을 입력하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie eine Hex-Farbe im Format #RRGGBB ein." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un color hexadecimal con el formato #RRGGBB." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez une couleur hexadécimale au format #RRVVBB." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un colore esadecimale nel formato #RRGGBB." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast en hex-farve i formatet #RRGGBB." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź kolor szesnastkowy w formacie #RRGGBB." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите цвет в формате #RRGGBB." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite heksadecimalnu boju u formatu #RRGGBB." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل لونًا سداسيًا بالتنسيق #RRGGBB." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn en heksadesimal farge i formatet #RRGGBB." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira uma cor hexadecimal no formato #RRGGBB." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนรหัสสี hex ในรูปแบบ #RRGGBB" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB biçiminde bir onaltılık renk girin." + } } } }, @@ -254,6 +1598,102 @@ "state": "translated", "value": "カスタムワークスペースカラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自定义工作区颜色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自訂工作區顏色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 작업 공간 색상" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzerdefinierte Arbeitsbereichsfarbe" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Color personalizado del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleur personnalisée de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Colore personalizzato area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tilpasset arbejdsområdefarve" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Własny kolor przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользовательский цвет рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prilagođena boja radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لون مساحة عمل مخصص" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Egendefinert arbeidsområdefarge" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cor Personalizada da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สีเวิร์กสเปซที่กำหนดเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel Çalışma Alanı Rengi" + } } } }, @@ -271,6 +1711,102 @@ "state": "translated", "value": "#RRGGBB形式で16進カラーコードを入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请输入 #RRGGBB 格式的十六进制颜色。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請輸入 #RRGGBB 格式的十六進位色碼。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB 형식으로 16진수 색상을 입력하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie eine Hex-Farbe im Format #RRGGBB ein." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un color hexadecimal con el formato #RRGGBB." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez une couleur hexadécimale au format #RRVVBB." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un colore esadecimale nel formato #RRGGBB." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast en hex-farve i formatet #RRGGBB." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź kolor szesnastkowy w formacie #RRGGBB." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите цвет в формате #RRGGBB." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite heksadecimalnu boju u formatu #RRGGBB." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل لونًا سداسيًا بالتنسيق #RRGGBB." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn en heksadesimal farge i formatet #RRGGBB." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira uma cor hexadecimal no formato #RRGGBB." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนรหัสสี hex ในรูปแบบ #RRGGBB" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB biçiminde bir onaltılık renk girin." + } } } }, @@ -288,6 +1824,102 @@ "state": "translated", "value": "「%@」は有効な16進カラーではありません。#RRGGBB形式で入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" 不是有效的十六进制颜色。请使用 #RRGGBB 格式。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "「%@」不是有效的十六進位色碼。請使用 #RRGGBB 格式。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "\"%@\"은(는) 유효한 16진수 색상이 아닙니다. #RRGGBB 형식을 사용하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" ist keine gültige Hex-Farbe. Verwenden Sie #RRGGBB." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" no es un color hexadecimal válido. Usa #RRGGBB." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "« %@ » n'est pas une couleur hexadécimale valide. Utilisez le format #RRVVBB." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" non è un colore esadecimale valido. Usa #RRGGBB." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" er ikke en gyldig hex-farve. Brug #RRGGBB." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" nie jest prawidłowym kolorem szesnastkowym. Użyj #RRGGBB." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "«%@» не является допустимым цветом. Используйте формат #RRGGBB." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" nije važeća heksadecimalna boja. Koristite #RRGGBB." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" ليس لونًا سداسيًا صالحًا. استخدم #RRGGBB." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "«%@» er ikke en gyldig heksadesimal farge. Bruk #RRGGBB." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" não é uma cor hexadecimal válida. Use #RRGGBB." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" ไม่ใช่รหัสสี hex ที่ถูกต้อง ใช้ #RRGGBB" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" geçerli bir onaltılık renk değil. #RRGGBB kullanın." + } } } }, @@ -305,6 +1937,102 @@ "state": "translated", "value": "OK" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tamam" + } } } }, @@ -322,6 +2050,102 @@ "state": "translated", "value": "無効なカラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无效颜色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無效的顏色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "잘못된 색상" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ungültige Farbe" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Color no válido" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleur non valide" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Colore non valido" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ugyldig farve" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nieprawidłowy kolor" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Недопустимый цвет" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nevažeća boja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لون غير صالح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ugyldig farge" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cor Inválida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สีไม่ถูกต้อง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçersiz Renk" + } } } }, @@ -339,6 +2163,102 @@ "state": "translated", "value": "キャンセル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "취소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Annuler" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Annulla" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Annuller" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Anuluj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkaži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avbryt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Vazgeç" + } } } }, @@ -356,6 +2276,102 @@ "state": "translated", "value": "このワークスペースのカスタム名を入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请为此工作区输入自定义名称。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "為此工作區輸入自訂名稱。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 작업 공간의 사용자 지정 이름을 입력하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie einen benutzerdefinierten Namen für diesen Arbeitsbereich ein." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un nombre personalizado para este espacio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez un nom personnalisé pour cet espace de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un nome personalizzato per questa area di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast et brugerdefineret navn til dette arbejdsområde." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź własną nazwę dla tej przestrzeni roboczej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите пользовательское имя для этого рабочего пространства." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite prilagođeni naziv za ovaj radni prostor." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل اسمًا مخصصًا لمساحة العمل هذه." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn et egendefinert navn for dette arbeidsområdet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira um nome personalizado para esta área de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนชื่อที่กำหนดเองสำหรับเวิร์กสเปซนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu çalışma alanı için özel bir ad girin." + } } } }, @@ -373,6 +2389,102 @@ "state": "translated", "value": "ワークスペース名" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Name des Arbeitsbereichs" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nombre del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nom de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nome area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Navn på arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nazwa przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naziv radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اسم مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Navn på arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nome da área de trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma alanı adı" + } } } }, @@ -390,6 +2502,102 @@ "state": "translated", "value": "名称変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeniden Adlandır" + } } } }, @@ -407,6 +2615,102 @@ "state": "translated", "value": "ワークスペースの名称変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır" + } } } }, @@ -424,6 +2728,102 @@ "state": "translated", "value": "自動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自动" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatisch" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Automático" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Automatique" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Automatica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Automatisk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Automatyczna" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Автоматически" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Automatski" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تلقائي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Automatisk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Automático" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อัตโนมัติ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomatik" + } } } }, @@ -441,6 +2841,102 @@ "state": "translated", "value": "ダーク" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "深色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "深色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다크" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dunkel" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Oscuro" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Sombre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scura" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Mørk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ciemna" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Темная" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tamna" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "داكن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Mørk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escuro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มืด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Koyu" + } } } }, @@ -458,6 +2954,102 @@ "state": "translated", "value": "ライト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浅色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "淺色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "라이트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hell" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Claro" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Clair" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiara" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Lys" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Jasna" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлая" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Svijetla" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فاتح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lys" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Claro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สว่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Açık" + } } } }, @@ -475,6 +3067,102 @@ "state": "translated", "value": "自動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自动" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatisch" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Automático" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Auto" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Automatico" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Automatisk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Automatyczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Авто" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Automatski" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تلقائي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Auto" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Automático" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อัตโนมัติ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomatik" + } } } }, @@ -492,6 +3180,102 @@ "state": "translated", "value": "ダーク" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "深色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "深色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다크" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dunkel" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Oscuro" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Sombre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scuro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Mørk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ciemny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Темное" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tamna" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "داكن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Mørk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escuro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มืด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Koyu" + } } } }, @@ -509,6 +3293,102 @@ "state": "translated", "value": "ライト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浅色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "淺色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "라이트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hell" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Claro" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Clair" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiaro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Lys" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Jasny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлое" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Svijetla" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فاتح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lys" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Claro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สว่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Açık" + } } } }, @@ -526,6 +3406,102 @@ "state": "translated", "value": "システム" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "系统" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "系統" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "시스템" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Système" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Systemowy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системное" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sistemski" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النظام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ระบบ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sistem" + } } } }, @@ -543,6 +3519,102 @@ "state": "translated", "value": "新規タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "nueva pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "nouvel onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "nuova scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "ny fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "nowa karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "новая вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "novi tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان جديد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "ny fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "nova aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "yeni sekme" + } } } }, @@ -560,6 +3632,102 @@ "state": "translated", "value": "検索またはURLを入力" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "搜索或输入 URL" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "搜尋或輸入 URL" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "검색 또는 URL 입력" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen oder URL eingeben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar o introducir URL" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher ou saisir une URL" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cerca o inserisci un URL" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg eller indtast URL" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szukaj lub wprowadź URL" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск или ввод URL" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pretražite ili unesite URL" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ابحث أو أدخل URL" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Søk eller skriv inn URL" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pesquisar ou digitar URL" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหาหรือป้อน URL" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Arayın veya URL girin" + } } } }, @@ -577,6 +3745,102 @@ "state": "translated", "value": "アドレスバーの候補" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "地址栏建议" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "網址列建議" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "주소 표시줄 제안" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Adressleisten-Vorschläge" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sugerencias de la barra de direcciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Suggestions de la barre d'adresse" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Suggerimenti barra degli indirizzi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forslag til adresselinjen" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podpowiedzi paska adresu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подсказки адресной строки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prijedlozi adresne trake" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اقتراحات شريط العنوان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forslag i adressefeltet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sugestões da barra de endereço" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คำแนะนำแถบที่อยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Adres çubuğu önerileri" + } } } }, @@ -594,6 +3858,102 @@ "state": "translated", "value": "このホストを cmux で常に許可" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "始终允许此主机在 cmux 中打开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "一律允許此主機在 cmux 中開啟" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 이 호스트 항상 허용" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Diesen Host immer in cmux zulassen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Permitir siempre este host en cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Toujours autoriser cet hôte dans cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Consenti sempre questo host in cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tillad altid denne vært i cmux" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zawsze zezwalaj na ten host w cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Всегда разрешать этот хост в cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Uvijek dozvoli ovaj host u cmux" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السماح دائمًا لهذا المضيف في cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Alltid tillat denne verten i cmux" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sempre permitir este host no cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อนุญาตโฮสต์นี้ใน cmux เสมอ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu ana bilgisayarı cmux'ta her zaman izin ver" + } } } }, @@ -611,6 +3971,102 @@ "state": "translated", "value": "デフォルトブラウザでリンクを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在默认浏览器中打开链接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在預設瀏覽器中開啟連結" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본 브라우저에서 링크 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Link im Standardbrowser öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir enlace en el navegador predeterminado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le lien dans le navigateur par défaut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri link nel browser predefinito" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn link i standardbrowser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz odnośnik w domyślnej przeglądarce" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть ссылку в браузере по умолчанию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori link u podrazumijevanom pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الرابط في المتصفح الافتراضي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne lenke i standard nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Link no Navegador Padrão" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดลิงก์ในเบราว์เซอร์เริ่มต้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bağlantıyı Varsayılan Tarayıcıda Aç" + } } } }, @@ -628,6 +4084,102 @@ "state": "translated", "value": "新規タブでリンクを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在新标签页中打开链接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在新標籤頁中開啟連結" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 탭에서 링크 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Link in neuem Tab öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir enlace en una nueva pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le lien dans un nouvel onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri link in una nuova scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn link i ny fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz odnośnik w nowej karcie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть ссылку в новой вкладке" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori link u novom tabu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الرابط في لسان جديد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne lenke i ny fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Link em Nova Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดลิงก์ในแท็บใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bağlantıyı Yeni Sekmede Aç" + } } } }, @@ -645,6 +4197,102 @@ "state": "translated", "value": "このページの内容:" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "此页面显示:" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "此頁面顯示:" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 페이지의 메시지:" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Diese Seite meldet:" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esta página dice:" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cette page indique :" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa pagina dice:" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Denne side siger:" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ta strona mówi:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщение на странице:" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ova stranica kaže:" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقول هذه الصفحة:" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Denne siden sier:" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Esta página diz:" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้านี้แจ้งว่า:" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sayfa diyor ki:" + } } } }, @@ -662,6 +4310,102 @@ "state": "translated", "value": "ページ %@ のメッセージ:" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%@ 页面显示:" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%@ 頁面顯示:" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@ 페이지의 메시지:" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Seite auf %@ meldet:" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La página en %@ dice:" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La page à %@ indique :" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La pagina su %@ dice:" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Siden på %@ siger:" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Strona pod adresem %@ mówi:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Страница %@ сообщает:" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Stranica na %@ kaže:" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقول الصفحة في %@:" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Siden på %@ sier:" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A página em %@ diz:" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าที่ %@ แจ้งว่า:" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ sayfası diyor ki:" + } } } }, @@ -679,6 +4423,102 @@ "state": "translated", "value": "ダウンロード中" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下载进行中" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在下載" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다운로드 진행 중" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Download läuft" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descarga en curso" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Téléchargement en cours" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Download in corso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Download i gang" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobieranie w toku" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка выполняется" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzimanje u toku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التنزيل قيد التقدم" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nedlasting pågår" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Download em andamento" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังดาวน์โหลด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İndirme devam ediyor" + } } } }, @@ -696,6 +4536,102 @@ "state": "translated", "value": "ダウンロード中..." } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在下载..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下載中..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다운로드 중..." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird heruntergeladen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descargando..." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Téléchargement..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Download in corso..." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Downloader…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobieranie…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzimanje..." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التنزيل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Laster ned …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Baixando..." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังดาวน์โหลด..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İndiriliyor..." + } } } }, @@ -713,6 +4649,102 @@ "state": "translated", "value": "このページを開けません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法打开此页面" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法開啟此頁面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 페이지를 열 수 없습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Diese Seite kann nicht geöffnet werden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se puede abrir esta página" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible d'ouvrir cette page" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile aprire questa pagina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kan ikke åbne denne side" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie można otworzyć tej strony" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удается открыть эту страницу" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće otvoriti ovu stranicu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا يمكن فتح هذه الصفحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kan ikke åpne denne siden" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não foi possível abrir esta página" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถเปิดหน้านี้ได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sayfa açılamıyor" + } } } }, @@ -730,6 +4762,102 @@ "state": "translated", "value": "サイトに接続できませんでした。このアドレスでサーバーが実行されていることを確認してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "网站拒绝了连接。请检查此地址上是否有服务器正在运行。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "網站拒絕連線。請確認此位址上有伺服器正在運行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이트에서 연결을 거부했습니다. 이 주소에서 서버가 실행 중인지 확인하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Website hat die Verbindung verweigert. Überprüfen Sie, ob ein Server unter dieser Adresse läuft." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El sitio rechazó la conexión. Comprueba que haya un servidor en ejecución en esta dirección." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le site a refusé la connexion. Vérifiez qu'un serveur est bien en cours d'exécution à cette adresse." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Il sito ha rifiutato la connessione. Verifica che un server sia in esecuzione su questo indirizzo." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Siden nægtede forbindelsen. Kontroller at en server kører på denne adresse." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Serwer odmówił połączenia. Sprawdź, czy pod tym adresem działa serwer." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сайт отклонил подключение. Убедитесь, что сервер запущен по этому адресу." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Stranica je odbila vezu. Provjerite da li je server pokrenut na ovoj adresi." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "رفض الموقع الاتصال. تأكد من تشغيل خادم على هذا العنوان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettstedet nektet tilkobling. Kontroller at en server kjører på denne adressen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O site recusou a conexão. Verifique se há um servidor em execução neste endereço." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เว็บไซต์ปฏิเสธการเชื่อมต่อ ตรวจสอบว่ามีเซิร์ฟเวอร์ทำงานบนที่อยู่นี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Site bağlantıyı reddetti. Bu adreste bir sunucunun çalışıp çalışmadığını kontrol edin." + } } } }, @@ -747,6 +4875,102 @@ "state": "translated", "value": "%@ に接続できませんでした。このアドレスでサーバーが実行されていることを確認してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%@ 拒绝了连接。请检查此地址上是否有服务器正在运行。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%@ 拒絕連線。請確認此位址上有伺服器正在運行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@에서 연결을 거부했습니다. 이 주소에서 서버가 실행 중인지 확인하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ hat die Verbindung verweigert. Überprüfen Sie, ob ein Server unter dieser Adresse läuft." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%@ rechazó la conexión. Comprueba que haya un servidor en ejecución en esta dirección." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%@ a refusé la connexion. Vérifiez qu'un serveur est bien en cours d'exécution à cette adresse." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ ha rifiutato la connessione. Verifica che un server sia in esecuzione su questo indirizzo." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ nægtede forbindelsen. Kontroller at en server kører på denne adresse." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ odmówił połączenia. Sprawdź, czy pod tym adresem działa serwer." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ отклонил подключение. Убедитесь, что сервер запущен по этому адресу." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ je odbio vezu. Provjerite da li je server pokrenut na ovoj adresi." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "رفض %@ الاتصال. تأكد من تشغيل خادم على هذا العنوان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ nektet tilkobling. Kontroller at en server kjører på denne adressen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ recusou a conexão. Verifique se há um servidor em execução neste endereço." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ ปฏิเสธการเชื่อมต่อ ตรวจสอบว่ามีเซิร์ฟเวอร์ทำงานบนที่อยู่นี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ bağlantıyı reddetti. Bu adreste bir sunucunun çalışıp çalışmadığını kontrol edin." + } } } }, @@ -764,6 +4988,102 @@ "state": "translated", "value": "このページに到達できません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法访问此页面" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法連線至此頁面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 페이지에 연결할 수 없습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Diese Seite ist nicht erreichbar" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se puede acceder a esta página" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible d'atteindre cette page" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile raggiungere questa pagina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kan ikke nå denne side" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie można uzyskać dostępu do tej strony" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удается подключиться к странице" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće dosegnuti ovu stranicu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا يمكن الوصول إلى هذه الصفحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kan ikke nå denne siden" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não foi possível acessar esta página" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถเข้าถึงหน้านี้ได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sayfaya ulaşılamıyor" + } } } }, @@ -781,6 +5101,102 @@ "state": "translated", "value": "ネットワーク接続を確認してもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请检查您的网络连接,然后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請檢查您的網路連線後再試一次。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "네트워크 연결을 확인하고 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Comprueba tu conexión de red e inténtalo de nuevo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Vérifiez votre connexion réseau et réessayez." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Controlla la connessione di rete e riprova." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kontroller din netværksforbindelse, og prøv igen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sprawdź połączenie sieciowe i spróbuj ponownie." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверьте сетевое подключение и повторите попытку." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Provjerite mrežnu vezu i pokušajte ponovo." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحقق من اتصال الشبكة وحاول مجددًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kontroller nettverkstilkoblingen og prøv igjen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Verifique sua conexão de rede e tente novamente." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตรวจสอบการเชื่อมต่อเครือข่ายแล้วลองอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ağ bağlantınızı kontrol edip tekrar deneyin." + } } } }, @@ -798,6 +5214,102 @@ "state": "translated", "value": "フレームの読み込みが中断されました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "页面框架加载中断" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "頁框載入中斷" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "프레임 로딩이 중단되었습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Laden des Frames unterbrochen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Carga del marco interrumpida" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Chargement du cadre interrompu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Caricamento del frame interrotto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indlæsning af ramme blev afbrudt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ładowanie ramki zostało przerwane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка фрейма прервана" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Učitavanje okvira je prekinuto" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تم قطع تحميل الإطار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innlasting av ramme ble avbrutt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Carregamento do frame interrompido" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การโหลดเฟรมถูกขัดจังหวะ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çerçeve yüklemesi kesildi" + } } } }, @@ -815,6 +5327,102 @@ "state": "translated", "value": "%@ は HTTP 接続を使用しているため、通信内容がネットワーク上で読み取られたり改ざんされる可能性があります。\n\nデフォルトブラウザで開くか、cmux で続行してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%@ 使用纯 HTTP 连接,网络上的流量可能被读取或修改。\n\n请在默认浏览器中打开此 URL,或在 cmux 中继续访问。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%@ 使用未加密的 HTTP,網路上的流量可能被讀取或竄改。\n\n在您的預設瀏覽器中開啟此 URL,或在 cmux 中繼續。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@은(는) 일반 HTTP를 사용하므로, 네트워크에서 트래픽이 읽히거나 변조될 수 있습니다.\n\n기본 브라우저에서 이 URL을 열거나, cmux에서 계속 진행하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ verwendet unverschlüsseltes HTTP, daher kann der Datenverkehr im Netzwerk gelesen oder verändert werden.\n\nÖffnen Sie diese URL in Ihrem Standardbrowser oder fahren Sie in cmux fort." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%@ usa HTTP sin cifrar, por lo que el tráfico puede ser leído o modificado en la red.\n\nAbre esta URL en tu navegador predeterminado o continúa en cmux." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%@ utilise le protocole HTTP non chiffré, le trafic peut donc être lu ou modifié sur le réseau.\n\nOuvrez cette URL dans votre navigateur par défaut ou continuez dans cmux." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ utilizza HTTP non crittografato, quindi il traffico può essere letto o modificato sulla rete.\n\nApri questo URL nel browser predefinito oppure continua in cmux." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ bruger almindelig HTTP, så trafikken kan læses eller ændres på netværket.\n\nÅbn denne URL i din standardbrowser, eller fortsæt i cmux." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ używa zwykłego HTTP, więc ruch sieciowy może być odczytany lub zmodyfikowany.\n\nOtwórz ten URL w domyślnej przeglądarce lub kontynuuj w cmux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ использует незащищенный протокол HTTP, поэтому трафик может быть перехвачен или изменен в сети.\n\nОткройте этот URL в браузере по умолчанию или продолжите в cmux." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ koristi čisti HTTP, pa se promet može čitati ili mijenjati na mreži.\n\nOtvorite ovaj URL u podrazumijevanom pregledniku ili nastavite u cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يستخدم %@ بروتوكول HTTP غير مشفر، لذا يمكن قراءة حركة البيانات أو تعديلها على الشبكة.\n\nافتح هذا URL في متصفحك الافتراضي، أو تابع في cmux." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ bruker vanlig HTTP, så trafikk kan leses eller endres på nettverket.\n\nÅpne denne URL-en i standard nettleser, eller fortsett i cmux." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ usa HTTP simples, então o tráfego pode ser lido ou modificado na rede.\n\nAbra esta URL no seu navegador padrão ou continue no cmux." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ ใช้ HTTP แบบธรรมดา ดังนั้นข้อมูลอาจถูกอ่านหรือแก้ไขบนเครือข่ายได้\n\nเปิด URL นี้ในเบราว์เซอร์เริ่มต้น หรือดำเนินการต่อใน cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ düz HTTP kullanıyor, bu nedenle trafik ağda okunabilir veya değiştirilebilir.\n\nBu URL'yi varsayılan tarayıcınızda açın ya da cmux'ta devam edin." + } } } }, @@ -832,6 +5440,102 @@ "state": "translated", "value": "接続は安全ではありません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "连接不安全" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "連線不安全" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "연결이 안전하지 않습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung ist nicht sicher" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La conexión no es segura" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La connexion n'est pas sécurisée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La connessione non è sicura" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forbindelsen er ikke sikker" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Połączenie nie jest bezpieczne" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подключение не защищено" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Veza nije sigurna" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الاتصال غير آمن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilkoblingen er ikke sikker" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A conexão não é segura" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเชื่อมต่อไม่ปลอดภัย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bağlantı güvenli değil" + } } } }, @@ -849,6 +5553,102 @@ "state": "translated", "value": "このサイトの証明書が無効です。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "此网站的证书无效。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "此網站的憑證無效。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 사이트의 인증서가 유효하지 않습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Zertifikat für diese Website ist ungültig." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El certificado de este sitio no es válido." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le certificat de ce site n'est pas valide." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Il certificato per questo sito non è valido." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Certifikatet for denne side er ugyldigt." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Certyfikat tej strony jest nieprawidłowy." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сертификат этого сайта недействителен." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Certifikat za ovu stranicu je nevažeći." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "شهادة هذا الموقع غير صالحة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sertifikatet for dette nettstedet er ugyldig." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O certificado deste site é inválido." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใบรับรองสำหรับเว็บไซต์นี้ไม่ถูกต้อง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sitenin sertifikası geçersiz." + } } } }, @@ -866,6 +5666,102 @@ "state": "translated", "value": "インターネット接続がありません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无互联网连接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有網際網路連線" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "인터넷에 연결되어 있지 않습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Internetverbindung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sin conexión a internet" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune connexion Internet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna connessione a internet" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen internetforbindelse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak połączenia z internetem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет подключения к интернету" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nema internetske veze" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا يوجد اتصال بالإنترنت" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen internettforbindelse" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sem conexão com a internet" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มีการเชื่อมต่ออินเทอร์เน็ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İnternet bağlantısı yok" + } } } }, @@ -883,6 +5779,102 @@ "state": "translated", "value": "再読み込み" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새로고침" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neu laden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Recargar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recharger" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ricarica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genindlæs" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odśwież" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезагрузить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo učitaj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تحميل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Last inn på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Recarregar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหลดใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeniden Yükle" + } } } }, @@ -900,6 +5892,102 @@ "state": "translated", "value": "戻る" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "后退" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "뒤로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurück" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Retroceder" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Page précédente" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Indietro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå tilbage" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wstecz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Назад" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nazad" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "رجوع" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå tilbake" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Voltar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้อนกลับ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geri Git" + } } } }, @@ -917,6 +6005,102 @@ "state": "translated", "value": "進む" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "前进" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앞으로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vor" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Avanzar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Page suivante" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Avanti" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå frem" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Do przodu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вперед" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naprijed" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقدم" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå fremover" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Avançar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไปข้างหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İleri Git" + } } } }, @@ -934,6 +6118,102 @@ "state": "translated", "value": "URLに移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "前往 URL" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "前往 URL" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "URL로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "URL aufrufen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "ir a URL" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "accéder à l'URL" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "vai all'URL" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "gå til URL" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "przejdź do URL" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "перейти по URL" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "idi na URL" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انتقل إلى URL" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "gå til URL" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "ir para URL" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไปที่ URL" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "URL'ye git" + } } } }, @@ -951,6 +6231,102 @@ "state": "translated", "value": "新規タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ny fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новая вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان جديد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ny fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni sekme" + } } } }, @@ -968,6 +6344,102 @@ "state": "translated", "value": "デフォルトブラウザで開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在默认浏览器中打开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在預設瀏覽器中開啟" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본 브라우저에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Standardbrowser öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir en el navegador predeterminado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir dans le navigateur par défaut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri nel browser predefinito" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn i standardbrowser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz w domyślnej przeglądarce" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть в браузере по умолчанию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori u podrazumijevanom pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح في المتصفح الافتراضي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne i standard nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir no Navegador Padrão" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดในเบราว์เซอร์เริ่มต้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Varsayılan Tarayıcıda Aç" + } } } }, @@ -985,6 +6457,102 @@ "state": "translated", "value": "cmux で続行" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 中继续" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 中繼續" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 계속" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "In cmux fortfahren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Continuar en cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Continuer dans cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Continua in cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fortsæt i cmux" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kontynuuj w cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Продолжить в cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nastavi u cmux" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المتابعة في cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fortsett i cmux" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Continuar no cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ดำเนินการต่อใน cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux'ta devam et" + } } } }, @@ -1002,6 +6570,102 @@ "state": "translated", "value": "再読み込み" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새로고침" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neu laden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Recargar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recharger" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ricarica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genindlæs" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odśwież" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезагрузить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo učitaj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تحميل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Last inn på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Recarregar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหลดใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeniden Yükle" + } } } }, @@ -1019,6 +6683,102 @@ "state": "translated", "value": "検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "搜索" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "搜尋" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "검색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szukaj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pretraži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Søk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pesquisar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ara" + } } } }, @@ -1036,6 +6796,102 @@ "state": "translated", "value": "停止" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "停止" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "停止" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "중단" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Stopp" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Detener" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Arrêter" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Interrompi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Stop" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zatrzymaj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стоп" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zaustavi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إيقاف" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Stopp" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Parar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หยุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Durdur" + } } } }, @@ -1053,6 +6909,102 @@ "state": "translated", "value": "タブに切替" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换到标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換至標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭으로 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zum Tab wechseln" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cambiar a pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Basculer vers l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Passa alla scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skift til fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz na kartę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к вкладке" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci se na tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التبديل إلى اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bytt til fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar para aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับไปยังแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeye geç" + } } } }, @@ -1070,6 +7022,102 @@ "state": "translated", "value": "デベロッパツールを切替" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换开发者工具" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換開發者工具" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "개발자 도구 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Entwicklerwerkzeuge ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar herramientas de desarrollo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer les outils de développement" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva Strumenti sviluppatore" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå udviklerværktøjer til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz narzędzia deweloperskie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Инструменты разработчика" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci razvojne alate" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل أدوات المطور" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå utviklerverktøy av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Ferramentas do Desenvolvedor" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับเครื่องมือนักพัฒนา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geliştirici Araçlarını Aç/Kapat" + } } } }, @@ -1087,6 +7135,102 @@ "state": "translated", "value": "/usr/local/binへの書き込みに管理者権限が必要でした。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "写入 /usr/local/bin 需要管理员权限。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "寫入 /usr/local/bin 需要管理者權限。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/bin에 기록하기 위해 관리자 권한이 필요합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zum Schreiben in /usr/local/bin waren Administratorrechte erforderlich." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se requirieron privilegios de administrador para escribir en /usr/local/bin." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Des privilèges d'administrateur étaient nécessaires pour écrire dans /usr/local/bin." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sono stati richiesti i privilegi di amministratore per scrivere in /usr/local/bin." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Administratorrettigheder var nødvendige for at skrive til /usr/local/bin." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Do zapisu w /usr/local/bin wymagane były uprawnienia administratora." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для записи в /usr/local/bin потребовались права администратора." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Potrebne su administratorske privilegije za pisanje u /usr/local/bin." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "كانت صلاحيات المسؤول مطلوبة للكتابة في /usr/local/bin." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Administratorrettigheter var nødvendig for å skrive til /usr/local/bin." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Privilégios de administrador foram necessários para gravar em /usr/local/bin." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ต้องใช้สิทธิ์ผู้ดูแลระบบเพื่อเขียนไปยัง /usr/local/bin" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/bin dizinine yazmak için yönetici ayrıcalıkları gerekiyordu." + } } } }, @@ -1104,6 +7248,102 @@ "state": "translated", "value": "シンボリックリンクを作成しました:\n\n%1$@ -> %2$@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已创建符号链接:\n\n%1$@ -> %2$@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "已建立符號連結:\n\n%1$@ -> %2$@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "심볼릭 링크 생성 완료:\n\n%1$@ -> %2$@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Symbolische Verknüpfung erstellt:\n\n%1$@ -> %2$@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enlace simbólico creado:\n\n%1$@ -> %2$@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lien symbolique créé :\n\n%1$@ -> %2$@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Collegamento simbolico creato:\n\n%1$@ -> %2$@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Symbolsk link oprettet:\n%1$@ -> %2$@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Utworzono dowiązanie symboliczne:\n\n%1$@ -> %2$@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Создана символическая ссылка:\n\n%1$@ -> %2$@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kreirana simbolička veza:\n\n%1$@ -> %2$@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تم إنشاء رابط رمزي:\n\n%1$@ -> %2$@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Opprettet symbolsk lenke:\n\n%1$@ -> %2$@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Link simbólico criado:\n\n%1$@ -> %2$@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สร้างลิงก์สัญลักษณ์แล้ว:\n\n%1$@ -> %2$@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sembolik bağ oluşturuldu:\n\n%1$@ -> %2$@" + } } } }, @@ -1121,6 +7361,102 @@ "state": "translated", "value": "cmux CLI をインストールできませんでした" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法安装 cmux CLI" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法安裝 cmux CLI" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI를 설치할 수 없습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI konnte nicht installiert werden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo instalar la CLI de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible d'installer la CLI cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile installare la CLI di cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke installere cmux CLI" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się zainstalować CLI cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось установить cmux CLI" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće instalirati cmux CLI" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر تثبيت واجهة أوامر cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke installere cmux CLI" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não Foi Possível Instalar o CLI do cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถติดตั้ง cmux CLI ได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Yüklenemedi" + } } } }, @@ -1138,6 +7474,102 @@ "state": "translated", "value": "cmux CLI がインストールされました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI 已安装" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI 已安裝" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI 설치 완료" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI installiert" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "CLI de cmux instalada" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "CLI cmux installée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "CLI di cmux installata" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI installeret" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "CLI cmux zainstalowane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI установлен" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI instaliran" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تم تثبيت واجهة أوامر cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI installert" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "CLI do cmux Instalado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ติดตั้ง cmux CLI แล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Yüklendi" + } } } }, @@ -1155,6 +7587,102 @@ "state": "translated", "value": "/usr/local/binの変更に管理者権限が必要でした。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "修改 /usr/local/bin 需要管理员权限。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "修改 /usr/local/bin 需要管理者權限。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/bin을 수정하기 위해 관리자 권한이 필요합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zum Ändern von /usr/local/bin waren Administratorrechte erforderlich." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se requirieron privilegios de administrador para modificar /usr/local/bin." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Des privilèges d'administrateur étaient nécessaires pour modifier /usr/local/bin." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sono stati richiesti i privilegi di amministratore per modificare /usr/local/bin." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Administratorrettigheder var nødvendige for at ændre /usr/local/bin." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Do modyfikacji /usr/local/bin wymagane były uprawnienia administratora." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для изменения /usr/local/bin потребовались права администратора." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Potrebne su administratorske privilegije za izmjenu /usr/local/bin." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "كانت صلاحيات المسؤول مطلوبة لتعديل /usr/local/bin." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Administratorrettigheter var nødvendig for å endre /usr/local/bin." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Privilégios de administrador foram necessários para modificar /usr/local/bin." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ต้องใช้สิทธิ์ผู้ดูแลระบบเพื่อแก้ไข /usr/local/bin" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/bin dizinini değiştirmek için yönetici ayrıcalıkları gerekiyordu." + } } } }, @@ -1172,6 +7700,102 @@ "state": "translated", "value": "%@にcmux CLIのシンボリックリンクが見つかりませんでした。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 %@ 处未找到 cmux CLI 符号链接。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 %@ 找不到 cmux CLI 的符號連結。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@에서 cmux CLI 심볼릭 링크를 찾을 수 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Es wurde keine symbolische Verknüpfung für cmux CLI unter %@ gefunden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se encontró ningún enlace simbólico de la CLI de cmux en %@." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucun lien symbolique de la CLI cmux n'a été trouvé à l'emplacement %@." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessun collegamento simbolico della CLI di cmux trovato in %@." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Intet cmux CLI symbolsk link blev fundet på %@." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie znaleziono dowiązania symbolicznego CLI cmux pod adresem %@." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Символическая ссылка cmux CLI не найдена по пути %@." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Simbolička veza cmux CLI nije pronađena na %@." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لم يتم العثور على رابط رمزي لواجهة أوامر cmux في %@." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen cmux CLI-symbolsk lenke ble funnet på %@." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhum link simbólico do CLI do cmux foi encontrado em %@." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่พบลิงก์สัญลักษณ์ cmux CLI ที่ %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ konumunda cmux CLI sembolik bağı bulunamadı." + } } } }, @@ -1189,6 +7813,102 @@ "state": "translated", "value": "%@を削除しました。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已移除 %@。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "已移除 %@。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@을(를) 제거했습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ wurde entfernt." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se eliminó %@." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%@ a été supprimé." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rimosso %@." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fjernede %@." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Usunięto %@." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалено: %@." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Uklonjeno %@." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تمت إزالة %@." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjernet %@." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ removido." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ลบ %@ แล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ kaldırıldı." + } } } }, @@ -1206,6 +7926,102 @@ "state": "translated", "value": "cmux CLI をアンインストールできませんでした" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法卸载 cmux CLI" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法解除安裝 cmux CLI" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI를 제거할 수 없습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI konnte nicht deinstalliert werden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo desinstalar la CLI de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible de désinstaller la CLI cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile disinstallare la CLI di cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke afinstallere cmux CLI" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się odinstalować CLI cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось удалить cmux CLI" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće deinstalirati cmux CLI" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر إلغاء تثبيت واجهة أوامر cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke avinstallere cmux CLI" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não Foi Possível Desinstalar o CLI do cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถถอนการติดตั้ง cmux CLI ได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Kaldırılamadı" + } } } }, @@ -1223,6 +8039,102 @@ "state": "translated", "value": "cmux CLI がアンインストールされました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI 已卸载" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI 已解除安裝" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI 제거 완료" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI deinstalliert" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "CLI de cmux desinstalada" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "CLI cmux désinstallée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "CLI di cmux disinstallata" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI afinstalleret" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "CLI cmux odinstalowane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI удален" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI deinstaliran" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تم إلغاء تثبيت واجهة أوامر cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI avinstallert" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "CLI do cmux Desinstalado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ถอนการติดตั้ง cmux CLI แล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Kaldırıldı" + } } } }, @@ -1240,6 +8152,102 @@ "state": "translated", "value": "グローバル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全域" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전역" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Général" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Globale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Globalne" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Глобальные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Globalno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทั่วไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Genel" + } } } }, @@ -1257,6 +8265,102 @@ "state": "translated", "value": "アップデートを適用(利用可能な場合)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "应用更新(如果可用)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "套用更新(如果有的話)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 적용 (사용 가능한 경우)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update anwenden (falls verfügbar)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Aplicar actualización (si está disponible)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Appliquer la mise à jour (si disponible)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Applica aggiornamento (se disponibile)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Anvend opdatering (hvis tilgængelig)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zastosuj aktualizację (jeśli dostępna)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Применить обновление (если доступно)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Primijeni ažuriranje (ako je dostupno)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تطبيق التحديث (إن توفر)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Installer oppdatering (hvis tilgjengelig)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aplicar Atualização (Se Disponível)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ติดตั้งอัปเดต (ถ้ามี)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeyi Uygula (Varsa)" + } } } }, @@ -1274,6 +8378,102 @@ "state": "translated", "value": "グローバル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全域" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전역" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Général" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Globale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Globalne" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Глобальные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Globalno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทั่วไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Genel" + } } } }, @@ -1291,6 +8491,102 @@ "state": "translated", "value": "アップデートを試行" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "尝试更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "嘗試更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 시도" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update versuchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Intentar actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Tenter la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Tenta aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forsøg opdatering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spróbuj zaktualizować" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попытаться обновить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pokušaj ažuriranje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "محاولة التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forsøk oppdatering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tentar Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ลองอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeyi Dene" + } } } }, @@ -1308,6 +8604,102 @@ "state": "translated", "value": "戻る" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "后退" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "뒤로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurück" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Atrás" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Indietro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tilbage" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wstecz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Назад" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nazad" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "رجوع" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilbake" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Voltar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้อนกลับ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geri" + } } } }, @@ -1325,6 +8717,102 @@ "state": "translated", "value": "ブラウザ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przeglądarka" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preglednik" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı" + } } } }, @@ -1342,6 +8830,102 @@ "state": "translated", "value": "ブラウザ履歴をクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除浏览器历史记录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除瀏覽記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 기록 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browserverlauf löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar historial del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer l'historique du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella cronologia browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd browserhistorik" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść historię przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить историю браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši historiju preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح سجل المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tøm nettleserhistorikk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Histórico do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างประวัติเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Geçmişini Temizle" + } } } }, @@ -1359,6 +8943,102 @@ "state": "translated", "value": "JavaScriptコンソールを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示 JavaScript 控制台" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示 JavaScript 主控台" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "JavaScript 콘솔 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "JavaScript-Konsole anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar consola de JavaScript" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la console JavaScript" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra console JavaScript" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis JavaScript-konsol" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż konsolę JavaScript" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать консоль JavaScript" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži JavaScript konzolu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض وحدة تحكم JavaScript" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis JavaScript-konsoll" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Console JavaScript" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงคอนโซล JavaScript" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "JavaScript Konsolunu Göster" + } } } }, @@ -1376,6 +9056,102 @@ "state": "translated", "value": "ブラウザレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browserlayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleseroppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Düzeni" + } } } }, @@ -1393,6 +9169,102 @@ "state": "translated", "value": "ブラウザを右に複製" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右复制浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將瀏覽器複製到右側" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 오른쪽으로 복제" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach rechts duplizieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Duplicar navegador a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Dupliquer le navigateur à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Duplica browser a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dupliker browser til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Duplikuj przeglądarkę na prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дублировать браузер вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dupliciraj preglednik desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نسخ المتصفح إلى اليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dupliser nettleser til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Duplicar Navegador à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำซ้ำเบราว์เซอร์ไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Sağa Çoğalt" + } } } }, @@ -1410,6 +9282,102 @@ "state": "translated", "value": "アドレスバーにフォーカス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "聚焦地址栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "聚焦網址列" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "주소 표시줄 포커스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Adressleiste fokussieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enfocar barra de direcciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer la barre d'adresse" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Porta il focus sulla barra degli indirizzi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fokuser adresselinjen" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ustaw fokus na pasku adresu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к адресной строке" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Fokusiraj adresnu traku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التركيز على شريط العنوان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fokuser adressefeltet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Focar na Barra de Endereço" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฟกัสแถบที่อยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Adres Çubuğuna Odaklan" + } } } }, @@ -1427,6 +9395,102 @@ "state": "translated", "value": "進む" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "前进" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앞으로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vor" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Adelante" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Avanti" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Frem" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Do przodu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вперед" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naprijed" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقدم" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fremover" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Avançar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไปข้างหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İleri" + } } } }, @@ -1444,6 +9508,102 @@ "state": "translated", "value": "現在のページをデフォルトブラウザで開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在默认浏览器中打开当前页面" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在預設瀏覽器中開啟目前頁面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 페이지를 기본 브라우저에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelle Seite im Standardbrowser öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir página actual en el navegador predeterminado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir la page dans le navigateur par défaut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la pagina corrente nel browser predefinito" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn aktuel side i standardbrowser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżącą stronę w domyślnej przeglądarce" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущую страницу в браузере по умолчанию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutnu stranicu u podrazumijevanom pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الصفحة الحالية في المتصفح الافتراضي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende side i standard nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Página Atual no Navegador Padrão" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดหน้าปัจจุบันในเบราว์เซอร์เริ่มต้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Sayfayı Varsayılan Tarayıcıda Aç" + } } } }, @@ -1461,6 +9621,102 @@ "state": "translated", "value": "ページを再読み込み" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载页面" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入頁面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "페이지 새로고침" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seite neu laden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Recargar página" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recharger la page" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ricarica pagina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genindlæs side" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odśwież stronę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезагрузить страницу" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo učitaj stranicu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تحميل الصفحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Last inn siden på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Recarregar Página" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหลดหน้าใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sayfayı Yeniden Yükle" + } } } }, @@ -1478,6 +9734,102 @@ "state": "translated", "value": "ブラウザレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browserlayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleseroppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Düzeni" + } } } }, @@ -1495,6 +9847,102 @@ "state": "translated", "value": "ブラウザを下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Aşağı Böl" + } } } }, @@ -1512,6 +9960,102 @@ "state": "translated", "value": "ブラウザレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browserlayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleseroppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Düzeni" + } } } }, @@ -1529,6 +10073,102 @@ "state": "translated", "value": "ブラウザを右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Sağa Böl" + } } } }, @@ -1546,6 +10186,102 @@ "state": "translated", "value": "デベロッパツールの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换开发者工具" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換開發者工具" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "개발자 도구 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Entwicklerwerkzeuge ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar herramientas de desarrollo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer les outils de développement" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva Strumenti sviluppatore" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå udviklerværktøjer til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz narzędzia deweloperskie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Инструменты разработчика" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci razvojne alate" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل أدوات المطور" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå utviklerverktøy av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Ferramentas do Desenvolvedor" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับเครื่องมือนักพัฒนา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geliştirici Araçlarını Aç/Kapat" + } } } }, @@ -1563,6 +10299,102 @@ "state": "translated", "value": "拡大" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "放大" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "放大" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "확대" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einzoomen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ampliar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Zoom avant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ingrandisci" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Zoom ind" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiększ" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Увеличить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Uvećaj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تكبير" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Zoom inn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aumentar Zoom" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ซูมเข้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yakınlaştır" + } } } }, @@ -1580,6 +10412,102 @@ "state": "translated", "value": "縮小" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "缩小" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "縮小" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "축소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Auszoomen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reducir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Zoom arrière" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riduci" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Zoom ud" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pomniejsz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уменьшить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Umanji" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تصغير" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Zoom ut" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Diminuir Zoom" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ซูมออก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uzaklaştır" + } } } }, @@ -1597,6 +10525,102 @@ "state": "translated", "value": "実際のサイズ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "实际大小" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "實際大小" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "실제 크기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Originalgröße" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tamaño real" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Taille réelle" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dimensione reale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Faktisk størrelse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Rozmiar rzeczywisty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фактический размер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Stvarna veličina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الحجم الفعلي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Faktisk størrelse" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tamanho Real" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ขนาดจริง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Gerçek Boyut" + } } } }, @@ -1614,6 +10638,102 @@ "state": "translated", "value": "グローバル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全域" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전역" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Général" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Globale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Globalne" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Глобальные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Globalno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทั่วไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Genel" + } } } }, @@ -1631,6 +10751,102 @@ "state": "translated", "value": "アップデートを確認" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "检查更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "檢查更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 확인" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach Updates suchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar actualizaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher des mises à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verifica aggiornamenti" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg efter opdateringer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sprawdź aktualizacje" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Provjeri ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التحقق من التحديثات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Se etter oppdateringer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Atualizações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตรวจหาอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeleri Denetle" + } } } }, @@ -1648,6 +10864,102 @@ "state": "translated", "value": "タブ名をクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除标签页名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除標籤頁名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab-Name löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar nombre de pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer le nom de l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella nome scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd fanenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść nazwę karty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить имя вкладки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši naziv taba" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح اسم اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern fanenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Nome da Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างชื่อแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme Adını Temizle" + } } } }, @@ -1665,6 +10977,102 @@ "state": "translated", "value": "ワークスペース名をクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除工作区名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除工作區名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereichsname löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar nombre del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer le nom de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella nome area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd arbejdsområdenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść nazwę przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить имя рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši naziv radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح اسم مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern arbeidsområdenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Nome da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Adını Temizle" + } } } }, @@ -1682,6 +11090,102 @@ "state": "translated", "value": "タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme" + } } } }, @@ -1699,6 +11203,102 @@ "state": "translated", "value": "タブを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij kartę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Kapat" + } } } }, @@ -1716,6 +11316,102 @@ "state": "translated", "value": "ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere" + } } } }, @@ -1733,6 +11429,102 @@ "state": "translated", "value": "ウインドウを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencereyi Kapat" + } } } }, @@ -1750,6 +11542,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -1767,6 +11655,102 @@ "state": "translated", "value": "ワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Kapat" + } } } }, @@ -1784,6 +11768,102 @@ "state": "translated", "value": "分割を均等にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "均分面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "均等分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "분할 균등화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Teilungen angleichen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Igualar divisiones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Égaliser les divisions" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Equalizza divisioni" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Udlign opdelinger" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyrównaj podziały" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выровнять разделение" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Izjednači podjele" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تسوية التقسيمات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gjør delinger like" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Equalizar Divisões" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปรับขนาดช่องแยกให้เท่ากัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bölmeleri Eşitle" + } } } }, @@ -1801,6 +11881,102 @@ "state": "translated", "value": "CLI" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "واجهة الأوامر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } } } }, @@ -1818,6 +11994,102 @@ "state": "translated", "value": "シェルコマンド: 'cmux'をPATHにインストール" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Shell 命令:将 'cmux' 安装到 PATH" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Shell 指令:將「cmux」安裝至 PATH" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "셸 명령어: PATH에 'cmux' 설치" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Shell-Befehl: 'cmux' im PATH installieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Comando de shell: Instalar 'cmux' en PATH" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Commande Shell : installer « cmux » dans le PATH" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Comando shell: installa 'cmux' nel PATH" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skalkommando: Installer 'cmux' i PATH" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Polecenie powłoki: Zainstaluj „cmux” w PATH" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команда оболочки: установить «cmux» в PATH" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Shell naredba: Instaliraj 'cmux' u PATH" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أمر الصدفة: تثبيت 'cmux' في PATH" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skallkommando: Installer «cmux» i PATH" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Comando Shell: Instalar 'cmux' no PATH" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คำสั่ง Shell: ติดตั้ง 'cmux' ใน PATH" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kabuk Komutu: 'cmux'u PATH'e Yükle" + } } } }, @@ -1835,6 +12107,102 @@ "state": "translated", "value": "通知" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimler" + } } } }, @@ -1852,6 +12220,102 @@ "state": "translated", "value": "最新の未読にジャンプ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳转到最新未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跳至最新未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 읽지 않은 항목으로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur letzten ungelesenen springen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir a la última no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller au dernier message non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'ultimo non letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til seneste ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do najnowszego nieprzeczytanego" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к последнему непрочитанному" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Skoči na najnovije nepročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى أحدث غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til siste uleste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Última Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้ามไปยังรายการยังไม่อ่านล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son Okunmamışa Atla" + } } } }, @@ -1869,6 +12333,102 @@ "state": "translated", "value": "タブを既読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将标签页标记为已读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將標籤頁標為已讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭을 읽음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab als gelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar pestaña como leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Marquer l'onglet comme lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna scheda come letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker fane som læst" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz kartę jako przeczytaną" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить вкладку как прочитанную" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi tab kao pročitan" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم اللسان كمقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk fane som lest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Aba como Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายแท็บว่าอ่านแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Okundu Olarak İşaretle" + } } } }, @@ -1886,6 +12446,102 @@ "state": "translated", "value": "タブを未読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将标签页标记为未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將標籤頁標為未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭을 읽지 않음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab als ungelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar pestaña como no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Marquer l'onglet comme non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna scheda come non letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker fane som ulæst" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz kartę jako nieprzeczytaną" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить вкладку как непрочитанную" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi tab kao nepročitan" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم اللسان كغير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk fane som ulest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Aba como Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายแท็บว่ายังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Okunmadı Olarak İşaretle" + } } } }, @@ -1903,6 +12559,102 @@ "state": "translated", "value": "タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme" + } } } }, @@ -1920,6 +12672,102 @@ "state": "translated", "value": "新規タブ(ブラウザ)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建标签页(浏览器)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增標籤頁(瀏覽器)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 탭 (브라우저)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Tab (Browser)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva pestaña (Navegador)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel onglet (navigateur)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova scheda (browser)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ny fane (browser)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa karta (Przeglądarka)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новая вкладка (Браузер)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi tab (Preglednik)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان جديد (متصفح)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ny fane (nettleser)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Aba (Navegador)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บใหม่ (เบราว์เซอร์)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Sekme (Tarayıcı)" + } } } }, @@ -1937,6 +12785,102 @@ "state": "translated", "value": "タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme" + } } } }, @@ -1954,6 +12898,102 @@ "state": "translated", "value": "新規タブ(ターミナル)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建标签页(终端)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增標籤頁(終端機)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 탭 (터미널)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Tab (Terminal)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva pestaña (Terminal)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel onglet (terminal)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova scheda (terminale)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ny fane (terminal)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa karta (Terminal)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новая вкладка (Терминал)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi tab (Terminal)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان جديد (طرفية)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ny fane (terminal)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Aba (Terminal)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บใหม่ (เทอร์มินัล)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Sekme (Terminal)" + } } } }, @@ -1971,6 +13011,102 @@ "state": "translated", "value": "ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere" + } } } }, @@ -1988,6 +13124,102 @@ "state": "translated", "value": "新規ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neues Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvelle fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowe okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่างใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Pencere" + } } } }, @@ -2005,6 +13237,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -2022,6 +13350,102 @@ "state": "translated", "value": "新規ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Çalışma Alanı" + } } } }, @@ -2039,6 +13463,102 @@ "state": "translated", "value": "タブナビゲーション" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页导航" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁導覽" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 탐색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab-Navigation" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegación de pestañas" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigation par onglets" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Navigazione schede" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fanenavigation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nawigacja po kartach" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Навигация по вкладкам" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Navigacija tabovima" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التنقل بين الألسنة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fanenavigering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegação de Abas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การนำทางแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme Gezinme" + } } } }, @@ -2056,6 +13576,102 @@ "state": "translated", "value": "ペイン内の次のタブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个面板标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "面板中的下一個標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패널 내 다음 탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Tab im Bereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente pestaña en el panel" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet suivant dans le panneau" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda successiva nel pannello" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste fane i panel" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następna karta w panelu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующая вкладка в панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeći tab u panelu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اللسان التالي في اللوحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste fane i panelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próxima Aba no Painel" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บถัดไปในบานหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bölmedeki Sonraki Sekme" + } } } }, @@ -2073,6 +13689,102 @@ "state": "translated", "value": "ワークスペースナビゲーション" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区导航" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區導覽" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 탐색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich-Navigation" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegación de espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigation par espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Navigazione aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdenavigation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nawigacja po przestrzeniach roboczych" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Навигация по рабочим пространствам" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Navigacija radnim prostorima" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التنقل بين مساحات العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdenavigering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegação de Áreas de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การนำทางเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Gezinme" + } } } }, @@ -2090,6 +13802,102 @@ "state": "translated", "value": "次のワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一個工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro successiva" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następna przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующее рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeći radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل التالية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próxima Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonraki Çalışma Alanı" + } } } }, @@ -2107,6 +13915,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -2124,6 +14028,102 @@ "state": "translated", "value": "フォルダを開く…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开文件夹..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟資料夾..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "폴더 열기…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ordner öffnen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir carpeta…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir un dossier..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri cartella…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn mappe…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz folder…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть папку..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori folder…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح مجلد…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne mappe …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Pasta…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดโฟลเดอร์..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Klasör Aç…" + } } } }, @@ -2141,6 +14141,102 @@ "state": "translated", "value": "グローバル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全域" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전역" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Général" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Globale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Globalne" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Глобальные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Globalno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทั่วไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Genel" + } } } }, @@ -2158,6 +14254,102 @@ "state": "translated", "value": "設定を開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开设置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟設定" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설정 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir ajustes" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir les réglages" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri impostazioni" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn indstillinger" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz ustawienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть настройки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori postavke" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الإعدادات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne innstillinger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Ajustes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดการตั้งค่า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayarları Aç" + } } } }, @@ -2175,6 +14367,102 @@ "state": "translated", "value": "ワークスペースのPRリンクをすべて開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开工作区所有 PR 链接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟所有工作區 PR 連結" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 PR 링크 모두 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle PR-Links des Arbeitsbereichs öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir todos los enlaces de PR del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir tous les liens PR de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri tutti i link PR dell'area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn alle PR-links for arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz wszystkie linki PR przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть все ссылки PR рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori sve PR linkove radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح جميع روابط طلبات السحب في مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne alle PR-lenker for arbeidsområdet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Todos os Links de PR da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดลิงก์ PR ทั้งหมดของเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tüm Çalışma Alanı PR Bağlantılarını Aç" + } } } }, @@ -2192,6 +14480,102 @@ "state": "translated", "value": "タブをピンで固定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "固定标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "釘選標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 고정" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab anheften" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fijar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Épingler l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Fissa scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fastgør fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przypnij kartę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрепить вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zakači tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تثبيت اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fest fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fixar Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปักหมุดแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Sabitle" + } } } }, @@ -2209,6 +14593,102 @@ "state": "translated", "value": "ワークスペースをピンで固定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "固定工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "釘選工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 고정" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich anheften" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fijar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Épingler l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Fissa area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fastgør arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przypnij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрепить рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zakači radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تثبيت مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fest arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fixar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปักหมุดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Sabitle" + } } } }, @@ -2226,6 +14706,102 @@ "state": "translated", "value": "タブナビゲーション" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页导航" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁導覽" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 탐색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab-Navigation" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegación de pestañas" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigation par onglets" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Navigazione schede" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fanenavigation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nawigacja po kartach" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Навигация по вкладкам" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Navigacija tabovima" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التنقل بين الألسنة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fanenavigering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegação de Abas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การนำทางแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme Gezinme" + } } } }, @@ -2243,6 +14819,102 @@ "state": "translated", "value": "ペイン内の前のタブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个面板标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "面板中的上一個標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패널 내 이전 탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriger Tab im Bereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña anterior en el panel" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet précédent dans le panneau" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda precedente nel pannello" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige fane i panel" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednia karta w panelu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущая вкладка в панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodni tab u panelu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اللسان السابق في اللوحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige fane i panelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba Anterior no Painel" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บก่อนหน้าในบานหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bölmedeki Önceki Sekme" + } } } }, @@ -2260,6 +14932,102 @@ "state": "translated", "value": "ワークスペースナビゲーション" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区导航" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區導覽" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 탐색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich-Navigation" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegación de espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigation par espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Navigazione aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdenavigation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nawigacja po przestrzeniach roboczych" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Навигация по рабочим пространствам" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Navigacija radnim prostorima" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التنقل بين مساحات العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdenavigering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegação de Áreas de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การนำทางเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Gezinme" + } } } }, @@ -2277,6 +15045,102 @@ "state": "translated", "value": "前のワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一個工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriger Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednia przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущее рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodni radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل السابقة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önceki Çalışma Alanı" + } } } }, @@ -2294,6 +15158,102 @@ "state": "translated", "value": "タブの名称変更…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名标签页..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名標籤頁..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름 변경…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab umbenennen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar pestaña…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'onglet..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina scheda…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb fane…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę karty…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать вкладку..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj tab…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية اللسان…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi fanen nytt navn …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Aba…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อแท็บ..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Yeniden Adlandır…" + } } } }, @@ -2311,6 +15271,102 @@ "state": "translated", "value": "ワークスペースの名称変更…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır…" + } } } }, @@ -2328,6 +15384,102 @@ "state": "translated", "value": "ブラウザ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przeglądarka" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preglednik" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı" + } } } }, @@ -2345,6 +15497,102 @@ "state": "translated", "value": "閉じたブラウザタブを再度開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新打开已关闭的浏览器标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新開啟已關閉的瀏覽器標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "닫은 브라우저 탭 다시 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geschlossenen Browser-Tab erneut öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reabrir pestaña del navegador cerrada" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rouvrir l'onglet de navigateur fermé" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riapri scheda browser chiusa" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genåbn lukket browserfane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz ponownie zamkniętą kartę przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть закрытую вкладку браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo otvori zatvoreni tab preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة فتح لسان المتصفح المغلق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne lukket nettleserfane på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reabrir Aba do Navegador Fechada" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดแท็บเบราว์เซอร์ที่ปิดไปอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapatılan Tarayıcı Sekmesini Yeniden Aç" + } } } }, @@ -2362,6 +15610,102 @@ "state": "translated", "value": "グローバル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全域" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전역" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Général" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Globale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Globalne" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Глобальные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Globalno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Globalt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทั่วไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Genel" + } } } }, @@ -2379,6 +15723,102 @@ "state": "translated", "value": "CLIリスナーを再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重启 CLI 监听器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新啟動 CLI 監聽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "CLI 리스너 재시작" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "CLI-Listener neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar receptor de CLI" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer l'écouteur CLI" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia listener CLI" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart CLI-lytter" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie nasłuchiwanie CLI" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить прослушиватель CLI" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokreni CLI osluškivač" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تشغيل مستمع واجهة الأوامر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start CLI-lytteren på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar Listener do CLI" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตตัวรับฟัง CLI" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "CLI Dinleyicisini Yeniden Başlat" + } } } }, @@ -2396,6 +15836,102 @@ "state": "translated", "value": "通知" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimler" + } } } }, @@ -2413,6 +15949,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri Göster" + } } } }, @@ -2430,6 +16062,102 @@ "state": "translated", "value": "検索…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "찾기…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найти..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بحث…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหา..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bul…" + } } } }, @@ -2447,6 +16175,102 @@ "state": "translated", "value": "次を検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找下一个" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找下一個" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 찾기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Weitersuchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar siguiente" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Résultat suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova successivo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Find næste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź następny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найти далее" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi sljedeće" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "البحث عن التالي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn neste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Próximo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหาถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonrakini Bul" + } } } }, @@ -2464,6 +16288,102 @@ "state": "translated", "value": "前を検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找上一个" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找上一個" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 찾기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriges suchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Résultat précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Find forrige" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź poprzedni" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найти ранее" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi prethodno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "البحث عن السابق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn forrige" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหาก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Öncekini Bul" + } } } }, @@ -2481,6 +16401,102 @@ "state": "translated", "value": "検索バーを非表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "隐藏查找栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "隱藏尋找列" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "찾기 막대 숨기기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchleiste ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ocultar barra de búsqueda" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Masquer la barre de recherche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nascondi barra di ricerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skjul søgelinje" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ukryj pasek wyszukiwania" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть панель поиска" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sakrij traku za pretragu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إخفاء شريط البحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skjul søkelinje" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ocultar Barra de Busca" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ซ่อนแถบค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Arama Çubuğunu Gizle" + } } } }, @@ -2498,6 +16514,102 @@ "state": "translated", "value": "ターミナルレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminallayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ terminala" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет терминала" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored terminala" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminaloppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Düzeni" + } } } }, @@ -2515,6 +16627,102 @@ "state": "translated", "value": "ブラウザを下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Aşağı Böl" + } } } }, @@ -2532,6 +16740,102 @@ "state": "translated", "value": "ターミナルレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminallayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ terminala" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет терминала" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored terminala" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminaloppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Düzeni" + } } } }, @@ -2549,6 +16853,102 @@ "state": "translated", "value": "ブラウザを右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Sağa Böl" + } } } }, @@ -2566,6 +16966,102 @@ "state": "translated", "value": "ターミナルレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminallayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ terminala" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет терминала" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored terminala" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminaloppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Düzeni" + } } } }, @@ -2583,6 +17079,102 @@ "state": "translated", "value": "下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağı Böl" + } } } }, @@ -2600,6 +17192,102 @@ "state": "translated", "value": "ターミナルレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminallayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ terminala" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет терминала" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored terminala" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminaloppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Düzeni" + } } } }, @@ -2617,6 +17305,102 @@ "state": "translated", "value": "右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sağa Böl" + } } } }, @@ -2634,6 +17418,102 @@ "state": "translated", "value": "選択範囲を検索に使用" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用选中内容查找" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用所選範圍來尋找" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "선택 항목을 찾기에 사용" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Auswahl für Suche verwenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usar selección para buscar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utiliser la sélection pour la recherche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Usa selezione per la ricerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Brug markering til søgning" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Użyj zaznaczenia do wyszukiwania" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Использовать выделение для поиска" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Koristi odabrano za pretragu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استخدام التحديد للبحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bruk utvalg for søk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Usar Seleção para Busca" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใช้ข้อความที่เลือกเพื่อค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Seçimi Bulmak İçin Kullan" + } } } }, @@ -2651,6 +17531,102 @@ "state": "translated", "value": "ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere" + } } } }, @@ -2668,6 +17644,102 @@ "state": "translated", "value": "フルスクリーンの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换全屏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換全螢幕" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전체 화면 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollbild umschalten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar pantalla completa" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer/désactiver le plein écran" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva schermo intero" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skift fuldskærm" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz pełny ekran" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Полноэкранный режим" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci puni ekran" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل ملء الشاشة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå fullskjerm av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Tela Cheia" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับเต็มหน้าจอ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tam Ekranı Aç/Kapat" + } } } }, @@ -2685,6 +17757,102 @@ "state": "translated", "value": "レイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Layout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التخطيط" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Düzen" + } } } }, @@ -2702,6 +17870,102 @@ "state": "translated", "value": "サイドバーの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå sidebjælke til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Боковая панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci bočnu traku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå sidepanelet av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunu Aç/Kapat" + } } } }, @@ -2719,6 +17983,102 @@ "state": "translated", "value": "ターミナルレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición del terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition du terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminallayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ terminala" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет терминала" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored terminala" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminaloppsett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout do Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Düzeni" + } } } }, @@ -2736,6 +18096,102 @@ "state": "translated", "value": "ペインズームの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换面板缩放" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換面板縮放" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패널 확대/축소 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bereichszoom umschalten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar zoom del panel" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer/désactiver le zoom du panneau" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva zoom pannello" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå panelzoom til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz powiększenie panelu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Масштаб панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci uvećanje panela" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل تكبير اللوحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå panelzoom av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Zoom do Painel" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับการซูมบานหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bölme Yakınlaştırmasını Aç/Kapat" + } } } }, @@ -2753,6 +18209,102 @@ "state": "translated", "value": "表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "视图" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示方式" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "보기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ansicht" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Vista" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Présentation" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vista" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Visning" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Widok" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вид" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaz" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "العرض" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Visualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มุมมอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Görünüm" + } } } }, @@ -2770,6 +18322,102 @@ "state": "translated", "value": "フォーカスペインを強調" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "闪烁聚焦面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "閃爍聚焦面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "포커스된 패널 깜빡이기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fokussierten Bereich hervorheben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Resaltar panel enfocado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Flasher le panneau actif" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Evidenzia pannello attivo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fremhæv fokuseret panel" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podświetl aktywny panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подсветить активную панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi fokusirani panel" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض اللوحة المركّزة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Blink fokusert panel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Piscar Painel em Foco" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กะพริบแผงที่โฟกัส" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Odaklanılan Paneli Yanıp Söndür" + } } } }, @@ -2787,6 +18435,102 @@ "state": "translated", "value": "CLI" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "واجهة الأوامر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } } } }, @@ -2804,6 +18548,102 @@ "state": "translated", "value": "シェルコマンド: 'cmux'をPATHからアンインストール" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Shell 命令:从 PATH 卸载 'cmux'" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Shell 指令:從 PATH 解除安裝「cmux」" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "셸 명령어: PATH에서 'cmux' 제거" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Shell-Befehl: 'cmux' aus PATH entfernen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Comando de shell: Desinstalar 'cmux' de PATH" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Commande Shell : désinstaller « cmux » du PATH" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Comando shell: disinstalla 'cmux' dal PATH" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skalkommando: Afinstaller 'cmux' fra PATH" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Polecenie powłoki: Odinstaluj „cmux” z PATH" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команда оболочки: удалить «cmux» из PATH" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Shell naredba: Deinstaliraj 'cmux' iz PATH" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أمر الصدفة: إلغاء تثبيت 'cmux' من PATH" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skallkommando: Avinstaller «cmux» fra PATH" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Comando Shell: Desinstalar 'cmux' do PATH" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คำสั่ง Shell: ถอนการติดตั้ง 'cmux' จาก PATH" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kabuk Komutu: 'cmux'u PATH'ten Kaldır" + } } } }, @@ -2821,6 +18661,102 @@ "state": "translated", "value": "タブのピンを外す" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消固定标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消釘選標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 고정 해제" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab loslösen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Desfijar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Désépingler l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sblocca scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Frigør fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odepnij kartę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открепить вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkači tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء تثبيت اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Løsne fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desafixar Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลิกปักหมุดแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme Sabitlemesini Kaldır" + } } } }, @@ -2838,6 +18774,102 @@ "state": "translated", "value": "ワークスペースのピンを外す" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消固定工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消釘選工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 고정 해제" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich loslösen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Desfijar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Désépingler l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sblocca area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Frigør arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odepnij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открепить рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkači radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء تثبيت مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Løsne arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desafixar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลิกปักหมุดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Sabitlemesini Kaldır" + } } } }, @@ -2855,6 +18887,102 @@ "state": "translated", "value": "VS Codeインラインサーバーを再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重启 VS Code 内联服务器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新啟動 VS Code 內嵌伺服器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "VS Code 인라인 서버 재시작" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "VS Code Inline-Server neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar servidor en línea de VS Code" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer le serveur VS Code intégré" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia server inline VS Code" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart VS Code Inline Server" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie wbudowany serwer VS Code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить встроенный сервер VS Code" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokreni VS Code inline server" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تشغيل خادم VS Code المضمّن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start VS Code innebygd server på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar Servidor Inline do VS Code" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตเซิร์ฟเวอร์ VS Code แบบอินไลน์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "VS Code Satır İçi Sunucusunu Yeniden Başlat" + } } } }, @@ -2872,6 +19000,102 @@ "state": "translated", "value": "VS Codeインラインサーバーを停止" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "停止 VS Code 内联服务器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "停止 VS Code 內嵌伺服器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "VS Code 인라인 서버 중지" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "VS Code Inline-Server stoppen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Detener servidor en línea de VS Code" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Arrêter le serveur VS Code intégré" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Arresta server inline VS Code" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Stop VS Code Inline Server" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zatrzymaj wbudowany serwer VS Code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить встроенный сервер VS Code" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zaustavi VS Code inline server" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إيقاف خادم VS Code المضمّن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Stopp VS Code innebygd server" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Parar Servidor Inline do VS Code" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หยุดเซิร์ฟเวอร์ VS Code แบบอินไลน์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "VS Code Satır İçi Sunucusunu Durdur" + } } } }, @@ -2889,6 +19113,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -2906,6 +19226,102 @@ "state": "translated", "value": "(カスタム名をクリア)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "(清除自定义名称)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "(清除自訂名稱)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "(사용자 지정 이름 지우기)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "(Benutzerdefinierten Namen löschen)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "(borrar nombre personalizado)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "(effacer le nom personnalisé)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "(cancella nome personalizzato)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "(ryd brugerdefineret navn)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "(wyczyść własną nazwę)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "(очистить пользовательское имя)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "(obriši prilagođeni naziv)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "(مسح الاسم المخصص)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "(fjern egendefinert navn)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "(limpar nome personalizado)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "(ล้างชื่อที่กำหนดเอง)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "(özel adı temizle)" + } } } }, @@ -2923,6 +19339,102 @@ "state": "translated", "value": "Enterキーでタブ名を適用、Escapeキーでキャンセルします。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "按 Enter 应用此标签页名称,或按 Escape 取消。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按 Enter 套用此標籤頁名稱,或按 Escape 取消。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Enter를 눌러 탭 이름을 적용하거나, Escape를 눌러 취소하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Drücken Sie die Eingabetaste, um den Tab-Namen zu übernehmen, oder Escape zum Abbrechen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pulsa Intro para aplicar este nombre de pestaña, o Escape para cancelar." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Appuyez sur Entrée pour appliquer ce nom d'onglet, ou sur Échap pour annuler." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Premi Invio per applicare il nome della scheda, oppure Esc per annullare." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tryk Enter for at anvende dette fanenavn, eller Escape for at annullere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Naciśnij Enter, aby zastosować nazwę karty, lub Escape, aby anulować." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите Enter, чтобы применить имя вкладки, или Escape для отмены." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pritisnite Enter za primjenu naziva taba, ili Escape za otkazivanje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اضغط Enter لتطبيق اسم اللسان، أو Escape للإلغاء." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Trykk Enter for å bruke dette fanenavnet, eller Escape for å avbryte." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pressione Enter para aplicar este nome de aba ou Escape para cancelar." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กด Enter เพื่อใช้ชื่อแท็บนี้ หรือ Escape เพื่อยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sekme adını uygulamak için Enter tuşuna basın veya iptal etmek için Escape tuşuna basın." + } } } }, @@ -2940,6 +19452,102 @@ "state": "translated", "value": "タブのカスタム名を選択してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择自定义标签页名称。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇自訂標籤頁名稱。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 탭 이름을 선택하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie einen benutzerdefinierten Tab-Namen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige un nombre personalizado para la pestaña." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez un nom personnalisé pour l'onglet." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scegli un nome personalizzato per la scheda." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vælg et brugerdefineret fanenavn." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wybierz własną nazwę karty." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите пользовательское имя вкладки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Odaberite prilagođeni naziv taba." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر اسمًا مخصصًا للسان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Velg et egendefinert fanenavn." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escolha um nome personalizado para a aba." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลือกชื่อแท็บที่กำหนดเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel bir sekme adı seçin." + } } } }, @@ -2957,6 +19565,102 @@ "state": "translated", "value": "タブ名を入力してください。Enterで名称変更、Escapeでキャンセルします。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "输入标签页名称。按 Enter 重命名,按 Escape 取消。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "輸入標籤頁名稱。按 Enter 重新命名,按 Escape 取消。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름을 입력하세요. Enter로 이름 변경, Escape로 취소." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie einen Tab-Namen ein. Eingabetaste zum Umbenennen, Escape zum Abbrechen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un nombre de pestaña. Pulsa Intro para renombrar, Escape para cancelar." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez un nom d'onglet. Appuyez sur Entrée pour renommer, Échap pour annuler." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un nome per la scheda. Premi Invio per rinominare, Esc per annullare." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast et fanenavn. Tryk Enter for at omdøbe, Escape for at annullere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź nazwę karty. Naciśnij Enter, aby zmienić nazwę, Escape, aby anulować." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите имя вкладки. Нажмите Enter для переименования, Escape для отмены." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite naziv taba. Pritisnite Enter za preimenovanje, Escape za otkazivanje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل اسم اللسان. اضغط Enter لإعادة التسمية، Escape للإلغاء." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn et fanenavn. Trykk Enter for å gi nytt navn, Escape for å avbryte." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira um nome para a aba. Pressione Enter para renomear, Escape para cancelar." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนชื่อแท็บ กด Enter เพื่อเปลี่ยนชื่อ, Escape เพื่อยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bir sekme adı girin. Yeniden adlandırmak için Enter, iptal etmek için Escape tuşuna basın." + } } } }, @@ -2974,6 +19678,102 @@ "state": "translated", "value": "タブ名" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab-Name" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nombre de pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nom de l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nome scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fanenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nazwa karty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя вкладки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naziv taba" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اسم اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fanenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nome da aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ชื่อแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme adı" + } } } }, @@ -2991,6 +19791,102 @@ "state": "translated", "value": "タブの名称変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę karty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi fanen nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Yeniden Adlandır" + } } } }, @@ -3008,6 +19904,102 @@ "state": "translated", "value": "Enterキーでワークスペース名を適用、Escapeキーでキャンセルします。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "按 Enter 应用此工作区名称,或按 Escape 取消。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按 Enter 套用此工作區名稱,或按 Escape 取消。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Enter를 눌러 작업 공간 이름을 적용하거나, Escape를 눌러 취소하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Drücken Sie die Eingabetaste, um den Arbeitsbereichsnamen zu übernehmen, oder Escape zum Abbrechen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pulsa Intro para aplicar este nombre de espacio de trabajo, o Escape para cancelar." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Appuyez sur Entrée pour appliquer ce nom d'espace de travail, ou sur Échap pour annuler." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Premi Invio per applicare il nome dell'area di lavoro, oppure Esc per annullare." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tryk Enter for at anvende dette arbejdsområdenavn, eller Escape for at annullere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Naciśnij Enter, aby zastosować nazwę przestrzeni roboczej, lub Escape, aby anulować." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите Enter, чтобы применить имя рабочего пространства, или Escape для отмены." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pritisnite Enter za primjenu naziva radnog prostora, ili Escape za otkazivanje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اضغط Enter لتطبيق اسم مساحة العمل، أو Escape للإلغاء." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Trykk Enter for å bruke dette arbeidsområdenavnet, eller Escape for å avbryte." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pressione Enter para aplicar este nome de área de trabalho ou Escape para cancelar." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กด Enter เพื่อใช้ชื่อเวิร์กสเปซนี้ หรือ Escape เพื่อยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu çalışma alanı adını uygulamak için Enter tuşuna basın veya iptal etmek için Escape tuşuna basın." + } } } }, @@ -3025,6 +20017,102 @@ "state": "translated", "value": "ワークスペースのカスタム名を選択してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择自定义工作区名称。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇自訂工作區名稱。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 작업 공간 이름을 선택하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie einen benutzerdefinierten Arbeitsbereichsnamen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige un nombre personalizado para el espacio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez un nom personnalisé pour l'espace de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scegli un nome personalizzato per l'area di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vælg et brugerdefineret arbejdsområdenavn." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wybierz własną nazwę przestrzeni roboczej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите пользовательское имя рабочего пространства." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Odaberite prilagođeni naziv radnog prostora." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر اسمًا مخصصًا لمساحة العمل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Velg et egendefinert arbeidsområdenavn." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escolha um nome personalizado para a área de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลือกชื่อเวิร์กสเปซที่กำหนดเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel bir çalışma alanı adı seçin." + } } } }, @@ -3042,6 +20130,102 @@ "state": "translated", "value": "ワークスペース名を入力してください。Enterで名称変更、Escapeでキャンセルします。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "输入工作区名称。按 Enter 重命名,按 Escape 取消。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "輸入工作區名稱。按 Enter 重新命名,按 Escape 取消。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름을 입력하세요. Enter로 이름 변경, Escape로 취소." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie einen Arbeitsbereichsnamen ein. Eingabetaste zum Umbenennen, Escape zum Abbrechen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un nombre de espacio de trabajo. Pulsa Intro para renombrar, Escape para cancelar." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez un nom d'espace de travail. Appuyez sur Entrée pour renommer, Échap pour annuler." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un nome per l'area di lavoro. Premi Invio per rinominare, Esc per annullare." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast et arbejdsområdenavn. Tryk Enter for at omdøbe, Escape for at annullere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź nazwę przestrzeni roboczej. Naciśnij Enter, aby zmienić nazwę, Escape, aby anulować." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите имя рабочего пространства. Нажмите Enter для переименования, Escape для отмены." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite naziv radnog prostora. Pritisnite Enter za preimenovanje, Escape za otkazivanje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل اسم مساحة العمل. اضغط Enter لإعادة التسمية، Escape للإلغاء." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn et arbeidsområdenavn. Trykk Enter for å gi nytt navn, Escape for å avbryte." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira um nome para a área de trabalho. Pressione Enter para renomear, Escape para cancelar." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนชื่อเวิร์กสเปซ กด Enter เพื่อเปลี่ยนชื่อ, Escape เพื่อยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bir çalışma alanı adı girin. Yeniden adlandırmak için Enter, iptal etmek için Escape tuşuna basın." + } } } }, @@ -3059,6 +20243,102 @@ "state": "translated", "value": "ワークスペース名" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Name des Arbeitsbereichs" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nombre del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nom de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nome area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nazwa przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naziv radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اسم مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nome da área de trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma alanı adı" + } } } }, @@ -3076,6 +20356,102 @@ "state": "translated", "value": "ワークスペースの名称変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır" + } } } }, @@ -3093,6 +20469,102 @@ "state": "translated", "value": "検索に一致するコマンドがありません。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "没有匹配的命令。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有符合搜尋的指令。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "검색과 일치하는 명령어가 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Befehle entsprechen Ihrer Suche." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ningún comando coincide con tu búsqueda." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune commande ne correspond à votre recherche." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessun comando corrisponde alla ricerca." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen kommandoer matcher din søgning." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak poleceń pasujących do wyszukiwania." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет команд, соответствующих запросу." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nijedna naredba ne odgovara vašoj pretrazi." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا توجد أوامر تطابق بحثك." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen kommandoer samsvarer med søket ditt." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhum comando corresponde à sua pesquisa." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มีคำสั่งที่ตรงกับการค้นหาของคุณ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aramanızla eşleşen komut yok." + } } } }, @@ -3110,6 +20582,102 @@ "state": "translated", "value": "コマンドを入力" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "输入命令" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "輸入指令" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "명령어를 입력하세요" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Befehl eingeben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Escribe un comando" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez une commande" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Digita un comando" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skriv en kommando" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wpisz polecenie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите команду" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite naredbu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اكتب أمرًا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv en kommando" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Digite um comando" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พิมพ์คำสั่ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bir komut yazın" + } } } }, @@ -3127,6 +20695,102 @@ "state": "translated", "value": "検索に一致するワークスペースがありません。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "没有匹配的工作区。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有符合搜尋的工作區。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "검색과 일치하는 작업 공간이 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Arbeitsbereiche entsprechen Ihrer Suche." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ningún espacio de trabajo coincide con tu búsqueda." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucun espace de travail ne correspond à votre recherche." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna area di lavoro corrisponde alla ricerca." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen arbejdsområder matcher din søgning." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak przestrzeni roboczych pasujących do wyszukiwania." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет рабочих пространств, соответствующих запросу." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nijedan radni prostor ne odgovara vašoj pretrazi." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا توجد مساحات عمل تطابق بحثك." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen arbeidsområder samsvarer med søket ditt." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma área de trabalho corresponde à sua pesquisa." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มีเวิร์กสเปซที่ตรงกับการค้นหาของคุณ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aramanızla eşleşen çalışma alanı yok." + } } } }, @@ -3144,6 +20808,102 @@ "state": "translated", "value": "ワークスペースを検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "搜索工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "搜尋工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 검색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche durchsuchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher des espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cerca aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg i arbejdsområder" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szukaj przestrzeni roboczych" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск рабочих пространств" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pretraži radne prostore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "البحث في مساحات العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Søk i arbeidsområder" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pesquisar áreas de trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหาเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma alanlarını ara" + } } } }, @@ -3161,6 +20921,102 @@ "state": "translated", "value": "ブラウザ • %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器 • %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器 • %@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 • %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser • %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegador • %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigateur • %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Browser • %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browser • %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przeglądarka • %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузер • %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preglednik • %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المتصفح • %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleser • %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegador • %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เบราว์เซอร์ • %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı • %@" + } } } }, @@ -3178,6 +21034,102 @@ "state": "translated", "value": "タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme" + } } } }, @@ -3195,6 +21147,102 @@ "state": "translated", "value": "タブ • %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页 • %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁 • %@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 • %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab • %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña • %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet • %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda • %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fane • %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta • %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вкладка • %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab • %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان • %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fane • %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba • %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บ • %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme • %@" + } } } }, @@ -3212,6 +21260,102 @@ "state": "translated", "value": "ターミナル • %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端 • %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機 • %@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 • %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Terminale • %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал • %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الطرفية • %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัล • %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } } } }, @@ -3229,6 +21373,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -3246,6 +21486,102 @@ "state": "translated", "value": "ワークスペース • %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区 • %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區 • %@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 • %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich • %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo • %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail • %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro • %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde • %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza • %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство • %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor • %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل • %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde • %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho • %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ • %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı • %@" + } } } }, @@ -3263,6 +21599,102 @@ "state": "translated", "value": "ウインドウ %lld" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口 %lld" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗 %lld" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 %lld" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster %lld" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana %lld" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre %lld" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra %lld" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vindue %lld" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Okno %lld" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно %lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prozor %lld" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النافذة %lld" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vindu %lld" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela %lld" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง %lld" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere %lld" + } } } }, @@ -3280,6 +21712,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -3297,6 +21825,102 @@ "state": "translated", "value": "許可" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "允許" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "허용" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Erlauben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Permitir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Autoriser" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Consenti" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tillad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zezwól" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разрешить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dozvoli" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سماح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tillat" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Permitir" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อนุญาต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İzin Ver" + } } } }, @@ -3314,6 +21938,102 @@ "state": "translated", "value": "キャンセル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "취소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Annuler" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Annulla" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Annuller" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Anuluj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkaži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avbryt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Vazgeç" + } } } }, @@ -3331,6 +22051,102 @@ "state": "translated", "value": "閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapat" + } } } }, @@ -3348,6 +22164,102 @@ "state": "translated", "value": "詳細をコピー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "拷贝详细信息" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "拷貝詳細資訊" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세부 정보 복사" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Details kopieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Copiar detalles" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Copier les détails" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Copia dettagli" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kopier detaljer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kopiuj szczegóły" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать подробности" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kopiraj detalje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نسخ التفاصيل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kopier detaljer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Copiar Detalhes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คัดลอกรายละเอียด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayrıntıları Kopyala" + } } } }, @@ -3365,6 +22277,102 @@ "state": "translated", "value": "保存しない" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "不存储" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "不儲存" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "저장 안 함" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nicht sichern" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No guardar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ne pas enregistrer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Non salvare" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gem ikke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie zachowuj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не сохранять" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ne spremi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عدم الحفظ" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ikke arkiver" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não Salvar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่บันทึก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kaydetme" + } } } }, @@ -3382,6 +22390,102 @@ "state": "translated", "value": "インストールして再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "安装并重新启动" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "安裝並重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설치 후 재실행" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Installieren und neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Instalar y reiniciar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Installer et relancer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Installa e riavvia" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Installer og genstart" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zainstaluj i uruchom ponownie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить и перезапустить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Instaliraj i ponovo pokreni" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تثبيت وإعادة التشغيل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Installer og start på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Instalar e Reiniciar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ติดตั้งและเปิดใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yükle ve Yeniden Başlat" + } } } }, @@ -3399,6 +22503,102 @@ "state": "translated", "value": "後で" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "稍后" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "稍後" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "나중에" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Später" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Más tarde" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Plus tard" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Più tardi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Senere" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Później" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Позже" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kasnije" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لاحقًا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Senere" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Depois" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha Sonra" + } } } }, @@ -3416,6 +22616,102 @@ "state": "translated", "value": "今はしない" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "以后再说" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "現在不要" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "나중에" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nicht jetzt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ahora no" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Pas maintenant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Non ora" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ikke nu" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie teraz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не сейчас" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ne sada" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ليس الآن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ikke nå" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Agora Não" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่ใช่ตอนนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Şimdi Değil" + } } } }, @@ -3433,6 +22729,102 @@ "state": "translated", "value": "OK" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tamam" + } } } }, @@ -3450,6 +22842,102 @@ "state": "translated", "value": "名前を変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeniden Adlandır" + } } } }, @@ -3467,6 +22955,102 @@ "state": "translated", "value": "後で再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "稍后重启" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "稍後重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "나중에 재시작" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Später neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar más tarde" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer plus tard" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia più tardi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart senere" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie później" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить позже" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokreni kasnije" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التشغيل لاحقًا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start på nytt senere" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar Depois" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha Sonra Yeniden Başlat" + } } } }, @@ -3484,6 +23068,102 @@ "state": "translated", "value": "今すぐ再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "立即重启" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "立即重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "지금 재시작" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Jetzt neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar ahora" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer maintenant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia ora" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart nu" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie teraz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить сейчас" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokreni sada" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التشغيل الآن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start på nytt nå" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar Agora" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตเดี๋ยวนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Şimdi Yeniden Başlat" + } } } }, @@ -3501,6 +23181,102 @@ "state": "translated", "value": "再試行" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重试" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重試" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "재시도" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Erneut versuchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reintentar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réessayer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riprova" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Prøv igen" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ponów" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Повторить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pokušaj ponovo" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة المحاولة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Prøv igjen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tentar Novamente" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ลองใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tekrar Dene" + } } } }, @@ -3518,6 +23294,102 @@ "state": "translated", "value": "スキップ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳过" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "略過" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "건너뛰기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Überspringen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Omitir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ignorer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Salta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Spring over" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pomiń" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пропустить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preskoči" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Hopp over" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pular" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้าม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Atla" + } } } }, @@ -3535,6 +23407,102 @@ "state": "translated", "value": "カスタムカラーを選択…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选取自定义颜色..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇自訂顏色..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 색상 선택…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Eigene Farbe wählen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elegir color personalizado…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisir une couleur personnalisée..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scegli colore personalizzato…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vælg brugerdefineret farve…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wybierz własny kolor…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать пользовательский цвет..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Odaberi prilagođenu boju…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختيار لون مخصص…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Velg egendefinert farge …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escolher Cor Personalizada…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลือกสีที่กำหนดเอง..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel Renk Seç…" + } } } }, @@ -3552,6 +23520,102 @@ "state": "translated", "value": "カラーをクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除颜色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除顏色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "색상 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Farbe entfernen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar color" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer la couleur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rimuovi colore" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd farve" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść kolor" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать цвет" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši boju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح اللون" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern farge" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Cor" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างสี" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Rengi Temizle" + } } } }, @@ -3569,6 +23633,102 @@ "state": "translated", "value": "他のワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭其他工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉其他工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다른 작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Andere Arbeitsbereiche schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar otros espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer les autres espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi altre aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk andre arbejdsområder" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij inne przestrzenie robocze" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть другие рабочие пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori ostale radne prostore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحات العمل الأخرى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk andre arbeidsområder" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Outras Áreas de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซอื่น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Diğer Çalışma Alanlarını Kapat" + } } } }, @@ -3586,6 +23746,102 @@ "state": "translated", "value": "ワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Kapat" + } } } }, @@ -3603,6 +23859,102 @@ "state": "translated", "value": "ワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer les espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområder" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzenie robocze" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочие пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radne prostore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحات العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområder" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Áreas de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanlarını Kapat" + } } } }, @@ -3620,6 +23972,102 @@ "state": "translated", "value": "上のワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭上方工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉上方的工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "위쪽 작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche darüber schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacios de trabajo superiores" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer les espaces de travail au-dessus" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi aree di lavoro sopra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområder ovenfor" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzenie robocze powyżej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочие пространства выше" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radne prostore iznad" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحات العمل أعلاه" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområder over" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Áreas de Trabalho Acima" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซด้านบน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yukarıdaki Çalışma Alanlarını Kapat" + } } } }, @@ -3637,6 +24085,102 @@ "state": "translated", "value": "下のワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭下方工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉下方的工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래쪽 작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche darunter schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacios de trabajo inferiores" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer les espaces de travail en dessous" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi aree di lavoro sotto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområder nedenfor" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzenie robocze poniżej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочие пространства ниже" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radne prostore ispod" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحات العمل أدناه" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområder under" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Áreas de Trabalho Abaixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซด้านล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağıdaki Çalışma Alanlarını Kapat" + } } } }, @@ -3654,6 +24198,102 @@ "state": "translated", "value": "ワークスペースを既読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将工作区标记为已读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將工作區標為已讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 읽음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich als gelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar espacio de trabajo como leído" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Marquer l'espace de travail comme lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna area di lavoro come letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker arbejdsområde som læst" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz przestrzeń roboczą jako przeczytaną" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить рабочее пространство как прочитанное" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi radni prostor kao pročitan" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم مساحة العمل كمقروءة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk arbeidsområde som lest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Área de Trabalho como Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายเวิร์กสเปซว่าอ่านแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Okundu Olarak İşaretle" + } } } }, @@ -3671,6 +24311,102 @@ "state": "translated", "value": "ワークスペースを未読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将工作区标记为未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將工作區標為未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 읽지 않음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich als ungelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar espacio de trabajo como no leído" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Marquer l'espace de travail comme non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna area di lavoro come non letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker arbejdsområde som ulæst" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz przestrzeń roboczą jako nieprzeczytaną" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить рабочее пространство как непрочитанное" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi radni prostor kao nepročitan" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم مساحة العمل كغير مقروءة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk arbeidsområde som ulest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Área de Trabalho como Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายเวิร์กสเปซว่ายังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Okunmadı Olarak İşaretle" + } } } }, @@ -3688,6 +24424,102 @@ "state": "translated", "value": "ワークスペースを既読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将工作区标记为已读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將工作區標為已讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 읽음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche als gelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar espacios de trabajo como leídos" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Marquer les espaces de travail comme lus" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna aree di lavoro come lette" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker arbejdsområder som læste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz przestrzenie robocze jako przeczytane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить рабочие пространства как прочитанные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi radne prostore kao pročitane" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم مساحات العمل كمقروءة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk arbeidsområder som lest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Áreas de Trabalho como Lidas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายเวิร์กสเปซว่าอ่านแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanlarını Okundu Olarak İşaretle" + } } } }, @@ -3705,6 +24537,102 @@ "state": "translated", "value": "ワークスペースを未読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将工作区标记为未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將工作區標為未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 읽지 않음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche als ungelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar espacios de trabajo como no leídos" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Marquer les espaces de travail comme non lus" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna aree di lavoro come non lette" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker arbejdsområder som ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz przestrzenie robocze jako nieprzeczytane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить рабочие пространства как непрочитанные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi radne prostore kao nepročitane" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم مساحات العمل كغير مقروءة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk arbeidsområder som ulest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Áreas de Trabalho como Não Lidas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายเวิร์กสเปซว่ายังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanlarını Okunmadı Olarak İşaretle" + } } } }, @@ -3722,6 +24650,102 @@ "state": "translated", "value": "下に移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下移" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下移" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach unten bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt ned" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pomjeri dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลื่อนลง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağı Taşı" + } } } }, @@ -3739,6 +24763,102 @@ "state": "translated", "value": "一番上に移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "移到顶部" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "移至最上方" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "맨 위로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach oben bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover al inicio" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer en haut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta in cima" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt til toppen" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś na górę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить наверх" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pomjeri na vrh" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل إلى الأعلى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt til toppen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover para o Topo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้ายไปด้านบน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "En Üste Taşı" + } } } }, @@ -3756,6 +24876,102 @@ "state": "translated", "value": "上に移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上移" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上移" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "위로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach oben bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover hacia arriba" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer vers le haut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta in alto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt op" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś w górę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить вверх" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pomjeri gore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل للأعلى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt opp" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover para Cima" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลื่อนขึ้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yukarı Taşı" + } } } }, @@ -3773,6 +24989,102 @@ "state": "translated", "value": "ワークスペースをウインドウに移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将工作区移到窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將工作區移至視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 윈도우로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich in Fenster bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover espacio de trabajo a ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer l'espace de travail vers une fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta area di lavoro in una finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt arbejdsområde til vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś przestrzeń roboczą do okna" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить рабочее пространство в окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premjesti radni prostor u prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل مساحة العمل إلى نافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt arbeidsområde til vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover Área de Trabalho para Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้ายเวิร์กสเปซไปยังหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Pencereye Taşı" + } } } }, @@ -3790,6 +25102,102 @@ "state": "translated", "value": "ワークスペースをウインドウに移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将工作区移到窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將工作區移至視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 윈도우로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche in Fenster bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover espacios de trabajo a ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer les espaces de travail vers une fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta aree di lavoro in una finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt arbejdsområder til vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś przestrzenie robocze do okna" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить рабочие пространства в окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premjesti radne prostore u prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل مساحات العمل إلى نافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt arbeidsområder til vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover Áreas de Trabalho para Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้ายเวิร์กสเปซไปยังหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanlarını Pencereye Taşı" + } } } }, @@ -3807,6 +25215,102 @@ "state": "translated", "value": "新規ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neues Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvelle fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowe okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่างใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Pencere" + } } } }, @@ -3824,6 +25328,102 @@ "state": "translated", "value": "ワークスペースをピンで固定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "固定工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "釘選工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 고정" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich anheften" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fijar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Épingler l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Fissa area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fastgør arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przypnij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрепить рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zakači radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تثبيت مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fest arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fixar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปักหมุดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Sabitle" + } } } }, @@ -3841,6 +25441,102 @@ "state": "translated", "value": "ワークスペースをピンで固定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "固定工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "釘選工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 고정" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche anheften" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fijar espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Épingler les espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Fissa aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fastgør arbejdsområder" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przypnij przestrzenie robocze" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрепить рабочие пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zakači radne prostore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تثبيت مساحات العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fest arbeidsområder" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fixar Áreas de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปักหมุดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanlarını Sabitle" + } } } }, @@ -3858,6 +25554,102 @@ "state": "translated", "value": "カスタムワークスペース名を削除" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "移除自定义工作区名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "移除自訂工作區名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 작업 공간 이름 제거" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzerdefinierten Arbeitsbereichsnamen entfernen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Eliminar nombre personalizado del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Supprimer le nom personnalisé de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rimuovi nome personalizzato area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fjern brugerdefineret arbejdsområdenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Usuń własną nazwę przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить пользовательское имя рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ukloni prilagođeni naziv radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إزالة اسم مساحة العمل المخصص" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern egendefinert arbeidsområdenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Remover Nome Personalizado da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ลบชื่อเวิร์กสเปซที่กำหนดเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel Çalışma Alanı Adını Kaldır" + } } } }, @@ -3875,6 +25667,102 @@ "state": "translated", "value": "ワークスペースの名称変更…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır…" + } } } }, @@ -3892,6 +25780,102 @@ "state": "translated", "value": "ワークスペースのピンを外す" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消固定工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消釘選工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 고정 해제" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich loslösen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Desfijar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Désépingler l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sblocca area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Frigør arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odepnij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открепить рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkači radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء تثبيت مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Løsne arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desafixar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลิกปักหมุดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Sabitlemesini Kaldır" + } } } }, @@ -3909,6 +25893,102 @@ "state": "translated", "value": "ワークスペースのピンを外す" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消固定工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消釘選工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 고정 해제" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche loslösen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Desfijar espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Désépingler les espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sblocca aree di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Frigør arbejdsområder" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odepnij przestrzenie robocze" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открепить рабочие пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkači radne prostore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء تثبيت مساحات العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Løsne arbeidsområder" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desafixar Áreas de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลิกปักหมุดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanları Sabitlemesini Kaldır" + } } } }, @@ -3926,6 +26006,102 @@ "state": "translated", "value": "ワークスペースカラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区颜色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區顏色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 색상" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereichsfarbe" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Color del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleur de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Colore area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdefarve" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kolor przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Цвет рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Boja radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لون مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdefarge" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cor da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สีเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Rengi" + } } } }, @@ -3943,6 +26119,102 @@ "state": "translated", "value": "最後のタブを閉じ、ウインドウを閉じます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭最后一个标签页并关闭窗口。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉最後一個標籤頁並關閉視窗。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "마지막 탭이 닫히면 윈도우도 닫힙니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch wird der letzte Tab geschlossen und das Fenster geschlossen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará la última pestaña y cerrará la ventana." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera le dernier onglet et fermera la fenêtre." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà l'ultima scheda e la finestra." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker den sidste fane og lukker vinduet." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie ostatniej karty i zamknięcie okna." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Это закроет последнюю вкладку и окно." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti posljednji tab i zatvoriti prozor." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق آخر لسان وإغلاق النافذة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke den siste fanen og lukke vinduet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará a última aba e fechará a janela." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิดแท็บสุดท้ายและปิดหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, son sekmeyi kapatacak ve pencereyi kapatacak." + } } } }, @@ -3960,6 +26232,102 @@ "state": "translated", "value": "最後のタブを閉じ、ワークスペースを閉じます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭最后一个标签页并关闭其工作区。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉最後一個標籤頁並關閉其工作區。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "마지막 탭이 닫히면 해당 작업 공간도 닫힙니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch wird der letzte Tab geschlossen und der Arbeitsbereich geschlossen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará la última pestaña y cerrará su espacio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera le dernier onglet et fermera son espace de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà l'ultima scheda e la sua area di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker den sidste fane og lukker dets arbejdsområde." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie ostatniej karty i zamknięcie jej przestrzeni roboczej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Это закроет последнюю вкладку и её рабочее пространство." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti posljednji tab i zatvoriti njegov radni prostor." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق آخر لسان وإغلاق مساحة العمل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke den siste fanen og lukke arbeidsområdet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará a última aba e fechará sua área de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิดแท็บสุดท้ายและปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, son sekmeyi kapatacak ve çalışma alanını kapatacak." + } } } }, @@ -3977,6 +26345,102 @@ "state": "translated", "value": "このペインの 1 個のタブを閉じます:\n%@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭此面板中的 1 个标签页:\n%@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉此面板中的 1 個標籤頁:\n%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 패널에서 탭 1개를 닫습니다:\n%@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch wird 1 Tab in diesem Bereich geschlossen:\n%@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará 1 pestaña en este panel:\n%@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera 1 onglet dans ce panneau :\n%@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà 1 scheda in questo pannello:\n%@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker 1 fane i dette panel:\n%@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie 1 karty w tym panelu:\n%@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будет закрыта 1 вкладка в этой панели:\n%@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti 1 tab u ovom panelu:\n%@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق لسان واحد في هذه اللوحة:\n%@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke 1 fane i dette panelet:\n%@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará 1 aba neste painel:\n%@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิด 1 แท็บในบานหน้าต่างนี้:\n%@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu bölmedeki 1 sekme kapatılacak:\n%@" + } } } }, @@ -3994,6 +26458,102 @@ "state": "translated", "value": "このペインの %1$lld 個のタブを閉じます:\n%2$@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭此面板中的 %1$lld 个标签页:\n%2$@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉此面板中的 %1$lld 個標籤頁:\n%2$@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 패널에서 탭 %1$lld개를 닫습니다:\n%2$@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch werden %1$lld Tabs in diesem Bereich geschlossen:\n%2$@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará %1$lld pestañas en este panel:\n%2$@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera %1$lld onglets dans ce panneau :\n%2$@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà %1$lld schede in questo pannello:\n%2$@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker %1$lld faner i dette panel:\n%2$@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie %1$lld kart w tym panelu:\n%2$@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будет закрыто вкладок в этой панели: %1$lld\n%2$@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti %1$lld tabova u ovom panelu:\n%2$@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق %1$lld لسان في هذه اللوحة:\n%2$@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke %1$lld faner i dette panelet:\n%2$@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará %1$lld abas neste painel:\n%2$@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิด %1$lld แท็บในบานหน้าต่างนี้:\n%2$@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu bölmedeki %1$lld sekme kapatılacak:\n%2$@" + } } } }, @@ -4011,6 +26571,102 @@ "state": "translated", "value": "他のタブを閉じますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭其他标签页?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要關閉其他標籤頁嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다른 탭을 닫으시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Andere Tabs schließen?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Cerrar otras pestañas?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer les autres onglets ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudere le altre schede?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk andre faner?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknąć inne karty?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть другие вкладки?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvoriti ostale tabove?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق الألسنة الأخرى؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukke andre faner?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar outras abas?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดแท็บอื่นหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Diğer sekmeler kapatılsın mı?" + } } } }, @@ -4028,6 +26684,102 @@ "state": "translated", "value": "閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapat" + } } } }, @@ -4045,6 +26797,102 @@ "state": "translated", "value": "現在のタブを閉じます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭当前标签页。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉目前的標籤頁。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 탭을 닫습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch wird der aktuelle Tab geschlossen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará la pestaña actual." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera l'onglet actuel." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà la scheda corrente." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker den aktuelle fane." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie bieżącej karty." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Текущая вкладка будет закрыта." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti trenutni tab." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق اللسان الحالي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke den gjeldende fanen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará a aba atual." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิดแท็บปัจจุบัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, geçerli sekmeyi kapatacak." + } } } }, @@ -4062,6 +26910,102 @@ "state": "translated", "value": "タブを閉じますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭标签页?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要關閉標籤頁嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭을 닫으시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab schließen?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Cerrar pestaña?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'onglet ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudere la scheda?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk fane?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknąć kartę?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть вкладку?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvoriti tab?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق اللسان؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukke fane?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar aba?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดแท็บหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme kapatılsın mı?" + } } } }, @@ -4079,6 +27023,102 @@ "state": "translated", "value": "ワークスペースとそのすべてのパネルを閉じます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭工作区及其所有面板。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉工作區及其所有面板。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간과 모든 패널을 닫습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch werden der Arbeitsbereich und alle zugehörigen Bereiche geschlossen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará el espacio de trabajo y todos sus paneles." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera l'espace de travail et tous ses panneaux." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà l'area di lavoro e tutti i suoi pannelli." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker arbejdsområdet og alle dets paneler." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie przestrzeni roboczej i wszystkich jej paneli." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство и все его панели будут закрыты." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti radni prostor i sve njegove panele." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق مساحة العمل وجميع لوحاتها." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke arbeidsområdet og alle panelene." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará a área de trabalho e todos os seus painéis." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิดเวิร์กสเปซและแผงทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, çalışma alanını ve tüm panellerini kapatacak." + } } } }, @@ -4096,6 +27136,102 @@ "state": "translated", "value": "ワークスペースを閉じますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要關閉工作區嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간을 닫으시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich schließen?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Cerrar espacio de trabajo?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'espace de travail ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudere l'area di lavoro?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområde?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknąć przestrzeń roboczą?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочее пространство?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvoriti radni prostor?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحة العمل؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukke arbeidsområde?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar área de trabalho?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma alanı kapatılsın mı?" + } } } }, @@ -4113,6 +27249,102 @@ "state": "translated", "value": "Cmd+Q の警告を表示しない" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "不再提示 Cmd+Q 警告" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "不再顯示 Cmd+Q 警告" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q에 대해 다시 경고하지 않기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nicht erneut bei Cmd+Q warnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No volver a advertir para Cmd+Q" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ne plus avertir pour Cmd+Q" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Non avvisare più per Cmd+Q" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Advar ikke igen for Cmd+Q" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie ostrzegaj ponownie przy Cmd+Q" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Больше не предупреждать при Cmd+Q" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ne upozoravaj ponovo za Cmd+Q" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عدم التحذير مجددًا عند Cmd+Q" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ikke advar igjen for Cmd+Q" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não avisar novamente para Cmd+Q" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่ต้องเตือนอีกสำหรับ Cmd+Q" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q için bir daha uyarma" + } } } }, @@ -4130,6 +27362,102 @@ "state": "translated", "value": "cmuxの通知が無効になっています。アラートを表示するにはシステム設定で有効にしてください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 的通知已被禁用。请在系统设置中启用通知以接收提醒。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 的通知已停用。請在「系統設定」中啟用,以接收提示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에 대한 알림이 비활성화되어 있습니다. 알림을 보려면 시스템 설정에서 활성화하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen sind für cmux deaktiviert. Aktivieren Sie sie in den Systemeinstellungen, um Hinweise zu sehen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Las notificaciones están desactivadas para cmux. Actívalas en Ajustes del Sistema para ver alertas." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les notifications sont désactivées pour cmux. Activez-les dans les Réglages Système pour voir les alertes." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Le notifiche sono disabilitate per cmux. Abilitale nelle Impostazioni di Sistema per visualizzare gli avvisi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Notifikationer er deaktiveret for cmux. Aktiver dem i Systemindstillinger for at se advarsler." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia są wyłączone dla cmux. Włącz je w Ustawieniach systemowych, aby widzieć alerty." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления для cmux отключены. Включите их в Системных настройках, чтобы видеть оповещения." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja su onemogućena za cmux. Omogućite ih u Postavkama sistema da biste vidjeli upozorenja." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإشعارات معطلة لـ cmux. قم بتفعيلها في إعدادات النظام لرؤية التنبيهات." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Varsler er deaktivert for cmux. Aktiver dem i Systeminnstillinger for å se varsler." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "As notificações estão desativadas para o cmux. Ative-as nos Ajustes do Sistema para ver os alertas." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือนถูกปิดใช้งานสำหรับ cmux เปิดใช้งานในการตั้งค่าระบบเพื่อดูการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux için bildirimler devre dışı. Uyarıları görmek için Sistem Ayarları'nda etkinleştirin." + } } } }, @@ -4147,6 +27475,102 @@ "state": "translated", "value": "今はしない" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "以后再说" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "現在不要" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "나중에" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nicht jetzt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ahora no" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Pas maintenant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Non ora" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ikke nu" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie teraz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не сейчас" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ne sada" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ليس الآن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ikke nå" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Agora Não" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่ใช่ตอนนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Şimdi Değil" + } } } }, @@ -4164,6 +27588,102 @@ "state": "translated", "value": "設定を開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开设置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟設定" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설정 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir Ajustes" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir les réglages" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri impostazioni" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn indstillinger" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz ustawienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть настройки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori postavke" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الإعدادات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne innstillinger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Ajustes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดการตั้งค่า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayarları Aç" + } } } }, @@ -4181,6 +27701,102 @@ "state": "translated", "value": "cmuxの通知を有効にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用 cmux 通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "為 cmux 啟用通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 알림 활성화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen für cmux aktivieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Activar notificaciones para cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer les notifications pour cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Abilita notifiche per cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiver notifikationer for cmux" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Włącz powiadomienia dla cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включить уведомления для cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Omogući obavještenja za cmux" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تفعيل الإشعارات لـ cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktiver varsler for cmux" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ativar Notificações para o cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดใช้งานการแจ้งเตือนสำหรับ cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux İçin Bildirimleri Etkinleştir" + } } } }, @@ -4198,6 +27814,102 @@ "state": "translated", "value": "cmuxはこのタブを選択した移動先に移動できませんでした。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 无法将此标签页移动到所选目标。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 無法將此標籤頁移至所選的目的地。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 이 탭을 선택한 대상으로 이동할 수 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux konnte diesen Tab nicht zum ausgewählten Ziel verschieben." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux no pudo mover esta pestaña al destino seleccionado." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux n'a pas pu déplacer cet onglet vers la destination sélectionnée." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux non è riuscito a spostare questa scheda nella destinazione selezionata." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux kunne ikke flytte denne fane til den valgte destination." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux nie mógł przenieść tej karty do wybranego miejsca docelowego." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux не удалось переместить эту вкладку в выбранное место назначения." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux nije mogao premjestiti ovaj tab na odabrano odredište." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لم يتمكن cmux من نقل هذا اللسان إلى الوجهة المحددة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux kunne ikke flytte denne fanen til den valgte destinasjonen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O cmux não conseguiu mover esta aba para o destino selecionado." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux ไม่สามารถย้ายแท็บนี้ไปยังปลายทางที่เลือกได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux bu sekmeyi seçilen hedefe taşıyamadı." + } } } }, @@ -4215,6 +27927,102 @@ "state": "translated", "value": "移動失敗" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "移动失败" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "移動失敗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이동 실패" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verschieben fehlgeschlagen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error al mover" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Échec du déplacement" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Spostamento non riuscito" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flytning mislykkedes" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenoszenie nie powiodło się" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка перемещения" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premještanje nije uspjelo" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فشل النقل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytting mislyktes" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Falha ao Mover" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การย้ายล้มเหลว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Taşıma Başarısız" + } } } }, @@ -4232,6 +28040,102 @@ "state": "translated", "value": "このタブの移動先を選択してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请为此标签页选择目标位置。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇此標籤頁的目的地。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 탭의 대상을 선택하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie ein Ziel für diesen Tab." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige un destino para esta pestaña." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez une destination pour cet onglet." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scegli una destinazione per questa scheda." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vælg en destination for denne fane." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wybierz miejsce docelowe dla tej karty." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите место назначения для этой вкладки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Odaberite odredište za ovaj tab." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر وجهة لهذا اللسان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Velg en destinasjon for denne fanen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escolha um destino para esta aba." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลือกปลายทางสำหรับแท็บนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sekme için bir hedef seçin." + } } } }, @@ -4249,6 +28153,102 @@ "state": "translated", "value": "移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "移动" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "移動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verschieben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premjesti" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้าย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Taşı" + } } } }, @@ -4266,6 +28266,102 @@ "state": "translated", "value": "現在のウインドウの新規ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "当前窗口的新工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "目前視窗的新工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 윈도우의 새 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Arbeitsbereich im aktuellen Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo en la ventana actual" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail dans la fenêtre actuelle" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro nella finestra corrente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt arbejdsområde i nuværende vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza w bieżącym oknie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство в текущем окне" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi radni prostor u trenutnom prozoru" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل جديدة في النافذة الحالية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt arbeidsområde i gjeldende vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Área de Trabalho na Janela Atual" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซใหม่ในหน้าต่างปัจจุบัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Pencerede Yeni Çalışma Alanı" + } } } }, @@ -4283,6 +28379,102 @@ "state": "translated", "value": "選択したワークスペースを新規ウインドウに" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新窗口中的所选工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "所選工作區至新視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "선택한 작업 공간을 새 윈도우로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ausgewählter Arbeitsbereich in neuem Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo seleccionado en nueva ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail sélectionné dans une nouvelle fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro selezionata in una nuova finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Valgt arbejdsområde i nyt vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wybrana przestrzeń robocza w nowym oknie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбранное рабочее пространство в новом окне" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Odabrani radni prostor u novom prozoru" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل المحددة في نافذة جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Valgt arbeidsområde i nytt vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho Selecionada em Nova Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซที่เลือกในหน้าต่างใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Pencerede Seçili Çalışma Alanı" + } } } }, @@ -4300,6 +28492,102 @@ "state": "translated", "value": "タブの移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "移动标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "移動標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab verschieben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś kartę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premjesti tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้ายแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Taşı" + } } } }, @@ -4317,6 +28605,102 @@ "state": "translated", "value": "すべてのウインドウとワークスペースを閉じます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将关闭所有窗口和工作区。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將關閉所有視窗和工作區。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모든 윈도우와 작업 공간을 닫습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dadurch werden alle Fenster und Arbeitsbereiche geschlossen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto cerrará todas las ventanas y espacios de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela fermera toutes les fenêtres et tous les espaces de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione chiuderà tutte le finestre e le aree di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette lukker alle vinduer og arbejdsområder." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to zamknięcie wszystkich okien i przestrzeni roboczych." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все окна и рабочие пространства будут закрыты." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo će zatvoriti sve prozore i radne prostore." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سيؤدي هذا إلى إغلاق جميع النوافذ ومساحات العمل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette vil lukke alle vinduer og arbeidsområder." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto fechará todas as janelas e áreas de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิดหน้าต่างและเวิร์กสเปซทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, tüm pencereleri ve çalışma alanlarını kapatacak." + } } } }, @@ -4334,6 +28718,102 @@ "state": "translated", "value": "終了" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "退出" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "結束" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "종료" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Salir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Quitter" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Esci" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Afslut" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zakończ" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Завершить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إنهاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avslutt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Encerrar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ออก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çık" + } } } }, @@ -4351,6 +28831,102 @@ "state": "translated", "value": "cmux を終了しますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "退出 cmux?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要結束 cmux 嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux를 종료하시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux beenden?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Salir de cmux?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Quitter cmux ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Uscire da cmux?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Afslut cmux?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zakończyć cmux?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Завершить cmux?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvoriti cmux?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إنهاء cmux؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avslutte cmux?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Encerrar o cmux?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ออกจาก cmux หรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux'tan çıkılsın mı?" + } } } }, @@ -4368,6 +28944,102 @@ "state": "translated", "value": "このタブのカスタム名を入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请为此标签页输入自定义名称。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "為此標籤頁輸入自訂名稱。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 탭의 사용자 지정 이름을 입력하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie einen benutzerdefinierten Namen für diesen Tab ein." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un nombre personalizado para esta pestaña." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez un nom personnalisé pour cet onglet." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un nome personalizzato per questa scheda." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast et brugerdefineret navn til denne fane." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź własną nazwę dla tej karty." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите пользовательское имя для этой вкладки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite prilagođeni naziv za ovaj tab." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل اسمًا مخصصًا لهذا اللسان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn et egendefinert navn for denne fanen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira um nome personalizado para esta aba." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนชื่อที่กำหนดเองสำหรับแท็บนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu sekme için özel bir ad girin." + } } } }, @@ -4385,6 +29057,102 @@ "state": "translated", "value": "タブ名" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab-Name" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nombre de pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nom de l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nome scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fanenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nazwa karty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя вкладки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naziv taba" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اسم اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fanenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nome da aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ชื่อแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme adı" + } } } }, @@ -4402,6 +29170,102 @@ "state": "translated", "value": "タブの名称変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę karty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi fanen nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Yeniden Adlandır" + } } } }, @@ -4419,6 +29283,102 @@ "state": "translated", "value": "このワークスペースのカスタム名を入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请为此工作区输入自定义名称。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "為此工作區輸入自訂名稱。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 작업 공간의 사용자 지정 이름을 입력하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie einen benutzerdefinierten Namen für diesen Arbeitsbereich ein." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce un nombre personalizado para este espacio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez un nom personnalisé pour cet espace de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci un nome personalizzato per questa area di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast et brugerdefineret navn til dette arbejdsområde." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wprowadź własną nazwę dla tej przestrzeni roboczej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите пользовательское имя для этого рабочего пространства." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unesite prilagođeni naziv za ovaj radni prostor." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل اسمًا مخصصًا لمساحة العمل هذه." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn et egendefinert navn for dette arbeidsområdet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira um nome personalizado para esta área de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนชื่อที่กำหนดเองสำหรับเวิร์กสเปซนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu çalışma alanı için özel bir ad girin." + } } } }, @@ -4436,6 +29396,102 @@ "state": "translated", "value": "ワークスペース名" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Name des Arbeitsbereichs" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nombre del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nom de l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nome area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdenavn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nazwa przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naziv radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اسم مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdenavn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nome da área de trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma alanı adı" + } } } }, @@ -4453,6 +29509,102 @@ "state": "translated", "value": "ワークスペースの名称変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır" + } } } }, @@ -4470,6 +29622,102 @@ "state": "translated", "value": "クリップボードからフォルダパスを読み込めませんでした。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法从剪贴板加载文件夹路径。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法從剪貼簿載入資料夾路徑。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "클립보드에서 폴더 경로를 불러올 수 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Es konnte kein Ordnerpfad aus der Zwischenablage geladen werden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo cargar ninguna ruta de carpeta desde el portapapeles." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible de charger un chemin de dossier depuis le presse-papiers." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile caricare un percorso cartella dagli appunti." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke indlæse nogen mappesti fra udklipsholderen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się wczytać ścieżki folderu ze schowka." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить путь к папке из буфера обмена." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće učitati putanju foldera iz međuspremnika." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر تحميل أي مسار مجلد من الحافظة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke laste inn noen mappesti fra utklippstavlen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não foi possível carregar nenhum caminho de pasta da área de transferência." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถโหลดเส้นทางโฟลเดอร์จากคลิปบอร์ดได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Panodan herhangi bir klasör yolu yüklenemedi." + } } } }, @@ -4487,6 +29735,102 @@ "state": "translated", "value": "システム" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "系统" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "系統" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "시스템" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Système" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Systemowy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системный" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sistemski" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النظام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ระบบ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sistem" + } } } }, @@ -4504,6 +29848,102 @@ "state": "translated", "value": "cmuxについて" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关于 cmux" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關於 cmux" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에 관하여" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Über cmux" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Acerca de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "À propos de cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Informazioni su cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Om cmux" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "O cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О программе cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "O programu cmux" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "حول cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Om cmux" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sobre o cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เกี่ยวกับ cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux Hakkında" + } } } }, @@ -4521,6 +29961,102 @@ "state": "translated", "value": "アップデートを確認…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "检查更新..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "檢查更新..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 확인…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach Updates suchen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar actualizaciones…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher des mises à jour..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verifica aggiornamenti…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg efter opdateringer…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sprawdź aktualizacje…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Provjeri ažuriranja…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التحقق من التحديثات…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Se etter oppdateringer …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Atualizações…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตรวจหาอัปเดต..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeleri Denetle…" + } } } }, @@ -4538,6 +30074,102 @@ "state": "translated", "value": "Ghostty設定…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Ghostty 设置..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Ghostty 設定..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Ghostty 설정…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ghostty-Einstellungen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ajustes de Ghostty…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réglages Ghostty..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impostazioni Ghostty…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ghostty-indstillinger…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ustawienia Ghostty…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки Ghostty..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ghostty postavke…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعدادات Ghostty…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ghostty-innstillinger …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ajustes do Ghostty…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การตั้งค่า Ghostty..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ghostty Ayarları…" + } } } }, @@ -4555,6 +30187,102 @@ "state": "translated", "value": "構成を再読み込み" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载配置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入設定" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "구성 다시 불러오기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Konfiguration neu laden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Recargar configuración" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recharger la configuration" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ricarica configurazione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genindlæs konfiguration" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odśwież konfigurację" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезагрузить конфигурацию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo učitaj konfiguraciju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تحميل الإعدادات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Last inn konfigurasjon på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Recarregar Configuração" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหลดการกำหนดค่าใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yapılandırmayı Yeniden Yükle" + } } } }, @@ -4572,6 +30300,102 @@ "state": "translated", "value": "設定…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "设置..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "設定..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설정…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ajustes…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réglages..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impostazioni…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indstillinger…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ustawienia…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Postavke…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإعدادات…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innstillinger …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ajustes…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การตั้งค่า..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayarlar…" + } } } }, @@ -4589,6 +30413,102 @@ "state": "translated", "value": "アップデートを確認…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "检查更新..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "檢查更新..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 확인…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach Updates suchen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar actualizaciones…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher des mises à jour..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verifica aggiornamenti…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg efter opdateringer…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sprawdź aktualizacje…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Provjeri ažuriranja…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التحقق من التحديثات…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Se etter oppdateringer …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Atualizações…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตรวจหาอัปเดต..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeleri Denetle…" + } } } }, @@ -4606,6 +30526,102 @@ "state": "translated", "value": "現在のウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "当前窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "目前視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana actual" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre actuelle" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra corrente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nuværende vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Bieżące okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Текущее окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Trenutni prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النافذة الحالية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gjeldende vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela Atual" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่างปัจจุบัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Pencere" + } } } }, @@ -4623,6 +30639,102 @@ "state": "translated", "value": "ペイン内の他のタブを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭面板中的其他标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉面板中的其他標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패널의 다른 탭 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Andere Tabs im Bereich schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar otras pestañas del panel" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer les autres onglets du panneau" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi altre schede nel pannello" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk andre faner i panel" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij inne karty w panelu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть другие вкладки в панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori ostale tabove u panelu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق الألسنة الأخرى في اللوحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk andre faner i panelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Outras Abas no Painel" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดแท็บอื่นในบานหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bölmedeki Diğer Sekmeleri Kapat" + } } } }, @@ -4640,6 +30752,102 @@ "state": "translated", "value": "タブを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij kartę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Kapat" + } } } }, @@ -4657,6 +30865,102 @@ "state": "translated", "value": "ワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Kapat" + } } } }, @@ -4674,6 +30978,102 @@ "state": "translated", "value": "コマンドパレット…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "命令面板..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "指令面板..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "명령어 팔레트…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Befehlspalette …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Paleta de comandos…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Palette de commandes..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Tavolozza comandi…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kommandopalette…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Paleta poleceń…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Paleta naredbi…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لوحة الأوامر…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kommandopalett …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Paleta de Comandos…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แถบคำสั่ง..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Komut Paleti…" + } } } }, @@ -4691,6 +31091,102 @@ "state": "translated", "value": "ワークスペースに移動…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "前往工作区..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "前往工作區..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간으로 이동…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zum Arbeitsbereich wechseln …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir al espacio de trabajo…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller à l'espace de travail..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'area di lavoro…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til arbejdsområde…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do przestrzeni roboczej…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к рабочему пространству..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Idi na radni prostor…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى مساحة العمل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til arbeidsområde …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Área de Trabalho…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไปที่เวิร์กสเปซ..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanına Git…" + } } } }, @@ -4708,6 +31204,102 @@ "state": "translated", "value": "新規ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neues Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvelle fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowe okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่างใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Pencere" + } } } }, @@ -4725,6 +31317,102 @@ "state": "translated", "value": "新規ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Çalışma Alanı" + } } } }, @@ -4742,6 +31430,102 @@ "state": "translated", "value": "フォルダを開く…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开文件夹..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟資料夾..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "폴더 열기…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ordner öffnen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir carpeta…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir un dossier..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri cartella…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn mappe…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz folder…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть папку..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori folder…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح مجلد…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne mappe …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Pasta…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดโฟลเดอร์..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Klasör Aç…" + } } } }, @@ -4759,6 +31543,102 @@ "state": "translated", "value": "開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aç" + } } } }, @@ -4776,6 +31656,102 @@ "state": "translated", "value": "フォルダを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开文件夹" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟資料夾" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "폴더 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ordner öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir carpeta" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir un dossier" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri cartella" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn mappe" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz folder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть папку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori folder" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح مجلد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne mappe" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Pasta" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดโฟลเดอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Klasör Aç" + } } } }, @@ -4793,6 +31769,102 @@ "state": "translated", "value": "閉じたブラウザパネルを再度開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新打开已关闭的浏览器面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新開啟已關閉的瀏覽器面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "닫은 브라우저 패널 다시 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geschlossenes Browserfenster erneut öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reabrir panel del navegador cerrado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rouvrir le panneau de navigateur fermé" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riapri pannello browser chiuso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genåbn lukket browserpanel" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz ponownie zamknięty panel przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть закрытую панель браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo otvori zatvoreni panel preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة فتح لوحة المتصفح المغلقة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne lukket nettleserpanel på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reabrir Painel do Navegador Fechado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดแผงเบราว์เซอร์ที่ปิดไปอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapatılan Tarayıcı Panelini Yeniden Aç" + } } } }, @@ -4810,6 +31882,102 @@ "state": "translated", "value": "検索…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "찾기…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найти..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بحث…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหา..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bul…" + } } } }, @@ -4827,6 +31995,102 @@ "state": "translated", "value": "次を検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找下一个" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找下一個" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 찾기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Weitersuchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar siguiente" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Résultat suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova successivo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Find næste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź następny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найти далее" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi sljedeće" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "البحث عن التالي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn neste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Próximo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหาถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonrakini Bul" + } } } }, @@ -4844,6 +32108,102 @@ "state": "translated", "value": "前を検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找上一个" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找上一個" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 찾기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriges suchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Résultat précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Find forrige" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź poprzedni" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найти ранее" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi prethodno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "البحث عن السابق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn forrige" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหาก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Öncekini Bul" + } } } }, @@ -4861,6 +32221,102 @@ "state": "translated", "value": "検索バーを非表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "隐藏查找栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "隱藏尋找列" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "찾기 막대 숨기기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchleiste ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ocultar barra de búsqueda" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Masquer la barre de recherche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nascondi barra di ricerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skjul søgelinje" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ukryj pasek wyszukiwania" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть панель поиска" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sakrij traku za pretragu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إخفاء شريط البحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skjul søkelinje" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ocultar Barra de Busca" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ซ่อนแถบค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Arama Çubuğunu Gizle" + } } } }, @@ -4878,6 +32334,102 @@ "state": "translated", "value": "検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查找" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尋找" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "찾기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trova" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Znajdź" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pronađi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Finn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Buscar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bul" + } } } }, @@ -4895,6 +32447,102 @@ "state": "translated", "value": "選択範囲を検索に使用" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用选中内容查找" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用所選範圍來尋找" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "선택 항목을 찾기에 사용" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Auswahl für Suche verwenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usar selección para buscar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utiliser la sélection pour la recherche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Usa selezione per la ricerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Brug markering til søgning" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Użyj zaznaczenia do wyszukiwania" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Использовать выделение для поиска" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Koristi odabrano za pretragu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استخدام التحديد للبحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bruk utvalg for søk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Usar Seleção para Busca" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใช้ข้อความที่เลือกเพื่อค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Seçimi Bulmak İçin Kullan" + } } } }, @@ -4912,6 +32560,102 @@ "state": "translated", "value": "すべてクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全部清除" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除全部" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모두 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar todo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Tout effacer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella tutto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd alle" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść wszystko" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить все" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši sve" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح الكل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern alle" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Tudo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tümünü Temizle" + } } } }, @@ -4929,6 +32673,102 @@ "state": "translated", "value": "最新の未読にジャンプ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳转到最新未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跳至最新未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 읽지 않은 항목으로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur letzten ungelesenen springen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir a la última no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller au dernier message non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'ultimo non letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til seneste ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do najnowszego nieprzeczytanego" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к последнему непрочитанному" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Skoči na najnovije nepročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى أحدث غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til siste uleste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Última Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้ามไปยังรายการยังไม่อ่านล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son Okunmamışa Atla" + } } } }, @@ -4946,6 +32786,102 @@ "state": "translated", "value": "すべて既読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全部标记为已读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全部標為已讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모두 읽음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle als gelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar todo como leído" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Tout marquer comme lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna tutto come letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker alle som læste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz wszystko jako przeczytane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить все как прочитанные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi sve kao pročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم الكل كمقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk alle som lest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Tudo como Lido" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายว่าอ่านทั้งหมดแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tümünü Okundu İşaretle" + } } } }, @@ -4963,6 +32899,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri Göster" + } } } }, @@ -4980,6 +33012,102 @@ "state": "translated", "value": "通知" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimler" + } } } }, @@ -4997,6 +33125,102 @@ "state": "translated", "value": "現在のディレクトリを Android Studio で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Android Studio 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Android Studio 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Android Studio에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Android Studio öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Android Studio" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Android Studio" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Android Studio" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Android Studio" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Android Studio" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Android Studio" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Android Studio" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Android Studio" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Android Studio" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Android Studio" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Android Studio" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Android Studio'da Aç" + } } } }, @@ -5014,6 +33238,102 @@ "state": "translated", "value": "現在のディレクトリを Antigravity で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Antigravity 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Antigravity 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Antigravity에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Antigravity öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Antigravity" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Antigravity" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Antigravity" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Antigravity" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Antigravity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Antigravity" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Antigravity" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Antigravity" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Antigravity" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Antigravity" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Antigravity" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Antigravity'de Aç" + } } } }, @@ -5031,6 +33351,102 @@ "state": "translated", "value": "現在のディレクトリを Cursor で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Cursor 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Cursor 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Cursor에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Cursor öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Cursor" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Cursor" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Cursor" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Cursor" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Cursor" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Cursor" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Cursor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Cursor" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Cursor" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Cursor" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Cursor" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Cursor'da Aç" + } } } }, @@ -5048,6 +33464,102 @@ "state": "translated", "value": "現在のディレクトリを Finder で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在访达中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Finder 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Finder에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis im Finder öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Finder" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans le Finder" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente nel Finder" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Finder" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Finderze" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Finder" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Finder" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Finder" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Finder" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Finder" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Finder" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Finder'da Aç" + } } } }, @@ -5065,6 +33577,102 @@ "state": "translated", "value": "現在のディレクトリを Ghostty で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Ghostty 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Ghostty 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Ghostty에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Ghostty öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Ghostty" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Ghostty" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Ghostty" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Ghostty" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Ghostty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Ghostty" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Ghostty" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Ghostty" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Ghostty" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Ghostty" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Ghostty" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Ghostty'de Aç" + } } } }, @@ -5082,6 +33690,102 @@ "state": "translated", "value": "現在のディレクトリを iTerm2 で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 iTerm2 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 iTerm2 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 iTerm2에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in iTerm2 öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en iTerm2" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans iTerm2" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in iTerm2" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i iTerm2" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w iTerm2" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в iTerm2" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u iTerm2" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في iTerm2" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i iTerm2" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no iTerm2" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน iTerm2" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini iTerm2'de Aç" + } } } }, @@ -5099,6 +33803,102 @@ "state": "translated", "value": "現在のディレクトリをターミナルで開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在终端中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在「終端機」中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 터미널에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Terminal öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Terminal" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Terminalu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Терминале" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Terminal" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Terminal" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Terminal" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Terminal" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Terminal'de Aç" + } } } }, @@ -5116,6 +33916,102 @@ "state": "translated", "value": "現在のディレクトリを Tower で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Tower 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Tower 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Tower에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Tower öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Tower" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Tower" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Tower" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Tower" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Tower" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Tower" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Tower" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Tower" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Tower" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Tower" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Tower" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Tower'da Aç" + } } } }, @@ -5133,6 +34029,102 @@ "state": "translated", "value": "現在のディレクトリを VS Code で開く(インライン)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 VS Code(内联)中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 VS Code(內嵌)中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 VS Code (인라인)에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in VS Code (Inline) öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en VS Code (en línea)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans VS Code (intégré)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in VS Code (Inline)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i VS Code (Inline)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w VS Code (wbudowany)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в VS Code (встроенный)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u VS Code (Inline)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في VS Code (مضمّن)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i VS Code (innebygd)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no VS Code (Inline)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน VS Code (แบบอินไลน์)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini VS Code'da Aç (Satır İçi)" + } } } }, @@ -5150,6 +34142,102 @@ "state": "translated", "value": "現在のディレクトリを Warp で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Warp 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Warp 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Warp에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Warp öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Warp" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Warp" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Warp" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Warp" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Warp" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Warp" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Warp" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Warp" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Warp" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Warp" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Warp" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Warp'ta Aç" + } } } }, @@ -5167,6 +34255,102 @@ "state": "translated", "value": "現在のディレクトリを Windsurf で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Windsurf 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Windsurf 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Windsurf에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Windsurf öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Windsurf" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Windsurf" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Windsurf" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Windsurf" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Windsurf" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Windsurf" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Windsurf" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Windsurf" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Windsurf" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Windsurf" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Windsurf" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Windsurf'te Aç" + } } } }, @@ -5184,6 +34368,102 @@ "state": "translated", "value": "現在のディレクトリを Xcode で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Xcode 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Xcode 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Xcode에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Xcode öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Xcode" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Xcode" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Xcode" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Xcode" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Xcode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Xcode" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Xcode" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Xcode" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Xcode" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Xcode" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Xcode" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Xcode'da Aç" + } } } }, @@ -5201,6 +34481,102 @@ "state": "translated", "value": "現在のディレクトリを Zed で開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 Zed 中打开当前目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 Zed 中開啟目前目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 디렉토리를 Zed에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktuelles Verzeichnis in Zed öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir directorio actual en Zed" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le répertoire courant dans Zed" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri la directory corrente in Zed" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn nuværende mappe i Zed" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz bieżący katalog w Zed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть текущий каталог в Zed" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori trenutni direktorij u Zed" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الدليل الحالي في Zed" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne gjeldende katalog i Zed" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Diretório Atual no Zed" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดไดเรกทอรีปัจจุบันใน Zed" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli Dizini Zed'de Aç" + } } } }, @@ -5218,6 +34594,102 @@ "state": "translated", "value": "設定…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "偏好设置..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "偏好設定..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "환경설정…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Preferencias…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Préférences..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Preferenze…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indstillinger…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Preferencje…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Postavke…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التفضيلات…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innstillinger …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Preferências…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การตั้งค่า..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tercihler…" + } } } }, @@ -5235,6 +34707,102 @@ "state": "translated", "value": "cmux を終了" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "退出 cmux" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "結束 cmux" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 종료" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux beenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Salir de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Quitter cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Esci da cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Afslut cmux" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zakończ cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Завершить cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori cmux" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إنهاء cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avslutt cmux" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Encerrar o cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ออกจาก cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux'tan Çık" + } } } }, @@ -5252,6 +34820,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri Göster" + } } } }, @@ -5269,6 +34933,102 @@ "state": "translated", "value": "フォーカスログをコピー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "拷贝焦点日志" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "拷貝焦點記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "포커스 로그 복사" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fokus-Protokolle kopieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Copiar registros de enfoque" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Copier les journaux de focus" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Copia log di focus" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kopier fokuslogfiler" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kopiuj dzienniki fokusu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать журнал фокуса" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kopiraj logove fokusa" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نسخ سجلات التركيز" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kopier fokuslogger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Copiar Logs de Foco" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คัดลอกบันทึกการโฟกัส" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Odak Günlüklerini Kopyala" + } } } }, @@ -5286,6 +35046,102 @@ "state": "translated", "value": "アップデートログをコピー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "拷贝更新日志" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "拷貝更新記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 로그 복사" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update-Protokolle kopieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Copiar registros de actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Copier les journaux de mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Copia log aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kopier opdateringslogfiler" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kopiuj dzienniki aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать журнал обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kopiraj logove ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نسخ سجلات التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kopier oppdateringslogger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Copiar Logs de Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คัดลอกบันทึกการอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Günlüklerini Kopyala" + } } } }, @@ -5303,6 +35159,102 @@ "state": "translated", "value": "アップデートログ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新日志" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 로그" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update-Protokolle" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Registros de actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Journaux de mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Log aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringslogfiler" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dzienniki aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Журналы обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Logovi ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سجلات التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringslogger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Logs de Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "บันทึกการอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Günlükleri" + } } } }, @@ -5320,6 +35272,102 @@ "state": "translated", "value": "実際のサイズ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "实际大小" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "實際大小" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "실제 크기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Originalgröße" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tamaño real" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Taille réelle" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dimensione reale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Faktisk størrelse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Rozmiar rzeczywisty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фактический размер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Stvarna veličina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الحجم الفعلي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Faktisk størrelse" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tamanho Real" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ขนาดจริง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Gerçek Boyut" + } } } }, @@ -5337,6 +35385,102 @@ "state": "translated", "value": "戻る" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "后退" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "뒤로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurück" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Atrás" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Indietro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tilbage" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wstecz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Назад" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nazad" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "رجوع" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilbake" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Voltar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้อนกลับ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geri" + } } } }, @@ -5354,6 +35498,102 @@ "state": "translated", "value": "ブラウザ履歴をクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除浏览器历史记录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除瀏覽記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 기록 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browserverlauf löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar historial del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer l'historique du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella cronologia browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd browserhistorik" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść historię przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить историю браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši historiju preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح سجل المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tøm nettleserhistorikk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Histórico do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างประวัติเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Geçmişini Temizle" + } } } }, @@ -5371,6 +35611,102 @@ "state": "translated", "value": "進む" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "前进" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앞으로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vor" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Adelante" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Avanti" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Frem" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Do przodu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вперед" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Naprijed" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقدم" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fremover" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Avançar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไปข้างหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İleri" + } } } }, @@ -5388,6 +35724,102 @@ "state": "translated", "value": "最新の未読にジャンプ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳转到最新未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跳至最新未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 읽지 않은 항목으로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur letzten ungelesenen springen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir a la última no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller au dernier message non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'ultimo non letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til seneste ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do najnowszego nieprzeczytanego" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к последнему непрочитанному" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Skoči na najnovije nepročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى أحدث غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til siste uleste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Última Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้ามไปยังรายการยังไม่อ่านล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son Okunmamışa Atla" + } } } }, @@ -5405,6 +35837,102 @@ "state": "translated", "value": "次のサーフェス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个 Surface" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一個 Surface" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 화면" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächste Oberfläche" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente superficie" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Surface suivante" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Superficie successiva" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste overflade" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następna powierzchnia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующая поверхность" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeća površina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السطح التالي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste flate" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próxima Superfície" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นผิวถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonraki Yüzey" + } } } }, @@ -5422,6 +35950,102 @@ "state": "translated", "value": "次のワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一個工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro successiva" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następna przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующее рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeći radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل التالية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próxima Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonraki Çalışma Alanı" + } } } }, @@ -5439,6 +36063,102 @@ "state": "translated", "value": "前のサーフェス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个 Surface" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一個 Surface" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 화면" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorherige Oberfläche" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Superficie anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Surface précédente" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Superficie precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige overflade" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednia powierzchnia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущая поверхность" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodna površina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السطح السابق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige flate" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Superfície Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นผิวก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önceki Yüzey" + } } } }, @@ -5456,6 +36176,102 @@ "state": "translated", "value": "前のワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一個工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriger Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednia przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущее рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodni radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل السابقة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önceki Çalışma Alanı" + } } } }, @@ -5473,6 +36289,102 @@ "state": "translated", "value": "ページを再読み込み" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载页面" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入頁面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "페이지 새로고침" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seite neu laden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Recargar página" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recharger la page" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ricarica pagina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genindlæs side" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Odśwież stronę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезагрузить страницу" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo učitaj stranicu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تحميل الصفحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Last inn siden på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Recarregar Página" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหลดหน้าใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sayfayı Yeniden Yükle" + } } } }, @@ -5490,6 +36402,102 @@ "state": "translated", "value": "ワークスペースの名称変更…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır…" + } } } }, @@ -5507,6 +36515,102 @@ "state": "translated", "value": "JavaScriptコンソールを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示 JavaScript 控制台" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示 JavaScript 主控台" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "JavaScript 콘솔 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "JavaScript-Konsole anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar consola de JavaScript" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la console JavaScript" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra console JavaScript" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis JavaScript-konsol" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż konsolę JavaScript" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать консоль JavaScript" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži JavaScript konzolu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض وحدة تحكم JavaScript" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis JavaScript-konsoll" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Console JavaScript" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงคอนโซล JavaScript" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "JavaScript Konsolunu Göster" + } } } }, @@ -5524,6 +36628,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri Göster" + } } } }, @@ -5541,6 +36741,102 @@ "state": "translated", "value": "ブラウザを下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Aşağı Böl" + } } } }, @@ -5558,6 +36854,102 @@ "state": "translated", "value": "ブラウザを右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Sağa Böl" + } } } }, @@ -5575,6 +36967,102 @@ "state": "translated", "value": "下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağı Böl" + } } } }, @@ -5592,6 +37080,102 @@ "state": "translated", "value": "右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sağa Böl" + } } } }, @@ -5609,6 +37193,102 @@ "state": "translated", "value": "デベロッパツールの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换开发者工具" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換開發者工具" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "개발자 도구 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Entwicklerwerkzeuge ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar herramientas de desarrollo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer les outils de développement" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva Strumenti sviluppatore" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå udviklerværktøjer til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz narzędzia deweloperskie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Инструменты разработчика" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci razvojne alate" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل أدوات المطور" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå utviklerverktøy av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Ferramentas do Desenvolvedor" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับเครื่องมือนักพัฒนา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geliştirici Araçlarını Aç/Kapat" + } } } }, @@ -5626,6 +37306,102 @@ "state": "translated", "value": "サイドバーの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå sidebjælke til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Боковая панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci bočnu traku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå sidepanelet av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunu Aç/Kapat" + } } } }, @@ -5643,6 +37419,102 @@ "state": "translated", "value": "ワークスペース %lld" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区 %lld" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區 %lld" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 %lld" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich %lld" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo %lld" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail %lld" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro %lld" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde %lld" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza %lld" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство %lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor %lld" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل %lld" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde %lld" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho %lld" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ %lld" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı %lld" + } } } }, @@ -5660,6 +37532,102 @@ "state": "translated", "value": "拡大" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "放大" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "放大" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "확대" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einzoomen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ampliar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Zoom avant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ingrandisci" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Zoom ind" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiększ" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Увеличить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Uvećaj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تكبير" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Zoom inn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aumentar Zoom" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ซูมเข้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yakınlaştır" + } } } }, @@ -5677,6 +37645,102 @@ "state": "translated", "value": "縮小" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "缩小" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "縮小" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "축소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Auszoomen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reducir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Zoom arrière" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riduci" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Zoom ud" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pomniejsz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уменьшить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Umanji" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تصغير" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Zoom ut" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Diminuir Zoom" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ซูมออก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uzaklaştır" + } } } }, @@ -5694,6 +37758,102 @@ "state": "translated", "value": "ウインドウ %lld" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口 %lld" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗 %lld" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 %lld" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster %lld" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana %lld" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre %lld" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra %lld" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vindue %lld" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Okno %lld" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно %lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prozor %lld" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النافذة %lld" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vindu %lld" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela %lld" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง %lld" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere %lld" + } } } }, @@ -5711,6 +37871,102 @@ "state": "translated", "value": "すべてクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全部清除" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除全部" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모두 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar todo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Tout effacer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella tutto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd alle" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść wszystko" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить все" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši sve" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح الكل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern alle" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Tudo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tümünü Temizle" + } } } }, @@ -5728,6 +37984,102 @@ "state": "translated", "value": "デスクトップ通知がここに表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "桌面通知将在此处显示,方便快速查看。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "桌面通知將在這裡顯示,方便您快速查看。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "데스크톱 알림이 여기에 표시되어 빠르게 확인할 수 있습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Desktop-Benachrichtigungen werden hier zur schnellen Überprüfung angezeigt." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Las notificaciones de escritorio aparecerán aquí para su revisión rápida." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les notifications de bureau apparaîtront ici pour une consultation rapide." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Le notifiche desktop appariranno qui per una rapida consultazione." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skrivebordsnotifikationer vises her til hurtig gennemgang." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia pulpitu będą się tu pojawiać do szybkiego przeglądu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления рабочего стола будут отображаться здесь для быстрого просмотра." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja na radnoj površini će se pojavljivati ovdje radi brzog pregleda." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ستظهر إشعارات سطح المكتب هنا للمراجعة السريعة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skrivebordsvarsler vises her for rask gjennomgang." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "As notificações da área de trabalho aparecerão aqui para revisão rápida." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือนเดสก์ท็อปจะปรากฏที่นี่เพื่อตรวจสอบอย่างรวดเร็ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Masaüstü bildirimleri hızlı inceleme için burada görünecek." + } } } }, @@ -5745,6 +38097,102 @@ "state": "translated", "value": "デスクトップ通知がここに表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "桌面通知将在此处显示。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "桌面通知將在這裡顯示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "데스크톱 알림이 여기에 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Desktop-Benachrichtigungen werden hier angezeigt." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Las notificaciones de escritorio aparecerán aquí." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les notifications de bureau apparaîtront ici." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Le notifiche desktop appariranno qui." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skrivebordsnotifikationer vises her." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia pulpitu będą się tu pojawiać." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления рабочего стола будут отображаться здесь." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja na radnoj površini će se pojavljivati ovdje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ستظهر إشعارات سطح المكتب هنا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skrivebordsvarsler vises her." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "As notificações da área de trabalho aparecerão aqui." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือนเดสก์ท็อปจะปรากฏที่นี่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Masaüstü bildirimleri burada görünecek." + } } } }, @@ -5762,6 +38210,102 @@ "state": "translated", "value": "まだ通知はありません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "暂无通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尚無通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아직 알림이 없습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch keine Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Aún no hay notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune notification pour le moment" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna notifica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen notifikationer endnu" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak powiadomień" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомлений пока нет" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Još nema obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا توجد إشعارات بعد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen varsler ennå" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma notificação ainda" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยังไม่มีการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Henüz bildirim yok" + } } } }, @@ -5779,6 +38323,102 @@ "state": "translated", "value": "最新の未読にジャンプ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳转到最新未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跳至最新未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 읽지 않은 항목으로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur letzten ungelesenen springen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir a la última no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller au dernier message non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'ultimo non letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til seneste ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do najnowszego nieprzeczytanego" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к последнему непрочитанному" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Skoči na najnovije nepročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى أحدث غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til siste uleste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Última Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้ามไปยังรายการยังไม่อ่านล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son Okunmamışa Atla" + } } } }, @@ -5796,6 +38436,102 @@ "state": "translated", "value": "通知" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimler" + } } } }, @@ -5813,6 +38549,102 @@ "state": "translated", "value": "タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekme" + } } } }, @@ -5830,6 +38662,102 @@ "state": "translated", "value": "開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aç" + } } } }, @@ -5847,6 +38775,102 @@ "state": "translated", "value": "フォルダを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开文件夹" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟資料夾" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "폴더 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ordner öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir carpeta" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir un dossier" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri cartella" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn mappe" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz folder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть папку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori folder" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح مجلد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne mappe" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Pasta" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดโฟลเดอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Klasör Aç" + } } } }, @@ -5864,6 +38888,102 @@ "state": "translated", "value": "閉じる (Esc)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭 (Esc)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉 (Esc)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "닫기 (Esc)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Schließen (Esc)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar (Esc)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer (Échap)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi (Esc)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk (Esc)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij (Esc)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть (Esc)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori (Esc)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق (Esc)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk (Esc)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar (Esc)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิด (Esc)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapat (Esc)" + } } } }, @@ -5881,6 +39001,102 @@ "state": "translated", "value": "次の一致 (Return)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个匹配项 (Return)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一個符合項目 (Return)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 일치 (Return)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Treffer (Eingabetaste)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente coincidencia (Return)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Résultat suivant (Entrée)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Risultato successivo (Invio)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste match (Return)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następne dopasowanie (Return)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующее совпадение (Return)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeći rezultat (Return)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التطابق التالي (Return)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste treff (Return)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próximo resultado (Return)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ผลลัพธ์ถัดไป (Return)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonraki eşleşme (Return)" + } } } }, @@ -5898,6 +39114,102 @@ "state": "translated", "value": "検索" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "搜索" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "搜尋" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "검색" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rechercher" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søg" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szukaj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pretraži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Søk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pesquisar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ara" + } } } }, @@ -5915,6 +39227,102 @@ "state": "translated", "value": "前の一致 (Shift+Return)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个匹配项 (Shift+Return)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一個符合項目 (Shift+Return)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 일치 (Shift+Return)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriger Treffer (Umschalt+Eingabetaste)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Coincidencia anterior (Shift+Return)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Résultat précédent (Maj+Entrée)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Risultato precedente (Maiusc+Invio)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige match (Shift+Return)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednie dopasowanie (Shift+Return)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущее совпадение (Shift+Return)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodni rezultat (Shift+Return)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التطابق السابق (Shift+Return)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige treff (Shift+Return)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Resultado anterior (Shift+Return)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ผลลัพธ์ก่อนหน้า (Shift+Return)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önceki eşleşme (Shift+Return)" + } } } }, @@ -5932,6 +39340,102 @@ "state": "translated", "value": "アプリアイコン" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "应用图标" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "App 圖示" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앱 아이콘" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "App-Symbol" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ícono de la app" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Icône de l'app" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Icona app" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Appikon" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ikona aplikacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Значок приложения" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ikona aplikacije" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أيقونة التطبيق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Appikon" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ícone do App" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไอคอนแอป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygulama Simgesi" + } } } }, @@ -5949,6 +39453,102 @@ "state": "translated", "value": "Dockバッジ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "程序坞角标" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Dock 標記" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Dock 배지" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dock-Badge" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Insignia del Dock" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Badge du Dock" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Badge del Dock" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dock-badge" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Plakietka w Docku" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Значок Dock" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Oznaka na Docku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "شارة Dock" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dock-merke" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Emblema do Dock" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้าย Dock" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Dock Rozeti" + } } } }, @@ -5966,6 +39566,102 @@ "state": "translated", "value": "アプリアイコン(DockおよびCmd+Tab)に未読数を表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在应用图标上显示未读计数(程序坞和 Cmd+Tab)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 App 圖示上顯示未讀數量(Dock 和 Cmd+Tab)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앱 아이콘(Dock 및 Cmd+Tab)에 읽지 않은 수를 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ungelesene Anzahl auf dem App-Symbol anzeigen (Dock und Cmd+Tab)." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar el recuento de no leídos en el ícono de la app (Dock y Cmd+Tab)." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher le nombre de messages non lus sur l'icône de l'app (Dock et Cmd+Tab)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra il conteggio non letti sull'icona dell'app (Dock e Cmd+Tab)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis antal ulæste på appikonet (Dock og Cmd+Tab)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż liczbę nieprzeczytanych na ikonie aplikacji (Dock i Cmd+Tab)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать количество непрочитанных на значке приложения (Dock и Cmd+Tab)." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži broj nepročitanih na ikoni aplikacije (Dock i Cmd+Tab)." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض عدد غير المقروء على أيقونة التطبيق (Dock و Cmd+Tab)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis antall uleste på appikonet (Dock og Cmd+Tab)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar contagem de não lidos no ícone do app (Dock e Cmd+Tab)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงจำนวนยังไม่อ่านบนไอคอนแอป (Dock และ Cmd+Tab)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygulama simgesinde (Dock ve Cmd+Tab) okunmamış sayısını göster." + } } } }, @@ -5983,6 +39679,102 @@ "state": "translated", "value": "言語" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "语言" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "語言" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "언어" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Sprache" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Idioma" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Langue" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Lingua" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sprog" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Język" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Jezik" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اللغة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Språk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Idioma" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ภาษา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Dil" + } } } }, @@ -6000,6 +39792,102 @@ "state": "translated", "value": "今すぐ再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "立即重启" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "立即重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "지금 재시작" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Jetzt neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar ahora" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer maintenant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia ora" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart nu" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie teraz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить сейчас" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokreni sada" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التشغيل الآن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start på nytt nå" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar Agora" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตเดี๋ยวนี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Şimdi Yeniden Başlat" + } } } }, @@ -6017,6 +39905,102 @@ "state": "translated", "value": "後で" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "稍后" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "稍後" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "나중에" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Später" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Más tarde" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Plus tard" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Più tardi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Senere" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Później" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Позже" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kasnije" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لاحقًا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Senere" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Depois" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha Sonra" + } } } }, @@ -6034,6 +40018,102 @@ "state": "translated", "value": "言語変更を適用するために再起動しますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重启以应用语言更改?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要重新啟動以套用語言變更嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "언어 변경을 적용하려면 재시작하시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neu starten, um Sprachänderung zu übernehmen?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Reiniciar para aplicar el cambio de idioma?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer pour appliquer le changement de langue ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavviare per applicare il cambio di lingua?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart for at anvende sprogændring?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchomić ponownie, aby zastosować zmianę języka?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить для применения языка?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokrenuti za primjenu promjene jezika?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التشغيل لتطبيق تغيير اللغة؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Starte på nytt for å endre språk?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar para aplicar a mudança de idioma?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตเพื่อเปลี่ยนภาษาหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Dil değişikliğini uygulamak için yeniden başlatılsın mı?" + } } } }, @@ -6051,6 +40131,102 @@ "state": "translated", "value": "cmuxを再起動して適用" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重启 cmux 以应用" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新啟動 cmux 以套用" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "적용하려면 cmux를 재시작하세요" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux neu starten, um Änderung zu übernehmen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reinicia cmux para aplicar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrez cmux pour appliquer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia cmux per applicare" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart cmux for at anvende" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie cmux, aby zastosować" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустите cmux для применения" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokrenite cmux za primjenu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أعد تشغيل cmux للتطبيق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start cmux på nytt for å bruke" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reinicie o cmux para aplicar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ต cmux เพื่อนำไปใช้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygulamak için cmux'u yeniden başlatın" + } } } }, @@ -6068,6 +40244,102 @@ "state": "translated", "value": "新規ワークスペースの配置" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新工作区位置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新工作區位置" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간 위치" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Platzierung neuer Arbeitsbereiche" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ubicación de nuevo espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Emplacement des nouveaux espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Posizionamento nuova area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Placering af nyt arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Umieszczenie nowej przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Расположение нового рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pozicija novog radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "موضع مساحة العمل الجديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Plassering av nytt arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Posição da Nova Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตำแหน่งเวิร์กสเปซใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Çalışma Alanı Konumu" + } } } }, @@ -6085,6 +40357,102 @@ "state": "translated", "value": "サイドバーのPRリンクをcmuxブラウザで開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 浏览器中打开侧边栏 PR 链接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 瀏覽器中開啟側邊欄 PR 連結" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 PR 링크를 cmux 브라우저에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleisten-PR-Links im cmux-Browser öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir enlaces de PR de la barra lateral en el navegador de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir les liens PR de la barre latérale dans le navigateur cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri link PR della barra laterale nel browser cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn sidebjælkens PR-links i cmux-browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwieraj linki PR z paska bocznego w przeglądarce cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открывать ссылки PR боковой панели в браузере cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori PR linkove iz bočne trake u cmux pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح روابط طلبات السحب في متصفح cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne sidepanel-PR-lenker i cmux-nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Links de PR da Barra Lateral no Navegador do cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดลิงก์ PR ในแถบด้านข้างด้วยเบราว์เซอร์ cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğu PR Bağlantılarını cmux Tarayıcısında Aç" + } } } }, @@ -6102,6 +40470,102 @@ "state": "translated", "value": "クリックするとデフォルトブラウザで開きます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "点击在默认浏览器中打开。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "點擊會在您的預設瀏覽器中開啟。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "클릭하면 기본 브라우저에서 열립니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Klicks öffnen in Ihrem Standardbrowser." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los clics abren en tu navegador predeterminado." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les clics ouvrent dans votre navigateur par défaut." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "I clic aprono nel browser predefinito." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Klik åbner i din standardbrowser." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kliknięcia otwierają w domyślnej przeglądarce." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ссылки открываются в браузере по умолчанию." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Klikovi se otvaraju u podrazumijevanom pregledniku." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النقرات تفتح في متصفحك الافتراضي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Klikk åpner i standard nettleser." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cliques abrem no seu navegador padrão." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คลิกจะเปิดในเบราว์เซอร์เริ่มต้นของคุณ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tıklamalar varsayılan tarayıcınızda açılır." + } } } }, @@ -6119,6 +40583,102 @@ "state": "translated", "value": "クリックするとcmuxブラウザ内で開きます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "点击在 cmux 浏览器中打开。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "點擊會在 cmux 瀏覽器中開啟。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "클릭하면 cmux 브라우저에서 열립니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Klicks öffnen im cmux-Browser." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los clics abren dentro del navegador de cmux." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les clics ouvrent dans le navigateur cmux." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "I clic aprono nel browser cmux." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Klik åbner i cmux-browseren." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kliknięcia otwierają w przeglądarce cmux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ссылки открываются во встроенном браузере cmux." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Klikovi se otvaraju unutar cmux preglednika." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النقرات تفتح داخل متصفح cmux." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Klikk åpner i cmux-nettleseren." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cliques abrem dentro do navegador do cmux." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คลิกจะเปิดในเบราว์เซอร์ cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tıklamalar cmux tarayıcısında açılır." + } } } }, @@ -6136,6 +40696,102 @@ "state": "translated", "value": "名称変更時に既存の名前を選択" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名时选中现有名称" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名時選取現有名稱" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이름 변경 시 기존 이름 선택" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Umbenennen wählt vorhandenen Namen aus" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar selecciona el nombre existente" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le renommage sélectionne le nom existant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina seleziona il nome esistente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøbning markerer eksisterende navn" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmiana nazwy zaznacza istniejącą nazwę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименование выделяет имя" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenovanje odabere postojeći naziv" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التسمية تحدد الاسم الحالي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Omdøping velger eksisterende navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Seleciona o Nome Existente" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลือกชื่อเมื่อเปลี่ยนชื่อ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeniden Adlandırma Mevcut Adı Seçer" + } } } }, @@ -6153,6 +40809,102 @@ "state": "translated", "value": "コマンドパレットの名称変更ではキャレットが末尾に置かれます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "命令面板重命名时光标保持在末尾。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "指令面板重新命名時,游標保持在結尾。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "명령어 팔레트의 이름 변경 시 커서가 끝에 위치합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Umbenennen in der Befehlspalette bleibt der Cursor am Ende." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar en la paleta de comandos mantiene el cursor al final." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le renommage via la palette de commandes maintient le curseur à la fin." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La rinomina nella Tavolozza comandi mantiene il cursore alla fine." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøbning i kommandopaletten beholder markøren til sidst." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmiana nazwy w palecie poleceń pozostawia kursor na końcu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименование в палитре команд оставляет курсор в конце." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenovanje u paleti naredbi zadržava kursor na kraju." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التسمية في لوحة الأوامر تبقي المؤشر في النهاية." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Omdøping i kommandopaletten beholder markøren på slutten." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A renomeação na Paleta de Comandos mantém o cursor no final." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเปลี่ยนชื่อในแถบคำสั่งจะเก็บเคอร์เซอร์ไว้ที่ท้าย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Komut Paleti yeniden adlandırması imleci sonda tutar." + } } } }, @@ -6170,6 +40922,102 @@ "state": "translated", "value": "コマンドパレットの名称変更ではテキスト全体が選択された状態で始まります。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "命令面板重命名时全选文本。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "指令面板重新命名時,會先選取所有文字。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "명령어 팔레트의 이름 변경 시 전체 텍스트가 선택됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Umbenennen in der Befehlspalette wird der gesamte Text ausgewählt." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar en la paleta de comandos inicia con todo el texto seleccionado." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le renommage via la palette de commandes commence avec tout le texte sélectionné." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La rinomina nella Tavolozza comandi inizia con tutto il testo selezionato." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøbning i kommandopaletten starter med al tekst markeret." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmiana nazwy w palecie poleceń rozpoczyna z zaznaczonym całym tekstem." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименование в палитре команд начинается с выделения всего текста." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenovanje u paleti naredbi počinje sa svim odabranim tekstom." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التسمية في لوحة الأوامر تبدأ بتحديد كل النص." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Omdøping i kommandopaletten starter med all tekst valgt." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A renomeação na Paleta de Comandos começa com todo o texto selecionado." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเปลี่ยนชื่อในแถบคำสั่งจะเลือกข้อความทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Komut Paleti yeniden adlandırması tüm metin seçili olarak başlar." + } } } }, @@ -6187,6 +41035,102 @@ "state": "translated", "value": "通知時に並べ替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "收到通知时重新排序" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "收到通知時重新排序" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 시 순서 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bei Benachrichtigung neu sortieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reordenar al recibir notificación" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réordonner à la notification" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riordina alla notifica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omarranger ved notifikation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmiana kolejności przy powiadomieniu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перемещение при уведомлении" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prerasporedi pri obavještenju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة الترتيب عند الإشعار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Omorganiser ved varsel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reordenar ao Receber Notificação" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "จัดเรียงใหม่เมื่อมีการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimde Yeniden Sırala" + } } } }, @@ -6204,6 +41148,102 @@ "state": "translated", "value": "通知を受け取ったワークスペースを一番上に移動します。ショートカット位置を固定するには無効にしてください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "收到通知时将工作区移至顶部。禁用以保持快捷键位置稳定。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "收到通知時將工作區移至最上方。停用此選項可維持穩定的快捷鍵位置。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림을 받은 작업 공간을 맨 위로 이동합니다. 단축키 위치를 고정하려면 비활성화하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereiche nach oben verschieben, wenn sie eine Benachrichtigung erhalten. Deaktivieren Sie dies für stabile Tastenkürzel-Positionen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover espacios de trabajo al inicio cuando reciben una notificación. Desactiva para posiciones de atajo estables." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer les espaces de travail en haut lorsqu'ils reçoivent une notification. Désactivez pour des positions de raccourcis stables." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta le aree di lavoro in cima quando ricevono una notifica. Disabilita per posizioni di scorciatoia stabili." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt arbejdsområder til toppen, når de modtager en notifikation. Deaktiver for stabile genvejspositioner." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenoś przestrzenie robocze na górę, gdy otrzymają powiadomienie. Wyłącz, aby zachować stałe pozycje skrótów." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перемещать рабочие пространства наверх при получении уведомления. Отключите для стабильных позиций сочетаний клавиш." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pomjeri radne prostore na vrh kada prime obavještenje. Onemogućite za stabilne pozicije prečica." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل مساحات العمل إلى الأعلى عند تلقي إشعار. قم بالتعطيل للحفاظ على مواضع الاختصارات الثابتة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt arbeidsområder til toppen når de mottar et varsel. Deaktiver for stabile snarveiposisjoner." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Move áreas de trabalho para o topo ao receber uma notificação. Desative para posições de atalho estáveis." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้ายเวิร์กสเปซไปด้านบนเมื่อได้รับการแจ้งเตือน ปิดใช้งานเพื่อให้ตำแหน่งทางลัดคงที่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirim aldıklarında çalışma alanlarını en üste taşı. Sabit kısayol konumları için devre dışı bırakın." + } } } }, @@ -6221,6 +41261,102 @@ "state": "translated", "value": "サイドバーにブランチ+ディレクトリを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏显示分支和目录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄顯示分支與目錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 브랜치 + 디렉토리 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Branch + Verzeichnis in Seitenleiste anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar rama + directorio en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la branche et le répertoire dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra branch e directory nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis gren + mappe i sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż gałąź + katalog na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать ветку и каталог в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži granu i direktorij u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الفرع والدليل في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis gren + katalog i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Branch + Diretório na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงสาขา + ไดเรกทอรีในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunda Dal + Dizin Göster" + } } } }, @@ -6238,6 +41374,102 @@ "state": "translated", "value": "組み込みのgitブランチと作業ディレクトリの行を表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示内置的 Git 分支和工作目录行。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示內建的 git 分支及工作目錄列。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본 제공 git 브랜치 및 작업 디렉토리 행을 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die integrierte Git-Branch- und Arbeitsverzeichniszeile anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar la fila integrada de rama git y directorio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la ligne de branche git et de répertoire de travail intégrée." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza la riga integrata con il branch git e la directory di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis den indbyggede git-gren og arbejdsmapperækken." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyświetlaj wbudowany wiersz gałęzi git i katalogu roboczego." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отображать встроенную строку ветки git и рабочего каталога." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži ugrađeni red za git granu i radni direktorij." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض صف فرع git المدمج ودليل العمل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis den innebygde git-grenen og arbeidskatalograden." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exibir a linha integrada de branch git e diretório de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงแถวสาขา git และไดเรกทอรีทำงานในตัว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yerleşik git dalı ve çalışma dizini satırını göster." + } } } }, @@ -6255,6 +41487,102 @@ "state": "translated", "value": "サイドバーに最新ログを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏显示最新日志" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄顯示最新記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 최신 로그 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Letztes Protokoll in Seitenleiste anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar último registro en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher le dernier journal dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra ultimo log nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis seneste log i sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż najnowszy dziennik na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать последний журнал в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži najnoviji log u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض أحدث سجل في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis siste logg i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Último Log na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงบันทึกล่าสุดในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunda Son Günlüğü Göster" + } } } }, @@ -6272,6 +41600,102 @@ "state": "translated", "value": "最新の命令型ログ/ステータスメッセージを表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示最新的命令式日志/状态消息。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示最新的命令式記錄或狀態訊息。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 명령형 로그/상태 메시지를 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die letzte imperative Protokoll-/Statusmeldung anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar el último mensaje imperativo de registro/estado." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher le dernier message de journal/statut impératif." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza l'ultimo messaggio di log/stato imperativo." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis den seneste imperative log/statusmeddelelse." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyświetlaj najnowszy komunikat dziennika/statusu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отображать последнее служебное сообщение журнала/статуса." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži najnoviju imperativnu log/statusnu poruku." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض أحدث رسالة سجل/حالة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis den siste imperative logg-/statusmeldingen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exibir a última mensagem imperativa de log/status." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงข้อความบันทึก/สถานะล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son zorunlu günlük/durum mesajını göster." + } } } }, @@ -6289,6 +41713,102 @@ "state": "translated", "value": "サイドバーにカスタムメタデータを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏显示自定义元数据" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄顯示自訂中繼資料" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 사용자 지정 메타데이터 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzerdefinierte Metadaten in Seitenleiste anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar metadatos personalizados en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les métadonnées personnalisées dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra metadati personalizzati nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis brugerdefinerede metadata i sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż własne metadane na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать метаданные в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži prilagođene metapodatke u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض البيانات الوصفية المخصصة في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis egendefinerte metadata i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Metadados Personalizados na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงข้อมูลเมตาที่กำหนดเองในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunda Özel Üst Veriyi Göster" + } } } }, @@ -6306,6 +41826,102 @@ "state": "translated", "value": "report_meta/set_statusおよびreport_meta_blockからのカスタムメタデータを表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示来自 report_meta/set_status 和 report_meta_block 的自定义元数据。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示來自 report_meta/set_status 和 report_meta_block 的自訂中繼資料。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "report_meta/set_status 및 report_meta_block의 사용자 지정 메타데이터를 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzerdefinierte Metadaten von report_meta/set_status und report_meta_block anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar metadatos personalizados de report_meta/set_status y report_meta_block." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les métadonnées personnalisées de report_meta/set_status et report_meta_block." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza metadati personalizzati da report_meta/set_status e report_meta_block." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis brugerdefinerede metadata fra report_meta/set_status og report_meta_block." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyświetlaj własne metadane z report_meta/set_status i report_meta_block." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отображать пользовательские метаданные из report_meta/set_status и report_meta_block." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži prilagođene metapodatke iz report_meta/set_status i report_meta_block." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض البيانات الوصفية المخصصة من report_meta/set_status و report_meta_block." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis egendefinerte metadata fra report_meta/set_status og report_meta_block." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exibir metadados personalizados de report_meta/set_status e report_meta_block." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงข้อมูลเมตาที่กำหนดเองจาก report_meta/set_status และ report_meta_block" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "report_meta/set_status ve report_meta_block'tan özel üst veriyi göster." + } } } }, @@ -6323,6 +41939,102 @@ "state": "translated", "value": "サイドバーにリスニングポートを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏显示监听端口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄顯示監聽連接埠" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 수신 대기 포트 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Lauschende Ports in Seitenleiste anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar puertos en escucha en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les ports en écoute dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra porte in ascolto nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis lyttende porte i sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż nasłuchujące porty na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать прослушиваемые порты в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži osluškivane portove u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض المنافذ النشطة في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis lyttende porter i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Portas em Escuta na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงพอร์ตที่กำลังรับฟังในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunda Dinlenen Portları Göster" + } } } }, @@ -6340,6 +42052,102 @@ "state": "translated", "value": "アクティブなワークスペースで検出されたリスニングポートを表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示活动工作区检测到的监听端口。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示使用中工作區偵測到的監聽連接埠。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "활성 작업 공간에서 감지된 수신 대기 포트를 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Erkannte lauschende Ports für den aktiven Arbeitsbereich anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar los puertos en escucha detectados para el espacio de trabajo activo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les ports en écoute détectés pour l'espace de travail actif." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza le porte in ascolto rilevate per l'area di lavoro attiva." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis registrerede lyttende porte for det aktive arbejdsområde." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyświetlaj wykryte nasłuchujące porty dla aktywnej przestrzeni roboczej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отображать обнаруженные прослушиваемые порты для активного рабочего пространства." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži otkrivene osluškivane portove za aktivni radni prostor." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض المنافذ المكتشفة لمساحة العمل النشطة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis oppdagede lyttende porter for det aktive arbeidsområdet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exibir portas em escuta detectadas para a área de trabalho ativa." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงพอร์ตที่ตรวจพบสำหรับเวิร์กสเปซที่ใช้งานอยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Etkin çalışma alanı için algılanan dinlenen portları göster." + } } } }, @@ -6357,6 +42165,102 @@ "state": "translated", "value": "サイドバーに進捗を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏显示进度" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄顯示進度" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 진행 상황 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fortschritt in Seitenleiste anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar progreso en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la progression dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra progresso nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis fremskridt i sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż postęp na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать прогресс в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži napredak u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض التقدم في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis fremdrift i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Progresso na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงความคืบหน้าในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunda İlerlemeyi Göster" + } } } }, @@ -6374,6 +42278,102 @@ "state": "translated", "value": "set_progressによる組み込みプログレスバーを表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示来自 set_progress 的内置进度条。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示來自 set_progress 的內建進度列。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "set_progress의 기본 제공 진행 표시줄을 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Den integrierten Fortschrittsbalken von set_progress anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar la barra de progreso integrada de set_progress." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la barre de progression intégrée de set_progress." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza la barra di progresso integrata da set_progress." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis den indbyggede fremskridtslinje fra set_progress." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyświetlaj wbudowany pasek postępu z set_progress." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отображать встроенный индикатор прогресса из set_progress." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži ugrađenu traku napretka iz set_progress." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض شريط التقدم المدمج من set_progress." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis den innebygde fremdriftsindikatoren fra set_progress." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exibir a barra de progresso integrada de set_progress." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงแถบความคืบหน้าในตัวจาก set_progress" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "set_progress'ten yerleşik ilerleme çubuğunu göster." + } } } }, @@ -6391,6 +42391,102 @@ "state": "translated", "value": "サイドバーにプルリクエストを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏显示拉取请求" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄顯示 Pull Request" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Pull Request 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Pull Requests in Seitenleiste anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar pull requests en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les Pull Requests dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra Pull Request nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis pull requests i sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż pull requesty na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать запросы на слияние в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži pull zahtjeve u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض طلبات السحب في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis pull-forespørsler i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Pull Requests na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดง Pull Request ในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunda Çekme İsteklerini Göster" + } } } }, @@ -6408,6 +42504,102 @@ "state": "translated", "value": "ステータス、番号、クリック可能なリンク付きのレビュー項目(PR/MRなど)を表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示包含状态、编号和可点击链接的审查项(PR/MR 等)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示審查項目(PR/MR 等),包含狀態、編號和可點擊的連結。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "상태, 번호 및 클릭 가능한 링크가 있는 리뷰 항목(PR/MR 등)을 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Review-Elemente (PR/MR/etc.) mit Status, Nummer und anklickbarem Link anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar elementos de revisión (PR/MR/etc.) con estado, número y enlace interactivo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les éléments de revue (PR/MR/etc.) avec le statut, le numéro et un lien cliquable." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza elementi di revisione (PR/MR/ecc.) con stato, numero e link cliccabile." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis gennemgangselementer (PR/MR osv.) med status, nummer og klikbart link." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyświetlaj elementy przeglądu (PR/MR/itp.) ze statusem, numerem i klikalnym linkiem." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отображать элементы проверки (PR/MR и т.д.) со статусом, номером и ссылкой." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži stavke za pregled (PR/MR/itd.) sa statusom, brojem i linkom." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض عناصر المراجعة (طلبات السحب/الدمج/إلخ.) مع الحالة والرقم والرابط القابل للنقر." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis gjennomgangselementer (PR/MR/osv.) med status, nummer og klikkbar lenke." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exibir itens de revisão (PR/MR/etc.) com status, número e link clicável." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงรายการตรวจสอบ (PR/MR/อื่นๆ) พร้อมสถานะ หมายเลข และลิงก์ที่คลิกได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Durum, numara ve tıklanabilir bağlantıyla inceleme öğelerini (PR/MR/vb.) göster." + } } } }, @@ -6425,6 +42617,102 @@ "state": "translated", "value": "サイドバーのブランチレイアウト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "侧边栏分支布局" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "側邊欄分支版面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 브랜치 레이아웃" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleisten-Branch-Layout" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Disposición de ramas en la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Disposition des branches dans la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Layout branch nella barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sidebjælkens grenlayout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Układ gałęzi na pasku bocznym" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Макет веток в боковой панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raspored grana u bočnoj traci" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخطيط الفروع في الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Grenoppsett i sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Layout de Branch na Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เค้าโครงสาขาในแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğu Dal Düzeni" + } } } }, @@ -6442,6 +42730,102 @@ "state": "translated", "value": "インライン" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "单行" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "行內" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "인라인" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einzeilig" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "En línea" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "En ligne" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "In linea" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Inline" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "W linii" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В строку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "U redu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سطري" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innebygd" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Em Linha" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แบบอินไลน์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Satır İçi" + } } } }, @@ -6459,6 +42843,102 @@ "state": "translated", "value": "インライン: すべてのブランチが1行に表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "单行:所有分支共享一行。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "行內:所有分支共用一行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "인라인: 모든 브랜치가 한 줄에 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einzeilig: Alle Branches teilen sich eine Zeile." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "En línea: todas las ramas comparten una sola línea." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "En ligne : toutes les branches partagent une seule ligne." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "In linea: tutti i branch condividono una riga." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Inline: alle grene deler én linje." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "W linii: wszystkie gałęzie w jednym wierszu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В строку: все ветки в одной строке." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "U redu: sve grane dijele jednu liniju." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سطري: جميع الفروع تشترك في سطر واحد." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innebygd: alle grener deler én linje." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Em Linha: todas as branches compartilham uma linha." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แบบอินไลน์: สาขาทั้งหมดแสดงในบรรทัดเดียว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Satır İçi: tüm dallar tek satırı paylaşır." + } } } }, @@ -6476,6 +42956,102 @@ "state": "translated", "value": "縦: 各ブランチがそれぞれの行に表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "垂直:每个分支独占一行。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "垂直:每個分支獨立一行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세로: 각 브랜치가 별도의 줄에 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vertikal: Jeder Branch erscheint in einer eigenen Zeile." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Vertical: cada rama aparece en su propia línea." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Vertical : chaque branche apparaît sur sa propre ligne." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verticale: ogni branch appare sulla propria riga." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Lodret: hver gren vises på sin egen linje." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pionowo: każda gałąź w osobnym wierszu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вертикально: каждая ветка на отдельной строке." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Vertikalno: svaka grana se pojavljuje u svom redu." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عمودي: كل فرع يظهر في سطر خاص به." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vertikal: hver gren vises på sin egen linje." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Vertical: cada branch aparece em sua própria linha." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แบบแนวตั้ง: แต่ละสาขาแสดงในบรรทัดของตัวเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Dikey: her dal kendi satırında görünür." + } } } }, @@ -6493,6 +43069,102 @@ "state": "translated", "value": "縦" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "垂直" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "垂直" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세로" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vertikal" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Vertical" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Vertical" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verticale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Lodret" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pionowo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вертикально" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Vertikalno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عمودي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vertikal" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Vertical" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แนวตั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Dikey" + } } } }, @@ -6510,6 +43182,102 @@ "state": "translated", "value": "匿名のテレメトリを送信" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "发送匿名遥测数据" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "傳送匿名遙測資料" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "익명 원격 측정 전송" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Anonyme Telemetriedaten senden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enviar telemetría anónima" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Envoyer des données de télémétrie anonymes" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Invia telemetria anonima" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Send anonym telemetri" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wysyłaj anonimową telemetrię" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять анонимную телеметрию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Šalji anonimnu telemetriju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إرسال بيانات تحليلية مجهولة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Send anonym telemetri" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Enviar telemetria anônima" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ส่งข้อมูลการวิเคราะห์แบบไม่ระบุตัวตน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Anonim telemetri gönder" + } } } }, @@ -6527,6 +43295,102 @@ "state": "translated", "value": "cmuxの改善に役立てるため、匿名化されたクラッシュおよび使用状況データを共有します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "分享匿名的崩溃和使用数据,帮助改进 cmux。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "分享匿名的當機與使用資料,以協助改善 cmux。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 개선을 위해 익명화된 충돌 및 사용 데이터를 공유합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Anonymisierte Absturz- und Nutzungsdaten teilen, um cmux zu verbessern." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Compartir datos anónimos de fallos y uso para ayudar a mejorar cmux." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Partager des données anonymisées de plantage et d'utilisation pour aider à améliorer cmux." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Condividi dati anonimi su arresti anomali e utilizzo per migliorare cmux." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Del anonymiserede nedbrud- og brugsdata for at hjælpe med at forbedre cmux." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Udostępniaj zanonimizowane dane o awariach i użyciu, aby pomóc ulepszyć cmux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Делиться анонимными данными о сбоях и использовании для улучшения cmux." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dijelite anonimizirane podatke o padovima i korištenju kako biste pomogli u poboljšanju cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مشاركة بيانات الأعطال والاستخدام المجهولة للمساعدة في تحسين cmux." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del anonymiserte krasj- og bruksdata for å bidra til å forbedre cmux." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Compartilhar dados anonimizados de falhas e uso para ajudar a melhorar o cmux." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แชร์ข้อมูลการขัดข้องและการใช้งานแบบไม่ระบุตัวตนเพื่อช่วยปรับปรุง cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux'u iyileştirmek için anonimleştirilmiş çökme ve kullanım verilerini paylaş." + } } } }, @@ -6544,6 +43408,102 @@ "state": "translated", "value": "変更は次回起動時に反映されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更改将在下次启动时生效。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "變更將在下次啟動時生效。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "변경 사항은 다음 실행 시 적용됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Änderung wird beim nächsten Start wirksam." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El cambio se aplicará en el próximo inicio." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La modification prendra effet au prochain lancement." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La modifica avrà effetto al prossimo avvio." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ændringen træder i kraft ved næste start." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmiana zacznie obowiązywać po ponownym uruchomieniu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменение вступит в силу при следующем запуске." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Promjena stupa na snagu pri sljedećem pokretanju." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يسري التغيير عند التشغيل التالي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Endringen trer i kraft ved neste oppstart." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A alteração terá efeito na próxima inicialização." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเปลี่ยนแปลงจะมีผลเมื่อเปิดใหม่ครั้งถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Değişiklik bir sonraki başlatmada geçerli olur." + } } } }, @@ -6561,6 +43521,102 @@ "state": "translated", "value": "テーマ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "主题" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "主題" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "테마" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Design" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Thème" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Motyw" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المظهر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ธีม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tema" + } } } }, @@ -6578,6 +43634,102 @@ "state": "translated", "value": "終了前に警告" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "退出前警告" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "結束前警告" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "종료 전 경고" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vor dem Beenden warnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Advertir antes de salir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Avertir avant de quitter" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Avvisa prima di uscire" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Advar før afslutning" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ostrzegaj przed zamknięciem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предупреждать перед выходом" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Upozori prije zatvaranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التحذير قبل الإنهاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Advar før avslutning" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Avisar Antes de Encerrar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เตือนก่อนออก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çıkmadan Önce Uyar" + } } } }, @@ -6595,6 +43747,102 @@ "state": "translated", "value": "Cmd+Qで確認なしにすぐ終了します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q 直接退出,无需确认。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q 會立即結束,不顯示確認提示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q를 누르면 확인 없이 즉시 종료됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q beendet sofort ohne Bestätigung." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q sale inmediatamente sin confirmación." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q quitte immédiatement sans confirmation." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q esce immediatamente senza conferma." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q afslutter øjeblikkeligt uden bekræftelse." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q zamyka natychmiast bez potwierdzenia." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q завершает приложение немедленно без подтверждения." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q odmah zatvara bez potvrde." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q ينهي فورًا بدون تأكيد." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q avslutter umiddelbart uten bekreftelse." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q encerra imediatamente sem confirmação." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q ออกทันทีโดยไม่มีการยืนยัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q onay olmadan hemen çıkar." + } } } }, @@ -6612,6 +43860,102 @@ "state": "translated", "value": "Cmd+Qで終了する前に確認を表示します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用 Cmd+Q 退出前显示确认对话框。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用 Cmd+Q 結束前顯示確認提示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q로 종료하기 전에 확인 대화상자를 표시합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vor dem Beenden mit Cmd+Q eine Bestätigung anzeigen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar una confirmación antes de salir con Cmd+Q." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher une confirmation avant de quitter avec Cmd+Q." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra una conferma prima di uscire con Cmd+Q." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis en bekræftelse, før du afslutter med Cmd+Q." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż potwierdzenie przed zamknięciem za pomocą Cmd+Q." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать подтверждение перед выходом по Cmd+Q." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži potvrdu prije zatvaranja sa Cmd+Q." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض تأكيد قبل الإنهاء بـ Cmd+Q." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis en bekreftelse før avslutning med Cmd+Q." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar uma confirmação antes de encerrar com Cmd+Q." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการยืนยันก่อนออกด้วย Cmd+Q" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q ile çıkmadan önce onay göster." + } } } }, @@ -6629,6 +43973,102 @@ "state": "translated", "value": "Claude Code連携" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Claude Code 集成" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Claude Code 整合" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Claude Code 연동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Claude Code-Integration" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Integración con Claude Code" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Intégration Claude Code" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Integrazione Claude Code" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Claude Code-integration" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Integracja z Claude Code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Интеграция с Claude Code" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Claude Code integracija" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تكامل Claude Code" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Claude Code-integrering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Integração com Claude Code" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การผสานรวม Claude Code" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Claude Code Entegrasyonu" + } } } }, @@ -6646,6 +44086,102 @@ "state": "translated", "value": "有効にすると、cmuxはclaudeコマンドをラップしてセッション追跡と通知フックを挿入します。Claude Codeのフックを自分で管理する場合は無効にしてください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用后,cmux 会包装 claude 命令以注入会话跟踪和通知钩子。如果您希望自行管理 Claude Code 钩子,请禁用此选项。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "啟用後,cmux 會包裝 claude 指令以注入工作階段追蹤和通知掛鉤。如果您偏好自行管理 Claude Code 掛鉤,請停用此選項。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "활성화하면 cmux가 claude 명령을 래핑하여 세션 추적 및 알림 훅을 삽입합니다. Claude Code 훅을 직접 관리하려면 비활성화하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wenn aktiviert, umhüllt cmux den claude-Befehl, um Sitzungsverfolgung und Benachrichtigungs-Hooks einzufügen. Deaktivieren Sie dies, wenn Sie Claude Code-Hooks selbst verwalten möchten." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cuando está activado, cmux envuelve el comando claude para inyectar seguimiento de sesión y hooks de notificación. Desactiva si prefieres gestionar los hooks de Claude Code tú mismo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lorsque cette option est activée, cmux encapsule la commande claude pour injecter le suivi de session et les hooks de notification. Désactivez si vous préférez gérer les hooks Claude Code vous-même." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Quando abilitato, cmux avvolge il comando claude per iniettare il tracciamento della sessione e gli hook di notifica. Disabilita se preferisci gestire gli hook di Claude Code manualmente." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Når aktiveret, wrapper cmux claude-kommandoen for at injicere sessionssporing og notifikationshooks. Deaktiver, hvis du foretrækker at administrere Claude Code-hooks selv." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Po włączeniu cmux opakowuje polecenie claude, aby wstrzyknąć śledzenie sesji i hooki powiadomień. Wyłącz, jeśli wolisz samodzielnie zarządzać hookami Claude Code." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "При включении cmux оборачивает команду claude для отслеживания сеансов и уведомлений. Отключите, если предпочитаете управлять хуками Claude Code самостоятельно." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kada je omogućeno, cmux omotava naredbu claude kako bi ubacio praćenje sesije i zakačke za obavještenja. Onemogućite ako preferirate sami upravljati Claude Code zakačkama." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند التفعيل، يغلّف cmux أمر claude لحقن تتبع الجلسة وخطافات الإشعارات. قم بالتعطيل إذا كنت تفضل إدارة خطافات Claude Code بنفسك." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Når aktivert, pakker cmux inn claude-kommandoen for å injisere sesjonssporing og varslingsmekanismer. Deaktiver hvis du foretrekker å håndtere Claude Code-mekanismer selv." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Quando ativado, o cmux encapsula o comando claude para injetar rastreamento de sessão e hooks de notificação. Desative se preferir gerenciar os hooks do Claude Code você mesmo." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เมื่อเปิดใช้งาน cmux จะห่อคำสั่ง claude เพื่อเพิ่มการติดตามเซสชันและตะขอการแจ้งเตือน ปิดใช้งานหากคุณต้องการจัดการตะขอ Claude Code ด้วยตัวเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Etkinleştirildiğinde, cmux oturum izleme ve bildirim kancaları eklemek için claude komutunu sarar. Claude Code kancalarını kendiniz yönetmeyi tercih ediyorsanız devre dışı bırakın." + } } } }, @@ -6663,6 +44199,102 @@ "state": "translated", "value": "Claude Codeはcmux連携なしで実行されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Claude Code 在没有 cmux 集成的情况下运行。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Claude Code 在不使用 cmux 整合的情況下運行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Claude Code가 cmux 연동 없이 실행됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Claude Code wird ohne cmux-Integration ausgeführt." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Claude Code se ejecuta sin integración con cmux." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Claude Code s'exécute sans intégration cmux." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Claude Code viene eseguito senza integrazione cmux." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Claude Code kører uden cmux-integration." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Claude Code działa bez integracji z cmux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Claude Code работает без интеграции с cmux." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Claude Code radi bez cmux integracije." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعمل Claude Code بدون تكامل cmux." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Claude Code kjører uten cmux-integrering." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O Claude Code é executado sem integração com o cmux." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Claude Code ทำงานโดยไม่มีการผสานรวม cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Claude Code cmux entegrasyonu olmadan çalışır." + } } } }, @@ -6680,6 +44312,102 @@ "state": "translated", "value": "サイドバーにClaudeセッションのステータスと通知が表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "侧边栏显示 Claude 会话状态和通知。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "側邊欄顯示 Claude 工作階段狀態和通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Claude 세션 상태와 알림이 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Seitenleiste zeigt den Claude-Sitzungsstatus und Benachrichtigungen an." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La barra lateral muestra el estado de la sesión de Claude y las notificaciones." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La barre latérale affiche le statut de la session Claude et les notifications." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La barra laterale mostra lo stato della sessione Claude e le notifiche." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sidebjælken viser Claude-sessionsstatus og notifikationer." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pasek boczny pokazuje status sesji Claude i powiadomienia." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Боковая панель показывает статус сеанса Claude и уведомления." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Bočna traka prikazuje status Claude sesije i obavještenja." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض الشريط الجانبي حالة جلسة Claude والإشعارات." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sidepanelet viser Claude-sesjonsstatus og varsler." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A barra lateral mostra o status da sessão do Claude e notificações." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แถบด้านข้างแสดงสถานะเซสชัน Claude และการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar çubuğu Claude oturum durumunu ve bildirimlerini gösterir." + } } } }, @@ -6697,6 +44425,102 @@ "state": "translated", "value": "キャンセル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "취소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Annuler" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Annulla" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Annuller" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Anuluj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkaži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avbryt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Vazgeç" + } } } }, @@ -6714,6 +44538,102 @@ "state": "translated", "value": "フルオープンアクセスを有効にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用完全开放访问" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "啟用完全開放存取" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전체 개방 접근 활성화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollständigen offenen Zugriff aktivieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Activar acceso abierto completo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer l'accès ouvert complet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Abilita accesso aperto completo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiver fuld åben adgang" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Włącz pełny otwarty dostęp" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включить полный открытый доступ" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Omogući potpuni otvoreni pristup" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تفعيل الوصول المفتوح الكامل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktiver full åpen tilgang" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ativar Acesso Aberto Total" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดใช้งานการเข้าถึงเปิดแบบเต็ม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tam Açık Erişimi Etkinleştir" + } } } }, @@ -6731,6 +44651,102 @@ "state": "translated", "value": "これにより、祖先プロセスチェックとパスワードチェックが無効になり、すべてのローカルユーザーにソケットが公開されます。リスクを理解した上で有効にしてください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将禁用祖先和密码检查,并向所有本地用户开放套接字。请确保您了解其中的风险后再启用。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這將停用來源驗證和密碼檢查,並將 Socket 開放給所有本機使用者。請確認您了解風險後再啟用。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 설정은 출처 확인 및 비밀번호 검사를 비활성화하고 모든 로컬 사용자에게 소켓을 개방합니다. 위험을 이해한 경우에만 활성화하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dies deaktiviert Abstammungs- und Passwortprüfungen und öffnet den Socket für alle lokalen Benutzer. Aktivieren Sie dies nur, wenn Sie das Risiko verstehen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto desactiva las verificaciones de ascendencia y contraseña, y abre el socket a todos los usuarios locales. Activa solo si comprendes el riesgo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela désactive les vérifications de parenté et de mot de passe et ouvre le socket à tous les utilisateurs locaux. N'activez que si vous comprenez les risques." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione disabilita i controlli di discendenza e password e apre il socket a tutti gli utenti locali. Abilita solo se comprendi il rischio." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette deaktiverer herkomst- og adgangskodekontrol og åbner socketen for alle lokale brugere. Aktiver kun, når du forstår risikoen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "To wyłącza sprawdzanie pochodzenia i hasła oraz otwiera gniazdo dla wszystkich lokalnych użytkowników. Włączaj tylko wtedy, gdy rozumiesz ryzyko." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Это отключает проверку происхождения и пароля и открывает сокет для всех локальных пользователей. Включайте только если понимаете риски." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo onemogućava provjere porijekla i lozinke i otvara utičnicu svim lokalnim korisnicima. Omogućite samo kada razumijete rizik." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يؤدي هذا إلى تعطيل فحوصات النسب وكلمة المرور ويفتح المقبس لجميع المستخدمين المحليين. قم بالتفعيل فقط عندما تفهم المخاطر." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette deaktiverer opphavs- og passordkontroller og åpner socketen for alle lokale brukere. Aktiver bare når du forstår risikoen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto desativa verificações de ancestralidade e senha e abre o socket para todos os usuários locais. Ative somente quando entender os riscos." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะปิดใช้งานการตรวจสอบสายสืบทอดและรหัสผ่าน และเปิดซ็อกเก็ตให้ผู้ใช้ในเครื่องทุกคน เปิดใช้งานเฉพาะเมื่อคุณเข้าใจความเสี่ยง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, soy ve parola denetimlerini devre dışı bırakır ve soketi tüm yerel kullanıcılara açar. Yalnızca riski anladığınızda etkinleştirin." + } } } }, @@ -6748,6 +44764,102 @@ "state": "translated", "value": "フルオープンアクセスを有効にしますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用完全开放访问?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要啟用完全開放存取嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전체 개방 접근을 활성화하시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollständigen offenen Zugriff aktivieren?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Activar acceso abierto completo?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer l'accès ouvert complet ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Abilitare l'accesso aperto completo?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiver fuld åben adgang?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Włączyć pełny otwarty dostęp?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включить полный открытый доступ?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Omogućiti potpuni otvoreni pristup?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تفعيل الوصول المفتوح الكامل؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktivere full åpen tilgang?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ativar acesso aberto total?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดใช้งานการเข้าถึงเปิดแบบเต็มหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tam açık erişim etkinleştirilsin mi?" + } } } }, @@ -6765,6 +44877,102 @@ "state": "translated", "value": "警告: フルオープンアクセスにすると、このMac上の制御ソケットが誰でも読み書き可能になり、認証チェックが無効になります。ローカルデバッグ用途にのみ使用してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "警告:完全开放访问将使控制套接字在此 Mac 上对所有用户可读/可写,并禁用身份验证检查。仅用于本地调试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "警告:完全開放存取會讓控制 Socket 在此 Mac 上可被所有人讀寫,並停用驗證檢查。僅供本機除錯使用。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "경고: 전체 개방 접근은 이 Mac에서 제어 소켓을 누구나 읽고 쓸 수 있게 하며 인증 검사를 비활성화합니다. 로컬 디버깅 전용으로만 사용하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Warnung: Vollständiger offener Zugriff macht den Steuerungs-Socket auf diesem Mac für alle les- und schreibbar und deaktiviert Authentifizierungsprüfungen. Nur für lokales Debugging verwenden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Advertencia: El acceso abierto completo hace que el socket de control sea legible/escribible para todos en este Mac y desactiva las verificaciones de autenticación. Usa solo para depuración local." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Attention : l'accès ouvert complet rend le socket de contrôle lisible/inscriptible par tous sur ce Mac et désactive les vérifications d'authentification. À utiliser uniquement pour le débogage local." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attenzione: l'accesso aperto completo rende il socket di controllo leggibile/scrivibile da tutti su questo Mac e disabilita i controlli di autenticazione. Usa solo per il debug locale." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Advarsel: Fuld åben adgang gør kontrolsocketen læsbar/skrivbar for alle på denne Mac og deaktiverer autentifikationskontrol. Brug kun til lokal fejlsøgning." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ostrzeżenie: Pełny otwarty dostęp czyni gniazdo sterujące odczytywalnym/zapisywalnym dla wszystkich na tym Macu i wyłącza sprawdzanie uwierzytelniania. Używaj tylko do lokalnego debugowania." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Внимание: полный открытый доступ делает управляющий сокет доступным для чтения и записи всем пользователям на этом Mac и отключает проверку авторизации. Используйте только для локальной отладки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Upozorenje: Potpuni otvoreni pristup čini kontrolnu utičnicu čitljivom/zapisivom za sve na ovom Macu i onemogućava provjere autentikacije. Koristite samo za lokalno debugiranje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحذير: الوصول المفتوح الكامل يجعل مقبس التحكم قابلاً للقراءة والكتابة من الجميع على هذا الـ Mac ويعطل فحوصات المصادقة. استخدم فقط لأغراض التصحيح المحلي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Advarsel: Full åpen tilgang gjør kontrollsocketen lese-/skrivbar for alle på denne Mac-en og deaktiverer autentiseringskontroller. Bruk kun for lokal feilsøking." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aviso: O acesso aberto total torna o socket de controle legível/gravável por todos neste Mac e desativa verificações de autenticação. Use apenas para depuração local." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คำเตือน: การเข้าถึงเปิดแบบเต็มทำให้ซ็อกเก็ตควบคุมอ่าน/เขียนได้จากทุกผู้ใช้บน Mac นี้และปิดใช้งานการตรวจสอบสิทธิ์ ใช้สำหรับการดีบักในเครื่องเท่านั้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uyarı: Tam açık erişim, kontrol soketini bu Mac'te herkes tarafından okunabilir/yazılabilir yapar ve kimlik doğrulama denetimlerini devre dışı bırakır. Yalnızca yerel hata ayıklama için kullanın." + } } } }, @@ -6782,6 +44990,102 @@ "state": "translated", "value": "各ワークスペースにはCMUX_PORTとCMUX_PORT_END環境変数で専用のポート範囲が割り当てられます。新しいターミナルはこれらの値を継承します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "每个工作区会获得 CMUX_PORT 和 CMUX_PORT_END 环境变量,包含专用的端口范围。新终端会继承这些值。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "每個工作區都會取得包含專用連接埠範圍的 CMUX_PORT 和 CMUX_PORT_END 環境變數。新終端機會繼承這些值。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "각 작업 공간에 전용 포트 범위가 있는 CMUX_PORT 및 CMUX_PORT_END 환경 변수가 할당됩니다. 새 터미널은 이 값을 상속합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Jeder Arbeitsbereich erhält CMUX_PORT- und CMUX_PORT_END-Umgebungsvariablen mit einem dedizierten Portbereich. Neue Terminals erben diese Werte." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cada espacio de trabajo recibe las variables de entorno CMUX_PORT y CMUX_PORT_END con un rango de puertos dedicado. Los nuevos terminales heredan estos valores." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Chaque espace de travail reçoit les variables d'environnement CMUX_PORT et CMUX_PORT_END avec une plage de ports dédiée. Les nouveaux terminaux héritent de ces valeurs." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ogni area di lavoro riceve le variabili d'ambiente CMUX_PORT e CMUX_PORT_END con un intervallo di porte dedicato. I nuovi terminali ereditano questi valori." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Hvert arbejdsområde får CMUX_PORT og CMUX_PORT_END miljøvariabler med et dedikeret portområde. Nye terminaler arver disse værdier." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Każda przestrzeń robocza otrzymuje zmienne środowiskowe CMUX_PORT i CMUX_PORT_END z dedykowanym zakresem portów. Nowe terminale dziedziczą te wartości." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каждое рабочее пространство получает переменные окружения CMUX_PORT и CMUX_PORT_END с выделенным диапазоном портов. Новые терминалы наследуют эти значения." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Svaki radni prostor dobija CMUX_PORT i CMUX_PORT_END varijable okruženja sa dodijeljenim rasponom portova. Novi terminali nasljeđuju ove vrijednosti." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحصل كل مساحة عمل على متغيرات البيئة CMUX_PORT و CMUX_PORT_END مع نطاق منافذ مخصص. ترث الطرفيات الجديدة هذه القيم." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Hvert arbeidsområde får CMUX_PORT- og CMUX_PORT_END-miljøvariabler med et dedikert portområde. Nye terminaler arver disse verdiene." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cada área de trabalho recebe as variáveis de ambiente CMUX_PORT e CMUX_PORT_END com uma faixa de portas dedicada. Novos terminais herdam esses valores." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แต่ละเวิร์กสเปซจะได้รับตัวแปรสภาพแวดล้อม CMUX_PORT และ CMUX_PORT_END พร้อมช่วงพอร์ตเฉพาะ เทอร์มินัลใหม่จะสืบทอดค่าเหล่านี้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Her çalışma alanı ayrılmış bir port aralığıyla CMUX_PORT ve CMUX_PORT_END ortam değişkenlerini alır. Yeni terminaller bu değerleri devralır." + } } } }, @@ -6799,6 +45103,102 @@ "state": "translated", "value": "ポートベース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "起始端口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "連接埠起始值" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "포트 기준값" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Port-Basis" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Puerto base" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Port de base" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Porta base" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Portbase" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Port bazowy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Базовый порт" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Početni port" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المنفذ الأساسي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Portbase" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Porta Base" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พอร์ตเริ่มต้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Port Tabanı" + } } } }, @@ -6816,6 +45216,102 @@ "state": "translated", "value": "CMUX_PORT環境変数の開始ポート。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "CMUX_PORT 环境变量的起始端口。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "CMUX_PORT 環境變數的起始連接埠。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "CMUX_PORT 환경 변수의 시작 포트." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Startport für die CMUX_PORT-Umgebungsvariable." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Puerto inicial para la variable de entorno CMUX_PORT." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Port de départ pour la variable d'environnement CMUX_PORT." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Porta iniziale per la variabile d'ambiente CMUX_PORT." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Startport for CMUX_PORT-miljøvariabel." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Port początkowy dla zmiennej środowiskowej CMUX_PORT." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Начальный порт для переменной окружения CMUX_PORT." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Početni port za CMUX_PORT varijablu okruženja." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المنفذ الابتدائي لمتغير البيئة CMUX_PORT." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Startport for CMUX_PORT-miljøvariabelen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Porta inicial para a variável de ambiente CMUX_PORT." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พอร์ตเริ่มต้นสำหรับตัวแปรสภาพแวดล้อม CMUX_PORT" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "CMUX_PORT ortam değişkeni için başlangıç portu." + } } } }, @@ -6833,6 +45329,102 @@ "state": "translated", "value": "ポート範囲サイズ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "端口范围大小" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "連接埠範圍大小" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "포트 범위 크기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Portbereichsgröße" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tamaño del rango de puertos" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Taille de la plage de ports" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dimensione intervallo porte" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Portområdestørrelse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Rozmiar zakresu portów" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Размер диапазона портов" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Veličina raspona portova" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "حجم نطاق المنافذ" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Portområdestørrelse" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tamanho da Faixa de Portas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ขนาดช่วงพอร์ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Port Aralığı Boyutu" + } } } }, @@ -6850,6 +45442,102 @@ "state": "translated", "value": "ワークスペースあたりのポート数。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "每个工作区的端口数量。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "每個工作區的連接埠數量。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간당 포트 수." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Anzahl der Ports pro Arbeitsbereich." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Número de puertos por espacio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nombre de ports par espace de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Numero di porte per area di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Antal porte pr. arbejdsområde." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Liczba portów na przestrzeń roboczą." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Количество портов на рабочее пространство." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Broj portova po radnom prostoru." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عدد المنافذ لكل مساحة عمل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Antall porter per arbeidsområde." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Número de portas por área de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "จำนวนพอร์ตต่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma alanı başına port sayısı." + } } } }, @@ -6867,6 +45555,102 @@ "state": "translated", "value": "ソケット制御モード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "套接字控制模式" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Socket 控制模式" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "소켓 제어 모드" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Socket-Steuerungsmodus" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Modo de control del socket" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mode de contrôle du socket" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Modalità controllo socket" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Socket-kontroltilstand" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tryb sterowania gniazdem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Режим управления сокетом" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Režim kontrolne utičnice" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وضع التحكم بالمقبس" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Socket-kontrollmodus" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Modo de Controle do Socket" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหมดควบคุมซ็อกเก็ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Soket Kontrol Modu" + } } } }, @@ -6884,6 +45668,102 @@ "state": "translated", "value": "プログラムによる制御のためのローカルUnix socketへのアクセスを制御します。脅威モデルに合ったモードを選択してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "控制本地 Unix 套接字的访问权限,用于程序化控制。请选择与您的安全需求匹配的模式。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "控制本機 Unix Socket 的程式化控制存取。選擇符合您安全需求的模式。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "프로그래밍 방식 제어를 위한 로컬 Unix 소켓 접근을 제어합니다. 보안 모델에 맞는 모드를 선택하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Steuert den Zugriff auf den lokalen Unix-Socket für programmatische Steuerung. Wählen Sie einen Modus, der Ihrem Bedrohungsmodell entspricht." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Controla el acceso al socket Unix local para control programático. Elige un modo que coincida con tu modelo de amenazas." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Contrôle l'accès au socket Unix local pour le contrôle programmatique. Choisissez un mode adapté à votre modèle de menace." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Controlla l'accesso al socket Unix locale per il controllo programmatico. Scegli una modalità adeguata al tuo modello di rischio." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Styrer adgangen til den lokale Unix-socket til programmatisk kontrol. Vælg en tilstand, der passer til din trusselsmodel." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kontroluje dostęp do lokalnego gniazda Unix do programowego sterowania. Wybierz tryb odpowiadający Twojemu modelowi zagrożeń." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Управляет доступом к локальному Unix-сокету для программного управления. Выберите режим, соответствующий вашей модели угроз." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kontroliše pristup lokalnoj Unix utičnici za programsku kontrolu. Odaberite režim koji odgovara vašem modelu prijetnji." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يتحكم بالوصول إلى مقبس Unix المحلي للتحكم البرمجي. اختر الوضع الذي يتناسب مع نموذج التهديد الخاص بك." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Styrer tilgang til den lokale Unix-socketen for programmatisk kontroll. Velg en modus som passer til din trusselmodell." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Controla o acesso ao socket Unix local para controle programático. Escolha um modo que corresponda ao seu modelo de ameaça." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ควบคุมการเข้าถึงซ็อกเก็ต Unix ในเครื่องสำหรับการควบคุมแบบโปรแกรม เลือกโหมดที่ตรงกับรูปแบบภัยคุกคามของคุณ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Programatik kontrol için yerel Unix soketine erişimi kontrol eder. Tehdit modelinize uyan bir mod seçin." + } } } }, @@ -6901,6 +45781,102 @@ "state": "translated", "value": "オーバーライド: CMUX_SOCKET_ENABLE、CMUX_SOCKET_MODE、CMUX_SOCKET_PATH(stable/nightlyビルドではCMUX_ALLOW_SOCKET_OVERRIDE=1を設定)。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "覆盖项:CMUX_SOCKET_ENABLE、CMUX_SOCKET_MODE 和 CMUX_SOCKET_PATH(对稳定版/每日构建版需设置 CMUX_ALLOW_SOCKET_OVERRIDE=1)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "覆寫設定:CMUX_SOCKET_ENABLE、CMUX_SOCKET_MODE 和 CMUX_SOCKET_PATH(穩定版/每夜版需設定 CMUX_ALLOW_SOCKET_OVERRIDE=1)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "재정의: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, CMUX_SOCKET_PATH (안정/나이틀리 빌드의 경우 CMUX_ALLOW_SOCKET_OVERRIDE=1 설정)." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Überschreibungen: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE und CMUX_SOCKET_PATH (setzen Sie CMUX_ALLOW_SOCKET_OVERRIDE=1 für Stable-/Nightly-Builds)." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sobreescrituras: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE y CMUX_SOCKET_PATH (establece CMUX_ALLOW_SOCKET_OVERRIDE=1 para compilaciones estables/nightly)." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Remplacements : CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE et CMUX_SOCKET_PATH (définissez CMUX_ALLOW_SOCKET_OVERRIDE=1 pour les builds stable/nightly)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Override: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE e CMUX_SOCKET_PATH (imposta CMUX_ALLOW_SOCKET_OVERRIDE=1 per le build stable/nightly)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tilsidesættelser: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE og CMUX_SOCKET_PATH (sæt CMUX_ALLOW_SOCKET_OVERRIDE=1 for stabile/nightly builds)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nadpisania: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE i CMUX_SOCKET_PATH (ustaw CMUX_ALLOW_SOCKET_OVERRIDE=1 dla wersji stabilnych/nightly)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переопределения: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE и CMUX_SOCKET_PATH (установите CMUX_ALLOW_SOCKET_OVERRIDE=1 для стабильных/ночных сборок)." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premošćenja: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE i CMUX_SOCKET_PATH (postavite CMUX_ALLOW_SOCKET_OVERRIDE=1 za stabilne/nightly verzije)." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التجاوزات: CMUX_SOCKET_ENABLE و CMUX_SOCKET_MODE و CMUX_SOCKET_PATH (اضبط CMUX_ALLOW_SOCKET_OVERRIDE=1 لبناءات stable/nightly)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Overstyringer: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE og CMUX_SOCKET_PATH (sett CMUX_ALLOW_SOCKET_OVERRIDE=1 for stabile/nattlige bygg)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Substituições: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE e CMUX_SOCKET_PATH (defina CMUX_ALLOW_SOCKET_OVERRIDE=1 para builds stable/nightly)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแทนที่: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE และ CMUX_SOCKET_PATH (ตั้ง CMUX_ALLOW_SOCKET_OVERRIDE=1 สำหรับบิลด์ stable/nightly)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçersiz kılmalar: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE ve CMUX_SOCKET_PATH (kararlı/gece derlemeleri için CMUX_ALLOW_SOCKET_OVERRIDE=1 ayarlayın)." + } } } }, @@ -6918,6 +45894,102 @@ "state": "translated", "value": "ソケットパスワード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "套接字密码" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Socket 密碼" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "소켓 비밀번호" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Socket-Passwort" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Contraseña del socket" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mot de passe du socket" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Password socket" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Socket-adgangskode" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hasło gniazda" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пароль сокета" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lozinka utičnice" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "كلمة مرور المقبس" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Socket-passord" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Senha do Socket" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รหัสผ่านซ็อกเก็ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Soket Parolası" + } } } }, @@ -6935,6 +46007,102 @@ "state": "translated", "value": "変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更改" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "變更" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ändern" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cambiar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Modifier" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Modifica" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Skift" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Promijeni" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تغيير" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Endre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alterar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Değiştir" + } } } }, @@ -6952,6 +46120,102 @@ "state": "translated", "value": "クリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Temizle" + } } } }, @@ -6969,6 +46233,102 @@ "state": "translated", "value": "パスワードのクリアに失敗しました(%@)。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除密码失败 (%@)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除密碼失敗(%@)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호 지우기 실패 (%@)." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort konnte nicht gelöscht werden (%@)." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo borrar la contraseña (%@)." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible d'effacer le mot de passe (%@)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile cancellare la password (%@)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke rydde adgangskode (%@)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się wyczyścić hasła (%@)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось очистить пароль (%@)." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije uspjelo brisanje lozinke (%@)." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فشل مسح كلمة المرور (%@)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke fjerne passord (%@)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Falha ao limpar a senha (%@)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถล้างรหัสผ่านได้ (%@)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola temizlenemedi (%@)." + } } } }, @@ -6986,6 +46346,102 @@ "state": "translated", "value": "パスワードをクリアしました。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "密码已清除。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "密碼已清除。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호가 지워졌습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort gelöscht." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Contraseña borrada." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mot de passe effacé." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Password cancellata." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Adgangskode ryddet." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hasło wyczyszczone." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пароль очищен." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lozinka obrisana." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تم مسح كلمة المرور." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Passord fjernet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Senha removida." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างรหัสผ่านแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola temizlendi." + } } } }, @@ -7003,6 +46459,102 @@ "state": "translated", "value": "まずパスワードを入力してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请先输入密码。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請先輸入密碼。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "먼저 비밀번호를 입력하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Geben Sie zuerst ein Passwort ein." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Introduce una contraseña primero." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisissez d'abord un mot de passe." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci prima una password." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indtast en adgangskode først." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Najpierw wprowadź hasło." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сначала введите пароль." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prvo unesite lozinku." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أدخل كلمة مرور أولاً." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Skriv inn et passord først." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Insira uma senha primeiro." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป้อนรหัสผ่านก่อน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önce bir parola girin." + } } } }, @@ -7020,6 +46572,102 @@ "state": "translated", "value": "パスワード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "密码" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "密碼" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Contraseña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mot de passe" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Password" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Adgangskode" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hasło" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пароль" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lozinka" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "كلمة المرور" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Passord" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Senha" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รหัสผ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola" + } } } }, @@ -7037,6 +46685,102 @@ "state": "translated", "value": "パスワードの保存に失敗しました(%@)。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "保存密码失败 (%@)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "儲存密碼失敗(%@)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호 저장 실패 (%@)." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort konnte nicht gespeichert werden (%@)." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo guardar la contraseña (%@)." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible d'enregistrer le mot de passe (%@)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile salvare la password (%@)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke gemme adgangskode (%@)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się zapisać hasła (%@)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось сохранить пароль (%@)." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije uspjelo spremanje lozinke (%@)." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فشل حفظ كلمة المرور (%@)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke lagre passord (%@)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Falha ao salvar a senha (%@)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถบันทึกรหัสผ่านได้ (%@)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola kaydedilemedi (%@)." + } } } }, @@ -7054,6 +46798,102 @@ "state": "translated", "value": "パスワードを保存しました。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "密码已保存。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "密碼已儲存。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호가 저장되었습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort gespeichert." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Contraseña guardada." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mot de passe enregistré." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Password salvata." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Adgangskode gemt." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hasło zapisane." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пароль сохранен." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lozinka spremljena." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تم حفظ كلمة المرور." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Passord lagret." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Senha salva." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "บันทึกรหัสผ่านแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola kaydedildi." + } } } }, @@ -7071,6 +46911,102 @@ "state": "translated", "value": "設定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "设置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "設定" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설정" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Festlegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Establecer" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Définir" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Imposta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Angiv" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ustaw" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Postavi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعيين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Angi" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Definir" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตั้งค่า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayarla" + } } } }, @@ -7088,6 +47024,102 @@ "state": "translated", "value": "Application Supportに保存済み。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "存储在应用程序支持目录中。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "儲存於 Application Support。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Application Support에 저장됨." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Gespeichert in Application Support." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Almacenada en Application Support." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Stocké dans Application Support." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Memorizzata nel Supporto Applicazioni." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gemt i Application Support." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przechowywane w Application Support." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранится в Application Support." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pohranjena u Application Support." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مخزنة في Application Support." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lagret i Application Support." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Armazenada em Application Support." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "จัดเก็บใน Application Support" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygulama Desteği klasöründe saklanır." + } } } }, @@ -7105,6 +47137,102 @@ "state": "translated", "value": "パスワードが設定されていません。設定するまで外部クライアントはブロックされます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未设置密码。外部客户端将被阻止,直到配置密码为止。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尚未設定密碼。外部用戶端將被封鎖,直到設定密碼為止。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호가 설정되지 않았습니다. 비밀번호를 구성하기 전까지 외부 클라이언트가 차단됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Kein Passwort festgelegt. Externe Clients werden blockiert, bis eines konfiguriert ist." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se ha establecido contraseña. Los clientes externos serán bloqueados hasta que se configure una." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucun mot de passe défini. Les clients externes seront bloqués tant qu'un mot de passe n'aura pas été configuré." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna password impostata. I client esterni verranno bloccati finché non ne viene configurata una." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen adgangskode angivet. Eksterne klienter blokeres, indtil en er konfigureret." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hasło nie jest ustawione. Klienty zewnętrzne będą blokowane, dopóki nie zostanie skonfigurowane." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пароль не установлен. Внешние клиенты будут заблокированы, пока он не будет настроен." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lozinka nije postavljena. Vanjski klijenti će biti blokirani dok se ne konfiguriše." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لم يتم تعيين كلمة مرور. سيتم حظر العملاء الخارجيين حتى يتم تكوين واحدة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen passord angitt. Eksterne klienter vil bli blokkert inntil et passord er konfigurert." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma senha definida. Clientes externos serão bloqueados até que uma seja configurada." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยังไม่ได้ตั้งรหัสผ่าน ไคลเอ็นต์ภายนอกจะถูกบล็อกจนกว่าจะกำหนดค่า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola ayarlanmadı. Bir parola yapılandırılana kadar harici istemciler engellenecek." + } } } }, @@ -7122,6 +47250,102 @@ "state": "translated", "value": "ウインドウの背面" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口后方" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗後方" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 뒤" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hinter dem Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Detrás de la ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Derrière la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dietro la finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Bag vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Za oknem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "За окном" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Iza prozora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "خلف النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bak vinduet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Atrás da Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ด้านหลังหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencerenin Arkası" + } } } }, @@ -7139,6 +47363,102 @@ "state": "translated", "value": "ウインドウ内" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口内部" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗內" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 내" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Innerhalb des Fensters" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dentro de la ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Dans la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "All'interno della finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Inden i vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "W oknie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Внутри окна" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unutar prozora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "داخل النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Inni vinduet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dentro da Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ภายในหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere İçinde" + } } } }, @@ -7156,6 +47476,102 @@ "state": "translated", "value": "常に外部で開くURL" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "始终在外部打开的 URL" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "一律在外部開啟的 URL" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "항상 외부에서 열 URL" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "URLs, die immer extern geöffnet werden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "URLs para abrir siempre externamente" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "URL à toujours ouvrir dans un navigateur externe" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "URL da aprire sempre esternamente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "URL'er der altid åbnes eksternt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Adresy URL otwierane zawsze zewnętrznie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "URL для открытия во внешнем браузере" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "URL-ovi za uvijek vanjsko otvaranje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عناوين URL للفتح خارجيًا دائمًا" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "URL-er som alltid åpnes eksternt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "URLs para Sempre Abrir Externamente" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "URL ที่เปิดภายนอกเสมอ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Her Zaman Harici Olarak Açılacak URL'ler" + } } } }, @@ -7173,6 +47589,102 @@ "state": "translated", "value": "ターミナルのリンククリックおよびインターセプトされた`open https://...`呼び出しに適用されます。1行に1ルール。プレーンテキストはURL部分文字列に一致し、`re:`プレフィックスで正規表現を使用できます(例: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "适用于终端中的链接点击和拦截的 `open https://...` 调用。每行一条规则。纯文本匹配任意 URL 子串,或使用 `re:` 前缀表示正则表达式(例如:openai.com/usage、re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "套用於終端機連結點擊和攔截的 `open https://...` 呼叫。每行一條規則。純文字會比對 URL 的任何子字串,或加上 `re:` 前綴使用正規表示式(例如:openai.com/usage、re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 링크 클릭 및 인터셉트된 `open https://...` 호출에 적용됩니다. 한 줄에 하나의 규칙. 일반 텍스트는 URL의 하위 문자열을 일치시키며, `re:` 접두사로 정규식을 사용할 수 있습니다 (예: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt für Klicks auf Terminal-Links und abgefangene `open https://...`-Aufrufe. Eine Regel pro Zeile. Klartext stimmt mit jedem URL-Teilstring überein, oder verwenden Sie das Präfix `re:` für Regex (zum Beispiel: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se aplica a clics en enlaces del terminal y llamadas interceptadas de `open https://...`. Una regla por línea. El texto plano coincide con cualquier subcadena de URL, o usa el prefijo `re:` para regex (por ejemplo: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "S'applique aux clics sur les liens du terminal et aux appels `open https://...` interceptés. Une règle par ligne. Le texte brut correspond à toute sous-chaîne d'URL, ou préfixez par `re:` pour une expression régulière (par exemple : openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Si applica ai clic sui link nel terminale e alle chiamate intercettate `open https://...`. Una regola per riga. Il testo semplice corrisponde a qualsiasi sottostringa dell'URL, oppure usa il prefisso `re:` per le regex (ad esempio: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gælder for terminallinksklik og opfangede `open https://...`-kald. Én regel pr. linje. Almindelig tekst matcher enhver URL-delstreng, eller brug `re:` som præfiks for regex (f.eks.: openai.com/usage, re:^https?://[^/]*\\.example\\.com/(billing|usage))." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dotyczy kliknięć linków w terminalu i przechwyconych wywołań `open https://...`. Jedna reguła na wiersz. Zwykły tekst dopasowuje dowolny fragment URL, lub użyj prefiksu `re:` dla wyrażeń regularnych (na przykład: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Применяется к нажатиям на ссылки в терминале и перехваченным вызовам `open https://...`. Одно правило на строку. Обычный текст совпадает с любой подстрокой URL, или используйте префикс `re:` для регулярных выражений (например: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Primjenjuje se na klikove linkova u terminalu i presretnute `open https://...` pozive. Jedno pravilo po redu. Običan tekst odgovara bilo kojem dijelu URL-a, ili dodajte prefiks `re:` za regex (na primjer: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ينطبق على نقرات الروابط في الطرفية واستدعاءات `open https://...` المعترضة. قاعدة واحدة لكل سطر. النص العادي يطابق أي جزء من URL، أو ابدأ بـ `re:` للتعبيرات النمطية (مثال: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gjelder for terminallenkeklikk og avlyttede `open https://...`-kall. Én regel per linje. Ren tekst matcher enhver del av URL-en, eller prefiks med `re:` for regulære uttrykk (for eksempel: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aplica-se a cliques em links do terminal e chamadas interceptadas de `open https://...`. Uma regra por linha. Texto simples corresponde a qualquer substring de URL, ou prefixe com `re:` para regex (por exemplo: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใช้กับการคลิกลิงก์ในเทอร์มินัลและการดักจับคำสั่ง `open https://...` กฎหนึ่งข้อต่อบรรทัด ข้อความธรรมดาจะจับคู่กับส่วนใดก็ได้ของ URL หรือนำหน้าด้วย `re:` สำหรับ regex (ตัวอย่าง: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal bağlantı tıklamalarına ve yakalanan `open https://...` çağrılarına uygulanır. Satır başına bir kural. Düz metin herhangi bir URL alt dizesiyle eşleşir veya regex için `re:` ön ekini kullanın (örneğin: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } } } }, @@ -7190,6 +47702,102 @@ "state": "translated", "value": "ブラウザ履歴" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览历史" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탐색 기록" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browserverlauf" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Historial de navegación" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Historique de navigation" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cronologia di navigazione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browserhistorik" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Historia przeglądania" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "История просмотра" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Historija pregledanja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سجل التصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleserhistorikk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Histórico de Navegação" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ประวัติการท่องเว็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarama Geçmişi" + } } } }, @@ -7207,6 +47815,102 @@ "state": "translated", "value": "履歴をクリア…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除历史记录..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除記錄..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기록 지우기…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verlauf löschen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar historial…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer l'historique..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella cronologia…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd historik…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść historię…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить историю..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši historiju…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح السجل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tøm historikk …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Histórico…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างประวัติ..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçmişi Temizle…" + } } } }, @@ -7224,6 +47928,102 @@ "state": "translated", "value": "キャンセル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "취소" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Annuler" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Annulla" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Annuller" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Anuluj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otkaži" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avbryt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยกเลิก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Vazgeç" + } } } }, @@ -7241,6 +48041,102 @@ "state": "translated", "value": "履歴をクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除历史记录" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除記錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기록 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verlauf löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar historial" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer l'historique" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella cronologia" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd historik" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść historię" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить историю" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši historiju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح السجل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tøm historikk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Histórico" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างประวัติ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçmişi Temizle" + } } } }, @@ -7258,6 +48154,102 @@ "state": "translated", "value": "ブラウザのオムニバーから訪問済みページの候補が削除されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "这将从浏览器地址栏中移除已访问页面的建议。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "這會移除瀏覽器網址列中已造訪頁面的建議。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 옴니바에서 방문 페이지 제안을 제거합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dies entfernt Vorschläge für besuchte Seiten aus der Browser-Adressleiste." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esto elimina las sugerencias de páginas visitadas de la barra de direcciones del navegador." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cela supprime les suggestions de pages visitées de la barre d'adresse du navigateur." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Questa operazione rimuove i suggerimenti delle pagine visitate dalla barra degli indirizzi del browser." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Dette fjerner besøgte sideforslag fra browserens omnibar." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Spowoduje to usunięcie podpowiedzi odwiedzonych stron z paska adresu przeglądarki." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Это удалит подсказки посещенных страниц из адресной строки браузера." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ovo uklanja prijedloge posjećenih stranica iz omnibar preglednika." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يؤدي هذا إلى إزالة اقتراحات الصفحات المزارة من شريط عنوان المتصفح." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dette fjerner forslag basert på besøkte sider fra nettleserens adressefelt." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Isto remove as sugestões de páginas visitadas da barra de endereço do navegador." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การดำเนินการนี้จะลบคำแนะนำหน้าที่เยี่ยมชมจากแถบที่อยู่เบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu, tarayıcı çok amaçlı çubuğundan ziyaret edilen sayfa önerilerini kaldırır." + } } } }, @@ -7275,6 +48267,102 @@ "state": "translated", "value": "ブラウザ履歴をクリアしますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "清除浏览器历史记录?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要清除瀏覽記錄嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 기록을 지우시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browserverlauf löschen?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Borrar historial del navegador?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Effacer l'historique du navigateur ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancellare la cronologia del browser?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd browserhistorik?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyścić historię przeglądarki?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить историю браузера?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obrisati historiju preglednika?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح سجل المتصفح؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tømme nettleserhistorikk?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar histórico do navegador?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างประวัติเบราว์เซอร์หรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı geçmişi temizlensin mi?" + } } } }, @@ -7292,6 +48380,102 @@ "state": "translated", "value": "保存済みのページはまだありません。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "尚无已保存的页面。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "尚無已儲存的頁面。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아직 저장된 페이지가 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch keine gespeicherten Seiten." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Aún no hay páginas guardadas." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune page enregistrée pour le moment." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna pagina salvata." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen gemte sider endnu." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak zapisanych stron." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохраненных страниц пока нет." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Još nema sačuvanih stranica." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا توجد صفحات محفوظة بعد." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen lagrede sider ennå." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma página salva ainda." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ยังไม่มีหน้าที่บันทึก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Henüz kayıtlı sayfa yok." + } } } }, @@ -7309,6 +48493,102 @@ "state": "translated", "value": "%lld件の保存済みページがオムニバーの候補に表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%lld 个已保存页面显示在地址栏建议中。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%lld 個已儲存頁面會出現在網址列建議中。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%lld개의 저장된 페이지가 옴니바 제안에 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld gespeicherte Seiten erscheinen in den Adressleisten-Vorschlägen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%lld páginas guardadas aparecen en las sugerencias de la barra de direcciones." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%lld pages enregistrées apparaissent dans les suggestions de la barre d'adresse." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%lld pagine salvate appaiono nei suggerimenti della barra degli indirizzi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%lld gemte sider vises i omnibar-forslag." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%lld zapisanych stron pojawia się w podpowiedziach paska adresu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохраненных страниц: %lld. Отображаются в подсказках адресной строки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%lld sačuvanih stranica se pojavljuje u prijedlozima omnibara." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "%lld صفحة محفوظة تظهر في اقتراحات شريط العنوان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%lld lagrede sider vises i adressefeltforslag." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%lld páginas salvas aparecem nas sugestões da barra de endereço." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%lld หน้าที่บันทึกจะปรากฏในคำแนะนำแถบที่อยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%lld kayıtlı sayfa çok amaçlı çubuk önerilerinde görünür." + } } } }, @@ -7326,6 +48606,102 @@ "state": "translated", "value": "1件の保存済みページがオムニバーの候補に表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "1 个已保存页面显示在地址栏建议中。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "1 個已儲存頁面會出現在網址列建議中。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "1개의 저장된 페이지가 옴니바 제안에 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "1 gespeicherte Seite erscheint in den Adressleisten-Vorschlägen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "1 página guardada aparece en las sugerencias de la barra de direcciones." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "1 page enregistrée apparaît dans les suggestions de la barre d'adresse." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "1 pagina salvata appare nei suggerimenti della barra degli indirizzi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "1 gemt side vises i omnibar-forslag." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "1 zapisana strona pojawia się w podpowiedziach paska adresu." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "1 сохраненная страница отображается в подсказках адресной строки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "1 sačuvana stranica se pojavljuje u prijedlozima omnibara." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "صفحة محفوظة واحدة تظهر في اقتراحات شريط العنوان." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "1 lagret side vises i adressefeltforslag." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "1 página salva aparece nas sugestões da barra de endereço." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "1 หน้าที่บันทึกจะปรากฏในคำแนะนำแถบที่อยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "1 kayıtlı sayfa çok amaçlı çubuk önerilerinde görünür." + } } } }, @@ -7343,6 +48719,102 @@ "state": "translated", "value": "内蔵ブラウザで開くホスト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在内嵌浏览器中打开的主机" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在內建瀏覽器中開啟的主機" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "내장 브라우저에서 열 호스트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hosts, die im integrierten Browser geöffnet werden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Hosts para abrir en el navegador integrado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Hôtes à ouvrir dans le navigateur intégré" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Host da aprire nel browser integrato" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Værter der åbnes i den indlejrede browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hosty do otwierania we wbudowanej przeglądarce" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хосты для открытия во встроенном браузере" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Hostovi za otvaranje u ugrađenom pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المضيفون للفتح في المتصفح المضمّن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Verter som åpnes i innebygd nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Hosts para Abrir no Navegador Integrado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฮสต์ที่เปิดในเบราว์เซอร์ในตัว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Gömülü Tarayıcıda Açılacak Ana Bilgisayarlar" + } } } }, @@ -7360,6 +48832,102 @@ "state": "translated", "value": "ターミナルのリンククリックおよびインターセプトされた`open https://...`呼び出しに適用されます。これらのホストのみcmuxで開きます。その他はデフォルトブラウザで開きます。1行に1つのホストまたはワイルドカード(例: example.com, *.internal.example)。空欄にするとすべてのホストをcmuxで開きます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "适用于终端中的链接点击和拦截的 `open https://...` 调用。仅这些主机在 cmux 中打开,其他主机在默认浏览器中打开。每行一个主机或通配符(例如:example.com、*.internal.example)。留空则在 cmux 中打开所有主机。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "套用於終端機連結點擊和攔截的 `open https://...` 呼叫。僅這些主機會在 cmux 中開啟,其他主機會在您的預設瀏覽器中開啟。每行一個主機或萬用字元(例如:example.com、*.internal.example)。留空則在 cmux 中開啟所有主機。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 링크 클릭 및 인터셉트된 `open https://...` 호출에 적용됩니다. 이 호스트만 cmux에서 열립니다. 나머지는 기본 브라우저에서 열립니다. 한 줄에 하나의 호스트 또는 와일드카드 (예: example.com, *.internal.example). 비워두면 모든 호스트가 cmux에서 열립니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt für Klicks auf Terminal-Links und abgefangene `open https://...`-Aufrufe. Nur diese Hosts werden in cmux geöffnet. Andere werden in Ihrem Standardbrowser geöffnet. Ein Host oder Platzhalter pro Zeile (zum Beispiel: example.com, *.internal.example). Leer lassen, um alle Hosts in cmux zu öffnen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se aplica a clics en enlaces del terminal y llamadas interceptadas de `open https://...`. Solo estos hosts se abren en cmux. Los demás se abren en tu navegador predeterminado. Un host o comodín por línea (por ejemplo: example.com, *.internal.example). Déjalo vacío para abrir todos los hosts en cmux." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "S'applique aux clics sur les liens du terminal et aux appels `open https://...` interceptés. Seuls ces hôtes s'ouvrent dans cmux. Les autres s'ouvrent dans votre navigateur par défaut. Un hôte ou caractère générique par ligne (par exemple : example.com, *.internal.example). Laissez vide pour ouvrir tous les hôtes dans cmux." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Si applica ai clic sui link nel terminale e alle chiamate intercettate `open https://...`. Solo questi host si aprono in cmux. Gli altri si aprono nel browser predefinito. Un host o wildcard per riga (ad esempio: example.com, *.internal.example). Lascia vuoto per aprire tutti gli host in cmux." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gælder for terminallinksklik og opfangede `open https://...`-kald. Kun disse værter åbnes i cmux. Andre åbnes i din standardbrowser. Én vært eller wildcard pr. linje (f.eks.: example.com, *.internal.example). Lad stå tomt for at åbne alle værter i cmux." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dotyczy kliknięć linków w terminalu i przechwyconych wywołań `open https://...`. Tylko te hosty otwierają się w cmux. Pozostałe otwierają się w domyślnej przeglądarce. Jeden host lub wzorzec na wiersz (na przykład: example.com, *.internal.example). Pozostaw puste, aby otwierać wszystkie hosty w cmux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Применяется к нажатиям на ссылки в терминале и перехваченным вызовам `open https://...`. Только эти хосты открываются в cmux. Остальные открываются в браузере по умолчанию. Один хост или шаблон на строку (например: example.com, *.internal.example). Оставьте пустым, чтобы открывать все хосты в cmux." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Primjenjuje se na klikove linkova u terminalu i presretnute `open https://...` pozive. Samo se ovi hostovi otvaraju u cmux. Ostali se otvaraju u podrazumijevanom pregledniku. Jedan host ili zamjenski znak po redu (na primjer: example.com, *.internal.example). Ostavite prazno da otvorite sve hostove u cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ينطبق على نقرات الروابط في الطرفية واستدعاءات `open https://...` المعترضة. فقط هؤلاء المضيفون يفتحون في cmux. البقية تفتح في متصفحك الافتراضي. مضيف واحد أو حرف بدل لكل سطر (مثال: example.com, *.internal.example). اتركه فارغًا لفتح جميع المضيفين في cmux." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gjelder for terminallenkeklikk og avlyttede `open https://...`-kall. Bare disse vertene åpnes i cmux. Andre åpnes i standard nettleser. Én vert eller jokertegn per linje (for eksempel: example.com, *.internal.example). La stå tom for å åpne alle verter i cmux." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aplica-se a cliques em links do terminal e chamadas interceptadas de `open https://...`. Apenas estes hosts abrem no cmux. Outros abrem no seu navegador padrão. Um host ou curinga por linha (por exemplo: example.com, *.internal.example). Deixe vazio para abrir todos os hosts no cmux." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใช้กับการคลิกลิงก์ในเทอร์มินัลและการดักจับคำสั่ง `open https://...` เฉพาะโฮสต์เหล่านี้เท่านั้นที่จะเปิดใน cmux โฮสต์อื่นจะเปิดในเบราว์เซอร์เริ่มต้นของคุณ โฮสต์หรือ wildcard หนึ่งรายการต่อบรรทัด (ตัวอย่าง: example.com, *.internal.example) เว้นว่างเพื่อเปิดโฮสต์ทั้งหมดใน cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal bağlantı tıklamalarına ve yakalanan `open https://...` çağrılarına uygulanır. Yalnızca bu ana bilgisayarlar cmux'ta açılır. Diğerleri varsayılan tarayıcınızda açılır. Satır başına bir ana bilgisayar veya joker karakter (örneğin: example.com, *.internal.example). Tüm ana bilgisayarları cmux'ta açmak için boş bırakın." + } } } }, @@ -7377,6 +48945,102 @@ "state": "translated", "value": "内蔵ブラウザで許可するHTTPホスト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "内嵌浏览器中允许的 HTTP 主机" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "允許在內建瀏覽器中使用的 HTTP 主機" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "내장 브라우저에서 허용할 HTTP 호스트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "HTTP-Hosts, die im integrierten Browser zugelassen sind" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Hosts HTTP permitidos en el navegador integrado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Hôtes HTTP autorisés dans le navigateur intégré" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Host HTTP consentiti nel browser integrato" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "HTTP-værter tilladt i den indlejrede browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Hosty HTTP dozwolone we wbudowanej przeglądarce" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "HTTP-хосты, разрешенные во встроенном браузере" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "HTTP hostovi dozvoljeni u ugrađenom pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مضيفو HTTP المسموح بهم في المتصفح المضمّن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "HTTP-verter tillatt i innebygd nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Hosts HTTP Permitidos no Navegador Integrado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฮสต์ HTTP ที่อนุญาตในเบราว์เซอร์ในตัว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Gömülü Tarayıcıda İzin Verilen HTTP Ana Bilgisayarları" + } } } }, @@ -7394,6 +49058,102 @@ "state": "translated", "value": "警告プロンプトなしでcmuxで開けるHTTP(非HTTPS)ホストを制御します。デフォルトにはlocalhost、127.0.0.1、::1、0.0.0.0、*.localtest.meが含まれます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "控制哪些 HTTP(非 HTTPS)主机可以在 cmux 中打开而不显示警告提示。默认包括 localhost、127.0.0.1、::1、0.0.0.0 和 *.localtest.me。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "控制哪些 HTTP(非 HTTPS)主機可以在 cmux 中開啟而不顯示警告提示。預設包含 localhost、127.0.0.1、::1、0.0.0.0 和 *.localtest.me。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 경고 없이 열 수 있는 HTTP(비HTTPS) 호스트를 제어합니다. 기본값에는 localhost, 127.0.0.1, ::1, 0.0.0.0 및 *.localtest.me가 포함됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Steuert, welche HTTP-Hosts (nicht HTTPS) ohne Warnhinweis in cmux geöffnet werden können. Standardmäßig enthalten: localhost, 127.0.0.1, ::1, 0.0.0.0 und *.localtest.me." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Controla qué hosts HTTP (no HTTPS) pueden abrirse en cmux sin un mensaje de advertencia. Los valores predeterminados incluyen localhost, 127.0.0.1, ::1, 0.0.0.0 y *.localtest.me." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Contrôle quels hôtes HTTP (non HTTPS) peuvent s'ouvrir dans cmux sans avertissement. Les valeurs par défaut incluent localhost, 127.0.0.1, ::1, 0.0.0.0 et *.localtest.me." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Controlla quali host HTTP (non HTTPS) possono aprirsi in cmux senza un avviso. I valori predefiniti includono localhost, 127.0.0.1, ::1, 0.0.0.0 e *.localtest.me." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Styrer hvilke HTTP-værter (ikke-HTTPS) der kan åbnes i cmux uden advarselsmeddelelse. Standardindstillinger inkluderer localhost, 127.0.0.1, ::1, 0.0.0.0 og *.localtest.me." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Określa, które hosty HTTP (inne niż HTTPS) mogą być otwierane w cmux bez ostrzeżenia. Domyślnie uwzględniono localhost, 127.0.0.1, ::1, 0.0.0.0 i *.localtest.me." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Определяет, какие HTTP (не HTTPS) хосты могут открываться в cmux без предупреждения. По умолчанию включены localhost, 127.0.0.1, ::1, 0.0.0.0 и *.localtest.me." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kontroliše koji HTTP (ne-HTTPS) hostovi mogu biti otvoreni u cmux bez upozorenja. Podrazumijevani uključuju localhost, 127.0.0.1, ::1, 0.0.0.0 i *.localtest.me." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يتحكم في مضيفي HTTP (غير HTTPS) الذين يمكنهم الفتح في cmux بدون رسالة تحذير. الافتراضيات تشمل localhost و 127.0.0.1 و ::1 و 0.0.0.0 و *.localtest.me." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Styrer hvilke HTTP-verter (ikke HTTPS) som kan åpnes i cmux uten advarselsmelding. Standardverdier inkluderer localhost, 127.0.0.1, ::1, 0.0.0.0 og *.localtest.me." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Controla quais hosts HTTP (não HTTPS) podem abrir no cmux sem um aviso. Os padrões incluem localhost, 127.0.0.1, ::1, 0.0.0.0 e *.localtest.me." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ควบคุมว่าโฮสต์ HTTP (ไม่ใช่ HTTPS) ใดที่สามารถเปิดใน cmux โดยไม่ต้องแสดงคำเตือน ค่าเริ่มต้นรวมถึง localhost, 127.0.0.1, ::1, 0.0.0.0 และ *.localtest.me" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Hangi HTTP (HTTPS olmayan) ana bilgisayarlarının cmux'ta uyarı istemi olmadan açılabileceğini kontrol eder. Varsayılanlar localhost, 127.0.0.1, ::1, 0.0.0.0 ve *.localtest.me içerir." + } } } }, @@ -7411,6 +49171,102 @@ "state": "translated", "value": "1行に1つのホストまたはワイルドカード(例: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "每行一个主机或通配符(例如:localhost、127.0.0.1、::1、0.0.0.0、*.localtest.me)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "每行一個主機或萬用字元(例如:localhost、127.0.0.1、::1、0.0.0.0、*.localtest.me)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "한 줄에 하나의 호스트 또는 와일드카드 (예: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ein Host oder Platzhalter pro Zeile (zum Beispiel: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Un host o comodín por línea (por ejemplo: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Un hôte ou caractère générique par ligne (par exemple : localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Un host o wildcard per riga (ad esempio: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Én vært eller wildcard pr. linje (f.eks.: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Jeden host lub wzorzec na wiersz (na przykład: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Один хост или шаблон на строку (например: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Jedan host ili zamjenski znak po redu (na primjer: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مضيف واحد أو حرف بدل لكل سطر (مثال: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Én vert eller jokertegn per linje (for eksempel: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Um host ou curinga por linha (por exemplo: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฮสต์หรือ wildcard หนึ่งรายการต่อบรรทัด (ตัวอย่าง: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Satır başına bir ana bilgisayar veya joker karakter (örneğin: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } } } }, @@ -7428,6 +49284,102 @@ "state": "translated", "value": "保存" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "保存" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "儲存" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "저장" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Speichern" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Guardar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Enregistrer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Salva" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gem" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zapisz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохранить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Spremi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "حفظ" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lagre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Salvar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "บันทึก" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kaydet" + } } } }, @@ -7445,6 +49397,102 @@ "state": "translated", "value": "ターミナルでopen http(s)をインターセプト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在终端中拦截 open http(s)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在終端機中攔截 open http(s)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널에서 open http(s) 인터셉트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "open http(s) im Terminal abfangen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Interceptar open http(s) en Terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Intercepter open http(s) dans le terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Intercetta open http(s) nel terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opfang open http(s) i terminal" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przechwytuj open http(s) w terminalu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перехватывать open http(s) в терминале" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Presretni open http(s) u Terminalu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اعتراض open http(s) في الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Avlytt open http(s) i terminal" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Interceptar open http(s) no Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ดักจับ open http(s) ในเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminalde open http(s) Komutlarını Yakala" + } } } }, @@ -7462,6 +49510,102 @@ "state": "translated", "value": "オフの場合、`open https://...`および`open http://...`は常にデフォルトブラウザを使用します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭后,`open https://...` 和 `open http://...` 始终使用默认浏览器。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉時,`open https://...` 和 `open http://...` 一律使用您的預設瀏覽器。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비활성화하면 `open https://...` 및 `open http://...`가 항상 기본 브라우저를 사용합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wenn deaktiviert, verwenden `open https://...` und `open http://...` immer Ihren Standardbrowser." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cuando está desactivado, `open https://...` y `open http://...` siempre usan tu navegador predeterminado." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lorsque désactivé, `open https://...` et `open http://...` utilisent toujours votre navigateur par défaut." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Quando disattivato, `open https://...` e `open http://...` usano sempre il browser predefinito." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Når deaktiveret, bruger `open https://...` og `open http://...` altid din standardbrowser." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Po wyłączeniu `open https://...` i `open http://...` zawsze używają domyślnej przeglądarki." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "При отключении `open https://...` и `open http://...` всегда используют браузер по умолчанию." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kada je isključeno, `open https://...` i `open http://...` uvijek koriste podrazumijevani preglednik." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند التعطيل، يستخدم `open https://...` و `open http://...` دائمًا متصفحك الافتراضي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Når av, bruker `open https://...` og `open http://...` alltid standard nettleser." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Quando desativado, `open https://...` e `open http://...` sempre usam seu navegador padrão." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เมื่อปิด `open https://...` และ `open http://...` จะใช้เบราว์เซอร์เริ่มต้นของคุณเสมอ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapalıyken, `open https://...` ve `open http://...` her zaman varsayılan tarayıcınızı kullanır." + } } } }, @@ -7479,6 +49623,102 @@ "state": "translated", "value": "ターミナルのリンクをcmuxブラウザで開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 浏览器中打开终端链接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在 cmux 瀏覽器中開啟終端機連結" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 링크를 cmux 브라우저에서 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Links im cmux-Browser öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir enlaces del terminal en el navegador de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir les liens du terminal dans le navigateur cmux" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri link del terminale nel browser cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn terminallinks i cmux-browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwieraj linki terminala w przeglądarce cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открывать ссылки из терминала в браузере cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori linkove terminala u cmux pregledniku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح روابط الطرفية في متصفح cmux" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne terminallenker i cmux-nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Links do Terminal no Navegador do cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดลิงก์เทอร์มินัลในเบราว์เซอร์ cmux" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Bağlantılarını cmux Tarayıcısında Aç" + } } } }, @@ -7496,6 +49736,102 @@ "state": "translated", "value": "オフの場合、ターミナル出力のリンクはデフォルトブラウザで開きます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭后,终端输出中点击的链接在默认浏览器中打开。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉時,終端機輸出中點擊的連結會在您的預設瀏覽器中開啟。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비활성화하면 터미널 출력에서 클릭한 링크가 기본 브라우저에서 열립니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wenn deaktiviert, werden im Terminal angeklickte Links in Ihrem Standardbrowser geöffnet." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cuando está desactivado, los enlaces en la salida del terminal se abren en tu navegador predeterminado." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lorsque désactivé, les liens cliqués dans la sortie du terminal s'ouvrent dans votre navigateur par défaut." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Quando disattivato, i link cliccati nell'output del terminale si aprono nel browser predefinito." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Når deaktiveret, åbnes links, der klikkes i terminaloutput, i din standardbrowser." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Po wyłączeniu linki kliknięte w terminalu otwierają się w domyślnej przeglądarce." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "При отключении ссылки из терминала открываются в браузере по умолчанию." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kada je isključeno, linkovi kliknuti u izlazu terminala se otvaraju u podrazumijevanom pregledniku." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند التعطيل، تفتح الروابط المنقورة في مخرجات الطرفية في متصفحك الافتراضي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Når av, åpnes lenker som klikkes i terminalutdata i standard nettleser." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Quando desativado, links clicados na saída do terminal abrem no seu navegador padrão." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เมื่อปิด ลิงก์ที่คลิกในเอาต์พุตเทอร์มินัลจะเปิดในเบราว์เซอร์เริ่มต้นของคุณ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapalıyken, terminal çıktısında tıklanan bağlantılar varsayılan tarayıcınızda açılır." + } } } }, @@ -7513,6 +49849,102 @@ "state": "translated", "value": "デフォルト検索エンジン" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "默认搜索引擎" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "預設搜尋引擎" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본 검색 엔진" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Standardsuchmaschine" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Motor de búsqueda predeterminado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Moteur de recherche par défaut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Motore di ricerca predefinito" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Standardsøgemaskine" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Domyślna wyszukiwarka" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поисковая система по умолчанию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podrazumijevani pretraživač" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "محرك البحث الافتراضي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Standard søkemotor" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Motor de Busca Padrão" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เครื่องมือค้นหาเริ่มต้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Varsayılan Arama Motoru" + } } } }, @@ -7530,6 +49962,102 @@ "state": "translated", "value": "ブラウザのアドレスバーで入力がURLでない場合に使用されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器地址栏中输入非 URL 内容时使用。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "當瀏覽器網址列的輸入不是 URL 時使用。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "입력이 URL이 아닌 경우 브라우저 주소 표시줄에서 사용됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird von der Browser-Adressleiste verwendet, wenn die Eingabe keine URL ist." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usado por la barra de direcciones del navegador cuando la entrada no es una URL." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utilisé par la barre d'adresse du navigateur lorsque la saisie n'est pas une URL." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Utilizzato dalla barra degli indirizzi del browser quando l'input non è un URL." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Bruges af browserens adresselinje, når input ikke er en URL." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Używana przez pasek adresu przeglądarki, gdy dane wejściowe nie są adresem URL." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используется адресной строкой браузера, когда ввод не является URL." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Koristi se u adresnoj traci preglednika kada unos nije URL." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُستخدم بواسطة شريط عنوان المتصفح عندما لا يكون الإدخال URL." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Brukes av adressefeltet i nettleseren når inndataen ikke er en URL." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Usado pela barra de endereço do navegador quando a entrada não é uma URL." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใช้โดยแถบที่อยู่เบราว์เซอร์เมื่ออินพุตไม่ใช่ URL" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Giriş bir URL olmadığında tarayıcı adres çubuğu tarafından kullanılır." + } } } }, @@ -7547,6 +50075,102 @@ "state": "translated", "value": "検索候補を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示搜索建议" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示搜尋建議" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "검색 제안 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchvorschläge anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar sugerencias de búsqueda" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les suggestions de recherche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra suggerimenti di ricerca" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis søgeforslag" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż podpowiedzi wyszukiwania" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать поисковые подсказки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži prijedloge pretrage" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض اقتراحات البحث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis søkeforslag" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Sugestões de Pesquisa" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงคำแนะนำการค้นหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Arama Önerilerini Göster" + } } } }, @@ -7564,6 +50188,102 @@ "state": "translated", "value": "ブラウザテーマ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器主题" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器主題" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 테마" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Design" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tema del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Thème du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Tema del browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browsertema" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Motyw przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tema preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مظهر المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettlesertema" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tema do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ธีมเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Teması" + } } } }, @@ -7581,6 +50301,102 @@ "state": "translated", "value": "%@は対応ページにそのカラースキームを強制します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%@ 为兼容页面强制使用该配色方案。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%@ 會為相容的頁面強制套用該色彩配置。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@은(는) 호환 페이지에 해당 색상 구성표를 강제 적용합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ erzwingt dieses Farbschema für kompatible Seiten." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%@ fuerza ese esquema de colores para las páginas compatibles." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%@ impose ce jeu de couleurs aux pages compatibles." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ impone quello schema di colori per le pagine compatibili." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ tvinger det farveskema for kompatible sider." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ wymusza ten schemat kolorów dla zgodnych stron." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ принудительно применяет эту цветовую схему для совместимых страниц." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ nameće tu shemu boja za kompatibilne stranice." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "%@ يفرض نظام الألوان هذا للصفحات المتوافقة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ tvinger det fargeskjemaet for kompatible sider." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ força esse esquema de cores para páginas compatíveis." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ บังคับใช้โทนสีนั้นสำหรับหน้าที่รองรับ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ uyumlu sayfalar için bu renk şemasını zorlar." + } } } }, @@ -7598,6 +50414,102 @@ "state": "translated", "value": "システムはアプリとmacOSの外観に従います。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "系统跟随应用和 macOS 外观。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "「系統」會跟隨 App 和 macOS 外觀。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "시스템은 앱 및 macOS 외관을 따릅니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "System folgt der App- und macOS-Darstellung." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sistema sigue la apariencia de la app y macOS." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Système suit l'apparence de l'app et de macOS." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sistema segue l'aspetto dell'app e di macOS." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "System følger app- og macOS-udseende." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Systemowy podąża za wyglądem aplikacji i macOS." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная тема следует за оформлением приложения и macOS." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sistemski prati izgled aplikacije i macOS." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النظام يتبع مظهر التطبيق و macOS." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "System følger app- og macOS-utseende." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sistema segue a aparência do app e do macOS." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ระบบจะตามธีมแอปและรูปลักษณ์ macOS" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sistem, uygulama ve macOS görünümünü takip eder." + } } } }, @@ -7615,6 +50527,102 @@ "state": "translated", "value": "コンテンツ背景" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "内容背景" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "內容背景" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "콘텐츠 배경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Inhaltshintergrund" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fondo de contenido" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Arrière-plan du contenu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sfondo contenuto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indholdsbaggrund" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tło zawartości" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фон содержимого" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pozadina sadržaja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "خلفية المحتوى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innholdsbakgrunn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fundo de Conteúdo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นหลังเนื้อหา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İçerik Arka Planı" + } } } }, @@ -7632,6 +50640,102 @@ "state": "translated", "value": "フルスクリーンUI" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全屏 UI" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全螢幕 UI" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전체 화면 UI" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollbild-UI" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Interfaz a pantalla completa" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Interface plein écran" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "UI a schermo intero" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fuldskærms-UI" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pełnoekranowy interfejs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Полноэкранный интерфейс" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "UI punog ekrana" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "واجهة ملء الشاشة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fullskjerm-UI" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Interface em Tela Cheia" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "UI เต็มหน้าจอ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tam Ekran Kullanıcı Arayüzü" + } } } }, @@ -7649,6 +50753,102 @@ "state": "translated", "value": "ヘッダビュー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "头部视图" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標題列" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "헤더 뷰" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Kopfansicht" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Vista de encabezado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Vue d'en-tête" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vista intestazione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Headervisning" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Widok nagłówka" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заголовок" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zaglavlje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الرأس" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Topptekstvisning" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cabeçalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มุมมองส่วนหัว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Başlık Görünümü" + } } } }, @@ -7666,6 +50866,102 @@ "state": "translated", "value": "HUDウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "HUD 窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "HUD 視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "HUD 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "HUD-Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ventana HUD" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fenêtre HUD" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Finestra HUD" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "HUD-vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Okno HUD" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "HUD-окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "HUD prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة HUD" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "HUD-vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Janela HUD" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่าง HUD" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "HUD Penceresi" + } } } }, @@ -7683,6 +50979,102 @@ "state": "translated", "value": "Liquid Glass(macOS 26以降)" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } } } }, @@ -7700,6 +51092,102 @@ "state": "translated", "value": "メニュー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "菜单" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選單" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "메뉴" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Menü" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Menú" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Меню" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Meni" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "القائمة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Meny" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เมนู" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Menü" + } } } }, @@ -7717,6 +51205,102 @@ "state": "translated", "value": "なし" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ninguno" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucun" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuno" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ništa" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بدون" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhum" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มี" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yok" + } } } }, @@ -7734,6 +51318,102 @@ "state": "translated", "value": "ポップオーバー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "弹出框" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "彈出框" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "팝오버" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyskakujące okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Всплывающее окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Iskočni prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة منبثقة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ป็อปโอเวอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Açılır Pencere" + } } } }, @@ -7751,6 +51431,102 @@ "state": "translated", "value": "シート" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作表" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作表" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "시트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dialogblatt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Hoja" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Feuille" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Foglio" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ark" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Arkusz" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лист" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "List" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ورقة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ark" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Folha" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ชีท" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sayfa" + } } } }, @@ -7768,6 +51544,102 @@ "state": "translated", "value": "サイドバー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Боковая панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Bočna traka" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sidepanel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğu" + } } } }, @@ -7785,6 +51657,102 @@ "state": "translated", "value": "ツールチップ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工具提示" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工具提示" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "도구 설명" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tooltip" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descripción emergente" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Info-bulle" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Suggerimento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Værktøjstip" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podpowiedź" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подсказка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Opis alata" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تلميح أداة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Verktøytips" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dica de Ferramenta" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คำแนะนำเครื่องมือ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Araç İpucu" + } } } }, @@ -7802,6 +51770,102 @@ "state": "translated", "value": "ウインドウ下" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口底部" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗下方" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 아래" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Unter dem Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Debajo de la ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Sous la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sotto la finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Under vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pod oknem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Под окном" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ispod prozora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحت النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Under vinduet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sob a Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใต้หน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere Altı" + } } } }, @@ -7819,6 +51883,102 @@ "state": "translated", "value": "ウインドウ背景" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口背景" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗背景" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 배경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fensterhintergrund" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fondo de ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Arrière-plan de la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sfondo finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vinduesbaggrund" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tło okna" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фон окна" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pozadina prozora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "خلفية النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vindubakgrunn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fundo da Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นหลังหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere Arka Planı" + } } } }, @@ -7836,6 +51996,102 @@ "state": "translated", "value": "HUD Glass" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "HUD 玻璃" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "HUD 玻璃" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "HUD 글래스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "HUD-glas" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szkło HUD" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "HUD staklo" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "زجاج HUD" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "HUD-glass" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Vidro HUD" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } } } }, @@ -7853,6 +52109,102 @@ "state": "translated", "value": "ネイティブサイドバー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "原生侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "原生側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본 사이드바" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Native Seitenleiste" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Barra lateral nativa" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Barre latérale native" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Barra laterale nativa" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indbygget sidebjælke" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Natywny pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная боковая панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nativna bočna traka" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "شريط جانبي أصلي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innebygd sidepanel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Barra Lateral Nativa" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แถบด้านข้างแบบดั้งเดิม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yerel Kenar Çubuğu" + } } } }, @@ -7870,6 +52222,102 @@ "state": "translated", "value": "ポップオーバーGlass" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "弹出框玻璃" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "彈出框玻璃" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "팝오버 글래스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Popover-glas" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szkło wyskakujące" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Iskočno staklo" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "زجاج منبثق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Popover-glass" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Vidro Popover" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Açılır Pencere Glass" + } } } }, @@ -7887,6 +52335,102 @@ "state": "translated", "value": "Raycast Gray" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Raycast 灰" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Raycast 灰" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Raycast 그레이" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Raycast-grå" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szary Raycast" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Raycast siva" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "رمادي Raycast" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Raycast-grå" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cinza Raycast" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gri" + } } } }, @@ -7904,6 +52448,102 @@ "state": "translated", "value": "ソフトブラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "柔和模糊" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "柔和模糊" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "소프트 블러" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Blød sløring" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Delikatne rozmycie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Meko zamućenje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ضبابية ناعمة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Myk uskarphet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desfoque Suave" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yumuşak Bulanıklık" + } } } }, @@ -7921,6 +52561,102 @@ "state": "translated", "value": "ウインドウ下" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗口底部" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "視窗下方" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 아래" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Under vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pod oknem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ispod prozora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحت النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Under vinduet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sob a Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencere Altı" + } } } }, @@ -7938,6 +52674,102 @@ "state": "translated", "value": "すべての設定をリセット" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重置所有设置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重置所有設定" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모든 설정 초기화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle Einstellungen zurücksetzen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Restablecer todos los ajustes" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réinitialiser tous les réglages" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ripristina tutte le impostazioni" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nulstil alle indstillinger" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Resetuj wszystkie ustawienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить все настройки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Resetuj sve postavke" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تعيين جميع الإعدادات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilbakestill alle innstillinger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Redefinir Todos os Ajustes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีเซ็ตการตั้งค่าทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tüm Ayarları Sıfırla" + } } } }, @@ -7955,6 +52787,102 @@ "state": "translated", "value": "アプリ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "应用" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앱" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Aplikacja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Приложение" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Aplikacija" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التطبيق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แอป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygulama" + } } } }, @@ -7972,6 +52900,102 @@ "state": "translated", "value": "オートメーション" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自动化" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自動化" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatisierung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Automatización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Automatisation" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Automazione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Automatisering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Automatyzacja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Автоматизация" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Automatizacija" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الأتمتة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Automatisering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Automação" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ระบบอัตโนมัติ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomasyon" + } } } }, @@ -7989,6 +53013,102 @@ "state": "translated", "value": "ブラウザ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przeglądarka" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preglednik" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı" + } } } }, @@ -8006,6 +53126,102 @@ "state": "translated", "value": "キーボードショートカット" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "键盘快捷键" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "鍵盤快速鍵" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "키보드 단축키" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tastaturkurzbefehle" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Atajos de teclado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Raccourcis clavier" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Abbreviazioni da tastiera" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tastaturgenveje" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Skróty klawiaturowe" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сочетания клавиш" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prečice na tastaturi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختصارات لوحة المفاتيح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tastatursnarveier" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Atalhos de Teclado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แป้นพิมพ์ลัด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Klavye Kısayolları" + } } } }, @@ -8023,6 +53239,102 @@ "state": "translated", "value": "リセット" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重置" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "초기화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurücksetzen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Restablecer" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réinitialiser" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ripristina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nulstil" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Resetuj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сброс" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Resetovanje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التعيين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilbakestill" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Redefinir" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีเซ็ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sıfırla" + } } } }, @@ -8040,6 +53352,102 @@ "state": "translated", "value": "ワークスペースカラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区颜色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區顏色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 색상" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereichsfarben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Colores de espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleurs des espaces de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Colori area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdefarver" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kolory przestrzeni roboczych" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Цвета рабочих пространств" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Boje radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ألوان مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdefarger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cores da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สีเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Renkleri" + } } } }, @@ -8057,6 +53465,102 @@ "state": "translated", "value": "ショートカット値をクリックして新しいショートカットを記録します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "点击快捷键值以录制新快捷键。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按一下快速鍵值以錄製新的快速鍵。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "단축키 값을 클릭하여 새 단축키를 기록하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Klicken Sie auf einen Kurzbefehlswert, um einen neuen Kurzbefehl aufzunehmen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Haz clic en un valor de atajo para grabar un nuevo atajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Cliquez sur une valeur de raccourci pour en enregistrer un nouveau." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Fai clic su un valore di scorciatoia per registrarne una nuova." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Klik på en genvejsværdi for at optage en ny genvej." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kliknij wartość skrótu, aby nagrać nowy skrót." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите на значение сочетания, чтобы записать новое." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kliknite na vrijednost prečice da snimite novu prečicu." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انقر على قيمة اختصار لتسجيل اختصار جديد." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Klikk på en snarveiverdi for å ta opp en ny snarvei." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Clique em um valor de atalho para gravar um novo atalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คลิกค่าทางลัดเพื่อบันทึกทางลัดใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni bir kısayol kaydetmek için bir kısayol değerine tıklayın." + } } } }, @@ -8074,6 +53578,102 @@ "state": "translated", "value": "Cmd/Ctrl長押しのショートカットヒントを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示 Cmd/Ctrl 长按快捷键提示" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示 Cmd/Ctrl 按住時的快速鍵提示" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Cmd/Ctrl 누르고 있을 때 단축키 힌트 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Cmd/Ctrl-Gedrückthalten-Kurzbefehlhinweise anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar indicaciones de atajo al mantener Cmd/Ctrl" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les indications de raccourcis avec Cmd/Ctrl enfoncé" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra suggerimenti scorciatoie Cmd/Ctrl" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis Cmd/Ctrl-hold genvejstip" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż podpowiedzi skrótów przy przytrzymaniu Cmd/Ctrl" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать подсказки сочетаний при удержании Cmd/Ctrl" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži savjete za prečice pri držanju Cmd/Ctrl" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض تلميحات اختصارات Cmd/Ctrl عند الضغط المطول" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis Cmd/Ctrl-hold snarveihint" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Dicas de Atalho ao Segurar Cmd/Ctrl" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงคำแนะนำทางลัด Cmd/Ctrl ค้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cmd/Ctrl Basılı Tutma Kısayol İpuçlarını Göster" + } } } }, @@ -8091,6 +53691,102 @@ "state": "translated", "value": "CmdまたはCtrlを長押ししてもショートカットヒントピルは非表示のままです。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "长按 Cmd 或 Ctrl 时隐藏快捷键提示标签。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按住 Cmd 或 Ctrl 時不顯示快速鍵提示標籤。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Cmd 또는 Ctrl을 누르고 있어도 단축키 힌트 표시가 숨겨집니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Gedrückthalten von Cmd oder Ctrl hält die Kurzbefehlhinweise ausgeblendet." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mantener Cmd o Ctrl mantiene ocultas las indicaciones de atajos." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Maintenir Cmd ou Ctrl garde les pastilles d'indication de raccourcis masquées." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Tenere premuto Cmd o Ctrl mantiene nascosti i suggerimenti delle scorciatoie." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "At holde Cmd eller Ctrl holder genvejstip skjulte." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przytrzymanie Cmd lub Ctrl nie pokazuje podpowiedzi skrótów." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удержание Cmd или Ctrl не показывает подсказки сочетаний." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Držanje Cmd ili Ctrl drži savjete za prečice skrivenim." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الضغط المطول على Cmd أو Ctrl يبقي كبسولات التلميح مخفية." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Å holde Cmd eller Ctrl holder snarveihintpillene skjult." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Segurar Cmd ou Ctrl mantém as pílulas de dica de atalho ocultas." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การกด Cmd หรือ Ctrl ค้างจะซ่อนป้ายคำแนะนำทางลัด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cmd veya Ctrl basılı tutulduğunda kısayol ipucu rozetleri gizli kalır." + } } } }, @@ -8108,6 +53804,102 @@ "state": "translated", "value": "Cmd(サイドバー/タイトルバー)またはCtrl/Cmd(ペインタブ)を長押しするとショートカットヒントピルが表示されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "长按 Cmd(侧边栏/标题栏)或 Ctrl/Cmd(面板标签页)时显示快捷键提示标签。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按住 Cmd(側邊欄/標題列)或 Ctrl/Cmd(面板標籤頁)時顯示快速鍵提示標籤。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Cmd(사이드바/제목 표시줄) 또는 Ctrl/Cmd(패널 탭)를 누르고 있으면 단축키 힌트가 표시됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Gedrückthalten von Cmd (Seitenleiste/Titelleiste) oder Ctrl/Cmd (Bereichs-Tabs) zeigt Kurzbefehlhinweise an." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mantener Cmd (barra lateral/barra de título) o Ctrl/Cmd (pestañas del panel) muestra las indicaciones de atajos." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Maintenir Cmd (barre latérale/barre de titre) ou Ctrl/Cmd (onglets de panneau) affiche les pastilles d'indication de raccourcis." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Tenere premuto Cmd (barra laterale/barra del titolo) o Ctrl/Cmd (schede pannello) mostra i suggerimenti delle scorciatoie." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "At holde Cmd (sidebjælke/titellinje) eller Ctrl/Cmd (panelfaner) viser genvejstip." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przytrzymanie Cmd (pasek boczny/pasek tytułu) lub Ctrl/Cmd (karty panelu) pokazuje podpowiedzi skrótów." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удержание Cmd (боковая панель/заголовок) или Ctrl/Cmd (вкладки панели) показывает подсказки сочетаний." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Držanje Cmd (bočna traka/naslovna traka) ili Ctrl/Cmd (tabovi panela) prikazuje savjete za prečice." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الضغط المطول على Cmd (الشريط الجانبي/شريط العنوان) أو Ctrl/Cmd (ألسنة اللوحات) يعرض كبسولات التلميح." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Å holde Cmd (sidepanel/tittellinje) eller Ctrl/Cmd (panelfaner) viser snarveihintpiller." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Segurar Cmd (barra lateral/barra de título) ou Ctrl/Cmd (abas do painel) mostra pílulas de dica de atalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การกด Cmd ค้าง (แถบด้านข้าง/แถบชื่อ) หรือ Ctrl/Cmd (แท็บบานหน้าต่าง) จะแสดงป้ายคำแนะนำทางลัด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cmd (kenar çubuğu/başlık çubuğu) veya Ctrl/Cmd (bölme sekmeleri) basılı tutulduğunda kısayol ipucu rozetleri gösterilir." + } } } }, @@ -8125,6 +53917,102 @@ "state": "translated", "value": "アクティブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "活跃" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用中" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "활성" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktiv" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Activo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Actif" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attivo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiv" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Aktywny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активное" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Aktivno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نشط" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktiv" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ativo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ใช้งานอยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Etkin" + } } } }, @@ -8142,6 +54030,102 @@ "state": "translated", "value": "ウインドウに追従" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跟随窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跟隨視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 따르기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster folgen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Seguir ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Suivre la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segui finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Følg vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podążaj za oknem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следовать за окном" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prati prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "متابعة النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Følg vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Seguir Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตามหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencereyi Takip Et" + } } } }, @@ -8159,6 +54143,102 @@ "state": "translated", "value": "非アクティブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "不活跃" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "非使用中" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비활성" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Inaktiv" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Inactivo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Inactif" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inattivo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Inaktiv" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nieaktywny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Неактивное" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Neaktivno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "غير نشط" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Inaktiv" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Inativo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่ได้ใช้งาน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Etkin Değil" + } } } }, @@ -8176,6 +54256,102 @@ "state": "translated", "value": "設定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "设置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "設定" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설정" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ajustes" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réglages" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impostazioni" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indstillinger" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ustawienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Postavke" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإعدادات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Innstillinger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ajustes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การตั้งค่า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayarlar" + } } } }, @@ -8193,6 +54369,102 @@ "state": "translated", "value": "ベース: %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "基础色:%@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "基底:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Basis: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Base: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Base : %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Base: %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Base: %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Bazowy: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Базовый: %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Osnovna: %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الأساس: %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Basis: %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Base: %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ฐาน: %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Temel: %@" + } } } }, @@ -8210,6 +54482,102 @@ "state": "translated", "value": "カスタムカラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自定义颜色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自訂顏色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 색상" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzerdefinierte Farben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Colores personalizados" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleurs personnalisées" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Colori personalizzati" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Brugerdefinerede farver" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Własne kolory" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользовательские цвета" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prilagođene boje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ألوان مخصصة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Egendefinerte farger" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cores Personalizadas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สีที่กำหนดเอง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel Renkler" + } } } }, @@ -8227,6 +54595,102 @@ "state": "translated", "value": "ワークスペースカラーインジケーター" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区颜色指示器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區顏色指示器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 색상 표시기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereichsfarb-Indikator" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Indicador de color del espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Indicateur de couleur d'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Indicatore colore area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområdefarveindikator" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wskaźnik koloru przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Индикатор цвета рабочего пространства" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Indikator boje radnog prostora" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مؤشر لون مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområdefarge-indikator" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Indicador de Cor da Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ตัวบ่งชี้สีเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı Renk Göstergesi" + } } } }, @@ -8244,6 +54708,102 @@ "state": "translated", "value": "カスタムカラー: まだありません。ワークスペースのコンテキストメニューから「カスタムカラーを選択…」を使用してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自定义颜色:暂无。请从工作区右键菜单中使用「选取自定义颜色...」。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自訂顏色:尚無。請從工作區右鍵選單中使用「選擇自訂顏色...」。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용자 지정 색상: 아직 없습니다. 작업 공간 컨텍스트 메뉴에서 \"사용자 지정 색상 선택...\"을 사용하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzerdefinierte Farben: noch keine. Verwenden Sie 'Eigene Farbe wählen ...' im Kontextmenü eines Arbeitsbereichs." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Colores personalizados: ninguno aún. Usa \"Elegir color personalizado…\" desde el menú contextual de un espacio de trabajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleurs personnalisées : aucune pour le moment. Utilisez « Choisir une couleur personnalisée... » depuis le menu contextuel d'un espace de travail." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Colori personalizzati: nessuno. Usa \"Scegli colore personalizzato...\" dal menu contestuale di un'area di lavoro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Brugerdefinerede farver: ingen endnu. Brug \"Vælg brugerdefineret farve...\" fra en arbejdsområdekontekstmenu." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Własne kolory: jeszcze żadnych. Użyj „Wybierz własny kolor…” z menu kontekstowego przestrzeni roboczej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользовательские цвета: пока нет. Используйте «Выбрать пользовательский цвет...» из контекстного меню рабочего пространства." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prilagođene boje: još nema. Koristite \"Odaberi prilagođenu boju...\" iz kontekstnog menija radnog prostora." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ألوان مخصصة: لا يوجد بعد. استخدم \"اختيار لون مخصص...\" من قائمة سياق مساحة العمل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Egendefinerte farger: ingen ennå. Bruk «Velg egendefinert farge ...» fra en arbeidsområde-kontekstmeny." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cores personalizadas: nenhuma ainda. Use \"Escolher Cor Personalizada...\" no menu de contexto de uma área de trabalho." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สีที่กำหนดเอง: ยังไม่มี ใช้ \"เลือกสีที่กำหนดเอง...\" จากเมนูบริบทเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Özel renkler: henüz yok. Bir çalışma alanı bağlam menüsünden \"Özel Renk Seç...\" seçeneğini kullanın." + } } } }, @@ -8261,6 +54821,102 @@ "state": "translated", "value": "サイドバー > ワークスペースカラーで使用するカラーパレットをカスタマイズできます。「カスタムカラーを選択…」のエントリは以下に保存されます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自定义「侧边栏 > 工作区颜色」中使用的工作区调色板。「选取自定义颜色...」的条目将保存在下方。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自訂側邊欄 > 工作區顏色使用的色板。「選擇自訂顏色...」的項目會保存在下方。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 > 작업 공간 색상에서 사용하는 작업 공간 색상 팔레트를 사용자 지정합니다. \"사용자 지정 색상 선택...\" 항목은 아래에 저장됩니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passen Sie die Arbeitsbereichs-Farbpalette an, die unter Seitenleiste > Arbeitsbereichsfarbe verwendet wird. Einträge unter 'Eigene Farbe wählen ...' werden unten gespeichert." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Personaliza la paleta de colores de espacios de trabajo usada en Barra lateral > Color del espacio de trabajo. Las entradas de \"Elegir color personalizado…\" se guardan a continuación." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Personnalisez la palette de couleurs utilisée par Barre latérale > Couleur de l'espace de travail. Les entrées « Choisir une couleur personnalisée... » sont enregistrées ci-dessous." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Personalizza la palette di colori utilizzata da Barra laterale > Colore area di lavoro. Le voci \"Scegli colore personalizzato...\" vengono salvate qui sotto." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tilpas arbejdsområdets farvepalette, der bruges af Sidebjælke > Arbejdsområdefarve. \"Vælg brugerdefineret farve...\"-poster gemmes nedenfor." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dostosuj paletę kolorów przestrzeni roboczej używaną przez Pasek boczny > Kolor przestrzeni roboczej. Wpisy „Wybierz własny kolor…” są zapisywane poniżej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройте палитру цветов рабочих пространств, используемую в Боковая панель > Цвет рабочего пространства. Записи «Выбрать пользовательский цвет...» сохраняются ниже." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prilagodite paletu boja radnog prostora koju koristi Bočna traka > Boja radnog prostora. Unosi \"Odaberi prilagođenu boju...\" su trajno sačuvani ispod." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تخصيص لوحة ألوان مساحة العمل المستخدمة بواسطة الشريط الجانبي > لون مساحة العمل. إدخالات \"اختيار لون مخصص...\" تُحفظ أدناه." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilpass arbeidsområdefargepaletten som brukes av Sidepanel > Arbeidsområdefarge. «Velg egendefinert farge ...»-oppføringer lagres nedenfor." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Personalize a paleta de cores usada em Barra Lateral > Cor da Área de Trabalho. Entradas de \"Escolher Cor Personalizada...\" são salvas abaixo." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปรับแต่งจานสีเวิร์กสเปซที่ใช้ใน แถบด้านข้าง > สีเวิร์กสเปซ รายการ \"เลือกสีที่กำหนดเอง...\" จะถูกบันทึกด้านล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğu > Çalışma Alanı Rengi tarafından kullanılan çalışma alanı renk paletini özelleştirin. \"Özel Renk Seç...\" girişleri aşağıda saklanır." + } } } }, @@ -8278,6 +54934,102 @@ "state": "translated", "value": "削除" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "移除" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "移除" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "제거" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Entfernen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Eliminar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Supprimer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rimuovi" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fjern" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Usuń" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ukloni" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إزالة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Remover" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ลบ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kaldır" + } } } }, @@ -8295,6 +55047,102 @@ "state": "translated", "value": "パレットをリセット" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重置调色板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重置色板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "팔레트 초기화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Palette zurücksetzen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Restablecer paleta" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réinitialiser la palette" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ripristina palette" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nulstil palette" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Resetuj paletę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить палитру" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Resetuj paletu" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تعيين اللوحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilbakestill palett" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Redefinir Paleta" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีเซ็ตจานสี" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Paleti Sıfırla" + } } } }, @@ -8312,6 +55160,102 @@ "state": "translated", "value": "リセット" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重置" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "초기화" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurücksetzen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Restablecer" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Réinitialiser" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ripristina" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nulstil" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Resetuj" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Resetuj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تعيين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilbakestill" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Redefinir" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีเซ็ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sıfırla" + } } } }, @@ -8329,6 +55273,102 @@ "state": "translated", "value": "組み込みのデフォルトに戻し、すべてのカスタムカラーをクリアします。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "恢复内置默认值并清除所有自定义颜色。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "恢復內建預設值並清除所有自訂顏色。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "기본 제공 값을 복원하고 모든 사용자 지정 색상을 지웁니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Integrierte Standardeinstellungen wiederherstellen und alle benutzerdefinierten Farben löschen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Restaurar los valores predeterminados y borrar todos los colores personalizados." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Restaurer les valeurs par défaut et effacer toutes les couleurs personnalisées." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Ripristina i valori predefiniti e cancella tutti i colori personalizzati." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gendan indbyggede standardindstillinger og ryd alle brugerdefinerede farver." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przywróć wbudowane wartości domyślne i wyczyść wszystkie własne kolory." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Восстановить встроенные значения по умолчанию и удалить все пользовательские цвета." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Vrati ugrađene podrazumijevane vrijednosti i obriši sve prilagođene boje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استعادة الإعدادات الافتراضية المدمجة ومسح جميع الألوان المخصصة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gjenopprett innebygde standardverdier og fjern alle egendefinerte farger." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Restaurar padrões integrados e limpar todas as cores personalizadas." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คืนค่าเริ่มต้นในตัวและล้างสีที่กำหนดเองทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yerleşik varsayılanları geri yükle ve tüm özel renkleri temizle." + } } } }, @@ -8346,6 +55386,102 @@ "state": "translated", "value": "ウインドウを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "윈도우 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fenster schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer la fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق النافذة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Pencereyi Kapat" + } } } }, @@ -8363,6 +55499,102 @@ "state": "translated", "value": "ワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Kapat" + } } } }, @@ -8380,6 +55612,102 @@ "state": "translated", "value": "フォーカスペインを強調" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "闪烁聚焦面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "閃爍聚焦面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "포커스된 패널 깜빡이기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fokussierten Bereich hervorheben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Resaltar panel enfocado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Flasher le panneau actif" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Evidenzia pannello attivo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fremhæv fokuseret panel" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podświetl aktywny panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подсветить активную панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi fokusirani panel" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض اللوحة المركّزة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Blink fokusert panel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Piscar Painel em Foco" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กะพริบแผงที่โฟกัส" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Odaklanılan Paneli Yanıp Söndür" + } } } }, @@ -8397,6 +55725,102 @@ "state": "translated", "value": "下のペインにフォーカス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "聚焦下方面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "聚焦下方面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래쪽 패널로 포커스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bereich unten fokussieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enfocar panel inferior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer le panneau du bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta focus pannello in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Fokus na panel poniżej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фокус на панель снизу" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Fokusiraj panel dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التركيز على اللوحة أسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Focar Painel Abaixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฟกัสบานหน้าต่างด้านล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağıdaki Bölmeye Odaklan" + } } } }, @@ -8414,6 +55838,102 @@ "state": "translated", "value": "左のペインにフォーカス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "聚焦左侧面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "聚焦左側面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "왼쪽 패널로 포커스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bereich links fokussieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enfocar panel izquierdo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer le panneau de gauche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta focus pannello a sinistra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel til venstre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Fokus na panel po lewej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фокус на панель слева" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Fokusiraj panel lijevo" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التركيز على اللوحة يسار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel venstre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Focar Painel à Esquerda" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฟกัสบานหน้าต่างด้านซ้าย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Soldaki Bölmeye Odaklan" + } } } }, @@ -8431,6 +55951,102 @@ "state": "translated", "value": "右のペインにフォーカス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "聚焦右侧面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "聚焦右側面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽 패널로 포커스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bereich rechts fokussieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enfocar panel derecho" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer le panneau de droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta focus pannello a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Fokus na panel po prawej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фокус на панель справа" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Fokusiraj panel desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التركيز على اللوحة يمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Focar Painel à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฟกัสบานหน้าต่างด้านขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sağdaki Bölmeye Odaklan" + } } } }, @@ -8448,6 +56064,102 @@ "state": "translated", "value": "上のペインにフォーカス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "聚焦上方面板" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "聚焦上方面板" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "위쪽 패널로 포커스" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bereich oben fokussieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Enfocar panel superior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer le panneau du haut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta focus pannello in alto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel opad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Fokus na panel powyżej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фокус на панель сверху" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Fokusiraj panel gore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التركيز على اللوحة أعلى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fokuser panel opp" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Focar Painel Acima" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โฟกัสบานหน้าต่างด้านบน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yukarıdaki Bölmeye Odaklan" + } } } }, @@ -8465,6 +56177,102 @@ "state": "translated", "value": "最新の未読にジャンプ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳转到最新未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跳至最新未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 읽지 않은 항목으로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur letzten ungelesenen springen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir a la última no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller au dernier message non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'ultimo non letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til seneste ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do najnowszego nieprzeczytanego" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к последнему непрочитанному" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Skoči na najnovije nepročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى أحدث غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til siste uleste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Última Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้ามไปยังรายการยังไม่อ่านล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son Okunmamışa Atla" + } } } }, @@ -8482,6 +56290,102 @@ "state": "translated", "value": "新規サーフェス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建 Surface" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增 Surface" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 화면" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neue Oberfläche" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva superficie" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvelle surface" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova superficie" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ny overflade" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa powierzchnia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новая поверхность" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nova površina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سطح جديد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ny flate" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Superfície" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นผิวใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Yüzey" + } } } }, @@ -8499,6 +56403,102 @@ "state": "translated", "value": "新規ウインドウ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建窗口" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增視窗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 윈도우" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neues Fenster" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nueva ventana" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvelle fenêtre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova finestra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt vindue" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowe okno" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое окно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi prozor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نافذة جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt vindu" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Janela" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หน้าต่างใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Pencere" + } } } }, @@ -8516,6 +56516,102 @@ "state": "translated", "value": "新規ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Çalışma Alanı" + } } } }, @@ -8533,6 +56629,102 @@ "state": "translated", "value": "次のサーフェス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个 Surface" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一個 Surface" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 화면" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächste Oberfläche" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente superficie" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Surface suivante" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Superficie successiva" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste overflade" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następna powierzchnia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующая поверхность" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeća površina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السطح التالي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste flate" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próxima Superfície" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นผิวถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonraki Yüzey" + } } } }, @@ -8550,6 +56742,102 @@ "state": "translated", "value": "次のワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下一个工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下一個工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다음 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Siguiente espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail suivant" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro successiva" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Næste arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Następna przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следующее рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sljedeći radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل التالية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Neste arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Próxima Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซถัดไป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sonraki Çalışma Alanı" + } } } }, @@ -8567,6 +56855,102 @@ "state": "translated", "value": "ブラウザを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz przeglądarkę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть браузер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori preglednik" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح المتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Aç" + } } } }, @@ -8584,6 +56968,102 @@ "state": "translated", "value": "フォルダを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开文件夹" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟資料夾" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "폴더 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ordner öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir carpeta" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir un dossier" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri cartella" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn mappe" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz folder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть папку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori folder" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح مجلد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne mappe" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir Pasta" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดโฟลเดอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Klasör Aç" + } } } }, @@ -8601,6 +57081,102 @@ "state": "translated", "value": "ショートカットを入力…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请按快捷键..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請按快速鍵..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "단축키를 누르세요…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tastenkürzel drücken …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pulsa un atajo…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Appuyez sur un raccourci..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Premi la scorciatoia…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tryk genvej…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Naciśnij skrót…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите сочетание клавиш..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pritisnite prečicu…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اضغط الاختصار…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Trykk snarvei …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Pressione o atalho…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กดแป้นพิมพ์ลัด..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kısayola basın…" + } } } }, @@ -8618,6 +57194,102 @@ "state": "translated", "value": "前のサーフェス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个 Surface" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一個 Surface" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 화면" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorherige Oberfläche" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Superficie anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Surface précédente" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Superficie precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige overflade" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednia powierzchnia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущая поверхность" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodna površina" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السطح السابق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige flate" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Superfície Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นผิวก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önceki Yüzey" + } } } }, @@ -8635,6 +57307,102 @@ "state": "translated", "value": "前のワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上一个工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上一個工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이전 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorheriger Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo anterior" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail précédent" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro precedente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forrige arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Poprzednia przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Предыдущее рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prethodni radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل السابقة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forrige arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho Anterior" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซก่อนหน้า" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Önceki Çalışma Alanı" + } } } }, @@ -8652,6 +57420,102 @@ "state": "translated", "value": "タブ名を変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tab umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar pestaña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'onglet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina scheda" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę karty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать вкладку" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj tab" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية اللسان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi fanen nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Aba" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อแท็บ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sekmeyi Yeniden Adlandır" + } } } }, @@ -8669,6 +57533,102 @@ "state": "translated", "value": "ワークスペース名を変更" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重命名工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新命名工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 이름 변경" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich umbenennen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Renombrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Renommer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rinomina area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Omdøb arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zmień nazwę przestrzeni roboczej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переименовать рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preimenuj radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تسمية مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gi arbeidsområdet nytt navn" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Renomear Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปลี่ยนชื่อเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Yeniden Adlandır" + } } } }, @@ -8686,6 +57646,102 @@ "state": "translated", "value": "ブラウザ JavaScript コンソールを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示浏览器 JavaScript 控制台" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示瀏覽器 JavaScript 主控台" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 JavaScript 콘솔 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-JavaScript-Konsole anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar consola de JavaScript del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher la console JavaScript du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra console JavaScript del browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis browser JavaScript-konsol" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż konsolę JavaScript przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать консоль JavaScript в браузере" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži JavaScript konzolu preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض وحدة تحكم JavaScript للمتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis JavaScript-konsoll i nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Console JavaScript do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงคอนโซล JavaScript ของเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı JavaScript Konsolunu Göster" + } } } }, @@ -8703,6 +57759,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri Göster" + } } } }, @@ -8720,6 +57872,102 @@ "state": "translated", "value": "ブラウザを下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Aşağı Böl" + } } } }, @@ -8737,6 +57985,102 @@ "state": "translated", "value": "ブラウザを右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저를 오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir navegador a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser le navigateur à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi browser a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel browser til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel przeglądarkę w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить браузер вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli preglednik desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم المتصفح لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del nettleser til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir Navegador à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกเบราว์เซอร์ไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcıyı Sağa Böl" + } } } }, @@ -8754,6 +58098,102 @@ "state": "translated", "value": "下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağı Böl" + } } } }, @@ -8771,6 +58211,102 @@ "state": "translated", "value": "右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sağa Böl" + } } } }, @@ -8788,6 +58324,102 @@ "state": "translated", "value": "ブラウザデベロッパツールを切替" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换浏览器开发者工具" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換瀏覽器開發者工具" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 개발자 도구 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Entwicklerwerkzeuge ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar herramientas de desarrollo del navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer les outils de développement du navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva Strumenti sviluppatore browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå browserudviklerværktøjer til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz narzędzia deweloperskie przeglądarki" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Инструменты разработчика браузера" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci razvojne alate preglednika" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل أدوات المطور للمتصفح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå utviklerverktøy i nettleser av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Ferramentas do Desenvolvedor do Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับเครื่องมือนักพัฒนาเบราว์เซอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tarayıcı Geliştirici Araçlarını Aç/Kapat" + } } } }, @@ -8805,6 +58437,102 @@ "state": "translated", "value": "ペインズームを切替" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换面板缩放" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換面板縮放" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패널 확대/축소 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bereichszoom umschalten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar zoom del panel" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer/désactiver le zoom du panneau" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva zoom pannello" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå panelzoom til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz powiększenie panelu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Масштаб панели" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci uvećanje panela" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل تكبير اللوحة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå panelzoom av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Zoom do Painel" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับการซูมบานหน้าต่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bölme Yakınlaştırmasını Aç/Kapat" + } } } }, @@ -8822,6 +58550,102 @@ "state": "translated", "value": "サイドバーを切替" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå sidebjælke til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Боковая панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci bočnu traku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå sidepanelet av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunu Aç/Kapat" + } } } }, @@ -8839,6 +58663,102 @@ "state": "translated", "value": "ターミナルコピーモードを切替" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换终端复制模式" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換終端機複製模式" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 복사 모드 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal-Kopiermodus umschalten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar modo de copia del terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer/désactiver le mode copie du terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva modalità copia terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå terminalkopiertilstand til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz tryb kopiowania terminala" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Режим копирования терминала" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci režim kopiranja terminala" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل وضع النسخ في الطرفية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå terminalkopieringsmodus av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Modo de Cópia do Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับโหมดคัดลอกเทอร์มินัล" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal Kopyalama Modunu Aç/Kapat" + } } } }, @@ -8856,6 +58776,102 @@ "state": "translated", "value": "ワークスペースを閉じる" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 닫기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich schließen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cerrar espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fermer l'espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiudi area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Luk arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zamknij przestrzeń roboczą" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zatvori radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إغلاق مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lukk arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Fechar Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดเวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanını Kapat" + } } } }, @@ -8873,6 +58889,102 @@ "state": "translated", "value": "ドラッグしてFinderまたは他のアプリで開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "拖拽以在访达或其他应用中打开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "拖曳以在 Finder 或其他 App 中開啟" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Finder 또는 다른 앱으로 드래그하여 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ziehen, um im Finder oder einer anderen App zu öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Arrastra para abrir en Finder u otra app" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Glissez pour ouvrir dans le Finder ou une autre app" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Trascina per aprire nel Finder o in un'altra app" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Træk for at åbne i Finder eller et andet program" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przeciągnij, aby otworzyć w Finderze lub innej aplikacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перетащите, чтобы открыть в Finder или другом приложении" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prevucite za otvaranje u Finderu ili drugoj aplikaciji" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اسحب للفتح في Finder أو تطبيق آخر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Dra for å åpne i Finder eller en annen app" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Arraste para abrir no Finder ou em outro app" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ลากเพื่อเปิดใน Finder หรือแอปอื่น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Finder'da veya başka bir uygulamada açmak için sürükleyin" + } } } }, @@ -8890,6 +59002,102 @@ "state": "translated", "value": "左レール" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "左侧导轨" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "左側軌道" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "왼쪽 레일" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Linke Leiste" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Barra izquierda" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Rail gauche" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Barra sinistra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Venstre skinne" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Lewa listwa" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Левая полоса" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lijeva traka" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "شريط أيسر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Venstre skinne" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Trilho Esquerdo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แถบด้านซ้าย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sol Şerit" + } } } }, @@ -8907,6 +59115,102 @@ "state": "translated", "value": "ソリッドフィル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "纯色填充" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "實心填充" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "단색 채우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Farbfüllung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Relleno sólido" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Remplissage uni" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riempimento solido" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Solid udfyldning" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Jednolite wypełnienie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сплошная заливка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Puna ispuna" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعبئة صلبة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Heldekkende fyll" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Preenchimento Sólido" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "พื้นทึบ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Düz Dolgu" + } } } }, @@ -8924,6 +59228,102 @@ "state": "translated", "value": "表示を減らす" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "收起" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示較少" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "간략히 보기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Weniger anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar menos" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher moins" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra meno" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis mindre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż mniej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать меньше" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži manje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض أقل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis mindre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar menos" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงน้อยลง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha az göster" + } } } }, @@ -8941,6 +59341,102 @@ "state": "translated", "value": "詳細を折りたたむ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "收起详细信息" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示較少詳細資訊" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세부 정보 접기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Weniger Details anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar menos detalles" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher moins de détails" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra meno dettagli" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis færre detaljer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż mniej szczegółów" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать меньше подробностей" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži manje detalja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض تفاصيل أقل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis færre detaljer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar menos detalhes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงรายละเอียดน้อยลง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha az ayrıntı göster" + } } } }, @@ -8958,6 +59454,102 @@ "state": "translated", "value": "さらに表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "展开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示更多" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "더 보기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Mehr anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar más" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher plus" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra di più" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis mere" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż więcej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать больше" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži više" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض المزيد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis mer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar mais" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงเพิ่มเติม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha fazla göster" + } } } }, @@ -8975,6 +59567,102 @@ "state": "translated", "value": "詳細を展開" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "展开详细信息" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示更多詳細資訊" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세부 정보 펼치기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Mehr Details anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar más detalles" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher plus de détails" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra più dettagli" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis flere detaljer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż więcej szczegółów" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать больше подробностей" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži više detalja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض تفاصيل أكثر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis flere detaljer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar mais detalhes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงรายละเอียดเพิ่มเติม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Daha fazla ayrıntı göster" + } } } }, @@ -8992,6 +59680,102 @@ "state": "translated", "value": "Macintosh HD" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } } } }, @@ -9009,6 +59793,102 @@ "state": "translated", "value": "%1$@ #%2$lldを開く" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开 %1$@ #%2$lld" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟 %1$@ #%2$lld" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%1$@ #%2$lld 열기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%1$@ #%2$lld öffnen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir %1$@ #%2$lld" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir %1$@ #%2$lld" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Apri %1$@ #%2$lld" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Åbn %1$@ #%2$lld" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Otwórz %1$@ #%2$lld" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть %1$@ #%2$lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Otvori %1$@ #%2$lld" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح %1$@ #%2$lld" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Åpne %1$@ #%2$lld" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Abrir %1$@ #%2$lld" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิด %1$@ #%2$lld" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%1$@ #%2$lld Aç" + } } } }, @@ -9026,6 +59906,102 @@ "state": "translated", "value": "クローズ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已关闭" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "已關閉" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "닫힘" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "geschlossen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cerrado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "fermée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "chiusa" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "lukket" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "zamknięty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закрыт" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "zatvoren" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مغلق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "lukket" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "fechado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "kapalı" + } } } }, @@ -9043,6 +60019,102 @@ "state": "translated", "value": "マージ済み" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "已合并" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "已合併" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "병합됨" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "zusammengeführt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "fusionado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "fusionnée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "unita" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "sammenlagt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "scalony" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "объединен" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "spojen" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مدمج" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "sammenslått" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "mesclado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ผสานแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "birleştirildi" + } } } }, @@ -9060,6 +60132,102 @@ "state": "translated", "value": "オープン" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "열림" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "offen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "abierto" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "ouverte" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "aperta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "åben" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "otwarty" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "открыт" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "otvoren" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مفتوح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "åpen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "aberto" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "açık" + } } } }, @@ -9077,6 +60245,102 @@ "state": "translated", "value": "アクティブにしてこのワークスペースにフォーカスします。ドラッグで並べ替え、または「上に移動」「下に移動」アクションを使用します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "激活以聚焦此工作区。拖拽以重新排序,或使用上移和下移操作。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "啟用以聚焦此工作區。拖曳以重新排序,或使用上移和下移動作。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 작업 공간에 포커스하려면 활성화하세요. 드래그하여 순서를 변경하거나 위로 이동 및 아래로 이동 동작을 사용하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aktivieren, um diesen Arbeitsbereich zu fokussieren. Ziehen zum Umordnen oder verwenden Sie die Aktionen 'Nach oben' und 'Nach unten'." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Activa para enfocar este espacio de trabajo. Arrastra para reordenar, o usa las acciones Mover hacia arriba y Mover hacia abajo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activez pour accéder à cet espace de travail. Glissez pour réordonner, ou utilisez les actions Déplacer vers le haut et Déplacer vers le bas." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva per portare il focus su questa area di lavoro. Trascina per riordinare oppure usa le azioni Sposta in alto e Sposta in basso." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiver for at fokusere dette arbejdsområde. Træk for at omarrangere, eller brug handlingerne Flyt op og Flyt ned." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Aktywuj, aby ustawić fokus na tej przestrzeni roboczej. Przeciągnij, aby zmienić kolejność, lub użyj akcji Przenieś w górę i Przenieś w dół." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активируйте для перехода к этому рабочему пространству. Перетащите для изменения порядка или используйте действия «Переместить вверх» и «Переместить вниз»." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Aktivirajte za fokusiranje ovog radnog prostora. Prevucite za preraspoređivanje ili koristite akcije Pomjeri gore i Pomjeri dolje." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فعّل للتركيز على مساحة العمل هذه. اسحب لإعادة الترتيب، أو استخدم إجراءات نقل للأعلى ونقل للأسفل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktiver for å fokusere dette arbeidsområdet. Dra for å omorganisere, eller bruk Flytt opp og Flytt ned." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ative para focar nesta área de trabalho. Arraste para reordenar ou use as ações Mover para Cima e Mover para Baixo." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดใช้งานเพื่อโฟกัสเวิร์กสเปซนี้ ลากเพื่อจัดเรียงใหม่ หรือใช้การกระทำเลื่อนขึ้นและเลื่อนลง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu çalışma alanına odaklanmak için etkinleştirin. Yeniden sıralamak için sürükleyin veya Yukarı Taşı ve Aşağı Taşı eylemlerini kullanın." + } } } }, @@ -9094,6 +60358,102 @@ "state": "translated", "value": "下に移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下移" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下移" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach unten bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt ned" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pomjeri dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลื่อนลง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağı Taşı" + } } } }, @@ -9111,6 +60471,102 @@ "state": "translated", "value": "上に移動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "上移" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "上移" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "위로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach oben bewegen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mover hacia arriba" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacer vers le haut" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta in alto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt op" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś w górę" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместить вверх" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pomjeri gore" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "نقل للأعلى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt opp" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mover para Cima" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เลื่อนขึ้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yukarı Taşı" + } } } }, @@ -9128,6 +60584,102 @@ "state": "translated", "value": "認証なしで任意のローカルプロセスおよびユーザーの接続を許可します。安全ではありません。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许任何本地进程和用户无需认证即可连接。不安全。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "允許任何本機程序和使用者連線,無需驗證。不安全。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "인증 없이 모든 로컬 프로세스 및 사용자의 연결을 허용합니다. 안전하지 않습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Jeden lokalen Prozess und Benutzer ohne Authentifizierung verbinden lassen. Unsicher." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Permitir que cualquier proceso y usuario local se conecte sin autenticación. Inseguro." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Autoriser tout processus et utilisateur local à se connecter sans authentification. Non sécurisé." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Consenti a qualsiasi processo e utente locale di connettersi senza autenticazione. Non sicuro." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tillad enhver lokal proces og bruger at forbinde uden autentifikation. Usikkert." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zezwól dowolnemu lokalnemu procesowi i użytkownikowi na połączenie bez uwierzytelniania. Niebezpieczne." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разрешить подключение любому локальному процессу и пользователю без авторизации. Небезопасно." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dozvolite bilo kojem lokalnom procesu i korisniku da se poveže bez autentikacije. Nesigurno." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السماح لأي عملية ومستخدم محلي بالاتصال بدون مصادقة. غير آمن." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tillat enhver lokal prosess og bruker å koble til uten autentisering. Usikkert." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Permitir que qualquer processo e usuário local se conecte sem autenticação. Inseguro." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อนุญาตให้กระบวนการในเครื่องและผู้ใช้ทุกคนเชื่อมต่อโดยไม่ต้องยืนยันตัวตน ไม่ปลอดภัย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Herhangi bir yerel işlem ve kullanıcının kimlik doğrulama olmadan bağlanmasına izin ver. Güvensiz." + } } } }, @@ -9145,6 +60697,102 @@ "state": "translated", "value": "完全オープンアクセス" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "完全开放访问" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "完全開放存取" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "전체 개방 접근" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollständiger offener Zugriff" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Acceso abierto completo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Accès ouvert complet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Accesso aperto completo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fuld åben adgang" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pełny otwarty dostęp" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Полный открытый доступ" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Potpuni otvoreni pristup" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وصول مفتوح كامل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Full åpen tilgang" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Acesso aberto total" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเข้าถึงเปิดแบบเต็ม" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tam açık erişim" + } } } }, @@ -9162,6 +60810,102 @@ "state": "translated", "value": "この macOS ユーザーからの外部ローカル自動化クライアントを許可します(プロセス系譜チェックなし)。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许当前 macOS 用户的外部本地自动化客户端连接(不检查进程来源)。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "允許此 macOS 使用者的外部本機自動化用戶端(不檢查來源)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 macOS 사용자의 외부 로컬 자동화 클라이언트를 허용합니다 (출처 확인 없음)." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Externe lokale Automatisierungs-Clients dieses macOS-Benutzers zulassen (keine Abstammungsprüfung)." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Permitir clientes de automatización locales externos de este usuario de macOS (sin verificación de ascendencia)." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Autoriser les clients d'automatisation locaux externes de cet utilisateur macOS (sans vérification de parenté)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Consenti ai client di automazione locale esterni di questo utente macOS (senza controllo di discendenza)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tillad eksterne lokale automatiseringsklienter fra denne macOS-bruger (ingen herkomstkontrol)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zezwól zewnętrznym lokalnym klientom automatyzacji od tego użytkownika macOS (bez sprawdzania pochodzenia)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разрешить внешним клиентам автоматизации от текущего пользователя macOS (без проверки происхождения)." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dozvolite vanjskim lokalnim klijentima automatizacije od ovog macOS korisnika (bez provjere porijekla)." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "السماح لعملاء الأتمتة المحليين الخارجيين من مستخدم macOS هذا (بدون فحص النسب)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tillat eksterne lokale automatiseringsklienter fra denne macOS-brukeren (ingen opphavskontroll)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Permitir clientes de automação locais externos deste usuário macOS (sem verificação de ancestralidade)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อนุญาตไคลเอ็นต์ระบบอัตโนมัติภายนอกจากผู้ใช้ macOS นี้ (ไม่ตรวจสอบสายสืบทอด)" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bu macOS kullanıcısından gelen harici yerel otomasyon istemcilerine izin ver (soy denetimi yok)." + } } } }, @@ -9179,6 +60923,102 @@ "state": "translated", "value": "自動化モード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "自动化模式" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "自動化模式" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동화 모드" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatisierungsmodus" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Modo de automatización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mode automatisation" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Modalità automazione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Automatiseringstilstand" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tryb automatyzacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Режим автоматизации" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Režim automatizacije" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وضع الأتمتة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Automatiseringsmodus" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Modo de automação" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหมดระบบอัตโนมัติ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomasyon modu" + } } } }, @@ -9196,6 +61036,102 @@ "state": "translated", "value": "cmux ターミナル内で起動したプロセスのみがコマンドを送信できます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "仅允许在 cmux 终端内启动的进程发送命令。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "僅在 cmux 終端機內啟動的程序可以傳送指令。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 터미널 내에서 시작된 프로세스만 명령을 보낼 수 있습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Prozesse, die in cmux-Terminals gestartet wurden, können Befehle senden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Solo los procesos iniciados dentro de terminales de cmux pueden enviar comandos." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Seuls les processus lancés dans les terminaux cmux peuvent envoyer des commandes." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Solo i processi avviati nei terminali cmux possono inviare comandi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kun processer startet i cmux-terminaler kan sende kommandoer." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tylko procesy uruchomione wewnątrz terminali cmux mogą wysyłać polecenia." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Только процессы, запущенные в терминалах cmux, могут отправлять команды." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Samo procesi pokrenuti unutar cmux terminala mogu slati naredbe." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فقط العمليات التي بدأت داخل طرفيات cmux يمكنها إرسال الأوامر." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Bare prosesser startet innenfor cmux-terminaler kan sende kommandoer." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Apenas processos iniciados dentro de terminais do cmux podem enviar comandos." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เฉพาะกระบวนการที่เริ่มต้นภายในเทอร์มินัล cmux เท่านั้นที่สามารถส่งคำสั่งได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yalnızca cmux terminalleri içinden başlatılan işlemler komut gönderebilir." + } } } }, @@ -9213,6 +61149,102 @@ "state": "translated", "value": "cmux プロセスのみ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "仅限 cmux 进程" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "僅限 cmux 程序" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 프로세스만" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur cmux-Prozesse" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Solo procesos de cmux" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Processus cmux uniquement" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Solo processi cmux" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kun cmux-processer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tylko procesy cmux" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Только процессы cmux" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Samo cmux procesi" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عمليات cmux فقط" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kun cmux-prosesser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Apenas processos do cmux" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กระบวนการ cmux เท่านั้น" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yalnızca cmux işlemleri" + } } } }, @@ -9230,6 +61262,102 @@ "state": "translated", "value": "ソケットパスワードファイルのパスを解決できません。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法解析套接字密码文件路径。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法解析 Socket 密碼檔案路徑。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "소켓 비밀번호 파일 경로를 확인할 수 없습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Pfad zur Socket-Passwortdatei konnte nicht aufgelöst werden." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo resolver la ruta del archivo de contraseña del socket." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible de résoudre le chemin du fichier de mot de passe du socket." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile risolvere il percorso del file password del socket." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke bestemme stien til socket-adgangskodefilen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie można rozwiązać ścieżki pliku hasła gniazda." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось определить путь к файлу пароля сокета." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće razriješiti putanju datoteke lozinke utičnice." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر تحديد مسار ملف كلمة مرور المقبس." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kan ikke finne filstien for socket-passord." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não foi possível resolver o caminho do arquivo de senha do socket." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถระบุเส้นทางไฟล์รหัสผ่านซ็อกเก็ตได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Soket parola dosyası yolu çözümlenemedi." + } } } }, @@ -9247,6 +61375,102 @@ "state": "translated", "value": "ローカルコントロールソケットを無効にします。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "禁用本地控制套接字。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "停用本機控制 Socket。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "로컬 제어 소켓을 비활성화합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Den lokalen Steuerungs-Socket deaktivieren." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Desactivar el socket de control local." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Désactiver le socket de contrôle local." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Disabilita il socket di controllo locale." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Deaktiver den lokale kontrolsocket." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyłącz lokalne gniazdo sterujące." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключить локальный управляющий сокет." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Onemogući lokalnu kontrolnu utičnicu." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعطيل مقبس التحكم المحلي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Deaktiver den lokale kontrollsocketen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desativar o socket de controle local." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิดใช้งานซ็อกเก็ตควบคุมในเครื่อง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yerel kontrol soketini devre dışı bırak." + } } } }, @@ -9264,6 +61488,102 @@ "state": "translated", "value": "オフ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "끔" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Desactivado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Désactivé" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Disattivato" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyłączone" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выключено" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Isključeno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إيقاف" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Av" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Desativado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปิด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kapalı" + } } } }, @@ -9281,6 +61601,102 @@ "state": "translated", "value": "ローカルファイルに保存されたパスワードでソケット認証を要求します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用存储在本地文件中的密码进行套接字认证。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用儲存在本機檔案中的密碼進行 Socket 驗證。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "로컬 파일에 저장된 비밀번호로 소켓 인증을 요구합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Socket-Authentifizierung mit einem in einer lokalen Datei gespeicherten Passwort erfordern." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Requerir autenticación del socket con una contraseña almacenada en un archivo local." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Exiger l'authentification du socket avec un mot de passe stocké dans un fichier local." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Richiedi l'autenticazione del socket con una password memorizzata in un file locale." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kræv socket-autentifikation med en adgangskode gemt i en lokal fil." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wymagaj uwierzytelniania gniazda hasłem przechowywanym w pliku lokalnym." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Требовать аутентификацию сокета с паролем, хранящимся в локальном файле." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Zahtijevaj autentikaciju utičnice lozinkom pohranjenom u lokalnoj datoteci." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "طلب مصادقة المقبس بكلمة مرور مخزنة في ملف محلي." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Krev socket-autentisering med et passord lagret i en lokal fil." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Exigir autenticação do socket com uma senha armazenada em um arquivo local." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ต้องมีการยืนยันตัวตนซ็อกเก็ตด้วยรหัสผ่านที่จัดเก็บในไฟล์ในเครื่อง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yerel dosyada saklanan bir parola ile soket kimlik doğrulaması gerektir." + } } } }, @@ -9298,6 +61714,102 @@ "state": "translated", "value": "パスワードモード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "密码模式" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "密碼模式" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호 모드" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwortmodus" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Modo de contraseña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mode mot de passe" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Modalità password" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Adgangskodetilstand" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Tryb hasła" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Режим пароля" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Režim lozinke" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وضع كلمة المرور" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Passordmodus" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Modo de senha" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "โหมดรหัสผ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Parola modu" + } } } }, @@ -9315,6 +61827,102 @@ "state": "translated", "value": "すべてクリア" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全部清除" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "清除全部" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모두 지우기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle löschen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Borrar todo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Tout effacer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cancella tutto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ryd alle" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wyczyść wszystko" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить все" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obriši sve" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مسح الكل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Fjern alle" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Limpar Tudo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ล้างทั้งหมด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tümünü Temizle" + } } } }, @@ -9332,6 +61940,102 @@ "state": "translated", "value": "最新の未読にジャンプ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "跳转到最新未读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "跳至最新未讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 읽지 않은 항목으로 이동" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur letzten ungelesenen springen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ir a la última no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aller au dernier message non lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Vai all'ultimo non letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Gå til seneste ulæste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przejdź do najnowszego nieprzeczytanego" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перейти к последнему непрочитанному" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Skoči na najnovije nepročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الانتقال إلى أحدث غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Gå til siste uleste" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ir para Última Não Lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้ามไปยังรายการยังไม่อ่านล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Son Okunmamışa Atla" + } } } }, @@ -9349,6 +62053,102 @@ "state": "translated", "value": "すべて既読にする" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "全部标记为已读" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "全部標為已讀" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "모두 읽음으로 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle als gelesen markieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Marcar todo como leído" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Tout marquer comme lu" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Segna tutto come letto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Marker alle som læste" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Oznacz wszystko jako przeczytane" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отметить все как прочитанные" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Označi sve kao pročitano" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعليم الكل كمقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Merk alle som lest" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Marcar Tudo como Lido" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ทำเครื่องหมายว่าอ่านทั้งหมดแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Tümünü Okundu İşaretle" + } } } }, @@ -9366,6 +62166,102 @@ "state": "translated", "value": "未読の通知はありません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "没有未读通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有未讀通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "읽지 않은 알림 없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine ungelesenen Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sin notificaciones no leídas" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune notification non lue" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna notifica non letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen ulæste notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak nieprzeczytanych powiadomień" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет непрочитанных уведомлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nema nepročitanih obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا توجد إشعارات غير مقروءة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen uleste varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma notificação não lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มีการแจ้งเตือนที่ยังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Okunmamış bildirim yok" + } } } }, @@ -9383,6 +62279,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri Göster" + } } } }, @@ -9400,6 +62392,102 @@ "state": "translated", "value": "未読通知 1件" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "1 条未读通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "1 則未讀通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "읽지 않은 알림 1개" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "1 ungelesene Benachrichtigung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "1 notificación no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "1 notification non lue" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "1 notifica non letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "1 ulæst notifikation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "1 nieprzeczytane powiadomienie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "1 непрочитанное уведомление" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "1 nepročitano obavještenje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إشعار واحد غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "1 ulest varsel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "1 notificação não lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "1 การแจ้งเตือนที่ยังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "1 okunmamış bildirim" + } } } }, @@ -9417,6 +62505,102 @@ "state": "translated", "value": "未読通知 %lld件" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%lld 条未读通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%lld 則未讀通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "읽지 않은 알림 %lld개" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld ungelesene Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%lld notificaciones no leídas" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%lld notifications non lues" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%lld notifiche non lette" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%lld ulæste notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%lld nieprzeczytanych powiadomień" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Непрочитанных уведомлений: %lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%lld nepročitanih obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "%lld إشعار غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%lld uleste varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%lld notificações não lidas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%lld การแจ้งเตือนที่ยังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%lld okunmamış bildirim" + } } } }, @@ -9434,6 +62618,102 @@ "state": "translated", "value": "未読通知 1件" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "1 条未读通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "1 則未讀通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "읽지 않은 알림 1개" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "1 ungelesene Benachrichtigung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "1 notificación no leída" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "1 notification non lue" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "1 notifica non letta" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "1 ulæst notifikation" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "1 nieprzeczytane powiadomienie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "1 непрочитанное уведомление" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "1 nepročitano obavještenje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إشعار واحد غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "1 ulest varsel" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "1 notificação não lida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "1 การแจ้งเตือนที่ยังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "1 okunmamış bildirim" + } } } }, @@ -9451,6 +62731,102 @@ "state": "translated", "value": "未読通知 %lld件" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "%lld 条未读通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "%lld 則未讀通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "읽지 않은 알림 %lld개" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld ungelesene Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "%lld notificaciones no leídas" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "%lld notifications non lues" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%lld notifiche non lette" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%lld ulæste notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%lld nieprzeczytanych powiadomień" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Непрочитанных уведомлений: %lld" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%lld nepročitanih obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "%lld إشعار غير مقروء" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%lld uleste varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%lld notificações não lidas" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%lld การแจ้งเตือนที่ยังไม่อ่าน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%lld okunmamış bildirim" + } } } }, @@ -9468,6 +62844,102 @@ "state": "translated", "value": "名称未設定タブ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未命名标签页" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "未命名標籤頁" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "제목 없는 탭" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Unbenannter Tab" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pestaña sin título" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Onglet sans titre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scheda senza titolo" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Unavngivet fane" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Karta bez nazwy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Безымянная вкладка" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tab bez naziva" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لسان بلا عنوان" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Uten navn-fane" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aba Sem Título" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แท็บไม่มีชื่อ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Adsız Sekme" + } } } }, @@ -9485,6 +62957,102 @@ "state": "translated", "value": "ダーク" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "深色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "深色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다크" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dunkel" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Oscuro" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Sombre" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scuro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Mørk" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Ciemny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Темная" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Tamna" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "داكن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Mørk" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Escuro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มืด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Koyu" + } } } }, @@ -9502,6 +63070,102 @@ "state": "translated", "value": "ライト" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浅色" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "淺色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "라이트" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hell" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Claro" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Clair" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Chiaro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Lys" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Jasny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлая" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Svijetla" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فاتح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lys" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Claro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สว่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Açık" + } } } }, @@ -9519,6 +63183,102 @@ "state": "translated", "value": "システム" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "系统" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "系統" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "시스템" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Système" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Systemowy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sistemski" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النظام" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sistema" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ระบบ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sistem" + } } } }, @@ -9536,6 +63296,102 @@ "state": "translated", "value": "新規ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Çalışma Alanı" + } } } }, @@ -9553,6 +63409,102 @@ "state": "translated", "value": "新規ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouvel espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuova area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nyt arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новое рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة عمل جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nytt arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nova área de trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni çalışma alanı" + } } } }, @@ -9570,6 +63522,102 @@ "state": "translated", "value": "通知" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimler" + } } } }, @@ -9587,6 +63635,102 @@ "state": "translated", "value": "通知を表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示通知" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示通知" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알림 표시" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benachrichtigungen anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher les notifications" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra notifiche" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis notifikationer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż powiadomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уведомления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži obavještenja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض الإشعارات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis varsler" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar notificações" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงการแจ้งเตือน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bildirimleri göster" + } } } }, @@ -9604,6 +63748,102 @@ "state": "translated", "value": "サイドバーの切り替え" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "切换侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "切換側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 전환" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste ein-/ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Alternar barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher/masquer la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attiva/Disattiva barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Slå sidebjælke til/fra" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przełącz pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Боковая панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prebaci bočnu traku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبديل الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slå sidepanelet av/på" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Alternar Barra Lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "สลับแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar Çubuğunu Aç/Kapat" + } } } }, @@ -9621,6 +63861,102 @@ "state": "translated", "value": "サイドバーの表示/非表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示或隐藏侧边栏" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示或隱藏側邊欄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바 표시 또는 숨기기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste ein- oder ausblenden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar u ocultar la barra lateral" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher ou masquer la barre latérale" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Mostra o nascondi la barra laterale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis eller skjul sidebjælken" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż lub ukryj pasek boczny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать или скрыть боковую панель" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Prikaži ili sakrij bočnu traku" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إظهار أو إخفاء الشريط الجانبي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis eller skjul sidepanelet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mostrar ou ocultar a barra lateral" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แสดงหรือซ่อนแถบด้านข้าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Kenar çubuğunu göster veya gizle" + } } } }, @@ -9638,6 +63974,102 @@ "state": "translated", "value": "アップデートがあります" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "有可用更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "有可用的更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 사용 가능" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update verfügbar" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Actualización disponible" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mise à jour disponible" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Aggiornamento disponibile" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdatering tilgængelig" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dostępna aktualizacja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ažuriranje dostupno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحديث متوفر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdatering tilgjengelig" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Atualização Disponível" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มีอัปเดตใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Mevcut" + } } } }, @@ -9655,6 +64087,102 @@ "state": "translated", "value": "アップデートあり: %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "有可用更新:%@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "有可用的更新:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 사용 가능: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update verfügbar: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Actualización disponible: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mise à jour disponible : %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Aggiornamento disponibile: %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdatering tilgængelig: %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dostępna aktualizacja: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление: %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ažuriranje dostupno: %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحديث متوفر: %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdatering tilgjengelig: %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Atualização Disponível: %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มีอัปเดตใหม่: %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Mevcut: %@" + } } } }, @@ -9672,6 +64200,102 @@ "state": "translated", "value": "アップデートを確認中…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在检查更新..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在檢查更新..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 확인 중…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suche nach Updates …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscando actualizaciones…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recherche de mises à jour..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verifica aggiornamenti in corso…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søger efter opdateringer…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sprawdzanie aktualizacji…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка обновлений..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Provjeravanje ažuriranja…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التحقق من التحديثات…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ser etter oppdateringer …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Verificando Atualizações…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังตรวจหาอัปเดต..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeler denetleniyor…" + } } } }, @@ -9689,6 +64313,102 @@ "state": "translated", "value": "自動アップデートの設定" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "配置自动更新偏好设置" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "設定自動更新偏好" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동 업데이트 환경설정 구성" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen für automatische Updates konfigurieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Configurar preferencias de actualización automática" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Configurer les préférences de mise à jour automatique" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Configura le preferenze di aggiornamento automatico" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Konfigurer præferencer for automatiske opdateringer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Skonfiguruj preferencje automatycznych aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настроить параметры автоматических обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Konfigurišite postavke automatskog ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تكوين تفضيلات التحديث التلقائي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Konfigurer innstillinger for automatiske oppdateringer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Configurar preferências de atualização automática" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำหนดค่าการอัปเดตอัตโนมัติ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomatik güncelleme tercihlerini yapılandır" + } } } }, @@ -9706,6 +64426,102 @@ "state": "translated", "value": "最新バージョンをダウンロードしてインストール" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "下载并安装最新版本" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下載並安裝最新版本" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 버전 다운로드 및 설치" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neueste Version herunterladen und installieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descargar e instalar la última versión" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Télécharger et installer la dernière version" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Scarica e installa l'ultima versione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Download og installer den seneste version" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobierz i zainstaluj najnowszą wersję" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузить и установить последнюю версию" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzmite i instalirajte najnoviju verziju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تنزيل وتثبيت أحدث إصدار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Last ned og installer den nyeste versjonen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Baixar e instalar a versão mais recente" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ดาวน์โหลดและติดตั้งเวอร์ชันล่าสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "En son sürümü indir ve yükle" + } } } }, @@ -9723,6 +64539,102 @@ "state": "translated", "value": "ダウンロード中: %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在下载:%@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在下載:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다운로드 중: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Herunterladen: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descargando: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Téléchargement : %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Download: %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Downloader: %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobieranie: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка: %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzimanje: %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التنزيل: %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Laster ned: %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Baixando: %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังดาวน์โหลด: %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İndiriliyor: %@" + } } } }, @@ -9740,6 +64652,102 @@ "state": "translated", "value": "ダウンロード中…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在下载..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "下載中..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다운로드 중…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird heruntergeladen …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descargando…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Téléchargement..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Download in corso…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Downloader…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobieranie…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzimanje…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التنزيل…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Laster ned …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Baixando…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังดาวน์โหลด..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İndiriliyor…" + } } } }, @@ -9757,6 +64765,102 @@ "state": "translated", "value": "アップデートパッケージをダウンロード中" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在下载更新包" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在下載更新套件" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 패키지 다운로드 중" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update-Paket wird heruntergeladen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descargando el paquete de actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Téléchargement du paquet de mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Download del pacchetto di aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Downloader opdateringspakken" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobieranie pakietu aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка пакета обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzimanje paketa ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ تنزيل حزمة التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Laster ned oppdateringspakken" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Baixando o pacote de atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังดาวน์โหลดแพ็คเกจอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme paketi indiriliyor" + } } } }, @@ -9774,6 +64878,102 @@ "state": "translated", "value": "アプリの場所の問題" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "应用位置问题" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "App 位置問題" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "앱 위치 문제" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Problem mit dem App-Speicherort" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Problema con la ubicación de la app" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Problème d'emplacement de l'app" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Problema posizione app" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Problem med appplacering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Problem z lokalizacją aplikacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проблема с расположением приложения" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Problem s lokacijom aplikacije" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مشكلة في موقع التطبيق" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Problem med appplassering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Problema com Local do App" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ปัญหาตำแหน่งแอป" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Uygulama Konumu Sorunu" + } } } }, @@ -9791,6 +64991,102 @@ "state": "translated", "value": "アップデートの確認中にネットワーク接続が切れました。もう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "检查更新时网络连接中断。请重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "檢查更新時網路連線中斷。請再試一次。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 확인 중 네트워크 연결이 끊어졌습니다. 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Netzwerkverbindung wurde beim Suchen nach Updates unterbrochen. Versuchen Sie es erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se perdió la conexión de red al buscar actualizaciones. Inténtalo de nuevo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La connexion réseau a été perdue lors de la recherche de mises à jour. Réessayez." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "La connessione di rete è stata persa durante la verifica degli aggiornamenti. Riprova." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Netværksforbindelsen gik tabt under søgning efter opdateringer. Prøv igen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Połączenie sieciowe zostało utracone podczas sprawdzania aktualizacji. Spróbuj ponownie." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сетевое подключение было потеряно во время проверки обновлений. Повторите попытку." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Mrežna veza je prekinuta tokom provjere ažuriranja. Pokušajte ponovo." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انقطع اتصال الشبكة أثناء التحقق من التحديثات. حاول مجددًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nettverkstilkoblingen ble mistet under søk etter oppdateringer. Prøv igjen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A conexão de rede foi perdida ao verificar atualizações. Tente novamente." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเชื่อมต่อเครือข่ายขาดหายขณะตรวจหาอัปเดต ลองอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeler denetlenirken ağ bağlantısı kesildi. Tekrar deneyin." + } } } }, @@ -9808,6 +65104,102 @@ "state": "translated", "value": "接続が切れました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "连接中断" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "連線中斷" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "연결 끊김" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung unterbrochen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Conexión perdida" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Connexion perdue" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Connessione persa" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forbindelse tabt" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Utracono połączenie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подключение потеряно" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Veza prekinuta" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انقطع الاتصال" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tilkobling mistet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Conexão Perdida" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเชื่อมต่อขาดหาย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Bağlantı Kesildi" + } } } }, @@ -9825,6 +65217,102 @@ "state": "translated", "value": "アップデートをダウンロードできませんでした" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法下载更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法下載更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트를 다운로드할 수 없습니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update konnte nicht heruntergeladen werden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo descargar la actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible de télécharger la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile scaricare l'aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke downloade opdatering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się pobrać aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить обновление" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće preuzeti ažuriranje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر تنزيل التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke laste ned oppdatering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não Foi Possível Baixar a Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถดาวน์โหลดอัปเดตได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme İndirilemedi" + } } } }, @@ -9842,6 +65330,102 @@ "state": "translated", "value": "アップデートに失敗しました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新失败" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新失敗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 실패" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update fehlgeschlagen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error de actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Échec de la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Aggiornamento non riuscito" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdatering mislykkedes" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Aktualizacja nie powiodła się" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ažuriranje nije uspjelo" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فشل التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdatering mislyktes" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Falha na Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การอัปเดตล้มเหลว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Başarısız" + } } } }, @@ -9859,6 +65443,102 @@ "state": "translated", "value": "cmux はアップデートフィードをダウンロードできませんでした。接続を確認してもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 无法下载更新源。请检查网络连接后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 無法下載更新來源。請檢查您的連線後再試一次。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 업데이트 피드를 다운로드할 수 없습니다. 연결을 확인하고 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux konnte den Update-Feed nicht herunterladen. Überprüfen Sie Ihre Verbindung und versuchen Sie es erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux no pudo descargar la fuente de actualizaciones. Comprueba tu conexión e inténtalo de nuevo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux n'a pas pu télécharger le flux de mises à jour. Vérifiez votre connexion et réessayez." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux non è riuscito a scaricare il feed degli aggiornamenti. Controlla la connessione e riprova." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux kunne ikke downloade opdateringsfeedet. Kontroller din forbindelse, og prøv igen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux nie mógł pobrać kanału aktualizacji. Sprawdź połączenie i spróbuj ponownie." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux не удалось загрузить канал обновлений. Проверьте подключение и повторите попытку." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux nije mogao preuzeti feed ažuriranja. Provjerite vezu i pokušajte ponovo." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر على cmux تنزيل موجز التحديثات. تحقق من اتصالك وحاول مجددًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux kunne ikke laste ned oppdateringsfeeden. Kontroller tilkoblingen og prøv igjen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O cmux não conseguiu baixar o feed de atualização. Verifique sua conexão e tente novamente." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux ไม่สามารถดาวน์โหลดฟีดอัปเดตได้ ตรวจสอบการเชื่อมต่อแล้วลองอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux güncelleme akışını indiremedi. Bağlantınızı kontrol edip tekrar deneyin." + } } } }, @@ -9876,6 +65556,102 @@ "state": "translated", "value": "アップデートフィードエラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新源错误" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新來源錯誤" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 피드 오류" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehler im Update-Feed" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error en la fuente de actualizaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Erreur du flux de mises à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Errore feed aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fejl i opdateringsfeed" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Błąd kanału aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка канала обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Greška feeda ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "خطأ في موجز التحديثات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Feil i oppdateringsfeed" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Erro no Feed de Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้อผิดพลาดฟีดอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Akışı Hatası" + } } } }, @@ -9893,6 +65669,102 @@ "state": "translated", "value": "アップデートフィードを読み取れませんでした。後でもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法读取更新源。请稍后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法讀取更新來源。請稍後再試。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 피드를 읽을 수 없습니다. 나중에 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Update-Feed konnte nicht gelesen werden. Bitte versuchen Sie es später erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo leer la fuente de actualizaciones. Inténtalo de nuevo más tarde." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le flux de mises à jour n'a pas pu être lu. Veuillez réessayer plus tard." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile leggere il feed degli aggiornamenti. Riprova più tardi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringsfeedet kunne ikke læses. Prøv igen senere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie można odczytać kanału aktualizacji. Spróbuj ponownie później." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось прочитать канал обновлений. Повторите попытку позже." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Feed ažuriranja nije moguće pročitati. Pokušajte ponovo kasnije." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر قراءة موجز التحديثات. يرجى المحاولة لاحقًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringsfeeden kunne ikke leses. Prøv igjen senere." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O feed de atualização não pôde ser lido. Tente novamente mais tarde." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถอ่านฟีดอัปเดตได้ กรุณาลองอีกครั้งในภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme akışı okunamadı. Lütfen daha sonra tekrar deneyin." + } } } }, @@ -9910,6 +65782,102 @@ "state": "translated", "value": "アップデートフィードが安全ではありません。サポートにお問い合わせください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新源不安全。请联系支持团队。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新來源不安全。請聯絡支援團隊。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 피드가 안전하지 않습니다. 지원팀에 문의하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Update-Feed ist unsicher. Bitte kontaktieren Sie den Support." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La fuente de actualizaciones no es segura. Contacta con soporte." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le flux de mises à jour n'est pas sécurisé. Veuillez contacter le support." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Il feed degli aggiornamenti non è sicuro. Contatta il supporto." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringsfeedet er usikkert. Kontakt venligst supporten." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Kanał aktualizacji nie jest bezpieczny. Skontaktuj się ze wsparciem." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал обновлений не защищен. Обратитесь в службу поддержки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Feed ažuriranja nije siguran. Kontaktirajte podršku." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "موجز التحديثات غير آمن. يرجى الاتصال بالدعم." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringsfeeden er usikker. Kontakt brukerstøtte." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O feed de atualização é inseguro. Entre em contato com o suporte." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ฟีดอัปเดตไม่ปลอดภัย กรุณาติดต่อฝ่ายสนับสนุน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme akışı güvensiz. Lütfen destekle iletişime geçin." + } } } }, @@ -9927,6 +65895,102 @@ "state": "translated", "value": "安全でないアップデートフィード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "不安全的更新源" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "不安全的更新來源" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "안전하지 않은 업데이트 피드" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Unsicherer Update-Feed" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fuente de actualizaciones insegura" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Flux de mises à jour non sécurisé" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Feed aggiornamento non sicuro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Usikkert opdateringsfeed" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Niezabezpieczony kanał aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Незащищенный канал обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nesiguran feed ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "موجز تحديثات غير آمن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Usikker oppdateringsfeed" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Feed de Atualização Inseguro" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ฟีดอัปเดตไม่ปลอดภัย" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güvensiz Güncelleme Akışı" + } } } }, @@ -9944,6 +66008,102 @@ "state": "translated", "value": "アップデートフィードのURLが無効です。サポートにお問い合わせください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新源 URL 无效。请联系支持团队。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新來源 URL 無效。請聯絡支援團隊。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 피드 URL이 유효하지 않습니다. 지원팀에 문의하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die URL des Update-Feeds ist ungültig. Bitte kontaktieren Sie den Support." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La URL de la fuente de actualizaciones no es válida. Contacta con soporte." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "L'URL du flux de mises à jour n'est pas valide. Veuillez contacter le support." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "L'URL del feed degli aggiornamenti non è valido. Contatta il supporto." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "URL'en til opdateringsfeedet er ugyldig. Kontakt venligst supporten." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Adres URL kanału aktualizacji jest nieprawidłowy. Skontaktuj się ze wsparciem." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "URL канала обновлений недействителен. Обратитесь в службу поддержки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "URL feeda ažuriranja je nevažeći. Kontaktirajte podršku." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عنوان URL لموجز التحديثات غير صالح. يرجى الاتصال بالدعم." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "URL-en for oppdateringsfeeden er ugyldig. Kontakt brukerstøtte." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A URL do feed de atualização é inválida. Entre em contato com o suporte." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "URL ฟีดอัปเดตไม่ถูกต้อง กรุณาติดต่อฝ่ายสนับสนุน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme akışı URL'si geçersiz. Lütfen destekle iletişime geçin." + } } } }, @@ -9961,6 +66121,102 @@ "state": "translated", "value": "無効なアップデートフィード" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无效的更新源" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無效的更新來源" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "유효하지 않은 업데이트 피드" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ungültiger Update-Feed" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Fuente de actualizaciones no válida" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Flux de mises à jour non valide" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Feed aggiornamento non valido" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ugyldigt opdateringsfeed" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nieprawidłowy kanał aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Недействительный канал обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nevažeći feed ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "موجز تحديثات غير صالح" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ugyldig oppdateringsfeed" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Feed de Atualização Inválido" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ฟีดอัปเดตไม่ถูกต้อง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçersiz Güncelleme Akışı" + } } } }, @@ -9978,6 +66234,102 @@ "state": "translated", "value": "cmux はアップデートサーバーに接続できません。インターネット接続を確認してもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 无法连接到更新服务器。请检查互联网连接后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 無法連線至更新伺服器。請檢查您的網際網路連線後再試一次。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 업데이트 서버에 연결할 수 없습니다. 인터넷 연결을 확인하고 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux kann den Update-Server nicht erreichen. Überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux no puede acceder al servidor de actualizaciones. Comprueba tu conexión a internet e inténtalo de nuevo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux ne peut pas atteindre le serveur de mises à jour. Vérifiez votre connexion Internet et réessayez." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux non riesce a raggiungere il server degli aggiornamenti. Controlla la connessione a internet e riprova." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux kan ikke nå opdateringsserveren. Kontroller din internetforbindelse, og prøv igen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux nie może połączyć się z serwerem aktualizacji. Sprawdź połączenie z internetem i spróbuj ponownie." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux не может связаться с сервером обновлений. Проверьте подключение к интернету и повторите попытку." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux ne može dosegnuti server za ažuriranje. Provjerite internetsku vezu i pokušajte ponovo." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا يمكن لـ cmux الوصول إلى خادم التحديثات. تحقق من اتصال الإنترنت وحاول مجددًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux kan ikke nå oppdateringsserveren. Kontroller internettforbindelsen og prøv igjen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O cmux não consegue acessar o servidor de atualização. Verifique sua conexão com a internet e tente novamente." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux ไม่สามารถเข้าถึงเซิร์ฟเวอร์อัปเดตได้ ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลองอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux güncelleme sunucusuna ulaşamıyor. İnternet bağlantınızı kontrol edip tekrar deneyin." + } } } }, @@ -9995,6 +66347,102 @@ "state": "translated", "value": "インターネット接続がありません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无互联网连接" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有網際網路連線" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "인터넷 연결 없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Internetverbindung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Sin conexión a internet" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune connexion Internet" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessuna connessione a internet" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen internetforbindelse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak połączenia z internetem" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет подключения к интернету" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nema internetske veze" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا يوجد اتصال بالإنترنت" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen internettforbindelse" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sem Conexão com a Internet" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มีการเชื่อมต่ออินเทอร์เน็ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "İnternet Bağlantısı Yok" + } } } }, @@ -10012,6 +66460,102 @@ "state": "translated", "value": "アップデートを有効にするには、cmux を「アプリケーション」に移動して再起動してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "请将 cmux 移到「应用程序」文件夹中,然后重新启动以启用更新。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請將 cmux 移至「應用程式」資料夾並重新啟動,以啟用更新。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux를 응용 프로그램 폴더로 이동하고 재실행하여 업데이트를 활성화하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verschieben Sie cmux in den Ordner 'Programme' und starten Sie die App neu, um Updates zu aktivieren." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mueve cmux a Aplicaciones y vuelve a iniciarlo para activar las actualizaciones." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Déplacez cmux dans Applications et relancez pour activer les mises à jour." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sposta cmux nella cartella Applicazioni e riavvia per abilitare gli aggiornamenti." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Flyt cmux til Programmer, og genstart for at aktivere opdateringer." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przenieś cmux do folderu Aplikacje i uruchom ponownie, aby włączyć aktualizacje." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переместите cmux в папку «Программы» и перезапустите для включения обновлений." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Premjestite cmux u Applications i ponovo pokrenite da biste omogućili ažuriranja." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انقل cmux إلى مجلد التطبيقات وأعد التشغيل لتفعيل التحديثات." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Flytt cmux til Programmer og start på nytt for å aktivere oppdateringer." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Mova o cmux para Aplicativos e reinicie para ativar as atualizações." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ย้าย cmux ไปที่โฟลเดอร์ Applications แล้วเปิดใหม่เพื่อเปิดใช้งานการอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeleri etkinleştirmek için cmux'u Uygulamalar klasörüne taşıyıp yeniden başlatın." + } } } }, @@ -10029,6 +66573,102 @@ "state": "translated", "value": "アップデーター権限エラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新程序权限错误" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新程式權限錯誤" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이터 권한 오류" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Berechtigungsfehler des Updaters" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error de permisos del actualizador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Erreur de permissions du programme de mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Errore di autorizzazione dell'aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fejl i opdateringstilladelse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Błąd uprawnień aktualizatora" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка прав доступа программы обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Greška dozvole za ažuriranje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "خطأ في صلاحيات المحدّث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Tillatelsessfeil for oppdatering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Erro de Permissão do Atualizador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้อผิดพลาดสิทธิ์ตัวอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleyici İzin Hatası" + } } } }, @@ -10046,6 +66686,102 @@ "state": "translated", "value": "アップデートサーバーへのセキュア接続を確立できませんでした。後でもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法与更新服务器建立安全连接。请稍后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法建立與更新伺服器的安全連線。請稍後再試。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 서버에 대한 보안 연결을 설정할 수 없습니다. 나중에 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Es konnte keine sichere Verbindung zum Update-Server hergestellt werden. Versuchen Sie es später erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo establecer una conexión segura con el servidor de actualizaciones. Inténtalo de nuevo más tarde." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible d'établir une connexion sécurisée avec le serveur de mises à jour. Réessayez plus tard." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile stabilire una connessione sicura al server degli aggiornamenti. Riprova più tardi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "En sikker forbindelse til opdateringsserveren kunne ikke oprettes. Prøv igen senere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się nawiązać bezpiecznego połączenia z serwerem aktualizacji. Spróbuj ponownie później." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось установить защищенное подключение к серверу обновлений. Повторите попытку позже." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće uspostaviti sigurnu vezu sa serverom za ažuriranje. Pokušajte ponovo kasnije." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر إنشاء اتصال آمن بخادم التحديثات. حاول لاحقًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "En sikker tilkobling til oppdateringsserveren kunne ikke opprettes. Prøv igjen senere." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não foi possível estabelecer uma conexão segura com o servidor de atualização. Tente novamente mais tarde." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถสร้างการเชื่อมต่อที่ปลอดภัยกับเซิร์ฟเวอร์อัปเดตได้ ลองอีกครั้งในภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme sunucusuyla güvenli bir bağlantı kurulamadı. Daha sonra tekrar deneyin." + } } } }, @@ -10063,6 +66799,102 @@ "state": "translated", "value": "セキュア接続に失敗しました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "安全连接失败" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "安全連線失敗" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "보안 연결 실패" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Sichere Verbindung fehlgeschlagen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error de conexión segura" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Échec de la connexion sécurisée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Connessione sicura non riuscita" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sikker forbindelse mislykkedes" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Bezpieczne połączenie nie powiodło się" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка защищенного подключения" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sigurna veza nije uspjela" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فشل الاتصال الآمن" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sikker tilkobling mislyktes" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Falha na Conexão Segura" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การเชื่อมต่อที่ปลอดภัยล้มเหลว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güvenli Bağlantı Başarısız" + } } } }, @@ -10080,6 +66912,102 @@ "state": "translated", "value": "アップデートサーバーが見つかりません。接続を確認するか、後でもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "找不到更新服务器。请检查网络连接或稍后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "找不到更新伺服器。請檢查您的連線或稍後再試。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 서버를 찾을 수 없습니다. 연결을 확인하거나 나중에 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Update-Server kann nicht gefunden werden. Überprüfen Sie Ihre Verbindung oder versuchen Sie es später erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se encuentra el servidor de actualizaciones. Comprueba tu conexión o inténtalo de nuevo más tarde." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le serveur de mises à jour est introuvable. Vérifiez votre connexion ou réessayez plus tard." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile trovare il server degli aggiornamenti. Controlla la connessione o riprova più tardi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringsserveren kan ikke findes. Kontroller din forbindelse, eller prøv igen senere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie można znaleźć serwera aktualizacji. Sprawdź połączenie lub spróbuj ponownie później." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сервер обновлений не найден. Проверьте подключение или повторите попытку позже." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Server za ažuriranje nije pronađen. Provjerite vezu ili pokušajte ponovo kasnije." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر العثور على خادم التحديثات. تحقق من اتصالك أو حاول لاحقًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringsserveren ble ikke funnet. Kontroller tilkoblingen eller prøv igjen senere." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O servidor de atualização não foi encontrado. Verifique sua conexão ou tente novamente mais tarde." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่พบเซิร์ฟเวอร์อัปเดต ตรวจสอบการเชื่อมต่อหรือลองอีกครั้งในภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme sunucusu bulunamıyor. Bağlantınızı kontrol edin veya daha sonra tekrar deneyin." + } } } }, @@ -10097,6 +67025,102 @@ "state": "translated", "value": "サーバーが見つかりません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "找不到服务器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "找不到伺服器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "서버를 찾을 수 없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Server nicht gefunden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Servidor no encontrado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Serveur introuvable" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Server non trovato" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Server ikke fundet" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie znaleziono serwera" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сервер не найден" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Server nije pronađen" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لم يتم العثور على الخادم" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Server ikke funnet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Servidor Não Encontrado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่พบเซิร์ฟเวอร์" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sunucu Bulunamadı" + } } } }, @@ -10114,6 +67138,102 @@ "state": "translated", "value": "cmux はアップデートサーバーに接続できませんでした。接続を確認するか、後でもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 无法连接到更新服务器。请检查网络连接或稍后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 無法連線至更新伺服器。請檢查您的連線或稍後再試。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux에서 업데이트 서버에 연결할 수 없습니다. 연결을 확인하거나 나중에 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux konnte keine Verbindung zum Update-Server herstellen. Überprüfen Sie Ihre Verbindung oder versuchen Sie es später erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux no pudo conectar con el servidor de actualizaciones. Comprueba tu conexión o inténtalo de nuevo más tarde." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux n'a pas pu se connecter au serveur de mises à jour. Vérifiez votre connexion ou réessayez plus tard." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux non è riuscito a connettersi al server degli aggiornamenti. Controlla la connessione o riprova più tardi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux kunne ikke oprette forbindelse til opdateringsserveren. Kontroller din forbindelse, eller prøv igen senere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux nie mógł połączyć się z serwerem aktualizacji. Sprawdź połączenie lub spróbuj ponownie później." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux не удалось подключиться к серверу обновлений. Проверьте подключение или повторите попытку позже." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux se nije mogao povezati na server za ažuriranje. Provjerite vezu ili pokušajte ponovo kasnije." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر على cmux الاتصال بخادم التحديثات. تحقق من اتصالك أو حاول لاحقًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux kunne ikke koble til oppdateringsserveren. Kontroller tilkoblingen eller prøv igjen senere." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O cmux não conseguiu se conectar ao servidor de atualização. Verifique sua conexão ou tente novamente mais tarde." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์อัปเดตได้ ตรวจสอบการเชื่อมต่อหรือลองอีกครั้งในภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux güncelleme sunucusuna bağlanamadı. Bağlantınızı kontrol edin veya daha sonra tekrar deneyin." + } } } }, @@ -10131,6 +67251,102 @@ "state": "translated", "value": "サーバーに接続できません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "服务器不可达" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "伺服器無法連線" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "서버에 연결할 수 없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Server nicht erreichbar" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Servidor inaccesible" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Serveur inaccessible" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Server non raggiungibile" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Server utilgængelig" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Serwer nieosiągalny" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сервер недоступен" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Server nedostupan" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الخادم غير قابل للوصول" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Server utilgjengelig" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Servidor Inacessível" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เซิร์ฟเวอร์เข้าถึงไม่ได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sunucuya Erişilemiyor" + } } } }, @@ -10148,6 +67364,102 @@ "state": "translated", "value": "アップデートの署名を検証できませんでした。後でもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法验证更新的签名。请稍后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法驗證更新的簽章。請稍後再試。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 서명을 확인할 수 없습니다. 나중에 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Signatur des Updates konnte nicht überprüft werden. Bitte versuchen Sie es später erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo verificar la firma de la actualización. Inténtalo de nuevo más tarde." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La signature de la mise à jour n'a pas pu être vérifiée. Veuillez réessayer plus tard." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile verificare la firma dell'aggiornamento. Riprova più tardi." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringens signatur kunne ikke verificeres. Prøv igen senere." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się zweryfikować podpisu aktualizacji. Spróbuj ponownie później." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить подпись обновления. Повторите попытку позже." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Potpis ažuriranja nije mogao biti verificiran. Pokušajte ponovo kasnije." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذر التحقق من توقيع التحديث. يرجى المحاولة لاحقًا." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringens signatur kunne ikke verifiseres. Prøv igjen senere." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A assinatura da atualização não pôde ser verificada. Tente novamente mais tarde." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถยืนยันลายเซ็นของอัปเดตได้ กรุณาลองอีกครั้งในภายหลัง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemenin imzası doğrulanamadı. Lütfen daha sonra tekrar deneyin." + } } } }, @@ -10165,6 +67477,102 @@ "state": "translated", "value": "アップデート署名エラー" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新签名错误" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新簽章錯誤" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 서명 오류" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehler bei der Update-Signatur" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error de firma de actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Erreur de signature de la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Errore firma aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Fejl i opdateringssignatur" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Błąd podpisu aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка подписи обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Greška potpisa ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "خطأ في توقيع التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Feil i oppdateringssignatur" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Erro de Assinatura da Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ข้อผิดพลาดลายเซ็นอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme İmza Hatası" + } } } }, @@ -10182,6 +67590,102 @@ "state": "translated", "value": "アップデートサーバーの応答に時間がかかりすぎました。しばらくしてからもう一度お試しください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新服务器响应超时。请稍后重试。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新伺服器回應時間過長。請稍後再試。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 서버가 응답하는 데 너무 오래 걸렸습니다. 잠시 후 다시 시도하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Update-Server hat zu lange gebraucht, um zu antworten. Versuchen Sie es in einem Moment erneut." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El servidor de actualizaciones tardó demasiado en responder. Inténtalo de nuevo en un momento." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le serveur de mises à jour a mis trop de temps à répondre. Réessayez dans un instant." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Il server degli aggiornamenti ha impiegato troppo tempo a rispondere. Riprova tra un momento." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringsserveren var for lang tid om at svare. Prøv igen om et øjeblik." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Serwer aktualizacji zbyt długo nie odpowiadał. Spróbuj ponownie za chwilę." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сервер обновлений не ответил вовремя. Повторите попытку через некоторое время." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Server za ažuriranje je predugo čekao da odgovori. Pokušajte ponovo za trenutak." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استغرق خادم التحديثات وقتًا طويلاً للاستجابة. حاول مجددًا بعد لحظة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringsserveren brukte for lang tid på å svare. Prøv igjen om et øyeblikk." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O servidor de atualização demorou muito para responder. Tente novamente em instantes." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เซิร์ฟเวอร์อัปเดตใช้เวลานานเกินไปในการตอบกลับ ลองอีกครั้งในอีกสักครู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme sunucusu yanıt vermekte çok uzun sürdü. Birazdan tekrar deneyin." + } } } }, @@ -10199,6 +67703,102 @@ "state": "translated", "value": "アップデートがタイムアウトしました" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新超时" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新逾時" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 시간 초과" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update-Zeitüberschreitung" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tiempo de espera agotado" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Délai de mise à jour dépassé" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Aggiornamento scaduto" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdatering fik timeout" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przekroczono limit czasu aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Время ожидания обновления истекло" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Isteklo vrijeme ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انتهت مهلة التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdatering tidsavbrutt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tempo de Atualização Esgotado" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "การอัปเดตหมดเวลา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Zaman Aşımına Uğradı" + } } } }, @@ -10216,6 +67816,102 @@ "state": "translated", "value": "準備中: %@" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在准备:%@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在準備:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "준비 중: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorbereitung: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Preparando: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Préparation : %@" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Preparazione: %@" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forbereder: %@" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przygotowywanie: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подготовка: %@" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Priprema: %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التحضير: %@" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forbereder: %@" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Preparando: %@" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังเตรียม: %@" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Hazırlanıyor: %@" + } } } }, @@ -10233,6 +67929,102 @@ "state": "translated", "value": "アップデートをインストールして再起動" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "安装更新并重新启动" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "安裝更新並重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 설치 후 재실행" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update installieren und neu starten" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Instalar actualización y reiniciar" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Installer la mise à jour et relancer" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Installa aggiornamento e riavvia" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Installer opdatering og genstart" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Zainstaluj aktualizację i uruchom ponownie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить обновление и перезапустить" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Instaliraj ažuriranje i ponovo pokreni" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تثبيت التحديث وإعادة التشغيل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Installer oppdatering og start på nytt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Instalar Atualização e Reiniciar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ติดตั้งอัปเดตและเปิดใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeyi Yükle ve Yeniden Başlat" + } } } }, @@ -10250,6 +68042,102 @@ "state": "translated", "value": "インストール中…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在安装..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "安裝中..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "설치 중…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird installiert …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Instalando…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Installation..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Installazione in corso…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Installerer…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Instalowanie…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установка..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Instaliranje…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التثبيت…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Installerer …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Instalando…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังติดตั้ง..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yükleniyor…" + } } } }, @@ -10267,6 +68155,102 @@ "state": "translated", "value": "アップデートをインストールして再起動を準備中" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在安装更新并准备重启" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在安裝更新並準備重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 설치 및 재시작 준비 중" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update wird installiert und Neustart wird vorbereitet" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Instalando la actualización y preparando el reinicio" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Installation de la mise à jour et préparation du redémarrage" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Installazione dell'aggiornamento e preparazione al riavvio" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Installerer opdatering og forbereder genstart" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Instalowanie aktualizacji i przygotowywanie do ponownego uruchomienia" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установка обновления и подготовка к перезапуску" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Instaliranje ažuriranja i priprema za ponovni start" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ تثبيت التحديث والتحضير لإعادة التشغيل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Installerer oppdatering og forbereder omstart" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Instalando a atualização e preparando para reiniciar" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังติดตั้งอัปเดตและเตรียมรีสตาร์ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme yükleniyor ve yeniden başlatmaya hazırlanıyor" + } } } }, @@ -10284,6 +68268,102 @@ "state": "translated", "value": "最新バージョンを使用しています" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "您正在运行最新版本" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "您正在執行最新版本" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "최신 버전을 사용 중입니다" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Sie verwenden die neueste Version" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Estás ejecutando la última versión" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Vous utilisez la dernière version" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Stai utilizzando l'ultima versione" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Du kører den seneste version" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Używasz najnowszej wersji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "У вас установлена последняя версия" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Koristite najnoviju verziju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أنت تستخدم أحدث إصدار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Du kjører den nyeste versjonen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Você está usando a versão mais recente" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คุณใช้เวอร์ชันล่าสุดแล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "En son sürümü kullanıyorsunuz" + } } } }, @@ -10301,6 +68381,102 @@ "state": "translated", "value": "アップデートはありません" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "没有可用更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有可用的更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용 가능한 업데이트 없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Updates verfügbar" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No hay actualizaciones disponibles" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune mise à jour disponible" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessun aggiornamento disponibile" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen tilgængelige opdateringer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Brak dostępnych aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлений нет" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nema dostupnih ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لا توجد تحديثات متوفرة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen oppdateringer tilgjengelig" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma Atualização Disponível" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่มีอัปเดตใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Yok" + } } } }, @@ -10318,6 +68494,102 @@ "state": "translated", "value": "自動アップデートを有効にしますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用自动更新?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要啟用自動更新嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동 업데이트를 활성화하시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatische Updates aktivieren?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Activar actualizaciones automáticas?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer les mises à jour automatiques ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Abilitare gli aggiornamenti automatici?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiver automatiske opdateringer?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Włączyć automatyczne aktualizacje?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включить автоматические обновления?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Omogućiti automatska ažuriranja?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تفعيل التحديثات التلقائية؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktivere automatiske oppdateringer?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ativar Atualizações Automáticas?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดใช้งานการอัปเดตอัตโนมัติหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomatik Güncellemeler Etkinleştirilsin mi?" + } } } }, @@ -10335,6 +68607,102 @@ "state": "translated", "value": "アップデートを確認しています。しばらくお待ちください" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在检查可用更新,请稍候" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "請稍候,正在檢查可用的更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용 가능한 업데이트를 확인하는 동안 잠시 기다려주세요" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitte warten Sie, während nach verfügbaren Updates gesucht wird" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espera mientras buscamos actualizaciones disponibles" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Veuillez patienter pendant la recherche de mises à jour disponibles" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Attendi mentre verifichiamo gli aggiornamenti disponibili" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vent venligst, mens vi søger efter tilgængelige opdateringer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Proszę czekać, sprawdzamy dostępne aktualizacje" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подождите, пока мы проверяем наличие обновлений" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pričekajte dok provjeravamo dostupna ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يرجى الانتظار أثناء التحقق من التحديثات المتوفرة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vennligst vent mens vi ser etter tilgjengelige oppdateringer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Aguarde enquanto verificamos as atualizações disponíveis" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กรุณารอขณะตรวจหาอัปเดตที่มีอยู่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Mevcut güncellemeler denetlenirken lütfen bekleyin" + } } } }, @@ -10352,6 +68720,102 @@ "state": "translated", "value": "cmux はバックグラウンドで自動的にアップデートを確認できます。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 可以在后台自动检查更新。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 可以在背景自動檢查更新。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux가 백그라운드에서 자동으로 업데이트를 확인할 수 있습니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux kann automatisch im Hintergrund nach Updates suchen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux puede buscar actualizaciones automáticamente en segundo plano." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux peut rechercher automatiquement les mises à jour en arrière-plan." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux può verificare automaticamente la disponibilità di aggiornamenti in background." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux kan automatisk søge efter opdateringer i baggrunden." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux może automatycznie sprawdzać aktualizacje w tle." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux может автоматически проверять наличие обновлений в фоновом режиме." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux može automatski provjeravati ažuriranja u pozadini." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يمكن لـ cmux التحقق تلقائيًا من التحديثات في الخلفية." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux kan automatisk se etter oppdateringer i bakgrunnen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O cmux pode verificar automaticamente se há atualizações em segundo plano." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux สามารถตรวจหาอัปเดตอัตโนมัติในเบื้องหลังได้" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux arka planda otomatik olarak güncellemeleri denetleyebilir." + } } } }, @@ -10369,6 +68833,102 @@ "state": "translated", "value": "アップデートを確認中…" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在检查更新..." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在檢查更新..." + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 확인 중…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Suche nach Updates …" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Buscando actualizaciones…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recherche de mises à jour..." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Verifica aggiornamenti in corso…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Søger efter opdateringer…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sprawdzanie aktualizacji…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка обновлений..." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Provjeravanje ažuriranja…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ التحقق من التحديثات…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ser etter oppdateringer …" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Verificando atualizações…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังตรวจหาอัปเดต..." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeler denetleniyor…" + } } } }, @@ -10386,6 +68946,102 @@ "state": "translated", "value": "詳細" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "详细信息" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "詳細資訊" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "세부 정보" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Details" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Detalles" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Détails" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dettagli" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Detaljer" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Szczegóły" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подробности" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Detalji" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التفاصيل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Detaljer" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Detalhes" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รายละเอียด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Ayrıntılar" + } } } }, @@ -10403,6 +69059,102 @@ "state": "translated", "value": "アップデートをダウンロード中" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在下载更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在下載更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 다운로드 중" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update wird heruntergeladen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descargando actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Téléchargement de la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Download aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Downloader opdatering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pobieranie aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Preuzimanje ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ تنزيل التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Laster ned oppdatering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Baixando Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังดาวน์โหลดอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme İndiriliyor" + } } } }, @@ -10420,6 +69172,102 @@ "state": "translated", "value": "自動アップデートを有効にしますか?" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用自动更新?" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "要啟用自動更新嗎?" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "자동 업데이트를 활성화하시겠습니까?" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatische Updates aktivieren?" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "¿Activar actualizaciones automáticas?" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer les mises à jour automatiques ?" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Abilitare gli aggiornamenti automatici?" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Aktiver automatiske opdateringer?" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Włączyć automatyczne aktualizacje?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включить автоматические обновления?" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Omogućiti automatska ažuriranja?" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تفعيل التحديثات التلقائية؟" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Aktivere automatiske oppdateringer?" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ativar atualizações automáticas?" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เปิดใช้งานการอัปเดตอัตโนมัติหรือไม่?" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Otomatik güncellemeler etkinleştirilsin mi?" + } } } }, @@ -10437,6 +69285,102 @@ "state": "translated", "value": "アップデートは見つかりませんでした" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未发现更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "沒有找到更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 없음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Updates gefunden" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se encontraron actualizaciones" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Aucune mise à jour trouvée" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nessun aggiornamento trovato" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ingen opdateringer fundet" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie znaleziono aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлений не найдено" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ažuriranja nisu pronađena" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لم يتم العثور على تحديثات" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ingen oppdateringer funnet" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nenhuma Atualização Encontrada" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่พบอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Bulunamadı" + } } } }, @@ -10454,6 +69398,102 @@ "state": "translated", "value": "すでに最新バージョンを使用しています。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "您已经在运行最新版本。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "您已經在使用最新版本。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이미 최신 버전을 사용 중입니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Sie verwenden bereits die neueste Version." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ya estás ejecutando la última versión." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Vous utilisez déjà la dernière version." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Stai già utilizzando l'ultima versione." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Du kører allerede den seneste version." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Używasz już najnowszej wersji." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "У вас уже установлена последняя версия." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Već koristite najnoviju verziju." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أنت تستخدم أحدث إصدار بالفعل." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Du kjører allerede den nyeste versjonen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Você já está usando a versão mais recente." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "คุณใช้เวอร์ชันล่าสุดอยู่แล้ว" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Zaten en son sürümü kullanıyorsunuz." + } } } }, @@ -10471,6 +69511,102 @@ "state": "translated", "value": "アップデートを準備中" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在准备更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在準備更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 준비 중" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update wird vorbereitet" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Preparando actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Préparation de la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Preparazione aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Forbereder opdatering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przygotowywanie aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подготовка обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Priprema ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ تحضير التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Forbereder oppdatering" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Preparando Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังเตรียมอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Hazırlanıyor" + } } } }, @@ -10488,6 +69624,102 @@ "state": "translated", "value": "リリース日:" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "发布日期:" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "發佈日期:" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "출시일:" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Veröffentlicht:" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Publicado:" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Publiée le :" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Rilasciato:" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Udgivet:" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wydano:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выпущено:" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Objavljeno:" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تاريخ الإصدار:" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Utgitt:" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Lançado:" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เผยแพร่:" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yayınlanma:" + } } } }, @@ -10505,6 +69737,102 @@ "state": "translated", "value": "再起動が必要です" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "需要重启" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "需要重新啟動" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "재시작 필요" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neustart erforderlich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reinicio requerido" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrage requis" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvio necessario" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart påkrævet" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wymagane ponowne uruchomienie" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Требуется перезапуск" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Potreban ponovni start" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة التشغيل مطلوبة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Omstart nødvendig" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reinicialização Necessária" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ต้องรีสตาร์ต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeniden Başlatma Gerekli" + } } } }, @@ -10522,6 +69850,102 @@ "state": "translated", "value": "アップデートの準備ができました。インストールを完了するにはアプリケーションを再起動してください。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "更新已就绪。请重启应用程序以完成安装。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "更新已就緒。請重新啟動應用程式以完成安裝。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트가 준비되었습니다. 설치를 완료하려면 앱을 재시작하세요." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Update ist bereit. Bitte starten Sie die Anwendung neu, um die Installation abzuschließen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La actualización está lista. Reinicia la aplicación para completar la instalación." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La mise à jour est prête. Veuillez redémarrer l'application pour terminer l'installation." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "L'aggiornamento è pronto. Riavvia l'applicazione per completare l'installazione." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdateringen er klar. Genstart programmet for at fuldføre installationen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Aktualizacja jest gotowa. Uruchom ponownie aplikację, aby zakończyć instalację." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновление готово. Перезапустите приложение для завершения установки." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ažuriranje je spremno. Ponovo pokrenite aplikaciju da završite instalaciju." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التحديث جاهز. يرجى إعادة تشغيل التطبيق لإكمال التثبيت." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdateringen er klar. Start programmet på nytt for å fullføre installasjonen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "A atualização está pronta. Reinicie o aplicativo para concluir a instalação." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "อัปเดตพร้อมแล้ว กรุณารีสตาร์ตแอปพลิเคชันเพื่อเสร็จสิ้นการติดตั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme hazır. Yüklemeyi tamamlamak için lütfen uygulamayı yeniden başlatın." + } } } }, @@ -10539,6 +69963,102 @@ "state": "translated", "value": "サイズ:" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "大小:" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "大小:" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "크기:" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe:" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Tamaño:" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Taille :" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dimensione:" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Størrelse:" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Rozmiar:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Размер:" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Veličina:" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الحجم:" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Størrelse:" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Tamanho:" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ขนาด:" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Boyut:" + } } } }, @@ -10556,6 +70076,102 @@ "state": "translated", "value": "アップデートがあります" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "有可用更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "有可用的更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 사용 가능" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update verfügbar" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Actualización disponible" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mise à jour disponible" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Aggiornamento disponibile" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdatering tilgængelig" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dostępna aktualizacja" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ažuriranje dostupno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تحديث متوفر" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Oppdatering tilgjengelig" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Atualização Disponível" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "มีอัปเดตใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme Mevcut" + } } } }, @@ -10573,6 +70189,102 @@ "state": "translated", "value": "バージョン:" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "版本:" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "版本:" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "버전:" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Version:" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Versión:" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Version :" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Versione:" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Version:" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wersja:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Версия:" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Verzija:" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الإصدار:" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Versjon:" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Versão:" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวอร์ชัน:" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sürüm:" + } } } }, @@ -10590,6 +70302,102 @@ "state": "translated", "value": "アップデートを展開して準備中" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在解压和准备更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在解壓並準備更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트 추출 및 준비 중" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Update wird entpackt und vorbereitet" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Extrayendo y preparando la actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Extraction et préparation de la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Estrazione e preparazione dell'aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Udpakker og forbereder opdateringen" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Rozpakowywanie i przygotowywanie aktualizacji" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Извлечение и подготовка обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Izdvajanje i priprema ažuriranja" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ استخراج التحديث وتحضيره" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Pakker ut og forbereder oppdateringen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Extraindo e preparando a atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "กำลังแตกไฟล์และเตรียมอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncelleme çıkarılıyor ve hazırlanıyor" + } } } }, @@ -10607,6 +70415,102 @@ "state": "translated", "value": "アップデートを完了するには再起動してください" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重启以完成更新" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新啟動以完成更新" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "업데이트를 완료하려면 재시작" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neustart zum Abschließen des Updates" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar para completar la actualización" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Redémarrer pour terminer la mise à jour" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Riavvia per completare l'aggiornamento" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Genstart for at fuldføre opdatering" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Uruchom ponownie, aby zakończyć aktualizację" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустите для завершения обновления" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Ponovo pokrenite da završite ažuriranje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أعد التشغيل لإكمال التحديث" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Start på nytt for å fullføre oppdateringen" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reiniciar para Concluir a Atualização" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "รีสตาร์ตเพื่อเสร็จสิ้นการอัปเดต" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Güncellemeyi Tamamlamak İçin Yeniden Başlat" + } } } }, @@ -10624,6 +70528,102 @@ "state": "translated", "value": "GitHub コミットを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查看 GitHub 提交" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "檢視 GitHub 提交" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "GitHub 커밋 보기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "GitHub-Commit anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ver commit en GitHub" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Voir le commit GitHub" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza commit su GitHub" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis GitHub Commit" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż commit na GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Просмотреть коммит на GitHub" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pogledaj GitHub commit" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض إيداع GitHub" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis GitHub-commit" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ver Commit no GitHub" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ดูคอมมิต GitHub" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "GitHub Commit'ini Görüntüle" + } } } }, @@ -10641,6 +70641,102 @@ "state": "translated", "value": "リリースノートを表示" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "查看发行说明" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "檢視版本說明" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "릴리스 노트 보기" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Versionshinweise anzeigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ver notas de la versión" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Voir les notes de version" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Visualizza note di rilascio" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Vis udgivelsesnoter" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Pokaż informacje o wydaniu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Просмотреть заметки о выпуске" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Pogledaj bilješke o izdanju" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عرض ملاحظات الإصدار" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Vis versjonsnotater" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Ver Notas de Lançamento" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ดูบันทึกการเผยแพร่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sürüm Notlarını Görüntüle" + } } } }, @@ -10658,6 +70754,102 @@ "state": "translated", "value": "ワークスペース" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereich" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Espacio de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Espace de travail" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Area di lavoro" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Arbejdsområde" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Przestrzeń robocza" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Рабочее пространство" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Radni prostor" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مساحة العمل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Arbeidsområde" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Área de Trabalho" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เวิร์กสเปซ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Çalışma Alanı" + } } } }, @@ -10675,6 +70867,102 @@ "state": "translated", "value": "現在の後" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "当前之后" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "目前之後" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "현재 다음" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach aktuellem" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Después del actual" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Après l'actuel" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dopo la corrente" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Efter nuværende" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Po bieżącej" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После текущего" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Poslije trenutnog" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "بعد الحالي" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Etter gjeldende" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Após a atual" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "หลังรายการปัจจุบัน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Geçerli sonrasına" + } } } }, @@ -10692,6 +70980,102 @@ "state": "translated", "value": "アクティブなワークスペースの直後に新しいワークスペースを挿入します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在活动工作区之后插入新工作区。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將新工作區插入目前使用中工作區的後方。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "활성 작업 공간 바로 다음에 새 작업 공간을 삽입합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neue Arbeitsbereiche direkt nach dem aktiven Arbeitsbereich einfügen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Insertar nuevos espacios de trabajo justo después del espacio de trabajo activo." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Insérer les nouveaux espaces de travail juste après l'espace de travail actif." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci le nuove aree di lavoro subito dopo quella attiva." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indsæt nye arbejdsområder direkte efter det aktive arbejdsområde." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wstaw nowe przestrzenie robocze bezpośrednio po aktywnej." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вставлять новые рабочие пространства сразу после активного." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Umetnite nove radne prostore odmah nakon aktivnog radnog prostora." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إدراج مساحات العمل الجديدة مباشرة بعد مساحة العمل النشطة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sett inn nye arbeidsområder rett etter det aktive arbeidsområdet." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Inserir novas áreas de trabalho diretamente após a área de trabalho ativa." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แทรกเวิร์กสเปซใหม่หลังเวิร์กสเปซที่ใช้งานอยู่โดยตรง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni çalışma alanlarını etkin çalışma alanının hemen sonrasına ekle." + } } } }, @@ -10709,6 +71093,102 @@ "state": "translated", "value": "末尾" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "末尾" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "底部" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "끝" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ende" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Final" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Fin" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Fine" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sidst" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Na końcu" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В конец" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Kraj" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "النهاية" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Slutt" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Final" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ท้ายสุด" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sona" + } } } }, @@ -10726,6 +71206,102 @@ "state": "translated", "value": "新しいワークスペースをリストの末尾に追加します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将新工作区添加到列表底部。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將新工作區附加到列表底部。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "목록 하단에 새 작업 공간을 추가합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neue Arbeitsbereiche am Ende der Liste anfügen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Agregar nuevos espacios de trabajo al final de la lista." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajouter les nouveaux espaces de travail en bas de la liste." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Aggiungi le nuove aree di lavoro in fondo alla lista." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Tilføj nye arbejdsområder nederst på listen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Dodaj nowe przestrzenie robocze na dole listy." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Добавлять новые рабочие пространства в конец списка." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Dodajte nove radne prostore na kraj liste." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلحاق مساحات العمل الجديدة في أسفل القائمة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Legg til nye arbeidsområder nederst i listen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Adicionar novas áreas de trabalho ao final da lista." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เพิ่มเวิร์กสเปซใหม่ที่ด้านล่างของรายการ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni çalışma alanlarını listenin sonuna ekle." + } } } }, @@ -10743,6 +71319,102 @@ "state": "translated", "value": "先頭" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "顶部" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "最上方" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "맨 위" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Oben" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Inicio" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Début" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inizio" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Øverst" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Na górze" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В начало" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Vrh" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الأعلى" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Topp" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Topo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ด้านบน" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "En Üste" + } } } }, @@ -10760,6 +71432,102 @@ "state": "translated", "value": "新しいワークスペースをリストの先頭に挿入します。" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在列表顶部插入新工作区。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將新工作區插入列表最上方。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "목록 맨 위에 새 작업 공간을 삽입합니다." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neue Arbeitsbereiche oben in der Liste einfügen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Insertar nuevos espacios de trabajo al inicio de la lista." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Insérer les nouveaux espaces de travail en haut de la liste." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Inserisci le nuove aree di lavoro in cima alla lista." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Indsæt nye arbejdsområder øverst på listen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Wstaw nowe przestrzenie robocze na górze listy." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вставлять новые рабочие пространства в начало списка." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Umetnite nove radne prostore na vrh liste." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إدراج مساحات العمل الجديدة في أعلى القائمة." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sett inn nye arbeidsområder øverst i listen." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Inserir novas áreas de trabalho no topo da lista." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แทรกเวิร์กสเปซใหม่ที่ด้านบนของรายการ" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni çalışma alanlarını listenin en üstüne ekle." + } } } }, @@ -10777,6 +71545,102 @@ "state": "translated", "value": "新規ブラウザ" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建浏览器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增瀏覽器" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 브라우저" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neuer Browser" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo navegador" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouveau navigateur" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuovo browser" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ny browser" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowa przeglądarka" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новый браузер" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi preglednik" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "متصفح جديد" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ny nettleser" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Novo Navegador" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เบราว์เซอร์ใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Tarayıcı" + } } } }, @@ -10794,6 +71658,102 @@ "state": "translated", "value": "新規ターミナル" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新建终端" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新增終端機" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 터미널" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Neues Terminal" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Nuevo terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nouveau terminal" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nuovo terminale" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Ny terminal" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nowy terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новый терминал" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Novi terminal" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "طرفية جديدة" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Ny terminal" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Novo Terminal" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัลใหม่" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yeni Terminal" + } } } }, @@ -10811,6 +71771,102 @@ "state": "translated", "value": "下に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向下拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向下分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "아래로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach unten teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir hacia abajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser vers le bas" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi in basso" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel nedad" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w dół" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вниз" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli dolje" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم للأسفل" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del ned" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir para Baixo" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกลงล่าง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Aşağı Böl" + } } } }, @@ -10828,6 +71884,102 @@ "state": "translated", "value": "右に分割" } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "向右拆分" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "向右分割" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽으로 분할" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach rechts teilen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Dividir a la derecha" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Diviser à droite" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Dividi a destra" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Opdel til højre" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Podziel w prawo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделить вправо" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Podijeli desno" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تقسيم لليمين" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Del til høyre" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Dividir à Direita" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "แยกไปทางขวา" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sağa Böl" + } } } } diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 8b36f4bc..fdc91f13 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -44,6 +44,9 @@ struct cmuxApp: App { Self.configureGhosttyEnvironment() + // Apply saved language preference before any UI loads + LanguageSettings.apply(LanguageSettings.languageAtLaunch) + let startupAppearance = AppearanceSettings.resolvedMode() Self.applyAppearance(startupAppearance) _tabManager = StateObject(wrappedValue: TabManager()) @@ -2617,18 +2620,47 @@ enum AppearanceSettings { enum AppLanguage: String, CaseIterable, Identifiable { case system case en + case ar + case bs + case zhHans = "zh-Hans" + case zhHant = "zh-Hant" + case da + case de + case es + case fr + case it case ja + case ko + case nb + case pl + case ptBR = "pt-BR" + case ru + case th + case tr var id: String { rawValue } var displayName: String { switch self { - case .system: - return String(localized: "language.system", defaultValue: "System") - case .en: - return Locale.current.localizedString(forLanguageCode: "en") ?? "English" - case .ja: - return Locale.current.localizedString(forLanguageCode: "ja") ?? "Japanese" + case .system: return String(localized: "language.system", defaultValue: "System") + case .en: return "English" + case .ar: return "\u{200E}العربية (Arabic)" + case .bs: return "Bosanski (Bosnian)" + case .zhHans: return "简体中文 (Chinese Simplified)" + case .zhHant: return "繁體中文 (Chinese Traditional)" + case .da: return "Dansk (Danish)" + case .de: return "Deutsch (German)" + case .es: return "Español (Spanish)" + case .fr: return "Français (French)" + case .it: return "Italiano (Italian)" + case .ja: return "日本語 (Japanese)" + case .ko: return "한국어 (Korean)" + case .nb: return "Norsk (Norwegian)" + case .pl: return "Polski (Polish)" + case .ptBR: return "Português (Brasil)" + case .ru: return "Русский (Russian)" + case .th: return "ไทย (Thai)" + case .tr: return "Türkçe (Turkish)" } } } @@ -2638,10 +2670,9 @@ enum LanguageSettings { static let defaultLanguage: AppLanguage = .system static func apply(_ language: AppLanguage) { - switch language { - case .system: + if language == .system { UserDefaults.standard.removeObject(forKey: "AppleLanguages") - case .en, .ja: + } else { UserDefaults.standard.set([language.rawValue], forKey: "AppleLanguages") } } @@ -3062,9 +3093,13 @@ struct SettingsView: View { .pickerStyle(.menu) .onChange(of: appLanguage) { newValue in guard !isResettingSettings else { return } - if let lang = AppLanguage(rawValue: newValue) { - LanguageSettings.apply(lang) - if newValue != LanguageSettings.languageAtLaunch.rawValue { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [self] in + // Re-check current value to handle rapid changes + let current = appLanguage + if let lang = AppLanguage(rawValue: current) { + LanguageSettings.apply(lang) + } + if current != LanguageSettings.languageAtLaunch.rawValue { showLanguageRestartAlert = true } } @@ -3905,6 +3940,9 @@ struct SettingsView: View { isResettingSettings = true appLanguage = LanguageSettings.defaultLanguage.rawValue LanguageSettings.apply(.system) + if appLanguage != LanguageSettings.languageAtLaunch.rawValue { + showLanguageRestartAlert = true + } appearanceMode = AppearanceSettings.defaultMode.rawValue appIconMode = AppIconSettings.defaultMode.rawValue AppIconSettings.applyIcon(.automatic) From 34989e8ad0d81d46a6efd6ee5557601ba87a6bed Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 4 Mar 2026 18:36:26 -0800 Subject: [PATCH 079/232] Fix portal browser click focus after workspace switch (#908) --- Sources/AppDelegate.swift | 6 ++ Sources/BrowserWindowPortal.swift | 6 ++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 61 +++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index ab3f5579..acd7419f 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -9147,6 +9147,12 @@ private extension NSWindow { if let eventWindow = event.window, eventWindow !== window { return nil } + if let portalWebView = BrowserWindowPortalRegistry.webViewAtWindowPoint( + event.locationInWindow, + in: window + ) as? CmuxWebView { + return portalWebView + } guard let hitView = cmuxHitViewForCurrentEvent(in: window, event: event) else { return nil } diff --git a/Sources/BrowserWindowPortal.swift b/Sources/BrowserWindowPortal.swift index 1a5ea166..d53e1a71 100644 --- a/Sources/BrowserWindowPortal.swift +++ b/Sources/BrowserWindowPortal.swift @@ -1196,6 +1196,12 @@ enum BrowserWindowPortalRegistry { portalsByWindowId[windowId]?.detachWebView(withId: webViewId) } + static func webViewAtWindowPoint(_ windowPoint: NSPoint, in window: NSWindow) -> WKWebView? { + let windowId = ObjectIdentifier(window) + guard let portal = portalsByWindowId[windowId] else { return nil } + return portal.webViewAtWindowPoint(windowPoint) + } + #if DEBUG static func debugPortalCount() -> Int { portalsByWindowId.count diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 400ab90f..5aecfac8 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -411,6 +411,67 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase { XCTAssertFalse(window.makeFirstResponder(descendant), "Expected pointer bypass to be limited to click context") } + @MainActor + func testWindowFirstResponderGuardAllowsPointerInitiatedClickFocusForPortalHostedWebView() { + _ = NSApplication.shared + AppDelegate.installWindowResponderSwizzlesForTesting() + + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 640, height: 420), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + let container = NSView(frame: window.contentRect(forFrameRect: window.frame)) + window.contentView = container + + let anchor = NSView(frame: NSRect(x: 80, y: 60, width: 240, height: 150)) + container.addSubview(anchor) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + let descendant = FirstResponderView(frame: NSRect(x: 0, y: 0, width: 10, height: 10)) + webView.addSubview(descendant) + + window.makeKeyAndOrderFront(nil) + container.layoutSubtreeIfNeeded() + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + BrowserWindowPortalRegistry.bind(webView: webView, to: anchor, visibleInUI: true, zPriority: 1) + BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) + + defer { + BrowserWindowPortalRegistry.detach(webView: webView) + AppDelegate.clearWindowFirstResponderGuardTesting() + window.orderOut(nil) + } + + webView.allowsFirstResponderAcquisition = false + _ = window.makeFirstResponder(nil) + XCTAssertFalse(window.makeFirstResponder(descendant), "Expected blocked focus without pointer click context") + + let timestamp = ProcessInfo.processInfo.systemUptime + let pointerPointInContent = NSPoint(x: anchor.frame.midX, y: anchor.frame.midY) + let pointerPointInWindow = container.convert(pointerPointInContent, to: nil) + let pointerDownEvent = NSEvent.mouseEvent( + with: .leftMouseDown, + location: pointerPointInWindow, + modifierFlags: [], + timestamp: timestamp, + windowNumber: window.windowNumber, + context: nil, + eventNumber: 1, + clickCount: 1, + pressure: 1.0 + ) + XCTAssertNotNil(pointerDownEvent) + + AppDelegate.setWindowFirstResponderGuardTesting(currentEvent: pointerDownEvent, hitView: nil) + _ = window.makeFirstResponder(nil) + XCTAssertTrue( + window.makeFirstResponder(descendant), + "Expected portal-hosted pointer click context to bypass blocked policy" + ) + } + @MainActor func testWindowFirstResponderGuardAvoidsTextViewDelegateLookupForWebViewResolution() { _ = NSApplication.shared From 0a490b0e03885ae054670c67028c8b196e683e6c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:52:57 -0800 Subject: [PATCH 080/232] Fix Claude wrapper hook errors when cmux socket is stale (#868) * Fix Claude wrapper hook injection when cmux socket is stale * Harden socket listener lifecycle and rearm policy * Unset CLAUDECODE in stale-socket passthrough * Harden listener cleanup and bound claude ping probe * Guard socket unlink during listener startup window --- CLI/cmux.swift | 4 +- Resources/bin/claude | 34 +- Sources/TerminalController.swift | 422 ++++++++++++++++++++---- cmuxTests/SessionPersistenceTests.swift | 138 ++++++++ tests/test_claude_wrapper_hooks.py | 186 +++++++++++ 5 files changed, 716 insertions(+), 68 deletions(-) create mode 100644 tests/test_claude_wrapper_hooks.py diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 8d218262..4475f341 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -672,7 +672,9 @@ final class SocketClient { Darwin.close(socketFD) socketFD = -1 - let error = CLIError(message: "Failed to connect to socket at \(path)") + let error = CLIError( + message: "Failed to connect to socket at \(path) (\(String(cString: strerror(connectErrno))), errno \(connectErrno))" + ) lastError = error if Self.retriableConnectErrnos.contains(connectErrno), Date() < deadline { Thread.sleep(forTimeInterval: Self.connectRetryIntervalSeconds) diff --git a/Resources/bin/claude b/Resources/bin/claude index 2205fe3c..02939248 100755 --- a/Resources/bin/claude +++ b/Resources/bin/claude @@ -18,8 +18,36 @@ find_real_claude() { return 1 } -# Pass through if not in a cmux terminal or hooks are disabled. -if [[ -z "$CMUX_SURFACE_ID" || "$CMUX_CLAUDE_HOOKS_DISABLED" == "1" ]]; then +# Return 0 only when CMUX_SOCKET_PATH points to a live cmux socket. +cmux_socket_available() { + local socket="${CMUX_SOCKET_PATH:-}" + [[ -n "$socket" && -S "$socket" ]] || return 1 + + local self_dir cmux_bin + self_dir="$(cd "$(dirname "$0")" && pwd)" + cmux_bin="$self_dir/cmux" + [[ -x "$cmux_bin" ]] || cmux_bin="$(command -v cmux || true)" + [[ -n "$cmux_bin" ]] || return 1 + + # Keep stale/hung socket checks bounded so claude startup does not block + # behind the CLI default timeout (15s). + CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC=0.75 \ + "$cmux_bin" --socket "$socket" ping >/dev/null 2>&1 +} + +# Pass through if not in a cmux terminal, hooks are disabled, or the cmux +# socket is unavailable (stale env / app not running). +IN_CMUX=0 +if [[ -n "$CMUX_SURFACE_ID" ]]; then + IN_CMUX=1 +fi + +if [[ "$IN_CMUX" == "0" || "$CMUX_CLAUDE_HOOKS_DISABLED" == "1" ]] || ! cmux_socket_available; then + # In cmux-launched shells, preserve old behavior and always clear nested + # Claude session markers, even when we must pass through due to stale socket. + if [[ "$IN_CMUX" == "1" ]]; then + unset CLAUDECODE + fi REAL_CLAUDE="$(find_real_claude)" || { echo "Error: claude not found in PATH" >&2; exit 127; } exec "$REAL_CLAUDE" "$@" fi @@ -50,7 +78,7 @@ done # Build hooks settings JSON. # Claude Code merges --settings additively with the user's own settings.json. -HOOKS_JSON='{"hooks":{"SessionStart":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook session-start","timeout":10}]}],"Stop":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook stop","timeout":10}]}],"Notification":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook notification","timeout":10}]}],"UserPromptSubmit":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook prompt-submit","timeout":10}]}]}}' +HOOKS_JSON='{"hooks":{"SessionStart":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook session-start","timeout":10}]}],"Stop":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook stop","timeout":10}]}],"Notification":[{"matcher":"","hooks":[{"type":"command","command":"cmux claude-hook notification","timeout":10}]}]}}' if [[ "$SKIP_SESSION_ID" == true ]]; then exec "$REAL_CLAUDE" --settings "$HOOKS_JSON" "$@" diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 43995ccb..4f199842 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -34,6 +34,11 @@ class TerminalController { private nonisolated(unsafe) var serverSocket: Int32 = -1 private nonisolated(unsafe) var isRunning = false private nonisolated(unsafe) var acceptLoopAlive = false + private nonisolated(unsafe) var activeAcceptLoopGeneration: UInt64 = 0 + private nonisolated(unsafe) var nextAcceptLoopGeneration: UInt64 = 0 + private nonisolated(unsafe) var pendingAcceptLoopRearmGeneration: UInt64? + private nonisolated(unsafe) var listenerStartInProgress = false + private nonisolated let listenerStateLock = NSLock() private var clientHandlers: [Int32: Thread] = [:] private var tabManager: TabManager? private var accessMode: SocketControlMode = .cmuxOnly @@ -41,6 +46,20 @@ class TerminalController { private nonisolated(unsafe) static var socketCommandPolicyDepth: Int = 0 private nonisolated(unsafe) static var socketCommandFocusAllowanceStack: [Bool] = [] private nonisolated static let socketCommandPolicyLock = NSLock() + private nonisolated static let socketListenBacklog: Int32 = 128 + private nonisolated static let acceptFailureBaseBackoffMs = 10 + private nonisolated static let acceptFailureMaxBackoffMs = 5_000 + private nonisolated static let acceptFailureMinimumRearmDelayMs = 100 + private nonisolated static let acceptFailureRearmThreshold = 50 + + private struct ListenerStateSnapshot { + let socketPath: String + let serverSocket: Int32 + let isRunning: Bool + let acceptLoopAlive: Bool + let activeGeneration: UInt64 + let pendingRearmGeneration: UInt64? + } private static let focusIntentV1Commands: Set = [ "focus_window", @@ -127,6 +146,31 @@ class TerminalController { private init() {} + private nonisolated func withListenerState(_ body: () -> T) -> T { + listenerStateLock.lock() + defer { listenerStateLock.unlock() } + return body() + } + + private nonisolated func listenerStateSnapshot() -> ListenerStateSnapshot { + withListenerState { + ListenerStateSnapshot( + socketPath: socketPath, + serverSocket: serverSocket, + isRunning: isRunning, + acceptLoopAlive: acceptLoopAlive, + activeGeneration: activeAcceptLoopGeneration, + pendingRearmGeneration: pendingAcceptLoopRearmGeneration + ) + } + } + + private nonisolated func shouldContinueAcceptLoop(generation: UInt64) -> Bool { + withListenerState { + isRunning && generation == activeAcceptLoopGeneration + } + } + nonisolated static func shouldSuppressSocketCommandActivation() -> Bool { socketCommandPolicyLock.lock() defer { socketCommandPolicyLock.unlock() } @@ -369,12 +413,14 @@ class TerminalController { errnoCode: Int32? = nil, extra: [String: Any] = [:] ) -> [String: Any] { + let snapshot = listenerStateSnapshot() var data: [String: Any] = [ "stage": stage, - "path": socketPath, - "isRunning": isRunning ? 1 : 0, - "acceptLoopAlive": acceptLoopAlive ? 1 : 0, - "serverSocket": Int(serverSocket) + "path": snapshot.socketPath, + "isRunning": snapshot.isRunning ? 1 : 0, + "acceptLoopAlive": snapshot.acceptLoopAlive ? 1 : 0, + "serverSocket": Int(snapshot.serverSocket), + "activeGeneration": snapshot.activeGeneration ] if let errnoCode { data["errno"] = Int(errnoCode) @@ -397,27 +443,108 @@ class TerminalController { sentryCaptureError(message, category: "socket", data: data, contextKey: "socket_listener") } + nonisolated static func acceptErrorClassification(errnoCode: Int32) -> String { + switch errnoCode { + case EINTR, ECONNABORTED, EAGAIN, EWOULDBLOCK: + return "immediate_retry" + case EMFILE, ENFILE, ENOBUFS, ENOMEM: + return "resource_pressure" + case EBADF, EINVAL, ENOTSOCK: + return "fatal" + default: + return "retry_with_backoff" + } + } + + nonisolated static func shouldRearmListenerForAcceptError(errnoCode: Int32) -> Bool { + acceptErrorClassification(errnoCode: errnoCode) == "fatal" + } + + nonisolated static func shouldRetryAcceptImmediately(errnoCode: Int32) -> Bool { + acceptErrorClassification(errnoCode: errnoCode) == "immediate_retry" + } + + nonisolated static func shouldRearmForConsecutiveAcceptFailures(consecutiveFailures: Int) -> Bool { + consecutiveFailures >= acceptFailureRearmThreshold + } + + nonisolated static func acceptFailureBackoffMilliseconds(consecutiveFailures: Int) -> Int { + guard consecutiveFailures > 0 else { return 0 } + var delay = acceptFailureBaseBackoffMs + var remaining = consecutiveFailures - 1 + while remaining > 0 { + if delay >= acceptFailureMaxBackoffMs { + return acceptFailureMaxBackoffMs + } + delay = min(delay * 2, acceptFailureMaxBackoffMs) + remaining -= 1 + } + return delay + } + + nonisolated static func acceptFailureRearmDelayMilliseconds(consecutiveFailures: Int) -> Int { + max( + acceptFailureBackoffMilliseconds(consecutiveFailures: consecutiveFailures), + acceptFailureMinimumRearmDelayMs + ) + } + + nonisolated static func shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: Int) -> Bool { + guard consecutiveFailures > 0 else { return false } + if consecutiveFailures <= 3 { + return true + } + return (consecutiveFailures & (consecutiveFailures - 1)) == 0 + } + + nonisolated static func shouldUnlinkSocketPathAfterAcceptLoopCleanup( + pathMatches: Bool, + isRunning: Bool, + activeGeneration: UInt64, + listenerStartInProgress: Bool + ) -> Bool { + guard pathMatches else { return false } + guard !listenerStartInProgress else { return false } + return !isRunning && activeGeneration == 0 + } + func start(tabManager: TabManager, socketPath: String, accessMode: SocketControlMode) { self.tabManager = tabManager self.accessMode = accessMode - if isRunning { - if self.socketPath == socketPath && acceptLoopAlive { - self.accessMode = accessMode - applySocketPermissions() - return - } + let existing = withListenerState { + (isRunning: isRunning, socketPath: self.socketPath, acceptLoopAlive: acceptLoopAlive) + } + + if existing.isRunning && existing.socketPath == socketPath && existing.acceptLoopAlive { + self.accessMode = accessMode + applySocketPermissions() + return + } + + if existing.isRunning { stop() } - self.socketPath = socketPath + withListenerState { + self.socketPath = socketPath + listenerStartInProgress = true + } + var listenerActivated = false + defer { + if !listenerActivated { + withListenerState { + listenerStartInProgress = false + } + } + } // Remove existing socket file unlink(socketPath) // Create socket - serverSocket = socket(AF_UNIX, SOCK_STREAM, 0) - guard serverSocket >= 0 else { + let newServerSocket = socket(AF_UNIX, SOCK_STREAM, 0) + guard newServerSocket >= 0 else { let errnoCode = errno print("TerminalController: Failed to create socket") reportSocketListenerFailure( @@ -440,14 +567,14 @@ class TerminalController { let bindResult = withUnsafePointer(to: &addr) { ptr in ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in - bind(serverSocket, sockaddrPtr, socklen_t(MemoryLayout.size)) + bind(newServerSocket, sockaddrPtr, socklen_t(MemoryLayout.size)) } } guard bindResult >= 0 else { let errnoCode = errno print("TerminalController: Failed to bind socket") - close(serverSocket) + close(newServerSocket) reportSocketListenerFailure( message: "socket.listener.start.failed", stage: "bind", @@ -459,10 +586,10 @@ class TerminalController { applySocketPermissions() // Listen - guard listen(serverSocket, 5) >= 0 else { + guard listen(newServerSocket, Self.socketListenBacklog) >= 0 else { let errnoCode = errno print("TerminalController: Failed to listen on socket") - close(serverSocket) + close(newServerSocket) reportSocketListenerFailure( message: "socket.listener.start.failed", stage: "listen", @@ -471,14 +598,27 @@ class TerminalController { return } - isRunning = true + let generation = withListenerState { + isRunning = true + pendingAcceptLoopRearmGeneration = nil + nextAcceptLoopGeneration &+= 1 + let generation = nextAcceptLoopGeneration + activeAcceptLoopGeneration = generation + serverSocket = newServerSocket + listenerStartInProgress = false + return generation + } + listenerActivated = true + let listenerSocket = newServerSocket print("TerminalController: Listening on \(socketPath)") sentryBreadcrumb( "socket.listener.listening", category: "socket", data: [ "path": socketPath, - "mode": accessMode.rawValue + "mode": accessMode.rawValue, + "generation": generation, + "backlog": Self.socketListenBacklog ] ) @@ -503,40 +643,65 @@ class TerminalController { // Accept connections in background thread Thread.detachNewThread { [weak self] in - self?.acceptLoop() + self?.acceptLoop(listenerSocket: listenerSocket, generation: generation) } } nonisolated func socketListenerHealth(expectedSocketPath: String) -> SocketListenerHealth { - let running = isRunning - let loopAlive = acceptLoopAlive - let pathMatches = socketPath == expectedSocketPath + let snapshot = listenerStateSnapshot() + let pathMatches = snapshot.socketPath == expectedSocketPath var st = stat() let exists = lstat(expectedSocketPath, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK return SocketListenerHealth( - isRunning: running, - acceptLoopAlive: loopAlive, + isRunning: snapshot.isRunning, + acceptLoopAlive: snapshot.acceptLoopAlive, socketPathMatches: pathMatches, socketPathExists: exists ) } nonisolated func stop() { - isRunning = false - if serverSocket >= 0 { - close(serverSocket) + let (socketToClose, socketPathToUnlink) = withListenerState { + isRunning = false + acceptLoopAlive = false + pendingAcceptLoopRearmGeneration = nil + listenerStartInProgress = false + nextAcceptLoopGeneration &+= 1 + activeAcceptLoopGeneration = 0 + let socketToClose = serverSocket serverSocket = -1 + return (socketToClose, socketPath) + } + if socketToClose >= 0 { + close(socketToClose) + } + unlink(socketPathToUnlink) + } + + private nonisolated func unlinkSocketPathIfListenerStillInactive(_ path: String) { + let shouldUnlink = withListenerState { + Self.shouldUnlinkSocketPathAfterAcceptLoopCleanup( + pathMatches: socketPath == path, + isRunning: isRunning, + activeGeneration: activeAcceptLoopGeneration, + listenerStartInProgress: listenerStartInProgress + ) + } + if shouldUnlink { + unlink(path) } - unlink(socketPath) } private func applySocketPermissions() { let permissions = mode_t(accessMode.socketFilePermissions) - if chmod(socketPath, permissions) != 0 { + let currentSocketPath = withListenerState { socketPath } + if chmod(currentSocketPath, permissions) != 0 { let errnoCode = errno - print("TerminalController: Failed to set socket permissions to \(String(permissions, radix: 8)) for \(socketPath)") + print( + "TerminalController: Failed to set socket permissions to \(String(permissions, radix: 8)) for \(currentSocketPath)" + ) sentryBreadcrumb( "socket.listener.permissions.failed", category: "socket", @@ -640,27 +805,77 @@ class TerminalController { return nil } - private nonisolated func acceptLoop() { - acceptLoopAlive = true + private nonisolated func acceptLoop(listenerSocket: Int32, generation: UInt64) { + let armedAcceptLoop = withListenerState { + guard generation == activeAcceptLoopGeneration else { return false } + acceptLoopAlive = true + return true + } + guard armedAcceptLoop else { + return + } + sentryBreadcrumb( "socket.listener.accept_loop.started", category: "socket", - data: socketListenerEventData(stage: "accept_loop_start") + data: socketListenerEventData( + stage: "accept_loop_start", + extra: [ + "generation": generation, + "listenerSocket": Int(listenerSocket) + ] + ) ) + var exitReason = "stopped" var lastAcceptErrno: Int32? + var lastAcceptErrnoClass = "none" + var rearmRequested = false + defer { - if isRunning && exitReason == "stopped" { - exitReason = "unexpected_loop_exit" + let cleanup = withListenerState { + guard generation == activeAcceptLoopGeneration else { + return (shouldCaptureExit: false, socketToClose: Int32(-1), pathToUnlink: nil as String?) + } + + if isRunning && exitReason == "stopped" { + exitReason = "unexpected_loop_exit" + } + let shouldCaptureExit = exitReason != "stopped" + + acceptLoopAlive = false + isRunning = false + activeAcceptLoopGeneration = 0 + + var socketToClose: Int32 = -1 + var pathToUnlink: String? + if serverSocket == listenerSocket { + socketToClose = serverSocket + serverSocket = -1 + if shouldCaptureExit { + pathToUnlink = socketPath + } + } + return (shouldCaptureExit, socketToClose, pathToUnlink) } - let shouldCaptureExit = exitReason != "stopped" - acceptLoopAlive = false - isRunning = false - if shouldCaptureExit { + + if cleanup.socketToClose >= 0 { + close(cleanup.socketToClose) + } + if let pathToUnlink = cleanup.pathToUnlink { + unlinkSocketPathIfListenerStillInactive(pathToUnlink) + } + + if cleanup.shouldCaptureExit { let data = socketListenerEventData( stage: "accept_loop_exit", errnoCode: lastAcceptErrno, - extra: ["reason": exitReason] + extra: [ + "reason": exitReason, + "generation": generation, + "errnoClass": lastAcceptErrnoClass, + "rearmRequested": rearmRequested ? 1 : 0 + ] ) sentryBreadcrumb("socket.listener.accept_loop.exited", category: "socket", data: data) sentryCaptureError( @@ -673,39 +888,81 @@ class TerminalController { } var consecutiveFailures = 0 - while isRunning { + + while shouldContinueAcceptLoop(generation: generation) { var clientAddr = sockaddr_un() var clientAddrLen = socklen_t(MemoryLayout.size) let clientSocket = withUnsafeMutablePointer(to: &clientAddr) { ptr in ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in - accept(serverSocket, sockaddrPtr, &clientAddrLen) + accept(listenerSocket, sockaddrPtr, &clientAddrLen) } } guard clientSocket >= 0 else { - if isRunning { - let errnoCode = errno - lastAcceptErrno = errnoCode - consecutiveFailures += 1 - print("TerminalController: Accept failed (\(consecutiveFailures) consecutive)") - if consecutiveFailures == 1 || consecutiveFailures % 10 == 0 { - sentryBreadcrumb( - "socket.listener.accept.failed", - category: "socket", - data: socketListenerEventData( - stage: "accept", - errnoCode: errnoCode, - extra: ["consecutiveFailures": consecutiveFailures] - ) + if !shouldContinueAcceptLoop(generation: generation) { + exitReason = "stopped" + break + } + + let errnoCode = errno + lastAcceptErrno = errnoCode + let errnoClass = Self.acceptErrorClassification(errnoCode: errnoCode) + lastAcceptErrnoClass = errnoClass + + if Self.shouldRetryAcceptImmediately(errnoCode: errnoCode) { + continue + } + + consecutiveFailures += 1 + let backoffMs = Self.acceptFailureBackoffMilliseconds( + consecutiveFailures: consecutiveFailures + ) + let rearmDelayMs = Self.acceptFailureRearmDelayMilliseconds( + consecutiveFailures: consecutiveFailures + ) + + if Self.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: consecutiveFailures) { + sentryBreadcrumb( + "socket.listener.accept.failed", + category: "socket", + data: socketListenerEventData( + stage: "accept", + errnoCode: errnoCode, + extra: [ + "consecutiveFailures": consecutiveFailures, + "generation": generation, + "errnoClass": errnoClass, + "backoffMs": backoffMs + ] ) + ) + } + + let shouldRearmForFatalErrno = Self.shouldRearmListenerForAcceptError(errnoCode: errnoCode) + let shouldRearmForPersistentFailures = Self.shouldRearmForConsecutiveAcceptFailures( + consecutiveFailures: consecutiveFailures + ) + + if shouldRearmForFatalErrno || shouldRearmForPersistentFailures { + exitReason = shouldRearmForFatalErrno + ? "fatal_accept_error" + : "persistent_accept_failures" + rearmRequested = true + withListenerState { + pendingAcceptLoopRearmGeneration = generation } - if consecutiveFailures >= 50 { - print("TerminalController: Too many consecutive accept failures, exiting accept loop") - exitReason = "too_many_accept_failures" - break - } - usleep(10_000) // 10ms backoff + scheduleListenerRearm( + generation: generation, + errnoCode: errnoCode, + consecutiveFailures: consecutiveFailures, + delayMs: rearmDelayMs + ) + break + } + + if backoffMs > 0 { + usleep(useconds_t(backoffMs * 1_000)) } continue } @@ -724,6 +981,43 @@ class TerminalController { } } + private nonisolated func scheduleListenerRearm( + generation: UInt64, + errnoCode: Int32, + consecutiveFailures: Int, + delayMs: Int + ) { + let deadline = DispatchTime.now() + .milliseconds(delayMs) + DispatchQueue.main.asyncAfter(deadline: deadline) { [weak self] in + guard let self else { return } + guard let tabManager = self.tabManager else { return } + guard let restartPath = self.withListenerState({ () -> String? in + guard self.pendingAcceptLoopRearmGeneration == generation else { return nil } + self.pendingAcceptLoopRearmGeneration = nil + return self.socketPath + }) else { return } + + let restartMode = self.accessMode + + sentryBreadcrumb( + "socket.listener.rearm.requested", + category: "socket", + data: self.socketListenerEventData( + stage: "accept_rearm", + errnoCode: errnoCode, + extra: [ + "generation": generation, + "consecutiveFailures": consecutiveFailures, + "rearmDelayMs": delayMs + ] + ) + ) + + self.stop() + self.start(tabManager: tabManager, socketPath: restartPath, accessMode: restartMode) + } + } + private func handleClient(_ socket: Int32, peerPid: pid_t? = nil) { defer { close(socket) } @@ -760,7 +1054,7 @@ class TerminalController { var pending = "" var authenticated = false - while isRunning { + while withListenerState({ isRunning }) { let bytesRead = read(socket, &buffer, buffer.count - 1) guard bytesRead > 0 else { break } diff --git a/cmuxTests/SessionPersistenceTests.swift b/cmuxTests/SessionPersistenceTests.swift index ad9f5b3c..88d8f11c 100644 --- a/cmuxTests/SessionPersistenceTests.swift +++ b/cmuxTests/SessionPersistenceTests.swift @@ -733,3 +733,141 @@ final class SessionPersistenceTests: XCTestCase { ) } } + +final class SocketListenerAcceptPolicyTests: XCTestCase { + func testAcceptErrorClassificationBucketsExpectedErrnos() { + XCTAssertEqual( + TerminalController.acceptErrorClassification(errnoCode: EINTR), + "immediate_retry" + ) + XCTAssertEqual( + TerminalController.acceptErrorClassification(errnoCode: ECONNABORTED), + "immediate_retry" + ) + XCTAssertEqual( + TerminalController.acceptErrorClassification(errnoCode: EMFILE), + "resource_pressure" + ) + XCTAssertEqual( + TerminalController.acceptErrorClassification(errnoCode: ENOMEM), + "resource_pressure" + ) + XCTAssertEqual( + TerminalController.acceptErrorClassification(errnoCode: EBADF), + "fatal" + ) + XCTAssertEqual( + TerminalController.acceptErrorClassification(errnoCode: EINVAL), + "fatal" + ) + } + + func testAcceptErrorPolicySignalsRearmOnlyForFatalErrors() { + XCTAssertTrue(TerminalController.shouldRearmListenerForAcceptError(errnoCode: EBADF)) + XCTAssertTrue(TerminalController.shouldRearmListenerForAcceptError(errnoCode: ENOTSOCK)) + XCTAssertFalse(TerminalController.shouldRearmListenerForAcceptError(errnoCode: EMFILE)) + XCTAssertFalse(TerminalController.shouldRearmListenerForAcceptError(errnoCode: EINTR)) + } + + func testAcceptErrorPolicyRearmsAfterPersistentFailures() { + XCTAssertFalse(TerminalController.shouldRearmForConsecutiveAcceptFailures(consecutiveFailures: 0)) + XCTAssertFalse(TerminalController.shouldRearmForConsecutiveAcceptFailures(consecutiveFailures: 49)) + XCTAssertTrue(TerminalController.shouldRearmForConsecutiveAcceptFailures(consecutiveFailures: 50)) + XCTAssertTrue(TerminalController.shouldRearmForConsecutiveAcceptFailures(consecutiveFailures: 120)) + } + + func testAcceptFailureBackoffIsExponentialAndCapped() { + XCTAssertEqual( + TerminalController.acceptFailureBackoffMilliseconds(consecutiveFailures: 0), + 0 + ) + XCTAssertEqual( + TerminalController.acceptFailureBackoffMilliseconds(consecutiveFailures: 1), + 10 + ) + XCTAssertEqual( + TerminalController.acceptFailureBackoffMilliseconds(consecutiveFailures: 2), + 20 + ) + XCTAssertEqual( + TerminalController.acceptFailureBackoffMilliseconds(consecutiveFailures: 6), + 320 + ) + XCTAssertEqual( + TerminalController.acceptFailureBackoffMilliseconds(consecutiveFailures: 12), + 5_000 + ) + XCTAssertEqual( + TerminalController.acceptFailureBackoffMilliseconds(consecutiveFailures: 50), + 5_000 + ) + } + + func testAcceptFailureRearmDelayAppliesMinimumThrottle() { + XCTAssertEqual( + TerminalController.acceptFailureRearmDelayMilliseconds(consecutiveFailures: 0), + 100 + ) + XCTAssertEqual( + TerminalController.acceptFailureRearmDelayMilliseconds(consecutiveFailures: 1), + 100 + ) + XCTAssertEqual( + TerminalController.acceptFailureRearmDelayMilliseconds(consecutiveFailures: 2), + 100 + ) + XCTAssertEqual( + TerminalController.acceptFailureRearmDelayMilliseconds(consecutiveFailures: 6), + 320 + ) + XCTAssertEqual( + TerminalController.acceptFailureRearmDelayMilliseconds(consecutiveFailures: 12), + 5_000 + ) + } + + func testAcceptFailureBreadcrumbSamplingPrefersEarlyAndPowerOfTwoMilestones() { + XCTAssertTrue(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 1)) + XCTAssertTrue(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 2)) + XCTAssertTrue(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 3)) + XCTAssertFalse(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 5)) + XCTAssertTrue(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 8)) + XCTAssertFalse(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 9)) + XCTAssertTrue(TerminalController.shouldEmitAcceptFailureBreadcrumb(consecutiveFailures: 16)) + } + + func testAcceptLoopCleanupUnlinkPolicySkipsDuringListenerStartup() { + XCTAssertFalse( + TerminalController.shouldUnlinkSocketPathAfterAcceptLoopCleanup( + pathMatches: true, + isRunning: false, + activeGeneration: 0, + listenerStartInProgress: true + ) + ) + XCTAssertFalse( + TerminalController.shouldUnlinkSocketPathAfterAcceptLoopCleanup( + pathMatches: false, + isRunning: false, + activeGeneration: 0, + listenerStartInProgress: false + ) + ) + XCTAssertFalse( + TerminalController.shouldUnlinkSocketPathAfterAcceptLoopCleanup( + pathMatches: true, + isRunning: true, + activeGeneration: 7, + listenerStartInProgress: false + ) + ) + XCTAssertTrue( + TerminalController.shouldUnlinkSocketPathAfterAcceptLoopCleanup( + pathMatches: true, + isRunning: false, + activeGeneration: 0, + listenerStartInProgress: false + ) + ) + } +} diff --git a/tests/test_claude_wrapper_hooks.py b/tests/test_claude_wrapper_hooks.py new file mode 100644 index 00000000..7763bd76 --- /dev/null +++ b/tests/test_claude_wrapper_hooks.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +Regression tests for Resources/bin/claude wrapper hook injection. +""" + +from __future__ import annotations + +import json +import os +import shutil +import socket +import subprocess +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE_WRAPPER = ROOT / "Resources" / "bin" / "claude" + + +def make_executable(path: Path, content: str) -> None: + path.write_text(content, encoding="utf-8") + path.chmod(0o755) + + +def read_lines(path: Path) -> list[str]: + if not path.exists(): + return [] + return [line.rstrip("\n") for line in path.read_text(encoding="utf-8").splitlines()] + + +def parse_settings_arg(argv: list[str]) -> dict: + if "--settings" not in argv: + return {} + index = argv.index("--settings") + if index + 1 >= len(argv): + return {} + return json.loads(argv[index + 1]) + + +def run_wrapper(*, socket_state: str, argv: list[str]) -> tuple[int, list[str], list[str], str, str]: + with tempfile.TemporaryDirectory(prefix="cmux-claude-wrapper-test-") as td: + tmp = Path(td) + wrapper_dir = tmp / "wrapper-bin" + real_dir = tmp / "real-bin" + wrapper_dir.mkdir(parents=True, exist_ok=True) + real_dir.mkdir(parents=True, exist_ok=True) + + wrapper = wrapper_dir / "claude" + shutil.copy2(SOURCE_WRAPPER, wrapper) + wrapper.chmod(0o755) + + real_args_log = tmp / "real-args.log" + real_claudecode_log = tmp / "real-claudecode.log" + cmux_log = tmp / "cmux.log" + socket_path = str(tmp / "cmux.sock") + + make_executable( + real_dir / "claude", + """#!/usr/bin/env bash +set -euo pipefail +: > "$FAKE_REAL_ARGS_LOG" +printf '%s\\n' "${CLAUDECODE-__UNSET__}" > "$FAKE_REAL_CLAUDECODE_LOG" +for arg in "$@"; do + printf '%s\\n' "$arg" >> "$FAKE_REAL_ARGS_LOG" +done +""", + ) + + make_executable( + wrapper_dir / "cmux", + """#!/usr/bin/env bash +set -euo pipefail +printf '%s timeout=%s\\n' "$*" "${CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC-__UNSET__}" >> "$FAKE_CMUX_LOG" +if [[ "${1:-}" == "--socket" ]]; then + shift 2 +fi +if [[ "${1:-}" == "ping" ]]; then + if [[ "${FAKE_CMUX_PING_OK:-0}" == "1" ]]; then + exit 0 + fi + exit 1 +fi +exit 0 +""", + ) + + test_socket: socket.socket | None = None + if socket_state in {"live", "stale"}: + test_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + test_socket.bind(socket_path) + + env = os.environ.copy() + env["PATH"] = f"{wrapper_dir}:{real_dir}:/usr/bin:/bin" + env["CMUX_SURFACE_ID"] = "surface:test" + env["CMUX_SOCKET_PATH"] = socket_path + env["FAKE_REAL_ARGS_LOG"] = str(real_args_log) + env["FAKE_REAL_CLAUDECODE_LOG"] = str(real_claudecode_log) + env["FAKE_CMUX_LOG"] = str(cmux_log) + env["FAKE_CMUX_PING_OK"] = "1" if socket_state == "live" else "0" + env["CLAUDECODE"] = "nested-session-sentinel" + + try: + proc = subprocess.run( + ["claude", *argv], + cwd=tmp, + env=env, + capture_output=True, + text=True, + check=False, + ) + finally: + if test_socket is not None: + test_socket.close() + + claudecode_lines = read_lines(real_claudecode_log) + claudecode_value = claudecode_lines[0] if claudecode_lines else "" + return proc.returncode, read_lines(real_args_log), read_lines(cmux_log), proc.stderr.strip(), claudecode_value + + +def expect(condition: bool, message: str, failures: list[str]) -> None: + if not condition: + failures.append(message) + + +def test_live_socket_injects_supported_hooks(failures: list[str]) -> None: + code, real_argv, cmux_log, stderr, claudecode = run_wrapper(socket_state="live", argv=["hello"]) + expect(code == 0, f"live socket: wrapper exited {code}: {stderr}", failures) + expect("--settings" in real_argv, f"live socket: missing --settings in args: {real_argv}", failures) + expect("--session-id" in real_argv, f"live socket: missing --session-id in args: {real_argv}", failures) + expect(real_argv[-1] == "hello", f"live socket: expected original arg to pass through, got {real_argv}", failures) + expect(any(" ping" in line for line in cmux_log), f"live socket: expected cmux ping, got {cmux_log}", failures) + expect( + any("timeout=0.75" in line for line in cmux_log), + f"live socket: expected bounded ping timeout, got {cmux_log}", + failures, + ) + expect(claudecode == "__UNSET__", f"live socket: expected CLAUDECODE unset, got {claudecode!r}", failures) + + settings = parse_settings_arg(real_argv) + hooks = settings.get("hooks", {}) + expect(set(hooks.keys()) == {"SessionStart", "Stop", "Notification"}, f"unexpected hook keys: {hooks.keys()}", failures) + serialized = json.dumps(settings, sort_keys=True) + expect("UserPromptSubmit" not in serialized, "UserPromptSubmit hook should not be injected", failures) + expect("prompt-submit" not in serialized, "prompt-submit subcommand should not be injected", failures) + + +def test_missing_socket_skips_hook_injection(failures: list[str]) -> None: + code, real_argv, cmux_log, stderr, claudecode = run_wrapper(socket_state="missing", argv=["hello"]) + expect(code == 0, f"missing socket: wrapper exited {code}: {stderr}", failures) + expect(real_argv == ["hello"], f"missing socket: expected passthrough args, got {real_argv}", failures) + expect(cmux_log == [], f"missing socket: expected no cmux calls, got {cmux_log}", failures) + expect(claudecode == "__UNSET__", f"missing socket: expected CLAUDECODE unset, got {claudecode!r}", failures) + + +def test_stale_socket_skips_hook_injection(failures: list[str]) -> None: + code, real_argv, cmux_log, stderr, claudecode = run_wrapper(socket_state="stale", argv=["hello"]) + expect(code == 0, f"stale socket: wrapper exited {code}: {stderr}", failures) + expect(real_argv == ["hello"], f"stale socket: expected passthrough args, got {real_argv}", failures) + expect(any(" ping" in line for line in cmux_log), f"stale socket: expected cmux ping probe, got {cmux_log}", failures) + expect( + any("timeout=0.75" in line for line in cmux_log), + f"stale socket: expected bounded ping timeout, got {cmux_log}", + failures, + ) + expect(claudecode == "__UNSET__", f"stale socket: expected CLAUDECODE unset, got {claudecode!r}", failures) + + +def main() -> int: + failures: list[str] = [] + test_live_socket_injects_supported_hooks(failures) + test_missing_socket_skips_hook_injection(failures) + test_stale_socket_skips_hook_injection(failures) + + if failures: + print("FAIL: claude wrapper regression checks failed") + for failure in failures: + print(f"- {failure}") + return 1 + + print("PASS: claude wrapper hooks handle missing/stale sockets and inject only supported hooks") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 80baae355ac1e78717d48359153047542bfd6e9c Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 4 Mar 2026 18:56:22 -0800 Subject: [PATCH 081/232] Add browser camera permission support and metadata (#760) (#913) --- Resources/Info.plist | 2 ++ Resources/InfoPlist.xcstrings | 17 +++++++++++++++++ Sources/Panels/BrowserPanel.swift | 11 +++++++++++ cmux.entitlements | 2 ++ tests/test_microphone_access_metadata.py | 21 ++++++++++++++++++--- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/Resources/Info.plist b/Resources/Info.plist index ba335119..c2badb5d 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -28,6 +28,8 @@ NSMicrophoneUsageDescription A program running within cmux would like to use your microphone. + NSCameraUsageDescription + A program running within cmux would like to use your camera. NSPrincipalClass NSApplication NSServices diff --git a/Resources/InfoPlist.xcstrings b/Resources/InfoPlist.xcstrings index 00297777..baeee708 100644 --- a/Resources/InfoPlist.xcstrings +++ b/Resources/InfoPlist.xcstrings @@ -2,6 +2,23 @@ "sourceLanguage": "en", "version": "1.0", "strings": { + "NSCameraUsageDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A program running within cmux would like to use your camera." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux 内で実行中のプログラムがカメラの使用を求めています。" + } + } + } + }, "NSMicrophoneUsageDescription": { "extractionState": "manual", "localizations": { diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 29e12036..81eaf525 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1533,6 +1533,7 @@ final class BrowserPanel: Panel, ObservableObject { private static func makeWebView() -> CmuxWebView { let config = WKWebViewConfiguration() config.processPool = BrowserPanel.sharedProcessPool + config.mediaTypesRequiringUserActionForPlayback = [] // Ensure browser cookies/storage persist across navigations and launches. // This reduces repeated consent/bot-challenge flows on sites like Google. config.websiteDataStore = .default() @@ -3652,6 +3653,16 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { } } + func webView( + _ webView: WKWebView, + requestMediaCapturePermissionFor origin: WKSecurityOrigin, + initiatedByFrame frame: WKFrameInfo, + type: WKMediaCaptureType, + decisionHandler: @escaping (WKPermissionDecision) -> Void + ) { + decisionHandler(.prompt) + } + func webView( _ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, diff --git a/cmux.entitlements b/cmux.entitlements index ec456f35..09e191a5 100644 --- a/cmux.entitlements +++ b/cmux.entitlements @@ -8,6 +8,8 @@ com.apple.security.cs.allow-jit + com.apple.security.device.camera + com.apple.security.device.audio-input com.apple.security.automation.apple-events diff --git a/tests/test_microphone_access_metadata.py b/tests/test_microphone_access_metadata.py index 595aa542..69045141 100644 --- a/tests/test_microphone_access_metadata.py +++ b/tests/test_microphone_access_metadata.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Regression test: cmux advertises and allows microphone access.""" +"""Regression test: cmux advertises media-capture access metadata.""" from __future__ import annotations @@ -36,6 +36,7 @@ def main() -> int: entitlements = load_plist(repo_root / "cmux.entitlements", failures) mic_usage = info.get("NSMicrophoneUsageDescription") + camera_usage = info.get("NSCameraUsageDescription") if not isinstance(mic_usage, str) or not mic_usage.strip(): failures.append( "Resources/Info.plist must define a non-empty NSMicrophoneUsageDescription" @@ -50,13 +51,27 @@ def main() -> int: "cmux.entitlements must set com.apple.security.device.audio-input to true" ) + if not isinstance(camera_usage, str) or not camera_usage.strip(): + failures.append( + "Resources/Info.plist must define a non-empty NSCameraUsageDescription" + ) + elif camera_usage.strip() != "A program running within cmux would like to use your camera.": + failures.append( + "Resources/Info.plist NSCameraUsageDescription should match the Ghostty-style wording" + ) + + if entitlements.get("com.apple.security.device.camera") is not True: + failures.append( + "cmux.entitlements must set com.apple.security.device.camera to true" + ) + if failures: - print("FAIL: microphone access metadata regression(s) detected") + print("FAIL: media-capture metadata regression(s) detected") for failure in failures: print(f"- {failure}") return 1 - print("PASS: microphone usage description and entitlement are present") + print("PASS: microphone/camera usage descriptions and entitlements are present") return 0 From 39a0da2b7ea1e2e1365444ef6ce3b999c075a1e1 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 4 Mar 2026 19:13:19 -0800 Subject: [PATCH 082/232] Fix sidebar branch refresh during agent-driven git checkout (#671) --- .../cmux-zsh-integration.zsh | 81 ++++++++++++++++--- tests/test_sidebar_cwd_git.py | 15 ++++ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/Resources/shell-integration/cmux-zsh-integration.zsh b/Resources/shell-integration/cmux-zsh-integration.zsh index a9f1137a..988be2f1 100644 --- a/Resources/shell-integration/cmux-zsh-integration.zsh +++ b/Resources/shell-integration/cmux-zsh-integration.zsh @@ -46,6 +46,7 @@ typeset -g _CMUX_GIT_FORCE=0 typeset -g _CMUX_GIT_HEAD_LAST_PWD="" typeset -g _CMUX_GIT_HEAD_PATH="" typeset -g _CMUX_GIT_HEAD_MTIME=0 +typeset -g _CMUX_GIT_HEAD_WATCH_PID="" typeset -g _CMUX_HAVE_ZSTAT=0 typeset -g _CMUX_PR_LAST_PWD="" typeset -g _CMUX_PR_LAST_RUN=0 @@ -148,6 +149,65 @@ _cmux_ports_kick() { } >/dev/null 2>&1 &! } +_cmux_report_git_branch_for_path() { + local repo_path="$1" + [[ -n "$repo_path" ]] || return 0 + [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 + [[ -n "$CMUX_TAB_ID" ]] || return 0 + [[ -n "$CMUX_PANEL_ID" ]] || return 0 + + local branch dirty_opt="" first + branch="$(git -C "$repo_path" branch --show-current 2>/dev/null)" + if [[ -n "$branch" ]]; then + first="$(git -C "$repo_path" status --porcelain -uno 2>/dev/null | head -1)" + [[ -n "$first" ]] && dirty_opt="--status=dirty" + _cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" + else + _cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" + fi +} + +_cmux_stop_git_head_watch() { + if [[ -n "$_CMUX_GIT_HEAD_WATCH_PID" ]]; then + kill "$_CMUX_GIT_HEAD_WATCH_PID" >/dev/null 2>&1 || true + _CMUX_GIT_HEAD_WATCH_PID="" + fi +} + +_cmux_start_git_head_watch() { + [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 + [[ -n "$CMUX_TAB_ID" ]] || return 0 + [[ -n "$CMUX_PANEL_ID" ]] || return 0 + + local watch_pwd="$PWD" + local watch_head_path + watch_head_path="$(_cmux_git_resolve_head_path 2>/dev/null || true)" + [[ -n "$watch_head_path" ]] || return 0 + + local watch_head_mtime + watch_head_mtime="$(_cmux_git_head_mtime "$watch_head_path" 2>/dev/null || echo 0)" + + _CMUX_GIT_HEAD_LAST_PWD="$watch_pwd" + _CMUX_GIT_HEAD_PATH="$watch_head_path" + _CMUX_GIT_HEAD_MTIME="$watch_head_mtime" + + _cmux_stop_git_head_watch + { + local last_mtime="$watch_head_mtime" + while true; do + sleep 1 + + local mtime + mtime="$(_cmux_git_head_mtime "$watch_head_path" 2>/dev/null || echo 0)" + if [[ -n "$mtime" && "$mtime" != 0 && "$mtime" != "$last_mtime" ]]; then + last_mtime="$mtime" + _cmux_report_git_branch_for_path "$watch_pwd" + fi + done + } >/dev/null 2>&1 &! + _CMUX_GIT_HEAD_WATCH_PID=$! +} + _cmux_preexec() { if [[ -z "$_CMUX_TTY_NAME" ]]; then local t @@ -169,9 +229,12 @@ _cmux_preexec() { # Register TTY + kick batched port scan for foreground commands (servers). _cmux_report_tty_once _cmux_ports_kick + _cmux_start_git_head_watch } _cmux_precmd() { + _cmux_stop_git_head_watch + # Skip if socket doesn't exist yet [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 [[ -n "$CMUX_TAB_ID" ]] || return 0 @@ -227,6 +290,8 @@ _cmux_precmd() { fi # Git branch/dirty: update immediately on directory change, otherwise every ~3s. + # While a foreground command is running, _cmux_start_git_head_watch probes HEAD + # once per second so agent-initiated git checkouts still surface quickly. local should_git=0 # Git branch can change without a `git ...`-prefixed command (aliases like `gco`, @@ -280,16 +345,7 @@ _cmux_precmd() { _CMUX_GIT_LAST_PWD="$pwd" _CMUX_GIT_LAST_RUN=$now { - local branch dirty_opt="" - branch=$(git branch --show-current 2>/dev/null) - if [[ -n "$branch" ]]; then - local first - first=$(git status --porcelain -uno 2>/dev/null | head -1) - [[ -n "$first" ]] && dirty_opt="--status=dirty" - _cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" - else - _cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" - fi + _cmux_report_git_branch_for_path "$pwd" } >/dev/null 2>&1 &! _CMUX_GIT_JOB_PID=$! _CMUX_GIT_JOB_STARTED_AT=$now @@ -387,7 +443,12 @@ _cmux_fix_path() { add-zsh-hook -d precmd _cmux_fix_path } +_cmux_zshexit() { + _cmux_stop_git_head_watch +} + autoload -Uz add-zsh-hook add-zsh-hook preexec _cmux_preexec add-zsh-hook precmd _cmux_precmd add-zsh-hook precmd _cmux_fix_path +add-zsh-hook zshexit _cmux_zshexit diff --git a/tests/test_sidebar_cwd_git.py b/tests/test_sidebar_cwd_git.py index e6168a1f..520a0831 100644 --- a/tests/test_sidebar_cwd_git.py +++ b/tests/test_sidebar_cwd_git.py @@ -72,6 +72,7 @@ def _wait_for_git_branch( expected: str, timeout: float = 12.0, interval: float = 0.15, + allow_force_fallback: bool = True, ) -> dict[str, str]: def pred(): state = _parse_sidebar_state(client.sidebar_state()) @@ -82,6 +83,8 @@ def _wait_for_git_branch( try: return _wait_for(pred, timeout=timeout, interval=interval, label=f"git_branch={expected!r}") except AssertionError as original_error: + if not allow_force_fallback: + raise original_error # VM shells can occasionally skip a prompt hook; force a one-shot report so # the remainder of the flow can still validate transition behavior. try: @@ -180,6 +183,18 @@ def main() -> int: _send_cd_and_wait(client, repo) _wait_for_git_branch(client, "main") + # Branch changes during a long-running foreground command should still + # propagate before the prompt returns (agent-style workflows). + client.send("bash -lc 'git checkout -b feature/agent-live >/dev/null 2>&1; sleep 6'\n") + _wait_for_git_branch( + client, + "feature/agent-live", + timeout=3.5, + interval=0.1, + allow_force_fallback=False, + ) + time.sleep(6.3) + # Branch change should update. # Cover alias/non-`git ...` command paths too (regression: branch could # stick for ~3s when switching via alias/tools like `gh pr checkout`). From 26bef7316e584fee971489f360a6e16d5cc260fd Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:19:07 -0800 Subject: [PATCH 083/232] Fix custom notification sound staging reliability (#919) --- CLAUDE.md | 6 +- Resources/Localizable.xcstrings | 238 ++++++++++++++ Sources/TerminalNotificationStore.swift | 299 +++++++++++++++++- Sources/cmuxApp.swift | 163 +++++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 161 +++++++++- 5 files changed, 834 insertions(+), 33 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 03591777..18f06112 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,17 +21,19 @@ When reporting a tagged reload result in chat, use the format for your agent typ **Claude Code** (markdown link with correct derived-data path, cmd+clickable): ```markdown ======================================================= -[cmux DEV .app](file:///tmp/cmux-/Build/Products/Debug/cmux%20DEV%20.app) +[cmux DEV .app](file:///Users/lawrencechen/Library/Developer/Xcode/DerivedData/cmux-/Build/Products/Debug/cmux%20DEV%20.app) ======================================================= ``` **Codex** (plain text format): ``` ======================================================= -[: file:///tmp/cmux-.app](file:///tmp/cmux-.app) +[: file:///Users/lawrencechen/Library/Developer/Xcode/DerivedData/cmux-/Build/Products/Debug/cmux%20DEV%20.app](file:///Users/lawrencechen/Library/Developer/Xcode/DerivedData/cmux-/Build/Products/Debug/cmux%20DEV%20.app) ======================================================= ``` +Never use `/tmp/cmux-/...` app links in chat output. If the expected DerivedData path is missing, resolve the real `.app` path and report that `file://` URL. + After making code changes, always run the build: ```bash diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 3f711d05..2139e387 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -43959,6 +43959,244 @@ } } }, + "settings.notifications.sound.custom.choose.button": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose..." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択..." + } + } + } + }, + "settings.notifications.sound.custom.choose.prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択" + } + } + } + }, + "settings.notifications.sound.custom.choose.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose Notification Sound" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知サウンドを選択" + } + } + } + }, + "settings.notifications.sound.custom.clear.button": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリア" + } + } + } + }, + "settings.notifications.sound.custom.error.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom Notification Sound Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタム通知サウンドのエラー" + } + } + } + }, + "settings.notifications.sound.custom.file.none": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No file selected" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ファイル未選択" + } + } + } + }, + "settings.notifications.sound.custom.status.empty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a custom audio file first." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "先にカスタム音声ファイルを選択してください。" + } + } + } + }, + "settings.notifications.sound.custom.status.missingExtensionPrefix": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "File needs an extension: " + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "拡張子が必要です: " + } + } + } + }, + "settings.notifications.sound.custom.status.missingFilePrefix": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "File not found: " + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ファイルが見つかりません: " + } + } + } + }, + "settings.notifications.sound.custom.status.prepareFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Could not prepare this file for notifications. Try WAV, AIFF, or CAF." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知用にこのファイルを準備できませんでした。WAV、AIFF、またはCAFを試してください。" + } + } + } + }, + "settings.notifications.sound.custom.status.ready": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ready for notifications." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知用の準備ができました。" + } + } + } + }, + "settings.notifications.sound.custom.status.readyConverted": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Prepared for notifications (converted to CAF)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知用に準備しました(CAFに変換)。" + } + } + } + }, + "settings.notifications.sound.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sound played when a notification arrives." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を受信したときに再生するサウンドです。" + } + } + } + }, + "settings.notifications.sound.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notification Sound" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知サウンド" + } + } + } + }, "settings.automation.claudeCode": { "extractionState": "manual", "localizations": { diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 4d5ba1b6..5bb768cb 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -36,6 +36,45 @@ enum NotificationSoundSettings { static let customFilePathKey = "notificationSoundCustomFilePath" static let defaultCustomFilePath = "" private static let stagedCustomSoundBaseName = "cmux-custom-notification-sound" + private static let customSoundPreparationQueue = DispatchQueue( + label: "com.cmuxterm.notification-sound-preparation", + qos: .utility + ) + private static let pendingCustomSoundPreparationLock = NSLock() + private static var pendingCustomSoundPreparationPaths: Set = [] + private static let notificationSoundSupportedExtensions: Set = [ + "aif", + "aiff", + "caf", + "wav", + ] + + private struct CustomSoundSourceMetadata: Codable, Equatable { + let sourcePath: String + let sourceSize: UInt64 + let sourceModificationTime: Double + let sourceFileIdentifier: UInt64? + } + + enum CustomSoundPreparationIssue: Error { + case emptyPath + case missingFile(path: String) + case missingFileExtension(path: String) + case stagingFailed(path: String, details: String) + + var logMessage: String { + switch self { + case .emptyPath: + return "Notification custom sound path is empty" + case .missingFile(let path): + return "Notification custom sound file does not exist: \(path)" + case .missingFileExtension(let path): + return "Notification custom sound requires a file extension: \(path)" + case .stagingFailed(let path, let details): + return "Failed to stage custom notification sound from \(path): \(details)" + } + } + } static let customCommandKey = "notificationCustomCommand" static let defaultCustomCommand = "" @@ -97,33 +136,109 @@ enum NotificationSoundSettings { } static func stagedCustomSoundName(defaults: UserDefaults = .standard) -> String? { - guard let sourceURL = customFileURL(defaults: defaults) else { return nil } - let sourceExtension = sourceURL.pathExtension.trimmingCharacters(in: .whitespacesAndNewlines) - guard !sourceExtension.isEmpty else { - NSLog("Notification custom sound requires a file extension: \(sourceURL.path)") + let rawPath = defaults.string(forKey: customFilePathKey) ?? defaultCustomFilePath + guard let normalizedPath = normalizedCustomFilePath(rawPath) else { + NSLog("Notification custom sound unavailable: \(CustomSoundPreparationIssue.emptyPath.logMessage)") return nil } - let destinationDirectory = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) - .appendingPathComponent("Library", isDirectory: true) - .appendingPathComponent("Sounds", isDirectory: true) - let destinationFileName = "\(stagedCustomSoundBaseName).\(sourceExtension.lowercased())" - let destinationURL = destinationDirectory.appendingPathComponent(destinationFileName, isDirectory: false) + let sourceURL = URL(fileURLWithPath: (normalizedPath as NSString).expandingTildeInPath) + let sourceExtension = sourceURL.pathExtension + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + guard !sourceExtension.isEmpty else { + NSLog("Notification custom sound unavailable: \(CustomSoundPreparationIssue.missingFileExtension(path: sourceURL.path).logMessage)") + return nil + } + + let destinationExtension = stagedCustomSoundFileExtension(forSourceExtension: sourceExtension) + let stagedFileName = stagedCustomSoundFileName( + forSourceURL: sourceURL, + destinationExtension: destinationExtension + ) + let stagedURL = stagedSoundDirectoryURL().appendingPathComponent(stagedFileName, isDirectory: false) let fileManager = FileManager.default + guard fileManager.fileExists(atPath: sourceURL.path) else { + NSLog("Notification custom sound unavailable: \(CustomSoundPreparationIssue.missingFile(path: sourceURL.path).logMessage)") + return nil + } + + if fileManager.fileExists(atPath: stagedURL.path) { + if let sourceMetadata = currentSourceMetadata(for: sourceURL, fileManager: fileManager), + let stagedMetadata = loadStagedSourceMetadata(for: stagedURL), + stagedMetadata == sourceMetadata { + return stagedFileName + } + } + + if destinationExtension == sourceExtension { + switch prepareCustomFileForNotifications(path: normalizedPath) { + case .success(let preparedName): + return preparedName + case .failure(let issue): + NSLog("Notification custom sound unavailable: \(issue.logMessage)") + return nil + } + } + + queueCustomSoundPreparation(path: normalizedPath) + NSLog("Notification custom sound not ready yet, staging in background: \(sourceURL.path)") + return nil + } + + static func prepareCustomFileForNotifications(path: String) -> Result { + guard let normalizedPath = normalizedCustomFilePath(path) else { + return .failure(.emptyPath) + } + let sourceURL = URL(fileURLWithPath: (normalizedPath as NSString).expandingTildeInPath) + return prepareCustomSound(from: sourceURL) + } + + private static func prepareCustomSound(from sourceURL: URL) -> Result { + let sourcePath = sourceURL.path + let fileManager = FileManager.default + guard fileManager.fileExists(atPath: sourcePath) else { + return .failure(.missingFile(path: sourcePath)) + } + let sourceExtension = sourceURL.pathExtension.trimmingCharacters(in: .whitespacesAndNewlines) + guard !sourceExtension.isEmpty else { + return .failure(.missingFileExtension(path: sourcePath)) + } + let destinationExtension = stagedCustomSoundFileExtension(forSourceExtension: sourceExtension) + + let destinationDirectory = stagedSoundDirectoryURL() + let destinationFileName = stagedCustomSoundFileName( + forSourceURL: sourceURL, + destinationExtension: destinationExtension + ) + let destinationURL = destinationDirectory.appendingPathComponent(destinationFileName, isDirectory: false) + let sourceMetadata = currentSourceMetadata(for: sourceURL, fileManager: fileManager) do { try fileManager.createDirectory(at: destinationDirectory, withIntermediateDirectories: true) - try copyStagedSoundIfNeeded(from: sourceURL, to: destinationURL, fileManager: fileManager) + if fileManager.fileExists(atPath: destinationURL.path) { + let stagedMetadata = loadStagedSourceMetadata(for: destinationURL) + if stagedMetadata != sourceMetadata { + try? fileManager.removeItem(at: destinationURL) + } + } + if destinationExtension == sourceExtension.lowercased() { + try copyStagedSoundIfNeeded(from: sourceURL, to: destinationURL, fileManager: fileManager) + } else { + try transcodeStagedSoundIfNeeded(from: sourceURL, to: destinationURL, fileManager: fileManager) + } + if let sourceMetadata { + try saveStagedSourceMetadata(sourceMetadata, for: destinationURL) + } try cleanupStaleStagedSoundFiles( in: destinationDirectory, keeping: destinationFileName, preservingSourceURL: sourceURL, fileManager: fileManager ) - return destinationFileName + return .success(destinationFileName) } catch { - NSLog("Failed to stage custom notification sound: \(error)") - return nil + return .failure(.stagingFailed(path: sourcePath, details: error.localizedDescription)) } } @@ -158,12 +273,58 @@ enum NotificationSoundSettings { } } + static func stagedCustomSoundFileExtension(forSourceExtension sourceExtension: String) -> String { + let normalized = sourceExtension + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + guard !normalized.isEmpty else { return "caf" } + if notificationSoundSupportedExtensions.contains(normalized) { + return normalized + } + return "caf" + } + + static func stagedCustomSoundFileName(forSourceURL sourceURL: URL, destinationExtension: String) -> String { + let normalizedExtension = destinationExtension + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + let ext = normalizedExtension.isEmpty ? "caf" : normalizedExtension + let signature = stagedCustomSoundSourceSignature(for: sourceURL) + return "\(stagedCustomSoundBaseName)-\(signature).\(ext)" + } + private static func normalizedCustomFilePath(_ rawPath: String) -> String? { let trimmed = rawPath.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } return trimmed } + private static func stagedSoundDirectoryURL() -> URL { + URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Sounds", isDirectory: true) + } + + private static func queueCustomSoundPreparation(path: String) { + let expandedPath = (path as NSString).expandingTildeInPath + pendingCustomSoundPreparationLock.lock() + if pendingCustomSoundPreparationPaths.contains(expandedPath) { + pendingCustomSoundPreparationLock.unlock() + return + } + pendingCustomSoundPreparationPaths.insert(expandedPath) + pendingCustomSoundPreparationLock.unlock() + + customSoundPreparationQueue.async { + defer { + pendingCustomSoundPreparationLock.lock() + pendingCustomSoundPreparationPaths.remove(expandedPath) + pendingCustomSoundPreparationLock.unlock() + } + _ = prepareCustomFileForNotifications(path: expandedPath) + } + } + private static func playSoundFile(at url: URL) { DispatchQueue.main.async { guard let sound = NSSound(contentsOf: url, byReference: false) else { @@ -180,15 +341,21 @@ enum NotificationSoundSettings { preservingSourceURL: URL, fileManager: FileManager ) throws { - let prefix = "\(stagedCustomSoundBaseName)." + let legacyPrefix = "\(stagedCustomSoundBaseName)." + let hashedPrefix = "\(stagedCustomSoundBaseName)-" let normalizedSource = preservingSourceURL.standardizedFileURL + let keptStagedURL = directoryURL.appendingPathComponent(fileName, isDirectory: false) + let keptMetadataFileName = stagedSourceMetadataURL(for: keptStagedURL).lastPathComponent for fileNameCandidate in try fileManager.contentsOfDirectory(atPath: directoryURL.path) { - guard fileNameCandidate.hasPrefix(prefix), fileNameCandidate != fileName else { continue } + let isManagedName = fileNameCandidate.hasPrefix(legacyPrefix) || fileNameCandidate.hasPrefix(hashedPrefix) + let isKeptManagedFile = fileNameCandidate == fileName || fileNameCandidate == keptMetadataFileName + guard isManagedName, !isKeptManagedFile else { continue } let staleURL = directoryURL.appendingPathComponent(fileNameCandidate, isDirectory: false) if staleURL.standardizedFileURL == normalizedSource { continue } try? fileManager.removeItem(at: staleURL) + try? fileManager.removeItem(at: stagedSourceMetadataURL(for: staleURL)) } } @@ -217,6 +384,108 @@ enum NotificationSoundSettings { try fileManager.copyItem(at: normalizedSource, to: normalizedDestination) } + private static func transcodeStagedSoundIfNeeded( + from sourceURL: URL, + to destinationURL: URL, + fileManager: FileManager + ) throws { + let normalizedSource = sourceURL.standardizedFileURL + let normalizedDestination = destinationURL.standardizedFileURL + guard normalizedSource != normalizedDestination else { return } + + if fileManager.fileExists(atPath: normalizedDestination.path) { + let sourceAttributes = try fileManager.attributesOfItem(atPath: normalizedSource.path) + let destinationAttributes = try fileManager.attributesOfItem(atPath: normalizedDestination.path) + let sourceDate = sourceAttributes[.modificationDate] as? Date + let destinationDate = destinationAttributes[.modificationDate] as? Date + if let sourceDate, let destinationDate, destinationDate >= sourceDate { + return + } + try fileManager.removeItem(at: normalizedDestination) + } + + let outputPipe = Pipe() + let errorPipe = Pipe() + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/afconvert") + process.arguments = [ + "-f", "caff", + "-d", "LEI16", + normalizedSource.path, + normalizedDestination.path, + ] + process.standardOutput = outputPipe + process.standardError = errorPipe + try process.run() + process.waitUntilExit() + guard process.terminationStatus == 0 else { + let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile() + let errorOutput = String(data: errorData, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) + if fileManager.fileExists(atPath: normalizedDestination.path) { + try? fileManager.removeItem(at: normalizedDestination) + } + let description: String + if let errorOutput, !errorOutput.isEmpty { + description = errorOutput + } else { + description = "afconvert failed with exit code \(process.terminationStatus)" + } + throw NSError( + domain: "NotificationSoundSettings", + code: Int(process.terminationStatus), + userInfo: [ + NSLocalizedDescriptionKey: description, + ] + ) + } + } + + private static func stagedCustomSoundSourceSignature(for sourceURL: URL) -> String { + let normalizedPath = sourceURL.standardizedFileURL.path + var hash: UInt64 = 0xcbf29ce484222325 + for byte in normalizedPath.utf8 { + hash ^= UInt64(byte) + hash &*= 0x100000001b3 + } + return String(format: "%016llx", hash) + } + + private static func stagedSourceMetadataURL(for stagedURL: URL) -> URL { + stagedURL.appendingPathExtension("source-metadata") + } + + private static func currentSourceMetadata(for sourceURL: URL, fileManager: FileManager) -> CustomSoundSourceMetadata? { + guard let attributes = try? fileManager.attributesOfItem(atPath: sourceURL.path) else { + return nil + } + guard let sourceSizeNumber = attributes[.size] as? NSNumber else { + return nil + } + let sourceDate = (attributes[.modificationDate] as? Date) ?? .distantPast + let fileIdentifier = (attributes[.systemFileNumber] as? NSNumber)?.uint64Value + return CustomSoundSourceMetadata( + sourcePath: sourceURL.standardizedFileURL.path, + sourceSize: sourceSizeNumber.uint64Value, + sourceModificationTime: sourceDate.timeIntervalSinceReferenceDate, + sourceFileIdentifier: fileIdentifier + ) + } + + private static func loadStagedSourceMetadata(for stagedURL: URL) -> CustomSoundSourceMetadata? { + let metadataURL = stagedSourceMetadataURL(for: stagedURL) + guard let data = try? Data(contentsOf: metadataURL) else { + return nil + } + return try? JSONDecoder().decode(CustomSoundSourceMetadata.self, from: data) + } + + private static func saveStagedSourceMetadata(_ metadata: CustomSoundSourceMetadata, for stagedURL: URL) throws { + let metadataURL = stagedSourceMetadataURL(for: stagedURL) + let data = try JSONEncoder().encode(metadata) + try data.write(to: metadataURL, options: .atomic) + } + private static let customCommandQueue = DispatchQueue( label: "com.cmuxterm.notification-custom-command", qos: .utility diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index fdc91f13..ff392c1f 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2853,6 +2853,10 @@ struct SettingsView: View { @State private var socketPasswordDraft = "" @State private var socketPasswordStatusMessage: String? @State private var socketPasswordStatusIsError = false + @State private var notificationCustomSoundStatusMessage: String? + @State private var notificationCustomSoundStatusIsError = false + @State private var showNotificationCustomSoundErrorAlert = false + @State private var notificationCustomSoundErrorAlertMessage = "" @State private var telemetryValueAtLaunch = TelemetrySettings.enabledForCurrentLaunch @State private var showLanguageRestartAlert = false @State private var isResettingSettings = false @@ -2935,7 +2939,10 @@ struct SettingsView: View { private var notificationSoundCustomFileDisplayName: String { guard hasCustomNotificationSoundFilePath else { - return "No file selected" + return String( + localized: "settings.notifications.sound.custom.file.none", + defaultValue: "No file selected" + ) } return URL(fileURLWithPath: notificationSoundCustomFilePath).lastPathComponent } @@ -3004,18 +3011,113 @@ struct SettingsView: View { NotificationSoundSettings.previewSound(value: notificationSound) } + private func notificationCustomSoundIssueMessage(_ issue: NotificationSoundSettings.CustomSoundPreparationIssue) -> String { + switch issue { + case .emptyPath: + return String( + localized: "settings.notifications.sound.custom.status.empty", + defaultValue: "Choose a custom audio file first." + ) + case .missingFile(let path): + let fileName = URL(fileURLWithPath: path).lastPathComponent + return String( + localized: "settings.notifications.sound.custom.status.missingFilePrefix", + defaultValue: "File not found: " + ) + fileName + case .missingFileExtension(let path): + let fileName = URL(fileURLWithPath: path).lastPathComponent + return String( + localized: "settings.notifications.sound.custom.status.missingExtensionPrefix", + defaultValue: "File needs an extension: " + ) + fileName + case .stagingFailed(_, let details): + let prefix = String( + localized: "settings.notifications.sound.custom.status.prepareFailed", + defaultValue: "Could not prepare this file for notifications. Try WAV, AIFF, or CAF." + ) + return "\(prefix) (\(details))" + } + } + + private func notificationCustomSoundReadyStatusMessage(for path: String) -> String { + let sourceExtension = URL(fileURLWithPath: path).pathExtension + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + let stagedExtension = NotificationSoundSettings.stagedCustomSoundFileExtension(forSourceExtension: sourceExtension) + if !sourceExtension.isEmpty, stagedExtension != sourceExtension { + return String( + localized: "settings.notifications.sound.custom.status.readyConverted", + defaultValue: "Prepared for notifications (converted to CAF)." + ) + } + return String( + localized: "settings.notifications.sound.custom.status.ready", + defaultValue: "Ready for notifications." + ) + } + + private func refreshNotificationCustomSoundStatus(showAlertOnFailure: Bool = false) { + guard notificationSound == NotificationSoundSettings.customFileValue else { + notificationCustomSoundStatusMessage = nil + notificationCustomSoundStatusIsError = false + return + } + let pathSnapshot = notificationSoundCustomFilePath + DispatchQueue.global(qos: .userInitiated).async { + let result = NotificationSoundSettings.prepareCustomFileForNotifications(path: pathSnapshot) + DispatchQueue.main.async { + guard notificationSound == NotificationSoundSettings.customFileValue else { + notificationCustomSoundStatusMessage = nil + notificationCustomSoundStatusIsError = false + return + } + guard notificationSoundCustomFilePath == pathSnapshot else { return } + switch result { + case .success: + notificationCustomSoundStatusMessage = notificationCustomSoundReadyStatusMessage(for: pathSnapshot) + notificationCustomSoundStatusIsError = false + case .failure(let issue): + let message = notificationCustomSoundIssueMessage(issue) + notificationCustomSoundStatusMessage = message + notificationCustomSoundStatusIsError = true + if showAlertOnFailure { + notificationCustomSoundErrorAlertMessage = message + showNotificationCustomSoundErrorAlert = true + } + } + } + } + } + private func chooseNotificationSoundFile() { let panel = NSOpenPanel() panel.canChooseFiles = true panel.canChooseDirectories = false panel.allowsMultipleSelection = false panel.allowedContentTypes = [.audio] - panel.title = "Choose Notification Sound" - panel.prompt = "Choose" + panel.title = String( + localized: "settings.notifications.sound.custom.choose.title", + defaultValue: "Choose Notification Sound" + ) + panel.prompt = String( + localized: "settings.notifications.sound.custom.choose.prompt", + defaultValue: "Choose" + ) guard panel.runModal() == .OK, let url = panel.url else { return } - notificationSoundCustomFilePath = url.path - notificationSound = NotificationSoundSettings.customFileValue - previewNotificationSound() + let selectedPath = url.path + switch NotificationSoundSettings.prepareCustomFileForNotifications(path: selectedPath) { + case .success: + notificationSoundCustomFilePath = selectedPath + notificationSound = NotificationSoundSettings.customFileValue + notificationCustomSoundStatusMessage = notificationCustomSoundReadyStatusMessage(for: selectedPath) + notificationCustomSoundStatusIsError = false + previewNotificationSound() + case .failure(let issue): + let message = notificationCustomSoundIssueMessage(issue) + notificationCustomSoundErrorAlertMessage = message + showNotificationCustomSoundErrorAlert = true + refreshNotificationCustomSoundStatus() + } } private func handleNotificationPermissionAction() { @@ -3178,8 +3280,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Notification Sound", - subtitle: "Sound played when a notification arrives." + String(localized: "settings.notifications.sound.title", defaultValue: "Notification Sound"), + subtitle: String(localized: "settings.notifications.sound.subtitle", defaultValue: "Sound played when a notification arrives.") ) { VStack(alignment: .trailing, spacing: 6) { HStack(spacing: 6) { @@ -3208,16 +3310,35 @@ struct SettingsView: View { .lineLimit(1) .truncationMode(.middle) .frame(width: 170, alignment: .trailing) - Button("Choose...") { + Button( + String( + localized: "settings.notifications.sound.custom.choose.button", + defaultValue: "Choose..." + ) + ) { chooseNotificationSoundFile() } .controlSize(.small) - Button("Clear") { + Button( + String( + localized: "settings.notifications.sound.custom.clear.button", + defaultValue: "Clear" + ) + ) { notificationSoundCustomFilePath = NotificationSoundSettings.defaultCustomFilePath + refreshNotificationCustomSoundStatus() } .controlSize(.small) .disabled(!hasCustomNotificationSoundFilePath) } + if let notificationCustomSoundStatusMessage { + Text(notificationCustomSoundStatusMessage) + .font(.system(size: 11)) + .foregroundStyle(notificationCustomSoundStatusIsError ? Color.red : Color.secondary) + .lineLimit(2) + .multilineTextAlignment(.trailing) + .frame(width: 260, alignment: .trailing) + } } } } @@ -3870,6 +3991,13 @@ struct SettingsView: View { browserHistoryEntryCount = BrowserHistoryStore.shared.entries.count browserInsecureHTTPAllowlistDraft = browserInsecureHTTPAllowlist reloadWorkspaceTabColorSettings() + refreshNotificationCustomSoundStatus() + } + .onChange(of: notificationSound) { _, _ in + refreshNotificationCustomSoundStatus() + } + .onChange(of: notificationSoundCustomFilePath) { _, _ in + refreshNotificationCustomSoundStatus() } .onChange(of: browserInsecureHTTPAllowlist) { oldValue, newValue in // Keep draft in sync with external changes unless the user has local unsaved edits. @@ -3920,6 +4048,17 @@ struct SettingsView: View { } Button(String(localized: "settings.app.language.restartDialog.later", defaultValue: "Later"), role: .cancel) {} } + .alert( + String( + localized: "settings.notifications.sound.custom.error.title", + defaultValue: "Custom Notification Sound Error" + ), + isPresented: $showNotificationCustomSoundErrorAlert + ) { + Button(String(localized: "common.ok", defaultValue: "OK"), role: .cancel) {} + } message: { + Text(notificationCustomSoundErrorAlertMessage) + } } private func relaunchApp() { @@ -3960,6 +4099,10 @@ struct SettingsView: View { browserInsecureHTTPAllowlistDraft = BrowserInsecureHTTPSettings.defaultAllowlistText notificationSound = NotificationSoundSettings.defaultValue notificationSoundCustomFilePath = NotificationSoundSettings.defaultCustomFilePath + notificationCustomSoundStatusMessage = nil + notificationCustomSoundStatusIsError = false + showNotificationCustomSoundErrorAlert = false + notificationCustomSoundErrorAlertMessage = "" notificationCustomCommand = NotificationSoundSettings.defaultCustomCommand notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 5aecfac8..ffedcee4 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -6745,16 +6745,11 @@ final class NotificationDockBadgeTests: XCTestCase { } let sourceURL = soundsDirectory.appendingPathComponent( - "cmux-custom-notification-sound.source-\(UUID().uuidString).custtest", - isDirectory: false - ) - let stagedURL = soundsDirectory.appendingPathComponent( - "cmux-custom-notification-sound.custtest", + "cmux-custom-notification-sound.source-\(UUID().uuidString).wav", isDirectory: false ) defer { try? fileManager.removeItem(at: sourceURL) - try? fileManager.removeItem(at: stagedURL) } do { @@ -6769,8 +6764,162 @@ final class NotificationDockBadgeTests: XCTestCase { _ = NotificationSoundSettings.sound(defaults: defaults) + guard let stagedName = NotificationSoundSettings.stagedCustomSoundName(defaults: defaults) else { + XCTFail("Expected staged custom sound name") + return + } + let stagedURL = soundsDirectory.appendingPathComponent(stagedName, isDirectory: false) + defer { + try? fileManager.removeItem(at: stagedURL) + } + XCTAssertTrue(fileManager.fileExists(atPath: sourceURL.path)) XCTAssertTrue(fileManager.fileExists(atPath: stagedURL.path)) + XCTAssertTrue(stagedName.hasPrefix("cmux-custom-notification-sound-")) + XCTAssertTrue(stagedName.hasSuffix(".wav")) + } + + func testNotificationCustomUnsupportedExtensionsStageAsCaf() { + XCTAssertEqual( + NotificationSoundSettings.stagedCustomSoundFileExtension(forSourceExtension: "mp3"), + "caf" + ) + XCTAssertEqual( + NotificationSoundSettings.stagedCustomSoundFileExtension(forSourceExtension: "M4A"), + "caf" + ) + XCTAssertEqual( + NotificationSoundSettings.stagedCustomSoundFileExtension(forSourceExtension: "wav"), + "wav" + ) + XCTAssertEqual( + NotificationSoundSettings.stagedCustomSoundFileExtension(forSourceExtension: "AIFF"), + "aiff" + ) + + let sourceA = URL(fileURLWithPath: "/tmp/custom-a.mp3") + let sourceB = URL(fileURLWithPath: "/tmp/custom-b.mp3") + let stagedA = NotificationSoundSettings.stagedCustomSoundFileName( + forSourceURL: sourceA, + destinationExtension: "caf" + ) + let stagedB = NotificationSoundSettings.stagedCustomSoundFileName( + forSourceURL: sourceB, + destinationExtension: "caf" + ) + XCTAssertNotEqual(stagedA, stagedB) + XCTAssertTrue(stagedA.hasPrefix("cmux-custom-notification-sound-")) + XCTAssertTrue(stagedA.hasSuffix(".caf")) + } + + func testNotificationCustomPreparationKeepsActiveSourceMetadataSidecar() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + let fileManager = FileManager.default + let soundsDirectory = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Sounds", isDirectory: true) + do { + try fileManager.createDirectory(at: soundsDirectory, withIntermediateDirectories: true) + } catch { + XCTFail("Failed to create sounds directory: \(error)") + return + } + + let sourceURL = soundsDirectory.appendingPathComponent( + "cmux-custom-notification-sound.metadata-\(UUID().uuidString).wav", + isDirectory: false + ) + do { + try Data("test".utf8).write(to: sourceURL, options: .atomic) + } catch { + XCTFail("Failed to write source custom sound file: \(error)") + return + } + defer { + try? fileManager.removeItem(at: sourceURL) + } + + defaults.set(NotificationSoundSettings.customFileValue, forKey: NotificationSoundSettings.key) + defaults.set(sourceURL.path, forKey: NotificationSoundSettings.customFilePathKey) + + let prepareResult = NotificationSoundSettings.prepareCustomFileForNotifications(path: sourceURL.path) + let stagedName: String + switch prepareResult { + case .success(let name): + stagedName = name + case .failure(let issue): + XCTFail("Expected custom sound preparation success, got \(issue)") + return + } + + let stagedURL = soundsDirectory.appendingPathComponent(stagedName, isDirectory: false) + let metadataURL = stagedURL.appendingPathExtension("source-metadata") + defer { + try? fileManager.removeItem(at: stagedURL) + try? fileManager.removeItem(at: metadataURL) + } + + XCTAssertTrue(fileManager.fileExists(atPath: stagedURL.path)) + XCTAssertTrue(fileManager.fileExists(atPath: metadataURL.path)) + } + + func testNotificationCustomSoundReturnsNilWhenPreparationFails() { + let suiteName = "NotificationDockBadgeTests.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { + defaults.removePersistentDomain(forName: suiteName) + } + + let invalidSourceURL = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-invalid-sound-\(UUID().uuidString).mp3", isDirectory: false) + defer { + try? FileManager.default.removeItem(at: invalidSourceURL) + let stagedURL = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Sounds", isDirectory: true) + .appendingPathComponent("cmux-custom-notification-sound.caf", isDirectory: false) + try? FileManager.default.removeItem(at: stagedURL) + } + + do { + try Data("not-audio".utf8).write(to: invalidSourceURL, options: .atomic) + } catch { + XCTFail("Failed to write invalid custom sound source: \(error)") + return + } + + defaults.set(NotificationSoundSettings.customFileValue, forKey: NotificationSoundSettings.key) + defaults.set(invalidSourceURL.path, forKey: NotificationSoundSettings.customFilePathKey) + + XCTAssertNil(NotificationSoundSettings.sound(defaults: defaults)) + } + + func testNotificationCustomPreparationReportsMissingFile() { + let missingPath = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-missing-\(UUID().uuidString).wav", isDirectory: false) + .path + + let result = NotificationSoundSettings.prepareCustomFileForNotifications(path: missingPath) + switch result { + case .success: + XCTFail("Expected missing file failure") + case .failure(let issue): + guard case .missingFile = issue else { + XCTFail("Expected missingFile issue, got \(issue)") + return + } + } } func testNotificationAuthorizationStateMappingCoversKnownUNAuthorizationStatuses() { From 8193e55daed0e4db5dcecd8ac9176d77be4bcd54 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 4 Mar 2026 19:21:32 -0800 Subject: [PATCH 084/232] Fix background terminal startup for unfocused workspaces (#920) --- Sources/TerminalController.swift | 6 + ...t_cli_new_workspace_background_metadata.py | 191 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 tests_v2/test_cli_new_workspace_background_metadata.py diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 4f199842..56d7205b 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -2688,6 +2688,9 @@ class TerminalController { #endif v2MainSync { let ws = tabManager.addWorkspace(workingDirectory: cwd, select: shouldFocus) + if !shouldFocus, let terminalPanel = ws.focusedTerminalPanel { + terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() + } newId = ws.id } #if DEBUG @@ -10297,6 +10300,9 @@ class TerminalController { #endif DispatchQueue.main.sync { let workspace = tabManager.addTab(select: focus) + if !focus, let terminalPanel = workspace.focusedTerminalPanel { + terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() + } newTabId = workspace.id } #if DEBUG diff --git a/tests_v2/test_cli_new_workspace_background_metadata.py b/tests_v2/test_cli_new_workspace_background_metadata.py new file mode 100644 index 00000000..845b9a1a --- /dev/null +++ b/tests_v2/test_cli_new_workspace_background_metadata.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Regression: CLI `new-workspace --cwd` should preload sidebar metadata without focus.""" + +from __future__ import annotations + +import glob +import os +import shutil +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmux, cmuxError + + +SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def _find_cli_binary() -> str: + env_cli = os.environ.get("CMUXTERM_CLI") + if env_cli and os.path.isfile(env_cli) and os.access(env_cli, os.X_OK): + return env_cli + + fixed = os.path.expanduser("~/Library/Developer/Xcode/DerivedData/cmux-tests-v2/Build/Products/Debug/cmux") + if os.path.isfile(fixed) and os.access(fixed, os.X_OK): + return fixed + + candidates = glob.glob(os.path.expanduser("~/Library/Developer/Xcode/DerivedData/**/Build/Products/Debug/cmux"), recursive=True) + candidates += glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux") + candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)] + if not candidates: + raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI") + candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True) + return candidates[0] + + +def _run_cli(cli: str, args: list[str]) -> str: + env = dict(os.environ) + env.pop("CMUX_WORKSPACE_ID", None) + env.pop("CMUX_SURFACE_ID", None) + env.pop("CMUX_TAB_ID", None) + + cmd = [cli, "--socket", SOCKET_PATH] + args + proc = subprocess.run(cmd, capture_output=True, text=True, check=False, env=env) + if proc.returncode != 0: + merged = f"{proc.stdout}\n{proc.stderr}".strip() + raise cmuxError(f"CLI failed ({' '.join(cmd)}): {merged}") + return (proc.stdout or "").strip() + + +def _parse_sidebar_state(text: str) -> dict[str, str]: + parsed: dict[str, str] = {} + for raw in text.splitlines(): + line = raw.strip() + if not line or "=" not in line: + continue + key, value = line.split("=", 1) + parsed[key.strip()] = value.strip() + return parsed + + +def _wait_for_sidebar_git_branch(cli: str, workspace: str, timeout: float = 15.0) -> dict[str, str]: + deadline = time.time() + timeout + last_state = "" + + while time.time() < deadline: + state_text = _run_cli(cli, ["sidebar-state", "--workspace", workspace]) + last_state = state_text + state = _parse_sidebar_state(state_text) + raw_branch = state.get("git_branch", "") + branch = raw_branch.split(" ", 1)[0] + if branch and branch != "none": + return state + time.sleep(0.1) + + raise cmuxError( + "Timed out waiting for background git metadata on new workspace. " + f"Last sidebar-state: {last_state!r}" + ) + + +def _create_git_repo(root: Path) -> tuple[Path, str]: + repo = root / "repo" + repo.mkdir(parents=True, exist_ok=True) + + subprocess.run( + ["git", "-c", "init.defaultBranch=main", "init"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + ["git", "config", "user.name", "cmux-test"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + ["git", "config", "user.email", "cmux-test@example.com"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + (repo / "README.md").write_text("issue 915\n", encoding="utf-8") + subprocess.run( + ["git", "add", "README.md"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + ["git", "-c", "commit.gpgsign=false", "commit", "-m", "init"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + branch = subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + cwd=repo, + text=True, + ).strip() + return repo, branch + + +def main() -> int: + cli = _find_cli_binary() + temp_root = Path(tempfile.mkdtemp(prefix="cmux_issue_915_")) + created_workspace: str | None = None + + try: + repo_path, expected_branch = _create_git_repo(temp_root) + + with cmux(SOCKET_PATH) as c: + baseline_workspace = c.current_workspace() + + created = _run_cli(cli, ["new-workspace", "--cwd", str(repo_path)]) + _must(created.startswith("OK "), f"new-workspace expected OK response, got: {created!r}") + created_workspace = created.removeprefix("OK ").strip() + _must(bool(created_workspace), f"new-workspace returned no workspace handle: {created!r}") + + _must( + c.current_workspace() == baseline_workspace, + "new-workspace --cwd should preserve selected workspace", + ) + + sidebar_state = _wait_for_sidebar_git_branch(cli, created_workspace) + _must( + sidebar_state.get("cwd", "") == str(repo_path), + f"Expected sidebar cwd={repo_path!r}, got {sidebar_state.get('cwd', '')!r}", + ) + + raw_branch = sidebar_state.get("git_branch", "") + observed_branch = raw_branch.split(" ", 1)[0] + _must( + observed_branch == expected_branch, + f"Expected sidebar git branch {expected_branch!r}, got {raw_branch!r}", + ) + + _must( + c.current_workspace() == baseline_workspace, + "background metadata load should not switch selected workspace", + ) + finally: + if created_workspace: + try: + _run_cli(cli, ["close-workspace", "--workspace", created_workspace]) + except Exception: + pass + shutil.rmtree(temp_root, ignore_errors=True) + + print("PASS: new-workspace --cwd preloads sidebar metadata without focus") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 6fe1410918d1d44cf855187b282bb5b31837a69b Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 4 Mar 2026 19:21:45 -0800 Subject: [PATCH 085/232] Fix terminal link opens to stay in source workspace (#912) --- Sources/AppDelegate.swift | 34 ++++++++++++++++++++++++++++ Sources/GhosttyTerminalView.swift | 37 +++++++++++++++++++++++-------- Sources/Panels/BrowserPanel.swift | 9 +++++--- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index acd7419f..809b432c 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -3611,6 +3611,40 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return nil } + /// Resolve the workspace that currently owns a panel/surface ID. + /// Prefer the provided workspace when available, then fall back to global lookup. + func workspaceContainingPanel( + panelId: UUID, + preferredWorkspaceId: UUID? = nil + ) -> (workspace: Workspace, tabManager: TabManager)? { + if let preferredWorkspaceId, + let manager = tabManagerFor(tabId: preferredWorkspaceId), + let workspace = manager.tabs.first(where: { $0.id == preferredWorkspaceId }), + workspace.panels[panelId] != nil { + return (workspace, manager) + } + + if let located = locateSurface(surfaceId: panelId), + let workspace = located.tabManager.tabs.first(where: { $0.id == located.workspaceId }), + workspace.panels[panelId] != nil { + return (workspace, located.tabManager) + } + + if let preferredWorkspaceId, + let manager = tabManagerFor(tabId: preferredWorkspaceId) ?? tabManager, + let workspace = manager.tabs.first(where: { $0.id == preferredWorkspaceId }), + workspace.panels[panelId] != nil { + return (workspace, manager) + } + + if let manager = tabManager, + let workspace = manager.tabs.first(where: { $0.panels[panelId] != nil }) { + return (workspace, manager) + } + + return nil + } + func locateGhosttySurface(_ surface: ghostty_surface_t?) -> (windowId: UUID, workspaceId: UUID, panelId: UUID, tabManager: TabManager)? { guard let surface else { return nil } for ctx in mainWindowContexts.values { diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 1b65a5f6..8f946c4c 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -1860,35 +1860,54 @@ class GhosttyApp { NSWorkspace.shared.open(url) } } - guard let tabId = surfaceView.tabId, - let surfaceId = surfaceView.terminalSurface?.id else { + let sourceWorkspaceId = callbackTabId ?? surfaceView.tabId + let sourcePanelId = callbackSurfaceId ?? surfaceView.terminalSurface?.id + guard let sourceWorkspaceId, + let sourcePanelId else { #if DEBUG dlog("link.openURL target=embedded but tabId/surfaceId=nil") #endif return false } #if DEBUG - dlog("link.openURL target=embedded, opening in browser pane host=\(host) url=\(url) tabId=\(tabId) surfaceId=\(surfaceId)") + dlog( + "link.openURL target=embedded, opening in browser pane " + + "host=\(host) url=\(url) tabId=\(sourceWorkspaceId) surfaceId=\(sourcePanelId)" + ) #endif return performOnMain { guard let app = AppDelegate.shared, - let tabManager = app.tabManagerFor(tabId: tabId) ?? app.tabManager, - let workspace = tabManager.tabs.first(where: { $0.id == tabId }) else { + let resolved = app.workspaceContainingPanel( + panelId: sourcePanelId, + preferredWorkspaceId: sourceWorkspaceId + ) else { #if DEBUG - dlog("link.openURL embedded but workspace lookup failed tabId=\(tabId)") + dlog( + "link.openURL embedded but workspace lookup failed " + + "tabId=\(sourceWorkspaceId) surfaceId=\(sourcePanelId)" + ) #endif return false } - if let targetPane = workspace.preferredBrowserTargetPane(fromPanelId: surfaceId) { + let workspace = resolved.workspace + #if DEBUG + if workspace.id != sourceWorkspaceId { + dlog( + "link.openURL workspace.remap sourceTab=\(sourceWorkspaceId) " + + "resolvedTab=\(workspace.id) surfaceId=\(sourcePanelId)" + ) + } + #endif + if let targetPane = workspace.preferredBrowserTargetPane(fromPanelId: sourcePanelId) { #if DEBUG dlog("link.openURL opening in existing browser pane=\(targetPane)") #endif return workspace.newBrowserSurface(inPane: targetPane, url: url, focus: true) != nil } else { #if DEBUG - dlog("link.openURL opening as new browser split from surface=\(surfaceId)") + dlog("link.openURL opening as new browser split from surface=\(sourcePanelId)") #endif - return workspace.newBrowserSplit(from: surfaceId, orientation: .horizontal, url: url) != nil + return workspace.newBrowserSplit(from: sourcePanelId, orientation: .horizontal, url: url) != nil } } } diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 81eaf525..885dd16d 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -2371,13 +2371,16 @@ extension BrowserPanel { "bypass=\(bypassInsecureHTTPHostOnce ?? "nil")" ) #endif - guard let tabManager = AppDelegate.shared?.tabManager else { + guard let app = AppDelegate.shared else { #if DEBUG - dlog("browser.newTab.open.abort panel=\(id.uuidString.prefix(5)) reason=missingTabManager") + dlog("browser.newTab.open.abort panel=\(id.uuidString.prefix(5)) reason=missingAppDelegate") #endif return } - guard let workspace = tabManager.tabs.first(where: { $0.id == workspaceId }) else { + guard let workspace = app.workspaceContainingPanel( + panelId: id, + preferredWorkspaceId: workspaceId + )?.workspace else { #if DEBUG dlog("browser.newTab.open.abort panel=\(id.uuidString.prefix(5)) reason=workspaceMissing") #endif From 604ba6fcab7f2125ff35824e85047aa736e93046 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:26:05 -0800 Subject: [PATCH 086/232] Fix Cmd+F Escape passthrough into terminal (#918) * Fix find-bar Escape passthrough to terminal * Keep find Escape suppression armed until key-up --- Sources/Find/SurfaceSearchOverlay.swift | 14 +++ Sources/GhosttyTerminalView.swift | 36 ++++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 106 ++++++++++++++++++ 3 files changed, 156 insertions(+) diff --git a/Sources/Find/SurfaceSearchOverlay.swift b/Sources/Find/SurfaceSearchOverlay.swift index 44b37433..17c795e6 100644 --- a/Sources/Find/SurfaceSearchOverlay.swift +++ b/Sources/Find/SurfaceSearchOverlay.swift @@ -2,6 +2,19 @@ import AppKit import Bonsplit import SwiftUI +private extension NSView { + func cmuxAncestor(of type: T.Type) -> T? { + var current: NSView? = self + while let view = current { + if let target = view as? T { + return target + } + current = view.superview + } + return nil + } +} + struct SurfaceSearchOverlay: View { let tabId: UUID let surfaceId: UUID @@ -268,6 +281,7 @@ private struct SearchTextFieldRepresentable: NSViewRepresentable { case #selector(NSResponder.cancelOperation(_:)): // Don't intercept Escape during CJK IME composition (issue #118) if textView.hasMarkedText() { return false } + control.cmuxAncestor(of: GhosttySurfaceScrollView.self)?.beginFindEscapeSuppression() parent.onEscape() return true case #selector(NSResponder.insertNewline(_:)): diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 8f946c4c..106b35a1 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2915,6 +2915,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { private var lastScrollEventTime: CFTimeInterval = 0 private var visibleInUI: Bool = true private var pendingSurfaceSize: CGSize? + private var isFindEscapeSuppressionArmed = false #if DEBUG private var lastSizeSkipSignature: String? #endif @@ -3932,6 +3933,12 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { super.keyDown(with: event) return } + if event.keyCode != 53 { + endFindEscapeSuppression() + } + if shouldConsumeSuppressedFindEscape(event) { + return + } if handleKeyboardCopyModeIfNeeded(event, surface: surface) { keyboardCopyModeConsumedKeyUps.insert(event.keyCode) return @@ -4144,6 +4151,16 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { super.keyUp(with: event) return } + if event.keyCode != 53 { + endFindEscapeSuppression() + } + if shouldConsumeSuppressedFindEscape(event) { + endFindEscapeSuppression() + return + } + if event.keyCode == 53 { + endFindEscapeSuppression() + } if keyboardCopyModeConsumedKeyUps.remove(event.keyCode) != nil { return @@ -4196,6 +4213,21 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { return ghostty_input_mods_e(rawValue: mods) } + func beginFindEscapeSuppression() { + isFindEscapeSuppressionArmed = true + } + + private func endFindEscapeSuppression() { + isFindEscapeSuppressionArmed = false + } + + private func shouldConsumeSuppressedFindEscape(_ event: NSEvent) -> Bool { + guard event.keyCode == 53 else { return false } + let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + guard flags.isEmpty else { return false } + return isFindEscapeSuppressionArmed + } + /// Get the characters for a key event with control character handling. /// When control is pressed, we get the character without the control modifier /// so Ghostty's KeyEncoder can apply its own control character encoding. @@ -5292,6 +5324,10 @@ final class GhosttySurfaceScrollView: NSView { } } + func beginFindEscapeSuppression() { + surfaceView.beginFindEscapeSuppression() + } + func setTriggerFlashHandler(_ handler: (() -> Void)?) { surfaceView.onTriggerFlash = handler } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index ffedcee4..658ef0b0 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -8299,6 +8299,18 @@ final class GhosttySurfaceOverlayTests: XCTestCase { } } + private func findEditableTextField(in view: NSView) -> NSTextField? { + if let field = view as? NSTextField, field.isEditable { + return field + } + for subview in view.subviews { + if let field = findEditableTextField(in: subview) { + return field + } + } + return nil + } + func testTrackpadScrollRoutesToTerminalSurfaceAndPreservesKeyboardFocusPath() { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 360, height: 240), @@ -8437,6 +8449,100 @@ final class GhosttySurfaceOverlayTests: XCTestCase { XCTAssertFalse(hostedView.debugHasSearchOverlay()) } + func testEscapeDismissingFindOverlayDoesNotLeakEscapeKeyUpToTerminal() { + _ = NSApplication.shared + + let surface = TerminalSurface( + tabId: UUID(), + context: GHOSTTY_SURFACE_CONTEXT_SPLIT, + configTemplate: nil, + workingDirectory: nil + ) + let hostedView = surface.hostedView + + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 360, height: 240), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { + GhosttyNSView.debugGhosttySurfaceKeyEventObserver = nil + window.orderOut(nil) + } + + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + hostedView.frame = contentView.bounds + hostedView.autoresizingMask = [.width, .height] + contentView.addSubview(hostedView) + + window.makeKeyAndOrderFront(nil) + window.displayIfNeeded() + contentView.layoutSubtreeIfNeeded() + hostedView.setVisibleInUI(true) + hostedView.setActive(true) + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + + let searchState = TerminalSurface.SearchState(needle: "") + surface.searchState = searchState + hostedView.setSearchOverlay(searchState: searchState) + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + + guard let searchField = findEditableTextField(in: hostedView) else { + XCTFail("Expected mounted find text field") + return + } + window.makeFirstResponder(searchField) + + var escapeKeyUpCount = 0 + GhosttyNSView.debugGhosttySurfaceKeyEventObserver = { keyEvent in + guard keyEvent.action == GHOSTTY_ACTION_RELEASE, keyEvent.keycode == 53 else { return } + escapeKeyUpCount += 1 + } + + let timestamp = ProcessInfo.processInfo.systemUptime + guard let escapeKeyDown = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [], + timestamp: timestamp, + windowNumber: window.windowNumber, + context: nil, + characters: "\u{1b}", + charactersIgnoringModifiers: "\u{1b}", + isARepeat: false, + keyCode: 53 + ), let escapeKeyUp = NSEvent.keyEvent( + with: .keyUp, + location: .zero, + modifierFlags: [], + timestamp: timestamp + 0.001, + windowNumber: window.windowNumber, + context: nil, + characters: "\u{1b}", + charactersIgnoringModifiers: "\u{1b}", + isARepeat: false, + keyCode: 53 + ) else { + XCTFail("Failed to construct Escape key events") + return + } + + NSApp.sendEvent(escapeKeyDown) + NSApp.sendEvent(escapeKeyUp) + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + + XCTAssertNil(surface.searchState, "Escape should dismiss find overlay when search text is empty") + XCTAssertEqual( + escapeKeyUpCount, + 0, + "Escape used to dismiss find overlay must not pass through to the terminal key-up path" + ) + } + func testKeyboardCopyModeIndicatorMountsAndUnmounts() { let surface = TerminalSurface( tabId: UUID(), From 2712cabac9d4d11abdcf48f9cc80d6703a4b6831 Mon Sep 17 00:00:00 2001 From: Eray Bozoglu <165450912+novarii@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:00:35 -0500 Subject: [PATCH 087/232] Fix orphaned child processes when closing workspace tabs (#889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix orphaned child processes when closing workspace tabs When closing a workspace tab via the sidebar X button, child processes (login → zsh → claude) survived as orphans because TabManager.closeWorkspace() only removed the workspace from the tabs array without explicitly freeing Ghostty surfaces. It relied on ARC to cascade deallocation, but SwiftUI views and Combine publishers held references, delaying or preventing ghostty_surface_free() (which sends SIGHUP) from ever running. This adds explicit teardown on the workspace close path: - TerminalSurface.teardownSurface(): idempotent method to free the Ghostty runtime surface eagerly, matching the existing deinit logic - TerminalPanel.close() now calls teardownSurface() to ensure SIGHUP is sent - Workspace.teardownAllPanels() iterates all panels and closes them - TabManager.closeWorkspace() calls teardownAllPanels() before removing the workspace from the tabs array * Harden workspace teardown and ownership checks * Address follow-up teardown review feedback --------- Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> --- Sources/GhosttyTerminalView.swift | 26 +++++++++ Sources/Panels/TerminalPanel.swift | 1 + Sources/TabManager.swift | 18 +++--- Sources/Workspace.swift | 21 +++++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 58 +++++++++++++++++++ 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 106b35a1..e461e9d1 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2166,6 +2166,32 @@ final class TerminalSurface: Identifiable, ObservableObject { ) #endif } + + /// Explicitly free the Ghostty runtime surface. Idempotent — safe to call + /// before deinit; deinit will skip the free if already torn down. + @MainActor + func teardownSurface() { + markPortalLifecycleClosed(reason: "teardown") + + let callbackContext = surfaceCallbackContext + surfaceCallbackContext = nil + + let surfaceToFree = surface + surface = nil + + guard let surfaceToFree else { + callbackContext?.release() + return + } + + Task { @MainActor in + // Keep free behavior aligned with deinit: perform the runtime teardown on + // the next main-actor turn so SIGHUP delivery is deterministic but non-reentrant. + ghostty_surface_free(surfaceToFree) + callbackContext?.release() + } + } + #if DEBUG private static let surfaceLogPath = "/tmp/cmux-ghostty-surface.log" private static let sizeLogPath = "/tmp/cmux-ghostty-size.log" diff --git a/Sources/Panels/TerminalPanel.swift b/Sources/Panels/TerminalPanel.swift index cee42f2e..d4bb68b3 100644 --- a/Sources/Panels/TerminalPanel.swift +++ b/Sources/Panels/TerminalPanel.swift @@ -161,6 +161,7 @@ final class TerminalPanel: Panel, ObservableObject { "hidden=\(hostedView.isHidden ? 1 : 0)" ) #endif + surface.teardownSurface() } func requestViewReattach() { diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 5ff9c992..abdb3ea6 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1013,21 +1013,21 @@ class TabManager: ObservableObject { func closeWorkspace(_ workspace: Workspace) { guard tabs.count > 1 else { return } + guard let index = tabs.firstIndex(where: { $0.id == workspace.id }) else { return } sentryBreadcrumb("workspace.close", data: ["tabCount": tabs.count - 1]) AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: workspace.id) unwireClosedBrowserTracking(for: workspace) + workspace.teardownAllPanels() - if let index = tabs.firstIndex(where: { $0.id == workspace.id }) { - tabs.remove(at: index) + tabs.remove(at: index) - if selectedTabId == workspace.id { - // Keep the "focused index" stable when possible: - // - If we closed workspace i and there is still a workspace at index i, focus it (the one that moved up). - // - Otherwise (we closed the last workspace), focus the new last workspace (i-1). - let newIndex = min(index, max(0, tabs.count - 1)) - selectedTabId = tabs[newIndex].id - } + if selectedTabId == workspace.id { + // Keep the "focused index" stable when possible: + // - If we closed workspace i and there is still a workspace at index i, focus it (the one that moved up). + // - Otherwise (we closed the last workspace), focus the new last workspace (i-1). + let newIndex = min(index, max(0, tabs.count - 1)) + selectedTabId = tabs[newIndex].id } } diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 70ff179d..a4a870ea 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -2316,6 +2316,27 @@ final class Workspace: Identifiable, ObservableObject { return markdownPanel } + /// Tear down all panels in this workspace, freeing their Ghostty surfaces. + /// Called before the workspace is removed from TabManager to ensure child + /// processes receive SIGHUP even if ARC deallocation is delayed. + func teardownAllPanels() { + let panelEntries = Array(panels) + for (panelId, panel) in panelEntries { + panelSubscriptions.removeValue(forKey: panelId) + PortScanner.shared.unregisterPanel(workspaceId: id, panelId: panelId) + panel.close() + } + + panels.removeAll(keepingCapacity: false) + surfaceIdToPanelId.removeAll(keepingCapacity: false) + panelSubscriptions.removeAll(keepingCapacity: false) + pruneSurfaceMetadata(validSurfaceIds: []) + restoredTerminalScrollbackByPanelId.removeAll(keepingCapacity: false) + terminalInheritanceFontPointsByPanelId.removeAll(keepingCapacity: false) + lastTerminalConfigInheritancePanelId = nil + lastTerminalConfigInheritanceFontPoints = nil + } + /// Close a panel. /// Returns true when a bonsplit tab close request was issued. func closePanel(_ panelId: UUID, force: Bool = false) -> Bool { diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 658ef0b0..ca826cca 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -4109,6 +4109,64 @@ final class TabManagerChildExitCloseTests: XCTestCase { } } +@MainActor +final class WorkspaceTeardownTests: XCTestCase { + func testTeardownAllPanelsClearsPanelMetadataCaches() { + let workspace = Workspace() + guard let initialPanelId = workspace.focusedPanelId else { + XCTFail("Expected focused panel in new workspace") + return + } + + workspace.setPanelCustomTitle(panelId: initialPanelId, title: "Initial custom title") + workspace.setPanelPinned(panelId: initialPanelId, pinned: true) + + guard let splitPanel = workspace.newTerminalSplit(from: initialPanelId, orientation: .horizontal) else { + XCTFail("Expected split panel to be created") + return + } + + workspace.setPanelCustomTitle(panelId: splitPanel.id, title: "Split custom title") + workspace.setPanelPinned(panelId: splitPanel.id, pinned: true) + workspace.markPanelUnread(initialPanelId) + + XCTAssertFalse(workspace.panels.isEmpty) + XCTAssertFalse(workspace.panelTitles.isEmpty) + XCTAssertFalse(workspace.panelCustomTitles.isEmpty) + XCTAssertFalse(workspace.pinnedPanelIds.isEmpty) + XCTAssertFalse(workspace.manualUnreadPanelIds.isEmpty) + + workspace.teardownAllPanels() + + XCTAssertTrue(workspace.panels.isEmpty) + XCTAssertTrue(workspace.panelTitles.isEmpty) + XCTAssertTrue(workspace.panelCustomTitles.isEmpty) + XCTAssertTrue(workspace.pinnedPanelIds.isEmpty) + XCTAssertTrue(workspace.manualUnreadPanelIds.isEmpty) + } +} + +@MainActor +final class TabManagerWorkspaceOwnershipTests: XCTestCase { + func testCloseWorkspaceIgnoresWorkspaceNotOwnedByManager() { + let manager = TabManager() + _ = manager.addWorkspace() + let initialTabIds = manager.tabs.map(\.id) + let initialSelectedTabId = manager.selectedTabId + + let externalWorkspace = Workspace(title: "External workspace") + let externalPanelCountBefore = externalWorkspace.panels.count + let externalPanelTitlesBefore = externalWorkspace.panelTitles + + manager.closeWorkspace(externalWorkspace) + + XCTAssertEqual(manager.tabs.map(\.id), initialTabIds) + XCTAssertEqual(manager.selectedTabId, initialSelectedTabId) + XCTAssertEqual(externalWorkspace.panels.count, externalPanelCountBefore) + XCTAssertEqual(externalWorkspace.panelTitles, externalPanelTitlesBefore) + } +} + @MainActor final class TabManagerPendingUnfocusPolicyTests: XCTestCase { func testDoesNotUnfocusWhenPendingTabIsCurrentlySelected() { From 648f4c00db7964a24fefeea6c3cf64e7554d670f Mon Sep 17 00:00:00 2001 From: jleechan Date: Wed, 4 Mar 2026 21:57:01 -0800 Subject: [PATCH 088/232] WORKING: Fix split CWD inheritance and bash job notification spam - Pass inherited working directory when creating split panes (panelDirectories fallback to currentDirectory) - Suppress bash job-done "[N] Done ..." notifications in shell integration by toggling job control (set +m / set -m) around background probes - Add integration test for split/tab CWD inheritance Co-Authored-By: Claude Opus 4.6 --- .../cmux-bash-integration.bash | 8 + Sources/Workspace.swift | 10 ++ tests/test_split_cwd_inheritance.py | 170 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 tests/test_split_cwd_inheritance.py diff --git a/Resources/shell-integration/cmux-bash-integration.bash b/Resources/shell-integration/cmux-bash-integration.bash index 85027ee4..4576212c 100644 --- a/Resources/shell-integration/cmux-bash-integration.bash +++ b/Resources/shell-integration/cmux-bash-integration.bash @@ -82,6 +82,11 @@ _cmux_prompt_command() { [[ -n "$CMUX_TAB_ID" ]] || return 0 [[ -n "$CMUX_PANEL_ID" ]] || return 0 + # Suppress bash job-done notifications for background tasks spawned below. + # Without this, every completed async probe prints "[N] Done ..." to the terminal. + local _cmux_old_monitor="${-//[^m]/}" + set +m + local now=$SECONDS local pwd="$PWD" @@ -205,6 +210,9 @@ _cmux_prompt_command() { if (( now - _CMUX_PORTS_LAST_RUN >= 10 )); then _cmux_ports_kick fi + + # Restore job control if it was previously enabled. + [[ -n "$_cmux_old_monitor" ]] && set -m } _cmux_install_prompt_command() { diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index a4a870ea..14c6475e 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -1956,11 +1956,21 @@ final class Workspace: Identifiable, ObservableObject { guard let paneId = sourcePaneId else { return nil } let inheritedConfig = inheritedTerminalConfig(preferredPanelId: panelId, inPane: paneId) + // Inherit working directory: prefer the source panel's reported cwd, + // fall back to the workspace's current directory. + let splitWorkingDirectory: String? = panelDirectories[panelId] + ?? (currentDirectory.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + ? nil : currentDirectory) +#if DEBUG + dlog("split.cwd panelId=\(panelId.uuidString.prefix(5)) panelDir=\(panelDirectories[panelId] ?? "nil") currentDir=\(currentDirectory) resolved=\(splitWorkingDirectory ?? "nil")") +#endif + // Create the new terminal panel. let newPanel = TerminalPanel( workspaceId: id, context: GHOSTTY_SURFACE_CONTEXT_SPLIT, configTemplate: inheritedConfig, + workingDirectory: splitWorkingDirectory, portOrdinal: portOrdinal ) panels[newPanel.id] = newPanel diff --git a/tests/test_split_cwd_inheritance.py b/tests/test_split_cwd_inheritance.py new file mode 100644 index 00000000..bc192939 --- /dev/null +++ b/tests/test_split_cwd_inheritance.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +""" +End-to-end test for split CWD inheritance. + +Verifies that new split panes and new workspace tabs inherit the current +working directory from the source terminal. + +Requires: + - cmux running with allowAll socket mode + - bash shell integration sourced (cmux-bash-integration.bash) + +Run with a tagged instance: + CMUX_TAG= python3 tests/test_split_cwd_inheritance.py +""" + +from __future__ import annotations + +import os +import sys +import time +from pathlib import Path + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from cmux import cmux, cmuxError # noqa: E402 + + +def _parse_sidebar_state(text: str) -> dict[str, str]: + data: dict[str, str] = {} + for raw in (text or "").splitlines(): + line = raw.rstrip("\n") + if not line or line.startswith(" "): + continue + if "=" not in line: + continue + k, v = line.split("=", 1) + data[k.strip()] = v.strip() + return data + + +def _wait_for(predicate, timeout: float, interval: float, label: str): + start = time.time() + last_error: Exception | None = None + while time.time() - start < timeout: + try: + value = predicate() + if value: + return value + except Exception as e: + last_error = e + time.sleep(interval) + extra = "" + if last_error is not None: + extra = f" Last error: {last_error}" + raise AssertionError(f"Timed out waiting for {label}.{extra}") + + +def _wait_for_focused_cwd( + client: cmux, + expected: str, + timeout: float = 12.0, +) -> dict[str, str]: + def pred(): + state = _parse_sidebar_state(client.sidebar_state()) + cwd = state.get("focused_cwd", "") + return state if cwd == expected else None + return _wait_for(pred, timeout=timeout, interval=0.3, label=f"focused_cwd={expected!r}") + + +def _send_cd_and_wait( + client: cmux, + target: str, + timeout: float = 12.0, +) -> dict[str, str]: + """cd to target and wait for sidebar focused_cwd to reflect it.""" + client.send(f"cd {target}\n") + return _wait_for_focused_cwd(client, target, timeout=timeout) + + +def main() -> int: + tag = os.environ.get("CMUX_TAG", "") + + socket_path = None + if tag: + socket_path = f"/tmp/cmux-debug-{tag}.sock" + client = cmux(socket_path=socket_path) + client.connect() + + # Use resolved paths to avoid /tmp -> /private/tmp symlink mismatch on macOS + test_dir_a = str(Path("/tmp/cmux_split_cwd_test_a").resolve()) + test_dir_b = str(Path("/tmp/cmux_split_cwd_test_b").resolve()) + os.makedirs(test_dir_a, exist_ok=True) + os.makedirs(test_dir_b, exist_ok=True) + + passed = 0 + failed = 0 + + def check(name: str, condition: bool, detail: str = ""): + nonlocal passed, failed + if condition: + print(f" PASS {name}") + passed += 1 + else: + print(f" FAIL {name}{': ' + detail if detail else ''}") + failed += 1 + + print("=== Split CWD Inheritance Tests ===") + + # --- Setup: cd to test_dir_a in workspace 1 --- + print(" [setup] cd to test_dir_a and wait for shell integration...") + _send_cd_and_wait(client, test_dir_a) + state = _parse_sidebar_state(client.sidebar_state()) + check("setup: focused_cwd is test_dir_a", state.get("focused_cwd") == test_dir_a, + f"got {state.get('focused_cwd')!r}") + + # --- Test 1: New split inherits test_dir_a --- + print(" [test1] creating right split from test_dir_a...") + split_result = client.new_split("right") + check("split created", bool(split_result)) + + # Wait for the new pane's shell to start and report cwd. + # The split should inherit test_dir_a from the source pane. + time.sleep(4) # wait for new bash to start + run PROMPT_COMMAND + + # Use report_pwd to manually seed the new pane's cwd if shell integration + # hasn't fired yet (macOS /bin/bash can be slow). + # Instead, just wait for sidebar to converge. + try: + state = _wait_for_focused_cwd(client, test_dir_a, timeout=15.0) + check("test1: split inherited test_dir_a", True) + except AssertionError as e: + # Check what we got instead + state = _parse_sidebar_state(client.sidebar_state()) + check("test1: split inherited test_dir_a", False, + f"focused_cwd={state.get('focused_cwd')!r}") + + # --- Test 2: New workspace tab inherits CWD --- + # First cd to test_dir_b so we have a different dir to inherit + print(" [test2] cd to test_dir_b, then creating new workspace tab...") + _send_cd_and_wait(client, test_dir_b) + + tab_result = client.new_tab() + check("new tab created", bool(tab_result)) + + # New workspace should inherit test_dir_b from the previous workspace + time.sleep(4) + try: + state = _wait_for_focused_cwd(client, test_dir_b, timeout=15.0) + check("test2: new workspace inherited test_dir_b", True) + except AssertionError as e: + state = _parse_sidebar_state(client.sidebar_state()) + check("test2: new workspace inherited test_dir_b", False, + f"focused_cwd={state.get('focused_cwd')!r}") + + print(f"\n{passed} passed, {failed} failed") + + client.close() + + # Cleanup + for d in [test_dir_a, test_dir_b]: + try: + os.rmdir(d) + except OSError: + pass + + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) From 4402a5b0ed46d44c4de81430f6b3dca16b268fe1 Mon Sep 17 00:00:00 2001 From: jleechan Date: Wed, 4 Mar 2026 22:03:05 -0800 Subject: [PATCH 089/232] Fix bash job spam: use disown instead of set +m set +m only suppresses notifications for jobs started after it runs. Jobs that complete between prompts still trigger Done output. Using disown removes jobs from bash job table entirely so bash never prints completion notifications for them. Co-Authored-By: Claude Opus 4.6 --- .../shell-integration/cmux-bash-integration.bash | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Resources/shell-integration/cmux-bash-integration.bash b/Resources/shell-integration/cmux-bash-integration.bash index 4576212c..1cfd1919 100644 --- a/Resources/shell-integration/cmux-bash-integration.bash +++ b/Resources/shell-integration/cmux-bash-integration.bash @@ -62,7 +62,7 @@ _cmux_report_tty_once() { _CMUX_TTY_REPORTED=1 { _cmux_send "report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" - } >/dev/null 2>&1 & + } >/dev/null 2>&1 & disown } _cmux_ports_kick() { @@ -74,7 +74,7 @@ _cmux_ports_kick() { _CMUX_PORTS_LAST_RUN=$SECONDS { _cmux_send "ports_kick --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" - } >/dev/null 2>&1 & + } >/dev/null 2>&1 & disown } _cmux_prompt_command() { @@ -82,11 +82,6 @@ _cmux_prompt_command() { [[ -n "$CMUX_TAB_ID" ]] || return 0 [[ -n "$CMUX_PANEL_ID" ]] || return 0 - # Suppress bash job-done notifications for background tasks spawned below. - # Without this, every completed async probe prints "[N] Done ..." to the terminal. - local _cmux_old_monitor="${-//[^m]/}" - set +m - local now=$SECONDS local pwd="$PWD" @@ -128,7 +123,7 @@ _cmux_prompt_command() { { local qpwd="${pwd//\"/\\\"}" _cmux_send "report_pwd \"${qpwd}\" --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" - } >/dev/null 2>&1 & + } >/dev/null 2>&1 & disown fi # Git branch/dirty can change without a directory change (e.g. `git checkout`), @@ -159,6 +154,7 @@ _cmux_prompt_command() { fi } >/dev/null 2>&1 & _CMUX_GIT_JOB_PID=$! + disown _CMUX_GIT_JOB_STARTED_AT=$now fi @@ -202,6 +198,7 @@ _cmux_prompt_command() { fi } >/dev/null 2>&1 & _CMUX_PR_JOB_PID=$! + disown _CMUX_PR_JOB_STARTED_AT=$now fi fi @@ -211,8 +208,6 @@ _cmux_prompt_command() { _cmux_ports_kick fi - # Restore job control if it was previously enabled. - [[ -n "$_cmux_old_monitor" ]] && set -m } _cmux_install_prompt_command() { From 00451417c2fecb5a075efff78b031a68380e7849 Mon Sep 17 00:00:00 2001 From: jleechan Date: Wed, 4 Mar 2026 22:09:11 -0800 Subject: [PATCH 090/232] Fix test false positives: verify new pane/tab identity before checking CWD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR review comments: - Test1: record original panel ID, wait for a *different* panel to report the expected CWD — prevents false pass when focus stays on source pane - Test2: record original tab ID, wait for a *different* tab with the expected CWD — prevents false pass when checking the old workspace - Remove unused `as e` exception bindings (Ruff F841) - Shell race (set -m) was already fixed in 4402a5b0 via disown Co-Authored-By: Claude Opus 4.6 --- tests/test_split_cwd_inheritance.py | 61 +++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/tests/test_split_cwd_inheritance.py b/tests/test_split_cwd_inheritance.py index bc192939..845c9a7c 100644 --- a/tests/test_split_cwd_inheritance.py +++ b/tests/test_split_cwd_inheritance.py @@ -59,12 +59,25 @@ def _wait_for_focused_cwd( client: cmux, expected: str, timeout: float = 12.0, + exclude_panel: str | None = None, ) -> dict[str, str]: + """Wait for focused_cwd to match expected. + + If exclude_panel is given, also require that focused_panel differs from + that value — ensuring we're checking the *new* pane, not the original. + """ def pred(): state = _parse_sidebar_state(client.sidebar_state()) cwd = state.get("focused_cwd", "") - return state if cwd == expected else None - return _wait_for(pred, timeout=timeout, interval=0.3, label=f"focused_cwd={expected!r}") + if cwd != expected: + return None + if exclude_panel and state.get("focused_panel", "") == exclude_panel: + return None + return state + label = f"focused_cwd={expected!r}" + if exclude_panel: + label += f" (panel != {exclude_panel})" + return _wait_for(pred, timeout=timeout, interval=0.3, label=label) def _send_cd_and_wait( @@ -115,42 +128,58 @@ def main() -> int: # --- Test 1: New split inherits test_dir_a --- print(" [test1] creating right split from test_dir_a...") + # Record the original panel so we can verify focus moves to the NEW pane. + original_panel = state.get("focused_panel", "") split_result = client.new_split("right") check("split created", bool(split_result)) - # Wait for the new pane's shell to start and report cwd. - # The split should inherit test_dir_a from the source pane. + # Wait for the NEW pane (different panel ID) to report test_dir_a. time.sleep(4) # wait for new bash to start + run PROMPT_COMMAND - - # Use report_pwd to manually seed the new pane's cwd if shell integration - # hasn't fired yet (macOS /bin/bash can be slow). - # Instead, just wait for sidebar to converge. try: - state = _wait_for_focused_cwd(client, test_dir_a, timeout=15.0) + state = _wait_for_focused_cwd( + client, test_dir_a, timeout=15.0, exclude_panel=original_panel, + ) + new_panel = state.get("focused_panel", "") + check("test1: focus moved to new pane", new_panel != original_panel, + f"original={original_panel!r}, current={new_panel!r}") check("test1: split inherited test_dir_a", True) - except AssertionError as e: - # Check what we got instead + except AssertionError: state = _parse_sidebar_state(client.sidebar_state()) check("test1: split inherited test_dir_a", False, - f"focused_cwd={state.get('focused_cwd')!r}") + f"focused_cwd={state.get('focused_cwd')!r}, focused_panel={state.get('focused_panel')!r}") # --- Test 2: New workspace tab inherits CWD --- # First cd to test_dir_b so we have a different dir to inherit print(" [test2] cd to test_dir_b, then creating new workspace tab...") _send_cd_and_wait(client, test_dir_b) + state = _parse_sidebar_state(client.sidebar_state()) + original_tab = state.get("tab", "") tab_result = client.new_tab() check("new tab created", bool(tab_result)) - # New workspace should inherit test_dir_b from the previous workspace + # New workspace should be a different tab AND inherit test_dir_b time.sleep(4) try: - state = _wait_for_focused_cwd(client, test_dir_b, timeout=15.0) + def _new_tab_with_cwd(): + s = _parse_sidebar_state(client.sidebar_state()) + tab_id = s.get("tab", "") + cwd = s.get("focused_cwd", "") + if tab_id != original_tab and cwd == test_dir_b: + return s + return None + + state = _wait_for( + _new_tab_with_cwd, timeout=15.0, interval=0.3, + label=f"new tab with focused_cwd={test_dir_b!r}", + ) + check("test2: focus moved to new tab", state.get("tab") != original_tab, + f"original={original_tab!r}, current={state.get('tab')!r}") check("test2: new workspace inherited test_dir_b", True) - except AssertionError as e: + except AssertionError: state = _parse_sidebar_state(client.sidebar_state()) check("test2: new workspace inherited test_dir_b", False, - f"focused_cwd={state.get('focused_cwd')!r}") + f"focused_cwd={state.get('focused_cwd')!r}, tab={state.get('tab')!r}") print(f"\n{passed} passed, {failed} failed") From 00b7357fdc7fdcd4670a003ee6cb7979c0663eaf Mon Sep 17 00:00:00 2001 From: jleechan Date: Wed, 4 Mar 2026 22:33:55 -0800 Subject: [PATCH 091/232] Add split CWD inheritance demo GIF Visual proof of fix: split panes inherit CWD from the source panel. Co-Authored-By: Claude Opus 4.6 --- docs/assets/split-cwd-inheritance-demo.gif | Bin 0 -> 849043 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/split-cwd-inheritance-demo.gif diff --git a/docs/assets/split-cwd-inheritance-demo.gif b/docs/assets/split-cwd-inheritance-demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..5a1c1c9ccf14ba2e43584224a239be8fd97c6902 GIT binary patch literal 849043 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 F{tsg*JP7~* literal 0 HcmV?d00001 From bf28f5df4b1ab6a154bfda9a8d16a9de9faf6804 Mon Sep 17 00:00:00 2001 From: sminamot Date: Thu, 5 Mar 2026 17:24:40 +0900 Subject: [PATCH 092/232] Skip Ctrl fast path during IME composition (#790) During IME composition (e.g. Japanese input), Ctrl+H should delete composing characters via the IME, not bypass it and send a backspace directly to the terminal. Add a hasMarkedText() check so the fast path is only taken when no IME composition is active, letting interpretKeyEvents() handle the key instead. Co-authored-by: Claude Opus 4.6 --- Sources/GhosttyTerminalView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index e461e9d1..7855f1fc 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -3992,7 +3992,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { // AppKit text interpretation and send a single deterministic Ghostty key event. // This avoids intermittent drops after rapid split close/reparent transitions. let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) - if flags.contains(.control) && !flags.contains(.command) && !flags.contains(.option) { + if flags.contains(.control) && !flags.contains(.command) && !flags.contains(.option) && !hasMarkedText() { ghostty_surface_set_focus(surface, true) var keyEvent = ghostty_input_key_s() keyEvent.action = event.isARepeat ? GHOSTTY_ACTION_REPEAT : GHOSTTY_ACTION_PRESS From 017f4416d0dc25d59a7558750533a93b3b5d8f38 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:48:09 -0800 Subject: [PATCH 093/232] Prompt bug reporters to test NIGHTLY (#930) --- .github/ISSUE_TEMPLATE/bug_report.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0b5735eb..81a47cdc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -46,6 +46,18 @@ body: validations: required: true + - type: dropdown + id: nightly-repro + attributes: + label: Can you reproduce this on cmux NIGHTLY? + description: "Please test with the latest NIGHTLY build first: https://github.com/manaflow-ai/cmux?tab=readme-ov-file#nightly-builds" + options: + - Yes, it still reproduces on NIGHTLY + - No, it does not reproduce on NIGHTLY + - I could not test NIGHTLY + validations: + required: true + - type: textarea id: description attributes: From 4351c3cf185cf42893c5b8cac4e81864a6163212 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:20:23 -0800 Subject: [PATCH 094/232] Fix notify CLI focus-steal regression and add UI test --- Sources/AppDelegate.swift | 18 ++ .../MultiWindowNotificationsUITests.swift | 195 ++++++++++++++++++ 2 files changed, 213 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 809b432c..9df55688 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1683,6 +1683,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent PostHogAnalytics.shared.startIfNeeded() } + let forceDuplicateLaunchObserver = env["CMUX_UI_TEST_ENABLE_DUPLICATE_LAUNCH_OBSERVER"] == "1" + // UI tests frequently time out waiting for the main window if we do heavyweight // LaunchServices registration / single-instance enforcement synchronously at startup. // Skip these during XCTest (the app-under-test) so the window can appear quickly. @@ -1693,6 +1695,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent self.enforceSingleInstance() self.observeDuplicateLaunches() } + } else if forceDuplicateLaunchObserver { + // Some UI regressions specifically exercise launch-observer behavior while still + // running under XCTest. Allow an explicit opt-in for those cases only. + DispatchQueue.main.async { [weak self] in + self?.observeDuplicateLaunches() + } } NSWindow.allowsAutomaticWindowTabbing = false disableNativeTabbingShortcut() @@ -7621,6 +7629,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private func observeDuplicateLaunches() { guard let bundleId = Bundle.main.bundleIdentifier else { return } + let embeddedCLIURL = Bundle.main.bundleURL + .appendingPathComponent("Contents/Resources/bin/cmux", isDirectory: false) + .standardizedFileURL + .resolvingSymlinksInPath() let currentPid = ProcessInfo.processInfo.processIdentifier workspaceObserver = NSWorkspace.shared.notificationCenter.addObserver( @@ -7631,6 +7643,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent guard self != nil else { return } guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return } guard app.bundleIdentifier == bundleId, app.processIdentifier != currentPid else { return } + if let executableURL = app.executableURL? + .standardizedFileURL + .resolvingSymlinksInPath(), + executableURL == embeddedCLIURL { + return + } app.terminate() if !app.isTerminated { diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index f698b9af..89948f66 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -190,6 +190,72 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertFalse(after.contains(marker), "Expected typing to be blocked while empty notifications popover is open") } + func testNotifyCLIDoesNotStealFocusAcrossWindows() throws { + let app = XCUIApplication() + app.launchArguments += ["-socketControlMode", "allowAll"] + app.launchEnvironment["CMUX_UI_TEST_MULTI_WINDOW_NOTIF_SETUP"] = "1" + app.launchEnvironment["CMUX_UI_TEST_MULTI_WINDOW_NOTIF_PATH"] = dataPath + app.launchEnvironment["CMUX_SOCKET_PATH"] = socketPath + app.launchEnvironment["CMUX_SOCKET_MODE"] = "allowAll" + app.launchEnvironment["CMUX_SOCKET_ENABLE"] = "1" + app.launchEnvironment["CMUX_UI_TEST_SOCKET_SANITY"] = "1" + app.launchEnvironment["CMUX_UI_TEST_ENABLE_DUPLICATE_LAUNCH_OBSERVER"] = "1" + app.launchEnvironment["CMUX_TAG"] = launchTag + app.launch() + XCTAssertTrue( + ensureForegroundAfterLaunch(app, timeout: 12.0), + "Expected app to launch for notify focus regression test. state=\(app.state.rawValue)" + ) + XCTAssertTrue( + waitForData(keys: ["tabId2"], timeout: 15.0), + "Expected multi-window notification setup data" + ) + + guard let tabId2 = loadData()?["tabId2"], !tabId2.isEmpty else { + XCTFail("Missing setup workspace id") + return + } + + XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) + + guard let resolvedPath = resolveSocketPath(timeout: 8.0) else { + throw XCTSkip("Control socket unavailable in this test environment. requested=\(socketPath)") + } + socketPath = resolvedPath + let pingResponse = waitForSocketPong(timeout: 8.0) + guard pingResponse == "PONG" else { + throw XCTSkip("Control socket did not respond in time. path=\(socketPath) response=\(pingResponse ?? "")") + } + + guard let surfaceId = firstSurfaceId(forWorkspaceId: tabId2) else { + XCTFail("Expected at least one surface in workspace \(tabId2)") + return + } + + let finder = XCUIApplication(bundleIdentifier: "com.apple.finder") + finder.activate() + XCTAssertTrue( + waitForAppToLeaveForeground(app, timeout: 8.0), + "Expected cmux to move to background before sending notify command. state=\(app.state.rawValue)" + ) + + let title = "focus-regression-\(UUID().uuidString.prefix(8))" + let notifyResult = runCmuxNotify( + socketPath: socketPath, + workspaceId: tabId2, + surfaceId: surfaceId, + title: title + ) + XCTAssertEqual(notifyResult.terminationStatus, 0, "Expected `cmux notify` to succeed. stderr=\(notifyResult.stderr)") + XCTAssertTrue(notifyResult.stdout.contains("OK"), "Expected notify command to return OK. stdout=\(notifyResult.stdout)") + + RunLoop.current.run(until: Date().addingTimeInterval(0.5)) + XCTAssertFalse( + app.state == .runningForeground, + "Expected cmux to remain in background after `cmux notify`. state=\(app.state.rawValue)" + ) + } + private func clickNotificationPopoverRowAndWaitForFocusChange( button: XCUIElement, app: XCUIApplication, @@ -287,6 +353,135 @@ final class MultiWindowNotificationsUITests: XCTestCase { return socketCommand("ping") ?? lastResponse } + private func waitForAppToLeaveForeground(_ app: XCUIApplication, timeout: TimeInterval) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if app.state != .runningForeground { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + return app.state != .runningForeground + } + + private func firstSurfaceId(forWorkspaceId workspaceId: String) -> String? { + guard let response = socketCommand("list_surfaces \(workspaceId)"), + !response.isEmpty, + !response.hasPrefix("ERROR"), + response != "No surfaces" else { + return nil + } + + for line in response.split(separator: "\n", omittingEmptySubsequences: true) { + let parts = line.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false) + guard parts.count == 2 else { continue } + let candidate = String(parts[1]).trimmingCharacters(in: .whitespacesAndNewlines) + if UUID(uuidString: candidate) != nil { + return candidate + } + } + return nil + } + + private func runCmuxNotify( + socketPath: String, + workspaceId: String, + surfaceId: String, + title: String + ) -> (terminationStatus: Int32, stdout: String, stderr: String) { + let process = Process() + let cliPath = resolveCmuxCLIPath() + var args = [ + "--socket", + socketPath, + "notify", + "--workspace", + workspaceId, + "--surface", + surfaceId, + "--title", + title, + "--subtitle", + "ui-test", + "--body", + "focus-regression" + ] + if let cliPath { + process.executableURL = URL(fileURLWithPath: cliPath) + } else { + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + args.insert("cmux", at: 0) + } + process.arguments = args + + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + process.standardOutput = stdoutPipe + process.standardError = stderrPipe + + do { + try process.run() + process.waitUntilExit() + } catch { + return ( + terminationStatus: -1, + stdout: "", + stderr: "Failed to run cmux notify: \(error.localizedDescription) (cliPath=\(cliPath ?? "env:cmux"))" + ) + } + + let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() + let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() + let stdout = String(data: stdoutData, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + let stderr = String(data: stderrData, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return (process.terminationStatus, stdout, stderr) + } + + private func resolveCmuxCLIPath() -> String? { + let fileManager = FileManager.default + let env = ProcessInfo.processInfo.environment + var candidates: [String] = [] + + for key in ["CMUX_UI_TEST_CLI_PATH", "CMUXTERM_CLI"] { + if let value = env[key], !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + candidates.append(value) + } + } + + if let builtProductsDir = env["BUILT_PRODUCTS_DIR"], !builtProductsDir.isEmpty { + candidates.append("\(builtProductsDir)/cmux DEV.app/Contents/Resources/bin/cmux") + candidates.append("\(builtProductsDir)/cmux.app/Contents/Resources/bin/cmux") + candidates.append("\(builtProductsDir)/cmux") + } + + if let hostPath = env["TEST_HOST"], !hostPath.isEmpty { + let hostURL = URL(fileURLWithPath: hostPath) + let productsDir = hostURL + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .path + candidates.append("\(productsDir)/cmux DEV.app/Contents/Resources/bin/cmux") + candidates.append("\(productsDir)/cmux.app/Contents/Resources/bin/cmux") + candidates.append("\(productsDir)/cmux") + } + + candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux DEV.app/Contents/Resources/bin/cmux") + candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux.app/Contents/Resources/bin/cmux") + candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux") + + for path in candidates { + if fileManager.isExecutableFile(atPath: path) { + return URL(fileURLWithPath: path).resolvingSymlinksInPath().path + } + } + + return nil + } + private func resolveSocketPath(timeout: TimeInterval) -> String? { let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { From 3a5bd8494cc0b908b8af10117c10ad4089547b41 Mon Sep 17 00:00:00 2001 From: atani Date: Thu, 5 Mar 2026 19:31:23 +0900 Subject: [PATCH 095/232] fix: avoid NSTextView tracking loop in omnibar mouseDown (#928) * fix: avoid NSTextView tracking loop in omnibar mouseDown (#917) Replace the synthetic mouseUp timeout workaround with direct cursor positioning via NSTextView.characterIndexForInsertion(at:). The previous approach posted a fake mouseUp event via NSApp.postEvent after 3 seconds, but the NSTextView tracking loop does not always dequeue events from the application event queue when stuck in an infinite NSTextLayoutManager.enumerateTextLayoutFragments cycle, so the hang persisted. The new approach bypasses super.mouseDown entirely when the field editor is already active, positioning the cursor (or extending the selection with Shift+click) without entering the tracking loop. Drag-to-select is not supported in this code path, but for a single-line omnibar this is an acceptable trade-off. * fix: handle double-click, UTF-16 length, and shift-click anchor Address review feedback: - Forward double/triple-click events to editor.mouseDown(with:) to preserve word and line selection without entering NSTextField's tracking loop - Use (editor.string as NSString).length instead of String.count for NSRange clamping (NSRange uses UTF-16 indices) - Track shift-click anchor independently via shiftClickAnchor property to correctly handle bidirectional selection extension * fix: reset shiftClickAnchor on keyDown to prevent stale anchor Clear the shift-click selection anchor whenever a key is pressed, so that keyboard navigation (arrow keys, Shift+arrow, Home/End, etc.) properly invalidates the mouse-originated anchor. A subsequent Shift+click will then use the current selection position as anchor instead of a stale value from a prior mouse interaction. * fix: reset shiftClickAnchor in performKeyEquivalent and on re-focus Key equivalents (Cmd+A, Cmd+V, etc.) bypass keyDown and go through performKeyEquivalent, so the anchor must also be cleared there. Similarly, re-focusing the field (currentEditor() == nil path) should reset the anchor since selectAll changes the selection state. --- Sources/Panels/BrowserPanelView.swift | 81 ++++++++++++++++++--------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index f811b468..c965af1b 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -2247,6 +2247,8 @@ func browserOmnibarShouldReacquireFocusAfterEndEditing( private final class OmnibarNativeTextField: NSTextField { var onPointerDown: (() -> Void)? var onHandleKeyEvent: ((NSEvent, NSTextView?) -> Bool)? + /// Anchor index for Shift+click selection extension, reset on non-shift clicks. + private var shiftClickAnchor: Int? override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -2274,37 +2276,65 @@ private final class OmnibarNativeTextField: NSTextField { // enters an infinite invalidation cycle (e.g. under memory pressure). window?.makeFirstResponder(self) currentEditor()?.selectAll(nil) + shiftClickAnchor = nil } else { - // Already editing — allow normal click-to-place-cursor and drag-to-select. - // Guard against a stuck tracking loop by posting a synthetic mouseUp after - // a timeout. IMPORTANT: must use a background queue because super.mouseDown - // blocks the main thread in NSTextView's tracking loop, so - // DispatchQueue.main.asyncAfter would never fire. - let cancelled = DispatchWorkItem { /* sentinel */ } - let windowNumber = window?.windowNumber ?? 0 - let location = event.locationInWindow - DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 3.0) { - guard !cancelled.isCancelled else { return } - if let fakeUp = NSEvent.mouseEvent( - with: .leftMouseUp, - location: location, - modifierFlags: [], - timestamp: ProcessInfo.processInfo.systemUptime, - windowNumber: windowNumber, - context: nil, - eventNumber: 0, - clickCount: 1, - pressure: 0.0 - ) { - NSApp.postEvent(fakeUp, atStart: true) - } + // Already editing — place the cursor at the click position without calling + // super.mouseDown, which enters NSTextView's mouse-tracking loop. That loop + // can spin forever when NSTextLayoutManager.enumerateTextLayoutFragments hits + // an infinite invalidation cycle (see #917). The previous mitigation posted a + // synthetic mouseUp via NSApp.postEvent after a timeout, but the tracking loop + // does not always dequeue events from the application event queue, so the hang + // persisted. By positioning the cursor ourselves we avoid the tracking loop + // entirely. Drag-to-select is not supported in this path, but for a single-line + // omnibar this is an acceptable trade-off (double-click to select word and + // Shift+click to extend selection still work via the field editor). + guard let editor = currentEditor() as? NSTextView else { + super.mouseDown(with: event) + return + } + + // Double/triple-click: forward directly to the field editor (NSTextView) + // which handles word and line selection internally. This bypasses + // NSTextField's super.mouseDown (and its problematic tracking loop) + // while preserving multi-click semantics. + if event.clickCount > 1 { + editor.mouseDown(with: event) + shiftClickAnchor = nil + return + } + + let localPoint = editor.convert(event.locationInWindow, from: nil) + let index = editor.characterIndexForInsertion(at: localPoint) + let textLength = (editor.string as NSString).length + let safeIndex = min(index, textLength) + + if event.modifierFlags.contains(.shift) { + // Shift+click: extend the existing selection to the clicked position. + // Use stored anchor to handle bidirectional extension correctly; + // NSRange.location is always the lower index so it cannot serve as + // a directional anchor on its own. + let sel = editor.selectedRange() + let anchor = shiftClickAnchor ?? sel.location + shiftClickAnchor = anchor + let newRange: NSRange + if safeIndex >= anchor { + newRange = NSRange(location: anchor, length: safeIndex - anchor) + } else { + newRange = NSRange(location: safeIndex, length: anchor - safeIndex) + } + editor.setSelectedRange(newRange) + } else { + shiftClickAnchor = nil + editor.setSelectedRange(NSRange(location: safeIndex, length: 0)) } - super.mouseDown(with: event) - cancelled.cancel() } } override func keyDown(with event: NSEvent) { + // Reset shift-click anchor on any keyboard input so that a subsequent + // Shift+click uses the post-keyboard selection as its anchor, not a + // stale value from a prior mouse interaction. + shiftClickAnchor = nil if (currentEditor() as? NSTextView)?.hasMarkedText() == true { super.keyDown(with: event) return @@ -2316,6 +2346,7 @@ private final class OmnibarNativeTextField: NSTextField { } override func performKeyEquivalent(with event: NSEvent) -> Bool { + shiftClickAnchor = nil if (currentEditor() as? NSTextView)?.hasMarkedText() == true { return super.performKeyEquivalent(with: event) } From efdfd76484ab433be6045d3a0cd06c173b7371ba Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:39:46 -0800 Subject: [PATCH 096/232] Harden notify UI test socket resolution --- .../MultiWindowNotificationsUITests.swift | 97 ++++++++++++++++--- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 89948f66..69dcf00c 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -218,13 +218,15 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) - guard let resolvedPath = resolveSocketPath(timeout: 8.0) else { - throw XCTSkip("Control socket unavailable in this test environment. requested=\(socketPath)") + guard let resolvedPath = resolveSocketPath(timeout: 8.0, requiredWorkspaceId: tabId2) else { + XCTFail("Control socket unavailable in this test environment. requested=\(socketPath)") + return } socketPath = resolvedPath let pingResponse = waitForSocketPong(timeout: 8.0) guard pingResponse == "PONG" else { - throw XCTSkip("Control socket did not respond in time. path=\(socketPath) response=\(pingResponse ?? "")") + XCTFail("Control socket did not respond in time. path=\(socketPath) response=\(pingResponse ?? "")") + return } guard let surfaceId = firstSurfaceId(forWorkspaceId: tabId2) else { @@ -482,33 +484,106 @@ final class MultiWindowNotificationsUITests: XCTestCase { return nil } - private func resolveSocketPath(timeout: TimeInterval) -> String? { + private func resolveSocketPath(timeout: TimeInterval, requiredWorkspaceId: String? = nil) -> String? { + let primaryCandidates = expectedSocketCandidates(includeGlobalFallback: false) + let fallbackCandidates: [String] + if let requiredWorkspaceId, !requiredWorkspaceId.isEmpty { + fallbackCandidates = expectedSocketCandidates(includeGlobalFallback: true) + .filter { !primaryCandidates.contains($0) } + } else { + fallbackCandidates = [] + } + let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { - for candidate in expectedSocketCandidates() { + for candidate in primaryCandidates { guard FileManager.default.fileExists(atPath: candidate) else { continue } - if socketRespondsToPing(at: candidate) { + if socketRespondsToPing(at: candidate), + socketMatchesRequiredWorkspace(candidate, workspaceId: requiredWorkspaceId) { + return candidate + } + } + for candidate in fallbackCandidates { + guard FileManager.default.fileExists(atPath: candidate) else { continue } + if socketRespondsToPing(at: candidate), + socketMatchesRequiredWorkspace(candidate, workspaceId: requiredWorkspaceId) { return candidate } } RunLoop.current.run(until: Date().addingTimeInterval(0.05)) } - for candidate in expectedSocketCandidates() { + for candidate in primaryCandidates { guard FileManager.default.fileExists(atPath: candidate) else { continue } - if socketRespondsToPing(at: candidate) { + if socketRespondsToPing(at: candidate), + socketMatchesRequiredWorkspace(candidate, workspaceId: requiredWorkspaceId) { + return candidate + } + } + for candidate in fallbackCandidates { + guard FileManager.default.fileExists(atPath: candidate) else { continue } + if socketRespondsToPing(at: candidate), + socketMatchesRequiredWorkspace(candidate, workspaceId: requiredWorkspaceId) { return candidate } } return nil } - private func expectedSocketCandidates() -> [String] { + private func expectedSocketCandidates(includeGlobalFallback: Bool) -> [String] { var candidates = [socketPath] let taggedDebugSocket = "/tmp/cmux-debug-\(launchTag).sock" - if taggedDebugSocket != socketPath { + if !taggedDebugSocket.isEmpty { candidates.append(taggedDebugSocket) } - return candidates + if includeGlobalFallback { + candidates.append(contentsOf: discoverTmpSocketCandidates(limit: 12)) + candidates.append("/tmp/cmux-debug.sock") + candidates.append("/tmp/cmux.sock") + } + + var unique: [String] = [] + var seen = Set() + for candidate in candidates { + if seen.insert(candidate).inserted { + unique.append(candidate) + } + } + return unique + } + + private func socketMatchesRequiredWorkspace(_ candidatePath: String, workspaceId: String?) -> Bool { + guard let workspaceId, !workspaceId.isEmpty else { return true } + let originalPath = socketPath + socketPath = candidatePath + defer { socketPath = originalPath } + + guard let response = socketCommand("list_surfaces \(workspaceId)"), + !response.isEmpty, + !response.hasPrefix("ERROR"), + response != "No surfaces" else { + return false + } + return true + } + + private func discoverTmpSocketCandidates(limit: Int) -> [String] { + let tmpPath = "/tmp" + guard let entries = try? FileManager.default.contentsOfDirectory(atPath: tmpPath) else { + return [] + } + + let matches = entries.filter { $0.hasPrefix("cmux") && $0.hasSuffix(".sock") } + let sorted = matches.compactMap { entry -> (path: String, mtime: Date)? in + let fullPath = (tmpPath as NSString).appendingPathComponent(entry) + guard let attrs = try? FileManager.default.attributesOfItem(atPath: fullPath) else { + return nil + } + let mtime = (attrs[.modificationDate] as? Date) ?? .distantPast + return (fullPath, mtime) + } + .sorted { $0.mtime > $1.mtime } + + return Array(sorted.prefix(limit)).map(\.path) } private func socketRespondsToPing(at path: String) -> Bool { From c8487e1457dc1b81a7f39b7d8b9fda700af78164 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:47:36 -0800 Subject: [PATCH 097/232] Stabilize notify focus regression socket detection --- cmuxUITests/MultiWindowNotificationsUITests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 69dcf00c..4567bb63 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -218,12 +218,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) - guard let resolvedPath = resolveSocketPath(timeout: 8.0, requiredWorkspaceId: tabId2) else { + guard let resolvedPath = resolveSocketPath(timeout: 20.0, requiredWorkspaceId: tabId2) else { XCTFail("Control socket unavailable in this test environment. requested=\(socketPath)") return } socketPath = resolvedPath - let pingResponse = waitForSocketPong(timeout: 8.0) + let pingResponse = waitForSocketPong(timeout: 20.0) guard pingResponse == "PONG" else { XCTFail("Control socket did not respond in time. path=\(socketPath) response=\(pingResponse ?? "")") return From 11197a49c4a8b717f263fccd452170b07947a7d4 Mon Sep 17 00:00:00 2001 From: Yuki Yamashina Date: Thu, 5 Mar 2026 20:03:32 +0900 Subject: [PATCH 098/232] Fix startup SIGSEGV: pre-warm locale before SentrySDK.start (#927) Locale initialization on the main thread (os.locale.ensureLocale / NSLocale._preferredLanguages) can race with Sentry's background init thread calling posix.getenv, causing a SIGSEGV and leaving the SDK disabled. Related to #836 --- Sources/AppDelegate.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 809b432c..bae418c0 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1656,6 +1656,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent #endif if telemetryEnabled { + // Pre-warm locale before Sentry to avoid a startup data race. + // Locale initialization (os.locale.ensureLocale / NSLocale._preferredLanguages) + // on the main thread can race with Sentry's background init thread + // calling posix.getenv, causing a SIGSEGV ~134ms after launch. + // Forcing locale access here before SentrySDK.start eliminates the race. + // Related to: #836 + _ = Locale.current + _ = NSLocale.preferredLanguages + SentrySDK.start { options in options.dsn = "https://ecba1ec90ecaee02a102fba931b6d2b3@o4507547940749312.ingest.us.sentry.io/4510796264636416" #if DEBUG From 3d6645cb18d08d0f51a625d3c38768a82d8958af Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 03:05:35 -0800 Subject: [PATCH 099/232] Add PR template section for demo videos (#933) * Add PR template section for demo videos * Remove PR template author exemption note * Refine PR template with review trigger checklist --- .github/pull_request_template.md | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..8e1723cd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,33 @@ +## Summary + +- What changed? +- Why? + +## Testing + +- How did you test this change? +- What did you verify manually? + +## Demo Video + +For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link). + +- Video URL or attachment: + +## Review Trigger (Copy/Paste as PR comment) + +```text +@codex review +@coderabbitai review +@greptile-apps review +@cubic-dev-ai review +``` + +## Checklist + +- [ ] I tested the change locally +- [ ] I added or updated tests for behavior changes +- [ ] I updated docs/changelog if needed +- [ ] I requested bot reviews after my latest commit (copy/paste block above or equivalent) +- [ ] All code review bot comments are resolved +- [ ] All human review comments are resolved From 69cfce9596a27683e9fd79ec644c35246db9a47d Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 03:14:52 -0800 Subject: [PATCH 100/232] Stabilize notify focus UI test socket and surface waits --- .../MultiWindowNotificationsUITests.swift | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 4567bb63..4f875312 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -229,8 +229,8 @@ final class MultiWindowNotificationsUITests: XCTestCase { return } - guard let surfaceId = firstSurfaceId(forWorkspaceId: tabId2) else { - XCTFail("Expected at least one surface in workspace \(tabId2)") + guard let surfaceId = waitForSurfaceId(forWorkspaceId: tabId2, timeout: 12.0) else { + XCTFail("Expected at least one surface in workspace \(tabId2). socket=\(socketPath)") return } @@ -385,6 +385,17 @@ final class MultiWindowNotificationsUITests: XCTestCase { return nil } + private func waitForSurfaceId(forWorkspaceId workspaceId: String, timeout: TimeInterval) -> String? { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if let surfaceId = firstSurfaceId(forWorkspaceId: workspaceId) { + return surfaceId + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + return firstSurfaceId(forWorkspaceId: workspaceId) + } + private func runCmuxNotify( socketPath: String, workspaceId: String, @@ -498,8 +509,9 @@ final class MultiWindowNotificationsUITests: XCTestCase { while Date() < deadline { for candidate in primaryCandidates { guard FileManager.default.fileExists(atPath: candidate) else { continue } - if socketRespondsToPing(at: candidate), - socketMatchesRequiredWorkspace(candidate, workspaceId: requiredWorkspaceId) { + // Primary candidate is the explicitly requested CMUX_SOCKET_PATH. If it responds, + // prefer it even before workspace contents are fully initialized. + if socketRespondsToPing(at: candidate) { return candidate } } @@ -514,8 +526,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { } for candidate in primaryCandidates { guard FileManager.default.fileExists(atPath: candidate) else { continue } - if socketRespondsToPing(at: candidate), - socketMatchesRequiredWorkspace(candidate, workspaceId: requiredWorkspaceId) { + if socketRespondsToPing(at: candidate) { return candidate } } From 1408cbb68c6450673684ccc88f03bdeaf3632924 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 04:00:04 -0800 Subject: [PATCH 101/232] Flush PostHog hourly active events immediately (#934) * PostHog: flush hourly active captures immediately * PostHog: move focus-triggered active tracking off main queue * Lighten PostHog app focus hot path * Address review: dedupe PostHog event names --- Sources/AppDelegate.swift | 24 +++-- Sources/PostHogAnalytics.swift | 152 +++++++++++++++++++++++------ cmuxTests/GhosttyConfigTests.swift | 6 ++ 3 files changed, 145 insertions(+), 37 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index bae418c0..07a7ba8b 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1377,10 +1377,13 @@ func shouldSuppressWindowMoveForFolderDrag(window: NSWindow, event: NSEvent) -> final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, NSMenuItemValidation { static var shared: AppDelegate? - private func isRunningUnderXCTest(_ env: [String: String]) -> Bool { - // On some macOS/Xcode setups, the app-under-test process doesn't get - // `XCTestConfigurationFilePath`. Use a broader set of signals so UI tests - // can reliably skip heavyweight startup work and bring up a window. + private static let cachedIsRunningUnderXCTest = detectRunningUnderXCTest(ProcessInfo.processInfo.environment) + + private var isRunningUnderXCTestCached: Bool { + Self.cachedIsRunningUnderXCTest + } + + private static func detectRunningUnderXCTest(_ env: [String: String]) -> Bool { if env["XCTestConfigurationFilePath"] != nil { return true } if env["XCTestBundlePath"] != nil { return true } if env["XCTestSessionIdentifier"] != nil { return true } @@ -1391,6 +1394,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return false } + private func isRunningUnderXCTest(_ env: [String: String]) -> Bool { + // On some macOS/Xcode setups, the app-under-test process doesn't get + // `XCTestConfigurationFilePath`. Use a broader set of signals so UI tests + // can reliably skip heavyweight startup work and bring up a window. + Self.detectRunningUnderXCTest(env) + } + private final class MainWindowContext { let windowId: UUID let tabManager: TabManager @@ -1794,10 +1804,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent sentryBreadcrumb("app.didBecomeActive", category: "lifecycle", data: [ "tabCount": tabManager?.tabs.count ?? 0 ]) - let env = ProcessInfo.processInfo.environment - if TelemetrySettings.enabledForCurrentLaunch && !isRunningUnderXCTest(env) { - PostHogAnalytics.shared.trackDailyActive(reason: "didBecomeActive") - PostHogAnalytics.shared.trackHourlyActive(reason: "didBecomeActive") + if TelemetrySettings.enabledForCurrentLaunch && !isRunningUnderXCTestCached { + PostHogAnalytics.shared.trackActive(reason: "didBecomeActive") } guard let notificationStore else { return } diff --git a/Sources/PostHogAnalytics.swift b/Sources/PostHogAnalytics.swift index 031533aa..90eb071f 100644 --- a/Sources/PostHogAnalytics.swift +++ b/Sources/PostHogAnalytics.swift @@ -2,7 +2,6 @@ import AppKit import Foundation import PostHog -@MainActor final class PostHogAnalytics { static let shared = PostHogAnalytics() @@ -12,12 +11,27 @@ final class PostHogAnalytics { // PostHog Cloud US default (matches other cmux properties). private let host = "https://us.i.posthog.com" + private let dailyActiveEvent = "cmux_daily_active" + private let hourlyActiveEvent = "cmux_hourly_active" + private let lastActiveDayUTCKey = "posthog.lastActiveDayUTC" private let lastActiveHourUTCKey = "posthog.lastActiveHourUTC" + private let workQueue: DispatchQueue + private let workQueueSpecificKey = DispatchSpecificKey() + private let utcHourFormatter: DateFormatter + private let utcDayFormatter: DateFormatter + private var didStart = false private var activeCheckTimer: Timer? + private init() { + workQueue = DispatchQueue(label: "com.cmux.posthog.analytics", qos: .utility) + utcHourFormatter = Self.makeUTCFormatter("yyyy-MM-dd'T'HH") + utcDayFormatter = Self.makeUTCFormatter("yyyy-MM-dd") + workQueue.setSpecific(key: workQueueSpecificKey, value: ()) + } + private var isEnabled: Bool { guard TelemetrySettings.enabledForCurrentLaunch else { return false } #if DEBUG @@ -29,6 +43,44 @@ final class PostHogAnalytics { } func startIfNeeded() { + dispatchAsyncOnWorkQueue { [weak self] in + self?.startIfNeededOnWorkQueue() + } + } + + func trackActive(reason: String) { + dispatchAsyncOnWorkQueue { [weak self] in + guard let self else { return } + + let didCaptureDaily = self.trackDailyActiveOnWorkQueue(reason: reason, flush: false) + let didCaptureHourly = self.trackHourlyActiveOnWorkQueue(reason: reason, flush: false) + if didCaptureDaily || didCaptureHourly { + // On app focus we can capture both events; flush once to reduce extra work. + PostHogSDK.shared.flush() + } + } + } + + func trackDailyActive(reason: String) { + dispatchAsyncOnWorkQueue { [weak self] in + self?.trackDailyActiveOnWorkQueue(reason: reason, flush: true) + } + } + + func trackHourlyActive(reason: String) { + dispatchAsyncOnWorkQueue { [weak self] in + self?.trackHourlyActiveOnWorkQueue(reason: reason, flush: true) + } + } + + func flush() { + dispatchSyncOnWorkQueue { + guard didStart else { return } + PostHogSDK.shared.flush() + } + } + + private func startIfNeededOnWorkQueue() { guard !didStart else { return } guard isEnabled else { return } @@ -49,31 +101,40 @@ final class PostHogAnalytics { didStart = true + scheduleActiveCheckTimer() + } + + private func scheduleActiveCheckTimer() { // If the app stays in the foreground across midnight, `applicationDidBecomeActive` // won't fire again, so a periodic check avoids undercounting those users. - activeCheckTimer?.invalidate() - activeCheckTimer = Timer.scheduledTimer(withTimeInterval: 30 * 60, repeats: true) { [weak self] _ in + DispatchQueue.main.async { [weak self] in guard let self else { return } - guard NSApp.isActive else { return } - self.trackDailyActive(reason: "activeTimer") - self.trackHourlyActive(reason: "activeTimer") + self.activeCheckTimer?.invalidate() + self.activeCheckTimer = Timer.scheduledTimer(withTimeInterval: 30 * 60, repeats: true) { [weak self] _ in + guard let self else { return } + guard NSApp.isActive else { return } + self.trackActive(reason: "activeTimer") + } } } - func trackDailyActive(reason: String) { - startIfNeeded() - guard didStart else { return } + @discardableResult + private func trackDailyActiveOnWorkQueue(reason: String, flush: Bool) -> Bool { + startIfNeededOnWorkQueue() + guard didStart else { return false } let today = utcDayString(Date()) let defaults = UserDefaults.standard if defaults.string(forKey: lastActiveDayUTCKey) == today { - return + return false } defaults.set(today, forKey: lastActiveDayUTCKey) + let event = dailyActiveEvent + PostHogSDK.shared.capture( - "cmux_daily_active", + event, properties: Self.dailyActiveProperties( dayUTC: today, reason: reason, @@ -81,53 +142,77 @@ final class PostHogAnalytics { ) ) - // For DAU we care more about delivery than batching. - PostHogSDK.shared.flush() + if flush && Self.shouldFlushAfterCapture(event: event) { + // For active metrics we care more about delivery than batching. + PostHogSDK.shared.flush() + } + + return true } - func trackHourlyActive(reason: String) { - startIfNeeded() - guard didStart else { return } + @discardableResult + private func trackHourlyActiveOnWorkQueue(reason: String, flush: Bool) -> Bool { + startIfNeededOnWorkQueue() + guard didStart else { return false } let hour = utcHourString(Date()) let defaults = UserDefaults.standard if defaults.string(forKey: lastActiveHourUTCKey) == hour { - return + return false } defaults.set(hour, forKey: lastActiveHourUTCKey) + let event = hourlyActiveEvent + PostHogSDK.shared.capture( - "cmux_hourly_active", + event, properties: Self.hourlyActiveProperties( hourUTC: hour, reason: reason, infoDictionary: Bundle.main.infoDictionary ?? [:] ) ) + + if flush && Self.shouldFlushAfterCapture(event: event) { + // Keep hourly freshness and avoid losing a deduped hour on abrupt exits. + PostHogSDK.shared.flush() + } + + return true } - func flush() { - guard didStart else { return } - PostHogSDK.shared.flush() + private func dispatchAsyncOnWorkQueue(_ block: @escaping () -> Void) { + if DispatchQueue.getSpecific(key: workQueueSpecificKey) != nil { + block() + return + } + workQueue.async(execute: block) + } + + private func dispatchSyncOnWorkQueue(_ block: () -> Void) { + if DispatchQueue.getSpecific(key: workQueueSpecificKey) != nil { + block() + return + } + workQueue.sync(execute: block) } private func utcHourString(_ date: Date) -> String { - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH" - return formatter.string(from: date) + utcHourFormatter.string(from: date) } private func utcDayString(_ date: Date) -> String { + utcDayFormatter.string(from: date) + } + + private static func makeUTCFormatter(_ dateFormat: String) -> DateFormatter { let formatter = DateFormatter() formatter.calendar = Calendar(identifier: .iso8601) formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd" - return formatter.string(from: date) + formatter.dateFormat = dateFormat + return formatter } nonisolated static func superProperties(infoDictionary: [String: Any]) -> [String: Any] { @@ -162,6 +247,15 @@ final class PostHogAnalytics { return properties } + nonisolated static func shouldFlushAfterCapture(event: String) -> Bool { + switch event { + case "cmux_daily_active", "cmux_hourly_active": + return true + default: + return false + } + } + nonisolated private static func versionProperties(infoDictionary: [String: Any]) -> [String: Any] { var properties: [String: Any] = [:] if let value = infoDictionary["CFBundleShortVersionString"] as? String, !value.isEmpty { diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index 98bff922..53f988aa 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -1259,6 +1259,12 @@ final class PostHogAnalyticsPropertiesTests: XCTestCase { XCTAssertNil(dailyProperties["app_version"]) XCTAssertNil(dailyProperties["app_build"]) } + + func testFlushPolicyIncludesDailyAndHourlyActiveEvents() { + XCTAssertTrue(PostHogAnalytics.shouldFlushAfterCapture(event: "cmux_daily_active")) + XCTAssertTrue(PostHogAnalytics.shouldFlushAfterCapture(event: "cmux_hourly_active")) + XCTAssertFalse(PostHogAnalytics.shouldFlushAfterCapture(event: "cmux_other_event")) + } } final class GhosttyMouseFocusTests: XCTestCase { From 6d0c90c8c8b259c22ba3293716ce58060b6862ea Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 5 Mar 2026 12:45:04 -0800 Subject: [PATCH 102/232] Fix sidebar branch refresh after checkout (issue #666) (#905) * Fix sidebar branch refresh after checkout * Fix bash PR probe not refreshing on checkout (PR review feedback) When HEAD changes (e.g. git checkout), the bash integration now resets _CMUX_PR_LAST_RUN=0 so the PR probe is forced to re-run immediately. This matches the zsh integration which already sets _CMUX_PR_FORCE=1 on HEAD change. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../cmux-bash-integration.bash | 65 ++++++++++- .../cmux-zsh-integration.zsh | 66 ++++------- ...sue_666_sidebar_branch_checkout_refresh.py | 105 ++++++++++++++++++ 3 files changed, 186 insertions(+), 50 deletions(-) create mode 100644 tests/test_issue_666_sidebar_branch_checkout_refresh.py diff --git a/Resources/shell-integration/cmux-bash-integration.bash b/Resources/shell-integration/cmux-bash-integration.bash index 85027ee4..242dcb7d 100644 --- a/Resources/shell-integration/cmux-bash-integration.bash +++ b/Resources/shell-integration/cmux-bash-integration.bash @@ -41,6 +41,9 @@ _CMUX_GIT_LAST_PWD="${_CMUX_GIT_LAST_PWD:-}" _CMUX_GIT_LAST_RUN="${_CMUX_GIT_LAST_RUN:-0}" _CMUX_GIT_JOB_PID="${_CMUX_GIT_JOB_PID:-}" _CMUX_GIT_JOB_STARTED_AT="${_CMUX_GIT_JOB_STARTED_AT:-0}" +_CMUX_GIT_HEAD_LAST_PWD="${_CMUX_GIT_HEAD_LAST_PWD:-}" +_CMUX_GIT_HEAD_PATH="${_CMUX_GIT_HEAD_PATH:-}" +_CMUX_GIT_HEAD_SIGNATURE="${_CMUX_GIT_HEAD_SIGNATURE:-}" _CMUX_PR_LAST_PWD="${_CMUX_PR_LAST_PWD:-}" _CMUX_PR_LAST_RUN="${_CMUX_PR_LAST_RUN:-0}" _CMUX_PR_JOB_PID="${_CMUX_PR_JOB_PID:-}" @@ -51,6 +54,41 @@ _CMUX_PORTS_LAST_RUN="${_CMUX_PORTS_LAST_RUN:-0}" _CMUX_TTY_NAME="${_CMUX_TTY_NAME:-}" _CMUX_TTY_REPORTED="${_CMUX_TTY_REPORTED:-0}" +_cmux_git_resolve_head_path() { + # Resolve the HEAD file path without invoking git (fast; works for worktrees). + local dir="$PWD" + while :; do + if [[ -d "$dir/.git" ]]; then + printf '%s\n' "$dir/.git/HEAD" + return 0 + fi + if [[ -f "$dir/.git" ]]; then + local line gitdir + IFS= read -r line < "$dir/.git" || line="" + if [[ "$line" == gitdir:* ]]; then + gitdir="${line#gitdir:}" + gitdir="${gitdir## }" + gitdir="${gitdir%% }" + [[ -n "$gitdir" ]] || return 1 + [[ "$gitdir" != /* ]] && gitdir="$dir/$gitdir" + printf '%s\n' "$gitdir/HEAD" + return 0 + fi + fi + [[ "$dir" == "/" || -z "$dir" ]] && break + dir="$(dirname "$dir")" + done + return 1 +} + +_cmux_git_head_signature() { + local head_path="$1" + [[ -n "$head_path" && -r "$head_path" ]] || return 1 + local line + IFS= read -r line < "$head_path" || return 1 + printf '%s\n' "$line" +} + _cmux_report_tty_once() { # Send the TTY name to the app once per session so the batched port scanner # knows which TTY belongs to this panel. @@ -126,12 +164,31 @@ _cmux_prompt_command() { } >/dev/null 2>&1 & fi + # Branch can change via aliases/tools while an older probe is still in flight. + # Track .git/HEAD content so we can restart stale probes immediately. + local git_head_changed=0 + if [[ "$pwd" != "$_CMUX_GIT_HEAD_LAST_PWD" ]]; then + _CMUX_GIT_HEAD_LAST_PWD="$pwd" + _CMUX_GIT_HEAD_PATH="$(_cmux_git_resolve_head_path 2>/dev/null || true)" + _CMUX_GIT_HEAD_SIGNATURE="" + fi + if [[ -n "$_CMUX_GIT_HEAD_PATH" ]]; then + local head_signature + head_signature="$(_cmux_git_head_signature "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || true)" + if [[ -n "$head_signature" && "$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE" ]]; then + _CMUX_GIT_HEAD_SIGNATURE="$head_signature" + git_head_changed=1 + # Also invalidate the PR probe so it refreshes with the new branch. + _CMUX_PR_LAST_RUN=0 + fi + fi + # Git branch/dirty can change without a directory change (e.g. `git checkout`), # so update on every prompt (still async + de-duped by the running-job check). # When pwd changes (cd into a different repo), kill the old probe and start fresh # so the sidebar picks up the new branch immediately. if [[ -n "$_CMUX_GIT_JOB_PID" ]] && kill -0 "$_CMUX_GIT_JOB_PID" 2>/dev/null; then - if [[ "$pwd" != "$_CMUX_GIT_LAST_PWD" ]]; then + if [[ "$pwd" != "$_CMUX_GIT_LAST_PWD" || "$git_head_changed" == "1" ]]; then kill "$_CMUX_GIT_JOB_PID" >/dev/null 2>&1 || true _CMUX_GIT_JOB_PID="" _CMUX_GIT_JOB_STARTED_AT=0 @@ -158,16 +215,16 @@ _cmux_prompt_command() { fi # Pull request metadata (number/state/url): - # refresh on cwd change and periodically to avoid stale status. + # refresh on cwd change, HEAD change, and periodically to avoid stale status. if [[ -n "$_CMUX_PR_JOB_PID" ]] && kill -0 "$_CMUX_PR_JOB_PID" 2>/dev/null; then - if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" ]]; then + if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" || "$git_head_changed" == "1" ]]; then kill "$_CMUX_PR_JOB_PID" >/dev/null 2>&1 || true _CMUX_PR_JOB_PID="" _CMUX_PR_JOB_STARTED_AT=0 fi fi - if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" ]] || (( now - _CMUX_PR_LAST_RUN >= 60 )); then + if [[ "$pwd" != "$_CMUX_PR_LAST_PWD" || "$git_head_changed" == "1" ]] || (( now - _CMUX_PR_LAST_RUN >= 60 )); then if [[ -z "$_CMUX_PR_JOB_PID" ]] || ! kill -0 "$_CMUX_PR_JOB_PID" 2>/dev/null; then _CMUX_PR_LAST_PWD="$pwd" _CMUX_PR_LAST_RUN=$now diff --git a/Resources/shell-integration/cmux-zsh-integration.zsh b/Resources/shell-integration/cmux-zsh-integration.zsh index 988be2f1..f35814bc 100644 --- a/Resources/shell-integration/cmux-zsh-integration.zsh +++ b/Resources/shell-integration/cmux-zsh-integration.zsh @@ -45,9 +45,8 @@ typeset -g _CMUX_GIT_JOB_STARTED_AT=0 typeset -g _CMUX_GIT_FORCE=0 typeset -g _CMUX_GIT_HEAD_LAST_PWD="" typeset -g _CMUX_GIT_HEAD_PATH="" -typeset -g _CMUX_GIT_HEAD_MTIME=0 +typeset -g _CMUX_GIT_HEAD_SIGNATURE="" typeset -g _CMUX_GIT_HEAD_WATCH_PID="" -typeset -g _CMUX_HAVE_ZSTAT=0 typeset -g _CMUX_PR_LAST_PWD="" typeset -g _CMUX_PR_LAST_RUN=0 typeset -g _CMUX_PR_JOB_PID="" @@ -60,19 +59,6 @@ typeset -g _CMUX_CMD_START=0 typeset -g _CMUX_TTY_NAME="" typeset -g _CMUX_TTY_REPORTED=0 -_cmux_ensure_zstat() { - # zstat is substantially cheaper than spawning external `stat`. - if (( _CMUX_HAVE_ZSTAT != 0 )); then - return 0 - fi - if zmodload -F zsh/stat b:zstat 2>/dev/null; then - _CMUX_HAVE_ZSTAT=1 - return 0 - fi - _CMUX_HAVE_ZSTAT=-1 - return 1 -} - _cmux_git_resolve_head_path() { # Resolve the HEAD file path without invoking git (fast; works for worktrees). local dir="$PWD" @@ -100,27 +86,15 @@ _cmux_git_resolve_head_path() { return 1 } -_cmux_git_head_mtime() { +_cmux_git_head_signature() { local head_path="$1" - [[ -n "$head_path" && -f "$head_path" ]] || { print -r -- 0; return 0; } - - if _cmux_ensure_zstat; then - typeset -A st - if zstat -H st +mtime -- "$head_path" 2>/dev/null; then - print -r -- "${st[mtime]:-0}" - return 0 - fi - fi - - # Fallback for environments where zsh/stat isn't available. - if command -v stat >/dev/null 2>&1; then - local mtime - mtime="$(stat -f %m "$head_path" 2>/dev/null || stat -c %Y "$head_path" 2>/dev/null || echo 0)" - print -r -- "$mtime" + [[ -n "$head_path" && -r "$head_path" ]] || return 1 + local line="" + if IFS= read -r line < "$head_path"; then + print -r -- "$line" return 0 fi - - print -r -- 0 + return 1 } _cmux_report_tty_once() { @@ -184,23 +158,23 @@ _cmux_start_git_head_watch() { watch_head_path="$(_cmux_git_resolve_head_path 2>/dev/null || true)" [[ -n "$watch_head_path" ]] || return 0 - local watch_head_mtime - watch_head_mtime="$(_cmux_git_head_mtime "$watch_head_path" 2>/dev/null || echo 0)" + local watch_head_signature + watch_head_signature="$(_cmux_git_head_signature "$watch_head_path" 2>/dev/null || true)" _CMUX_GIT_HEAD_LAST_PWD="$watch_pwd" _CMUX_GIT_HEAD_PATH="$watch_head_path" - _CMUX_GIT_HEAD_MTIME="$watch_head_mtime" + _CMUX_GIT_HEAD_SIGNATURE="$watch_head_signature" _cmux_stop_git_head_watch { - local last_mtime="$watch_head_mtime" + local last_signature="$watch_head_signature" while true; do sleep 1 - local mtime - mtime="$(_cmux_git_head_mtime "$watch_head_path" 2>/dev/null || echo 0)" - if [[ -n "$mtime" && "$mtime" != 0 && "$mtime" != "$last_mtime" ]]; then - last_mtime="$mtime" + local signature + signature="$(_cmux_git_head_signature "$watch_head_path" 2>/dev/null || true)" + if [[ -n "$signature" && "$signature" != "$last_signature" ]]; then + last_signature="$signature" _cmux_report_git_branch_for_path "$watch_pwd" fi done @@ -299,13 +273,13 @@ _cmux_precmd() { if [[ "$pwd" != "$_CMUX_GIT_HEAD_LAST_PWD" ]]; then _CMUX_GIT_HEAD_LAST_PWD="$pwd" _CMUX_GIT_HEAD_PATH="$(_cmux_git_resolve_head_path 2>/dev/null || true)" - _CMUX_GIT_HEAD_MTIME=0 + _CMUX_GIT_HEAD_SIGNATURE="" fi if [[ -n "$_CMUX_GIT_HEAD_PATH" ]]; then - local head_mtime - head_mtime="$(_cmux_git_head_mtime "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || echo 0)" - if [[ -n "$head_mtime" && "$head_mtime" != 0 && "$head_mtime" != "$_CMUX_GIT_HEAD_MTIME" ]]; then - _CMUX_GIT_HEAD_MTIME="$head_mtime" + local head_signature + head_signature="$(_cmux_git_head_signature "$_CMUX_GIT_HEAD_PATH" 2>/dev/null || true)" + if [[ -n "$head_signature" && "$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE" ]]; then + _CMUX_GIT_HEAD_SIGNATURE="$head_signature" # Treat HEAD file change like a git command — force-replace any # running probe so the sidebar picks up the new branch immediately. _CMUX_GIT_FORCE=1 diff --git a/tests/test_issue_666_sidebar_branch_checkout_refresh.py b/tests/test_issue_666_sidebar_branch_checkout_refresh.py new file mode 100644 index 00000000..751a8c70 --- /dev/null +++ b/tests/test_issue_666_sidebar_branch_checkout_refresh.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Regression guard for issue #666 (sidebar branch stuck after checkout).""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + + +def get_repo_root() -> Path: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + ) + if result.returncode == 0: + return Path(result.stdout.strip()) + return Path.cwd() + + +def require(content: str, needle: str, message: str, failures: list[str]) -> None: + if needle not in content: + failures.append(message) + + +def main() -> int: + repo_root = get_repo_root() + zsh_path = repo_root / "Resources" / "shell-integration" / "cmux-zsh-integration.zsh" + bash_path = repo_root / "Resources" / "shell-integration" / "cmux-bash-integration.bash" + + required_paths = [zsh_path, bash_path] + missing_paths = [str(path) for path in required_paths if not path.exists()] + if missing_paths: + print("Missing expected files:") + for path in missing_paths: + print(f" - {path}") + return 1 + + zsh_content = zsh_path.read_text(encoding="utf-8") + bash_content = bash_path.read_text(encoding="utf-8") + + failures: list[str] = [] + + require( + zsh_content, + "_CMUX_GIT_HEAD_SIGNATURE", + "zsh integration is missing git HEAD signature tracking", + failures, + ) + require( + zsh_content, + "_cmux_git_head_signature", + "zsh integration is missing git HEAD signature helper", + failures, + ) + require( + zsh_content, + '"$head_signature" != "$_CMUX_GIT_HEAD_SIGNATURE"', + "zsh integration no longer compares git HEAD signatures", + failures, + ) + require( + zsh_content, + "_CMUX_GIT_FORCE=1", + "zsh integration no longer forces git probe refresh on HEAD changes", + failures, + ) + + require( + bash_content, + "_CMUX_GIT_HEAD_SIGNATURE", + "bash integration is missing git HEAD signature tracking", + failures, + ) + require( + bash_content, + "_cmux_git_head_signature", + "bash integration is missing git HEAD signature helper", + failures, + ) + require( + bash_content, + "git_head_changed=1", + "bash integration no longer flags HEAD changes for immediate refresh", + failures, + ) + require( + bash_content, + '|| "$git_head_changed" == "1"', + "bash integration no longer restarts running git probes on HEAD change", + failures, + ) + + if failures: + print("FAIL: issue #666 regression(s) detected") + for failure in failures: + print(f"- {failure}") + return 1 + + print("PASS: issue #666 checkout refresh guards are present") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 9b215eddab4b4bb8ee94424133445d88ad02ee43 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 5 Mar 2026 15:27:17 -0800 Subject: [PATCH 103/232] Fix browser portal pane drag routing and uploads (#961) --- Sources/BrowserWindowPortal.swift | 873 +++++++++++++++++- Sources/ContentView.swift | 7 +- Sources/Panels/BrowserPanelView.swift | 35 +- Sources/Panels/CmuxWebView.swift | 20 + Sources/TerminalWindowPortal.swift | 15 +- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 565 +++++++++++- 6 files changed, 1479 insertions(+), 36 deletions(-) diff --git a/Sources/BrowserWindowPortal.swift b/Sources/BrowserWindowPortal.swift index d53e1a71..da7be546 100644 --- a/Sources/BrowserWindowPortal.swift +++ b/Sources/BrowserWindowPortal.swift @@ -1,9 +1,7 @@ import AppKit +import Bonsplit import ObjectiveC import WebKit -#if DEBUG -import Bonsplit -#endif private var cmuxWindowBrowserPortalKey: UInt8 = 0 private var cmuxWindowBrowserPortalCloseObserverKey: UInt8 = 0 @@ -126,6 +124,17 @@ final class WindowBrowserHostView: NSView { if shouldPassThroughToSplitDivider(at: point) { return nil } + + // Mirror terminal portal routing: while tab-reorder drags are active, + // pass through to SwiftUI drop targets behind the portal host. + // Browser hover routing also arrives as cursor/enter events and may not + // report a pressed-button state, so include that path here. + if Self.shouldPassThroughToDragTargets( + pasteboardTypes: NSPasteboard(name: .drag).types, + eventType: NSApp.currentEvent?.type + ) { + return nil + } let hitView = super.hitTest(point) return hitView === self ? nil : hitView } @@ -227,6 +236,31 @@ final class WindowBrowserHostView: NSView { splitDividerCursorKind(at: point) != nil } + static func shouldPassThroughToDragTargets( + pasteboardTypes: [NSPasteboard.PasteboardType]?, + eventType: NSEvent.EventType? + ) -> Bool { + if DragOverlayRoutingPolicy.shouldPassThroughPortalHitTesting( + pasteboardTypes: pasteboardTypes, + eventType: eventType + ) { + return true + } + + guard let eventType else { return false } + switch eventType { + case .cursorUpdate, .mouseEntered, .mouseExited, .mouseMoved: + // Browser-side tab drags can surface as hover events with a mixed + // pasteboard payload (tabtransfer plus promised-file UTIs). Prefer + // the explicit Bonsplit drag types so WKWebView cannot steal the + // session as a file upload. + return DragOverlayRoutingPolicy.hasBonsplitTabTransfer(pasteboardTypes) + || DragOverlayRoutingPolicy.hasSidebarTabReorder(pasteboardTypes) + default: + return false + } + } + private static func dividerCursorKind(at windowPoint: NSPoint, in view: NSView) -> DividerCursorKind? { guard !view.isHidden else { return nil } @@ -317,8 +351,329 @@ final class WindowBrowserHostView: NSView { } +private final class BrowserDropZoneOverlayView: NSView { + override var acceptsFirstResponder: Bool { false } + + override func hitTest(_ point: NSPoint) -> NSView? { + nil + } +} + +struct BrowserPaneDropContext: Equatable { + let workspaceId: UUID + let panelId: UUID + let paneId: PaneID +} + +struct BrowserPaneDragTransfer: Equatable { + let tabId: UUID + let sourcePaneId: UUID + let sourceProcessId: Int32 + + var isFromCurrentProcess: Bool { + sourceProcessId == Int32(ProcessInfo.processInfo.processIdentifier) + } + + static func decode(from pasteboard: NSPasteboard) -> BrowserPaneDragTransfer? { + if let data = pasteboard.data(forType: DragOverlayRoutingPolicy.bonsplitTabTransferType) { + return decode(from: data) + } + if let raw = pasteboard.string(forType: DragOverlayRoutingPolicy.bonsplitTabTransferType) { + return decode(from: Data(raw.utf8)) + } + return nil + } + + static func decode(from data: Data) -> BrowserPaneDragTransfer? { + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let tab = json["tab"] as? [String: Any], + let tabIdRaw = tab["id"] as? String, + let tabId = UUID(uuidString: tabIdRaw), + let sourcePaneIdRaw = json["sourcePaneId"] as? String, + let sourcePaneId = UUID(uuidString: sourcePaneIdRaw) else { + return nil + } + + let sourceProcessId = (json["sourceProcessId"] as? NSNumber)?.int32Value ?? -1 + return BrowserPaneDragTransfer( + tabId: tabId, + sourcePaneId: sourcePaneId, + sourceProcessId: sourceProcessId + ) + } +} + +struct BrowserPaneSplitTarget: Equatable { + let orientation: SplitOrientation + let insertFirst: Bool +} + +enum BrowserPaneDropAction: Equatable { + case noOp + case move( + tabId: UUID, + targetWorkspaceId: UUID, + targetPane: PaneID, + splitTarget: BrowserPaneSplitTarget? + ) +} + +enum BrowserPaneDropRouting { + static func zone(for location: CGPoint, in size: CGSize) -> DropZone { + let edgeRatio: CGFloat = 0.25 + let horizontalEdge = max(80, size.width * edgeRatio) + let verticalEdge = max(80, size.height * edgeRatio) + + if location.x < horizontalEdge { + return .left + } else if location.x > size.width - horizontalEdge { + return .right + } else if location.y > size.height - verticalEdge { + return .top + } else if location.y < verticalEdge { + return .bottom + } else { + return .center + } + } + + static func action( + for transfer: BrowserPaneDragTransfer, + target: BrowserPaneDropContext, + zone: DropZone + ) -> BrowserPaneDropAction? { + if zone == .center, transfer.sourcePaneId == target.paneId.id { + return .noOp + } + + let splitTarget: BrowserPaneSplitTarget? + switch zone { + case .center: + splitTarget = nil + case .left: + splitTarget = BrowserPaneSplitTarget(orientation: .horizontal, insertFirst: true) + case .right: + splitTarget = BrowserPaneSplitTarget(orientation: .horizontal, insertFirst: false) + case .top: + splitTarget = BrowserPaneSplitTarget(orientation: .vertical, insertFirst: true) + case .bottom: + splitTarget = BrowserPaneSplitTarget(orientation: .vertical, insertFirst: false) + } + + return .move( + tabId: transfer.tabId, + targetWorkspaceId: target.workspaceId, + targetPane: target.paneId, + splitTarget: splitTarget + ) + } +} + +final class BrowserPaneDropTargetView: NSView { + weak var slotView: WindowBrowserSlotView? + var dropContext: BrowserPaneDropContext? + private var activeZone: DropZone? +#if DEBUG + private var lastHitTestSignature: String? +#endif + + override var acceptsFirstResponder: Bool { false } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + registerForDraggedTypes([DragOverlayRoutingPolicy.bonsplitTabTransferType]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + nil + } + + static func shouldCaptureHitTesting( + pasteboardTypes: [NSPasteboard.PasteboardType]?, + eventType: NSEvent.EventType? + ) -> Bool { + guard DragOverlayRoutingPolicy.hasBonsplitTabTransfer(pasteboardTypes) else { return false } + guard let eventType else { return false } + + switch eventType { + case .cursorUpdate, + .mouseEntered, + .mouseExited, + .mouseMoved, + .leftMouseDragged, + .rightMouseDragged, + .otherMouseDragged, + .appKitDefined, + .applicationDefined, + .systemDefined, + .periodic: + return true + default: + return false + } + } + + override func hitTest(_ point: NSPoint) -> NSView? { + guard bounds.contains(point), dropContext != nil else { return nil } + + let pasteboardTypes = NSPasteboard(name: .drag).types + let eventType = NSApp.currentEvent?.type + let capture = Self.shouldCaptureHitTesting( + pasteboardTypes: pasteboardTypes, + eventType: eventType + ) +#if DEBUG + logHitTestDecision(capture: capture, pasteboardTypes: pasteboardTypes, eventType: eventType) +#endif + return capture ? self : nil + } + + override func draggingEntered(_ sender: any NSDraggingInfo) -> NSDragOperation { + updateDragState(sender, phase: "entered") + } + + override func draggingUpdated(_ sender: any NSDraggingInfo) -> NSDragOperation { + updateDragState(sender, phase: "updated") + } + + override func draggingExited(_ sender: (any NSDraggingInfo)?) { + clearDragState(phase: "exited") + } + + override func performDragOperation(_ sender: any NSDraggingInfo) -> Bool { + defer { + clearDragState(phase: "perform.clear") + } + + guard let dropContext, + let transfer = BrowserPaneDragTransfer.decode(from: sender.draggingPasteboard), + transfer.isFromCurrentProcess else { +#if DEBUG + dlog("browser.paneDrop.perform allowed=0 reason=missingTransfer") +#endif + return false + } + + let location = convert(sender.draggingLocation, from: nil) + let zone = BrowserPaneDropRouting.zone(for: location, in: bounds.size) + guard let action = BrowserPaneDropRouting.action( + for: transfer, + target: dropContext, + zone: zone + ) else { +#if DEBUG + dlog( + "browser.paneDrop.perform allowed=0 panel=\(dropContext.panelId.uuidString.prefix(5)) " + + "reason=noAction zone=\(zone)" + ) +#endif + return false + } + + switch action { + case .noOp: +#if DEBUG + dlog( + "browser.paneDrop.perform allowed=1 panel=\(dropContext.panelId.uuidString.prefix(5)) " + + "tab=\(transfer.tabId.uuidString.prefix(5)) action=noop" + ) +#endif + return true + case .move(let tabId, let workspaceId, let targetPane, let splitTarget): + let moved = AppDelegate.shared?.moveBonsplitTab( + tabId: tabId, + toWorkspace: workspaceId, + targetPane: targetPane, + splitTarget: splitTarget.map { ($0.orientation, $0.insertFirst) }, + focus: true, + focusWindow: true + ) ?? false +#if DEBUG + let splitLabel = splitTarget.map { + "\($0.orientation.rawValue):\($0.insertFirst ? 1 : 0)" + } ?? "none" + dlog( + "browser.paneDrop.perform panel=\(dropContext.panelId.uuidString.prefix(5)) " + + "tab=\(tabId.uuidString.prefix(5)) zone=\(zone) pane=\(targetPane.id.uuidString.prefix(5)) " + + "split=\(splitLabel) moved=\(moved ? 1 : 0)" + ) +#endif + return moved + } + } + + private func updateDragState(_ sender: any NSDraggingInfo, phase: String) -> NSDragOperation { + guard let dropContext, + let transfer = BrowserPaneDragTransfer.decode(from: sender.draggingPasteboard), + transfer.isFromCurrentProcess else { + clearDragState(phase: "\(phase).reject") + return [] + } + + let location = convert(sender.draggingLocation, from: nil) + let zone = BrowserPaneDropRouting.zone(for: location, in: bounds.size) + activeZone = zone + slotView?.setPortalDragDropZone(zone) +#if DEBUG + dlog( + "browser.paneDrop.\(phase) panel=\(dropContext.panelId.uuidString.prefix(5)) " + + "tab=\(transfer.tabId.uuidString.prefix(5)) zone=\(zone)" + ) +#endif + return .move + } + + private func clearDragState(phase: String) { + guard activeZone != nil else { return } + activeZone = nil + slotView?.setPortalDragDropZone(nil) +#if DEBUG + if let dropContext { + dlog( + "browser.paneDrop.\(phase) panel=\(dropContext.panelId.uuidString.prefix(5)) zone=none" + ) + } +#endif + } + +#if DEBUG + private func logHitTestDecision( + capture: Bool, + pasteboardTypes: [NSPasteboard.PasteboardType]?, + eventType: NSEvent.EventType? + ) { + let hasTransferType = DragOverlayRoutingPolicy.hasBonsplitTabTransfer(pasteboardTypes) + guard hasTransferType || capture else { return } + + let signature = [ + capture ? "1" : "0", + hasTransferType ? "1" : "0", + String(describing: dropContext != nil), + eventType.map { String($0.rawValue) } ?? "nil", + ].joined(separator: "|") + guard lastHitTestSignature != signature else { return } + lastHitTestSignature = signature + + let types = pasteboardTypes?.map(\.rawValue).joined(separator: ",") ?? "-" + dlog( + "browser.paneDrop.hitTest capture=\(capture ? 1 : 0) " + + "hasTransfer=\(hasTransferType ? 1 : 0) context=\(dropContext != nil ? 1 : 0) " + + "event=\(eventType.map { String($0.rawValue) } ?? "nil") types=\(types)" + ) + } +#endif +} + final class WindowBrowserSlotView: NSView { override var isOpaque: Bool { false } + private let paneDropTargetView = BrowserPaneDropTargetView(frame: .zero) + private let dropZoneOverlayView = BrowserDropZoneOverlayView(frame: .zero) + private var forwardedDropZone: DropZone? + private var portalDragDropZone: DropZone? + private var displayedDropZone: DropZone? + private var dropZoneOverlayAnimationGeneration: UInt64 = 0 + private var isRefreshingInteractionLayers = false override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -326,16 +681,193 @@ final class WindowBrowserSlotView: NSView { layer?.masksToBounds = true translatesAutoresizingMaskIntoConstraints = true autoresizingMask = [] + + paneDropTargetView.slotView = self + + dropZoneOverlayView.wantsLayer = true + dropZoneOverlayView.layer?.backgroundColor = cmuxAccentNSColor().withAlphaComponent(0.25).cgColor + dropZoneOverlayView.layer?.borderColor = cmuxAccentNSColor().cgColor + dropZoneOverlayView.layer?.borderWidth = 2 + dropZoneOverlayView.layer?.cornerRadius = 8 + dropZoneOverlayView.isHidden = true + addSubview(paneDropTargetView, positioned: .above, relativeTo: nil) + addSubview(dropZoneOverlayView, positioned: .above, relativeTo: nil) } @available(*, unavailable) required init?(coder: NSCoder) { nil } + + override func layout() { + super.layout() + paneDropTargetView.frame = bounds + applyResolvedDropZoneOverlay() + } + + func setDropZoneOverlay(zone: DropZone?) { + forwardedDropZone = zone + applyResolvedDropZoneOverlay() + } + + func setPortalDragDropZone(_ zone: DropZone?) { + portalDragDropZone = zone + applyResolvedDropZoneOverlay() + } + + func setPaneDropContext(_ context: BrowserPaneDropContext?) { + paneDropTargetView.dropContext = context + } + + override func didAddSubview(_ subview: NSView) { + super.didAddSubview(subview) + guard subview !== paneDropTargetView, subview !== dropZoneOverlayView else { return } + bringInteractionLayersToFrontIfNeeded() + } + + private var activeDropZone: DropZone? { + portalDragDropZone ?? forwardedDropZone + } + + private func applyResolvedDropZoneOverlay() { + let resolvedZone = activeDropZone + if resolvedZone != nil, (bounds.width <= 2 || bounds.height <= 2) { + bringInteractionLayersToFrontIfNeeded() + return + } + + let previousZone = displayedDropZone + displayedDropZone = resolvedZone + let previousFrame = dropZoneOverlayView.frame + + guard let zone = resolvedZone else { + guard !dropZoneOverlayView.isHidden else { + bringInteractionLayersToFrontIfNeeded() + return + } + + dropZoneOverlayAnimationGeneration &+= 1 + let animationGeneration = dropZoneOverlayAnimationGeneration + dropZoneOverlayView.layer?.removeAllAnimations() + bringInteractionLayersToFrontIfNeeded() + + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.14 + context.timingFunction = CAMediaTimingFunction(name: .easeOut) + dropZoneOverlayView.animator().alphaValue = 0 + } completionHandler: { [weak self] in + guard let self else { return } + guard self.dropZoneOverlayAnimationGeneration == animationGeneration else { return } + guard self.displayedDropZone == nil else { return } + self.dropZoneOverlayView.isHidden = true + self.dropZoneOverlayView.alphaValue = 1 + } + return + } + + let targetFrame = dropZoneOverlayFrame(for: zone, in: bounds.size) + let needsFrameUpdate = !Self.rectApproximatelyEqual(previousFrame, targetFrame) + let zoneChanged = previousZone != zone + + if !dropZoneOverlayView.isHidden && !needsFrameUpdate && !zoneChanged { + bringInteractionLayersToFrontIfNeeded() + return + } + + dropZoneOverlayAnimationGeneration &+= 1 + dropZoneOverlayView.layer?.removeAllAnimations() + + if dropZoneOverlayView.isHidden { + applyDropZoneOverlayFrame(targetFrame) + dropZoneOverlayView.alphaValue = 0 + dropZoneOverlayView.isHidden = false + bringInteractionLayersToFrontIfNeeded() + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.18 + context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + dropZoneOverlayView.animator().alphaValue = 1 + } + return + } + + bringInteractionLayersToFrontIfNeeded() + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.18 + context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + if needsFrameUpdate { + dropZoneOverlayView.animator().frame = targetFrame + } + if dropZoneOverlayView.alphaValue < 1 { + dropZoneOverlayView.animator().alphaValue = 1 + } + } + } + + private func interactionLayerPriority(of view: NSView) -> Int { + if view === paneDropTargetView { return 1 } + if view === dropZoneOverlayView { return 2 } + return 0 + } + + private func bringInteractionLayersToFrontIfNeeded() { + guard !isRefreshingInteractionLayers else { return } + isRefreshingInteractionLayers = true + defer { isRefreshingInteractionLayers = false } + + if paneDropTargetView.superview !== self { + addSubview(paneDropTargetView, positioned: .above, relativeTo: nil) + } + if dropZoneOverlayView.superview !== self { + addSubview(dropZoneOverlayView, positioned: .above, relativeTo: nil) + } + + let context = Unmanaged.passUnretained(self).toOpaque() + sortSubviews({ lhs, rhs, context in + guard let context else { return .orderedSame } + let slotView = Unmanaged.fromOpaque(context).takeUnretainedValue() + let lhsPriority = slotView.interactionLayerPriority(of: lhs) + let rhsPriority = slotView.interactionLayerPriority(of: rhs) + if lhsPriority == rhsPriority { return .orderedSame } + return lhsPriority < rhsPriority ? .orderedAscending : .orderedDescending + }, context: context) + } + + private func applyDropZoneOverlayFrame(_ frame: CGRect) { + if Self.rectApproximatelyEqual(dropZoneOverlayView.frame, frame) { return } + CATransaction.begin() + CATransaction.setDisableActions(true) + dropZoneOverlayView.frame = frame + CATransaction.commit() + } + + private func dropZoneOverlayFrame(for zone: DropZone, in size: CGSize) -> CGRect { + let padding: CGFloat = 4 + switch zone { + case .center: + return CGRect(x: padding, y: padding, width: size.width - padding * 2, height: size.height - padding * 2) + case .left: + return CGRect(x: padding, y: padding, width: size.width / 2 - padding, height: size.height - padding * 2) + case .right: + return CGRect(x: size.width / 2, y: padding, width: size.width / 2 - padding, height: size.height - padding * 2) + case .top: + return CGRect(x: padding, y: size.height / 2, width: size.width - padding * 2, height: size.height / 2 - padding) + case .bottom: + return CGRect(x: padding, y: padding, width: size.width - padding * 2, height: size.height / 2 - padding) + } + } + + private static func rectApproximatelyEqual(_ lhs: CGRect, _ rhs: CGRect, epsilon: CGFloat = 0.5) -> Bool { + abs(lhs.origin.x - rhs.origin.x) <= epsilon && + abs(lhs.origin.y - rhs.origin.y) <= epsilon && + abs(lhs.size.width - rhs.size.width) <= epsilon && + abs(lhs.size.height - rhs.size.height) <= epsilon + } } @MainActor final class WindowBrowserPortal: NSObject { + private static let transientRecoveryRetryBudget: Int = 12 + private weak var window: NSWindow? private let hostView = WindowBrowserHostView(frame: .zero) private weak var installedContainerView: NSView? @@ -350,6 +882,9 @@ final class WindowBrowserPortal: NSObject { weak var anchorView: NSView? var visibleInUI: Bool var zPriority: Int + var dropZone: DropZone? + var paneDropContext: BrowserPaneDropContext? + var transientRecoveryRetriesRemaining: Int } private var entriesByWebViewId: [ObjectIdentifier: Entry] = [:] @@ -427,22 +962,39 @@ final class WindowBrowserPortal: NSObject { hostView.superview?.layoutSubtreeIfNeeded() hostView.layoutSubtreeIfNeeded() synchronizeAllWebViews(excluding: nil, source: "externalGeometry") + + for entry in entriesByWebViewId.values { + guard let webView = entry.webView, + let containerView = entry.containerView, + !containerView.isHidden else { continue } + refreshHostedWebViewPresentation( + webView, + in: containerView, + reason: "externalGeometry" + ) + } } @discardableResult private func ensureInstalled() -> Bool { guard let window else { return false } guard let (container, reference) = installationTarget(for: window) else { return false } + let placementReference = preferredHostPlacementReference(in: container, fallback: reference) if hostView.superview !== container || installedContainerView !== container || installedReferenceView !== reference { hostView.removeFromSuperview() - container.addSubview(hostView, positioned: .above, relativeTo: reference) + container.addSubview(hostView, positioned: .above, relativeTo: placementReference) installedContainerView = container installedReferenceView = reference - } else if !Self.isView(hostView, above: reference, in: container) { - container.addSubview(hostView, positioned: .above, relativeTo: reference) + } else { + let aboveReference = Self.isView(hostView, above: reference, in: container) + let abovePlacementReference = placementReference === reference + || Self.isView(hostView, above: placementReference, in: container) + if !aboveReference || !abovePlacementReference { + container.addSubview(hostView, positioned: .above, relativeTo: placementReference) + } } synchronizeHostFrameToReference() @@ -526,6 +1078,30 @@ final class WindowBrowserPortal: NSObject { ) } + /// Convert an anchor view's bounds to window coordinates while honoring ancestor clipping. + /// SwiftUI/AppKit hosting layers can briefly report an anchor bounds rect larger than the + /// visible split pane during rearrangement; intersecting through ancestor bounds keeps the + /// portal locked to the pane the user can actually see. + private func effectiveAnchorFrameInWindow(for anchorView: NSView) -> NSRect { + var frameInWindow = anchorView.convert(anchorView.bounds, to: nil) + var current = anchorView.superview + while let ancestor = current { + let ancestorBoundsInWindow = ancestor.convert(ancestor.bounds, to: nil) + let finiteAncestorBounds = + ancestorBoundsInWindow.origin.x.isFinite && + ancestorBoundsInWindow.origin.y.isFinite && + ancestorBoundsInWindow.size.width.isFinite && + ancestorBoundsInWindow.size.height.isFinite + if finiteAncestorBounds { + frameInWindow = frameInWindow.intersection(ancestorBoundsInWindow) + if frameInWindow.isNull { return .zero } + } + if ancestor === installedReferenceView { break } + current = ancestor.superview + } + return frameInWindow + } + private static func frameExtendsOutsideBounds(_ frame: NSRect, bounds: NSRect, epsilon: CGFloat = 0.5) -> Bool { frame.minX < bounds.minX - epsilon || frame.minY < bounds.minY - epsilon || @@ -557,11 +1133,19 @@ final class WindowBrowserPortal: NSObject { return viewIndex > referenceIndex } + private func preferredHostPlacementReference(in container: NSView, fallback reference: NSView) -> NSView { + container.subviews.last(where: { + $0 !== hostView && ($0 === reference || $0 is WindowTerminalHostView) + }) ?? reference + } + private func ensureContainerView(for entry: Entry, webView: WKWebView) -> WindowBrowserSlotView { if let existing = entry.containerView { + existing.setPaneDropContext(entry.paneDropContext) return existing } let created = WindowBrowserSlotView(frame: .zero) + created.setPaneDropContext(entry.paneDropContext) #if DEBUG dlog( "browser.portal.container.create web=\(browserPortalDebugToken(webView)) " + @@ -571,6 +1155,48 @@ final class WindowBrowserPortal: NSObject { return created } + private func refreshHostedWebViewPresentation( + _ webView: WKWebView, + in containerView: WindowBrowserSlotView, + reason: String + ) { + guard !containerView.isHidden else { return } + + containerView.needsLayout = true + containerView.needsDisplay = true + containerView.setNeedsDisplay(containerView.bounds) + + if let scrollView = webView.enclosingScrollView { + scrollView.needsLayout = true + scrollView.needsDisplay = true + scrollView.setNeedsDisplay(scrollView.bounds) + } + + webView.needsLayout = true + webView.needsDisplay = true + webView.setNeedsDisplay(webView.bounds) + DispatchQueue.main.async { [weak self, weak webView, weak containerView] in + guard let self, let webView, let containerView, !containerView.isHidden else { return } + + containerView.layoutSubtreeIfNeeded() + if let scrollView = webView.enclosingScrollView { + scrollView.layoutSubtreeIfNeeded() + scrollView.displayIfNeeded() + } + webView.layoutSubtreeIfNeeded() + containerView.displayIfNeeded() + webView.displayIfNeeded() + (webView.window ?? self.hostView.window)?.displayIfNeeded() +#if DEBUG + dlog( + "browser.portal.refresh web=\(browserPortalDebugToken(webView)) " + + "container=\(browserPortalDebugToken(containerView)) reason=\(reason) " + + "frame=\(browserPortalDebugFrame(containerView.frame))" + ) +#endif + } + } + private func moveWebKitRelatedSubviewsIfNeeded( from sourceSuperview: NSView, to containerView: WindowBrowserSlotView, @@ -641,6 +1267,20 @@ final class WindowBrowserPortal: NSObject { entriesByWebViewId[webViewId] = entry } + func updateDropZoneOverlay(forWebViewId webViewId: ObjectIdentifier, zone: DropZone?) { + guard var entry = entriesByWebViewId[webViewId] else { return } + entry.dropZone = zone + entriesByWebViewId[webViewId] = entry + entry.containerView?.setDropZoneOverlay(zone: zone) + } + + func updatePaneDropContext(forWebViewId webViewId: ObjectIdentifier, context: BrowserPaneDropContext?) { + guard var entry = entriesByWebViewId[webViewId] else { return } + entry.paneDropContext = context + entriesByWebViewId[webViewId] = entry + entry.containerView?.setPaneDropContext(context) + } + func bind(webView: WKWebView, to anchorView: NSView, visibleInUI: Bool, zPriority: Int = 0) { guard ensureInstalled() else { return } @@ -648,7 +1288,16 @@ final class WindowBrowserPortal: NSObject { let anchorId = ObjectIdentifier(anchorView) let previousEntry = entriesByWebViewId[webViewId] let containerView = ensureContainerView( - for: previousEntry ?? Entry(webView: nil, containerView: nil, anchorView: nil, visibleInUI: false, zPriority: 0), + for: previousEntry ?? Entry( + webView: nil, + containerView: nil, + anchorView: nil, + visibleInUI: false, + zPriority: 0, + dropZone: nil, + paneDropContext: nil, + transientRecoveryRetriesRemaining: 0 + ), webView: webView ) @@ -677,7 +1326,10 @@ final class WindowBrowserPortal: NSObject { containerView: containerView, anchorView: anchorView, visibleInUI: visibleInUI, - zPriority: zPriority + zPriority: zPriority, + dropZone: previousEntry?.dropZone, + paneDropContext: previousEntry?.paneDropContext, + transientRecoveryRetriesRemaining: previousEntry?.transientRecoveryRetriesRemaining ?? 0 ) let didChangeAnchor: Bool = { @@ -747,7 +1399,11 @@ final class WindowBrowserPortal: NSObject { hostView.addSubview(containerView, positioned: .above, relativeTo: nil) } - synchronizeWebView(withId: webViewId, source: "bind") + synchronizeWebView( + withId: webViewId, + source: "bind", + forcePresentationRefresh: didChangeAnchor + ) pruneDeadEntries() } @@ -789,9 +1445,44 @@ final class WindowBrowserPortal: NSObject { } } - private func synchronizeWebView(withId webViewId: ObjectIdentifier, source: String) { + private func resetTransientRecoveryRetryIfNeeded(forWebViewId webViewId: ObjectIdentifier, entry: inout Entry) { + guard entry.transientRecoveryRetriesRemaining != 0 else { return } + entry.transientRecoveryRetriesRemaining = 0 + entriesByWebViewId[webViewId] = entry + } + + private func scheduleTransientRecoveryRetryIfNeeded( + forWebViewId webViewId: ObjectIdentifier, + entry: inout Entry, + webView: WKWebView, + reason: String + ) -> Bool { + if entry.transientRecoveryRetriesRemaining == 0 { + entry.transientRecoveryRetriesRemaining = Self.transientRecoveryRetryBudget + } + guard entry.transientRecoveryRetriesRemaining > 0 else { return false } + + entry.transientRecoveryRetriesRemaining -= 1 + entriesByWebViewId[webViewId] = entry +#if DEBUG + dlog( + "browser.portal.sync.deferRecover web=\(browserPortalDebugToken(webView)) " + + "reason=\(reason) remaining=\(entry.transientRecoveryRetriesRemaining)" + ) +#endif + if entry.transientRecoveryRetriesRemaining > 0 { + scheduleDeferredFullSynchronizeAll() + } + return true + } + + private func synchronizeWebView( + withId webViewId: ObjectIdentifier, + source: String, + forcePresentationRefresh: Bool = false + ) { guard ensureInstalled() else { return } - guard let entry = entriesByWebViewId[webViewId] else { return } + guard var entry = entriesByWebViewId[webViewId] else { return } guard let webView = entry.webView else { entriesByWebViewId.removeValue(forKey: webViewId) return @@ -804,6 +1495,16 @@ final class WindowBrowserPortal: NSObject { return } guard let anchorView = entry.anchorView, let window else { + if entry.visibleInUI { + _ = scheduleTransientRecoveryRetryIfNeeded( + forWebViewId: webViewId, + entry: &entry, + webView: webView, + reason: "missingAnchorOrWindow" + ) + } else { + resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) + } #if DEBUG if !containerView.isHidden { dlog( @@ -812,6 +1513,7 @@ final class WindowBrowserPortal: NSObject { ) } #endif + containerView.setDropZoneOverlay(zone: nil) containerView.isHidden = true return } @@ -825,10 +1527,22 @@ final class WindowBrowserPortal: NSObject { ) } #endif + if entry.visibleInUI { + _ = scheduleTransientRecoveryRetryIfNeeded( + forWebViewId: webViewId, + entry: &entry, + webView: webView, + reason: "anchorWindowMismatch" + ) + } else { + resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) + } + containerView.setDropZoneOverlay(zone: nil) containerView.isHidden = true return } + var refreshReasons: [String] = [] if containerView.superview !== hostView { #if DEBUG dlog( @@ -837,6 +1551,7 @@ final class WindowBrowserPortal: NSObject { ) #endif hostView.addSubview(containerView, positioned: .above, relativeTo: nil) + refreshReasons.append("syncAttachContainer") } if webView.superview !== containerView { #if DEBUG @@ -859,12 +1574,11 @@ final class WindowBrowserPortal: NSObject { webView.translatesAutoresizingMaskIntoConstraints = true webView.autoresizingMask = [.width, .height] webView.frame = containerView.bounds - webView.needsLayout = true - webView.layoutSubtreeIfNeeded() + refreshReasons.append("syncAttachWebView") } _ = synchronizeHostFrameToReference() - let frameInWindow = anchorView.convert(anchorView.bounds, to: nil) + let frameInWindow = effectiveAnchorFrameInWindow(for: anchorView) let frameInHostRaw = hostView.convert(frameInWindow, from: nil) let frameInHost = Self.pixelSnappedRect(frameInHostRaw, in: hostView) let hostBounds = hostView.bounds @@ -883,8 +1597,38 @@ final class WindowBrowserPortal: NSObject { "anchor=\(browserPortalDebugFrame(frameInHost)) visibleInUI=\(entry.visibleInUI ? 1 : 0)" ) #endif + if entry.visibleInUI { + let shouldPreserveVisibleOnTransient = !containerView.isHidden && + scheduleTransientRecoveryRetryIfNeeded( + forWebViewId: webViewId, + entry: &entry, + webView: webView, + reason: "hostBoundsNotReady" + ) + if shouldPreserveVisibleOnTransient { +#if DEBUG + dlog( + "browser.portal.hidden.deferKeep web=\(browserPortalDebugToken(webView)) " + + "reason=hostBoundsNotReady frame=\(browserPortalDebugFrame(containerView.frame))" + ) +#endif + return + } + } else { + resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) + } + containerView.setDropZoneOverlay(zone: nil) containerView.isHidden = true - scheduleDeferredFullSynchronizeAll() + if entry.visibleInUI { + _ = scheduleTransientRecoveryRetryIfNeeded( + forWebViewId: webViewId, + entry: &entry, + webView: webView, + reason: "hostBoundsNotReady" + ) + } else { + scheduleDeferredFullSynchronizeAll() + } return } let oldFrame = containerView.frame @@ -908,6 +1652,28 @@ final class WindowBrowserPortal: NSObject { tinyFrame || !hasFiniteFrame || outsideHostBounds + let transientRecoveryReason: String? = { + guard entry.visibleInUI else { return nil } + if anchorHidden { return "anchorHidden" } + if !hasFiniteFrame { return "nonFiniteFrame" } + if outsideHostBounds { return "outsideHostBounds" } + if tinyFrame { return "tinyFrame" } + return nil + }() + let didScheduleTransientRecovery: Bool = { + guard let transientRecoveryReason else { return false } + return scheduleTransientRecoveryRetryIfNeeded( + forWebViewId: webViewId, + entry: &entry, + webView: webView, + reason: transientRecoveryReason + ) + }() + let shouldPreserveVisibleOnTransientGeometry = + didScheduleTransientRecovery && + shouldHide && + entry.visibleInUI && + !containerView.isHidden #if DEBUG let frameWasClamped = hasFiniteFrame && !Self.rectApproximatelyEqual(frameInHost, targetFrame) if frameWasClamped { @@ -934,13 +1700,20 @@ final class WindowBrowserPortal: NSObject { ) } #endif + if shouldPreserveVisibleOnTransientGeometry { +#if DEBUG + dlog( + "browser.portal.hidden.deferKeep web=\(browserPortalDebugToken(webView)) " + + "reason=\(transientRecoveryReason ?? "unknown") frame=\(browserPortalDebugFrame(containerView.frame))" + ) +#endif + } if !Self.rectApproximatelyEqual(oldFrame, targetFrame) { CATransaction.begin() CATransaction.setDisableActions(true) containerView.frame = targetFrame CATransaction.commit() - webView.needsLayout = true - webView.layoutSubtreeIfNeeded() + refreshReasons.append("frame") } let expectedContainerBounds = NSRect(origin: .zero, size: targetFrame.size) @@ -957,6 +1730,7 @@ final class WindowBrowserPortal: NSObject { "target=\(browserPortalDebugFrame(expectedContainerBounds))" ) #endif + refreshReasons.append("bounds") } let containerBounds = containerView.bounds @@ -985,20 +1759,51 @@ final class WindowBrowserPortal: NSObject { "source=\(source)" ) #endif + refreshReasons.append("webFrame") } - if containerView.isHidden != shouldHide { + let revealedForDisplay = !shouldHide && containerView.isHidden + if shouldHide, !containerView.isHidden, !shouldPreserveVisibleOnTransientGeometry { #if DEBUG dlog( "browser.portal.hidden container=\(browserPortalDebugToken(containerView)) " + "web=\(browserPortalDebugToken(webView)) value=\(shouldHide ? 1 : 0) " + "visibleInUI=\(entry.visibleInUI ? 1 : 0) anchorHidden=\(anchorHidden ? 1 : 0) " + + "tiny=\(tinyFrame ? 1 : 0) finite=\(hasFiniteFrame ? 1 : 0) " + + "outside=\(outsideHostBounds ? 1 : 0) frame=\(browserPortalDebugFrame(targetFrame)) " + + "host=\(browserPortalDebugFrame(hostBounds))" + ) +#endif + containerView.isHidden = true + } else if !shouldHide, containerView.isHidden { +#if DEBUG + dlog( + "browser.portal.hidden container=\(browserPortalDebugToken(containerView)) " + + "web=\(browserPortalDebugToken(webView)) value=0 " + + "visibleInUI=\(entry.visibleInUI ? 1 : 0) anchorHidden=\(anchorHidden ? 1 : 0) " + "tiny=\(tinyFrame ? 1 : 0) finite=\(hasFiniteFrame ? 1 : 0) " + "outside=\(outsideHostBounds ? 1 : 0) frame=\(browserPortalDebugFrame(targetFrame)) " + "host=\(browserPortalDebugFrame(hostBounds))" ) #endif - containerView.isHidden = shouldHide + containerView.isHidden = false + } + containerView.setDropZoneOverlay(zone: containerView.isHidden ? nil : entry.dropZone) + if revealedForDisplay { + refreshReasons.append("reveal") + } + if forcePresentationRefresh { + refreshReasons.append("anchor") + } + if transientRecoveryReason == nil { + resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) + } + if !shouldHide, !refreshReasons.isEmpty { + refreshHostedWebViewPresentation( + webView, + in: containerView, + reason: "\(source):" + refreshReasons.joined(separator: ",") + ) } #if DEBUG dlog( @@ -1026,16 +1831,18 @@ final class WindowBrowserPortal: NSObject { let deadWebViewIds = entriesByWebViewId.compactMap { webViewId, entry -> ObjectIdentifier? in guard entry.webView != nil else { return webViewId } guard let container = entry.containerView else { return webViewId } - guard let anchor = entry.anchorView else { return webViewId } + guard let anchor = entry.anchorView else { + return entry.visibleInUI ? nil : webViewId + } if container.superview == nil || !container.isDescendant(of: hostView) { return webViewId } - if anchor.window !== currentWindow || anchor.superview == nil { - return webViewId - } - if let reference = installedReferenceView, - !anchor.isDescendant(of: reference) { - return webViewId + let anchorInvalidForCurrentHost = + anchor.window !== currentWindow || + anchor.superview == nil || + (installedReferenceView.map { !anchor.isDescendant(of: $0) } ?? false) + if anchorInvalidForCurrentHost { + return entry.visibleInUI ? nil : webViewId } return nil } @@ -1190,6 +1997,20 @@ enum BrowserWindowPortalRegistry { portal.updateEntryVisibility(forWebViewId: webViewId, visibleInUI: visibleInUI, zPriority: zPriority) } + static func updateDropZoneOverlay(for webView: WKWebView, zone: DropZone?) { + let webViewId = ObjectIdentifier(webView) + guard let windowId = webViewToWindowId[webViewId], + let portal = portalsByWindowId[windowId] else { return } + portal.updateDropZoneOverlay(forWebViewId: webViewId, zone: zone) + } + + static func updatePaneDropContext(for webView: WKWebView, context: BrowserPaneDropContext?) { + let webViewId = ObjectIdentifier(webView) + guard let windowId = webViewToWindowId[webViewId], + let portal = portalsByWindowId[windowId] else { return } + portal.updatePaneDropContext(forWebViewId: webViewId, context: context) + } + static func detach(webView: WKWebView) { let webViewId = ObjectIdentifier(webView) guard let windowId = webViewToWindowId.removeValue(forKey: webViewId) else { return } diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 0b9bd263..f886eb22 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -631,7 +631,12 @@ final class FileDropOverlayView: NSView { } /// Hit-tests the window to find a WKWebView (browser panel) under the cursor. - private func webViewUnderPoint(_ windowPoint: NSPoint) -> WKWebView? { + func webViewUnderPoint(_ windowPoint: NSPoint) -> WKWebView? { + if let window, + let portalWebView = BrowserWindowPortalRegistry.webViewAtWindowPoint(windowPoint, in: window) { + return portalWebView + } + guard let window, let contentView = window.contentView else { return nil } isHidden = true defer { isHidden = false } diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index c965af1b..73f8e3e5 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -211,6 +211,7 @@ struct BrowserPanelView: View { let portalPriority: Int let onRequestPanelFocus: () -> Void @Environment(\.colorScheme) private var colorScheme + @Environment(\.paneDropZone) private var paneDropZone @State private var omnibarState = OmnibarState() @State private var addressBarFocused: Bool = false @AppStorage(BrowserSearchSettings.searchEngineKey) private var searchEngineRaw = BrowserSearchSettings.defaultSearchEngine.rawValue @@ -737,7 +738,8 @@ struct BrowserPanelView: View { shouldAttachWebView: isVisibleInUI, shouldFocusWebView: isFocused && !addressBarFocused, isPanelFocused: isFocused, - portalZPriority: portalPriority + portalZPriority: portalPriority, + paneDropZone: paneDropZone ) // Keep the host stable for normal pane churn, but force a remount when // BrowserPanel replaces its underlying WKWebView after process termination. @@ -3036,6 +3038,7 @@ struct WebViewRepresentable: NSViewRepresentable { let shouldFocusWebView: Bool let isPanelFocused: Bool let portalZPriority: Int + let paneDropZone: DropZone? final class Coordinator { weak var panel: BrowserPanel? @@ -3206,6 +3209,7 @@ struct WebViewRepresentable: NSViewRepresentable { coordinator.desiredPortalZPriority = portalZPriority coordinator.attachGeneration += 1 let generation = coordinator.attachGeneration + let paneDropContext = shouldAttachWebView ? currentPaneDropContext() : nil host.onDidMoveToWindow = { [weak host, weak webView, weak coordinator] in guard let host, let webView, let coordinator else { return } @@ -3217,6 +3221,7 @@ struct WebViewRepresentable: NSViewRepresentable { visibleInUI: coordinator.desiredPortalVisibleInUI, zPriority: coordinator.desiredPortalZPriority ) + BrowserWindowPortalRegistry.updatePaneDropContext(for: webView, context: paneDropContext) coordinator.lastPortalHostId = ObjectIdentifier(host) } host.onGeometryChanged = { [weak host, weak coordinator] in @@ -3260,6 +3265,15 @@ struct WebViewRepresentable: NSViewRepresentable { ) } + BrowserWindowPortalRegistry.updateDropZoneOverlay( + for: webView, + zone: shouldAttachWebView ? paneDropZone : nil + ) + BrowserWindowPortalRegistry.updatePaneDropContext( + for: webView, + context: paneDropContext + ) + panel.restoreDeveloperToolsAfterAttachIfNeeded() #if DEBUG @@ -3372,7 +3386,24 @@ struct WebViewRepresentable: NSViewRepresentable { window.makeFirstResponder(nil) } } - BrowserWindowPortalRegistry.detach(webView: webView) + + // SwiftUI can transiently dismantle/rebuild the browser host view during split + // rearrangement. Do not detach the portal-hosted WKWebView here; explicit detach + // still happens on real web view replacement and panel teardown. + BrowserWindowPortalRegistry.updateDropZoneOverlay(for: webView, zone: nil) + BrowserWindowPortalRegistry.updatePaneDropContext(for: webView, context: nil) coordinator.lastPortalHostId = nil } + + private func currentPaneDropContext() -> BrowserPaneDropContext? { + guard let workspace = AppDelegate.shared?.tabManager?.tabs.first(where: { $0.id == panel.workspaceId }), + let paneId = workspace.paneId(forPanelId: panel.id) else { + return nil + } + return BrowserPaneDropContext( + workspaceId: panel.workspaceId, + panelId: panel.id, + paneId: paneId + ) + } } diff --git a/Sources/Panels/CmuxWebView.swift b/Sources/Panels/CmuxWebView.swift index a8a9e144..723dedb9 100644 --- a/Sources/Panels/CmuxWebView.swift +++ b/Sources/Panels/CmuxWebView.swift @@ -1113,6 +1113,11 @@ final class CmuxWebView: WKWebView { NSPasteboard.PasteboardType("com.cmux.sidebar-tab-reorder"), ] + static func shouldRejectInternalPaneDrag(_ pasteboardTypes: [NSPasteboard.PasteboardType]?) -> Bool { + DragOverlayRoutingPolicy.hasBonsplitTabTransfer(pasteboardTypes) + || DragOverlayRoutingPolicy.hasSidebarTabReorder(pasteboardTypes) + } + override func registerForDraggedTypes(_ newTypes: [NSPasteboard.PasteboardType]) { let filtered = newTypes.filter { !Self.blockedDragTypes.contains($0) } if !filtered.isEmpty { @@ -1120,6 +1125,21 @@ final class CmuxWebView: WKWebView { } } + override func draggingEntered(_ sender: any NSDraggingInfo) -> NSDragOperation { + guard !Self.shouldRejectInternalPaneDrag(sender.draggingPasteboard.types) else { return [] } + return super.draggingEntered(sender) + } + + override func draggingUpdated(_ sender: any NSDraggingInfo) -> NSDragOperation { + guard !Self.shouldRejectInternalPaneDrag(sender.draggingPasteboard.types) else { return [] } + return super.draggingUpdated(sender) + } + + override func performDragOperation(_ sender: any NSDraggingInfo) -> Bool { + guard !Self.shouldRejectInternalPaneDrag(sender.draggingPasteboard.types) else { return false } + return super.performDragOperation(sender) + } + override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) { super.willOpenMenu(menu, with: event) lastContextMenuPoint = convert(event.locationInWindow, from: nil) diff --git a/Sources/TerminalWindowPortal.swift b/Sources/TerminalWindowPortal.swift index 0c466aca..56a1783f 100644 --- a/Sources/TerminalWindowPortal.swift +++ b/Sources/TerminalWindowPortal.swift @@ -726,6 +726,7 @@ final class WindowTerminalPortal: NSObject { guard let window else { return false } guard let (container, reference) = installedTargetIfStillValid(for: window) ?? installationTarget(for: window) else { return false } + let browserHost = preferredBrowserHost(in: container) if hostView.superview !== container || installedContainerView !== container || @@ -734,7 +735,11 @@ final class WindowTerminalPortal: NSObject { installConstraints.removeAll() hostView.removeFromSuperview() - container.addSubview(hostView, positioned: .above, relativeTo: reference) + if let browserHost { + container.addSubview(hostView, positioned: .below, relativeTo: browserHost) + } else { + container.addSubview(hostView, positioned: .above, relativeTo: reference) + } installConstraints = [ hostView.leadingAnchor.constraint(equalTo: reference.leadingAnchor), @@ -745,6 +750,10 @@ final class WindowTerminalPortal: NSObject { NSLayoutConstraint.activate(installConstraints) installedContainerView = container installedReferenceView = reference + } else if let browserHost { + if !Self.isView(browserHost, above: hostView, in: container) { + container.addSubview(hostView, positioned: .below, relativeTo: browserHost) + } } else if !Self.isView(hostView, above: reference, in: container) { container.addSubview(hostView, positioned: .above, relativeTo: reference) } @@ -837,6 +846,10 @@ final class WindowTerminalPortal: NSObject { return viewIndex > referenceIndex } + private func preferredBrowserHost(in container: NSView) -> WindowBrowserHostView? { + container.subviews.last(where: { $0 is WindowBrowserHostView }) as? WindowBrowserHostView + } + #if DEBUG private func nearestBonsplitContainer(from anchorView: NSView) -> NSView? { var current: NSView? = anchorView diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index ca826cca..e5f4b40f 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2423,7 +2423,7 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { XCTAssertFalse(panel.shouldPreserveWebViewAttachmentDuringTransientHide()) } - func testWebViewDismantleDetachesPortalHostedWebViewWhenDeveloperToolsIntentIsVisible() { + func testWebViewDismantleKeepsPortalHostedWebViewAttachedWhenDeveloperToolsIntentIsVisible() { let (panel, _) = makePanelWithInspector() XCTAssertTrue(panel.showDeveloperTools()) @@ -2449,17 +2449,18 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { shouldAttachWebView: true, shouldFocusWebView: false, isPanelFocused: true, - portalZPriority: 0 + portalZPriority: 0, + paneDropZone: nil ) let coordinator = representable.makeCoordinator() coordinator.webView = panel.webView WebViewRepresentable.dismantleNSView(anchor, coordinator: coordinator) - XCTAssertNil(panel.webView.superview) + XCTAssertNotNil(panel.webView.superview) window.orderOut(nil) } - func testWebViewDismantleDetachesPortalHostedWebViewWhenDeveloperToolsIntentIsHidden() { + func testWebViewDismantleKeepsPortalHostedWebViewAttachedWhenDeveloperToolsIntentIsHidden() { let (panel, _) = makePanelWithInspector() XCTAssertFalse(panel.shouldPreserveWebViewAttachmentDuringTransientHide()) @@ -2485,13 +2486,14 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { shouldAttachWebView: true, shouldFocusWebView: false, isPanelFocused: true, - portalZPriority: 0 + portalZPriority: 0, + paneDropZone: nil ) let coordinator = representable.makeCoordinator() coordinator.webView = panel.webView WebViewRepresentable.dismantleNSView(anchor, coordinator: coordinator) - XCTAssertNil(panel.webView.superview) + XCTAssertNotNil(panel.webView.superview) window.orderOut(nil) } } @@ -7708,6 +7710,197 @@ final class WindowBrowserHostViewTests: XCTestCase { let contentPointInHost = host.convert(contentPointInWindow, from: nil) XCTAssertTrue(host.hitTest(contentPointInHost) === child) } + + func testDragHoverEventsPassThroughForTabTransferOnBrowserHoverEvents() { + XCTAssertTrue( + WindowBrowserHostView.shouldPassThroughToDragTargets( + pasteboardTypes: [DragOverlayRoutingPolicy.bonsplitTabTransferType], + eventType: .cursorUpdate + ) + ) + XCTAssertTrue( + WindowBrowserHostView.shouldPassThroughToDragTargets( + pasteboardTypes: [DragOverlayRoutingPolicy.bonsplitTabTransferType], + eventType: .mouseEntered + ) + ) + } + + func testDragHoverEventsPassThroughForSidebarReorderWithoutMouseButtonState() { + XCTAssertTrue( + WindowBrowserHostView.shouldPassThroughToDragTargets( + pasteboardTypes: [DragOverlayRoutingPolicy.sidebarTabReorderType], + eventType: .cursorUpdate + ) + ) + } + + func testDragHoverEventsDoNotPassThroughForUnrelatedPasteboardTypes() { + XCTAssertFalse( + WindowBrowserHostView.shouldPassThroughToDragTargets( + pasteboardTypes: [.fileURL], + eventType: .cursorUpdate + ) + ) + } +} + +@MainActor +final class CmuxWebViewDragRoutingTests: XCTestCase { + func testRejectsInternalPaneDragEvenWhenFilePromiseTypesArePresent() { + XCTAssertTrue( + CmuxWebView.shouldRejectInternalPaneDrag([ + DragOverlayRoutingPolicy.bonsplitTabTransferType, + NSPasteboard.PasteboardType("com.apple.pasteboard.promised-file-url"), + ]) + ) + } + + func testAllowsRegularExternalFileDrops() { + XCTAssertFalse(CmuxWebView.shouldRejectInternalPaneDrag([.fileURL])) + } +} + +@MainActor +final class BrowserPaneDropRoutingTests: XCTestCase { + func testVerticalZonesFollowAppKitCoordinates() { + let size = CGSize(width: 240, height: 180) + + XCTAssertEqual( + BrowserPaneDropRouting.zone(for: CGPoint(x: size.width * 0.5, y: size.height - 8), in: size), + .top + ) + XCTAssertEqual( + BrowserPaneDropRouting.zone(for: CGPoint(x: size.width * 0.5, y: 8), in: size), + .bottom + ) + } + + func testHitTestingCapturesOnlyForRelevantDragEvents() { + XCTAssertTrue( + BrowserPaneDropTargetView.shouldCaptureHitTesting( + pasteboardTypes: [DragOverlayRoutingPolicy.bonsplitTabTransferType], + eventType: .cursorUpdate + ) + ) + XCTAssertFalse( + BrowserPaneDropTargetView.shouldCaptureHitTesting( + pasteboardTypes: [DragOverlayRoutingPolicy.bonsplitTabTransferType], + eventType: .leftMouseDown + ) + ) + XCTAssertFalse( + BrowserPaneDropTargetView.shouldCaptureHitTesting( + pasteboardTypes: [.fileURL], + eventType: .cursorUpdate + ) + ) + } + + func testCenterDropOnSamePaneIsNoOp() { + let paneId = PaneID(id: UUID()) + let target = BrowserPaneDropContext( + workspaceId: UUID(), + panelId: UUID(), + paneId: paneId + ) + let transfer = BrowserPaneDragTransfer( + tabId: UUID(), + sourcePaneId: paneId.id, + sourceProcessId: Int32(ProcessInfo.processInfo.processIdentifier) + ) + + XCTAssertEqual( + BrowserPaneDropRouting.action(for: transfer, target: target, zone: .center), + .noOp + ) + } + + func testRightEdgeDropBuildsSplitMoveAction() { + let paneId = PaneID(id: UUID()) + let target = BrowserPaneDropContext( + workspaceId: UUID(), + panelId: UUID(), + paneId: paneId + ) + let tabId = UUID() + let transfer = BrowserPaneDragTransfer( + tabId: tabId, + sourcePaneId: UUID(), + sourceProcessId: Int32(ProcessInfo.processInfo.processIdentifier) + ) + + XCTAssertEqual( + BrowserPaneDropRouting.action(for: transfer, target: target, zone: .right), + .move( + tabId: tabId, + targetWorkspaceId: target.workspaceId, + targetPane: paneId, + splitTarget: BrowserPaneSplitTarget(orientation: .horizontal, insertFirst: false) + ) + ) + } + + func testDecodeTransferPayloadReadsTabAndSourcePane() { + let tabId = UUID() + let sourcePaneId = UUID() + let payload = try! JSONSerialization.data( + withJSONObject: [ + "tab": ["id": tabId.uuidString], + "sourcePaneId": sourcePaneId.uuidString, + "sourceProcessId": ProcessInfo.processInfo.processIdentifier, + ] + ) + + let transfer = BrowserPaneDragTransfer.decode(from: payload) + + XCTAssertEqual(transfer?.tabId, tabId) + XCTAssertEqual(transfer?.sourcePaneId, sourcePaneId) + XCTAssertTrue(transfer?.isFromCurrentProcess == true) + } +} + +@MainActor +final class WindowBrowserSlotViewTests: XCTestCase { + private final class CapturingView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + bounds.contains(point) ? self : nil + } + } + + private func advanceAnimations() { + RunLoop.current.run(until: Date().addingTimeInterval(0.25)) + } + + func testDropZoneOverlayStaysAboveContentWithoutBlockingHits() { + let slot = WindowBrowserSlotView(frame: NSRect(x: 0, y: 0, width: 200, height: 100)) + let child = CapturingView(frame: slot.bounds) + child.autoresizingMask = [.width, .height] + slot.addSubview(child) + + slot.setDropZoneOverlay(zone: .right) + slot.layoutSubtreeIfNeeded() + + guard let overlay = slot.subviews.first(where: { + $0 !== child && String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") + }) else { + XCTFail("Expected browser slot drop-zone overlay") + return + } + + XCTAssertTrue(slot.subviews.last === overlay, "Overlay should stay above the hosted web view") + XCTAssertFalse(overlay.isHidden) + XCTAssertEqual(overlay.frame.origin.x, 100, accuracy: 0.5) + XCTAssertEqual(overlay.frame.origin.y, 4, accuracy: 0.5) + XCTAssertEqual(overlay.frame.size.width, 96, accuracy: 0.5) + XCTAssertEqual(overlay.frame.size.height, 92, accuracy: 0.5) + XCTAssertNil(overlay.hitTest(NSPoint(x: 120, y: 50)), "Overlay should never intercept pointer hits") + XCTAssertTrue(slot.hitTest(NSPoint(x: 120, y: 50)) === child) + + slot.setDropZoneOverlay(zone: nil) + advanceAnimations() + XCTAssertTrue(overlay.isHidden, "Clearing the drop zone should hide the overlay") + } } @MainActor @@ -8817,6 +9010,54 @@ final class TerminalWindowPortalLifecycleTests: XCTestCase { ) } + func testTerminalPortalHostStaysBelowBrowserPortalHostWhenBothAreInstalled() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 320), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + + let browserPortal = WindowBrowserPortal(window: window) + let terminalPortal = WindowTerminalPortal(window: window) + _ = browserPortal.webViewAtWindowPoint(NSPoint(x: 1, y: 1)) + _ = terminalPortal.viewAtWindowPoint(NSPoint(x: 1, y: 1)) + + guard let contentView = window.contentView, + let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + func assertHostOrder(_ message: String) { + guard let terminalHostIndex = container.subviews.firstIndex(where: { $0 is WindowTerminalHostView }), + let browserHostIndex = container.subviews.firstIndex(where: { $0 is WindowBrowserHostView }) else { + XCTFail("Expected both portal hosts in same container") + return + } + + XCTAssertLessThan( + terminalHostIndex, + browserHostIndex, + message + ) + } + + assertHostOrder("Terminal portal host should start below browser portal host") + + let anchor = NSView(frame: NSRect(x: 24, y: 24, width: 220, height: 150)) + contentView.addSubview(anchor) + let hosted = GhosttySurfaceScrollView( + surfaceView: GhosttyNSView(frame: NSRect(x: 0, y: 0, width: 120, height: 80)) + ) + terminalPortal.bind(hostedView: hosted, to: anchor, visibleInUI: true) + terminalPortal.synchronizeHostedViewForAnchor(anchor) + + assertHostOrder("Terminal portal bind/sync should not rise above the browser portal host") + } + func testRegistryPrunesPortalWhenWindowCloses() { let baseline = TerminalWindowPortalRegistry.debugPortalCount() let window = NSWindow( @@ -9057,6 +9298,15 @@ final class TerminalWindowPortalLifecycleTests: XCTestCase { @MainActor final class BrowserWindowPortalLifecycleTests: XCTestCase { + private final class TrackingPortalWebView: WKWebView { + private(set) var displayIfNeededCount = 0 + + override func displayIfNeeded() { + displayIfNeededCount += 1 + super.displayIfNeeded() + } + } + private func realizeWindowLayout(_ window: NSWindow) { window.makeKeyAndOrderFront(nil) window.displayIfNeeded() @@ -9065,6 +9315,16 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { window.contentView?.layoutSubtreeIfNeeded() } + private func advanceAnimations() { + RunLoop.current.run(until: Date().addingTimeInterval(0.25)) + } + + private func dropZoneOverlay(in slot: WindowBrowserSlotView, excluding webView: WKWebView) -> NSView? { + slot.subviews.first(where: { + $0 !== webView && String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") + }) + } + func testPortalHostInstallsAboveContentViewForVisibility() { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), @@ -9095,6 +9355,60 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { ) } + func testBrowserPortalHostStaysAboveTerminalPortalHostDuringPortalChurn() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 320), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + + let browserPortal = WindowBrowserPortal(window: window) + let terminalPortal = WindowTerminalPortal(window: window) + _ = browserPortal.webViewAtWindowPoint(NSPoint(x: 1, y: 1)) + _ = terminalPortal.viewAtWindowPoint(NSPoint(x: 1, y: 1)) + + guard let contentView = window.contentView, + let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + func assertHostOrder(_ message: String) { + guard let browserHostIndex = container.subviews.firstIndex(where: { $0 is WindowBrowserHostView }), + let terminalHostIndex = container.subviews.firstIndex(where: { $0 is WindowTerminalHostView }) else { + XCTFail("Expected both portal hosts in same container") + return + } + + XCTAssertGreaterThan( + browserHostIndex, + terminalHostIndex, + message + ) + } + + assertHostOrder("Browser portal host should start above terminal portal host") + + let terminalAnchor = NSView(frame: NSRect(x: 20, y: 20, width: 200, height: 140)) + contentView.addSubview(terminalAnchor) + let terminalHostedView = GhosttySurfaceScrollView( + surfaceView: GhosttyNSView(frame: NSRect(x: 0, y: 0, width: 120, height: 80)) + ) + terminalPortal.bind(hostedView: terminalHostedView, to: terminalAnchor, visibleInUI: true) + terminalPortal.synchronizeHostedViewForAnchor(terminalAnchor) + assertHostOrder("Terminal portal sync should not rise above the browser portal host") + + let browserAnchor = NSView(frame: NSRect(x: 240, y: 20, width: 220, height: 140)) + contentView.addSubview(browserAnchor) + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + browserPortal.bind(webView: webView, to: browserAnchor, visibleInUI: true) + browserPortal.synchronizeWebViewForAnchor(browserAnchor) + assertHostOrder("Browser portal sync should keep browser panes above portal-hosted terminals") + } + func testAnchorRebindKeepsWebViewInStablePortalSuperview() { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 500, height: 300), @@ -9175,6 +9489,46 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { XCTAssertEqual(slot.frame.size.height, 150, accuracy: 0.5) } + func testPortalClipsAnchorFrameThroughAncestorBounds() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 320), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + let portal = WindowBrowserPortal(window: window) + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let clipView = NSView(frame: NSRect(x: 60, y: 40, width: 150, height: 120)) + contentView.addSubview(clipView) + + // Simulate SwiftUI/AppKit reporting an anchor wider than the actual visible pane. + let anchor = NSView(frame: NSRect(x: -30, y: 0, width: 220, height: 120)) + clipView.addSubview(anchor) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + portal.bind(webView: webView, to: anchor, visibleInUI: true) + contentView.layoutSubtreeIfNeeded() + clipView.layoutSubtreeIfNeeded() + portal.synchronizeWebViewForAnchor(anchor) + + guard let slot = webView.superview as? WindowBrowserSlotView else { + XCTFail("Expected browser slot") + return + } + + XCTAssertFalse(slot.isHidden, "Ancestor clipping should keep the browser visible in the real pane") + XCTAssertEqual(slot.frame.origin.x, 60, accuracy: 0.5) + XCTAssertEqual(slot.frame.origin.y, 40, accuracy: 0.5) + XCTAssertEqual(slot.frame.size.width, 150, accuracy: 0.5) + XCTAssertEqual(slot.frame.size.height, 120, accuracy: 0.5) + } + func testPortalSyncNormalizesOutOfBoundsWebFrame() { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 500, height: 300), @@ -9245,6 +9599,154 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { XCTAssertGreaterThan(host.bounds.height, 1, "Portal host height should be ready for clipping/sync") } + func testPortalDropZoneOverlayPersistsAcrossVisibilityChanges() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 320), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + let portal = WindowBrowserPortal(window: window) + + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + let anchor = NSView(frame: NSRect(x: 40, y: 24, width: 220, height: 160)) + contentView.addSubview(anchor) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + portal.bind(webView: webView, to: anchor, visibleInUI: true) + portal.synchronizeWebViewForAnchor(anchor) + + guard let slot = webView.superview as? WindowBrowserSlotView, + let overlay = dropZoneOverlay(in: slot, excluding: webView) else { + XCTFail("Expected browser slot overlay") + return + } + + XCTAssertTrue(overlay.isHidden, "Overlay should start hidden without an active drop zone") + + portal.updateDropZoneOverlay(forWebViewId: ObjectIdentifier(webView), zone: .right) + slot.layoutSubtreeIfNeeded() + XCTAssertFalse(overlay.isHidden) + XCTAssertTrue(slot.subviews.last === overlay, "Overlay should remain above the hosted web view") + XCTAssertEqual(overlay.frame.origin.x, 110, accuracy: 0.5) + XCTAssertEqual(overlay.frame.origin.y, 4, accuracy: 0.5) + XCTAssertEqual(overlay.frame.size.width, 106, accuracy: 0.5) + XCTAssertEqual(overlay.frame.size.height, 152, accuracy: 0.5) + + portal.updateEntryVisibility(forWebViewId: ObjectIdentifier(webView), visibleInUI: false, zPriority: 0) + portal.synchronizeWebViewForAnchor(anchor) + advanceAnimations() + XCTAssertTrue(overlay.isHidden, "Invisible browser entries should hide the overlay") + + portal.updateEntryVisibility(forWebViewId: ObjectIdentifier(webView), visibleInUI: true, zPriority: 0) + portal.synchronizeWebViewForAnchor(anchor) + XCTAssertFalse(overlay.isHidden, "Restoring visibility should restore the active drop-zone overlay") + } + + func testPortalRevealRefreshesHostedWebViewWithoutFrameDelta() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 320), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + let portal = WindowBrowserPortal(window: window) + + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + let anchor = NSView(frame: NSRect(x: 40, y: 24, width: 220, height: 160)) + contentView.addSubview(anchor) + + let webView = TrackingPortalWebView(frame: .zero, configuration: WKWebViewConfiguration()) + portal.bind(webView: webView, to: anchor, visibleInUI: true) + portal.synchronizeWebViewForAnchor(anchor) + advanceAnimations() + let initialDisplayCount = webView.displayIfNeededCount + + portal.updateEntryVisibility(forWebViewId: ObjectIdentifier(webView), visibleInUI: false, zPriority: 0) + portal.synchronizeWebViewForAnchor(anchor) + advanceAnimations() + let hiddenDisplayCount = webView.displayIfNeededCount + + portal.updateEntryVisibility(forWebViewId: ObjectIdentifier(webView), visibleInUI: true, zPriority: 0) + portal.synchronizeWebViewForAnchor(anchor) + advanceAnimations() + + XCTAssertGreaterThanOrEqual(hiddenDisplayCount, initialDisplayCount) + XCTAssertGreaterThan( + webView.displayIfNeededCount, + hiddenDisplayCount, + "Revealing an existing portal-hosted browser should refresh WebKit presentation immediately" + ) + } + + func testVisiblePortalEntryHidesWithoutDetachingDuringTransientAnchorRemovalUntilRebind() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 320), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + let portal = WindowBrowserPortal(window: window) + + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let anchorFrame = NSRect(x: 40, y: 24, width: 220, height: 160) + let anchor1 = NSView(frame: anchorFrame) + contentView.addSubview(anchor1) + + let webView = TrackingPortalWebView(frame: .zero, configuration: WKWebViewConfiguration()) + portal.bind(webView: webView, to: anchor1, visibleInUI: true) + portal.synchronizeWebViewForAnchor(anchor1) + advanceAnimations() + + guard let slot = webView.superview as? WindowBrowserSlotView else { + XCTFail("Expected browser slot") + return + } + + anchor1.removeFromSuperview() + portal.synchronizeWebViewForAnchor(anchor1) + advanceAnimations() + + XCTAssertTrue(webView.superview === slot, "Visible browser entries should not detach during transient anchor removal") + XCTAssertTrue( + slot.isHidden, + "Transient anchor churn should hide the stale browser slot instead of rendering in the wrong pane" + ) + XCTAssertEqual(portal.debugEntryCount(), 1) + + let displayCountBeforeRebind = webView.displayIfNeededCount + let anchor2 = NSView(frame: anchorFrame) + contentView.addSubview(anchor2) + portal.bind(webView: webView, to: anchor2, visibleInUI: true) + portal.synchronizeWebViewForAnchor(anchor2) + advanceAnimations() + + XCTAssertTrue(webView.superview === slot, "Rebinding after transient anchor removal should reuse the existing portal slot") + XCTAssertFalse(slot.isHidden) + XCTAssertEqual(portal.debugEntryCount(), 1) + XCTAssertGreaterThan( + webView.displayIfNeededCount, + displayCountBeforeRebind, + "Anchor rebinds should refresh hosted browser presentation even when geometry is unchanged" + ) + } + func testRegistryDetachRemovesPortalHostedWebView() { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), @@ -9271,6 +9773,57 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { } } +@MainActor +final class FileDropOverlayViewTests: XCTestCase { + private func realizeWindowLayout(_ window: NSWindow) { + window.makeKeyAndOrderFront(nil) + window.displayIfNeeded() + window.contentView?.layoutSubtreeIfNeeded() + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + window.contentView?.layoutSubtreeIfNeeded() + } + + func testOverlayResolvesPortalHostedBrowserWebViewForFileDrops() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 280), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { + NotificationCenter.default.post(name: NSWindow.willCloseNotification, object: window) + window.orderOut(nil) + } + realizeWindowLayout(window) + + guard let contentView = window.contentView, + let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let anchor = NSView(frame: NSRect(x: 40, y: 36, width: 220, height: 150)) + contentView.addSubview(anchor) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + BrowserWindowPortalRegistry.bind(webView: webView, to: anchor, visibleInUI: true) + BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) + + let overlay = FileDropOverlayView(frame: container.bounds) + overlay.autoresizingMask = [.width, .height] + container.addSubview(overlay, positioned: .above, relativeTo: nil) + + let point = anchor.convert( + NSPoint(x: anchor.bounds.midX, y: anchor.bounds.midY), + to: nil + ) + XCTAssertTrue( + overlay.webViewUnderPoint(point) === webView, + "File-drop overlay should resolve portal-hosted browser panes so Finder uploads still reach WKWebView" + ) + } +} + final class BrowserLinkOpenSettingsTests: XCTestCase { private var suiteName: String! private var defaults: UserDefaults! From e49e572505f43e01810e3bfa05a60098cb114f7f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:36:47 -0800 Subject: [PATCH 104/232] Fix browser Cmd+F overlay clipping in portal mode (#916) * Fix browser Cmd+F overlay clipping in portal mode * Fix browser Cmd+F panel update regression * Fix browser find overlay lifecycle and focus * Extract regression test helpers for browser find guards * Restore new-tab Cmd+F overlay and harden test helper * Fix browser Cmd+F focus handoff race * Fix browser Cmd+F focus loss across page load * Address review feedback on browser find focus guards * Add Cmd+F pane-switch regression UI tests * Run Cmd+F pane-switch regressions from existing UI suite * Restore browser find focus on pane refocus * Stabilize Cmd+F pane-switch regressions with focus-state recorder * Make autofocus race UI test wait on deterministic page signal * Fix cmuxTests WebViewRepresentable init after browser search state param --- Sources/AppDelegate.swift | 82 +++++-- Sources/Find/BrowserSearchOverlay.swift | 14 +- Sources/Find/SurfaceSearchOverlay.swift | 2 + Sources/Panels/BrowserPanel.swift | 37 +++- Sources/Panels/BrowserPanelView.swift | 84 +++++++- Sources/Workspace.swift | 9 +- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 2 + .../BrowserPaneNavigationKeybindUITests.swift | 174 +++++++++++++++ tests/regression_helpers.py | 51 +++++ ..._browser_find_overlay_portal_regression.py | 203 ++++++++++++++++++ 10 files changed, 629 insertions(+), 29 deletions(-) create mode 100644 tests/regression_helpers.py create mode 100644 tests/test_browser_find_overlay_portal_regression.py diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 07a7ba8b..91625919 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5482,6 +5482,60 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + private func isGotoSplitUITestRecordingEnabled() -> Bool { + let env = ProcessInfo.processInfo.environment + return env["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] == "1" || env["CMUX_UI_TEST_GOTO_SPLIT_RECORD_ONLY"] == "1" + } + + private func gotoSplitUITestDataPath() -> String? { + guard isGotoSplitUITestRecordingEnabled() else { return nil } + let env = ProcessInfo.processInfo.environment + guard let path = env["CMUX_UI_TEST_GOTO_SPLIT_PATH"], !path.isEmpty else { return nil } + return path + } + + private func gotoSplitFindStateSnapshot(for workspace: Workspace) -> [String: String] { + var updates: [String: String] = [ + "focusedPaneId": workspace.bonsplitController.focusedPaneId?.description ?? "" + ] + + if let focusedPanelId = workspace.focusedPanelId { + updates["focusedPanelId"] = focusedPanelId.uuidString + if let terminal = workspace.terminalPanel(for: focusedPanelId) { + updates["focusedPanelKind"] = "terminal" + updates["focusedTerminalFindNeedle"] = terminal.searchState?.needle ?? "" + updates["focusedBrowserFindNeedle"] = "" + } else if let browser = workspace.browserPanel(for: focusedPanelId) { + updates["focusedPanelKind"] = "browser" + updates["focusedBrowserFindNeedle"] = browser.searchState?.needle ?? "" + updates["focusedTerminalFindNeedle"] = "" + } else { + updates["focusedPanelKind"] = "other" + updates["focusedTerminalFindNeedle"] = "" + updates["focusedBrowserFindNeedle"] = "" + } + } else { + updates["focusedPanelId"] = "" + updates["focusedPanelKind"] = "none" + updates["focusedTerminalFindNeedle"] = "" + updates["focusedBrowserFindNeedle"] = "" + } + + let terminalWithFind = workspace.panels.values + .compactMap { $0 as? TerminalPanel } + .first(where: { $0.searchState != nil }) + updates["terminalFindPanelId"] = terminalWithFind?.id.uuidString ?? "" + updates["terminalFindNeedle"] = terminalWithFind?.searchState?.needle ?? "" + + let browserWithFind = workspace.panels.values + .compactMap { $0 as? BrowserPanel } + .first(where: { $0.searchState != nil }) + updates["browserFindPanelId"] = browserWithFind?.id.uuidString ?? "" + updates["browserFindNeedle"] = browserWithFind?.searchState?.needle ?? "" + + return updates + } + private func focusWebViewForGotoSplitUITest(tab: Workspace, browserPanelId: UUID, attempt: Int = 0) { let maxAttempts = 120 guard attempt < maxAttempts else { @@ -5603,10 +5657,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } private func recordGotoSplitMoveIfNeeded(direction: NavigationDirection) { - let env = ProcessInfo.processInfo.environment - guard env["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] == "1" else { return } - guard let tabManager, - let focusedPaneId = tabManager.selectedWorkspace?.bonsplitController.focusedPaneId else { return } + guard isGotoSplitUITestRecordingEnabled() else { return } + guard let tabManager, let workspace = tabManager.selectedWorkspace else { return } let directionValue: String switch direction { @@ -5620,15 +5672,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent directionValue = "down" } - writeGotoSplitTestData([ - "lastMoveDirection": directionValue, - "focusedPaneId": focusedPaneId.description - ]) + var updates = gotoSplitFindStateSnapshot(for: workspace) + updates["lastMoveDirection"] = directionValue + writeGotoSplitTestData(updates) } private func recordGotoSplitSplitIfNeeded(direction: SplitDirection) { - let env = ProcessInfo.processInfo.environment - guard env["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] == "1" else { return } + guard isGotoSplitUITestRecordingEnabled() else { return } guard let workspace = tabManager?.selectedWorkspace else { return } let directionValue: String @@ -5643,16 +5693,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent directionValue = "down" } - writeGotoSplitTestData([ - "lastSplitDirection": directionValue, - "paneCountAfterSplit": String(workspace.bonsplitController.allPaneIds.count), - "focusedPaneId": workspace.bonsplitController.focusedPaneId?.description ?? "" - ]) + var updates = gotoSplitFindStateSnapshot(for: workspace) + updates["lastSplitDirection"] = directionValue + updates["paneCountAfterSplit"] = String(workspace.bonsplitController.allPaneIds.count) + writeGotoSplitTestData(updates) } private func writeGotoSplitTestData(_ updates: [String: String]) { - let env = ProcessInfo.processInfo.environment - guard let path = env["CMUX_UI_TEST_GOTO_SPLIT_PATH"], !path.isEmpty else { return } + guard let path = gotoSplitUITestDataPath() else { return } var payload = loadGotoSplitTestData(at: path) for (key, value) in updates { payload[key] = value diff --git a/Sources/Find/BrowserSearchOverlay.swift b/Sources/Find/BrowserSearchOverlay.swift index 635aecdb..9a022e5f 100644 --- a/Sources/Find/BrowserSearchOverlay.swift +++ b/Sources/Find/BrowserSearchOverlay.swift @@ -14,11 +14,21 @@ struct BrowserSearchOverlay: View { private let padding: CGFloat = 8 + private func requestSearchFieldFocus(maxAttempts: Int = 3) { + guard maxAttempts > 0 else { return } + isSearchFieldFocused = true + guard maxAttempts > 1 else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + requestSearchFieldFocus(maxAttempts: maxAttempts - 1) + } + } + var body: some View { GeometryReader { geo in HStack(spacing: 4) { TextField("Search", text: $searchState.needle) .textFieldStyle(.plain) + .accessibilityIdentifier("BrowserFindSearchTextField") .frame(width: 180) .padding(.leading, 8) .padding(.trailing, 50) @@ -95,13 +105,13 @@ struct BrowserSearchOverlay: View { #if DEBUG dlog("browser.findbar.appear panel=\(panelId.uuidString.prefix(5))") #endif - isSearchFieldFocused = true + requestSearchFieldFocus() } .onReceive(NotificationCenter.default.publisher(for: .browserSearchFocus)) { notification in guard let notifiedPanelId = notification.object as? UUID, notifiedPanelId == panelId else { return } DispatchQueue.main.async { - isSearchFieldFocused = true + requestSearchFieldFocus() } } .background( diff --git a/Sources/Find/SurfaceSearchOverlay.swift b/Sources/Find/SurfaceSearchOverlay.swift index 17c795e6..0efc3d50 100644 --- a/Sources/Find/SurfaceSearchOverlay.swift +++ b/Sources/Find/SurfaceSearchOverlay.swift @@ -55,6 +55,7 @@ struct SurfaceSearchOverlay: View { onNavigateSearch(action) } ) + .accessibilityIdentifier("TerminalFindSearchTextField") .frame(width: 180) .padding(.leading, 8) .padding(.trailing, 50) @@ -303,6 +304,7 @@ private struct SearchTextFieldRepresentable: NSViewRepresentable { let field = SearchNativeTextField(frame: .zero) field.font = .systemFont(ofSize: NSFont.systemFontSize) field.placeholderString = String(localized: "search.placeholder", defaultValue: "Search") + field.setAccessibilityIdentifier("TerminalFindSearchTextField") field.delegate = context.coordinator field.stringValue = text context.coordinator.parentField = field diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 885dd16d..89858475 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1595,10 +1595,8 @@ final class BrowserPanel: Panel, ObservableObject { Task { @MainActor [weak self] in self?.refreshFavicon(from: webView) self?.applyBrowserThemeModeIfNeeded() - // Clear find-in-page on navigation so stale highlights don't persist. - if self?.searchState != nil { - self?.searchState = nil - } + // Keep find-in-page open through load completion and refresh matches for the new DOM. + self?.restoreFindStateAfterNavigation(replaySearch: true) } } navDelegate.didFailNavigation = { [weak self] _, failedURL in @@ -1609,10 +1607,8 @@ final class BrowserPanel: Panel, ObservableObject { self.pageTitle = failedURL.isEmpty ? "" : failedURL self.faviconPNGData = nil self.lastFaviconURLString = nil - // Clear find-in-page so stale highlights don't persist. - if self.searchState != nil { - self.searchState = nil - } + // Keep find-in-page open and clear stale counters on failed loads. + self.restoreFindStateAfterNavigation(replaySearch: false) } } navDelegate.openInNewTab = { [weak self] url in @@ -2645,6 +2641,18 @@ extension BrowserPanel { if searchState == nil { searchState = BrowserSearchState() } + postBrowserSearchFocusNotification() + // Focus notification can race with portal overlay mount. Re-post on the + // next runloop and shortly after so the find field can claim first responder. + DispatchQueue.main.async { [weak self] in + self?.postBrowserSearchFocusNotification() + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + self?.postBrowserSearchFocusNotification() + } + } + + private func postBrowserSearchFocusNotification() { NotificationCenter.default.post(name: .browserSearchFocus, object: id) } @@ -2668,6 +2676,16 @@ extension BrowserPanel { searchState = nil } + private func restoreFindStateAfterNavigation(replaySearch: Bool) { + guard let state = searchState else { return } + state.total = nil + state.selected = nil + if replaySearch, !state.needle.isEmpty { + executeFindSearch(state.needle) + } + postBrowserSearchFocusNotification() + } + private func executeFindSearch(_ needle: String) { guard !needle.isEmpty else { executeFindClear() @@ -2743,6 +2761,9 @@ extension BrowserPanel { if suppressWebViewFocusForAddressBar { return true } + if searchState != nil { + return true + } if let until = suppressWebViewFocusUntil { return Date() < until } diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 73f8e3e5..07295066 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -318,7 +318,10 @@ struct BrowserPanelView: View { .allowsHitTesting(false) } .overlay { - if let searchState = panel.searchState { + // Keep Cmd+F usable when the browser is still in the empty new-tab + // state (no WKWebView mounted yet). WebView-backed cases are hosted + // in AppKit by WebViewRepresentable to avoid layering/clipping issues. + if !panel.shouldRenderWebView, let searchState = panel.searchState { BrowserSearchOverlay( panelId: panel.id, searchState: searchState, @@ -735,6 +738,7 @@ struct BrowserPanelView: View { if panel.shouldRenderWebView { WebViewRepresentable( panel: panel, + browserSearchState: panel.searchState, shouldAttachWebView: isVisibleInUI, shouldFocusWebView: isFocused && !addressBarFocused, isPanelFocused: isFocused, @@ -3034,6 +3038,7 @@ private struct OmnibarSuggestionsView: View { /// NSViewRepresentable wrapper for WKWebView struct WebViewRepresentable: NSViewRepresentable { let panel: BrowserPanel + let browserSearchState: BrowserSearchState? let shouldAttachWebView: Bool let shouldFocusWebView: Bool let isPanelFocused: Bool @@ -3047,6 +3052,7 @@ struct WebViewRepresentable: NSViewRepresentable { var desiredPortalVisibleInUI: Bool = true var desiredPortalZPriority: Int = 0 var lastPortalHostId: ObjectIdentifier? + var searchOverlayHostingView: NSHostingView? } private final class HostContainerView: NSView { @@ -3199,6 +3205,67 @@ struct WebViewRepresentable: NSViewRepresentable { host.onGeometryChanged = nil } + private static func removeSearchOverlay(from coordinator: Coordinator) { + coordinator.searchOverlayHostingView?.removeFromSuperview() + coordinator.searchOverlayHostingView = nil + } + + private static func updateSearchOverlay( + panel: BrowserPanel, + coordinator: Coordinator, + containerView: NSView? + ) { + // Layering contract: keep browser Cmd+F UI in the portal-hosted AppKit layer. + // SwiftUI panel overlays can be covered by portal-hosted WKWebView content. + guard let searchState = panel.searchState, + let containerView else { + removeSearchOverlay(from: coordinator) + return + } + + let rootView = BrowserSearchOverlay( + panelId: panel.id, + searchState: searchState, + onNext: { [weak panel] in + panel?.findNext() + }, + onPrevious: { [weak panel] in + panel?.findPrevious() + }, + onClose: { [weak panel] in + panel?.hideFind() + } + ) + + if let overlay = coordinator.searchOverlayHostingView { + overlay.rootView = rootView + if overlay.superview !== containerView { + overlay.removeFromSuperview() + containerView.addSubview(overlay, positioned: .above, relativeTo: nil) + NSLayoutConstraint.activate([ + overlay.topAnchor.constraint(equalTo: containerView.topAnchor), + overlay.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + overlay.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + overlay.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + ]) + } else if containerView.subviews.last !== overlay { + containerView.addSubview(overlay, positioned: .above, relativeTo: nil) + } + return + } + + let overlay = NSHostingView(rootView: rootView) + overlay.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(overlay, positioned: .above, relativeTo: nil) + NSLayoutConstraint.activate([ + overlay.topAnchor.constraint(equalTo: containerView.topAnchor), + overlay.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + overlay.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + overlay.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + ]) + coordinator.searchOverlayHostingView = overlay + } + private func updateUsingWindowPortal(_ nsView: NSView, context: Context, webView: WKWebView) { guard let host = nsView as? HostContainerView else { return } @@ -3223,6 +3290,13 @@ struct WebViewRepresentable: NSViewRepresentable { ) BrowserWindowPortalRegistry.updatePaneDropContext(for: webView, context: paneDropContext) coordinator.lastPortalHostId = ObjectIdentifier(host) + if let panel = coordinator.panel { + Self.updateSearchOverlay( + panel: panel, + coordinator: coordinator, + containerView: webView.superview + ) + } } host.onGeometryChanged = { [weak host, weak coordinator] in guard let host, let coordinator else { return } @@ -3254,6 +3328,11 @@ struct WebViewRepresentable: NSViewRepresentable { coordinator.lastPortalHostId = hostId } BrowserWindowPortalRegistry.synchronizeForAnchor(host) + Self.updateSearchOverlay( + panel: panel, + coordinator: coordinator, + containerView: webView.superview + ) } else { // Bind is deferred until host moves into a window. Keep the current // portal entry's desired state in sync so stale callbacks cannot keep @@ -3263,6 +3342,7 @@ struct WebViewRepresentable: NSViewRepresentable { visibleInUI: coordinator.desiredPortalVisibleInUI, zPriority: coordinator.desiredPortalZPriority ) + Self.removeSearchOverlay(from: coordinator) } BrowserWindowPortalRegistry.updateDropZoneOverlay( @@ -3291,6 +3371,7 @@ struct WebViewRepresentable: NSViewRepresentable { let webView = panel.webView let coordinator = context.coordinator if let previousWebView = coordinator.webView, previousWebView !== webView { + Self.removeSearchOverlay(from: coordinator) BrowserWindowPortalRegistry.detach(webView: previousWebView) coordinator.lastPortalHostId = nil } @@ -3362,6 +3443,7 @@ struct WebViewRepresentable: NSViewRepresentable { static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) { coordinator.attachGeneration += 1 clearPortalCallbacks(for: nsView) + removeSearchOverlay(from: coordinator) guard let webView = coordinator.webView else { return } let panel = coordinator.panel diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index a4a870ea..969819ee 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -3074,7 +3074,14 @@ final class Workspace: Identifiable, ObservableObject { } if let browserPanel = panels[panelId] as? BrowserPanel { - maybeAutoFocusBrowserAddressBarOnPanelFocus(browserPanel, trigger: trigger) + // Keep browser find focus behavior aligned with terminal find behavior. + // When switching back to a pane with an already-open find bar, reassert + // focus to that field instead of leaving first responder stale. + if browserPanel.searchState != nil { + browserPanel.startFind() + } else { + maybeAutoFocusBrowserAddressBarOnPanelFocus(browserPanel, trigger: trigger) + } } } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index e5f4b40f..2177f000 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2446,6 +2446,7 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { let representable = WebViewRepresentable( panel: panel, + browserSearchState: nil, shouldAttachWebView: true, shouldFocusWebView: false, isPanelFocused: true, @@ -2483,6 +2484,7 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { let representable = WebViewRepresentable( panel: panel, + browserSearchState: nil, shouldAttachWebView: true, shouldFocusWebView: false, isPanelFocused: true, diff --git a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift index 4ed0a584..9cd9f038 100644 --- a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift +++ b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift @@ -423,6 +423,180 @@ final class BrowserPaneNavigationKeybindUITests: XCTestCase { ) } + func testCmdOptionPaneSwitchPreservesFindFieldFocus() { + runFindFocusPersistenceScenario(route: .cmdOptionArrows, useAutofocusRacePage: false) + } + + func testCmdCtrlPaneSwitchPreservesFindFieldFocus() { + runFindFocusPersistenceScenario(route: .cmdCtrlLetters, useAutofocusRacePage: false) + } + + func testCmdOptionPaneSwitchPreservesFindFieldFocusDuringPageAutofocusRace() { + runFindFocusPersistenceScenario(route: .cmdOptionArrows, useAutofocusRacePage: true) + } + + private enum FindFocusRoute { + case cmdOptionArrows + case cmdCtrlLetters + } + + private func runFindFocusPersistenceScenario(route: FindFocusRoute, useAutofocusRacePage: Bool) { + let app = XCUIApplication() + app.launchEnvironment["CMUX_SOCKET_PATH"] = socketPath + app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_RECORD_ONLY"] = "1" + app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath + if route == .cmdCtrlLetters { + app.launchEnvironment["CMUX_UI_TEST_FOCUS_SHORTCUTS"] = "1" + } + launchAndEnsureForeground(app) + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 10.0), "Expected main window to exist") + + // Repro setup: split, open browser split, navigate to example.com. + app.typeKey("d", modifierFlags: [.command]) + focusRightPaneForFindScenario(app, route: route) + + app.typeKey("l", modifierFlags: [.command, .shift]) + let omnibar = app.textFields["BrowserOmnibarTextField"].firstMatch + XCTAssertTrue(omnibar.waitForExistence(timeout: 8.0), "Expected browser omnibar after Cmd+Shift+L") + + app.typeKey("a", modifierFlags: [.command]) + app.typeKey(XCUIKeyboardKey.delete.rawValue, modifierFlags: []) + if useAutofocusRacePage { + app.typeText(autofocusRacePageURL) + } else { + app.typeText("example.com") + } + app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: []) + + if useAutofocusRacePage { + XCTAssertTrue( + waitForOmnibarToContain(omnibar, value: "data:text/html", timeout: 8.0), + "Expected browser navigation to data URL before running find flow. value=\(String(describing: omnibar.value))" + ) + } else { + XCTAssertTrue( + waitForOmnibarToContainExampleDomain(omnibar, timeout: 8.0), + "Expected browser navigation to example domain before running find flow. value=\(String(describing: omnibar.value))" + ) + } + + // Left terminal: Cmd+F then type "la". + focusLeftPaneForFindScenario(app, route: route) + XCTAssertTrue( + waitForDataMatch(timeout: 6.0) { data in + data["focusedPanelKind"] == "terminal" + }, + "Expected left terminal pane to be focused before terminal find. data=\(String(describing: loadData()))" + ) + app.typeKey("f", modifierFlags: [.command]) + app.typeText("la") + + // Right browser: Cmd+F then type "am". + focusRightPaneForFindScenario(app, route: route) + XCTAssertTrue( + waitForDataMatch(timeout: 6.0) { data in + data["lastMoveDirection"] == "right" + && data["focusedPanelKind"] == "browser" + && data["terminalFindNeedle"] == "la" + }, + "Expected terminal find query to persist as 'la' after focusing browser pane. data=\(String(describing: loadData()))" + ) + app.typeKey("f", modifierFlags: [.command]) + app.typeText("am") + + if useAutofocusRacePage { + XCTAssertTrue( + waitForOmnibarToContain(omnibar, value: "#focused", timeout: 5.0), + "Expected autofocus race page to signal focus handoff via URL hash. value=\(String(describing: omnibar.value))" + ) + } + + // Left terminal: typing should keep going into terminal find field. + focusLeftPaneForFindScenario(app, route: route) + XCTAssertTrue( + waitForDataMatch(timeout: 6.0) { data in + data["lastMoveDirection"] == "left" + && data["focusedPanelKind"] == "terminal" + && data["browserFindNeedle"] == "am" + }, + "Expected browser find query to persist as 'am' after returning left. data=\(String(describing: loadData()))" + ) + app.typeText("foo") + + // Right browser: typing should keep going into browser find field. + focusRightPaneForFindScenario(app, route: route) + XCTAssertTrue( + waitForDataMatch(timeout: 6.0) { data in + data["lastMoveDirection"] == "right" + && data["focusedPanelKind"] == "browser" + && data["terminalFindNeedle"] == "lafoo" + }, + "Expected terminal find query to stay focused and become 'lafoo'. data=\(String(describing: loadData()))" + ) + app.typeText("do") + + // Move left once more so the recorder captures browser find state after typing. + focusLeftPaneForFindScenario(app, route: route) + XCTAssertTrue( + waitForDataMatch(timeout: 6.0) { data in + data["lastMoveDirection"] == "left" + && data["focusedPanelKind"] == "terminal" + && data["browserFindNeedle"] == "amdo" + }, + "Expected browser find query to stay focused and become 'amdo'. data=\(String(describing: loadData()))" + ) + } + + private func focusLeftPaneForFindScenario(_ app: XCUIApplication, route: FindFocusRoute) { + switch route { + case .cmdOptionArrows: + app.typeKey(XCUIKeyboardKey.leftArrow.rawValue, modifierFlags: [.command, .option]) + case .cmdCtrlLetters: + app.typeKey("h", modifierFlags: [.command, .control]) + } + } + + private func focusRightPaneForFindScenario(_ app: XCUIApplication, route: FindFocusRoute) { + switch route { + case .cmdOptionArrows: + app.typeKey(XCUIKeyboardKey.rightArrow.rawValue, modifierFlags: [.command, .option]) + case .cmdCtrlLetters: + app.typeKey("l", modifierFlags: [.command, .control]) + } + } + + private func waitForOmnibarToContainExampleDomain(_ omnibar: XCUIElement, timeout: TimeInterval) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + let value = (omnibar.value as? String) ?? "" + if value.contains("example.com") || value.contains("example.org") { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + let value = (omnibar.value as? String) ?? "" + return value.contains("example.com") || value.contains("example.org") + } + + private func waitForOmnibarToContain(_ omnibar: XCUIElement, value expectedSubstring: String, timeout: TimeInterval) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + let value = (omnibar.value as? String) ?? "" + if value.contains(expectedSubstring) { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + let value = (omnibar.value as? String) ?? "" + return value.contains(expectedSubstring) + } + + private var autofocusRacePageURL: String { + "data:text/html,%3Cinput%20id%3D%22q%22%3E%3Cscript%3EsetTimeout%28function%28%29%7Bdocument.getElementById%28%22q%22%29.focus%28%29%3Blocation.hash%3D%22focused%22%3B%7D%2C700%29%3B%3C%2Fscript%3E" + } + private func launchAndEnsureForeground(_ app: XCUIApplication, timeout: TimeInterval = 12.0) { app.launch() XCTAssertTrue( diff --git a/tests/regression_helpers.py b/tests/regression_helpers.py new file mode 100644 index 00000000..73965c51 --- /dev/null +++ b/tests/regression_helpers.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Shared helpers for static regression tests.""" + +from __future__ import annotations + +import shutil +import subprocess +from pathlib import Path + + +def repo_root() -> Path: + git = shutil.which("git") + if git is None: + return Path(__file__).resolve().parents[1] + try: + result = subprocess.run( + [git, "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=False, + timeout=2, + ) + except (subprocess.TimeoutExpired, OSError): + return Path(__file__).resolve().parents[1] + if result.returncode == 0: + return Path(result.stdout.strip()) + return Path(__file__).resolve().parents[1] + + +def extract_block(source: str, signature: str) -> str: + # Targeted helper for this regression suite: assumes braces in the matched + # block are structural (not inside strings/comments/character literals). + start = source.find(signature) + if start < 0: + raise ValueError(f"Missing signature: {signature}") + + brace_start = source.find("{", start) + if brace_start < 0: + raise ValueError(f"Missing opening brace for: {signature}") + + depth = 0 + for idx in range(brace_start, len(source)): + char = source[idx] + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return source[brace_start : idx + 1] + + raise ValueError(f"Unbalanced braces for: {signature}") diff --git a/tests/test_browser_find_overlay_portal_regression.py b/tests/test_browser_find_overlay_portal_regression.py new file mode 100644 index 00000000..468a1892 --- /dev/null +++ b/tests/test_browser_find_overlay_portal_regression.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +"""Regression guards for browser Cmd+F overlay layering in portal mode.""" + +from __future__ import annotations + +from regression_helpers import extract_block, repo_root + + +def main() -> int: + root = repo_root() + view_path = root / "Sources" / "Panels" / "BrowserPanelView.swift" + panel_path = root / "Sources" / "Panels" / "BrowserPanel.swift" + overlay_path = root / "Sources" / "Find" / "BrowserSearchOverlay.swift" + source = view_path.read_text(encoding="utf-8") + panel_source = panel_path.read_text(encoding="utf-8") + overlay_source = overlay_path.read_text(encoding="utf-8") + failures: list[str] = [] + + try: + browser_panel_view_block = extract_block( + source, "struct BrowserPanelView: View" + ) + except ValueError as error: + failures.append(str(error)) + browser_panel_view_block = "" + + try: + body_block = extract_block(browser_panel_view_block, "var body: some View") + except ValueError as error: + failures.append(str(error)) + body_block = "" + + fallback_signature = ( + "if !panel.shouldRenderWebView, let searchState = panel.searchState {" + ) + fallback_block = "" + if body_block: + try: + fallback_block = extract_block(body_block, fallback_signature) + except ValueError: + failures.append( + "BrowserPanelView must provide BrowserSearchOverlay fallback for new-tab state " + "(when WKWebView is not mounted)" + ) + if fallback_block and "BrowserSearchOverlay(" not in fallback_block: + failures.append( + "BrowserPanelView fallback branch must mount BrowserSearchOverlay for new-tab state" + ) + + try: + webview_repr_block = extract_block( + source, "struct WebViewRepresentable: NSViewRepresentable" + ) + except ValueError as error: + failures.append(str(error)) + webview_repr_block = "" + + if webview_repr_block: + if "let browserSearchState: BrowserSearchState?" not in webview_repr_block: + failures.append( + "WebViewRepresentable must include browserSearchState so Cmd+F state changes trigger updates" + ) + if ( + "var searchOverlayHostingView: NSHostingView?" + not in webview_repr_block + ): + failures.append( + "WebViewRepresentable.Coordinator must own a BrowserSearchOverlay hosting view" + ) + if "private static func updateSearchOverlay(" not in webview_repr_block: + failures.append( + "WebViewRepresentable must define updateSearchOverlay helper" + ) + if "containerView: webView.superview" not in webview_repr_block: + failures.append( + "Portal updates must sync BrowserSearchOverlay against the web view container" + ) + if "removeSearchOverlay(from: coordinator)" not in webview_repr_block: + failures.append( + "WebViewRepresentable must remove browser search overlays during teardown/rebind" + ) + + if "browserSearchState: panel.searchState" not in source: + failures.append( + "BrowserPanelView must pass panel.searchState into WebViewRepresentable" + ) + + try: + update_ns_view_block = extract_block( + webview_repr_block, "func updateNSView(_ nsView: NSView, context: Context)" + ) + except ValueError as error: + failures.append(str(error)) + update_ns_view_block = "" + + if "updateSearchOverlay(" in update_ns_view_block: + failures.append( + "updateNSView must not re-run updateSearchOverlay outside portal lifecycle paths" + ) + + try: + suppress_focus_block = extract_block( + panel_source, "func shouldSuppressWebViewFocus() -> Bool" + ) + except ValueError as error: + failures.append(str(error)) + suppress_focus_block = "" + + if "if searchState != nil {" not in suppress_focus_block: + failures.append( + "BrowserPanel.shouldSuppressWebViewFocus must suppress focus while find-in-page is active" + ) + + try: + start_find_block = extract_block(panel_source, "func startFind()") + except ValueError as error: + failures.append(str(error)) + start_find_block = "" + + if start_find_block: + if "postBrowserSearchFocusNotification()" not in start_find_block: + failures.append( + "BrowserPanel.startFind must publish browserSearchFocus notifications" + ) + if "DispatchQueue.main.async {" not in start_find_block: + failures.append( + "BrowserPanel.startFind must re-post focus on next runloop to avoid mount races" + ) + if "DispatchQueue.main.asyncAfter" not in start_find_block: + failures.append( + "BrowserPanel.startFind must re-post focus shortly after to avoid portal mount races" + ) + + try: + init_block = extract_block(panel_source, "init(workspaceId: UUID") + except ValueError as error: + failures.append(str(error)) + init_block = "" + + if init_block: + if ( + "self?.searchState = nil" in init_block + or "self.searchState = nil" in init_block + ): + failures.append( + "BrowserPanel navigation callbacks must not clear searchState entirely to avoid losing find bar focus" + ) + if "restoreFindStateAfterNavigation(replaySearch: true)" not in init_block: + failures.append( + "BrowserPanel.didFinish must preserve find state and replay search on the new page" + ) + if "restoreFindStateAfterNavigation(replaySearch: false)" not in init_block: + failures.append( + "BrowserPanel.didFailNavigation must preserve find state without replaying search" + ) + + try: + restore_find_state_block = extract_block( + panel_source, "private func restoreFindStateAfterNavigation(replaySearch: Bool)" + ) + except ValueError as error: + failures.append(str(error)) + restore_find_state_block = "" + + if restore_find_state_block: + if "state.total = nil" not in restore_find_state_block: + failures.append( + "BrowserPanel restoreFindStateAfterNavigation must clear stale find total count" + ) + if "state.selected = nil" not in restore_find_state_block: + failures.append( + "BrowserPanel restoreFindStateAfterNavigation must clear stale selected match" + ) + if "if replaySearch, !state.needle.isEmpty {" not in restore_find_state_block: + failures.append( + "BrowserPanel restoreFindStateAfterNavigation must only replay search for successful navigations" + ) + if "postBrowserSearchFocusNotification()" not in restore_find_state_block: + failures.append( + "BrowserPanel restoreFindStateAfterNavigation must reassert find field focus" + ) + + if "private func requestSearchFieldFocus(" not in overlay_source: + failures.append( + "BrowserSearchOverlay must define requestSearchFieldFocus retry helper" + ) + if "requestSearchFieldFocus()" not in overlay_source: + failures.append( + "BrowserSearchOverlay must request text focus from appear/notification paths" + ) + + if failures: + print("FAIL: browser find overlay portal regression guards failed") + for failure in failures: + print(f" - {failure}") + return 1 + + print("PASS: browser find overlay remains mounted in portal-hosted AppKit layer") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 0fb2b414b081a8a74df91d7be3f69c6542b4d449 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:19:02 -0800 Subject: [PATCH 105/232] "Claude PR Assistant workflow" (#965) --- .github/workflows/claude.yml | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..d300267f --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' + From ee899042ca9c1615e41d81da3ded77684bce6812 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:35:57 -0800 Subject: [PATCH 106/232] Publish socket health into notify focus UI test --- Sources/AppDelegate.swift | 50 +++++++++++++++++++ .../MultiWindowNotificationsUITests.swift | 49 ++++++++++++++++-- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 9df55688..0eb83629 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5725,10 +5725,60 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "expectedLatestWindowId": window1.windowId.uuidString, "expectedLatestTabId": tabId1.uuidString, ], at: path) + self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) } } } + private func publishMultiWindowNotificationSocketStateIfNeeded(at path: String) { + let env = ProcessInfo.processInfo.environment + guard env["CMUX_UI_TEST_SOCKET_SANITY"] == "1" else { return } + + guard let config = socketListenerConfigurationIfEnabled() else { + writeMultiWindowNotificationTestData([ + "socketExpectedPath": env["CMUX_SOCKET_PATH"] ?? "", + "socketMode": "off", + "socketReady": "0", + "socketIsRunning": "0", + "socketAcceptLoopAlive": "0", + "socketPathMatches": "0", + "socketPathExists": "0", + "socketFailureSignals": "socket_disabled", + ], at: path) + return + } + + writeMultiWindowNotificationTestData([ + "socketExpectedPath": config.path, + "socketMode": config.mode.rawValue, + "socketReady": "pending", + ], at: path) + + restartSocketListenerIfEnabled(source: "uiTest.multiWindowNotifications.setup") + + let deadline = Date().addingTimeInterval(12.0) + func publish() { + let health = TerminalController.shared.socketListenerHealth(expectedSocketPath: config.path) + let isTimedOut = Date() >= deadline + writeMultiWindowNotificationTestData([ + "socketExpectedPath": config.path, + "socketMode": config.mode.rawValue, + "socketReady": health.isHealthy ? "1" : (isTimedOut ? "0" : "pending"), + "socketIsRunning": health.isRunning ? "1" : "0", + "socketAcceptLoopAlive": health.acceptLoopAlive ? "1" : "0", + "socketPathMatches": health.socketPathMatches ? "1" : "0", + "socketPathExists": health.socketPathExists ? "1" : "0", + "socketFailureSignals": health.failureSignals.joined(separator: ","), + ], at: path) + guard !health.isHealthy, !isTimedOut else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + publish() + } + } + + publish() + } + private func writeMultiWindowNotificationTestData(_ updates: [String: String], at path: String) { var payload = loadMultiWindowNotificationTestData(at: path) for (key, value) in updates { diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 4f875312..e5c4e4c7 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -207,19 +207,44 @@ final class MultiWindowNotificationsUITests: XCTestCase { "Expected app to launch for notify focus regression test. state=\(app.state.rawValue)" ) XCTAssertTrue( - waitForData(keys: ["tabId2"], timeout: 15.0), - "Expected multi-window notification setup data" + waitForDataMatch(timeout: 20.0) { data in + let tabId2 = data["tabId2"] ?? "" + let socketReady = data["socketReady"] ?? "" + return !tabId2.isEmpty && !socketReady.isEmpty && socketReady != "pending" + }, + "Expected multi-window notification setup data and socket readiness" ) - guard let tabId2 = loadData()?["tabId2"], !tabId2.isEmpty else { + guard let setup = loadData() else { + XCTFail("Missing setup data") + return + } + guard let tabId2 = setup["tabId2"], !tabId2.isEmpty else { XCTFail("Missing setup workspace id") return } + if let expectedSocketPath = setup["socketExpectedPath"], !expectedSocketPath.isEmpty { + socketPath = expectedSocketPath + } + if setup["socketReady"] != "1" { + XCTFail( + "Control socket unavailable in this test environment. expected=\(socketPath) " + + "mode=\(setup["socketMode"] ?? "") running=\(setup["socketIsRunning"] ?? "") " + + "acceptLoopAlive=\(setup["socketAcceptLoopAlive"] ?? "") pathMatches=\(setup["socketPathMatches"] ?? "") " + + "pathExists=\(setup["socketPathExists"] ?? "") signals=\(setup["socketFailureSignals"] ?? "")" + ) + return + } XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) - guard let resolvedPath = resolveSocketPath(timeout: 20.0, requiredWorkspaceId: tabId2) else { - XCTFail("Control socket unavailable in this test environment. requested=\(socketPath)") + guard let resolvedPath = resolveSocketPath(timeout: 5.0, requiredWorkspaceId: tabId2) else { + XCTFail( + "Control socket unavailable in this test environment. requested=\(socketPath) " + + "mode=\(setup["socketMode"] ?? "") running=\(setup["socketIsRunning"] ?? "") " + + "acceptLoopAlive=\(setup["socketAcceptLoopAlive"] ?? "") pathMatches=\(setup["socketPathMatches"] ?? "") " + + "pathExists=\(setup["socketPathExists"] ?? "") signals=\(setup["socketFailureSignals"] ?? "")" + ) return } socketPath = resolvedPath @@ -342,6 +367,20 @@ final class MultiWindowNotificationsUITests: XCTestCase { return false } + private func waitForDataMatch(timeout: TimeInterval, predicate: ([String: String]) -> Bool) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if let data = loadData(), predicate(data) { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + if let data = loadData(), predicate(data) { + return true + } + return false + } + private func waitForSocketPong(timeout: TimeInterval) -> String? { let deadline = Date().addingTimeInterval(timeout) var lastResponse: String? From 9c9670ea7110a3b323a0f32e32cab356617d3873 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:44:48 -0800 Subject: [PATCH 107/232] Use socket ping before notify UI test fallback --- .../MultiWindowNotificationsUITests.swift | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index e5c4e4c7..b2383065 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -238,21 +238,22 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) - guard let resolvedPath = resolveSocketPath(timeout: 5.0, requiredWorkspaceId: tabId2) else { + let pingResponse = waitForSocketPong(timeout: 20.0) + if pingResponse != "PONG", + let resolvedPath = resolveSocketPath(timeout: 5.0, requiredWorkspaceId: tabId2) { + socketPath = resolvedPath + } + + let confirmedPingResponse = pingResponse == "PONG" ? pingResponse : waitForSocketPong(timeout: 5.0) + guard confirmedPingResponse == "PONG" else { XCTFail( - "Control socket unavailable in this test environment. requested=\(socketPath) " + + "Control socket did not respond in time. path=\(socketPath) response=\(confirmedPingResponse ?? "") " + "mode=\(setup["socketMode"] ?? "") running=\(setup["socketIsRunning"] ?? "") " + "acceptLoopAlive=\(setup["socketAcceptLoopAlive"] ?? "") pathMatches=\(setup["socketPathMatches"] ?? "") " + "pathExists=\(setup["socketPathExists"] ?? "") signals=\(setup["socketFailureSignals"] ?? "")" ) return } - socketPath = resolvedPath - let pingResponse = waitForSocketPong(timeout: 20.0) - guard pingResponse == "PONG" else { - XCTFail("Control socket did not respond in time. path=\(socketPath) response=\(pingResponse ?? "")") - return - } guard let surfaceId = waitForSurfaceId(forWorkspaceId: tabId2, timeout: 12.0) else { XCTFail("Expected at least one surface in workspace \(tabId2). socket=\(socketPath)") From 103f80fac2ecce9c6702a3a32fd3a50145647fef Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:56:37 -0800 Subject: [PATCH 108/232] Support trailing browser output flags --- CLI/cmux.swift | 61 ++++++++++++++++++------- tests_v2/test_browser_cli_agent_port.py | 57 +++++++++++++++++++---- 2 files changed, 92 insertions(+), 26 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 4475f341..e9f8d40b 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -2583,7 +2583,34 @@ struct CMUXCLI { throw CLIError(message: "browser requires a subcommand") } - let (surfaceOpt, argsWithoutSurfaceFlag) = parseOption(commandArgs, name: "--surface") + var effectiveJSONOutput = jsonOutput + var effectiveIDFormat = idFormat + var browserArgs = commandArgs + + // Browser-skill examples often place output flags at the end of the command. + // Strip trailing display flags so they don't become part of a URL or selector. + while !browserArgs.isEmpty { + if browserArgs.last == "--json" { + effectiveJSONOutput = true + browserArgs.removeLast() + continue + } + + if browserArgs.count >= 2, + browserArgs[browserArgs.count - 2] == "--id-format" { + let raw = browserArgs.last! + guard let parsed = try CLIIDFormat.parse(raw) else { + throw CLIError(message: "--id-format must be one of: refs, uuids, both") + } + effectiveIDFormat = parsed + browserArgs.removeLast(2) + continue + } + + break + } + + let (surfaceOpt, argsWithoutSurfaceFlag) = parseOption(browserArgs, name: "--surface") var surfaceRaw = surfaceOpt var args = argsWithoutSurfaceFlag @@ -2612,8 +2639,8 @@ struct CMUXCLI { } func output(_ payload: [String: Any], fallback: String) { - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) return } print(fallback) @@ -2746,8 +2773,8 @@ struct CMUXCLI { } } let payload = try client.sendV2(method: "browser.open_split", params: params) - let surfaceText = formatHandle(payload, kind: "surface", idFormat: idFormat) ?? "unknown" - let paneText = formatHandle(payload, kind: "pane", idFormat: idFormat) ?? "unknown" + let surfaceText = formatHandle(payload, kind: "surface", idFormat: effectiveIDFormat) ?? "unknown" + let paneText = formatHandle(payload, kind: "pane", idFormat: effectiveIDFormat) ?? "unknown" let placement = ((payload["created_split"] as? Bool) == true) ? "split" : "reuse" output(payload, fallback: "OK surface=\(surfaceText) pane=\(paneText) placement=\(placement)") return @@ -2787,8 +2814,8 @@ struct CMUXCLI { if subcommand == "url" || subcommand == "get-url" { let sid = try requireSurface() let payload = try client.sendV2(method: "browser.url.get", params: ["surface_id": sid]) - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) } else { print((payload["url"] as? String) ?? "") } @@ -2805,8 +2832,8 @@ struct CMUXCLI { if ["is-webview-focused", "is_webview_focused"].contains(subcommand) { let sid = try requireSurface() let payload = try client.sendV2(method: "browser.is_webview_focused", params: ["surface_id": sid]) - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) } else { print((payload["focused"] as? Bool) == true ? "true" : "false") } @@ -2839,8 +2866,8 @@ struct CMUXCLI { } let payload = try client.sendV2(method: "browser.snapshot", params: params) - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) } else if let text = payload["snapshot"] as? String { print(text) } else { @@ -3059,8 +3086,8 @@ struct CMUXCLI { try data.write(to: URL(fileURLWithPath: outPathOpt)) } - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) } else if let outPathOpt { print("OK \(outPathOpt)") } else { @@ -3120,8 +3147,8 @@ struct CMUXCLI { "styles": "browser.get.styles", ] let payload = try client.sendV2(method: methodMap[getVerb]!, params: params) - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) } else if let value = payload["value"] { if let str = value as? String { print(str) @@ -3160,8 +3187,8 @@ struct CMUXCLI { throw CLIError(message: "Unsupported browser is subcommand: \(isVerb)") } let payload = try client.sendV2(method: method, params: ["surface_id": sid, "selector": selector]) - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + if effectiveJSONOutput { + print(jsonString(formatIDs(payload, mode: effectiveIDFormat))) } else if let value = payload["value"] { print("\(value)") } else { diff --git a/tests_v2/test_browser_cli_agent_port.py b/tests_v2/test_browser_cli_agent_port.py index d8266a66..5e33e9b7 100644 --- a/tests_v2/test_browser_cli_agent_port.py +++ b/tests_v2/test_browser_cli_agent_port.py @@ -91,6 +91,32 @@ def _run_cli_text(cli: str, args: list[str], retries: int = 3) -> str: raise cmuxError(f"CLI failed ({' '.join(args)}): {last_merged}") + +def _run_cli_tail_json(cli: str, args: list[str], retries: int = 3) -> dict: + last_merged = "" + for attempt in range(1, retries + 1): + proc = subprocess.run( + [cli, "--socket", SOCKET_PATH] + args, + capture_output=True, + text=True, + check=False, + ) + if proc.returncode == 0: + try: + return json.loads(proc.stdout or "{}") + except Exception as exc: # noqa: BLE001 + raise cmuxError(f"Invalid CLI JSON output for {' '.join(args)}: {proc.stdout!r} ({exc})") + + merged = f"{proc.stdout}\n{proc.stderr}".strip() + last_merged = merged + if "Command timed out" in merged and attempt < retries: + time.sleep(0.2) + continue + raise cmuxError(f"CLI failed ({' '.join(args)}): {merged}") + + raise cmuxError(f"CLI failed ({' '.join(args)}): {last_merged}") + + def _run_cli_expect_failure(cli: str, args: list[str], needles: list[str]) -> None: proc = subprocess.run( [cli, "--socket", SOCKET_PATH, "--json"] + args, @@ -144,15 +170,6 @@ def main() -> int: cli = _find_cli_binary() with _local_test_server() as page_url: - opened = _run_cli_json(cli, ["browser", "open", page_url]) - surface = str(opened.get("surface_ref") or opened.get("surface_id") or "") - _must(bool(surface), f"browser open returned no surface handle: {opened}") - _must(surface.startswith("surface:"), f"Expected short surface ref from browser open, got: {opened}") - - _run_cli_json(cli, ["browser", surface, "wait", "--load-state", "complete", "--timeout-ms", "15000"]) - snapshot_text = _run_cli_text(cli, ["browser", surface, "snapshot", "--interactive"]) - _must("ref=e" in snapshot_text, f"Expected snapshot text with refs from CLI: {snapshot_text!r}") - identify = _run_cli_json(cli, ["identify"]) focused = identify.get("focused") or {} workspace = str( @@ -163,6 +180,28 @@ def main() -> int: or "" ) _must(bool(workspace), f"Expected workspace handle from identify: {identify}") + os.environ["CMUX_WORKSPACE_ID"] = workspace + + opened_tail_json = _run_cli_tail_json( + cli, + ["browser", "open", page_url, "--workspace", workspace, "--id-format", "both", "--json"], + ) + tail_surface = str(opened_tail_json.get("surface_ref") or "") + _must(tail_surface.startswith("surface:"), f"Expected trailing --json browser open to return surface_ref: {opened_tail_json}") + _must(bool(opened_tail_json.get("surface_id")), f"Expected trailing --id-format both to preserve surface_id: {opened_tail_json}") + _must("--json" not in str(opened_tail_json.get("url") or ""), f"Trailing output flags leaked into browser open URL: {opened_tail_json}") + _run_cli_json(cli, ["browser", tail_surface, "wait", "--load-state", "complete", "--timeout-ms", "15000"]) + tail_url_payload = _run_cli_json(cli, ["browser", tail_surface, "url"]) + _must(str(tail_url_payload.get("url") or "").startswith(page_url), f"Expected trailing --json browser open to navigate: {tail_url_payload}") + + opened = _run_cli_json(cli, ["browser", "open", page_url]) + surface = str(opened.get("surface_ref") or opened.get("surface_id") or "") + _must(bool(surface), f"browser open returned no surface handle: {opened}") + _must(surface.startswith("surface:"), f"Expected short surface ref from browser open, got: {opened}") + + _run_cli_json(cli, ["browser", surface, "wait", "--load-state", "complete", "--timeout-ms", "15000"]) + snapshot_text = _run_cli_text(cli, ["browser", surface, "snapshot", "--interactive"]) + _must("ref=e" in snapshot_text, f"Expected snapshot text with refs from CLI: {snapshot_text!r}") opened_routed = _run_cli_json(cli, ["browser", "open", page_url, "--workspace", workspace]) routed_surface = str(opened_routed.get("surface_ref") or opened_routed.get("surface_id") or "") From 61792c3cafedc1ca99ba2ca1e313a4ba16f94733 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:57:05 -0800 Subject: [PATCH 109/232] Fix browser goto snapshot-after parsing --- CLI/cmux.swift | 9 +++++++-- tests_v2/test_browser_cli_agent_port.py | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 4475f341..039f110b 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -2755,12 +2755,17 @@ struct CMUXCLI { if subcommand == "goto" || subcommand == "navigate" { let sid = try requireSurface() - let url = subArgs.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) + var urlArgs = subArgs + let snapshotAfter = urlArgs.last == "--snapshot-after" + if snapshotAfter { + urlArgs.removeLast() + } + let url = urlArgs.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) guard !url.isEmpty else { throw CLIError(message: "browser \(subcommand) requires a URL") } var params: [String: Any] = ["surface_id": sid, "url": url] - if hasFlag(subArgs, name: "--snapshot-after") { + if snapshotAfter { params["snapshot_after"] = true } let payload = try client.sendV2(method: "browser.navigate", params: params) diff --git a/tests_v2/test_browser_cli_agent_port.py b/tests_v2/test_browser_cli_agent_port.py index d8266a66..29c36e97 100644 --- a/tests_v2/test_browser_cli_agent_port.py +++ b/tests_v2/test_browser_cli_agent_port.py @@ -173,6 +173,14 @@ def main() -> int: _must(routed_url.startswith(page_url), f"Expected routed URL to start with page URL, got: {routed_url_payload}") _must("--workspace" not in routed_url and "--window" not in routed_url, f"Routing flags leaked into URL: {routed_url_payload}") + goto_url = f"{page_url}?goto=1" + goto_payload = _run_cli_json(cli, ["browser", surface, "goto", goto_url, "--snapshot-after"]) + _must(bool(goto_payload.get("post_action_snapshot")), f"Expected goto --snapshot-after to include post_action_snapshot: {goto_payload}") + goto_url_payload = _run_cli_json(cli, ["browser", surface, "url"]) + current_goto_url = str(goto_url_payload.get("url") or "") + _must(current_goto_url.startswith(goto_url), f"Expected goto --snapshot-after current URL to match target URL: {goto_url_payload}") + _must("--snapshot-after" not in current_goto_url, f"Expected goto URL to exclude trailing flag text: {goto_url_payload}") + find_text = _run_cli_json(cli, ["browser", surface, "find", "text", "row-b"]) _must(str(find_text.get("element_ref") or "").startswith("@e"), f"Expected element_ref from find text: {find_text}") From 5fa357063252a04dff77ce0ee2407369d1d03c0d Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:10:00 -0800 Subject: [PATCH 110/232] Harden notify focus regression socket checks --- Sources/AppDelegate.swift | 52 +++-- Sources/TerminalController.swift | 94 +++++++++ .../MultiWindowNotificationsUITests.swift | 178 +++++++++++++++--- 3 files changed, 281 insertions(+), 43 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 0eb83629..f74e65db 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5739,6 +5739,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "socketExpectedPath": env["CMUX_SOCKET_PATH"] ?? "", "socketMode": "off", "socketReady": "0", + "socketPingResponse": "", "socketIsRunning": "0", "socketAcceptLoopAlive": "0", "socketPathMatches": "0", @@ -5752,27 +5753,50 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "socketExpectedPath": config.path, "socketMode": config.mode.rawValue, "socketReady": "pending", + "socketPingResponse": "", ], at: path) restartSocketListenerIfEnabled(source: "uiTest.multiWindowNotifications.setup") - let deadline = Date().addingTimeInterval(12.0) + let deadline = Date().addingTimeInterval(20.0) func publish() { let health = TerminalController.shared.socketListenerHealth(expectedSocketPath: config.path) let isTimedOut = Date() >= deadline - writeMultiWindowNotificationTestData([ - "socketExpectedPath": config.path, - "socketMode": config.mode.rawValue, - "socketReady": health.isHealthy ? "1" : (isTimedOut ? "0" : "pending"), - "socketIsRunning": health.isRunning ? "1" : "0", - "socketAcceptLoopAlive": health.acceptLoopAlive ? "1" : "0", - "socketPathMatches": health.socketPathMatches ? "1" : "0", - "socketPathExists": health.socketPathExists ? "1" : "0", - "socketFailureSignals": health.failureSignals.joined(separator: ","), - ], at: path) - guard !health.isHealthy, !isTimedOut else { return } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { - publish() + let socketPath = config.path + let socketMode = config.mode.rawValue + let dataPath = path + + DispatchQueue.global(qos: .utility).async { [weak self] in + let pingResponse = health.isHealthy + ? TerminalController.probeSocketCommand("ping", at: socketPath, timeout: 1.0) + : nil + let isReady = health.isHealthy && pingResponse == "PONG" + let failureSignals = { + var signals = health.failureSignals + if health.isHealthy && pingResponse != "PONG" { + signals.append("ping_timeout") + } + return signals.joined(separator: ",") + }() + + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.writeMultiWindowNotificationTestData([ + "socketExpectedPath": socketPath, + "socketMode": socketMode, + "socketReady": isReady ? "1" : (isTimedOut ? "0" : "pending"), + "socketPingResponse": pingResponse ?? "", + "socketIsRunning": health.isRunning ? "1" : "0", + "socketAcceptLoopAlive": health.acceptLoopAlive ? "1" : "0", + "socketPathMatches": health.socketPathMatches ? "1" : "0", + "socketPathExists": health.socketPathExists ? "1" : "0", + "socketFailureSignals": failureSignals, + ], at: dataPath) + guard !isTimedOut else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + publish() + } + } } } diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 56d7205b..b536d0e7 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -662,6 +662,100 @@ class TerminalController { ) } + nonisolated static func probeSocketCommand( + _ command: String, + at socketPath: String, + timeout: TimeInterval + ) -> String? { + let fd = socket(AF_UNIX, SOCK_STREAM, 0) + guard fd >= 0 else { return nil } + defer { close(fd) } + +#if os(macOS) + var noSigPipe: Int32 = 1 + _ = withUnsafePointer(to: &noSigPipe) { ptr in + setsockopt( + fd, + SOL_SOCKET, + SO_NOSIGPIPE, + ptr, + socklen_t(MemoryLayout.size) + ) + } +#endif + + var addr = sockaddr_un() + memset(&addr, 0, MemoryLayout.size) + addr.sun_family = sa_family_t(AF_UNIX) + + let maxLen = MemoryLayout.size(ofValue: addr.sun_path) + let pathBytes = Array(socketPath.utf8CString) + guard pathBytes.count <= maxLen else { return nil } + withUnsafeMutablePointer(to: &addr.sun_path) { ptr in + let raw = UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: CChar.self) + memset(raw, 0, maxLen) + for index in 0...offset(of: \.sun_path) ?? 0 + let addrLen = socklen_t(pathOffset + pathBytes.count) +#if os(macOS) + addr.sun_len = UInt8(min(Int(addrLen), 255)) +#endif + + let connectResult = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + connect(fd, sockaddrPtr, addrLen) + } + } + guard connectResult == 0 else { return nil } + + let payload = command + "\n" + let wroteAll = payload.withCString { cString in + var remaining = strlen(cString) + var pointer = UnsafeRawPointer(cString) + while remaining > 0 { + let written = write(fd, pointer, remaining) + if written <= 0 { return false } + remaining -= written + pointer = pointer.advanced(by: written) + } + return true + } + guard wroteAll else { return nil } + + let deadline = Date().addingTimeInterval(timeout) + var buffer = [UInt8](repeating: 0, count: 4096) + var response = "" + + while Date() < deadline { + var pollDescriptor = pollfd(fd: fd, events: Int16(POLLIN), revents: 0) + let ready = poll(&pollDescriptor, 1, 100) + if ready < 0 { + return nil + } + if ready == 0 { + continue + } + + let count = read(fd, &buffer, buffer.count) + if count <= 0 { + break + } + if let chunk = String(bytes: buffer[0..") " + - "mode=\(setup["socketMode"] ?? "") running=\(setup["socketIsRunning"] ?? "") " + - "acceptLoopAlive=\(setup["socketAcceptLoopAlive"] ?? "") pathMatches=\(setup["socketPathMatches"] ?? "") " + - "pathExists=\(setup["socketPathExists"] ?? "") signals=\(setup["socketFailureSignals"] ?? "")" + "Control socket did not respond in time. path=\(socketPath) response=\(confirmedPingResult.stdout ?? "") " + + "stderr=\(confirmedPingResult.stderr ?? "") " + + socketDiagnostics(from: failureSetup) ) return } - guard let surfaceId = waitForSurfaceId(forWorkspaceId: tabId2, timeout: 12.0) else { - XCTFail("Expected at least one surface in workspace \(tabId2). socket=\(socketPath)") + guard let surfaceId = waitForSurfaceIdViaCLI(forWorkspaceId: tabId2, timeout: 12.0) + ?? waitForSurfaceId(forWorkspaceId: tabId2, timeout: 3.0) else { + let failureSetup = loadData() ?? setup + XCTFail( + "Expected at least one surface in workspace \(tabId2). socket=\(socketPath) " + + socketDiagnostics(from: failureSetup) + ) return } @@ -395,6 +398,40 @@ final class MultiWindowNotificationsUITests: XCTestCase { return socketCommand("ping") ?? lastResponse } + private func waitForCmuxPing(timeout: TimeInterval) -> (stdout: String?, stderr: String?) { + let deadline = Date().addingTimeInterval(timeout) + var lastStdout: String? + var lastStderr: String? + while Date() < deadline { + let result = runCmuxCommand( + socketPath: socketPath, + arguments: ["ping"], + responseTimeoutSeconds: 2.0 + ) + let stdout = result.stdout.isEmpty ? nil : result.stdout + let stderr = result.stderr.isEmpty ? nil : result.stderr + if let stdout { + lastStdout = stdout + } + if let stderr { + lastStderr = stderr + } + if result.terminationStatus == 0, stdout == "PONG" { + return ("PONG", stderr) + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + + let result = runCmuxCommand( + socketPath: socketPath, + arguments: ["ping"], + responseTimeoutSeconds: 2.0 + ) + let stdout = result.stdout.isEmpty ? nil : result.stdout + let stderr = result.stderr.isEmpty ? nil : result.stderr + return (stdout ?? lastStdout, stderr ?? lastStderr) + } + private func waitForAppToLeaveForeground(_ app: XCUIApplication, timeout: TimeInterval) -> Bool { let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { @@ -436,29 +473,101 @@ final class MultiWindowNotificationsUITests: XCTestCase { return firstSurfaceId(forWorkspaceId: workspaceId) } + private func waitForSurfaceIdViaCLI(forWorkspaceId workspaceId: String, timeout: TimeInterval) -> String? { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if let surfaceId = firstSurfaceIdViaCLI(forWorkspaceId: workspaceId) { + return surfaceId + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + return firstSurfaceIdViaCLI(forWorkspaceId: workspaceId) + } + + private func firstSurfaceIdViaCLI(forWorkspaceId workspaceId: String) -> String? { + guard let paneId = firstPaneIdViaCLI(forWorkspaceId: workspaceId) else { + return nil + } + let result = runCmuxCommand( + socketPath: socketPath, + arguments: [ + "list-pane-surfaces", + "--workspace", + workspaceId, + "--pane", + paneId, + "--id-format", + "uuids" + ], + responseTimeoutSeconds: 3.0 + ) + guard result.terminationStatus == 0 else { return nil } + return firstHandle(in: result.stdout) + } + + private func firstPaneIdViaCLI(forWorkspaceId workspaceId: String) -> String? { + let result = runCmuxCommand( + socketPath: socketPath, + arguments: [ + "list-panes", + "--workspace", + workspaceId, + "--id-format", + "uuids" + ], + responseTimeoutSeconds: 3.0 + ) + guard result.terminationStatus == 0 else { return nil } + return firstHandle(in: result.stdout) + } + + private func firstHandle(in output: String) -> String? { + for rawLine in output.split(separator: "\n", omittingEmptySubsequences: true) { + var line = rawLine.trimmingCharacters(in: .whitespacesAndNewlines) + guard !line.isEmpty, !line.hasPrefix("No ") else { continue } + if line.hasPrefix("* ") || line.hasPrefix(" ") { + line = String(line.dropFirst(2)) + } + guard let token = line.split(whereSeparator: \.isWhitespace).first else { continue } + return String(token) + } + return nil + } + private func runCmuxNotify( socketPath: String, workspaceId: String, surfaceId: String, title: String + ) -> (terminationStatus: Int32, stdout: String, stderr: String) { + runCmuxCommand( + socketPath: socketPath, + arguments: [ + "notify", + "--workspace", + workspaceId, + "--surface", + surfaceId, + "--title", + title, + "--subtitle", + "ui-test", + "--body", + "focus-regression" + ], + responseTimeoutSeconds: 4.0 + ) + } + + private func runCmuxCommand( + socketPath: String, + arguments: [String], + responseTimeoutSeconds: Double = 3.0 ) -> (terminationStatus: Int32, stdout: String, stderr: String) { let process = Process() let cliPath = resolveCmuxCLIPath() - var args = [ - "--socket", - socketPath, - "notify", - "--workspace", - workspaceId, - "--surface", - surfaceId, - "--title", - title, - "--subtitle", - "ui-test", - "--body", - "focus-regression" - ] + var args = ["--socket", socketPath] + args.append(contentsOf: arguments) if let cliPath { process.executableURL = URL(fileURLWithPath: cliPath) } else { @@ -466,6 +575,9 @@ final class MultiWindowNotificationsUITests: XCTestCase { args.insert("cmux", at: 0) } process.arguments = args + var environment = ProcessInfo.processInfo.environment + environment["CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC"] = String(responseTimeoutSeconds) + process.environment = environment let stdoutPipe = Pipe() let stderrPipe = Pipe() @@ -479,7 +591,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { return ( terminationStatus: -1, stdout: "", - stderr: "Failed to run cmux notify: \(error.localizedDescription) (cliPath=\(cliPath ?? "env:cmux"))" + stderr: "Failed to run cmux command: \(error.localizedDescription) (cliPath=\(cliPath ?? "env:cmux"))" ) } @@ -492,6 +604,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { return (process.terminationStatus, stdout, stderr) } + private func socketDiagnostics(from data: [String: String]) -> String { + let pingResponse = data["socketPingResponse"].flatMap { $0.isEmpty ? nil : $0 } ?? "" + return "mode=\(data["socketMode"] ?? "") running=\(data["socketIsRunning"] ?? "") " + + "acceptLoopAlive=\(data["socketAcceptLoopAlive"] ?? "") pathMatches=\(data["socketPathMatches"] ?? "") " + + "pathExists=\(data["socketPathExists"] ?? "") ping=\(pingResponse) " + + "signals=\(data["socketFailureSignals"] ?? "")" + } + private func resolveCmuxCLIPath() -> String? { let fileManager = FileManager.default let env = ProcessInfo.processInfo.environment From 9853235cb7ff2c5fb6f67c5216bac820bb898c24 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:22:05 -0800 Subject: [PATCH 111/232] Resolve notify UI test CLI path on CI --- .../MultiWindowNotificationsUITests.swift | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 79d49fea..be7daba1 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -616,6 +616,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { let fileManager = FileManager.default let env = ProcessInfo.processInfo.environment var candidates: [String] = [] + var productDirectories: [String] = [] for key in ["CMUX_UI_TEST_CLI_PATH", "CMUXTERM_CLI"] { if let value = env[key], !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { @@ -624,9 +625,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { } if let builtProductsDir = env["BUILT_PRODUCTS_DIR"], !builtProductsDir.isEmpty { - candidates.append("\(builtProductsDir)/cmux DEV.app/Contents/Resources/bin/cmux") - candidates.append("\(builtProductsDir)/cmux.app/Contents/Resources/bin/cmux") - candidates.append("\(builtProductsDir)/cmux") + productDirectories.append(builtProductsDir) } if let hostPath = env["TEST_HOST"], !hostPath.isEmpty { @@ -637,16 +636,19 @@ final class MultiWindowNotificationsUITests: XCTestCase { .deletingLastPathComponent() .deletingLastPathComponent() .path - candidates.append("\(productsDir)/cmux DEV.app/Contents/Resources/bin/cmux") - candidates.append("\(productsDir)/cmux.app/Contents/Resources/bin/cmux") - candidates.append("\(productsDir)/cmux") + productDirectories.append(productsDir) + } + + productDirectories.append(contentsOf: inferredBuildProductsDirectories()) + for productsDir in uniquePaths(productDirectories) { + appendCLIPathCandidates(fromProductsDirectory: productsDir, to: &candidates) } candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux DEV.app/Contents/Resources/bin/cmux") candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux.app/Contents/Resources/bin/cmux") candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux") - for path in candidates { + for path in uniquePaths(candidates) { if fileManager.isExecutableFile(atPath: path) { return URL(fileURLWithPath: path).resolvingSymlinksInPath().path } @@ -655,6 +657,53 @@ final class MultiWindowNotificationsUITests: XCTestCase { return nil } + private func inferredBuildProductsDirectories() -> [String] { + let bundleURLs = [ + Bundle.main.bundleURL, + Bundle(for: Self.self).bundleURL, + ] + + return bundleURLs.compactMap { bundleURL in + let standardizedPath = bundleURL.standardizedFileURL.path + let components = standardizedPath.split(separator: "/") + guard let productsIndex = components.firstIndex(of: "Products"), + productsIndex + 1 < components.count else { + return nil + } + let prefixComponents = components.prefix(productsIndex + 2) + return "/" + prefixComponents.joined(separator: "/") + } + } + + private func appendCLIPathCandidates(fromProductsDirectory productsDir: String, to candidates: inout [String]) { + candidates.append("\(productsDir)/cmux DEV.app/Contents/Resources/bin/cmux") + candidates.append("\(productsDir)/cmux.app/Contents/Resources/bin/cmux") + candidates.append("\(productsDir)/cmux") + + guard let entries = try? FileManager.default.contentsOfDirectory(atPath: productsDir) else { + return + } + + for entry in entries.sorted() where entry.hasSuffix(".app") { + let cliPath = URL(fileURLWithPath: productsDir) + .appendingPathComponent(entry) + .appendingPathComponent("Contents/Resources/bin/cmux") + .path + candidates.append(cliPath) + } + } + + private func uniquePaths(_ paths: [String]) -> [String] { + var unique: [String] = [] + var seen = Set() + for path in paths { + if seen.insert(path).inserted { + unique.append(path) + } + } + return unique + } + private func resolveSocketPath(timeout: TimeInterval, requiredWorkspaceId: String? = nil) -> String? { let primaryCandidates = expectedSocketCandidates(includeGlobalFallback: false) let fallbackCandidates: [String] From 6347571b7cc35a2fb6b9dafc3ad34c45170f3278 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:32:42 -0800 Subject: [PATCH 112/232] Fix Dvorak Cmd+C colliding with notifications shortcut (#762) * Fix layout-safe command shortcut matching * Fix unshifted symbol shortcut coercion * Fix layout handling for hardcoded Cmd shortcuts * Support Cmd/Ctrl modifier hold shortcut hints * Address PR shortcut review feedback * Handle Caps Lock in command shortcut matching * Address remaining PR shortcut review comments * Handle Cmd+Ctrl+F control-character fallback * Tighten shortcut hint hold-delay test * Expose shortcut hint visibility in settings * Restore Cmd+digit fallback on symbol-first layouts * Stabilize shortcut regression coverage * Align shortcut hint toggle with Cmd/Ctrl behavior * Restore Claude workflow file * Match Claude workflow file to main * Keep shortcut hint hold behavior command-only * Restore command shortcut fallback when layout data is unavailable * Preserve command-aware shortcut translation --- Sources/AppDelegate.swift | 194 +++- Sources/ContentView.swift | 50 +- Sources/KeyboardLayout.swift | 27 +- Sources/Update/UpdateTitlebarAccessory.swift | 28 +- Sources/cmuxApp.swift | 4 + .../AppDelegateShortcutRoutingTests.swift | 958 ++++++++++++++++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 134 ++- 7 files changed, 1308 insertions(+), 87 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 91625919..373778a4 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1005,14 +1005,31 @@ func shouldDispatchBrowserReturnViaFirstResponderKeyDown( func shouldToggleMainWindowFullScreenForCommandControlFShortcut( flags: NSEvent.ModifierFlags, chars: String, - keyCode: UInt16 + keyCode: UInt16, + layoutCharacterProvider: (UInt16, NSEvent.ModifierFlags) -> String? = KeyboardLayout.character(forKeyCode:modifierFlags:) ) -> Bool { let normalizedFlags = flags .intersection(.deviceIndependentFlagsMask) .subtracting([.numericPad, .function, .capsLock]) guard normalizedFlags == [.command, .control] else { return false } let normalizedChars = chars.lowercased() - return normalizedChars == "f" || keyCode == 3 + if normalizedChars == "f" { + return true + } + let charsAreControlSequence = !normalizedChars.isEmpty + && normalizedChars.unicodeScalars.allSatisfy { CharacterSet.controlCharacters.contains($0) } + if !normalizedChars.isEmpty && !charsAreControlSequence { + return false + } + + // Fallback to layout translation only when characters are unavailable (for + // synthetic/key-equivalent paths that can report an empty string). + if let translatedCharacter = layoutCharacterProvider(keyCode, flags), !translatedCharacter.isEmpty { + return translatedCharacter == "f" + } + + // Keep ANSI fallback as a final safety net when layout translation is unavailable. + return keyCode == 3 } func commandPaletteSelectionDeltaForKeyboardNavigation( @@ -1449,6 +1466,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent weak var sidebarState: SidebarState? weak var fullscreenControlsViewModel: TitlebarControlsViewModel? weak var sidebarSelectionState: SidebarSelectionState? + var shortcutLayoutCharacterProvider: (UInt16, NSEvent.ModifierFlags) -> String? = KeyboardLayout.character(forKeyCode:modifierFlags:) private var workspaceObserver: NSObjectProtocol? private var lifecycleSnapshotObservers: [NSObjectProtocol] = [] private var windowKeyObserver: NSObjectProtocol? @@ -5840,6 +5858,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent titlebarAccessoryController.toggleNotificationsPopover(animated: animated, anchorView: anchorView) } + @discardableResult + func dismissNotificationsPopoverIfShown() -> Bool { + titlebarAccessoryController.dismissNotificationsPopoverIfShown() + } + func jumpToLatestUnread() { guard let notificationStore else { return } #if DEBUG @@ -6151,7 +6174,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private func handleCustomShortcut(event: NSEvent) -> Bool { // `charactersIgnoringModifiers` can be nil for some synthetic NSEvents and certain special keys. - // Most shortcuts below use keyCode fallbacks, so treat nil as "" rather than bailing out. + // Treat nil as "" and rely on keyCode/layout-aware fallback logic where needed. let chars = (event.charactersIgnoringModifiers ?? "").lowercased() let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) let hasControl = flags.contains(.control) @@ -6188,7 +6211,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent // Special-case: Cmd+D should confirm destructive close on alerts. // XCUITest key events often hit the app-level local monitor first, so forward the key // equivalent to the alert panel explicitly. - if flags == [.command], chars == "d", + if matchShortcut( + event: event, + shortcut: StoredShortcut(key: "d", command: true, shift: false, option: false, control: false) + ), let root = closeConfirmationPanel.contentView, let closeButton = findButton(in: root, titled: "Close") { closeButton.performClick(nil) @@ -6363,15 +6389,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent // focused omnibar in another window does not suppress Cmd+P here. let hasFocusedAddressBarInShortcutContext = focusedBrowserAddressBarPanelIdForShortcutEvent(event) != nil let isCommandP = !hasFocusedAddressBarInShortcutContext - && normalizedFlags == [.command] - && (chars == "p" || event.keyCode == 35) + && matchShortcut( + event: event, + shortcut: StoredShortcut(key: "p", command: true, shift: false, option: false, control: false) + ) if isCommandP { let targetWindow = commandPaletteTargetWindow ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow requestCommandPaletteSwitcher(preferredWindow: targetWindow, source: "shortcut.cmdP") return true } - let isCommandShiftP = normalizedFlags == [.command, .shift] && (chars == "p" || event.keyCode == 35) + let isCommandShiftP = matchShortcut( + event: event, + shortcut: StoredShortcut(key: "p", command: true, shift: true, option: false, control: false) + ) if isCommandShiftP { let targetWindow = commandPaletteTargetWindow ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow requestCommandPaletteCommands(preferredWindow: targetWindow, source: "shortcut.cmdShiftP") @@ -6387,11 +6418,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } - if normalizedFlags == [.command], chars == "q" { + if matchShortcut( + event: event, + shortcut: StoredShortcut(key: "q", command: true, shift: false, option: false, control: false) + ) { return handleQuitShortcutWarning() } - if normalizedFlags == [.command, .shift], - (chars == "," || chars == "<" || event.keyCode == 43) { + if matchShortcut( + event: event, + shortcut: StoredShortcut(key: ",", command: true, shift: true, option: false, control: false) + ) { GhosttyApp.shared.reloadConfiguration(source: "shortcut.cmd_shift_comma") return true } @@ -6611,7 +6647,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) } - if normalizedFlags == [.command, .option], (chars == "t" || event.keyCode == 17) { + if matchShortcut( + event: event, + shortcut: StoredShortcut(key: "t", command: true, shift: false, option: true, control: false) + ) { if let targetWindow = event.window ?? NSApp.keyWindow ?? NSApp.mainWindow, targetWindow.identifier?.rawValue == "cmux.settings" { targetWindow.performClose(nil) @@ -6632,7 +6671,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent // Cmd+W must close the focused panel even if first-responder momentarily lags on a // browser NSTextView during split focus transitions. - if normalizedFlags == [.command], (chars == "w" || event.keyCode == 13) { + if matchShortcut( + event: event, + shortcut: StoredShortcut(key: "w", command: true, shift: false, option: false, control: false) + ) { if let targetWindow = event.window ?? NSApp.keyWindow ?? NSApp.mainWindow, targetWindow.identifier?.rawValue == "cmux.settings" { targetWindow.performClose(nil) @@ -6861,7 +6903,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } // Focus browser address bar: Cmd+L - if flags == [.command] && chars == "l" { + if matchShortcut( + event: event, + shortcut: StoredShortcut(key: "l", command: true, shift: false, option: false, control: false) + ) { if let focusedPanel = tabManager?.focusedBrowserPanel { focusBrowserAddressBar(in: focusedPanel) return true @@ -7449,42 +7494,127 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return false } - /// Match a shortcut against an event, handling normal keys + /// Match a shortcut against an event, handling normal keys. private func matchShortcut(event: NSEvent, shortcut: StoredShortcut) -> Bool { // Some keys can include extra flags (e.g. .function) depending on the responder chain. // Strip those for consistent matching across first responders (terminal, WebKit, etc). let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) - .subtracting([.numericPad, .function]) + .subtracting([.numericPad, .function, .capsLock]) guard flags == shortcut.modifierFlags else { return false } - // NSEvent.charactersIgnoringModifiers preserves Shift for some symbol keys - // (e.g. Shift+] can yield "}" instead of "]"), so match brackets by keyCode. let shortcutKey = shortcut.key.lowercased() if shortcutKey == "\r" { return event.keyCode == 36 || event.keyCode == 76 } - if shortcutKey == "[" || shortcutKey == "]" { - switch event.keyCode { - case 33: // kVK_ANSI_LeftBracket - return shortcutKey == "[" - case 30: // kVK_ANSI_RightBracket - return shortcutKey == "]" - default: - return false - } - } - // Control-key combos can produce control characters (e.g. Ctrl+H => backspace), - // so fall back to keyCode matching for common printable keys. - if let chars = event.charactersIgnoringModifiers?.lowercased(), chars == shortcutKey { + let eventCharsIgnoringModifiers = event.charactersIgnoringModifiers + if shortcutCharacterMatches( + eventCharacter: eventCharsIgnoringModifiers, + shortcutKey: shortcutKey, + applyShiftSymbolNormalization: flags.contains(.shift), + eventKeyCode: event.keyCode + ) { return true } - if let expectedKeyCode = keyCodeForShortcutKey(shortcutKey) { + + // For command-based shortcuts, trust AppKit's layout-aware characters when present. + // Keep this strict for letter shortcuts to avoid physical-key collisions across layouts, + // while still allowing keyCode fallback for digit/punctuation shortcuts on non-US layouts. + let hasEventChars = !(eventCharsIgnoringModifiers?.isEmpty ?? true) + if hasEventChars, + flags.contains(.command), + !flags.contains(.control), + shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey) { + return false + } + + // Match using the current keyboard layout so Command shortcuts stay character-based + // across layouts (QWERTY, Dvorak, etc.) instead of being tied to ANSI physical keys. + let layoutCharacter = shortcutLayoutCharacterProvider(event.keyCode, event.modifierFlags) + if shortcutCharacterMatches( + eventCharacter: layoutCharacter, + shortcutKey: shortcutKey, + applyShiftSymbolNormalization: false, + eventKeyCode: event.keyCode + ) { + return true + } + + // Control-key combos can surface as ASCII control characters (e.g. Ctrl+H => backspace), + // so keep ANSI keyCode fallback for control-modified shortcuts. Also allow fallback for + // command punctuation shortcuts, since some non-US layouts report different characters + // for the same physical key even when menu-equivalent semantics should still apply. + let allowANSIKeyCodeFallback = flags.contains(.control) + || (flags.contains(.command) + && !flags.contains(.control) + && ( + !shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey) + || (!hasEventChars && (layoutCharacter?.isEmpty ?? true)) + )) + if allowANSIKeyCodeFallback, let expectedKeyCode = keyCodeForShortcutKey(shortcutKey) { return event.keyCode == expectedKeyCode } return false } + private func shouldRequireCharacterMatchForCommandShortcut(shortcutKey: String) -> Bool { + guard shortcutKey.count == 1, let scalar = shortcutKey.unicodeScalars.first else { + return false + } + return CharacterSet.letters.contains(scalar) + } + + private func shortcutCharacterMatches( + eventCharacter: String?, + shortcutKey: String, + applyShiftSymbolNormalization: Bool, + eventKeyCode: UInt16 + ) -> Bool { + guard let eventCharacter, !eventCharacter.isEmpty else { return false } + if normalizedShortcutEventCharacter( + eventCharacter, + applyShiftSymbolNormalization: applyShiftSymbolNormalization, + eventKeyCode: eventKeyCode + ) == shortcutKey { + return true + } + return false + } + + private func normalizedShortcutEventCharacter( + _ eventCharacter: String, + applyShiftSymbolNormalization: Bool, + eventKeyCode: UInt16 + ) -> String { + let lowered = eventCharacter.lowercased() + guard applyShiftSymbolNormalization else { return lowered } + + switch lowered { + case "{": return "[" + case "}": return "]" + case "<": return eventKeyCode == 43 ? "," : lowered // kVK_ANSI_Comma + case ">": return eventKeyCode == 47 ? "." : lowered // kVK_ANSI_Period + case "?": return "/" + case ":": return ";" + case "\"": return "'" + case "|": return "\\" + case "~": return "`" + case "+": return "=" + case "_": return "-" + case "!": return eventKeyCode == 18 ? "1" : lowered // kVK_ANSI_1 + case "@": return eventKeyCode == 19 ? "2" : lowered // kVK_ANSI_2 + case "#": return eventKeyCode == 20 ? "3" : lowered // kVK_ANSI_3 + case "$": return eventKeyCode == 21 ? "4" : lowered // kVK_ANSI_4 + case "%": return eventKeyCode == 23 ? "5" : lowered // kVK_ANSI_5 + case "^": return eventKeyCode == 22 ? "6" : lowered // kVK_ANSI_6 + case "&": return eventKeyCode == 26 ? "7" : lowered // kVK_ANSI_7 + case "*": return eventKeyCode == 28 ? "8" : lowered // kVK_ANSI_8 + case "(": return eventKeyCode == 25 ? "9" : lowered // kVK_ANSI_9 + case ")": return eventKeyCode == 29 ? "0" : lowered // kVK_ANSI_0 + default: return lowered + } + } + private func keyCodeForShortcutKey(_ key: String) -> UInt16? { // Matches macOS ANSI key codes. This is intentionally limited to keys we // support in StoredShortcut/ghostty trigger translation. @@ -7518,8 +7648,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent case "-": return 27 // kVK_ANSI_Minus case "8": return 28 // kVK_ANSI_8 case "0": return 29 // kVK_ANSI_0 + case "]": return 30 // kVK_ANSI_RightBracket case "o": return 31 // kVK_ANSI_O case "u": return 32 // kVK_ANSI_U + case "[": return 33 // kVK_ANSI_LeftBracket case "i": return 34 // kVK_ANSI_I case "p": return 35 // kVK_ANSI_P case "l": return 37 // kVK_ANSI_L diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index f886eb22..a289f0eb 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -5632,7 +5632,7 @@ struct VerticalTabsSidebar: View { @Binding var selection: SidebarSelection @Binding var selectedTabIds: Set @Binding var lastSidebarSelectionIndex: Int? - @StateObject private var commandKeyMonitor = SidebarCommandKeyMonitor() + @StateObject private var modifierKeyMonitor = SidebarShortcutHintModifierMonitor() @StateObject private var dragAutoScrollController = SidebarDragAutoScrollController() @StateObject private var dragFailsafeMonitor = SidebarDragFailsafeMonitor() @State private var draggedTabId: UUID? @@ -5660,7 +5660,7 @@ struct VerticalTabsSidebar: View { selection: $selection, selectedTabIds: $selectedTabIds, lastSidebarSelectionIndex: $lastSidebarSelectionIndex, - showsCommandShortcutHints: commandKeyMonitor.isCommandPressed, + showsModifierShortcutHints: modifierKeyMonitor.isModifierPressed, dragAutoScrollController: dragAutoScrollController, draggedTabId: $draggedTabId, dropIndicator: $dropIndicator @@ -5716,12 +5716,12 @@ struct VerticalTabsSidebar: View { .background(SidebarBackdrop().ignoresSafeArea()) .background( WindowAccessor { window in - commandKeyMonitor.setHostWindow(window) + modifierKeyMonitor.setHostWindow(window) } .frame(width: 0, height: 0) ) .onAppear { - commandKeyMonitor.start() + modifierKeyMonitor.start() draggedTabId = nil dropIndicator = nil SidebarDragLifecycleNotification.postStateDidChange( @@ -5730,7 +5730,7 @@ struct VerticalTabsSidebar: View { ) } .onDisappear { - commandKeyMonitor.stop() + modifierKeyMonitor.stop() dragAutoScrollController.stop() dragFailsafeMonitor.stop() draggedTabId = nil @@ -5774,15 +5774,18 @@ struct VerticalTabsSidebar: View { } } -enum SidebarCommandHintPolicy { +enum ShortcutHintModifierPolicy { static let intentionalHoldDelay: TimeInterval = 0.30 static func shouldShowHints( for modifierFlags: NSEvent.ModifierFlags, defaults: UserDefaults = .standard ) -> Bool { - ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults) && - modifierFlags.intersection(.deviceIndependentFlagsMask) == [.command] + let normalized = modifierFlags.intersection(.deviceIndependentFlagsMask) + guard normalized == [.command] else { + return false + } + return ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults) } static func isCurrentWindow( @@ -5847,6 +5850,11 @@ enum ShortcutHintDebugSettings { } return defaults.bool(forKey: showHintsOnCommandHoldKey) } + + static func resetVisibilityDefaults(defaults: UserDefaults = .standard) { + defaults.set(defaultAlwaysShowHints, forKey: alwaysShowHintsKey) + defaults.set(defaultShowHintsOnCommandHold, forKey: showHintsOnCommandHoldKey) + } } enum SidebarDragLifecycleNotification { @@ -6064,8 +6072,8 @@ private struct SidebarExternalDropDelegate: DropDelegate { } @MainActor -private final class SidebarCommandKeyMonitor: ObservableObject { - @Published private(set) var isCommandPressed = false +private final class SidebarShortcutHintModifierMonitor: ObservableObject { + @Published private(set) var isModifierPressed = false private weak var hostWindow: NSWindow? private var hostWindowDidBecomeKeyObserver: NSObjectProtocol? @@ -6159,7 +6167,7 @@ private final class SidebarCommandKeyMonitor: ObservableObject { } private func isCurrentWindow(eventWindow: NSWindow?) -> Bool { - SidebarCommandHintPolicy.isCurrentWindow( + ShortcutHintModifierPolicy.isCurrentWindow( hostWindowNumber: hostWindow?.windowNumber, hostWindowIsKey: hostWindow?.isKeyWindow ?? false, eventWindowNumber: eventWindow?.windowNumber, @@ -6168,7 +6176,7 @@ private final class SidebarCommandKeyMonitor: ObservableObject { } private func update(from modifierFlags: NSEvent.ModifierFlags, eventWindow: NSWindow?) { - guard SidebarCommandHintPolicy.shouldShowHints( + guard ShortcutHintModifierPolicy.shouldShowHints( for: modifierFlags, hostWindowNumber: hostWindow?.windowNumber, hostWindowIsKey: hostWindow?.isKeyWindow ?? false, @@ -6183,31 +6191,31 @@ private final class SidebarCommandKeyMonitor: ObservableObject { } private func queueHintShow() { - guard !isCommandPressed else { return } + guard !isModifierPressed else { return } guard pendingShowWorkItem == nil else { return } let workItem = DispatchWorkItem { [weak self] in guard let self else { return } self.pendingShowWorkItem = nil - guard SidebarCommandHintPolicy.shouldShowHints( + guard ShortcutHintModifierPolicy.shouldShowHints( for: NSEvent.modifierFlags, hostWindowNumber: self.hostWindow?.windowNumber, hostWindowIsKey: self.hostWindow?.isKeyWindow ?? false, eventWindowNumber: nil, keyWindowNumber: NSApp.keyWindow?.windowNumber ) else { return } - self.isCommandPressed = true + self.isModifierPressed = true } pendingShowWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + SidebarCommandHintPolicy.intentionalHoldDelay, execute: workItem) + DispatchQueue.main.asyncAfter(deadline: .now() + ShortcutHintModifierPolicy.intentionalHoldDelay, execute: workItem) } private func cancelPendingHintShow(resetVisible: Bool) { pendingShowWorkItem?.cancel() pendingShowWorkItem = nil if resetVisible { - isCommandPressed = false + isModifierPressed = false } } @@ -6445,7 +6453,7 @@ private struct TabItemView: View { @Binding var selection: SidebarSelection @Binding var selectedTabIds: Set @Binding var lastSidebarSelectionIndex: Int? - let showsCommandShortcutHints: Bool + let showsModifierShortcutHints: Bool let dragAutoScrollController: SidebarDragAutoScrollController @Binding var draggedTabId: UUID? @Binding var dropIndicator: SidebarDropIndicator? @@ -6548,7 +6556,7 @@ private struct TabItemView: View { } private var showCloseButton: Bool { - isHovering && tabManager.tabs.count > 1 && !(showsCommandShortcutHints || alwaysShowShortcutHints) + isHovering && tabManager.tabs.count > 1 && !(showsModifierShortcutHints || alwaysShowShortcutHints) } private var workspaceShortcutLabel: String? { @@ -6557,7 +6565,7 @@ private struct TabItemView: View { } private var showsWorkspaceShortcutHint: Bool { - (showsCommandShortcutHints || alwaysShowShortcutHints) && workspaceShortcutLabel != nil + (showsModifierShortcutHints || alwaysShowShortcutHints) && workspaceShortcutLabel != nil } private var workspaceHintSlotWidth: CGFloat { @@ -6673,7 +6681,7 @@ private struct TabItemView: View { .transition(.opacity) } } - .animation(.easeInOut(duration: 0.14), value: showsCommandShortcutHints || alwaysShowShortcutHints) + .animation(.easeInOut(duration: 0.14), value: showsModifierShortcutHints || alwaysShowShortcutHints) .frame(width: workspaceHintSlotWidth, height: 16, alignment: .trailing) } diff --git a/Sources/KeyboardLayout.swift b/Sources/KeyboardLayout.swift index 392d0723..f7b7110a 100644 --- a/Sources/KeyboardLayout.swift +++ b/Sources/KeyboardLayout.swift @@ -1,3 +1,4 @@ +import AppKit import Carbon class KeyboardLayout { @@ -12,8 +13,12 @@ class KeyboardLayout { return nil } - /// Translate a physical keyCode to the unmodified character under the current keyboard layout. - static func character(forKeyCode keyCode: UInt16) -> String? { + /// Translate a physical keyCode to the character AppKit would use for shortcut matching, + /// preserving command-aware layouts such as "Dvorak - QWERTY Command". + static func character( + forKeyCode keyCode: UInt16, + modifierFlags: NSEvent.ModifierFlags = [] + ) -> String? { guard let source = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(), let layoutDataPointer = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else { return nil @@ -31,7 +36,7 @@ class KeyboardLayout { keyboardLayout, keyCode, UInt16(kUCKeyActionDisplay), - 0, + translationModifierKeyState(for: modifierFlags), UInt32(LMGetKbdType()), UInt32(kUCKeyTranslateNoDeadKeysBit), &deadKeyState, @@ -43,4 +48,20 @@ class KeyboardLayout { guard status == noErr, length > 0 else { return nil } return String(utf16CodeUnits: chars, count: length).lowercased() } + + private static func translationModifierKeyState(for modifierFlags: NSEvent.ModifierFlags) -> UInt32 { + let normalized = modifierFlags + .intersection(.deviceIndependentFlagsMask) + .intersection([.shift, .command]) + + var carbonModifiers: Int = 0 + if normalized.contains(.shift) { + carbonModifiers |= shiftKey + } + if normalized.contains(.command) { + carbonModifiers |= cmdKey + } + + return UInt32((carbonModifiers >> 8) & 0xFF) + } } diff --git a/Sources/Update/UpdateTitlebarAccessory.swift b/Sources/Update/UpdateTitlebarAccessory.swift index c7a96e0e..12af22b6 100644 --- a/Sources/Update/UpdateTitlebarAccessory.swift +++ b/Sources/Update/UpdateTitlebarAccessory.swift @@ -237,7 +237,7 @@ struct TitlebarControlsView: View { @AppStorage(ShortcutHintDebugSettings.titlebarHintYKey) private var titlebarShortcutHintYOffset = ShortcutHintDebugSettings.defaultTitlebarHintY @AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints @State private var shortcutRefreshTick = 0 - @StateObject private var commandKeyMonitor = TitlebarCommandKeyMonitor() + @StateObject private var modifierKeyMonitor = TitlebarShortcutHintModifierMonitor() private let titlebarHintRightSafetyShift: CGFloat = 10 private let titlebarHintBaseXShift: CGFloat = -10 private let titlebarHintBaseYShift: CGFloat = 1 @@ -269,7 +269,7 @@ struct TitlebarControlsView: View { } private var shouldShowTitlebarShortcutHints: Bool { - alwaysShowShortcutHints || commandKeyMonitor.isCommandPressed + alwaysShowShortcutHints || modifierKeyMonitor.isModifierPressed } var body: some View { @@ -283,7 +283,7 @@ struct TitlebarControlsView: View { .padding(.trailing, titlebarHintTrailingInset) .background( WindowAccessor { window in - commandKeyMonitor.setHostWindow(window) + modifierKeyMonitor.setHostWindow(window) } .frame(width: 0, height: 0) ) @@ -291,10 +291,10 @@ struct TitlebarControlsView: View { shortcutRefreshTick &+= 1 } .onAppear { - commandKeyMonitor.start() + modifierKeyMonitor.start() } .onDisappear { - commandKeyMonitor.stop() + modifierKeyMonitor.stop() } } @@ -503,8 +503,8 @@ struct TitlebarControlsView: View { } @MainActor -private final class TitlebarCommandKeyMonitor: ObservableObject { - @Published private(set) var isCommandPressed = false +private final class TitlebarShortcutHintModifierMonitor: ObservableObject { + @Published private(set) var isModifierPressed = false private weak var hostWindow: NSWindow? private var hostWindowDidBecomeKeyObserver: NSObjectProtocol? @@ -598,7 +598,7 @@ private final class TitlebarCommandKeyMonitor: ObservableObject { } private func isCurrentWindow(eventWindow: NSWindow?) -> Bool { - SidebarCommandHintPolicy.isCurrentWindow( + ShortcutHintModifierPolicy.isCurrentWindow( hostWindowNumber: hostWindow?.windowNumber, hostWindowIsKey: hostWindow?.isKeyWindow ?? false, eventWindowNumber: eventWindow?.windowNumber, @@ -607,7 +607,7 @@ private final class TitlebarCommandKeyMonitor: ObservableObject { } private func update(from modifierFlags: NSEvent.ModifierFlags, eventWindow: NSWindow?) { - guard SidebarCommandHintPolicy.shouldShowHints( + guard ShortcutHintModifierPolicy.shouldShowHints( for: modifierFlags, hostWindowNumber: hostWindow?.windowNumber, hostWindowIsKey: hostWindow?.isKeyWindow ?? false, @@ -622,31 +622,31 @@ private final class TitlebarCommandKeyMonitor: ObservableObject { } private func queueHintShow() { - guard !isCommandPressed else { return } + guard !isModifierPressed else { return } guard pendingShowWorkItem == nil else { return } let workItem = DispatchWorkItem { [weak self] in guard let self else { return } self.pendingShowWorkItem = nil - guard SidebarCommandHintPolicy.shouldShowHints( + guard ShortcutHintModifierPolicy.shouldShowHints( for: NSEvent.modifierFlags, hostWindowNumber: self.hostWindow?.windowNumber, hostWindowIsKey: self.hostWindow?.isKeyWindow ?? false, eventWindowNumber: nil, keyWindowNumber: NSApp.keyWindow?.windowNumber ) else { return } - self.isCommandPressed = true + self.isModifierPressed = true } pendingShowWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + SidebarCommandHintPolicy.intentionalHoldDelay, execute: workItem) + DispatchQueue.main.asyncAfter(deadline: .now() + ShortcutHintModifierPolicy.intentionalHoldDelay, execute: workItem) } private func cancelPendingHintShow(resetVisible: Bool) { pendingShowWorkItem?.cancel() pendingShowWorkItem = nil if resetVisible { - isCommandPressed = false + isModifierPressed = false } } diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index ff392c1f..00cd360b 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -2825,6 +2825,8 @@ struct SettingsView: View { @AppStorage(QuitWarningSettings.warnBeforeQuitKey) private var warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit @AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey) private var commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus + @AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) + private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints @AppStorage(WorkspacePlacementSettings.placementKey) private var newWorkspacePlacement = WorkspacePlacementSettings.defaultPlacement.rawValue @AppStorage(WorkspaceAutoReorderSettings.key) private var workspaceAutoReorder = WorkspaceAutoReorderSettings.defaultValue @AppStorage(SidebarBranchLayoutSettings.key) private var sidebarBranchVerticalLayout = SidebarBranchLayoutSettings.defaultVerticalLayout @@ -4107,6 +4109,8 @@ struct SettingsView: View { notificationDockBadgeEnabled = NotificationBadgeSettings.defaultDockBadgeEnabled warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus + ShortcutHintDebugSettings.resetVisibilityDefaults() + alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints newWorkspacePlacement = WorkspacePlacementSettings.defaultPlacement.rawValue workspaceAutoReorder = WorkspaceAutoReorderSettings.defaultValue sidebarBranchVerticalLayout = SidebarBranchLayoutSettings.defaultVerticalLayout diff --git a/cmuxTests/AppDelegateShortcutRoutingTests.swift b/cmuxTests/AppDelegateShortcutRoutingTests.swift index 49dfa9ab..22b09b39 100644 --- a/cmuxTests/AppDelegateShortcutRoutingTests.swift +++ b/cmuxTests/AppDelegateShortcutRoutingTests.swift @@ -8,6 +8,39 @@ import XCTest @MainActor final class AppDelegateShortcutRoutingTests: XCTestCase { + private var savedShortcutsByAction: [KeyboardShortcutSettings.Action: StoredShortcut] = [:] + private var actionsWithPersistedShortcut: Set = [] + + override func setUp() { + super.setUp() + actionsWithPersistedShortcut = Set( + KeyboardShortcutSettings.Action.allCases.filter { + UserDefaults.standard.object(forKey: $0.defaultsKey) != nil + } + ) + savedShortcutsByAction = Dictionary( + uniqueKeysWithValues: actionsWithPersistedShortcut.map { action in + (action, KeyboardShortcutSettings.shortcut(for: action)) + } + ) + KeyboardShortcutSettings.resetAll() + } + + override func tearDown() { + AppDelegate.shared?.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) + AppDelegate.shared?.dismissNotificationsPopoverIfShown() + RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) + for action in KeyboardShortcutSettings.Action.allCases { + if actionsWithPersistedShortcut.contains(action), + let savedShortcut = savedShortcutsByAction[action] { + KeyboardShortcutSettings.setShortcut(savedShortcut, for: action) + } else { + KeyboardShortcutSettings.resetShortcut(for: action) + } + } + super.tearDown() + } + func testCmdNUsesEventWindowContextWhenActiveManagerIsStale() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") @@ -311,6 +344,910 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { XCTAssertTrue(appDelegate.tabManager === secondManager, "Shortcut routing should retarget active manager to event window") } + func testCmdPhysicalIWithDvorakCharactersDoesNotTriggerShowNotifications() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut(action: .showNotifications) { + // Dvorak: physical ANSI "I" key can produce the character "c". + // This should behave like Cmd+C (copy), not match the Cmd+I app shortcut. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "c", + charactersIgnoringModifiers: "c", + isARepeat: false, + keyCode: 34 // kVK_ANSI_I + ) else { + XCTFail("Failed to construct Dvorak Cmd+C event on physical ANSI I key") + return + } + +#if DEBUG + XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdPhysicalPWithDvorakCharactersDoesNotTriggerCommandPaletteSwitcher() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let switcherExpectation = expectation(description: "Cmd+L should not request command palette switcher") + switcherExpectation.isInverted = true + let token = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { _ in + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(token) } + + // Dvorak: physical ANSI "P" key can produce "l". + // This should behave as Cmd+L, not as physical Cmd+P. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "l", + charactersIgnoringModifiers: "l", + isARepeat: false, + keyCode: 35 // kVK_ANSI_P + ) else { + XCTFail("Failed to construct Dvorak Cmd+L event on physical ANSI P key") + return + } + +#if DEBUG + _ = appDelegate.debugHandleCustomShortcut(event: event) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [switcherExpectation], timeout: 0.15) + } + + func testCmdPWithCapsLockStillTriggersCommandPaletteSwitcher() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let switcherExpectation = expectation(description: "Cmd+P with Caps Lock should request command palette switcher") + let token = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { _ in + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(token) } + + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .capsLock], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "p", + charactersIgnoringModifiers: "p", + isARepeat: false, + keyCode: 35 // kVK_ANSI_P + ) else { + XCTFail("Failed to construct Cmd+P + Caps Lock event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [switcherExpectation], timeout: 0.15) + } + + func testCmdPFallsBackToANSIKeyCodeWhenCharactersAndLayoutTranslationAreUnavailable() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + appDelegate.shortcutLayoutCharacterProvider = { _, _ in nil } + defer { + appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) + } + + let switcherExpectation = expectation(description: "Cmd+P with unavailable characters should request command palette switcher") + let token = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { _ in + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(token) } + + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "", + charactersIgnoringModifiers: "", + isARepeat: false, + keyCode: 35 // kVK_ANSI_P + ) else { + XCTFail("Failed to construct Cmd+P event with unavailable characters") + return + } + + XCTAssertTrue(appDelegate.handleBrowserSurfaceKeyEquivalent(event)) + wait(for: [switcherExpectation], timeout: 0.15) + } + + func testCmdPDoesNotFallbackToANSIKeyCodeWhenLayoutTranslationProvidesDifferentLetter() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + appDelegate.shortcutLayoutCharacterProvider = { _, _ in "b" } + defer { + appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) + } + + let switcherExpectation = expectation(description: "Non-P layout translation should not request command palette switcher") + switcherExpectation.isInverted = true + let token = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { _ in + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(token) } + + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "", + charactersIgnoringModifiers: "", + isARepeat: false, + keyCode: 35 // kVK_ANSI_P + ) else { + XCTFail("Failed to construct Cmd+P event with unavailable characters") + return + } + + _ = appDelegate.handleBrowserSurfaceKeyEquivalent(event) + wait(for: [switcherExpectation], timeout: 0.15) + } + + func testCmdPFallsBackToCommandAwareLayoutTranslationWhenCharactersAreUnavailable() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + appDelegate.shortcutLayoutCharacterProvider = { keyCode, modifierFlags in + guard keyCode == 35 else { return nil } // kVK_ANSI_P + return modifierFlags.contains(.command) ? "p" : "r" + } + defer { + appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) + } + + let switcherExpectation = expectation(description: "Command-aware layout translation should request command palette switcher") + let token = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { _ in + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(token) } + + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "", + charactersIgnoringModifiers: "", + isARepeat: false, + keyCode: 35 // kVK_ANSI_P + ) else { + XCTFail("Failed to construct Cmd+P event with unavailable characters") + return + } + + XCTAssertTrue(appDelegate.handleBrowserSurfaceKeyEquivalent(event)) + wait(for: [switcherExpectation], timeout: 0.15) + } + + func testCmdShiftPhysicalPWithDvorakCharactersDoesNotTriggerCommandPalette() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let paletteExpectation = expectation(description: "Cmd+Shift+L should not request command palette") + paletteExpectation.isInverted = true + let token = NotificationCenter.default.addObserver( + forName: .commandPaletteRequested, + object: nil, + queue: nil + ) { _ in + paletteExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(token) } + + // Dvorak: physical ANSI "P" key can produce "l". + // This should behave as Cmd+Shift+L, not as physical Cmd+Shift+P. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "l", + charactersIgnoringModifiers: "l", + isARepeat: false, + keyCode: 35 // kVK_ANSI_P + ) else { + XCTFail("Failed to construct Dvorak Cmd+Shift+L event on physical ANSI P key") + return + } + +#if DEBUG + _ = appDelegate.debugHandleCustomShortcut(event: event) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [paletteExpectation], timeout: 0.15) + } + + func testCmdOptionPhysicalTWithDvorakCharactersDoesNotTriggerCloseOtherTabsShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + // Dvorak: physical ANSI "T" key can produce "y". + // This should not match the Cmd+Option+T app shortcut. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .option], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "y", + charactersIgnoringModifiers: "y", + isARepeat: false, + keyCode: 17 // kVK_ANSI_T + ) else { + XCTFail("Failed to construct Dvorak Cmd+Option+Y event on physical ANSI T key") + return + } + +#if DEBUG + XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + + func testCmdPhysicalWWithDvorakCharactersDoesNotTriggerClosePanelShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId), + let manager = appDelegate.tabManagerFor(windowId: windowId), + let workspace = manager.selectedWorkspace else { + XCTFail("Expected test window and workspace") + return + } + + let panelCountBefore = workspace.panels.count + + // Dvorak: physical ANSI "W" key can produce ",". + // This should not match the Cmd+W close-panel shortcut. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: ",", + charactersIgnoringModifiers: ",", + isARepeat: false, + keyCode: 13 // kVK_ANSI_W + ) else { + XCTFail("Failed to construct Dvorak Cmd+, event on physical ANSI W key") + return + } + +#if DEBUG + XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + XCTAssertEqual(workspace.panels.count, panelCountBefore) + } + + func testCmdIStillTriggersShowNotificationsShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut(action: .showNotifications) { + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "i", + charactersIgnoringModifiers: "i", + isARepeat: false, + keyCode: 34 // kVK_ANSI_I + ) else { + XCTFail("Failed to construct Cmd+I event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdUnshiftedSymbolDoesNotMatchDigitShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut( + action: .showNotifications, + shortcut: StoredShortcut(key: "8", command: true, shift: false, option: false, control: false) + ) { + // Some non-US layouts can produce "*" without Shift. + // This must not be coerced into "8" for a Cmd+8 shortcut match. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "*", + charactersIgnoringModifiers: "*", + isARepeat: false, + keyCode: 30 // kVK_ANSI_RightBracket + ) else { + XCTFail("Failed to construct Cmd+* event") + return + } + +#if DEBUG + XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdDigitShortcutFallsBackByKeyCodeOnSymbolFirstLayouts() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut( + action: .showNotifications, + shortcut: StoredShortcut(key: "1", command: true, shift: false, option: false, control: false) + ) { + // Symbol-first layouts (for example AZERTY) can report "&" for the ANSI 1 key. + // Cmd+1 shortcuts should still match via keyCode fallback in this case. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "&", + charactersIgnoringModifiers: "&", + isARepeat: false, + keyCode: 18 // kVK_ANSI_1 + ) else { + XCTFail("Failed to construct Cmd+& event on ANSI 1 key") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdShiftNonDigitKeySymbolDoesNotMatchShiftedDigitShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut( + action: .showNotifications, + shortcut: StoredShortcut(key: "8", command: true, shift: true, option: false, control: false) + ) { + // Avoid unrelated default Cmd+Shift+] handling for this assertion. + withTemporaryShortcut( + action: .nextSurface, + shortcut: StoredShortcut(key: "x", command: true, shift: true, option: false, control: false) + ) { + // On some non-US layouts, Shift+RightBracket can produce "*". + // This must not be interpreted as Shift+8. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "*", + charactersIgnoringModifiers: "*", + isARepeat: false, + keyCode: 30 // kVK_ANSI_RightBracket + ) else { + XCTFail("Failed to construct Cmd+Shift+* event from non-digit key") + return + } + +#if DEBUG + XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + } + + func testCmdShiftDigitShortcutMatchesShiftedDigitKey() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut( + action: .showNotifications, + shortcut: StoredShortcut(key: "8", command: true, shift: true, option: false, control: false) + ) { + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "*", + charactersIgnoringModifiers: "*", + isARepeat: false, + keyCode: 28 // kVK_ANSI_8 + ) else { + XCTFail("Failed to construct Cmd+Shift+8 event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdShiftQuestionMarkMatchesSlashShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut( + action: .triggerFlash, + shortcut: StoredShortcut(key: "/", command: true, shift: true, option: false, control: false) + ) { + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "?", + charactersIgnoringModifiers: "?", + isARepeat: false, + keyCode: 44 // kVK_ANSI_Slash + ) else { + XCTFail("Failed to construct Cmd+Shift+/ event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdShiftISOAngleBracketDoesNotMatchCommaShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut( + action: .showNotifications, + shortcut: StoredShortcut(key: ",", command: true, shift: true, option: false, control: false) + ) { + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "<", + charactersIgnoringModifiers: "<", + isARepeat: false, + keyCode: 10 // kVK_ISO_Section + ) else { + XCTFail("Failed to construct Cmd+Shift+< event from ISO key") + return + } + +#if DEBUG + XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdShiftRightBracketCanFallbackByKeyCodeOnNonUSLayouts() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + withTemporaryShortcut(action: .nextSurface) { + // Non-US layouts can report "*" (or other symbols) for kVK_ANSI_RightBracket with Shift. + // Shortcut matching should still allow Cmd+Shift+] via keyCode fallback. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command, .shift], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "*", + charactersIgnoringModifiers: "*", + isARepeat: false, + keyCode: 30 // kVK_ANSI_RightBracket + ) else { + XCTFail("Failed to construct non-US Cmd+Shift+] event") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + } + + func testCmdPhysicalOWithDvorakCharactersTriggersRenameTabShortcut() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let renameTabExpectation = expectation(description: "Expected rename tab request for semantic Cmd+R") + var observedRenameTabWindow: NSWindow? + let renameTabToken = NotificationCenter.default.addObserver( + forName: .commandPaletteRenameTabRequested, + object: nil, + queue: nil + ) { notification in + observedRenameTabWindow = notification.object as? NSWindow + renameTabExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(renameTabToken) } + + let switcherExpectation = expectation(description: "Cmd+R should not trigger command palette switcher") + switcherExpectation.isInverted = true + let switcherToken = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { _ in + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(switcherToken) } + + withTemporaryShortcut(action: .renameTab) { + // Dvorak: physical ANSI "O" key can produce "r". + // This should behave as semantic Cmd+R (rename tab), not Cmd+P. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "r", + charactersIgnoringModifiers: "r", + isARepeat: false, + keyCode: 31 // kVK_ANSI_O + ) else { + XCTFail("Failed to construct Dvorak Cmd+R event on physical ANSI O key") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + } + + wait(for: [renameTabExpectation, switcherExpectation], timeout: 1.0) + XCTAssertEqual(observedRenameTabWindow?.windowNumber, window.windowNumber) + } + + func testCmdPhysicalRWithDvorakCharactersTriggersCommandPaletteSwitcher() { + guard let appDelegate = AppDelegate.shared else { + XCTFail("Expected AppDelegate.shared") + return + } + + let windowId = appDelegate.createMainWindow() + defer { closeWindow(withId: windowId) } + + guard let window = window(withId: windowId) else { + XCTFail("Expected test window") + return + } + + let switcherExpectation = expectation(description: "Expected command palette switcher request for semantic Cmd+P") + var observedSwitcherWindow: NSWindow? + let switcherToken = NotificationCenter.default.addObserver( + forName: .commandPaletteSwitcherRequested, + object: nil, + queue: nil + ) { notification in + observedSwitcherWindow = notification.object as? NSWindow + switcherExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(switcherToken) } + + let renameTabExpectation = expectation(description: "Physical R on Dvorak should not trigger rename tab") + renameTabExpectation.isInverted = true + let renameTabToken = NotificationCenter.default.addObserver( + forName: .commandPaletteRenameTabRequested, + object: nil, + queue: nil + ) { _ in + renameTabExpectation.fulfill() + } + defer { NotificationCenter.default.removeObserver(renameTabToken) } + + // Dvorak: physical ANSI "R" key can produce "p". + // This should behave as semantic Cmd+P (palette switcher), not Cmd+R. + guard let event = NSEvent.keyEvent( + with: .keyDown, + location: .zero, + modifierFlags: [.command], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + characters: "p", + charactersIgnoringModifiers: "p", + isARepeat: false, + keyCode: 15 // kVK_ANSI_R + ) else { + XCTFail("Failed to construct Dvorak Cmd+P event on physical ANSI R key") + return + } + +#if DEBUG + XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) +#else + XCTFail("debugHandleCustomShortcut is only available in DEBUG") +#endif + + wait(for: [switcherExpectation, renameTabExpectation], timeout: 1.0) + XCTAssertEqual(observedSwitcherWindow?.windowNumber, window.windowNumber) + } + func testCmdShiftRRequestsRenameWorkspaceInCommandPalette() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") @@ -684,6 +1621,9 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { return } + window.makeKeyAndOrderFront(nil) + RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) + #if DEBUG XCTAssertTrue( appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 20.0), @@ -1210,6 +2150,24 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { ) } + private func withTemporaryShortcut( + action: KeyboardShortcutSettings.Action, + shortcut: StoredShortcut? = nil, + _ body: () -> Void + ) { + let hadPersistedShortcut = UserDefaults.standard.object(forKey: action.defaultsKey) != nil + let originalShortcut = KeyboardShortcutSettings.shortcut(for: action) + defer { + if hadPersistedShortcut { + KeyboardShortcutSettings.setShortcut(originalShortcut, for: action) + } else { + KeyboardShortcutSettings.resetShortcut(for: action) + } + } + KeyboardShortcutSettings.setShortcut(shortcut ?? action.defaultShortcut, for: action) + body() + } + private func assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest( _ openRequest: (_ appDelegate: AppDelegate, _ window: NSWindow) -> Void, file: StaticString = #filePath, diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 2177f000..7d428888 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2758,6 +2758,52 @@ final class FullScreenShortcutTests: XCTestCase { shouldToggleMainWindowFullScreenForCommandControlFShortcut( flags: [.command, .control], chars: "", + keyCode: 3, + layoutCharacterProvider: { _, _ in nil } + ) + ) + } + + func testDoesNotFallbackToANSIWhenLayoutTranslationReturnsNonFCharacter() { + XCTAssertFalse( + shouldToggleMainWindowFullScreenForCommandControlFShortcut( + flags: [.command, .control], + chars: "", + keyCode: 3, + layoutCharacterProvider: { _, _ in "u" } + ) + ) + } + + func testMatchesCommandControlFWhenCommandAwareLayoutTranslationProvidesF() { + XCTAssertTrue( + shouldToggleMainWindowFullScreenForCommandControlFShortcut( + flags: [.command, .control], + chars: "", + keyCode: 3, + layoutCharacterProvider: { _, modifierFlags in + modifierFlags.contains(.command) ? "f" : "u" + } + ) + ) + } + + func testMatchesCommandControlFWhenCharsAreControlSequence() { + XCTAssertTrue( + shouldToggleMainWindowFullScreenForCommandControlFShortcut( + flags: [.command, .control], + chars: "\u{06}", + keyCode: 3, + layoutCharacterProvider: { _, _ in nil } + ) + ) + } + + func testRejectsPhysicalFWhenCharacterRepresentsDifferentLayoutKey() { + XCTAssertFalse( + shouldToggleMainWindowFullScreenForCommandControlFShortcut( + flags: [.command, .control], + chars: "u", keyCode: 3 ) ) @@ -3349,16 +3395,19 @@ final class CommandPaletteSelectionScrollBehaviorTests: XCTestCase { } } -final class SidebarCommandHintPolicyTests: XCTestCase { - func testCommandHintRequiresCommandOnlyModifier() { +final class ShortcutHintModifierPolicyTests: XCTestCase { + func testShortcutHintRequiresEnabledCommandOnlyModifier() { withDefaultsSuite { defaults in defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) - XCTAssertTrue(SidebarCommandHintPolicy.shouldShowHints(for: [.command], defaults: defaults)) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [], defaults: defaults)) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .shift], defaults: defaults)) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .option], defaults: defaults)) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command, .control], defaults: defaults)) + XCTAssertTrue(ShortcutHintModifierPolicy.shouldShowHints(for: [.command], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command, .shift], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control, .shift], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command, .option], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control, .option], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command, .control], defaults: defaults)) } } @@ -3366,7 +3415,8 @@ final class SidebarCommandHintPolicyTests: XCTestCase { withDefaultsSuite { defaults in defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) - XCTAssertFalse(SidebarCommandHintPolicy.shouldShowHints(for: [.command], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control], defaults: defaults)) } } @@ -3374,17 +3424,18 @@ final class SidebarCommandHintPolicyTests: XCTestCase { withDefaultsSuite { defaults in defaults.removeObject(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) - XCTAssertTrue(SidebarCommandHintPolicy.shouldShowHints(for: [.command], defaults: defaults)) + XCTAssertTrue(ShortcutHintModifierPolicy.shouldShowHints(for: [.command], defaults: defaults)) + XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control], defaults: defaults)) } } - func testCommandHintUsesIntentionalHoldDelay() { - XCTAssertGreaterThanOrEqual(SidebarCommandHintPolicy.intentionalHoldDelay, 0.25) + func testShortcutHintUsesIntentionalHoldDelay() { + XCTAssertEqual(ShortcutHintModifierPolicy.intentionalHoldDelay, 0.30, accuracy: 0.001) } func testCurrentWindowRequiresHostWindowToBeKeyAndMatchEventWindow() { XCTAssertTrue( - SidebarCommandHintPolicy.isCurrentWindow( + ShortcutHintModifierPolicy.isCurrentWindow( hostWindowNumber: 42, hostWindowIsKey: true, eventWindowNumber: 42, @@ -3393,7 +3444,7 @@ final class SidebarCommandHintPolicyTests: XCTestCase { ) XCTAssertFalse( - SidebarCommandHintPolicy.isCurrentWindow( + ShortcutHintModifierPolicy.isCurrentWindow( hostWindowNumber: 42, hostWindowIsKey: true, eventWindowNumber: 7, @@ -3402,7 +3453,7 @@ final class SidebarCommandHintPolicyTests: XCTestCase { ) XCTAssertFalse( - SidebarCommandHintPolicy.isCurrentWindow( + ShortcutHintModifierPolicy.isCurrentWindow( hostWindowNumber: 42, hostWindowIsKey: false, eventWindowNumber: 42, @@ -3411,12 +3462,12 @@ final class SidebarCommandHintPolicyTests: XCTestCase { ) } - func testWindowScopedCommandHintsUseKeyWindowWhenNoEventWindowIsAvailable() { + func testWindowScopedShortcutHintsUseKeyWindowWhenNoEventWindowIsAvailable() { withDefaultsSuite { defaults in defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) XCTAssertTrue( - SidebarCommandHintPolicy.shouldShowHints( + ShortcutHintModifierPolicy.shouldShowHints( for: [.command], hostWindowNumber: 42, hostWindowIsKey: true, @@ -3427,7 +3478,7 @@ final class SidebarCommandHintPolicyTests: XCTestCase { ) XCTAssertFalse( - SidebarCommandHintPolicy.shouldShowHints( + ShortcutHintModifierPolicy.shouldShowHints( for: [.command], hostWindowNumber: 42, hostWindowIsKey: true, @@ -3436,11 +3487,33 @@ final class SidebarCommandHintPolicyTests: XCTestCase { defaults: defaults ) ) + + XCTAssertTrue( + ShortcutHintModifierPolicy.shouldShowHints( + for: [.command], + hostWindowNumber: 42, + hostWindowIsKey: true, + eventWindowNumber: nil, + keyWindowNumber: 42, + defaults: defaults + ) + ) + + XCTAssertFalse( + ShortcutHintModifierPolicy.shouldShowHints( + for: [.control], + hostWindowNumber: 42, + hostWindowIsKey: true, + eventWindowNumber: nil, + keyWindowNumber: 42, + defaults: defaults + ) + ) } } private func withDefaultsSuite(_ body: (UserDefaults) -> Void) { - let suiteName = "SidebarCommandHintPolicyTests-\(UUID().uuidString)" + let suiteName = "ShortcutHintModifierPolicyTests-\(UUID().uuidString)" guard let defaults = UserDefaults(suiteName: suiteName) else { XCTFail("Failed to create defaults suite") return @@ -3490,6 +3563,31 @@ final class ShortcutHintDebugSettingsTests: XCTestCase { defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) XCTAssertTrue(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults)) } + + func testResetVisibilityDefaultsRestoresAlwaysShowAndCommandHoldFlags() { + let suiteName = "ShortcutHintDebugSettingsTests-\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create defaults suite") + return + } + + defaults.removePersistentDomain(forName: suiteName) + defer { defaults.removePersistentDomain(forName: suiteName) } + + defaults.set(true, forKey: ShortcutHintDebugSettings.alwaysShowHintsKey) + defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) + + ShortcutHintDebugSettings.resetVisibilityDefaults(defaults: defaults) + + XCTAssertEqual( + defaults.object(forKey: ShortcutHintDebugSettings.alwaysShowHintsKey) as? Bool, + ShortcutHintDebugSettings.defaultAlwaysShowHints + ) + XCTAssertEqual( + defaults.object(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) as? Bool, + ShortcutHintDebugSettings.defaultShowHintsOnCommandHold + ) + } } final class ShortcutHintLanePlannerTests: XCTestCase { From a08ad56244811f5718ffd5cca0202e5cf0c00d05 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:41:22 -0800 Subject: [PATCH 113/232] Prevent agents from building/launching untagged cmux DEV (#979) Agents were following CLAUDE.md instructions to run bare xcodebuild without -derivedDataPath, producing untagged cmux DEV.app that shares the default debug socket and steals window focus. Replace bare xcodebuild instruction with reload.sh --tag. Replace E2E/Basic tests sections with a unified testing policy that forbids local test runs and requires tagged builds. --- CLAUDE.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 18f06112..5a244316 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,10 +34,16 @@ When reporting a tagged reload result in chat, use the format for your agent typ Never use `/tmp/cmux-/...` app links in chat output. If the expected DerivedData path is missing, resolve the real `.app` path and report that `file://` URL. -After making code changes, always run the build: +After making code changes, always use `reload.sh --tag` to build and launch. **Never run bare `xcodebuild` or `open` an untagged `cmux DEV.app`.** Untagged builds share the default debug socket and bundle ID with other agents, causing conflicts and stealing focus. ```bash -xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination 'platform=macOS' build +./scripts/reload.sh --tag +``` + +If you only need to verify the build compiles (no launch), use a tagged derivedDataPath: + +```bash +xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination 'platform=macOS' -derivedDataPath /tmp/cmux- build ``` When rebuilding GhosttyKit.xcframework, always use Release optimizations: @@ -131,21 +137,14 @@ tail -f "$(cat /tmp/cmux-last-debug-log-path 2>/dev/null || echo /tmp/cmux-debug - Only explicit focus-intent commands may mutate in-app focus/selection (`window.focus`, `workspace.select/next/previous/last`, `surface.focus`, `pane.focus/last`, browser focus commands, and v1 focus equivalents). - All non-focus commands should preserve current user focus context while still applying data/model changes. -## E2E mac UI tests +## Testing policy -Run UI tests on the UTM macOS VM (never on the host machine). Always run e2e UI tests via `ssh cmux-vm`: +**Never run tests locally.** All tests (E2E, UI, python socket tests) run via GitHub Actions or on the VM. -```bash -ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" -only-testing:cmuxUITests/UpdatePillUITests test' -``` - -## Basic tests - -Run basic automated tests on the UTM macOS VM (never on the host machine): - -```bash -ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux -configuration Debug -destination "platform=macOS" build && pkill -x "cmux DEV" || true && APP=$(find /Users/cmux/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Debug/cmux DEV.app" -print -quit) && open "$APP" --env CMUX_SOCKET_MODE=allowAll && for i in {1..20}; do [ -S /tmp/cmux-debug.sock ] && break; sleep 0.5; done && python3 tests/test_update_timing.py && python3 tests/test_signals_auto.py && python3 tests/test_ctrl_socket.py && python3 tests/test_notifications.py' -``` +- **E2E / UI tests:** trigger via `gh workflow run test-e2e.yml` (see cmuxterm-hq CLAUDE.md for details) +- **Unit tests:** `xcodebuild -scheme cmux-unit` is safe (no app launch), but prefer CI +- **Python socket tests (tests_v2/):** these connect to a running cmux instance's socket. Never launch an untagged `cmux DEV.app` to run them. If you must test locally, use a tagged build's socket (`/tmp/cmux-debug-.sock`) with `CMUX_SOCKET=/tmp/cmux-debug-.sock` +- **Never `open` an untagged `cmux DEV.app`** from DerivedData. It conflicts with the user's running debug instance. ## Ghostty submodule workflow From 827df22fee3b71e754a247a1cf303d16ee5ecdea Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:41:26 -0800 Subject: [PATCH 114/232] Prefer standalone CLI in notify UI test --- .../MultiWindowNotificationsUITests.swift | 109 ++++++++++++------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index be7daba1..10343dc1 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -564,44 +564,38 @@ final class MultiWindowNotificationsUITests: XCTestCase { arguments: [String], responseTimeoutSeconds: Double = 3.0 ) -> (terminationStatus: Int32, stdout: String, stderr: String) { - let process = Process() - let cliPath = resolveCmuxCLIPath() var args = ["--socket", socketPath] args.append(contentsOf: arguments) - if let cliPath { - process.executableURL = URL(fileURLWithPath: cliPath) - } else { - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - args.insert("cmux", at: 0) - } - process.arguments = args var environment = ProcessInfo.processInfo.environment environment["CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC"] = String(responseTimeoutSeconds) - process.environment = environment - let stdoutPipe = Pipe() - let stderrPipe = Pipe() - process.standardOutput = stdoutPipe - process.standardError = stderrPipe - - do { - try process.run() - process.waitUntilExit() - } catch { - return ( - terminationStatus: -1, - stdout: "", - stderr: "Failed to run cmux command: \(error.localizedDescription) (cliPath=\(cliPath ?? "env:cmux"))" + var lastPermissionFailure: (terminationStatus: Int32, stdout: String, stderr: String)? + for cliPath in resolveCmuxCLIPaths() { + let result = executeCmuxCommand( + executablePath: cliPath, + arguments: args, + environment: environment ) + if result.terminationStatus == 0 { + return result + } + if result.stderr.localizedCaseInsensitiveContains("operation not permitted") { + lastPermissionFailure = result + continue + } + return result } - let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() - let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() - let stdout = String(data: stdoutData, encoding: .utf8)? - .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - let stderr = String(data: stderrData, encoding: .utf8)? - .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - return (process.terminationStatus, stdout, stderr) + let fallbackArgs = ["cmux"] + args + let fallbackResult = executeCmuxCommand( + executablePath: "/usr/bin/env", + arguments: fallbackArgs, + environment: environment + ) + if fallbackResult.terminationStatus == 0 || lastPermissionFailure == nil { + return fallbackResult + } + return lastPermissionFailure ?? fallbackResult } private func socketDiagnostics(from data: [String: String]) -> String { @@ -612,7 +606,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { "signals=\(data["socketFailureSignals"] ?? "")" } - private func resolveCmuxCLIPath() -> String? { + private func resolveCmuxCLIPaths() -> [String] { let fileManager = FileManager.default let env = ProcessInfo.processInfo.environment var candidates: [String] = [] @@ -648,13 +642,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux.app/Contents/Resources/bin/cmux") candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux") + var resolvedPaths: [String] = [] for path in uniquePaths(candidates) { - if fileManager.isExecutableFile(atPath: path) { - return URL(fileURLWithPath: path).resolvingSymlinksInPath().path - } + guard fileManager.isExecutableFile(atPath: path) else { continue } + resolvedPaths.append(URL(fileURLWithPath: path).resolvingSymlinksInPath().path) } - - return nil + return uniquePaths(resolvedPaths) } private func inferredBuildProductsDirectories() -> [String] { @@ -676,14 +669,20 @@ final class MultiWindowNotificationsUITests: XCTestCase { } private func appendCLIPathCandidates(fromProductsDirectory productsDir: String, to candidates: inout [String]) { + candidates.append("\(productsDir)/cmux") candidates.append("\(productsDir)/cmux DEV.app/Contents/Resources/bin/cmux") candidates.append("\(productsDir)/cmux.app/Contents/Resources/bin/cmux") - candidates.append("\(productsDir)/cmux") guard let entries = try? FileManager.default.contentsOfDirectory(atPath: productsDir) else { return } + for entry in entries.sorted() where entry == "cmux" { + let cliPath = URL(fileURLWithPath: productsDir) + .appendingPathComponent(entry) + .path + candidates.append(cliPath) + } for entry in entries.sorted() where entry.hasSuffix(".app") { let cliPath = URL(fileURLWithPath: productsDir) .appendingPathComponent(entry) @@ -693,6 +692,42 @@ final class MultiWindowNotificationsUITests: XCTestCase { } } + private func executeCmuxCommand( + executablePath: String, + arguments: [String], + environment: [String: String] + ) -> (terminationStatus: Int32, stdout: String, stderr: String) { + let process = Process() + process.executableURL = URL(fileURLWithPath: executablePath) + process.arguments = arguments + process.environment = environment + + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + process.standardOutput = stdoutPipe + process.standardError = stderrPipe + + do { + try process.run() + process.waitUntilExit() + } catch { + return ( + terminationStatus: -1, + stdout: "", + stderr: "Failed to run cmux command: \(error.localizedDescription) (cliPath=\(executablePath))" + ) + } + + let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() + let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() + let stdout = String(data: stdoutData, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + let rawStderr = String(data: stderrData, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + let stderr = rawStderr.isEmpty ? "" : "\(rawStderr) (cliPath=\(executablePath))" + return (process.terminationStatus, stdout, stderr) + } + private func uniquePaths(_ paths: [String]) -> [String] { var unique: [String] = [] var seen = Set() From 3e10c3f790492699f71be898ac4a209c1d0da55d Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:55:05 -0800 Subject: [PATCH 115/232] Use raw socket fallback in notify UI test --- .../MultiWindowNotificationsUITests.swift | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 10343dc1..966d060f 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -236,25 +236,23 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) - let pingResult = waitForCmuxPing(timeout: 20.0) - if pingResult.stdout != "PONG", + let pingResponse = waitForSocketPong(timeout: 20.0) + if pingResponse != "PONG", let resolvedPath = resolveSocketPath(timeout: 5.0, requiredWorkspaceId: tabId2) { socketPath = resolvedPath } - let confirmedPingResult = pingResult.stdout == "PONG" ? pingResult : waitForCmuxPing(timeout: 5.0) - guard confirmedPingResult.stdout == "PONG" else { + let confirmedPingResponse = pingResponse == "PONG" ? pingResponse : waitForSocketPong(timeout: 5.0) + guard confirmedPingResponse == "PONG" else { let failureSetup = loadData() ?? setup XCTFail( - "Control socket did not respond in time. path=\(socketPath) response=\(confirmedPingResult.stdout ?? "") " + - "stderr=\(confirmedPingResult.stderr ?? "") " + + "Control socket did not respond in time. path=\(socketPath) response=\(confirmedPingResponse ?? "") " + socketDiagnostics(from: failureSetup) ) return } - guard let surfaceId = waitForSurfaceIdViaCLI(forWorkspaceId: tabId2, timeout: 12.0) - ?? waitForSurfaceId(forWorkspaceId: tabId2, timeout: 3.0) else { + guard let surfaceId = waitForSurfaceId(forWorkspaceId: tabId2, timeout: 12.0) else { let failureSetup = loadData() ?? setup XCTFail( "Expected at least one surface in workspace \(tabId2). socket=\(socketPath) " + @@ -419,6 +417,10 @@ final class MultiWindowNotificationsUITests: XCTestCase { if result.terminationStatus == 0, stdout == "PONG" { return ("PONG", stderr) } + if isSocketPermissionFailure(stderr), + waitForSocketPong(timeout: 0.5) == "PONG" { + return ("PONG", stderr) + } RunLoop.current.run(until: Date().addingTimeInterval(0.05)) } @@ -429,6 +431,10 @@ final class MultiWindowNotificationsUITests: XCTestCase { ) let stdout = result.stdout.isEmpty ? nil : result.stdout let stderr = result.stderr.isEmpty ? nil : result.stderr + if isSocketPermissionFailure(stderr), + waitForSocketPong(timeout: 0.5) == "PONG" { + return ("PONG", stderr) + } return (stdout ?? lastStdout, stderr ?? lastStderr) } @@ -486,7 +492,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { private func firstSurfaceIdViaCLI(forWorkspaceId workspaceId: String) -> String? { guard let paneId = firstPaneIdViaCLI(forWorkspaceId: workspaceId) else { - return nil + return firstSurfaceId(forWorkspaceId: workspaceId) } let result = runCmuxCommand( socketPath: socketPath, @@ -501,7 +507,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { ], responseTimeoutSeconds: 3.0 ) - guard result.terminationStatus == 0 else { return nil } + guard result.terminationStatus == 0 else { + if isSocketPermissionFailure(result.stderr) { + return firstSurfaceId(forWorkspaceId: workspaceId) + } + return nil + } return firstHandle(in: result.stdout) } @@ -517,7 +528,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { ], responseTimeoutSeconds: 3.0 ) - guard result.terminationStatus == 0 else { return nil } + guard result.terminationStatus == 0 else { + if isSocketPermissionFailure(result.stderr) { + return nil + } + return nil + } return firstHandle(in: result.stdout) } @@ -540,7 +556,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { surfaceId: String, title: String ) -> (terminationStatus: Int32, stdout: String, stderr: String) { - runCmuxCommand( + let result = runCmuxCommand( socketPath: socketPath, arguments: [ "notify", @@ -557,6 +573,19 @@ final class MultiWindowNotificationsUITests: XCTestCase { ], responseTimeoutSeconds: 4.0 ) + if result.terminationStatus == 0 || !isSocketPermissionFailure(result.stderr) { + return result + } + + let response = socketCommand("notify_target \(workspaceId) \(surfaceId) \(title)|ui-test|focus-regression") ?? "" + let succeeded = response == "OK" + return ( + terminationStatus: succeeded ? 0 : 1, + stdout: response, + stderr: succeeded + ? "\(result.stderr) raw-socket-fallback" + : "\(result.stderr) raw-socket-fallback-response=\(response)" + ) } private func runCmuxCommand( @@ -728,6 +757,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { return (process.terminationStatus, stdout, stderr) } + private func isSocketPermissionFailure(_ stderr: String?) -> Bool { + guard let stderr, !stderr.isEmpty else { return false } + return stderr.localizedCaseInsensitiveContains("failed to connect to socket") && + stderr.localizedCaseInsensitiveContains("operation not permitted") + } + private func uniquePaths(_ paths: [String]) -> [String] { var unique: [String] = [] var seen = Set() From 5f43a3fc32045cf63cd4bab97befe8f0756c6c16 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 5 Mar 2026 18:56:03 -0800 Subject: [PATCH 116/232] Fix notification unread persistence when workspaces regain focus (#971) * Fix notification unread persistence on focus * Address review feedback on notification unread fix --- Sources/AppDelegate.swift | 36 ----- Sources/TabManager.swift | 12 +- Sources/TerminalNotificationStore.swift | 43 ++--- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 151 ++++++++++++++++++ tests/test_focus_notification_dismiss.py | 12 +- tests/test_notifications.py | 95 +++++++---- tests_v2/test_focus_notification_dismiss.py | 12 +- tests_v2/test_notifications.py | 95 +++++++---- 8 files changed, 316 insertions(+), 140 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 373778a4..dfbff6c2 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1837,7 +1837,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let tab = tabManager.tabs.first(where: { $0.id == tabId }) { tab.triggerNotificationFocusFlash(panelId: surfaceId, requiresSplit: false, shouldFocus: false) } - notificationStore.markRead(forTabId: tabId, surfaceId: surfaceId) } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -8123,16 +8122,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) #endif - if let notificationId, let store = notificationStore { - markReadIfFocused( - notificationId: notificationId, - tabId: tabId, - surfaceId: surfaceId, - tabManager: context.tabManager, - notificationStore: store - ) - } - #if DEBUG recordMultiWindowNotificationFocusIfNeeded( windowId: context.windowId, @@ -8186,15 +8175,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) #endif - if let notificationId, let store = notificationStore { - markReadIfFocused( - notificationId: notificationId, - tabId: tabId, - surfaceId: surfaceId, - tabManager: tabManager, - notificationStore: store - ) - } #if DEBUG if ProcessInfo.processInfo.environment["CMUX_UI_TEST_JUMP_UNREAD_SETUP"] == "1" { writeJumpUnreadTestData(["jumpUnreadOpenInFallback": "1", "jumpUnreadOpenResult": "1"]) @@ -8254,22 +8234,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) } - private func markReadIfFocused( - notificationId: UUID, - tabId: UUID, - surfaceId: UUID?, - tabManager: TabManager, - notificationStore: TerminalNotificationStore - ) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { - guard tabManager.selectedTabId == tabId else { return } - if let surfaceId { - guard tabManager.focusedSurfaceId(for: tabId) == surfaceId else { return } - } - notificationStore.markRead(id: notificationId) - } - } - #if DEBUG private func recordMultiWindowNotificationOpenFailureIfNeeded( tabId: UUID, diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index abdb3ea6..528669a6 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -599,7 +599,7 @@ class TabManager: ObservableObject { self.focusSelectedTabPanel(previousTabId: previousTabId) self.updateWindowTitleForSelectedTab() if let selectedTabId = self.selectedTabId { - self.markFocusedPanelReadIfActive(tabId: selectedTabId) + self.flashFocusedPanelIfUnreadAndActive(tabId: selectedTabId) } #if DEBUG let dtMs = self.debugWorkspaceSwitchStartTime > 0 @@ -671,7 +671,7 @@ class TabManager: ObservableObject { guard let self else { return } guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID else { return } guard let surfaceId = notification.userInfo?[GhosttyNotificationKey.surfaceId] as? UUID else { return } - markPanelReadOnFocusIfActive(tabId: tabId, panelId: surfaceId) + flashPanelIfUnreadAndActive(tabId: tabId, panelId: surfaceId) } }) @@ -1596,16 +1596,16 @@ class TabManager: ObservableObject { selectedTabId != pendingTabId } - private func markFocusedPanelReadIfActive(tabId: UUID) { + private func flashFocusedPanelIfUnreadAndActive(tabId: UUID) { let shouldSuppressFlash = suppressFocusFlash suppressFocusFlash = false guard !shouldSuppressFlash else { return } guard AppFocusState.isAppActive() else { return } guard let panelId = focusedPanelId(for: tabId) else { return } - markPanelReadOnFocusIfActive(tabId: tabId, panelId: panelId) + flashPanelIfUnreadAndActive(tabId: tabId, panelId: panelId) } - private func markPanelReadOnFocusIfActive(tabId: UUID, panelId: UUID) { + private func flashPanelIfUnreadAndActive(tabId: UUID, panelId: UUID) { guard selectedTabId == tabId else { return } guard !suppressFocusFlash else { return } guard AppFocusState.isAppActive() else { return } @@ -1614,7 +1614,6 @@ class TabManager: ObservableObject { if let tab = tabs.first(where: { $0.id == tabId }) { tab.triggerNotificationFocusFlash(panelId: panelId, requiresSplit: false, shouldFocus: false) } - notificationStore.markRead(forTabId: tabId, surfaceId: panelId) } private func enqueuePanelTitleUpdate(tabId: UUID, panelId: UUID, title: String) { @@ -1740,7 +1739,6 @@ class TabManager: ObservableObject { guard let notificationStore = AppDelegate.shared?.notificationStore else { return } guard notificationStore.hasUnreadNotification(forTabId: tabId, surfaceId: targetPanelId) else { return } tab.triggerNotificationFocusFlash(panelId: targetPanelId, requiresSplit: false, shouldFocus: true) - notificationStore.markRead(forTabId: tabId, surfaceId: targetPanelId) } } diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 5bb768cb..1f1dac18 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -833,16 +833,9 @@ final class TerminalNotificationStore: ObservableObject { let isFocusedSurface = surfaceId == nil || focusedSurfaceId == surfaceId let isFocusedPanel = isActiveTab && isFocusedSurface let isAppFocused = AppFocusState.isAppFocused() - if isAppFocused && isFocusedPanel { - if !idsToClear.isEmpty { - notifications = updated - center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) - center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) - } - return - } + let suppressNativeDelivery = isAppFocused && isFocusedPanel - if WorkspaceAutoReorderSettings.isEnabled() { + if WorkspaceAutoReorderSettings.isEnabled() && !suppressNativeDelivery { AppDelegate.shared?.tabManager?.moveTabToTop(tabId) } @@ -862,7 +855,11 @@ final class TerminalNotificationStore: ObservableObject { center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } - scheduleUserNotification(notification) + if suppressNativeDelivery { + Self.runNotificationCustomCommand(notification) + } else { + scheduleUserNotification(notification) + } } func markRead(id: UUID) { @@ -993,10 +990,7 @@ final class TerminalNotificationStore: ObservableObject { guard let self, authorized else { return } let content = UNMutableNotificationContent() - let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String - ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String - ?? "cmux" - content.title = notification.title.isEmpty ? appName : notification.title + content.title = Self.notificationDisplayTitle(notification) content.subtitle = notification.subtitle content.body = notification.body content.sound = NotificationSoundSettings.sound() @@ -1019,16 +1013,27 @@ final class TerminalNotificationStore: ObservableObject { if let error { NSLog("Failed to schedule notification: \(error)") } else { - NotificationSoundSettings.runCustomCommand( - title: content.title, - subtitle: content.subtitle, - body: content.body - ) + Self.runNotificationCustomCommand(notification) } } } } + nonisolated private static func notificationDisplayTitle(_ notification: TerminalNotification) -> String { + let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String + ?? "cmux" + return notification.title.isEmpty ? appName : notification.title + } + + nonisolated private static func runNotificationCustomCommand(_ notification: TerminalNotification) { + NotificationSoundSettings.runCustomCommand( + title: notificationDisplayTitle(notification), + subtitle: notification.subtitle, + body: notification.body + ) + } + private func ensureAuthorization( origin: AuthorizationRequestOrigin, _ completion: @escaping (Bool) -> Void diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 7d428888..114c7b2b 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -6756,6 +6756,8 @@ final class NotificationDockBadgeTests: XCTestCase { } override func tearDown() { + AppFocusState.overrideIsFocused = nil + AppDelegate.shared = nil TerminalNotificationStore.shared.resetNotificationSettingsPromptHooksForTesting() TerminalNotificationStore.shared.replaceNotificationsForTesting([]) super.tearDown() @@ -7259,6 +7261,155 @@ final class NotificationDockBadgeTests: XCTestCase { XCTAssertEqual(store.latestNotification(forTabId: tabB)?.id, notificationBUnread.id) } + func testFocusedTabNotificationIsStoredWhenNativeDeliveryIsSuppressed() { + let store = TerminalNotificationStore.shared + store.replaceNotificationsForTesting([]) + + let appDelegate = AppDelegate() + let tabManager = TabManager() + appDelegate.tabManager = tabManager + AppDelegate.shared = appDelegate + AppFocusState.overrideIsFocused = true + + guard let tabId = tabManager.selectedTabId else { + XCTFail("Expected selected tab for notification test") + return + } + + store.addNotification( + tabId: tabId, + surfaceId: nil, + title: "Needs input", + subtitle: "", + body: "agent requires user action" + ) + + XCTAssertEqual(store.unreadCount(forTabId: tabId), 1) + guard let latest = store.latestNotification(forTabId: tabId) else { + XCTFail("Expected notification to be stored for focused tab") + return + } + XCTAssertEqual(latest.tabId, tabId) + XCTAssertEqual(latest.title, "Needs input") + XCTAssertEqual(latest.body, "agent requires user action") + XCTAssertFalse(latest.isRead) + } + + func testApplicationDidBecomeActiveDoesNotMarkFocusedNotificationRead() { + let store = TerminalNotificationStore.shared + let appDelegate = AppDelegate() + let tabManager = TabManager() + appDelegate.tabManager = tabManager + appDelegate.notificationStore = store + AppDelegate.shared = appDelegate + AppFocusState.overrideIsFocused = true + + guard let tabId = tabManager.selectedTabId, + let surfaceId = tabManager.focusedSurfaceId(for: tabId) else { + XCTFail("Expected selected tab and focused surface for activation test") + return + } + + let notification = TerminalNotification( + id: UUID(), + tabId: tabId, + surfaceId: surfaceId, + title: "Unread", + subtitle: "", + body: "should persist across app activation", + createdAt: Date(), + isRead: false + ) + store.replaceNotificationsForTesting([notification]) + + appDelegate.applicationDidBecomeActive( + Notification(name: NSApplication.didBecomeActiveNotification) + ) + + XCTAssertTrue(store.hasUnreadNotification(forTabId: tabId, surfaceId: surfaceId)) + XCTAssertFalse(store.notifications[0].isRead) + } + + func testSelectingWorkspaceDoesNotMarkFocusedNotificationRead() { + let store = TerminalNotificationStore.shared + let appDelegate = AppDelegate() + let tabManager = TabManager() + appDelegate.tabManager = tabManager + appDelegate.notificationStore = store + AppDelegate.shared = appDelegate + AppFocusState.overrideIsFocused = true + + guard let originalTabId = tabManager.selectedTabId, + let originalSurfaceId = tabManager.focusedSurfaceId(for: originalTabId) else { + XCTFail("Expected selected tab and focused surface for workspace selection test") + return + } + guard let originalWorkspace = tabManager.tabs.first(where: { $0.id == originalTabId }) else { + XCTFail("Expected original workspace for workspace selection test") + return + } + + let notification = TerminalNotification( + id: UUID(), + tabId: originalTabId, + surfaceId: originalSurfaceId, + title: "Unread", + subtitle: "", + body: "should persist across workspace selection", + createdAt: Date(), + isRead: false + ) + store.replaceNotificationsForTesting([notification]) + + _ = tabManager.addWorkspace(select: true) + tabManager.selectWorkspace(originalWorkspace) + + let drained = expectation(description: "workspace selection side effects drained") + DispatchQueue.main.async { drained.fulfill() } + wait(for: [drained], timeout: 1.0) + + XCTAssertEqual(tabManager.selectedTabId, originalTabId) + XCTAssertTrue(store.hasUnreadNotification(forTabId: originalTabId, surfaceId: originalSurfaceId)) + XCTAssertFalse(store.notifications[0].isRead) + } + + func testNotificationFocusNavigationDoesNotMarkNotificationRead() { + let store = TerminalNotificationStore.shared + let appDelegate = AppDelegate() + let tabManager = TabManager() + appDelegate.tabManager = tabManager + appDelegate.notificationStore = store + AppDelegate.shared = appDelegate + AppFocusState.overrideIsFocused = true + + guard let tabId = tabManager.selectedTabId, + let surfaceId = tabManager.focusedSurfaceId(for: tabId) else { + XCTFail("Expected selected tab and focused surface for notification focus test") + return + } + + let notification = TerminalNotification( + id: UUID(), + tabId: tabId, + surfaceId: surfaceId, + title: "Unread", + subtitle: "", + body: "should persist after notification focus", + createdAt: Date(), + isRead: false + ) + store.replaceNotificationsForTesting([notification]) + + tabManager.focusTabFromNotification(tabId, surfaceId: surfaceId) + + let drained = expectation(description: "notification focus drained") + DispatchQueue.main.async { drained.fulfill() } + wait(for: [drained], timeout: 1.0) + + XCTAssertTrue(store.hasUnreadNotification(forTabId: tabId, surfaceId: surfaceId)) + XCTAssertFalse(store.notifications[0].isRead) + } + func testNotificationIndexesUpdateAfterReadAndClearMutations() { let tab = UUID() let surfaceUnread = UUID() diff --git a/tests/test_focus_notification_dismiss.py b/tests/test_focus_notification_dismiss.py index d7569b65..14cef434 100755 --- a/tests/test_focus_notification_dismiss.py +++ b/tests/test_focus_notification_dismiss.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -E2E: focusing a panel clears its notification and triggers a flash. +E2E: focusing a panel preserves its notification and triggers a flash. Note: This uses the socket focus command (no assistive access needed). """ @@ -74,8 +74,12 @@ def main() -> int: client.send("x") time.sleep(0.2) - if not wait_for_notification(client, surface_id, is_read=True, timeout=2.0): - print("FAIL: Notification did not become read after focus") + if wait_for_notification(client, surface_id, is_read=True, timeout=2.0): + print("FAIL: Notification became read after focus") + return 1 + items = client.list_notifications() + if not any(item["surface_id"] == surface_id and not item["is_read"] for item in items): + print("FAIL: Notification did not remain present and unread after focus") return 1 final_flash = client.flash_count(term_b) @@ -93,7 +97,7 @@ def main() -> int: except Exception: pass - print("PASS: Focus clears notification and flashes panel") + print("PASS: Focus preserves notification and flashes panel") return 0 except (cmuxError, RuntimeError) as exc: print(f"FAIL: {exc}") diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 1ac25c4b..23b4bf10 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -58,6 +58,15 @@ def wait_for_flash_count(client: cmux, surface: str, minimum: int = 1, timeout: return last +def wait_for_current_workspace(client: cmux, expected: str, timeout: float = 2.0) -> bool: + start = time.time() + while time.time() - start < timeout: + if client.current_workspace() == expected: + return True + time.sleep(0.05) + return client.current_workspace() == expected + + def ensure_two_surfaces(client: cmux) -> list[tuple[int, str, bool]]: surfaces = client.list_surfaces() if len(surfaces) < 2: @@ -215,8 +224,8 @@ def test_rxvt_notification_osc777(client: cmux) -> TestResult: return result -def test_mark_read_on_focus_change(client: cmux) -> TestResult: - result = TestResult("Mark Read On Panel Focus") +def test_preserve_unread_on_focus_change(client: cmux) -> TestResult: + result = TestResult("Preserve Unread On Panel Focus") try: client.clear_notifications() client.reset_flash_counts() @@ -229,81 +238,88 @@ def test_mark_read_on_focus_change(client: cmux) -> TestResult: client.set_app_focus(False) client.notify_surface(other[0], "focusread") - time.sleep(0.1) + items = wait_for_notifications(client, 1) + target = next((n for n in items if n["surface_id"] == other[1]), None) + if target is None or target["is_read"]: + result.failure("Expected unread notification for target surface before focus") + return result client.set_app_focus(True) client.focus_surface(other[0]) - time.sleep(0.1) + count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) + if count < 1: + result.failure("Expected flash on panel focus") + return result items = client.list_notifications() target = next((n for n in items if n["surface_id"] == other[1]), None) if target is None: result.failure("Expected notification for target surface") - elif not target["is_read"]: - result.failure("Expected notification to be marked read on focus") + elif target["is_read"]: + result.failure("Expected notification to remain unread on focus") else: - count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) - if count < 1: - result.failure("Expected flash on panel focus dismissal") - else: - result.success("Notification marked read on focus") + result.success("Notification persisted across panel focus") except Exception as e: result.failure(f"Exception: {e}") return result -def test_mark_read_on_app_active(client: cmux) -> TestResult: - result = TestResult("Mark Read On App Active") +def test_preserve_unread_on_app_active(client: cmux) -> TestResult: + result = TestResult("Preserve Unread On App Active") try: client.clear_notifications() client.set_app_focus(False) client.notify("activate") - time.sleep(0.1) - - items = client.list_notifications() + items = wait_for_notifications(client, 1) if not items or items[0]["is_read"]: result.failure("Expected unread notification before activation") return result client.simulate_app_active() - time.sleep(0.1) - - items = client.list_notifications() + items = wait_for_notifications(client, 1) if not items: result.failure("Expected notification to remain after activation") - elif not items[0]["is_read"]: - result.failure("Expected notification to be marked read on app active") + elif items[0]["is_read"]: + result.failure("Expected notification to remain unread on app active") else: - result.success("Notification marked read on app active") + result.success("Notification persisted across app activation") except Exception as e: result.failure(f"Exception: {e}") return result -def test_mark_read_on_tab_switch(client: cmux) -> TestResult: - result = TestResult("Mark Read On Tab Switch") +def test_preserve_unread_on_tab_switch(client: cmux) -> TestResult: + result = TestResult("Preserve Unread On Tab Switch") try: client.clear_notifications() client.set_app_focus(False) tab1 = client.current_workspace() client.notify("tabswitch") - time.sleep(0.1) + items = wait_for_notifications(client, 1) + target = next((n for n in items if n["workspace_id"] == tab1), None) + if target is None or target["is_read"]: + result.failure("Expected unread notification for original tab before switching") + return result tab2 = client.new_workspace() - time.sleep(0.1) + if not wait_for_current_workspace(client, tab2): + result.failure("Expected new workspace to become selected") + return result client.set_app_focus(True) client.select_workspace(tab1) - time.sleep(0.1) + if not wait_for_current_workspace(client, tab1): + result.failure("Expected original workspace to become selected again") + return result - items = client.list_notifications() + items = wait_for_notifications(client, 1) target = next((n for n in items if n["workspace_id"] == tab1), None) if target is None: result.failure("Expected notification for original tab") - elif not target["is_read"]: - result.failure("Expected notification to be marked read on tab switch") + elif target["is_read"]: + result.failure("Expected notification to remain unread on tab switch") else: - result.success("Notification marked read on tab switch") + result.success("Notification persisted across tab switch") except Exception as e: result.failure(f"Exception: {e}") return result @@ -371,11 +387,20 @@ def test_focus_on_notification_click(client: cmux) -> TestResult: result.failure("Expected notification surface to be focused") return result + items = client.list_notifications() + notification = next((n for n in items if n["surface_id"] == other[1]), None) + if notification is None: + result.failure("Expected notification to remain listed after notification click") + return result + if notification["is_read"]: + result.failure("Expected notification click to preserve unread state") + return result + count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) if count < 1: result.failure(f"Expected flash count >= 1, got {count}") else: - result.success("Notification click focuses and flashes panel") + result.success("Notification click focuses, flashes, and preserves unread state") except Exception as e: result.failure(f"Exception: {e}") return result @@ -455,9 +480,9 @@ def run_tests() -> int: results.append(test_kitty_notification_simple(client)) results.append(test_kitty_notification_chunked(client)) results.append(test_rxvt_notification_osc777(client)) - results.append(test_mark_read_on_focus_change(client)) - results.append(test_mark_read_on_app_active(client)) - results.append(test_mark_read_on_tab_switch(client)) + results.append(test_preserve_unread_on_focus_change(client)) + results.append(test_preserve_unread_on_app_active(client)) + results.append(test_preserve_unread_on_tab_switch(client)) results.append(test_flash_on_tab_switch(client)) results.append(test_focus_on_notification_click(client)) results.append(test_restore_focus_on_tab_switch(client)) diff --git a/tests_v2/test_focus_notification_dismiss.py b/tests_v2/test_focus_notification_dismiss.py index d7569b65..14cef434 100755 --- a/tests_v2/test_focus_notification_dismiss.py +++ b/tests_v2/test_focus_notification_dismiss.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -E2E: focusing a panel clears its notification and triggers a flash. +E2E: focusing a panel preserves its notification and triggers a flash. Note: This uses the socket focus command (no assistive access needed). """ @@ -74,8 +74,12 @@ def main() -> int: client.send("x") time.sleep(0.2) - if not wait_for_notification(client, surface_id, is_read=True, timeout=2.0): - print("FAIL: Notification did not become read after focus") + if wait_for_notification(client, surface_id, is_read=True, timeout=2.0): + print("FAIL: Notification became read after focus") + return 1 + items = client.list_notifications() + if not any(item["surface_id"] == surface_id and not item["is_read"] for item in items): + print("FAIL: Notification did not remain present and unread after focus") return 1 final_flash = client.flash_count(term_b) @@ -93,7 +97,7 @@ def main() -> int: except Exception: pass - print("PASS: Focus clears notification and flashes panel") + print("PASS: Focus preserves notification and flashes panel") return 0 except (cmuxError, RuntimeError) as exc: print(f"FAIL: {exc}") diff --git a/tests_v2/test_notifications.py b/tests_v2/test_notifications.py index 1ac25c4b..23b4bf10 100644 --- a/tests_v2/test_notifications.py +++ b/tests_v2/test_notifications.py @@ -58,6 +58,15 @@ def wait_for_flash_count(client: cmux, surface: str, minimum: int = 1, timeout: return last +def wait_for_current_workspace(client: cmux, expected: str, timeout: float = 2.0) -> bool: + start = time.time() + while time.time() - start < timeout: + if client.current_workspace() == expected: + return True + time.sleep(0.05) + return client.current_workspace() == expected + + def ensure_two_surfaces(client: cmux) -> list[tuple[int, str, bool]]: surfaces = client.list_surfaces() if len(surfaces) < 2: @@ -215,8 +224,8 @@ def test_rxvt_notification_osc777(client: cmux) -> TestResult: return result -def test_mark_read_on_focus_change(client: cmux) -> TestResult: - result = TestResult("Mark Read On Panel Focus") +def test_preserve_unread_on_focus_change(client: cmux) -> TestResult: + result = TestResult("Preserve Unread On Panel Focus") try: client.clear_notifications() client.reset_flash_counts() @@ -229,81 +238,88 @@ def test_mark_read_on_focus_change(client: cmux) -> TestResult: client.set_app_focus(False) client.notify_surface(other[0], "focusread") - time.sleep(0.1) + items = wait_for_notifications(client, 1) + target = next((n for n in items if n["surface_id"] == other[1]), None) + if target is None or target["is_read"]: + result.failure("Expected unread notification for target surface before focus") + return result client.set_app_focus(True) client.focus_surface(other[0]) - time.sleep(0.1) + count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) + if count < 1: + result.failure("Expected flash on panel focus") + return result items = client.list_notifications() target = next((n for n in items if n["surface_id"] == other[1]), None) if target is None: result.failure("Expected notification for target surface") - elif not target["is_read"]: - result.failure("Expected notification to be marked read on focus") + elif target["is_read"]: + result.failure("Expected notification to remain unread on focus") else: - count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) - if count < 1: - result.failure("Expected flash on panel focus dismissal") - else: - result.success("Notification marked read on focus") + result.success("Notification persisted across panel focus") except Exception as e: result.failure(f"Exception: {e}") return result -def test_mark_read_on_app_active(client: cmux) -> TestResult: - result = TestResult("Mark Read On App Active") +def test_preserve_unread_on_app_active(client: cmux) -> TestResult: + result = TestResult("Preserve Unread On App Active") try: client.clear_notifications() client.set_app_focus(False) client.notify("activate") - time.sleep(0.1) - - items = client.list_notifications() + items = wait_for_notifications(client, 1) if not items or items[0]["is_read"]: result.failure("Expected unread notification before activation") return result client.simulate_app_active() - time.sleep(0.1) - - items = client.list_notifications() + items = wait_for_notifications(client, 1) if not items: result.failure("Expected notification to remain after activation") - elif not items[0]["is_read"]: - result.failure("Expected notification to be marked read on app active") + elif items[0]["is_read"]: + result.failure("Expected notification to remain unread on app active") else: - result.success("Notification marked read on app active") + result.success("Notification persisted across app activation") except Exception as e: result.failure(f"Exception: {e}") return result -def test_mark_read_on_tab_switch(client: cmux) -> TestResult: - result = TestResult("Mark Read On Tab Switch") +def test_preserve_unread_on_tab_switch(client: cmux) -> TestResult: + result = TestResult("Preserve Unread On Tab Switch") try: client.clear_notifications() client.set_app_focus(False) tab1 = client.current_workspace() client.notify("tabswitch") - time.sleep(0.1) + items = wait_for_notifications(client, 1) + target = next((n for n in items if n["workspace_id"] == tab1), None) + if target is None or target["is_read"]: + result.failure("Expected unread notification for original tab before switching") + return result tab2 = client.new_workspace() - time.sleep(0.1) + if not wait_for_current_workspace(client, tab2): + result.failure("Expected new workspace to become selected") + return result client.set_app_focus(True) client.select_workspace(tab1) - time.sleep(0.1) + if not wait_for_current_workspace(client, tab1): + result.failure("Expected original workspace to become selected again") + return result - items = client.list_notifications() + items = wait_for_notifications(client, 1) target = next((n for n in items if n["workspace_id"] == tab1), None) if target is None: result.failure("Expected notification for original tab") - elif not target["is_read"]: - result.failure("Expected notification to be marked read on tab switch") + elif target["is_read"]: + result.failure("Expected notification to remain unread on tab switch") else: - result.success("Notification marked read on tab switch") + result.success("Notification persisted across tab switch") except Exception as e: result.failure(f"Exception: {e}") return result @@ -371,11 +387,20 @@ def test_focus_on_notification_click(client: cmux) -> TestResult: result.failure("Expected notification surface to be focused") return result + items = client.list_notifications() + notification = next((n for n in items if n["surface_id"] == other[1]), None) + if notification is None: + result.failure("Expected notification to remain listed after notification click") + return result + if notification["is_read"]: + result.failure("Expected notification click to preserve unread state") + return result + count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) if count < 1: result.failure(f"Expected flash count >= 1, got {count}") else: - result.success("Notification click focuses and flashes panel") + result.success("Notification click focuses, flashes, and preserves unread state") except Exception as e: result.failure(f"Exception: {e}") return result @@ -455,9 +480,9 @@ def run_tests() -> int: results.append(test_kitty_notification_simple(client)) results.append(test_kitty_notification_chunked(client)) results.append(test_rxvt_notification_osc777(client)) - results.append(test_mark_read_on_focus_change(client)) - results.append(test_mark_read_on_app_active(client)) - results.append(test_mark_read_on_tab_switch(client)) + results.append(test_preserve_unread_on_focus_change(client)) + results.append(test_preserve_unread_on_app_active(client)) + results.append(test_preserve_unread_on_tab_switch(client)) results.append(test_flash_on_tab_switch(client)) results.append(test_focus_on_notification_click(client)) results.append(test_restore_focus_on_tab_switch(client)) From d99fa96c09406d11f67214091bc2c639bf641f3e Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:09:29 -0800 Subject: [PATCH 117/232] Fix browser screenshot to return image URL (#936) * Return browser screenshot image URL * Make screenshot path/url best effort * cli: omit screenshot png_base64 from json output * browser wait: fail fast on js errors and include screenshot in help * browser wait: avoid main-actor default world warning * tests: scope contentWorld regression check to function signature * browser screenshot: clean up output handling and tests * browser wait: resolve snapshot refs in selector waits --- CLAUDE.md | 6 + CLI/cmux.swift | 180 ++++++++- Sources/TerminalController.swift | 352 +++++++++++++----- .../test_browser_cli_wait_and_screenshot.py | 146 ++++++++ 4 files changed, 581 insertions(+), 103 deletions(-) create mode 100644 tests_v2/test_browser_cli_wait_and_screenshot.py diff --git a/CLAUDE.md b/CLAUDE.md index 5a244316..098b77ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,6 +121,12 @@ tail -f "$(cat /tmp/cmux-last-debug-log-path 2>/dev/null || echo /tmp/cmux-debug - **Submodule safety:** When modifying a submodule (ghostty, vendor/bonsplit, etc.), always push the submodule commit to its remote `main` branch BEFORE committing the updated pointer in the parent repo. Never commit on a detached HEAD or temporary branch — the commit will be orphaned and lost. Verify with: `cd && git merge-base --is-ancestor HEAD origin/main`. - **All user-facing strings must be localized.** Use `String(localized: "key.name", defaultValue: "English text")` for every string shown in the UI (labels, buttons, menus, dialogs, tooltips, error messages). Keys go in `Resources/Localizable.xcstrings` with translations for all supported languages (currently English and Japanese). Never use bare string literals in SwiftUI `Text()`, `Button()`, alert titles, etc. +## Test quality policy + +- Do not add tests that only verify source code text, method signatures, AST fragments, or grep-style patterns. +- Tests must verify observable runtime behavior through executable paths (unit/integration/e2e/CLI), not implementation shape. +- If a behavior cannot be exercised end-to-end yet, add a small runtime seam or harness first, then test through that seam. + ## Socket command threading policy - Do not use `DispatchQueue.main.sync` for high-frequency socket telemetry commands (`report_*`, `ports_kick`, status/progress/log metadata updates). diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 4475f341..2df4f56e 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -1736,6 +1736,45 @@ struct CMUXCLI { return (cwd as NSString).appendingPathComponent(expanded) } + private func sanitizedFilenameComponent(_ raw: String) -> String { + let sanitized = raw.replacingOccurrences( + of: #"[^\p{L}\p{N}._-]+"#, + with: "-", + options: .regularExpression + ) + let trimmed = sanitized.trimmingCharacters(in: CharacterSet(charactersIn: "-.")) + return trimmed.isEmpty ? "item" : trimmed + } + + private func bestEffortPruneTemporaryFiles( + in directoryURL: URL, + keepingMostRecent maxCount: Int = 50, + maxAge: TimeInterval = 24 * 60 * 60 + ) { + guard let entries = try? FileManager.default.contentsOfDirectory( + at: directoryURL, + includingPropertiesForKeys: [.isRegularFileKey, .contentModificationDateKey, .creationDateKey], + options: [.skipsHiddenFiles] + ) else { + return + } + + let now = Date() + let datedEntries = entries.compactMap { url -> (url: URL, date: Date)? in + guard let values = try? url.resourceValues(forKeys: [.isRegularFileKey, .contentModificationDateKey, .creationDateKey]), + values.isRegularFile == true else { + return nil + } + return (url, values.contentModificationDate ?? values.creationDate ?? .distantPast) + }.sorted { $0.date > $1.date } + + for (index, entry) in datedEntries.enumerated() { + if index >= maxCount || now.timeIntervalSince(entry.date) > maxAge { + try? FileManager.default.removeItem(at: entry.url) + } + } + } + // MARK: - Markdown Commands private func runMarkdownCommand( @@ -3052,17 +3091,139 @@ struct CMUXCLI { if subcommand == "screenshot" { let sid = try requireSurface() let (outPathOpt, _) = parseOption(subArgs, name: "--out") - let payload = try client.sendV2(method: "browser.screenshot", params: ["surface_id": sid]) - if let outPathOpt, - let b64 = payload["png_base64"] as? String, - let data = Data(base64Encoded: b64) { - try data.write(to: URL(fileURLWithPath: outPathOpt)) + let localJSONOutput = hasFlag(subArgs, name: "--json") + let outputAsJSON = jsonOutput || localJSONOutput + var payload = try client.sendV2(method: "browser.screenshot", params: ["surface_id": sid]) + + func fileURL(fromPath rawPath: String) -> URL { + let resolvedPath = resolvePath(rawPath) + return URL(fileURLWithPath: resolvedPath).standardizedFileURL } - if jsonOutput { - print(jsonString(formatIDs(payload, mode: idFormat))) + func writeScreenshot(_ data: Data, to destinationURL: URL) throws { + try FileManager.default.createDirectory( + at: destinationURL.deletingLastPathComponent(), + withIntermediateDirectories: true + ) + try data.write(to: destinationURL, options: .atomic) + } + + func hasText(_ value: String?) -> Bool { + guard let value else { return false } + return !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + + var screenshotPath = payload["path"] as? String + var screenshotURL = payload["url"] as? String + + func syncScreenshotLocationFields() { + if !hasText(screenshotPath), + let rawURL = screenshotURL, + let fileURL = URL(string: rawURL), + fileURL.isFileURL, + !fileURL.path.isEmpty { + screenshotPath = fileURL.path + } + if !hasText(screenshotURL), + let screenshotPath, + hasText(screenshotPath) { + screenshotURL = URL(fileURLWithPath: screenshotPath).standardizedFileURL.absoluteString + } + if let screenshotPath, hasText(screenshotPath) { + payload["path"] = screenshotPath + } + if let screenshotURL, hasText(screenshotURL) { + payload["url"] = screenshotURL + } + } + + func persistPayloadScreenshot(to destinationURL: URL, allowFailure: Bool) throws -> Bool { + if let sourcePath = screenshotPath, hasText(sourcePath) { + let sourceURL = URL(fileURLWithPath: sourcePath).standardizedFileURL + do { + if sourceURL.path != destinationURL.path { + try FileManager.default.createDirectory( + at: destinationURL.deletingLastPathComponent(), + withIntermediateDirectories: true + ) + try? FileManager.default.removeItem(at: destinationURL) + try FileManager.default.copyItem(at: sourceURL, to: destinationURL) + } + return true + } catch { + if payload["png_base64"] == nil { + if allowFailure { + return false + } + throw error + } + } + } + + if let b64 = payload["png_base64"] as? String, + let data = Data(base64Encoded: b64) { + do { + try writeScreenshot(data, to: destinationURL) + return true + } catch { + if allowFailure { + return false + } + throw error + } + } + + return false + } + + if let outPathOpt { + let outputURL = fileURL(fromPath: outPathOpt) + guard try persistPayloadScreenshot(to: outputURL, allowFailure: false) else { + throw CLIError(message: "browser screenshot missing image data") + } + screenshotPath = outputURL.path + screenshotURL = outputURL.absoluteString + payload["path"] = screenshotPath + payload["url"] = screenshotURL + } else { + syncScreenshotLocationFields() + if !hasText(screenshotPath) && !hasText(screenshotURL) { + let outputDir = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-browser-screenshots-cli", isDirectory: true) + if (try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true)) != nil { + bestEffortPruneTemporaryFiles(in: outputDir) + let timestampMs = Int(Date().timeIntervalSince1970 * 1000) + let safeSid = sanitizedFilenameComponent(sid) + let filename = "surface-\(safeSid)-\(timestampMs)-\(String(UUID().uuidString.prefix(8))).png" + let outputURL = outputDir.appendingPathComponent(filename, isDirectory: false) + if (try? persistPayloadScreenshot(to: outputURL, allowFailure: true)) == true { + screenshotPath = outputURL.path + screenshotURL = outputURL.absoluteString + payload["path"] = screenshotPath + payload["url"] = screenshotURL + } + } + } + } + + if outputAsJSON { + let formattedPayload = formatIDs(payload, mode: idFormat) + if var outputPayload = formattedPayload as? [String: Any] { + if hasText(screenshotPath) || hasText(screenshotURL) { + outputPayload.removeValue(forKey: "png_base64") + } + print(jsonString(outputPayload)) + } else { + print(jsonString(formattedPayload)) + } } else if let outPathOpt { print("OK \(outPathOpt)") + } else if let screenshotURL, + hasText(screenshotURL) { + print("OK \(screenshotURL)") + } else if let screenshotPath, + hasText(screenshotPath) { + print("OK \(screenshotPath)") } else { print("OK") } @@ -5511,8 +5672,10 @@ struct CMUXCLI { } private func jsonString(_ object: Any) -> String { + var options: JSONSerialization.WritingOptions = [.prettyPrinted] + options.insert(.withoutEscapingSlashes) guard JSONSerialization.isValidJSONObject(object), - let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]), + let data = try? JSONSerialization.data(withJSONObject: object, options: options), let output = String(data: data, encoding: .utf8) else { return "{}" } @@ -6797,6 +6960,7 @@ struct CMUXCLI { browser press|keydown|keyup [--snapshot-after] browser select [--snapshot-after] browser scroll [--selector ] [--dx ] [--dy ] [--snapshot-after] + browser screenshot [--out ] [--json] browser get [...] browser is browser find ... diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 56d7205b..5d905129 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -5284,41 +5284,70 @@ class TerminalController { _ webView: WKWebView, script: String, timeout: TimeInterval = 5.0, - preferAsync: Bool = false + preferAsync: Bool = false, + contentWorld: WKContentWorld ) -> V2JavaScriptResult { + let timeoutSeconds = max(0.01, timeout) + let resultLock = NSLock() + let completionSignal = DispatchSemaphore(value: 0) var done = false var resultValue: Any? var resultError: String? - if preferAsync, #available(macOS 11.0, *) { - webView.callAsyncJavaScript(script, arguments: [:], in: nil, in: .page) { result in - switch result { - case .success(let value): - resultValue = value - case .failure(let error): - resultError = error.localizedDescription - } + let finish: (_ value: Any?, _ error: String?) -> Void = { value, error in + resultLock.lock() + if !done { done = true + resultValue = value + resultError = error + completionSignal.signal() + } + resultLock.unlock() + } + + let evaluator = { + if preferAsync, #available(macOS 11.0, *) { + webView.callAsyncJavaScript(script, arguments: [:], in: nil, in: contentWorld) { result in + switch result { + case .success(let value): + finish(value, nil) + case .failure(let error): + finish(nil, error.localizedDescription) + } + } + } else { + webView.evaluateJavaScript(script) { value, error in + if let error { + finish(nil, error.localizedDescription) + } else { + finish(value, nil) + } + } + } + } + + if Thread.isMainThread { + evaluator() + let deadline = Date().addingTimeInterval(timeoutSeconds) + while true { + resultLock.lock() + let isDone = done + resultLock.unlock() + if isDone { + break + } + if Date() >= deadline { + return .failure("Timed out waiting for JavaScript result") + } + _ = RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.01)) } } else { - webView.evaluateJavaScript(script) { value, error in - if let error { - resultError = error.localizedDescription - } else { - resultValue = value - } - done = true + DispatchQueue.main.async(execute: evaluator) + if completionSignal.wait(timeout: .now() + timeoutSeconds) == .timedOut { + return .failure("Timed out waiting for JavaScript result") } } - let deadline = Date().addingTimeInterval(timeout) - while !done && Date() < deadline { - _ = RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.01)) - } - - if !done { - return .failure("Timed out waiting for JavaScript result") - } if let resultError { return .failure(resultError) } @@ -5368,7 +5397,8 @@ class TerminalController { _ webView: WKWebView, surfaceId: UUID, script: String, - timeout: TimeInterval = 5.0 + timeout: TimeInterval = 5.0, + useEval: Bool = true ) -> V2JavaScriptResult { let scriptLiteral = v2JSONLiteral(script) let framePrelude: String @@ -5387,6 +5417,13 @@ class TerminalController { framePrelude = "const __cmuxDoc = document;" } + let executionBlock: String + if useEval { + executionBlock = "const __r = eval(\(scriptLiteral));" + } else { + executionBlock = "const __r = \(script);" + } + let asyncFunctionBody = """ \(framePrelude) @@ -5399,7 +5436,7 @@ class TerminalController { const __cmuxEvalInFrame = async function() { const document = __cmuxDoc; - const __r = eval(\(scriptLiteral)); + \(executionBlock) const __value = await __cmuxMaybeAwait(__r); return { __cmux_t: (typeof __value === 'undefined') ? 'undefined' : 'value', @@ -5410,16 +5447,40 @@ class TerminalController { return await __cmuxEvalInFrame(); """ - let rawResult: V2JavaScriptResult + var rawResult: V2JavaScriptResult if #available(macOS 11.0, *) { - rawResult = v2RunJavaScript(webView, script: asyncFunctionBody, timeout: timeout, preferAsync: true) + rawResult = v2RunJavaScript( + webView, + script: asyncFunctionBody, + timeout: timeout, + preferAsync: true, + contentWorld: .page + ) } else { let evaluateFallback = """ (async () => { \(asyncFunctionBody) })() """ - rawResult = v2RunJavaScript(webView, script: evaluateFallback, timeout: timeout) + rawResult = v2RunJavaScript(webView, script: evaluateFallback, timeout: timeout, contentWorld: .page) + } + + if !useEval, case .failure(let pageMessage) = rawResult, #available(macOS 11.0, *) { + let isolatedResult = v2RunJavaScript( + webView, + script: asyncFunctionBody, + timeout: timeout, + preferAsync: true, + contentWorld: .defaultClient + ) + switch isolatedResult { + case .success: + rawResult = isolatedResult + case .failure(let isolatedMessage): + if isolatedMessage != pageMessage { + rawResult = .failure("\(pageMessage) (isolated-world retry: \(isolatedMessage))") + } + } } switch rawResult { @@ -5520,38 +5581,41 @@ class TerminalController { } } - private func v2BrowserWaitForCondition( - _ conditionScript: String, - webView: WKWebView, - surfaceId: UUID? = nil, - timeout: TimeInterval = 5.0, - pollInterval: TimeInterval = 0.05 - ) -> Bool { - let deadline = Date().addingTimeInterval(timeout) - while Date() < deadline { - let wrapped = "(() => { try { return !!(\(conditionScript)); } catch (_) { return false; } })()" - let jsResult: V2JavaScriptResult - if let surfaceId { - jsResult = v2RunBrowserJavaScript(webView, surfaceId: surfaceId, script: wrapped, timeout: max(0.5, pollInterval + 0.25)) - } else { - jsResult = v2RunJavaScript(webView, script: wrapped, timeout: max(0.5, pollInterval + 0.25)) - } - if case let .success(value) = jsResult, - let ok = value as? Bool, - ok { - return true - } - _ = RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(pollInterval)) - } - return false - } - private func v2PNGData(from image: NSImage) -> Data? { guard let tiff = image.tiffRepresentation, let rep = NSBitmapImageRep(data: tiff) else { return nil } return rep.representation(using: .png, properties: [:]) } + private func bestEffortPruneTemporaryFiles( + in directoryURL: URL, + keepingMostRecent maxCount: Int = 50, + maxAge: TimeInterval = 24 * 60 * 60 + ) { + guard let entries = try? FileManager.default.contentsOfDirectory( + at: directoryURL, + includingPropertiesForKeys: [.isRegularFileKey, .contentModificationDateKey, .creationDateKey], + options: [.skipsHiddenFiles] + ) else { + return + } + + let now = Date() + let datedEntries = entries.compactMap { url -> (url: URL, date: Date)? in + guard let values = try? url.resourceValues(forKeys: [.isRegularFileKey, .contentModificationDateKey, .creationDateKey]), + values.isRegularFile == true else { + return nil + } + return (url, values.contentModificationDate ?? values.creationDate ?? .distantPast) + }.sorted { $0.date > $1.date } + + for (index, entry) in datedEntries.enumerated() { + if index >= maxCount || now.timeIntervalSince(entry.date) > maxAge { + try? FileManager.default.removeItem(at: entry.url) + } + } + } + // MARK: - Markdown private func v2MarkdownOpen(params: [String: Any]) -> V2CallResult { @@ -5972,7 +6036,7 @@ class TerminalController { let retryAttempts = max(1, v2Int(params, "retry_attempts") ?? 3) for attempt in 1...retryAttempts { - switch v2RunBrowserJavaScript(browserPanel.webView, surfaceId: surfaceId, script: script) { + switch v2RunBrowserJavaScript(browserPanel.webView, surfaceId: surfaceId, script: script, useEval: false) { case .failure(let message): return .err(code: "js_error", message: message, data: ["action": actionName, "selector": selector]) case .success(let value): @@ -6230,7 +6294,7 @@ class TerminalController { })() """ - switch v2RunBrowserJavaScript(browserPanel.webView, surfaceId: surfaceId, script: script, timeout: 10.0) { + switch v2RunBrowserJavaScript(browserPanel.webView, surfaceId: surfaceId, script: script, timeout: 10.0, useEval: false) { case .failure(let message): return .err(code: "js_error", message: message, data: nil) case .success(let value): @@ -6327,42 +6391,120 @@ class TerminalController { private func v2BrowserWait(params: [String: Any]) -> V2CallResult { let timeoutMs = max(1, v2Int(params, "timeout_ms") ?? 5_000) let timeout = Double(timeoutMs) / 1000.0 + let selectorRaw = v2BrowserSelector(params) - return v2BrowserWithPanel(params: params) { _, ws, surfaceId, browserPanel in - let conditionScript: String = { - if let selector = v2BrowserSelector(params) { - let literal = v2JSONLiteral(selector) - return "document.querySelector(\(literal)) !== null" + let conditionScriptBase: String = { + if let urlContains = v2String(params, "url_contains") { + let literal = v2JSONLiteral(urlContains) + return "String(location.href || '').includes(\(literal))" + } + if let textContains = v2String(params, "text_contains") { + let literal = v2JSONLiteral(textContains) + return "(document.body && String(document.body.innerText || '').includes(\(literal)))" + } + if let loadState = v2String(params, "load_state") { + let normalizedLoadState = loadState.lowercased() + if normalizedLoadState == "interactive" { + return """ + (() => { + const __state = String(document.readyState || '').toLowerCase(); + return __state === 'interactive' || __state === 'complete'; + })() + """ } - if let urlContains = v2String(params, "url_contains") { - let literal = v2JSONLiteral(urlContains) - return "String(location.href || '').includes(\(literal))" - } - if let textContains = v2String(params, "text_contains") { - let literal = v2JSONLiteral(textContains) - return "(document.body && String(document.body.innerText || '').includes(\(literal)))" - } - if let loadState = v2String(params, "load_state") { - let literal = v2JSONLiteral(loadState.lowercased()) - return "String(document.readyState || '').toLowerCase() === \(literal)" - } - if let fn = v2String(params, "function") { - return "(() => { return !!(\(fn)); })()" - } - return "document.readyState === 'complete'" - }() + let literal = v2JSONLiteral(normalizedLoadState) + return "String(document.readyState || '').toLowerCase() === \(literal)" + } + if let fn = v2String(params, "function") { + return "(() => { return !!(\(fn)); })()" + } + return "document.readyState === 'complete'" + }() - let ok = v2BrowserWaitForCondition(conditionScript, webView: browserPanel.webView, surfaceId: surfaceId, timeout: timeout) - if !ok { + var setupResult: V2CallResult? + var workspaceId: UUID? + var surfaceIdOut: UUID? + var webView: WKWebView? + + v2MainSync { + guard let tabManager = self.v2ResolveTabManager(params: params) else { + setupResult = .err(code: "unavailable", message: "TabManager not available", data: nil) + return + } + guard let ws = self.v2ResolveWorkspace(params: params, tabManager: tabManager) else { + setupResult = .err(code: "not_found", message: "Workspace not found", data: nil) + return + } + let surfaceId = self.v2UUID(params, "surface_id") ?? ws.focusedPanelId + guard let surfaceId else { + setupResult = .err(code: "not_found", message: "No focused browser surface", data: nil) + return + } + guard let browserPanel = ws.browserPanel(for: surfaceId) else { + setupResult = .err(code: "invalid_params", message: "Surface is not a browser", data: ["surface_id": surfaceId.uuidString]) + return + } + workspaceId = ws.id + surfaceIdOut = surfaceId + webView = browserPanel.webView + } + + if let setupResult { + return setupResult + } + guard let workspaceId, let surfaceIdOut, let webView else { + return .err(code: "internal_error", message: "Failed to resolve browser surface", data: nil) + } + + let conditionScript: String + if let selectorRaw { + guard let selector = v2BrowserResolveSelector(selectorRaw, surfaceId: surfaceIdOut) else { + return .err(code: "not_found", message: "Element reference not found", data: ["selector": selectorRaw]) + } + let literal = v2JSONLiteral(selector) + conditionScript = "document.querySelector(\(literal)) !== null" + } else { + conditionScript = conditionScriptBase + } + + let deadline = Date().addingTimeInterval(timeout) + let pollInterval = 0.05 + let wrappedScript = "(() => { try { return !!(\(conditionScript)); } catch (_) { return false; } })()" + + while true { + switch v2RunBrowserJavaScript( + webView, + surfaceId: surfaceIdOut, + script: wrappedScript, + timeout: max(0.5, pollInterval + 0.25), + useEval: false + ) { + case .success(let value): + if let b = value as? Bool, b { + return .ok([ + "workspace_id": workspaceId.uuidString, + "workspace_ref": self.v2Ref(kind: .workspace, uuid: workspaceId), + "surface_id": surfaceIdOut.uuidString, + "surface_ref": self.v2Ref(kind: .surface, uuid: surfaceIdOut), + "waited": true + ]) + } + case .failure(let message): + return .err( + code: "js_error", + message: message, + data: [ + "condition": conditionScript, + "timeout_ms": timeoutMs + ] + ) + } + + if Date() >= deadline { return .err(code: "timeout", message: "Condition not met before timeout", data: ["timeout_ms": timeoutMs]) } - return .ok([ - "workspace_id": ws.id.uuidString, - "workspace_ref": v2Ref(kind: .workspace, uuid: ws.id), - "surface_id": surfaceId.uuidString, - "surface_ref": v2Ref(kind: .surface, uuid: surfaceId), - "waited": true - ]) + + Thread.sleep(forTimeInterval: pollInterval) } } @@ -6707,13 +6849,31 @@ class TerminalController { return .err(code: "internal_error", message: "Failed to capture snapshot", data: nil) } - return .ok([ + var result: [String: Any] = [ "workspace_id": ws.id.uuidString, "workspace_ref": v2Ref(kind: .workspace, uuid: ws.id), "surface_id": surfaceId.uuidString, "surface_ref": v2Ref(kind: .surface, uuid: surfaceId), "png_base64": imageData.base64EncodedString() - ]) + ] + + // Best effort: keep screenshot data available even when temp-file writes fail. + let screenshotsDirectory = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-browser-screenshots", isDirectory: true) + if (try? FileManager.default.createDirectory(at: screenshotsDirectory, withIntermediateDirectories: true)) != nil { + bestEffortPruneTemporaryFiles(in: screenshotsDirectory) + let timestampMs = Int(Date().timeIntervalSince1970 * 1000) + let shortSurfaceId = String(surfaceId.uuidString.prefix(8)) + let shortRandomId = String(UUID().uuidString.prefix(8)) + let filename = "surface-\(shortSurfaceId)-\(timestampMs)-\(shortRandomId).png" + let imageURL = screenshotsDirectory.appendingPathComponent(filename, isDirectory: false) + if (try? imageData.write(to: imageURL, options: .atomic)) != nil { + result["path"] = imageURL.path + result["url"] = imageURL.absoluteString + } + } + + return .ok(result) } } @@ -7543,7 +7703,8 @@ class TerminalController { _ = v2RunJavaScript( browserPanel.webView, script: BrowserPanel.telemetryHookBootstrapScriptSource, - timeout: 5.0 + timeout: 5.0, + contentWorld: .page ) } @@ -7551,7 +7712,8 @@ class TerminalController { _ = v2RunJavaScript( browserPanel.webView, script: BrowserPanel.dialogTelemetryHookBootstrapScriptSource, - timeout: 5.0 + timeout: 5.0, + contentWorld: .page ) } @@ -7583,7 +7745,7 @@ class TerminalController { })() """ - switch v2RunJavaScript(browserPanel.webView, script: script, timeout: 5.0) { + switch v2RunJavaScript(browserPanel.webView, script: script, timeout: 5.0, contentWorld: .page) { case .failure(let message): return .err(code: "js_error", message: message, data: nil) case .success(let value): @@ -8178,7 +8340,7 @@ class TerminalController { return { ok: true, items }; })() """ - switch v2RunJavaScript(browserPanel.webView, script: script, timeout: 5.0) { + switch v2RunJavaScript(browserPanel.webView, script: script, timeout: 5.0, contentWorld: .page) { case .failure(let message): return .err(code: "js_error", message: message, data: nil) case .success(let value): @@ -8216,7 +8378,7 @@ class TerminalController { return { ok: true, items }; })() """ - switch v2RunJavaScript(browserPanel.webView, script: script, timeout: 5.0) { + switch v2RunJavaScript(browserPanel.webView, script: script, timeout: 5.0, contentWorld: .page) { case .failure(let message): return .err(code: "js_error", message: message, data: nil) case .success(let value): diff --git a/tests_v2/test_browser_cli_wait_and_screenshot.py b/tests_v2/test_browser_cli_wait_and_screenshot.py new file mode 100644 index 00000000..fb4d2fb7 --- /dev/null +++ b/tests_v2/test_browser_cli_wait_and_screenshot.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Regression: browser wait/snapshot and screenshot CLI return usable file locations.""" + +import glob +import json +import os +import subprocess +import sys +import tempfile +import urllib.parse +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmux, cmuxError + + +SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def _find_cli_binary() -> str: + env_cli = os.environ.get("CMUXTERM_CLI") + if env_cli and os.path.isfile(env_cli) and os.access(env_cli, os.X_OK): + return env_cli + + fixed = os.path.expanduser( + "~/Library/Developer/Xcode/DerivedData/cmux-tests-v2/Build/Products/Debug/cmux" + ) + if os.path.isfile(fixed) and os.access(fixed, os.X_OK): + return fixed + + candidates = glob.glob( + os.path.expanduser("~/Library/Developer/Xcode/DerivedData/**/Build/Products/Debug/cmux"), + recursive=True, + ) + candidates += glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux") + candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)] + if not candidates: + raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI") + candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True) + return candidates[0] + + +def _run_cli(cli: str, *args: str) -> subprocess.CompletedProcess[str]: + cmd = [cli, "--socket", SOCKET_PATH, *args] + proc = subprocess.run(cmd, capture_output=True, text=True, check=False) + if proc.returncode != 0: + merged = f"{proc.stdout}\n{proc.stderr}".strip() + raise cmuxError(f"CLI failed ({' '.join(cmd)}): {merged}") + return proc + + +def main() -> int: + cli = _find_cli_binary() + + with cmux(SOCKET_PATH) as c: + opened = c._call("browser.open_split", {"url": "about:blank"}) or {} + target = str(opened.get("surface_id") or opened.get("surface_ref") or "") + _must(target != "", f"browser.open_split returned no surface handle: {opened}") + + html = """ + + + cmux-browser-cli-regression + +
+

browser cli regression

+

ready

+
+ + +""".strip() + data_url = "data:text/html;charset=utf-8," + urllib.parse.quote(html) + c._call("browser.navigate", {"surface_id": target, "url": data_url}) + + wait_proc = _run_cli( + cli, + "browser", + target, + "wait", + "--load-state", + "interactive", + "--timeout-ms", + "5000", + ) + _must(wait_proc.stdout.strip() == "OK", f"Expected browser wait OK output: {wait_proc.stdout!r}") + + snapshot_payload = c._call("browser.snapshot", {"surface_id": target}) or {} + refs = snapshot_payload.get("refs") or {} + _must(isinstance(refs, dict) and len(refs) > 0, f"Expected snapshot refs for ref-based wait coverage: {snapshot_payload}") + ref_selector = str(next(iter(refs.keys()))) + ref_wait_proc = _run_cli( + cli, + "browser", + target, + "wait", + "--selector", + ref_selector, + "--timeout-ms", + "2000", + ) + _must(ref_wait_proc.stdout.strip() == "OK", f"Expected browser wait to resolve snapshot refs: {ref_wait_proc.stdout!r}") + + snapshot_proc = _run_cli(cli, "browser", target, "snapshot", "--compact") + _must( + snapshot_proc.stdout.strip().startswith("- document"), + f"Expected snapshot command to succeed with structured output: {snapshot_proc.stdout!r}", + ) + + screenshot_json_proc = _run_cli(cli, "browser", target, "screenshot", "--json") + screenshot_json_text = screenshot_json_proc.stdout.strip() + payload = json.loads(screenshot_json_text or "{}") + + _must("\\/" not in screenshot_json_text, f"Expected screenshot JSON without escaped slashes: {screenshot_json_text!r}") + _must("png_base64" not in payload, f"Expected screenshot JSON to omit png_base64 when file location is available: {payload}") + + screenshot_path = str(payload.get("path") or "") + screenshot_url = str(payload.get("url") or "") + _must(screenshot_path.startswith("/"), f"Expected screenshot path in JSON payload: {payload}") + _must(screenshot_url.startswith("file://"), f"Expected screenshot file URL in JSON payload: {payload}") + _must(Path(screenshot_path).is_file(), f"Expected screenshot file to exist: {payload}") + + out_dir = Path(tempfile.mkdtemp(prefix="cmux-browser-screenshot-cli-")) / "nested" / "dir" + out_path = out_dir / "capture.png" + screenshot_out_proc = _run_cli( + cli, + "browser", + target, + "screenshot", + "--out", + str(out_path), + ) + _must(screenshot_out_proc.stdout.strip() == f"OK {out_path}", f"Expected --out to print the requested path: {screenshot_out_proc.stdout!r}") + _must("file://" not in screenshot_out_proc.stdout, f"Expected --out to print a path, not a file URL: {screenshot_out_proc.stdout!r}") + _must(out_path.is_file(), f"Expected --out screenshot file to exist: {out_path}") + + print("PASS: browser CLI wait/snapshot and screenshot output work end-to-end") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 2b99af19bcefb5210e65fcb1c2ef60e331efab49 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:18:01 -0800 Subject: [PATCH 118/232] Harden multi-window notify regression test --- Sources/AppDelegate.swift | 69 +++++--- .../MultiWindowNotificationsUITests.swift | 155 +++++++++++------- 2 files changed, 137 insertions(+), 87 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index f74e65db..21569100 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5683,6 +5683,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + func waitForSurfaceId( + on tabManager: TabManager, + tabId: UUID, + _ completion: @escaping (UUID) -> Void + ) { + if let surfaceId = tabManager.focusedPanelId(for: tabId) { + completion(surfaceId) + return + } + guard Date() < deadline else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + waitForSurfaceId(on: tabManager, tabId: tabId, completion) + } + } + waitForContexts(minCount: 1) { [weak self] in guard let self else { return } guard let window1 = self.mainWindowContexts.values.first else { return } @@ -5696,36 +5711,40 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let contexts = Array(self.mainWindowContexts.values) guard let window2 = contexts.first(where: { $0.windowId != window1.windowId }) else { return } guard let tabId2 = window2.tabManager.selectedTabId ?? window2.tabManager.tabs.first?.id else { return } - guard let store = self.notificationStore else { return } + waitForSurfaceId(on: window2.tabManager, tabId: tabId2) { [weak self] surfaceId2 in + guard let self else { return } + guard let store = self.notificationStore else { return } - // Ensure the target window is currently showing the Notifications overlay, - // so opening a notification must switch it back to the terminal UI. - window2.sidebarSelectionState.selection = .notifications + // Ensure the target window is currently showing the Notifications overlay, + // so opening a notification must switch it back to the terminal UI. + window2.sidebarSelectionState.selection = .notifications - // Create notifications for both windows. Ensure W2 isn't suppressed just because it's focused. - let prevOverride = AppFocusState.overrideIsFocused - AppFocusState.overrideIsFocused = false - store.addNotification(tabId: tabId2, surfaceId: nil, title: "W2", subtitle: "multiwindow", body: "") - AppFocusState.overrideIsFocused = prevOverride + // Create notifications for both windows. Ensure W2 isn't suppressed just because it's focused. + let prevOverride = AppFocusState.overrideIsFocused + AppFocusState.overrideIsFocused = false + store.addNotification(tabId: tabId2, surfaceId: nil, title: "W2", subtitle: "multiwindow", body: "") + AppFocusState.overrideIsFocused = prevOverride - // Insert after W2 so it becomes "latest unread" (first in list). - store.addNotification(tabId: tabId1, surfaceId: nil, title: "W1", subtitle: "multiwindow", body: "") + // Insert after W2 so it becomes "latest unread" (first in list). + store.addNotification(tabId: tabId1, surfaceId: nil, title: "W1", subtitle: "multiwindow", body: "") - let notif1 = store.notifications.first(where: { $0.tabId == tabId1 && $0.title == "W1" }) - let notif2 = store.notifications.first(where: { $0.tabId == tabId2 && $0.title == "W2" }) + let notif1 = store.notifications.first(where: { $0.tabId == tabId1 && $0.title == "W1" }) + let notif2 = store.notifications.first(where: { $0.tabId == tabId2 && $0.title == "W2" }) - self.writeMultiWindowNotificationTestData([ - "window1Id": window1.windowId.uuidString, - "window2Id": window2.windowId.uuidString, - "window2InitialSidebarSelection": "notifications", - "tabId1": tabId1.uuidString, - "tabId2": tabId2.uuidString, - "notifId1": notif1?.id.uuidString ?? "", - "notifId2": notif2?.id.uuidString ?? "", - "expectedLatestWindowId": window1.windowId.uuidString, - "expectedLatestTabId": tabId1.uuidString, - ], at: path) - self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) + self.writeMultiWindowNotificationTestData([ + "window1Id": window1.windowId.uuidString, + "window2Id": window2.windowId.uuidString, + "window2InitialSidebarSelection": "notifications", + "tabId1": tabId1.uuidString, + "tabId2": tabId2.uuidString, + "surfaceId2": surfaceId2.uuidString, + "notifId1": notif1?.id.uuidString ?? "", + "notifId2": notif2?.id.uuidString ?? "", + "expectedLatestWindowId": window1.windowId.uuidString, + "expectedLatestTabId": tabId1.uuidString, + ], at: path) + self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) + } } } } diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 966d060f..f42aba29 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -209,8 +209,9 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertTrue( waitForDataMatch(timeout: 20.0) { data in let tabId2 = data["tabId2"] ?? "" + let surfaceId2 = data["surfaceId2"] ?? "" let socketReady = data["socketReady"] ?? "" - return !tabId2.isEmpty && !socketReady.isEmpty && socketReady != "pending" + return !tabId2.isEmpty && !surfaceId2.isEmpty && !socketReady.isEmpty && socketReady != "pending" }, "Expected multi-window notification setup data and socket readiness" ) @@ -233,34 +234,20 @@ final class MultiWindowNotificationsUITests: XCTestCase { ) return } + guard setup["socketPingResponse"] == "PONG" else { + XCTFail( + "Control socket ping sanity check failed. path=\(socketPath) " + + socketDiagnostics(from: setup) + ) + return + } + guard let surfaceId = setup["surfaceId2"], !surfaceId.isEmpty else { + XCTFail("Missing target surface id for workspace \(tabId2)") + return + } XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) - let pingResponse = waitForSocketPong(timeout: 20.0) - if pingResponse != "PONG", - let resolvedPath = resolveSocketPath(timeout: 5.0, requiredWorkspaceId: tabId2) { - socketPath = resolvedPath - } - - let confirmedPingResponse = pingResponse == "PONG" ? pingResponse : waitForSocketPong(timeout: 5.0) - guard confirmedPingResponse == "PONG" else { - let failureSetup = loadData() ?? setup - XCTFail( - "Control socket did not respond in time. path=\(socketPath) response=\(confirmedPingResponse ?? "") " + - socketDiagnostics(from: failureSetup) - ) - return - } - - guard let surfaceId = waitForSurfaceId(forWorkspaceId: tabId2, timeout: 12.0) else { - let failureSetup = loadData() ?? setup - XCTFail( - "Expected at least one surface in workspace \(tabId2). socket=\(socketPath) " + - socketDiagnostics(from: failureSetup) - ) - return - } - let finder = XCUIApplication(bundleIdentifier: "com.apple.finder") finder.activate() XCTAssertTrue( @@ -275,14 +262,26 @@ final class MultiWindowNotificationsUITests: XCTestCase { surfaceId: surfaceId, title: title ) - XCTAssertEqual(notifyResult.terminationStatus, 0, "Expected `cmux notify` to succeed. stderr=\(notifyResult.stderr)") - XCTAssertTrue(notifyResult.stdout.contains("OK"), "Expected notify command to return OK. stdout=\(notifyResult.stdout)") - RunLoop.current.run(until: Date().addingTimeInterval(0.5)) XCTAssertFalse( app.state == .runningForeground, - "Expected cmux to remain in background after `cmux notify`. state=\(app.state.rawValue)" + "Expected cmux to remain in background after bundled `cmux notify`. state=\(app.state.rawValue) stderr=\(notifyResult.stderr)" ) + + guard notifyResult.terminationStatus == 0 else { + let rawFallbackResponse: String? + if isSocketPermissionFailure(notifyResult.stderr) { + rawFallbackResponse = socketCommand("notify_target \(tabId2) \(surfaceId) \(title)|ui-test|focus-regression") + } else { + rawFallbackResponse = nil + } + XCTFail( + "Expected bundled `cmux notify` to succeed. stderr=\(notifyResult.stderr) " + + "rawFallback=\(rawFallbackResponse ?? "")" + ) + return + } + XCTAssertTrue(notifyResult.stdout.contains("OK"), "Expected notify command to return OK. stdout=\(notifyResult.stdout)") } private func clickNotificationPopoverRowAndWaitForFocusChange( @@ -556,7 +555,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { surfaceId: String, title: String ) -> (terminationStatus: Int32, stdout: String, stderr: String) { - let result = runCmuxCommand( + runCmuxCommand( socketPath: socketPath, arguments: [ "notify", @@ -571,35 +570,33 @@ final class MultiWindowNotificationsUITests: XCTestCase { "--body", "focus-regression" ], - responseTimeoutSeconds: 4.0 - ) - if result.terminationStatus == 0 || !isSocketPermissionFailure(result.stderr) { - return result - } - - let response = socketCommand("notify_target \(workspaceId) \(surfaceId) \(title)|ui-test|focus-regression") ?? "" - let succeeded = response == "OK" - return ( - terminationStatus: succeeded ? 0 : 1, - stdout: response, - stderr: succeeded - ? "\(result.stderr) raw-socket-fallback" - : "\(result.stderr) raw-socket-fallback-response=\(response)" + responseTimeoutSeconds: 4.0, + cliStrategy: .bundledOnly ) } private func runCmuxCommand( socketPath: String, arguments: [String], - responseTimeoutSeconds: Double = 3.0 + responseTimeoutSeconds: Double = 3.0, + cliStrategy: CmuxCLIStrategy = .any ) -> (terminationStatus: Int32, stdout: String, stderr: String) { var args = ["--socket", socketPath] args.append(contentsOf: arguments) var environment = ProcessInfo.processInfo.environment environment["CMUXTERM_CLI_RESPONSE_TIMEOUT_SEC"] = String(responseTimeoutSeconds) + let cliPaths = resolveCmuxCLIPaths(strategy: cliStrategy) + if cliPaths.isEmpty, cliStrategy == .bundledOnly { + return ( + terminationStatus: -1, + stdout: "", + stderr: "Failed to locate bundled cmux CLI" + ) + } + var lastPermissionFailure: (terminationStatus: Int32, stdout: String, stderr: String)? - for cliPath in resolveCmuxCLIPaths() { + for cliPath in cliPaths { let result = executeCmuxCommand( executablePath: cliPath, arguments: args, @@ -615,6 +612,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { return result } + if cliStrategy == .bundledOnly { + return lastPermissionFailure ?? ( + terminationStatus: -1, + stdout: "", + stderr: "Bundled cmux CLI command failed without an executable path" + ) + } + let fallbackArgs = ["cmux"] + args let fallbackResult = executeCmuxCommand( executablePath: "/usr/bin/env", @@ -627,6 +632,11 @@ final class MultiWindowNotificationsUITests: XCTestCase { return lastPermissionFailure ?? fallbackResult } + private enum CmuxCLIStrategy: Equatable { + case any + case bundledOnly + } + private func socketDiagnostics(from data: [String: String]) -> String { let pingResponse = data["socketPingResponse"].flatMap { $0.isEmpty ? nil : $0 } ?? "" return "mode=\(data["socketMode"] ?? "") running=\(data["socketIsRunning"] ?? "") " + @@ -635,15 +645,17 @@ final class MultiWindowNotificationsUITests: XCTestCase { "signals=\(data["socketFailureSignals"] ?? "")" } - private func resolveCmuxCLIPaths() -> [String] { + private func resolveCmuxCLIPaths(strategy: CmuxCLIStrategy) -> [String] { let fileManager = FileManager.default let env = ProcessInfo.processInfo.environment var candidates: [String] = [] var productDirectories: [String] = [] - for key in ["CMUX_UI_TEST_CLI_PATH", "CMUXTERM_CLI"] { - if let value = env[key], !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - candidates.append(value) + if strategy == .any { + for key in ["CMUX_UI_TEST_CLI_PATH", "CMUXTERM_CLI"] { + if let value = env[key], !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + candidates.append(value) + } } } @@ -664,12 +676,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { productDirectories.append(contentsOf: inferredBuildProductsDirectories()) for productsDir in uniquePaths(productDirectories) { - appendCLIPathCandidates(fromProductsDirectory: productsDir, to: &candidates) + appendCLIPathCandidates(fromProductsDirectory: productsDir, strategy: strategy, to: &candidates) } candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux DEV.app/Contents/Resources/bin/cmux") candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux.app/Contents/Resources/bin/cmux") - candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux") + if strategy == .any { + candidates.append("/tmp/cmux-\(launchTag)/Build/Products/Debug/cmux") + } var resolvedPaths: [String] = [] for path in uniquePaths(candidates) { @@ -697,21 +711,21 @@ final class MultiWindowNotificationsUITests: XCTestCase { } } - private func appendCLIPathCandidates(fromProductsDirectory productsDir: String, to candidates: inout [String]) { - candidates.append("\(productsDir)/cmux") + private func appendCLIPathCandidates( + fromProductsDirectory productsDir: String, + strategy: CmuxCLIStrategy, + to candidates: inout [String] + ) { candidates.append("\(productsDir)/cmux DEV.app/Contents/Resources/bin/cmux") candidates.append("\(productsDir)/cmux.app/Contents/Resources/bin/cmux") + if strategy == .any { + candidates.append("\(productsDir)/cmux") + } guard let entries = try? FileManager.default.contentsOfDirectory(atPath: productsDir) else { return } - for entry in entries.sorted() where entry == "cmux" { - let cliPath = URL(fileURLWithPath: productsDir) - .appendingPathComponent(entry) - .path - candidates.append(cliPath) - } for entry in entries.sorted() where entry.hasSuffix(".app") { let cliPath = URL(fileURLWithPath: productsDir) .appendingPathComponent(entry) @@ -719,6 +733,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { .path candidates.append(cliPath) } + if strategy == .any { + for entry in entries.sorted() where entry == "cmux" { + let cliPath = URL(fileURLWithPath: productsDir) + .appendingPathComponent(entry) + .path + candidates.append(cliPath) + } + } } private func executeCmuxCommand( @@ -991,9 +1013,18 @@ final class MultiWindowNotificationsUITests: XCTestCase { } guard wrote else { return nil } + let deadline = Date().addingTimeInterval(2.0) var buf = [UInt8](repeating: 0, count: 4096) var accum = "" - while true { + while Date() < deadline { + var pollDescriptor = pollfd(fd: fd, events: Int16(POLLIN), revents: 0) + let ready = poll(&pollDescriptor, 1, 100) + if ready < 0 { + return nil + } + if ready == 0 { + continue + } let n = read(fd, &buf, buf.count) if n <= 0 { break } if let chunk = String(bytes: buf[0.. Date: Thu, 5 Mar 2026 19:26:08 -0800 Subject: [PATCH 119/232] Reset surface wait timeout in notify UI setup --- Sources/AppDelegate.swift | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 21569100..645c4e4e 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5670,14 +5670,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent try? FileManager.default.removeItem(atPath: path) - let deadline = Date().addingTimeInterval(8.0) + let contextDeadline = Date().addingTimeInterval(8.0) func waitForContexts(minCount: Int, _ completion: @escaping () -> Void) { if mainWindowContexts.count >= minCount, mainWindowContexts.values.allSatisfy({ $0.window != nil }) { completion() return } - guard Date() < deadline else { return } + guard Date() < contextDeadline else { return } DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { waitForContexts(minCount: minCount, completion) } @@ -5686,16 +5686,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent func waitForSurfaceId( on tabManager: TabManager, tabId: UUID, + timeout: TimeInterval = 8.0, _ completion: @escaping (UUID) -> Void ) { - if let surfaceId = tabManager.focusedPanelId(for: tabId) { - completion(surfaceId) - return - } - guard Date() < deadline else { return } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { - waitForSurfaceId(on: tabManager, tabId: tabId, completion) + let deadline = Date().addingTimeInterval(timeout) + + func poll() { + if let surfaceId = tabManager.focusedPanelId(for: tabId) { + completion(surfaceId) + return + } + guard Date() < deadline else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + poll() + } } + + poll() } waitForContexts(minCount: 1) { [weak self] in From c5577dd495d35d21140e7473d6359189cd762787 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 5 Mar 2026 19:52:59 -0800 Subject: [PATCH 120/232] Fix flaky CLI socket listener recovery (#952) (#954) * Harden socket listener health checks and path handling * Make socket health probe non-blocking * Address PR feedback for issue 952 recovery guard * Run issue 952 regression guard without pytest dependency * Harden socket probe timing and health telemetry * Handle missing git in issue 952 regression guard * Address remaining PR 954 review fixes --- .github/workflows/ci-macos-compat.yml | 3 + .github/workflows/ci.yml | 6 + .github/workflows/test-depot.yml | 3 + Sources/AppDelegate.swift | 41 ++++- Sources/TerminalController.swift | 141 +++++++++++++-- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 20 ++- ...test_issue_952_socket_listener_recovery.py | 162 ++++++++++++++++++ 7 files changed, 355 insertions(+), 21 deletions(-) create mode 100644 tests/test_issue_952_socket_listener_recovery.py diff --git a/.github/workflows/ci-macos-compat.yml b/.github/workflows/ci-macos-compat.yml index 4bcab6b0..463e5a56 100644 --- a/.github/workflows/ci-macos-compat.yml +++ b/.github/workflows/ci-macos-compat.yml @@ -94,6 +94,9 @@ jobs: sleep $((attempt * 5)) done + - name: Run issue #952 regression guard + run: python3 tests/test_issue_952_socket_listener_recovery.py + - name: Run unit tests run: | set -euo pipefail diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bac7a85..c106a18a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,6 +122,9 @@ jobs: sleep $((attempt * 5)) done + - name: Run issue #952 regression guard + run: python3 tests/test_issue_952_socket_listener_recovery.py + - name: Run unit tests run: | set -euo pipefail @@ -244,6 +247,9 @@ jobs: sleep $((attempt * 5)) done + - name: Run issue #952 regression guard + run: python3 tests/test_issue_952_socket_listener_recovery.py + - name: Create virtual display run: | set -euo pipefail diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml index ca636bf6..867479ad 100644 --- a/.github/workflows/test-depot.yml +++ b/.github/workflows/test-depot.yml @@ -122,6 +122,9 @@ jobs: sleep $((attempt * 5)) done + - name: Run issue #952 regression guard + run: python3 tests/test_issue_952_socket_listener_recovery.py + - name: Run unit tests if: ${{ !inputs.skip_unit_tests }} run: | diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index dfbff6c2..b015a5ea 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1577,7 +1577,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private var isApplyingStartupSessionRestore = false private var sessionAutosaveTimer: DispatchSourceTimer? private var socketListenerHealthTimer: DispatchSourceTimer? - private static let socketListenerHealthCheckInterval: DispatchTimeInterval = .seconds(5) + private var socketListenerHealthCheckInFlight = false + private static let socketListenerHealthCheckInterval: DispatchTimeInterval = .seconds(2) private var lastSocketListenerUnhealthyCaptureAt: Date = .distantPast private static let socketListenerUnhealthyCaptureCooldown: TimeInterval = 60 private let sessionPersistenceQueue = DispatchQueue( @@ -2508,25 +2509,57 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private func stopSocketListenerHealthMonitor() { socketListenerHealthTimer?.cancel() socketListenerHealthTimer = nil + socketListenerHealthCheckInFlight = false } private func restartSocketListenerIfNeededForHealthCheck(source: String) { - guard let config = socketListenerConfigurationIfEnabled() else { return } - let health = TerminalController.shared.socketListenerHealth(expectedSocketPath: config.path) + guard !socketListenerHealthCheckInFlight, + let config = socketListenerConfigurationIfEnabled() else { return } + let expectedSocketPath = config.path + let terminalController = TerminalController.shared + socketListenerHealthCheckInFlight = true + Thread.detachNewThread { [weak self, expectedSocketPath, source, terminalController] in + let health = terminalController.socketListenerHealth(expectedSocketPath: expectedSocketPath) + Task { @MainActor [weak self, health] in + guard let self else { return } + self.socketListenerHealthCheckInFlight = false + self.handleSocketListenerHealthCheckResult( + health, + source: source, + expectedSocketPath: expectedSocketPath + ) + } + } + } + + private func handleSocketListenerHealthCheckResult( + _ health: TerminalController.SocketListenerHealth, + source: String, + expectedSocketPath: String + ) { + guard let config = socketListenerConfigurationIfEnabled(), + config.path == expectedSocketPath else { return } guard !health.isHealthy else { lastSocketListenerUnhealthyCaptureAt = .distantPast return } let failureSignals = health.failureSignals - let data: [String: Any] = [ + var data: [String: Any] = [ "source": source, "path": config.path, "isRunning": health.isRunning ? 1 : 0, "acceptLoopAlive": health.acceptLoopAlive ? 1 : 0, "socketPathMatches": health.socketPathMatches ? 1 : 0, "socketPathExists": health.socketPathExists ? 1 : 0, + "socketProbePerformed": health.socketProbePerformed ? 1 : 0, "failureSignals": failureSignals ] + if let socketConnectable = health.socketConnectable { + data["socketConnectable"] = socketConnectable ? 1 : 0 + } + if let socketConnectErrno = health.socketConnectErrno { + data["socketConnectErrno"] = Int(socketConnectErrno) + } sentryBreadcrumb("socket.listener.unhealthy", category: "socket", data: data) let now = Date() if now.timeIntervalSince(lastSocketListenerUnhealthyCaptureAt) >= Self.socketListenerUnhealthyCaptureCooldown { diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 5d905129..8f8eadf1 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -13,6 +13,9 @@ class TerminalController { let acceptLoopAlive: Bool let socketPathMatches: Bool let socketPathExists: Bool + let socketProbePerformed: Bool + let socketConnectable: Bool? + let socketConnectErrno: Int32? var failureSignals: [String] { var signals: [String] = [] @@ -20,6 +23,9 @@ class TerminalController { if !acceptLoopAlive { signals.append("accept_loop_dead") } if !socketPathMatches { signals.append("socket_path_mismatch") } if !socketPathExists { signals.append("socket_missing") } + if socketProbePerformed && isRunning && acceptLoopAlive && socketPathMatches && socketPathExists && socketConnectable == false { + signals.append("socket_unreachable") + } return signals } @@ -51,6 +57,14 @@ class TerminalController { private nonisolated static let acceptFailureMaxBackoffMs = 5_000 private nonisolated static let acceptFailureMinimumRearmDelayMs = 100 private nonisolated static let acceptFailureRearmThreshold = 50 + private nonisolated static let socketProbePollTimeoutMs: Int32 = 100 + private nonisolated static let socketProbePollAttempts = 3 + private nonisolated static let socketProbePollRetryBackoffUs: useconds_t = 50_000 + private nonisolated static let unixSocketPathMaxLength: Int = { + var addr = sockaddr_un() + // Reserve one byte for the null terminator. + return MemoryLayout.size(ofValue: addr.sun_path) - 1 + }() private struct ListenerStateSnapshot { let socketPath: String @@ -508,6 +522,99 @@ class TerminalController { return !isRunning && activeGeneration == 0 } + private nonisolated static func unixSocketAddress(path: String) -> sockaddr_un? { + var addr = sockaddr_un() + addr.sun_family = sa_family_t(AF_UNIX) + + let maxLength = unixSocketPathMaxLength + 1 + var didFit = false + path.withCString { source in + let sourceLength = strlen(source) + guard sourceLength < maxLength else { return } + + _ = withUnsafeMutableBytes(of: &addr.sun_path) { buffer in + buffer.initializeMemory(as: UInt8.self, repeating: 0) + } + withUnsafeMutablePointer(to: &addr.sun_path) { pathPtr in + let destination = UnsafeMutableRawPointer(pathPtr).assumingMemoryBound(to: CChar.self) + strncpy(destination, source, maxLength - 1) + } + didFit = true + } + return didFit ? addr : nil + } + + private nonisolated static func bindUnixSocket(_ socket: Int32, path: String) -> Int32? { + guard var addr = unixSocketAddress(path: path) else { return nil } + return withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + bind(socket, sockaddrPtr, socklen_t(MemoryLayout.size)) + } + } + } + + private nonisolated static func probeSocketConnectability(path: String) -> (isConnectable: Bool?, errnoCode: Int32?) { + let probeSocket = socket(AF_UNIX, SOCK_STREAM, 0) + guard probeSocket >= 0 else { + return (false, errno) + } + defer { close(probeSocket) } + + let existingFlags = fcntl(probeSocket, F_GETFL, 0) + if existingFlags >= 0 { + _ = fcntl(probeSocket, F_SETFL, existingFlags | O_NONBLOCK) + } + + guard var addr = unixSocketAddress(path: path) else { + return (false, ENAMETOOLONG) + } + let connectResult = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + connect(probeSocket, sockaddrPtr, socklen_t(MemoryLayout.size)) + } + } + if connectResult == 0 { + return (true, nil) + } + let connectErrno = errno + if connectErrno == EINPROGRESS { + var pollDescriptor = pollfd(fd: probeSocket, events: Int16(POLLOUT), revents: 0) + for attempt in 0.. 0 { + var socketError: Int32 = 0 + var socketErrorLength = socklen_t(MemoryLayout.size) + let status = getsockopt( + probeSocket, + SOL_SOCKET, + SO_ERROR, + &socketError, + &socketErrorLength + ) + if status == 0 && socketError == 0 { + return (true, nil) + } + if status == 0 { + return (false, socketError) + } + return (false, errno) + } + + let pollErrno = errno + if pollResult == 0 || pollErrno == EINTR { + if attempt + 1 < Self.socketProbePollAttempts { + usleep(Self.socketProbePollRetryBackoffUs) + continue + } + return (false, pollResult == 0 ? ETIMEDOUT : pollErrno) + } + return (false, pollErrno) + } + } + return (false, connectErrno) + } + func start(tabManager: TabManager, socketPath: String, accessMode: SocketControlMode) { self.tabManager = tabManager self.accessMode = accessMode @@ -556,19 +663,18 @@ class TerminalController { } // Bind to path - var addr = sockaddr_un() - addr.sun_family = sa_family_t(AF_UNIX) - socketPath.withCString { ptr in - withUnsafeMutablePointer(to: &addr.sun_path) { pathPtr in - let pathBuf = UnsafeMutableRawPointer(pathPtr).assumingMemoryBound(to: CChar.self) - strcpy(pathBuf, ptr) - } - } - - let bindResult = withUnsafePointer(to: &addr) { ptr in - ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in - bind(newServerSocket, sockaddrPtr, socklen_t(MemoryLayout.size)) - } + guard let bindResult = Self.bindUnixSocket(newServerSocket, path: socketPath) else { + close(newServerSocket) + reportSocketListenerFailure( + message: "socket.listener.start.failed", + stage: "bind_path_too_long", + errnoCode: ENAMETOOLONG, + extra: [ + "pathLength": socketPath.utf8.count, + "maxPathLength": Self.unixSocketPathMaxLength + ] + ) + return } guard bindResult >= 0 else { @@ -653,12 +759,19 @@ class TerminalController { var st = stat() let exists = lstat(expectedSocketPath, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK + let shouldProbeConnection = snapshot.isRunning && snapshot.acceptLoopAlive && pathMatches && exists + let connectability = shouldProbeConnection + ? Self.probeSocketConnectability(path: expectedSocketPath) + : (isConnectable: nil, errnoCode: nil) return SocketListenerHealth( isRunning: snapshot.isRunning, acceptLoopAlive: snapshot.acceptLoopAlive, socketPathMatches: pathMatches, - socketPathExists: exists + socketPathExists: exists, + socketProbePerformed: shouldProbeConnection, + socketConnectable: connectability.isConnectable, + socketConnectErrno: connectability.errnoCode ) } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 114c7b2b..9e34690f 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -10732,6 +10732,7 @@ final class TerminalControllerSocketListenerHealthTests: XCTestCase { return fd } + @MainActor func testSocketListenerHealthRecognizesSocketPath() throws { let path = makeTempSocketPath() let fd = try bindUnixSocket(at: path) @@ -10745,6 +10746,7 @@ final class TerminalControllerSocketListenerHealthTests: XCTestCase { XCTAssertFalse(health.isHealthy) } + @MainActor func testSocketListenerHealthRejectsRegularFile() throws { let path = makeTempSocketPath() let url = URL(fileURLWithPath: path) @@ -10761,10 +10763,16 @@ final class TerminalControllerSocketListenerHealthTests: XCTestCase { isRunning: true, acceptLoopAlive: true, socketPathMatches: true, - socketPathExists: true + socketPathExists: true, + socketProbePerformed: true, + socketConnectable: true, + socketConnectErrno: nil ) XCTAssertTrue(health.isHealthy) - XCTAssertEqual(health.failureSignals, []) + XCTAssertTrue(health.failureSignals.isEmpty) + XCTAssertTrue(health.socketProbePerformed) + XCTAssertEqual(health.socketConnectable, true) + XCTAssertNil(health.socketConnectErrno) } func testSocketListenerHealthFailureSignalsIncludeAllDetectedProblems() { @@ -10772,9 +10780,15 @@ final class TerminalControllerSocketListenerHealthTests: XCTestCase { isRunning: false, acceptLoopAlive: false, socketPathMatches: false, - socketPathExists: false + socketPathExists: false, + socketProbePerformed: false, + socketConnectable: nil, + socketConnectErrno: nil ) XCTAssertFalse(health.isHealthy) + XCTAssertFalse(health.socketProbePerformed) + XCTAssertNil(health.socketConnectable) + XCTAssertNil(health.socketConnectErrno) XCTAssertEqual( health.failureSignals, ["not_running", "accept_loop_dead", "socket_path_mismatch", "socket_missing"] diff --git a/tests/test_issue_952_socket_listener_recovery.py b/tests/test_issue_952_socket_listener_recovery.py new file mode 100644 index 00000000..46a83644 --- /dev/null +++ b/tests/test_issue_952_socket_listener_recovery.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +"""Regression guard for issue #952 (flaky CLI socket connections).""" + +from __future__ import annotations + +import re +import shutil +import subprocess +from pathlib import Path + + +def get_repo_root() -> Path: + """Return the repository root for source inspections.""" + fallback_root = Path(__file__).resolve().parents[1] + git_path = shutil.which("git") + if git_path is None: + return fallback_root + + try: + result = subprocess.run( + [git_path, "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=False, + ) + except OSError: + return fallback_root + if result.returncode == 0: + return Path(result.stdout.strip()) + return fallback_root + + +def require(content: str, needle: str, message: str, failures: list[str], *, regex: bool = False) -> None: + """Record a failure when a required source pattern is missing.""" + matched = re.search(needle, content, re.MULTILINE) is not None if regex else needle in content + if not matched: + failures.append(message) + + +def collect_failures() -> list[str]: + """Collect missing source-level guards for the socket listener recovery fix.""" + repo_root = get_repo_root() + terminal_controller_path = repo_root / "Sources" / "TerminalController.swift" + app_delegate_path = repo_root / "Sources" / "AppDelegate.swift" + failures: list[str] = [] + + missing_paths = [ + str(path) for path in [terminal_controller_path, app_delegate_path] if not path.exists() + ] + if missing_paths: + for path in missing_paths: + failures.append(f"Missing expected file: {path}") + return failures + + terminal_controller = terminal_controller_path.read_text(encoding="utf-8") + app_delegate = app_delegate_path.read_text(encoding="utf-8") + + require( + terminal_controller, + "let socketProbePerformed: Bool", + "Socket health snapshot no longer tracks whether connectability was probed", + failures, + ) + require( + terminal_controller, + "let socketConnectable: Bool?", + "Socket health snapshot no longer distinguishes unprobed vs connectable sockets", + failures, + ) + require( + terminal_controller, + "let socketConnectErrno: Int32?", + "Socket health snapshot no longer preserves probe errno", + failures, + ) + require( + terminal_controller, + "signals.append(\"socket_unreachable\")", + "Socket health failures no longer flag unreachable listeners", + failures, + ) + require( + terminal_controller, + r"private\s+nonisolated\s+static\s+func\s+probeSocketConnectability\s*\(\s*path:\s*String\s*\)", + "Missing active socket connectability probe helper", + failures, + regex=True, + ) + require( + terminal_controller, + r"connect\s*\(\s*probeSocket\s*,\s*sockaddrPtr\s*,\s*socklen_t\s*\(\s*MemoryLayout\.size\s*\)\s*\)", + "Socket health probe no longer performs a real connect() check", + failures, + regex=True, + ) + require( + terminal_controller, + "stage: \"bind_path_too_long\"", + "Socket listener start no longer reports overlong Unix socket paths", + failures, + ) + require( + terminal_controller, + "Self.unixSocketPathMaxLength", + "Socket listener path-length telemetry was removed", + failures, + ) + + require( + app_delegate, + "private static let socketListenerHealthCheckInterval: DispatchTimeInterval = .seconds(2)", + "Socket health timer interval drifted from the fast recovery setting", + failures, + ) + require( + app_delegate, + "\"socketProbePerformed\": health.socketProbePerformed ? 1 : 0", + "Health telemetry no longer records whether a connectability probe ran", + failures, + ) + require( + app_delegate, + "if let socketConnectable = health.socketConnectable {", + "Health telemetry no longer gates connectability on an actual probe result", + failures, + ) + require( + app_delegate, + "data[\"socketConnectable\"] = socketConnectable ? 1 : 0", + "Health telemetry no longer includes connectability when a probe ran", + failures, + ) + require( + app_delegate, + "if let socketConnectErrno = health.socketConnectErrno {", + "Health telemetry no longer records connect probe errno when available", + failures, + ) + return failures + + +def test_issue_952_socket_listener_recovery() -> None: + """Keep the source-level recovery guards for issue #952 in CI.""" + failures = collect_failures() + assert not failures, "issue #952 regression(s) detected:\n- " + "\n- ".join(failures) + + +def main() -> int: + """Run the regression guard without requiring pytest to be installed.""" + failures = collect_failures() + if failures: + print("FAIL: issue #952 regression(s) detected") + for failure in failures: + print(f"- {failure}") + return 1 + + print("PASS: issue #952 socket listener recovery guards are present") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 24a8056b496378ba6da2408854fdccc606b116e0 Mon Sep 17 00:00:00 2001 From: jleechan Date: Thu, 5 Mar 2026 19:54:30 -0800 Subject: [PATCH 121/232] tests: fix split CWD e2e assertion signal and early aborts --- tests/test_split_cwd_inheritance.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/test_split_cwd_inheritance.py b/tests/test_split_cwd_inheritance.py index 845c9a7c..6677ee8e 100644 --- a/tests/test_split_cwd_inheritance.py +++ b/tests/test_split_cwd_inheritance.py @@ -22,7 +22,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from cmux import cmux, cmuxError # noqa: E402 +from cmux import cmux # noqa: E402 def _parse_sidebar_state(text: str) -> dict[str, str]: @@ -131,7 +131,12 @@ def main() -> int: # Record the original panel so we can verify focus moves to the NEW pane. original_panel = state.get("focused_panel", "") split_result = client.new_split("right") - check("split created", bool(split_result)) + if not split_result: + check("split created", False) + print(f"\n{passed} passed, {failed} failed") + client.close() + return 1 + check("split created", True) # Wait for the NEW pane (different panel ID) to report test_dir_a. time.sleep(4) # wait for new bash to start + run PROMPT_COMMAND @@ -142,7 +147,9 @@ def main() -> int: new_panel = state.get("focused_panel", "") check("test1: focus moved to new pane", new_panel != original_panel, f"original={original_panel!r}, current={new_panel!r}") - check("test1: split inherited test_dir_a", True) + check("test1: split inherited test_dir_a", + state.get("focused_cwd") == test_dir_a, + f"focused_cwd={state.get('focused_cwd')!r}") except AssertionError: state = _parse_sidebar_state(client.sidebar_state()) check("test1: split inherited test_dir_a", False, @@ -156,7 +163,12 @@ def main() -> int: original_tab = state.get("tab", "") tab_result = client.new_tab() - check("new tab created", bool(tab_result)) + if not tab_result: + check("new tab created", False) + print(f"\n{passed} passed, {failed} failed") + client.close() + return 1 + check("new tab created", True) # New workspace should be a different tab AND inherit test_dir_b time.sleep(4) @@ -175,7 +187,9 @@ def main() -> int: ) check("test2: focus moved to new tab", state.get("tab") != original_tab, f"original={original_tab!r}, current={state.get('tab')!r}") - check("test2: new workspace inherited test_dir_b", True) + check("test2: new workspace inherited test_dir_b", + state.get("focused_cwd") == test_dir_b, + f"focused_cwd={state.get('focused_cwd')!r}") except AssertionError: state = _parse_sidebar_state(client.sidebar_state()) check("test2: new workspace inherited test_dir_b", False, From 085e192bca1560560c897d6161c305a8fac8bbf4 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:01:05 -0800 Subject: [PATCH 122/232] Add diagnostics for empty browser snapshots (#977) --- CLI/cmux.swift | 27 ++++++++++++++++++++++--- tests_v2/test_browser_cli_agent_port.py | 6 ++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 2df4f56e..4172bb7e 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -2662,6 +2662,29 @@ struct CMUXCLI { } } + func displaySnapshotText(_ payload: [String: Any]) -> String { + let snapshotText = (payload["snapshot"] as? String) ?? "Empty page" + guard snapshotText.contains("\n- (empty)") else { + return snapshotText + } + + let url = ((payload["url"] as? String) ?? "").trimmingCharacters(in: .whitespacesAndNewlines) + let readyState = ((payload["ready_state"] as? String) ?? "").trimmingCharacters(in: .whitespacesAndNewlines) + var lines = [snapshotText] + + if !url.isEmpty { + lines.append("url: \(url)") + } + if !readyState.isEmpty { + lines.append("ready_state: \(readyState)") + } + if url.isEmpty || url == "about:blank" { + lines.append("hint: run 'cmux browser get url' to verify navigation") + } + + return lines.joined(separator: "\n") + } + func displayBrowserValue(_ value: Any) -> String { if let dict = value as? [String: Any], let type = dict["__cmux_t"] as? String, @@ -2880,10 +2903,8 @@ struct CMUXCLI { let payload = try client.sendV2(method: "browser.snapshot", params: params) if jsonOutput { print(jsonString(formatIDs(payload, mode: idFormat))) - } else if let text = payload["snapshot"] as? String { - print(text) } else { - print("Empty page") + print(displaySnapshotText(payload)) } return } diff --git a/tests_v2/test_browser_cli_agent_port.py b/tests_v2/test_browser_cli_agent_port.py index d8266a66..bbebdeec 100644 --- a/tests_v2/test_browser_cli_agent_port.py +++ b/tests_v2/test_browser_cli_agent_port.py @@ -164,6 +164,12 @@ def main() -> int: ) _must(bool(workspace), f"Expected workspace handle from identify: {identify}") + blank_opened = _run_cli_json(cli, ["browser", "open", "about:blank", "--workspace", workspace]) + blank_surface = str(blank_opened.get("surface_ref") or blank_opened.get("surface_id") or "") + _must(bool(blank_surface), f"Expected about:blank browser open to return a surface: {blank_opened}") + blank_snapshot = _run_cli_text(cli, ["browser", blank_surface, "snapshot", "--interactive"]) + _must("about:blank" in blank_snapshot and "get url" in blank_snapshot, f"Expected empty snapshot diagnostics for about:blank: {blank_snapshot!r}") + opened_routed = _run_cli_json(cli, ["browser", "open", page_url, "--workspace", workspace]) routed_surface = str(opened_routed.get("surface_ref") or opened_routed.get("surface_id") or "") _must(bool(routed_surface), f"browser open --workspace returned no surface handle: {opened_routed}") From 6c163b1eb004fe3b7fefee759598fee37c8b4f9a Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:04:52 -0800 Subject: [PATCH 123/232] Run notify regression from in-app shell --- Sources/AppDelegate.swift | 3 + .../MultiWindowNotificationsUITests.swift | 120 +++++++++++++++--- 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 645c4e4e..c3b56efe 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5750,6 +5750,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "expectedLatestWindowId": window1.windowId.uuidString, "expectedLatestTabId": tabId1.uuidString, ], at: path) + // Leave the initial window's terminal focused so UI tests can type shell + // commands while still keeping the second window configured for notifications. + window1.window?.makeKeyAndOrderFront(nil) self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) } } diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index f42aba29..245d423e 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -248,40 +248,83 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) + let title = "focus-regression-\(UUID().uuidString.prefix(8))" + let commandResultStem = UUID().uuidString + let commandStatusPath = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-ui-test-notify-\(commandResultStem).status") + .path + let commandStdoutPath = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-ui-test-notify-\(commandResultStem).stdout") + .path + let commandStderrPath = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-ui-test-notify-\(commandResultStem).stderr") + .path + defer { + try? FileManager.default.removeItem(atPath: commandStatusPath) + try? FileManager.default.removeItem(atPath: commandStdoutPath) + try? FileManager.default.removeItem(atPath: commandStderrPath) + } + + guard let bundledCLIPath = resolveCmuxCLIPaths(strategy: .bundledOnly).first else { + XCTFail("Failed to locate bundled cmux CLI for notify regression test") + return + } + + XCTAssertTrue(app.windows.element(boundBy: 0).waitForExistence(timeout: 4.0), "Expected at least one window before typing notify command") + app.windows.element(boundBy: 0) + .coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) + .click() + + let notifyCommand = [ + "rm -f \(shellSingleQuote(commandStatusPath)) \(shellSingleQuote(commandStdoutPath)) \(shellSingleQuote(commandStderrPath));", + "(sleep 1;", + "\(shellSingleQuote(bundledCLIPath))", + "--socket \(shellSingleQuote(socketPath))", + "notify", + "--workspace \(shellSingleQuote(tabId2))", + "--surface \(shellSingleQuote(surfaceId))", + "--title \(shellSingleQuote(title))", + "--subtitle \(shellSingleQuote("ui-test"))", + "--body \(shellSingleQuote("focus-regression"))", + ">\(shellSingleQuote(commandStdoutPath))", + "2>\(shellSingleQuote(commandStderrPath));", + "printf '%s' $? >\(shellSingleQuote(commandStatusPath))) >/dev/null 2>&1 &" + ].joined(separator: " ") + app.typeText(notifyCommand + "\n") + let finder = XCUIApplication(bundleIdentifier: "com.apple.finder") finder.activate() XCTAssertTrue( waitForAppToLeaveForeground(app, timeout: 8.0), - "Expected cmux to move to background before sending notify command. state=\(app.state.rawValue)" + "Expected cmux to move to background before delayed notify command runs. state=\(app.state.rawValue)" ) - let title = "focus-regression-\(UUID().uuidString.prefix(8))" - let notifyResult = runCmuxNotify( - socketPath: socketPath, - workspaceId: tabId2, - surfaceId: surfaceId, - title: title + XCTAssertTrue( + waitForCommandCompletionWhileBackgrounded( + statusPath: commandStatusPath, + app: app, + timeout: 15.0 + ), + "Expected delayed bundled `cmux notify` command to finish without foregrounding cmux. state=\(app.state.rawValue)" ) + + let notifyExitStatus = readTrimmedFile(atPath: commandStatusPath) ?? "" + let notifyStdout = readTrimmedFile(atPath: commandStdoutPath) ?? "" + let notifyStderr = readTrimmedFile(atPath: commandStderrPath) ?? "" + RunLoop.current.run(until: Date().addingTimeInterval(0.5)) XCTAssertFalse( app.state == .runningForeground, - "Expected cmux to remain in background after bundled `cmux notify`. state=\(app.state.rawValue) stderr=\(notifyResult.stderr)" + "Expected cmux to remain in background after bundled `cmux notify`. state=\(app.state.rawValue) stderr=\(notifyStderr)" ) - - guard notifyResult.terminationStatus == 0 else { - let rawFallbackResponse: String? - if isSocketPermissionFailure(notifyResult.stderr) { - rawFallbackResponse = socketCommand("notify_target \(tabId2) \(surfaceId) \(title)|ui-test|focus-regression") - } else { - rawFallbackResponse = nil - } + guard notifyExitStatus == "0" else { XCTFail( - "Expected bundled `cmux notify` to succeed. stderr=\(notifyResult.stderr) " + - "rawFallback=\(rawFallbackResponse ?? "")" + "Expected bundled `cmux notify` launched from the in-app shell to succeed. " + + "status=\(notifyExitStatus) stdout=\(notifyStdout) stderr=\(notifyStderr)" ) return } - XCTAssertTrue(notifyResult.stdout.contains("OK"), "Expected notify command to return OK. stdout=\(notifyResult.stdout)") + XCTAssertTrue(notifyStdout.contains("OK"), "Expected notify command to return OK. stdout=\(notifyStdout) stderr=\(notifyStderr)") } private func clickNotificationPopoverRowAndWaitForFocusChange( @@ -437,6 +480,37 @@ final class MultiWindowNotificationsUITests: XCTestCase { return (stdout ?? lastStdout, stderr ?? lastStderr) } + private func waitForCommandCompletionWhileBackgrounded( + statusPath: String, + app: XCUIApplication, + timeout: TimeInterval + ) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + var sawCompletion = false + while Date() < deadline { + if app.state == .runningForeground { + return false + } + if FileManager.default.fileExists(atPath: statusPath) { + sawCompletion = true + break + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + guard sawCompletion || FileManager.default.fileExists(atPath: statusPath) else { + return false + } + + let postCompletionDeadline = Date().addingTimeInterval(0.75) + while Date() < postCompletionDeadline { + if app.state == .runningForeground { + return false + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + return app.state != .runningForeground + } + private func waitForAppToLeaveForeground(_ app: XCUIApplication, timeout: TimeInterval) -> Bool { let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { @@ -946,6 +1020,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { return "'" + value.replacingOccurrences(of: "'", with: "'\"'\"'") + "'" } + private func readTrimmedFile(atPath path: String) -> String? { + guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)), + let value = String(data: data, encoding: .utf8) else { + return nil + } + return value.trimmingCharacters(in: .whitespacesAndNewlines) + } + private final class ControlSocketClient { private let path: String From d2ba5eefdebef2fe8cacbcd7fdeb737b8462c7cd Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:09:57 -0800 Subject: [PATCH 124/232] Add regression test commit policy to CLAUDE.md (#982) Two-commit structure for bug fix PRs: first commit adds the failing test (CI red), second commit adds the fix (CI green). Proves in the GitHub PR UI that the test genuinely catches the bug. --- CLAUDE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 098b77ea..e1c1c942 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,6 +113,15 @@ tail -f "$(cat /tmp/cmux-last-debug-log-path 2>/dev/null || echo /tmp/cmux-debug - Focus events: `focus.panel`, `focus.bonsplit`, `focus.firstResponder`, `focus.moveFocus` - Bonsplit events: `tab.select`, `tab.close`, `tab.dragStart`, `tab.drop`, `pane.focus`, `pane.drop`, `divider.dragStart` +## Regression test commit policy + +When adding a regression test for a bug fix, use a two-commit structure so CI proves the test catches the bug: + +1. **Commit 1:** Add the failing test only (no fix). CI should go red. +2. **Commit 2:** Add the fix. CI should go green. + +This makes it visible in the GitHub PR UI (Commits tab, check statuses) that the test genuinely fails without the fix. + ## Pitfalls - **Custom UTTypes** for drag-and-drop must be declared in `Resources/Info.plist` under `UTExportedTypeDeclarations` (e.g. `com.splittabbar.tabtransfer`, `com.cmux.sidebar-tab-reorder`). From e6e5a57dd6060e55ea090351c113376b2e53b290 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:18:45 -0800 Subject: [PATCH 125/232] Send notify regression input via workspace socket --- Sources/AppDelegate.swift | 5 +- Sources/TerminalController.swift | 47 +++++++++++++++++++ .../MultiWindowNotificationsUITests.swift | 14 +++--- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index c3b56efe..082b9486 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5750,9 +5750,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "expectedLatestWindowId": window1.windowId.uuidString, "expectedLatestTabId": tabId1.uuidString, ], at: path) - // Leave the initial window's terminal focused so UI tests can type shell - // commands while still keeping the second window configured for notifications. - window1.window?.makeKeyAndOrderFront(nil) self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) } } @@ -5821,7 +5818,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "socketPathExists": health.socketPathExists ? "1" : "0", "socketFailureSignals": failureSignals, ], at: dataPath) - guard !isTimedOut else { return } + guard !isTimedOut, !isReady else { return } DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { publish() } diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index b536d0e7..ca04b758 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -1247,6 +1247,9 @@ class TerminalController { case "send_key": return sendKey(args) + case "send_workspace": + return sendInputToWorkspace(args) + case "send_surface": return sendInputToSurface(args) @@ -9267,6 +9270,7 @@ class TerminalController { Input commands: send - Send text to current terminal send_key - Send special key (ctrl-c, ctrl-d, enter, tab, escape) + send_workspace - Send text to a workspace's focused terminal send_surface - Send text to a specific terminal send_key_surface - Send special key to a specific terminal read_screen [id|idx] [--scrollback] [--lines N] - Read terminal text (plain text) @@ -11594,6 +11598,49 @@ class TerminalController { return success ? "OK" : "ERROR: Failed to send input" } + private func sendInputToWorkspace(_ args: String) -> String { + guard let tabManager else { return "ERROR: TabManager not available" } + let parts = args.split(separator: " ", maxSplits: 1).map(String.init) + guard parts.count == 2 else { return "ERROR: Usage: send_workspace " } + + let workspaceArg = parts[0].trimmingCharacters(in: .whitespacesAndNewlines) + let text = parts[1] + guard let workspaceId = UUID(uuidString: workspaceArg) else { + return "ERROR: Invalid workspace ID" + } + + var success = false + var error: String? + DispatchQueue.main.sync { + guard let targetManager = AppDelegate.shared?.tabManagerFor(tabId: workspaceId) + ?? (tabManager.tabs.contains(where: { $0.id == workspaceId }) ? tabManager : nil) else { + error = "ERROR: Workspace not found" + return + } + guard let tab = targetManager.tabs.first(where: { $0.id == workspaceId }), + let terminalPanel = tab.focusedTerminalPanel else { + error = "ERROR: No focused terminal in workspace" + return + } + + let unescaped = text + .replacingOccurrences(of: "\\n", with: "\r") + .replacingOccurrences(of: "\\r", with: "\r") + .replacingOccurrences(of: "\\t", with: "\t") + + if let surface = terminalPanel.surface.surface { + sendSocketText(unescaped, surface: surface) + } else { + terminalPanel.sendText(unescaped) + terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() + } + success = true + } + + if let error { return error } + return success ? "OK" : "ERROR: Failed to send input" + } + private func sendInputToSurface(_ args: String) -> String { guard let tabManager = tabManager else { return "ERROR: TabManager not available" } let parts = args.split(separator: " ", maxSplits: 1).map(String.init) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 245d423e..5753173f 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -224,6 +224,10 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTFail("Missing setup workspace id") return } + guard let tabId1 = setup["tabId1"], !tabId1.isEmpty else { + XCTFail("Missing source workspace id") + return + } if let expectedSocketPath = setup["socketExpectedPath"], !expectedSocketPath.isEmpty { socketPath = expectedSocketPath } @@ -270,11 +274,6 @@ final class MultiWindowNotificationsUITests: XCTestCase { return } - XCTAssertTrue(app.windows.element(boundBy: 0).waitForExistence(timeout: 4.0), "Expected at least one window before typing notify command") - app.windows.element(boundBy: 0) - .coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) - .click() - let notifyCommand = [ "rm -f \(shellSingleQuote(commandStatusPath)) \(shellSingleQuote(commandStdoutPath)) \(shellSingleQuote(commandStderrPath));", "(sleep 1;", @@ -290,7 +289,10 @@ final class MultiWindowNotificationsUITests: XCTestCase { "2>\(shellSingleQuote(commandStderrPath));", "printf '%s' $? >\(shellSingleQuote(commandStatusPath))) >/dev/null 2>&1 &" ].joined(separator: " ") - app.typeText(notifyCommand + "\n") + guard socketCommand("send_workspace \(tabId1) \(notifyCommand)\\n") == "OK" else { + XCTFail("Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1)") + return + } let finder = XCUIApplication(bundleIdentifier: "com.apple.finder") finder.activate() From acd8dbff69e960c82085b989b2f4be4bed9402d1 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:24:49 -0800 Subject: [PATCH 126/232] Keep send_workspace test-only --- Sources/TerminalController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index ca04b758..0269522e 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -1247,9 +1247,6 @@ class TerminalController { case "send_key": return sendKey(args) - case "send_workspace": - return sendInputToWorkspace(args) - case "send_surface": return sendInputToSurface(args) @@ -1360,6 +1357,9 @@ class TerminalController { #if DEBUG + case "send_workspace": + return sendInputToWorkspace(args) + case "set_shortcut": return setShortcut(args) @@ -9270,7 +9270,6 @@ class TerminalController { Input commands: send - Send text to current terminal send_key - Send special key (ctrl-c, ctrl-d, enter, tab, escape) - send_workspace - Send text to a workspace's focused terminal send_surface - Send text to a specific terminal send_key_surface - Send special key to a specific terminal read_screen [id|idx] [--scrollback] [--lines N] - Read terminal text (plain text) @@ -9346,6 +9345,7 @@ class TerminalController { sidebar_overlay_gate [active|inactive] - Return true/false if sidebar outside-drop overlay would capture (test-only) terminal_drop_overlay_probe [deferred|direct] - Trigger focused terminal drop-overlay show path and report animation counts (test-only) activate_app - Bring app + main window to front (test-only) + send_workspace - Send text to a workspace's focused terminal (test-only) is_terminal_focused - Return true/false if terminal surface is first responder (test-only) read_terminal_text [id|idx] - Read visible terminal text (base64, test-only) render_stats [id|idx] - Read terminal render stats (draw counters, test-only) From 2023d8eb57418fd94db30b99c19058eaaabe0e6c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:34:51 -0800 Subject: [PATCH 127/232] Fallback panel lookup for notify UI setup --- Sources/AppDelegate.swift | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 082b9486..093f2a6c 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5691,8 +5691,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) { let deadline = Date().addingTimeInterval(timeout) - func poll() { + func resolvedSurfaceId() -> UUID? { if let surfaceId = tabManager.focusedPanelId(for: tabId) { + return surfaceId + } + + guard let workspace = tabManager.tabs.first(where: { $0.id == tabId }) else { + return nil + } + + if let terminalPanelId = workspace.focusedTerminalPanel?.id { + return terminalPanelId + } + + if let terminalPanelId = workspace.terminalPanelForConfigInheritance()?.id { + return terminalPanelId + } + + return workspace.panels.values + .compactMap { ($0 as? TerminalPanel)?.id } + .sorted(by: { $0.uuidString < $1.uuidString }) + .first + } + + func poll() { + if let surfaceId = resolvedSurfaceId() { completion(surfaceId) return } From f3f17cfc38229907776777bd3ff4ebf734a8533a Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:51:30 -0800 Subject: [PATCH 128/232] Fix browser get selector examples (#976) * Fix browser get selector examples * Add docs regression test for browser get selector examples --- skills/cmux-browser/references/commands.md | 10 ++++-- .../test_browser_skill_get_selector_docs.py | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests_v2/test_browser_skill_get_selector_docs.py diff --git a/skills/cmux-browser/references/commands.md b/skills/cmux-browser/references/commands.md index 5cc37625..72693a5d 100644 --- a/skills/cmux-browser/references/commands.md +++ b/skills/cmux-browser/references/commands.md @@ -11,7 +11,7 @@ This maps common `agent-browser` usage to `cmux browser` usage. - `agent-browser fill ` -> `cmux browser fill ` - `agent-browser type ` -> `cmux browser type ` - `agent-browser select ` -> `cmux browser select ` -- `agent-browser get text ` -> `cmux browser get text ` +- `agent-browser get text ` -> `cmux browser get text ` - `agent-browser get url` -> `cmux browser get url` - `agent-browser get title` -> `cmux browser get title` @@ -34,7 +34,13 @@ cmux browser get url|title ```bash cmux browser snapshot --interactive cmux browser snapshot --interactive --compact --max-depth 3 -cmux browser get text|html|value|attr|count|box|styles ... +cmux browser get text body +cmux browser get html body +cmux browser get value "#email" +cmux browser get attr "#email" --attr placeholder +cmux browser get count ".row" +cmux browser get box "#submit" +cmux browser get styles "#submit" --property color cmux browser eval '' ``` diff --git a/tests_v2/test_browser_skill_get_selector_docs.py b/tests_v2/test_browser_skill_get_selector_docs.py new file mode 100644 index 00000000..92c5a8f9 --- /dev/null +++ b/tests_v2/test_browser_skill_get_selector_docs.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Regression checks for cmux-browser get selector examples.""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmuxError + + +ROOT = Path(__file__).resolve().parents[1] +COMMANDS = ROOT / "skills/cmux-browser/references/commands.md" + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def main() -> int: + commands = COMMANDS.read_text(encoding="utf-8") + + _must("`agent-browser get text ` -> `cmux browser get text `" in commands, "Expected get text mapping to mention selector support") + _must("cmux browser get text body" in commands, "Expected get text body example") + _must("cmux browser get html body" in commands, "Expected get html body example") + _must('cmux browser get value "#email"' in commands, "Expected get value selector example") + _must('cmux browser get attr "#email" --attr placeholder' in commands, "Expected get attr selector example") + _must("cmux browser get text|html|value|attr|count|box|styles ..." not in commands, "Unexpected bare get example block") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From d4e16ae8c837640c578808f835708dbab7a219ab Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:51:47 -0800 Subject: [PATCH 129/232] Add browser js_error recovery guidance (#975) * Add browser js_error recovery guidance * Add docs regression test for browser js_error guidance --- skills/cmux-browser/SKILL.md | 18 ++++++++++ tests_v2/test_browser_skill_js_error_docs.py | 35 ++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests_v2/test_browser_skill_js_error_docs.py diff --git a/skills/cmux-browser/SKILL.md b/skills/cmux-browser/SKILL.md index 8d398377..33e6d47d 100644 --- a/skills/cmux-browser/SKILL.md +++ b/skills/cmux-browser/SKILL.md @@ -114,3 +114,21 @@ These commands currently return `not_supported` because they rely on Chrome/CDP- - low-level raw input injection Use supported high-level commands (`click`, `fill`, `press`, `scroll`, `wait`, `snapshot`) instead. + +## Troubleshooting + +### `js_error` on `snapshot --interactive` or `eval` + +Some complex pages can reject or break the JavaScript used for rich snapshots and ad-hoc evaluation. + +Recovery steps: + +```bash +cmux browser surface:7 get url +cmux browser surface:7 get text body +cmux browser surface:7 get html body +``` + +- Use `get url` first so you know whether the page actually navigated. +- Fall back to `get text body` or `get html body` when `snapshot --interactive` or `eval` returns `js_error`. +- If the page is still failing, navigate to a simpler intermediate page, then retry the task from there. diff --git a/tests_v2/test_browser_skill_js_error_docs.py b/tests_v2/test_browser_skill_js_error_docs.py new file mode 100644 index 00000000..566ab10f --- /dev/null +++ b/tests_v2/test_browser_skill_js_error_docs.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Regression checks for cmux-browser js_error troubleshooting docs.""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmuxError + + +ROOT = Path(__file__).resolve().parents[1] +SKILL = ROOT / "skills/cmux-browser/SKILL.md" + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def main() -> int: + skill = SKILL.read_text(encoding="utf-8") + + _must("## Troubleshooting" in skill, "Expected Troubleshooting section in cmux-browser skill") + _must("### `js_error` on `snapshot --interactive` or `eval`" in skill, "Expected js_error troubleshooting heading") + _must("cmux browser surface:7 get url" in skill, "Expected get url recovery step") + _must("cmux browser surface:7 get text body" in skill, "Expected get text body recovery step") + _must("cmux browser surface:7 get html body" in skill, "Expected get html body recovery step") + _must("when `snapshot --interactive` or `eval` returns `js_error`" in skill, "Expected js_error fallback guidance") + _must("navigate to a simpler intermediate page" in skill, "Expected simpler-page fallback guidance") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 990b6ba12aa931df3cdf1d0fa028a433e5e8c427 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 20:51:51 -0800 Subject: [PATCH 130/232] ok --- Sources/ContentView.swift | 84 +++++++++++++++++++++++++++++++- Sources/TabManager.swift | 24 ++++++++- Sources/TerminalController.swift | 14 +++--- Sources/Workspace.swift | 12 +++++ 4 files changed, 123 insertions(+), 11 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 0b9bd263..9a50947e 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1797,6 +1797,7 @@ struct ContentView: View { ForEach(mountedWorkspaces) { tab in let isSelectedWorkspace = selectedWorkspaceId == tab.id let isRetiringWorkspace = retiringWorkspaceId == tab.id + let shouldPrimeInBackground = tabManager.pendingBackgroundWorkspaceLoadIds.contains(tab.id) // Keep the retiring workspace visible during handoff, but never input-active. // Allowing both selected+retiring workspaces to be input-active lets the // old workspace steal first responder (notably with WKWebView), which can @@ -1823,6 +1824,9 @@ struct ContentView: View { .allowsHitTesting(isSelectedWorkspace) .accessibilityHidden(!isVisible) .zIndex(isSelectedWorkspace ? 2 : (isRetiringWorkspace ? 1 : 0)) + .task(id: shouldPrimeInBackground ? tab.id : nil) { + await primeBackgroundWorkspaceIfNeeded(workspaceId: tab.id) + } } } .opacity(sidebarSelectionState.selection == .tabs ? 1 : 0) @@ -2167,6 +2171,10 @@ struct ContentView: View { reconcileMountedWorkspaceIds() }) + view = AnyView(view.onReceive(tabManager.$pendingBackgroundWorkspaceLoadIds) { _ in + reconcileMountedWorkspaceIds() + }) + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .ghosttyDidSetTitle)) { notification in guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID, tabId == tabManager.selectedTabId else { return } @@ -2227,6 +2235,7 @@ struct ContentView: View { if let previousSelectedWorkspaceId, !existingIds.contains(previousSelectedWorkspaceId) { self.previousSelectedWorkspaceId = tabManager.selectedTabId } + tabManager.pruneBackgroundWorkspaceLoads(existingIds: existingIds) reconcileMountedWorkspaceIds(tabs: tabs) selectedTabIds = selectedTabIds.filter { existingIds.contains($0) } if selectedTabIds.isEmpty, let selectedId = tabManager.selectedTabId { @@ -2531,9 +2540,10 @@ struct ContentView: View { let currentTabs = tabs ?? tabManager.tabs let orderedTabIds = currentTabs.map { $0.id } let effectiveSelectedId = selectedId ?? tabManager.selectedTabId - let pinnedIds = retiringWorkspaceId.map { Set([ $0 ]) } ?? [] + let handoffPinnedIds = retiringWorkspaceId.map { Set([ $0 ]) } ?? [] + let pinnedIds = handoffPinnedIds.union(tabManager.pendingBackgroundWorkspaceLoadIds) let isCycleHot = tabManager.isWorkspaceCycleHot - let shouldKeepHandoffPair = isCycleHot && !pinnedIds.isEmpty + let shouldKeepHandoffPair = isCycleHot && !handoffPinnedIds.isEmpty let baseMaxMounted = shouldKeepHandoffPair ? WorkspaceMountPolicy.maxMountedWorkspacesDuringCycle : WorkspaceMountPolicy.maxMountedWorkspaces @@ -2570,6 +2580,76 @@ struct ContentView: View { #endif } + private enum BackgroundWorkspacePrimeState { + case pending + case completed(reason: String) + } + + private func primeBackgroundWorkspaceIfNeeded(workspaceId: UUID) async { + let shouldPrime = await MainActor.run { + tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId) + } + guard shouldPrime else { return } + +#if DEBUG + let startedAt = ProcessInfo.processInfo.systemUptime + dlog("workspace.backgroundPrime.start workspace=\(workspaceId.uuidString.prefix(5))") +#endif + + let timeout = Date().addingTimeInterval(2.0) + while !Task.isCancelled { + let state = await MainActor.run { + stepBackgroundWorkspacePrime(workspaceId: workspaceId) + } + switch state { + case .pending: + if Date() < timeout { + try? await Task.sleep(nanoseconds: 50_000_000) + continue + } + await MainActor.run { + tabManager.completeBackgroundWorkspaceLoad(for: workspaceId) + } +#if DEBUG + let elapsedMs = (ProcessInfo.processInfo.systemUptime - startedAt) * 1000 + dlog( + "workspace.backgroundPrime.finish workspace=\(workspaceId.uuidString.prefix(5)) " + + "reason=timeout ms=\(String(format: "%.2f", elapsedMs))" + ) +#endif + return + case .completed(let reason): +#if DEBUG + let elapsedMs = (ProcessInfo.processInfo.systemUptime - startedAt) * 1000 + dlog( + "workspace.backgroundPrime.finish workspace=\(workspaceId.uuidString.prefix(5)) " + + "reason=\(reason) ms=\(String(format: "%.2f", elapsedMs))" + ) +#endif + return + } + } + } + + @MainActor + private func stepBackgroundWorkspacePrime(workspaceId: UUID) -> BackgroundWorkspacePrimeState { + guard tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId) else { + return .completed(reason: "already_cleared") + } + guard let workspace = tabManager.tabs.first(where: { $0.id == workspaceId }) else { + tabManager.completeBackgroundWorkspaceLoad(for: workspaceId) + return .completed(reason: "workspace_removed") + } + + workspace.requestBackgroundTerminalSurfaceStartIfNeeded() + guard workspace.hasLoadedTerminalSurface() else { + return .pending + } + + tabManager.completeBackgroundWorkspaceLoad(for: workspaceId) + return .completed(reason: "surface_ready") + } + private func addTab() { tabManager.addTab() sidebarSelectionState.selection = .tabs diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index abdb3ea6..cd5ed2da 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -564,6 +564,7 @@ class TabManager: ObservableObject { @Published var tabs: [Workspace] = [] @Published private(set) var isWorkspaceCycleHot: Bool = false + @Published private(set) var pendingBackgroundWorkspaceLoadIds: Set = [] /// Global monotonically increasing counter for CMUX_PORT ordinal assignment. /// Static so port ranges don't overlap across multiple windows (each window has its own TabManager). @@ -799,6 +800,7 @@ class TabManager: ObservableObject { func addWorkspace( workingDirectory overrideWorkingDirectory: String? = nil, select: Bool = true, + eagerLoadTerminal: Bool = false, placementOverride: NewWorkspacePlacement? = nil ) -> Workspace { sentryBreadcrumb("workspace.create", data: ["tabCount": tabs.count + 1]) @@ -819,6 +821,10 @@ class TabManager: ObservableObject { } else { tabs.append(newWorkspace) } + if eagerLoadTerminal { + requestBackgroundWorkspaceLoad(for: newWorkspace.id) + newWorkspace.requestBackgroundTerminalSurfaceStartIfNeeded() + } if select { selectedTabId = newWorkspace.id NotificationCenter.default.post( @@ -837,9 +843,25 @@ class TabManager: ObservableObject { return newWorkspace } + func requestBackgroundWorkspaceLoad(for workspaceId: UUID) { + guard pendingBackgroundWorkspaceLoadIds.insert(workspaceId).inserted else { return } + } + + func completeBackgroundWorkspaceLoad(for workspaceId: UUID) { + guard pendingBackgroundWorkspaceLoadIds.remove(workspaceId) != nil else { return } + } + + func pruneBackgroundWorkspaceLoads(existingIds: Set) { + let pruned = pendingBackgroundWorkspaceLoadIds.intersection(existingIds) + guard pruned != pendingBackgroundWorkspaceLoadIds else { return } + pendingBackgroundWorkspaceLoadIds = pruned + } + // Keep addTab as convenience alias @discardableResult - func addTab(select: Bool = true) -> Workspace { addWorkspace(select: select) } + func addTab(select: Bool = true, eagerLoadTerminal: Bool = false) -> Workspace { + addWorkspace(select: select, eagerLoadTerminal: eagerLoadTerminal) + } func terminalPanelForWorkspaceConfigInheritanceSource() -> TerminalPanel? { guard let workspace = selectedWorkspace else { return nil } diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 56d7205b..dd9d9a1a 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -2687,10 +2687,11 @@ class TerminalController { let startedAt = ProcessInfo.processInfo.systemUptime #endif v2MainSync { - let ws = tabManager.addWorkspace(workingDirectory: cwd, select: shouldFocus) - if !shouldFocus, let terminalPanel = ws.focusedTerminalPanel { - terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() - } + let ws = tabManager.addWorkspace( + workingDirectory: cwd, + select: shouldFocus, + eagerLoadTerminal: !shouldFocus + ) newId = ws.id } #if DEBUG @@ -10299,10 +10300,7 @@ class TerminalController { let startedAt = ProcessInfo.processInfo.systemUptime #endif DispatchQueue.main.sync { - let workspace = tabManager.addTab(select: focus) - if !focus, let terminalPanel = workspace.focusedTerminalPanel { - terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() - } + let workspace = tabManager.addTab(select: focus, eagerLoadTerminal: !focus) newTabId = workspace.id } #if DEBUG diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index a4a870ea..cf3d064b 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -1480,6 +1480,18 @@ final class Workspace: Identifiable, ObservableObject { return surfaceKind(for: panel) } + func requestBackgroundTerminalSurfaceStartIfNeeded() { + for terminalPanel in panels.values.compactMap({ $0 as? TerminalPanel }) { + terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() + } + } + + func hasLoadedTerminalSurface() -> Bool { + let terminalPanels = panels.values.compactMap { $0 as? TerminalPanel } + guard !terminalPanels.isEmpty else { return true } + return terminalPanels.contains { $0.surface.surface != nil } + } + func panelTitle(panelId: UUID) -> String? { guard let panel = panels[panelId] else { return nil } let fallback = panelTitles[panelId] ?? panel.displayTitle From c58ad84ea0f5f0993061767976826c164064de36 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:54:46 -0800 Subject: [PATCH 131/232] Remove invalid docs text inspection tests --- .../test_browser_skill_get_selector_docs.py | 34 ------------------ tests_v2/test_browser_skill_js_error_docs.py | 35 ------------------- 2 files changed, 69 deletions(-) delete mode 100644 tests_v2/test_browser_skill_get_selector_docs.py delete mode 100644 tests_v2/test_browser_skill_js_error_docs.py diff --git a/tests_v2/test_browser_skill_get_selector_docs.py b/tests_v2/test_browser_skill_get_selector_docs.py deleted file mode 100644 index 92c5a8f9..00000000 --- a/tests_v2/test_browser_skill_get_selector_docs.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 -"""Regression checks for cmux-browser get selector examples.""" - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent)) -from cmux import cmuxError - - -ROOT = Path(__file__).resolve().parents[1] -COMMANDS = ROOT / "skills/cmux-browser/references/commands.md" - - -def _must(cond: bool, msg: str) -> None: - if not cond: - raise cmuxError(msg) - - -def main() -> int: - commands = COMMANDS.read_text(encoding="utf-8") - - _must("`agent-browser get text ` -> `cmux browser get text `" in commands, "Expected get text mapping to mention selector support") - _must("cmux browser get text body" in commands, "Expected get text body example") - _must("cmux browser get html body" in commands, "Expected get html body example") - _must('cmux browser get value "#email"' in commands, "Expected get value selector example") - _must('cmux browser get attr "#email" --attr placeholder' in commands, "Expected get attr selector example") - _must("cmux browser get text|html|value|attr|count|box|styles ..." not in commands, "Unexpected bare get example block") - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests_v2/test_browser_skill_js_error_docs.py b/tests_v2/test_browser_skill_js_error_docs.py deleted file mode 100644 index 566ab10f..00000000 --- a/tests_v2/test_browser_skill_js_error_docs.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -"""Regression checks for cmux-browser js_error troubleshooting docs.""" - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent)) -from cmux import cmuxError - - -ROOT = Path(__file__).resolve().parents[1] -SKILL = ROOT / "skills/cmux-browser/SKILL.md" - - -def _must(cond: bool, msg: str) -> None: - if not cond: - raise cmuxError(msg) - - -def main() -> int: - skill = SKILL.read_text(encoding="utf-8") - - _must("## Troubleshooting" in skill, "Expected Troubleshooting section in cmux-browser skill") - _must("### `js_error` on `snapshot --interactive` or `eval`" in skill, "Expected js_error troubleshooting heading") - _must("cmux browser surface:7 get url" in skill, "Expected get url recovery step") - _must("cmux browser surface:7 get text body" in skill, "Expected get text body recovery step") - _must("cmux browser surface:7 get html body" in skill, "Expected get html body recovery step") - _must("when `snapshot --interactive` or `eval` returns `js_error`" in skill, "Expected js_error fallback guidance") - _must("navigate to a simpler intermediate page" in skill, "Expected simpler-page fallback guidance") - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) From bb052198e5d18941e815c4a18ed859a57ef47a32 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:57:18 -0800 Subject: [PATCH 132/232] Document browser navigation verification loop (#974) * Document browser navigation verification loop * Add docs regression test for browser navigation guidance * Remove invalid docs text inspection test --- skills/cmux-browser/SKILL.md | 31 ++++++++++++------- .../templates/authenticated-session.sh | 1 + .../cmux-browser/templates/form-automation.sh | 1 + 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/skills/cmux-browser/SKILL.md b/skills/cmux-browser/SKILL.md index 33e6d47d..aed36c61 100644 --- a/skills/cmux-browser/SKILL.md +++ b/skills/cmux-browser/SKILL.md @@ -10,19 +10,21 @@ Use this skill for browser tasks inside cmux webviews. ## Core Workflow 1. Open or target a browser surface. -2. Snapshot (`--interactive`) to get fresh element refs. -3. Act with refs (`click`, `fill`, `type`, `select`, `press`). -4. Wait for state changes. -5. Re-snapshot after DOM/navigation changes. +2. Verify navigation with `get url` before waiting or snapshotting. +3. Snapshot (`--interactive`) to get fresh element refs. +4. Act with refs (`click`, `fill`, `type`, `select`, `press`). +5. Wait for state changes. +6. Re-snapshot after DOM/navigation changes. ```bash -cmux browser open https://example.com --json +cmux --json browser open https://example.com # use returned surface ref, for example: surface:7 +cmux browser surface:7 get url +cmux browser surface:7 wait --load-state complete --timeout-ms 15000 cmux browser surface:7 snapshot --interactive cmux browser surface:7 fill e1 "hello" -cmux browser surface:7 click e2 --snapshot-after --json -cmux browser surface:7 wait --load-state complete --timeout-ms 15000 +cmux --json browser surface:7 click e2 --snapshot-after cmux browser surface:7 snapshot --interactive ``` @@ -58,11 +60,13 @@ cmux browser wait --function "document.readyState === 'complete'" --ti ### Form Submit ```bash -cmux browser open https://example.com/signup --json +cmux --json browser open https://example.com/signup +cmux browser surface:7 get url +cmux browser surface:7 wait --load-state complete --timeout-ms 15000 cmux browser surface:7 snapshot --interactive cmux browser surface:7 fill e1 "Jane Doe" cmux browser surface:7 fill e2 "jane@example.com" -cmux browser surface:7 click e3 --snapshot-after --json +cmux --json browser surface:7 click e3 --snapshot-after cmux browser surface:7 wait --url-contains "/welcome" --timeout-ms 15000 cmux browser surface:7 snapshot --interactive ``` @@ -77,13 +81,16 @@ cmux browser surface:7 get value e11 --json ### Stable Agent Loop (Recommended) ```bash -# snapshot -> action -> wait -> snapshot -cmux browser surface:7 snapshot --interactive -cmux browser surface:7 click e5 --snapshot-after --json +# navigate -> verify -> wait -> snapshot -> action -> snapshot +cmux browser surface:7 get url cmux browser surface:7 wait --load-state complete --timeout-ms 15000 cmux browser surface:7 snapshot --interactive +cmux --json browser surface:7 click e5 --snapshot-after +cmux browser surface:7 snapshot --interactive ``` +If `get url` is empty or `about:blank`, navigate first instead of waiting on load state. + ## Deep-Dive References | Reference | When to Use | diff --git a/skills/cmux-browser/templates/authenticated-session.sh b/skills/cmux-browser/templates/authenticated-session.sh index bf19a274..284b77af 100755 --- a/skills/cmux-browser/templates/authenticated-session.sh +++ b/skills/cmux-browser/templates/authenticated-session.sh @@ -10,6 +10,7 @@ if [ -f "$STATE_FILE" ]; then fi cmux browser "$SURFACE" goto "$DASHBOARD_URL" +cmux browser "$SURFACE" get url cmux browser "$SURFACE" wait --load-state complete --timeout-ms 15000 cmux browser "$SURFACE" snapshot --interactive diff --git a/skills/cmux-browser/templates/form-automation.sh b/skills/cmux-browser/templates/form-automation.sh index f8a9406c..0c50d15e 100755 --- a/skills/cmux-browser/templates/form-automation.sh +++ b/skills/cmux-browser/templates/form-automation.sh @@ -5,6 +5,7 @@ URL="${1:-https://example.com/form}" SURFACE="${2:-surface:1}" cmux browser "$SURFACE" goto "$URL" +cmux browser "$SURFACE" get url cmux browser "$SURFACE" wait --load-state complete --timeout-ms 15000 cmux browser "$SURFACE" snapshot --interactive From 29054dc709c276971e5e69bc06dce067bcb12cba Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:00:42 -0800 Subject: [PATCH 133/232] Add sidebar help menu to footer (#958) * Add sidebar help menu * Fix help menu test wiring * Fix help menu accessibility * Use native popup for help menu * Use icon button for sidebar help * Add feedback composer and feedback API * Allow preview builds without feedback env * Tighten feedback upload limits * Adjust sidebar footer padding * Tighten sidebar footer spacing * Add link affordances to help menu * Polish sidebar feedback composer * Move feedback icon to trailing edge * Normalize help menu trailing icon sizes * Enlarge help menu trailing icons * Reduce help menu link icon size * Shrink help menu link arrow * Reduce help menu link arrow again * Fix feedback message editor focus * Add send feedback keyboard shortcut * Polish feedback launch and delivery --- GhosttyTabs.xcodeproj/project.pbxproj | 4 + Resources/Localizable.xcstrings | 578 ++++++++ Sources/AppDelegate.swift | 22 +- Sources/ContentView.swift | 1195 ++++++++++++++++- Sources/KeyboardShortcutSettings.swift | 5 + Sources/TabManager.swift | 1 + Sources/cmuxApp.swift | 54 +- .../AppDelegateShortcutRoutingTests.swift | 31 +- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 29 + cmuxUITests/SidebarHelpMenuUITests.swift | 296 ++++ web/.env.example | 3 + web/app/api/feedback/route.ts | 340 +++++ web/app/env.ts | 18 + web/bun.lock | 24 + web/next.config.ts | 1 + web/package-lock.json | 192 ++- web/package.json | 6 +- 17 files changed, 2771 insertions(+), 28 deletions(-) create mode 100644 cmuxUITests/SidebarHelpMenuUITests.swift create mode 100644 web/.env.example create mode 100644 web/app/api/feedback/route.ts create mode 100644 web/app/env.ts diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 9b977a9b..ece4ea09 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ A5002000 /* THIRD_PARTY_LICENSES.md in Resources */ = {isa = PBXBuildFile; fileRef = A5002001 /* THIRD_PARTY_LICENSES.md */; }; B9000012A1B2C3D4E5F60719 /* AutomationSocketUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000011A1B2C3D4E5F60719 /* AutomationSocketUITests.swift */; }; B8F266236A1A3D9A45BD840F /* SidebarResizeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */; }; + B8F266246A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F266256A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift */; }; C0B4D9B0A1B2C3D4E5F60718 /* UpdatePillUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */; }; B9000014A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000013A1B2C3D4E5F60719 /* JumpToUnreadUITests.swift */; }; B9000015A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9000016A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift */; }; @@ -203,6 +204,7 @@ A5001241 /* WindowDecorationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowDecorationsController.swift; sourceTree = ""; }; A5001611 /* SessionPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionPersistence.swift; sourceTree = ""; }; 818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarResizeUITests.swift; sourceTree = ""; }; + B8F266256A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarHelpMenuUITests.swift; sourceTree = ""; }; C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatePillUITests.swift; sourceTree = ""; }; A5001101 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B2E7294509CC42FE9191870E /* xterm-ghostty */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ghostty/terminfo/78/xterm-ghostty"; sourceTree = ""; }; @@ -433,6 +435,7 @@ B9000019A1B2C3D4E5F60719 /* CloseWorkspaceConfirmDialogUITests.swift */, B9000016A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift */, 818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */, + B8F266256A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift */, D0E0F0B1A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift */, D0E0F0B3A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift */, C0B4D9B1A1B2C3D4E5F60718 /* UpdatePillUITests.swift */, @@ -667,6 +670,7 @@ B9000023A1B2C3D4E5F60719 /* CloseWorkspaceCmdDUITests.swift in Sources */, B9000015A1B2C3D4E5F60719 /* MultiWindowNotificationsUITests.swift in Sources */, B8F266236A1A3D9A45BD840F /* SidebarResizeUITests.swift in Sources */, + B8F266246A1A3D9A45BD840F /* SidebarHelpMenuUITests.swift in Sources */, D0E0F0B0A1B2C3D4E5F60718 /* BrowserPaneNavigationKeybindUITests.swift in Sources */, D0E0F0B2A1B2C3D4E5F60718 /* BrowserOmnibarSuggestionsUITests.swift in Sources */, C0B4D9B0A1B2C3D4E5F60718 /* UpdatePillUITests.swift in Sources */, diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 2139e387..d7943f25 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -567,6 +567,584 @@ } } }, + "debug.devBuildBanner.show": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Dev Build Banner" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "開発ビルドバナーを表示" + } + } + } + }, + "debug.devBuildBanner.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "THIS IS A DEV BUILD" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "これは開発ビルドです" + } + } + } + }, + "sidebar.help.button": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Help" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ヘルプ" + } + } + } + }, + "sidebar.help.changelog": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Changelog" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "更新履歴" + } + } + } + }, + "sidebar.help.githubIssues": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub Issues" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "GitHub Issues" + } + } + } + }, + "sidebar.help.sendFeedback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Feedback" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバックを送信" + } + } + } + }, + "sidebar.help.feedback.attachImages": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Attach Images" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "画像を添付" + } + } + } + }, + "sidebar.help.feedback.attachImages.prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Attach" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "添付" + } + } + } + }, + "sidebar.help.feedback.attachImages.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Attach Images" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "画像を添付" + } + } + } + }, + "sidebar.help.feedback.attachmentsHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Up to 10 images. Large images will be optimized before sending." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "画像は最大10枚まで添付できます。大きな画像は送信前に最適化されます。" + } + } + } + }, + "sidebar.help.feedback.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "sidebar.help.feedback.connectionError": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't send feedback. Check your connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバックを送信できませんでした。接続を確認して、もう一度お試しください。" + } + } + } + }, + "sidebar.help.feedback.done": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Done" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "完了" + } + } + } + }, + "sidebar.help.feedback.email": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your Email" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "メールアドレス" + } + } + } + }, + "sidebar.help.feedback.emailPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "you@example.com" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "you@example.com" + } + } + } + }, + "sidebar.help.feedback.emptyMessage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a message before sending." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "送信する前にメッセージを入力してください。" + } + } + } + }, + "sidebar.help.feedback.endpointError": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Feedback is unavailable right now. Email founders@manaflow.com instead." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在フィードバックを送信できません。代わりに founders@manaflow.com までメールしてください。" + } + } + } + }, + "sidebar.help.feedback.genericError": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't send feedback. Please try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバックを送信できませんでした。もう一度お試しください。" + } + } + } + }, + "sidebar.help.feedback.imageTooLarge": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Each image must be 4 MB or smaller." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "各画像は 4 MB 以下にしてください。" + } + } + } + }, + "sidebar.help.feedback.invalidEmail": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a valid email address." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "有効なメールアドレスを入力してください。" + } + } + } + }, + "sidebar.help.feedback.invalidImageSelection": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "One of the selected files could not be attached." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択したファイルのうち1つを添付できませんでした。" + } + } + } + }, + "sidebar.help.feedback.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Message" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "メッセージ" + } + } + } + }, + "sidebar.help.feedback.messagePlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Share feedback, feature requests, or issues." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバック、機能要望、不具合をお知らせください。" + } + } + } + }, + "sidebar.help.feedback.messageTooLong": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your message is too long." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "メッセージが長すぎます。" + } + } + } + }, + "sidebar.help.feedback.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A human will read this! You can also reach us at founders@manaflow.com." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "人間がこれを読みます。founders@manaflow.com 宛てに直接ご連絡いただくこともできます。" + } + } + } + }, + "sidebar.help.feedback.rateLimited": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Too many feedback attempts. Please try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバックの送信回数が多すぎます。しばらくしてからもう一度お試しください。" + } + } + } + }, + "sidebar.help.feedback.removeAttachment": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "削除" + } + } + } + }, + "sidebar.help.feedback.send": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "送信" + } + } + } + }, + "sidebar.help.feedback.successBody": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A human will read this! You can also reach us at founders@manaflow.com." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "人間がこれを読みます。founders@manaflow.com 宛てに直接ご連絡いただくこともできます。" + } + } + } + }, + "sidebar.help.feedback.successTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Thanks for the feedback." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバックありがとうございます。" + } + } + } + }, + "sidebar.help.feedback.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Feedback" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フィードバックを送信" + } + } + } + }, + "sidebar.help.feedback.tooManyImages": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You can attach up to 10 images." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "画像は最大10枚まで添付できます。" + } + } + } + }, + "sidebar.help.feedback.totalImagesTooLarge": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "These images are too large to send together. Remove a few and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "これらの画像はまとめて送信するには大きすぎます。いくつか削除してもう一度お試しください。" + } + } + } + }, + "sidebar.help.feedback.validationError": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check your message and attachments, then try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "メッセージと添付ファイルを確認して、もう一度お試しください。" + } + } + } + }, "about.github": { "extractionState": "manual", "localizations": { diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index b015a5ea..400b5d14 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -4858,8 +4858,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent @MainActor static func presentPreferencesWindow( - showFallbackSettingsWindow: @MainActor () -> Void = { - SettingsWindowController.shared.show() + navigationTarget: SettingsNavigationTarget? = nil, + showFallbackSettingsWindow: @MainActor (SettingsNavigationTarget?) -> Void = { target in + SettingsWindowController.shared.show(navigationTarget: target) }, activateApplication: @MainActor () -> Void = { NSApp.activate(ignoringOtherApps: true) @@ -4868,7 +4869,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent #if DEBUG dlog("settings.open.present path=customWindowDirect") #endif - showFallbackSettingsWindow() + showFallbackSettingsWindow(navigationTarget) activateApplication() #if DEBUG dlog("settings.open.present activate=1") @@ -4876,11 +4877,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } @MainActor - func openPreferencesWindow(debugSource: String) { + func openPreferencesWindow(debugSource: String, navigationTarget: SettingsNavigationTarget? = nil) { #if DEBUG dlog("settings.open.request source=\(debugSource)") #endif - Self.presentPreferencesWindow() + Self.presentPreferencesWindow(navigationTarget: navigationTarget) } @objc func openPreferencesWindow() { @@ -6610,6 +6611,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return true } + if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .sendFeedback)) { + guard let targetContext = preferredMainWindowContextForShortcuts(event: event), + let targetWindow = targetContext.window ?? windowForMainWindowId(targetContext.windowId) else { + return false + } + setActiveMainWindow(targetWindow) + bringToFront(targetWindow) + NotificationCenter.default.post(name: .feedbackComposerRequested, object: targetWindow) + return true + } + // Check Jump to Unread shortcut if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .jumpToUnread)) { #if DEBUG diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index a289f0eb..418e023a 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1,5 +1,6 @@ import AppKit import Bonsplit +import ImageIO import SwiftUI import ObjectiveC import UniformTypeIdentifiers @@ -1262,6 +1263,7 @@ struct ContentView: View { @State private var commandPaletteScrollTargetAnchor: UnitPoint? @State private var commandPaletteRestoreFocusTarget: CommandPaletteRestoreFocusTarget? @State private var commandPaletteUsageHistoryByCommandId: [String: CommandPaletteUsageEntry] = [:] + @State private var isFeedbackComposerPresented = false @AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey) private var commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus @AppStorage(BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowserKey) @@ -1780,6 +1782,7 @@ struct ContentView: View { private var sidebarView: some View { VerticalTabsSidebar( updateViewModel: updateViewModel, + onSendFeedback: presentFeedbackComposer, selection: $sidebarSelectionState.selection, selectedTabIds: $selectedTabIds, lastSidebarSelectionIndex: $lastSidebarSelectionIndex @@ -2376,6 +2379,17 @@ struct ContentView: View { _ = handleCommandPaletteRenameDeleteBackward(modifiers: []) }) + view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .feedbackComposerRequested)) { notification in + let requestedWindow = notification.object as? NSWindow + guard Self.shouldHandleCommandPaletteRequest( + observedWindow: observedWindow, + requestedWindow: requestedWindow, + keyWindow: NSApp.keyWindow, + mainWindow: NSApp.mainWindow + ) else { return } + presentFeedbackComposer() + }) + view = AnyView(view.background(WindowAccessor(dedupeByWindow: false) { window in MainActor.assumeIsolated { let overlayController = commandPaletteWindowOverlayController(for: window) @@ -2443,6 +2457,9 @@ struct ContentView: View { }) view = AnyView(view.ignoresSafeArea()) + view = AnyView(view.sheet(isPresented: $isFeedbackComposerPresented) { + SidebarFeedbackComposerSheet() + }) view = AnyView(view.onDisappear { removeSidebarResizerPointerMonitor() @@ -4559,6 +4576,12 @@ struct ContentView: View { beginRenameWorkspaceFlow() } + private func presentFeedbackComposer() { + DispatchQueue.main.async { + isFeedbackComposerPresented = true + } + } + static func shouldHandleCommandPaletteRequest( observedWindow: NSWindow?, requestedWindow: NSWindow?, @@ -5628,6 +5651,7 @@ private struct SidebarResizerAccessibilityModifier: ViewModifier { struct VerticalTabsSidebar: View { @ObservedObject var updateViewModel: UpdateViewModel + let onSendFeedback: () -> Void @EnvironmentObject var tabManager: TabManager @Binding var selection: SidebarSelection @Binding var selectedTabIds: Set @@ -5701,15 +5725,8 @@ struct VerticalTabsSidebar: View { .background(Color.clear) .modifier(ClearScrollBackground()) } -#if DEBUG - SidebarDevFooter(updateViewModel: updateViewModel) + SidebarFooter(updateViewModel: updateViewModel, onSendFeedback: onSendFeedback) .frame(maxWidth: .infinity, alignment: .leading) -#else - UpdatePill(model: updateViewModel) - .padding(.horizontal, 10) - .padding(.bottom, 10) - .frame(maxWidth: .infinity, alignment: .leading) -#endif } .accessibilityIdentifier("Sidebar") .ignoresSafeArea() @@ -5857,6 +5874,343 @@ enum ShortcutHintDebugSettings { } } +enum DevBuildBannerDebugSettings { + static let sidebarBannerVisibleKey = "showSidebarDevBuildBanner" + static let defaultShowSidebarBanner = true + + static func showSidebarBanner(defaults: UserDefaults = .standard) -> Bool { + guard defaults.object(forKey: sidebarBannerVisibleKey) != nil else { + return defaultShowSidebarBanner + } + return defaults.bool(forKey: sidebarBannerVisibleKey) + } +} + +private enum FeedbackComposerSettings { + static let storedEmailKey = "sidebarHelpFeedbackEmail" + static let endpointEnvironmentKey = "CMUX_FEEDBACK_API_URL" + static let defaultEndpoint = "https://cmux.dev/api/feedback" + static let foundersEmail = "founders@manaflow.com" + static let maxMessageLength = 4_000 + static let maxAttachmentCount = 10 + // Keep the multipart body below Vercel's 4.5 MB request limit. + static let maxTotalAttachmentBytes = 4 * 1_024 * 1_024 + static let targetTotalAttachmentUploadBytes = 3_500_000 + + static func endpointURL() -> URL? { + let env = ProcessInfo.processInfo.environment + if let override = env[endpointEnvironmentKey]?.trimmingCharacters(in: .whitespacesAndNewlines), + !override.isEmpty { + return URL(string: override) + } + return URL(string: defaultEndpoint) + } +} + +private struct FeedbackComposerAttachment: Identifiable { + let id = UUID() + let url: URL + let fileName: String + let fileSize: Int64 + let mimeType: String + + var standardizedPath: String { + url.standardizedFileURL.path + } + + var displaySize: String { + ByteCountFormatter.string(fromByteCount: fileSize, countStyle: .file) + } + + init(url: URL) throws { + let resourceValues = try url.resourceValues(forKeys: [ + .contentTypeKey, + .fileSizeKey, + .isRegularFileKey, + .nameKey, + ]) + guard resourceValues.isRegularFile != false else { + throw CocoaError(.fileReadUnknown) + } + + self.url = url + self.fileName = resourceValues.name ?? url.lastPathComponent + self.fileSize = Int64(resourceValues.fileSize ?? 0) + self.mimeType = resourceValues.contentType?.preferredMIMEType ?? "application/octet-stream" + } +} + +private struct PreparedFeedbackComposerAttachment { + let fileName: String + let mimeType: String + let data: Data +} + +private struct FeedbackComposerAppMetadata { + let appVersion: String + let appBuild: String + let appCommit: String + let bundleIdentifier: String + let osVersion: String + let localeIdentifier: String + + static var current: FeedbackComposerAppMetadata { + let infoDictionary = Bundle.main.infoDictionary ?? [:] + let env = ProcessInfo.processInfo.environment + let commit = (infoDictionary["CMUXCommit"] as? String).flatMap { value in + value.isEmpty ? nil : value + } ?? env["CMUX_COMMIT"] + + return FeedbackComposerAppMetadata( + appVersion: infoDictionary["CFBundleShortVersionString"] as? String ?? "", + appBuild: infoDictionary["CFBundleVersion"] as? String ?? "", + appCommit: commit ?? "", + bundleIdentifier: Bundle.main.bundleIdentifier ?? "", + osVersion: ProcessInfo.processInfo.operatingSystemVersionString, + localeIdentifier: Locale.preferredLanguages.first ?? Locale.current.identifier + ) + } +} + +private enum FeedbackComposerSubmissionError: Error { + case invalidEndpoint + case invalidResponse + case rejected(statusCode: Int) + case attachmentReadFailed + case attachmentPreparationFailed + case transport(URLError) +} + +private enum FeedbackComposerClient { + private static let passthroughAttachmentMIMETypes: Set = [ + "image/gif", + "image/heic", + "image/heif", + "image/jpeg", + "image/png", + "image/tiff", + "image/webp", + ] + private static let optimizedAttachmentDimensions: [Int] = [2800, 2400, 2000, 1600, 1280, 1024, 768, 640, 512] + private static let optimizedAttachmentQualities: [CGFloat] = [0.82, 0.72, 0.62, 0.52, 0.42, 0.32] + private static let optimizedAttachmentMIMEType = "image/jpeg" + + static func submit( + email: String, + message: String, + attachments: [FeedbackComposerAttachment] + ) async throws { + guard let endpointURL = FeedbackComposerSettings.endpointURL() else { + throw FeedbackComposerSubmissionError.invalidEndpoint + } + + let metadata = FeedbackComposerAppMetadata.current + let boundary = "Boundary-\(UUID().uuidString)" + let preparedAttachments = try prepareAttachmentsForUpload(attachments) + + var request = URLRequest(url: endpointURL) + request.httpMethod = "POST" + request.timeoutInterval = 30 + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + request.setValue("application/json", forHTTPHeaderField: "Accept") + + var body = Data() + appendField("email", value: email, to: &body, boundary: boundary) + appendField("message", value: message, to: &body, boundary: boundary) + appendField("appVersion", value: metadata.appVersion, to: &body, boundary: boundary) + appendField("appBuild", value: metadata.appBuild, to: &body, boundary: boundary) + appendField("appCommit", value: metadata.appCommit, to: &body, boundary: boundary) + appendField("bundleIdentifier", value: metadata.bundleIdentifier, to: &body, boundary: boundary) + appendField("osVersion", value: metadata.osVersion, to: &body, boundary: boundary) + appendField("locale", value: metadata.localeIdentifier, to: &body, boundary: boundary) + + for attachment in preparedAttachments { + appendFile( + named: "attachments", + attachment: attachment, + to: &body, + boundary: boundary + ) + } + + body.append(Data("--\(boundary)--\r\n".utf8)) + request.httpBody = body + + let data: Data + let response: URLResponse + do { + (data, response) = try await URLSession.shared.data(for: request) + } catch let error as URLError { + throw FeedbackComposerSubmissionError.transport(error) + } catch { + throw FeedbackComposerSubmissionError.invalidResponse + } + + guard let httpResponse = response as? HTTPURLResponse else { + throw FeedbackComposerSubmissionError.invalidResponse + } + + guard (200..<300).contains(httpResponse.statusCode) else { + if let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let errorMessage = payload["error"] as? String, + errorMessage.isEmpty == false { + NSLog("feedback.submit.rejected status=%@ error=%@", String(httpResponse.statusCode), errorMessage) + } + throw FeedbackComposerSubmissionError.rejected(statusCode: httpResponse.statusCode) + } + } + + private static func appendField( + _ name: String, + value: String, + to body: inout Data, + boundary: String + ) { + body.append(Data("--\(boundary)\r\n".utf8)) + body.append(Data("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".utf8)) + body.append(Data(value.utf8)) + body.append(Data("\r\n".utf8)) + } + + private static func prepareAttachmentsForUpload( + _ attachments: [FeedbackComposerAttachment] + ) throws -> [PreparedFeedbackComposerAttachment] { + guard attachments.isEmpty == false else { return [] } + + struct IndexedAttachment { + let index: Int + let attachment: FeedbackComposerAttachment + } + + let sortedAttachments = attachments.enumerated() + .map { IndexedAttachment(index: $0.offset, attachment: $0.element) } + .sorted { lhs, rhs in + lhs.attachment.fileSize > rhs.attachment.fileSize + } + + var preparedByIndex: [Int: PreparedFeedbackComposerAttachment] = [:] + var remainingBudget = FeedbackComposerSettings.targetTotalAttachmentUploadBytes + var remainingCount = sortedAttachments.count + + for item in sortedAttachments { + let perAttachmentBudget = max(1, remainingBudget / max(remainingCount, 1)) + let preparedAttachment = try prepareAttachmentForUpload( + item.attachment, + maximumByteCount: perAttachmentBudget + ) + preparedByIndex[item.index] = preparedAttachment + remainingBudget -= preparedAttachment.data.count + remainingCount -= 1 + } + + let preparedAttachments = attachments.indices.compactMap { preparedByIndex[$0] } + let totalBytes = preparedAttachments.reduce(0) { $0 + $1.data.count } + guard totalBytes <= FeedbackComposerSettings.targetTotalAttachmentUploadBytes else { + throw FeedbackComposerSubmissionError.attachmentPreparationFailed + } + return preparedAttachments + } + + private static func prepareAttachmentForUpload( + _ attachment: FeedbackComposerAttachment, + maximumByteCount: Int + ) throws -> PreparedFeedbackComposerAttachment { + if attachment.fileSize > 0, + attachment.fileSize <= Int64(maximumByteCount), + passthroughAttachmentMIMETypes.contains(attachment.mimeType), + let fileData = try? Data(contentsOf: attachment.url, options: .mappedIfSafe) { + return PreparedFeedbackComposerAttachment( + fileName: attachment.fileName, + mimeType: attachment.mimeType, + data: fileData + ) + } + + guard let imageSource = CGImageSourceCreateWithURL(attachment.url as CFURL, nil) else { + throw FeedbackComposerSubmissionError.attachmentReadFailed + } + + for maxPixelDimension in optimizedAttachmentDimensions { + guard let cgImage = downsampledImage( + from: imageSource, + maxPixelDimension: maxPixelDimension + ) else { continue } + + for compressionQuality in optimizedAttachmentQualities { + guard let jpegData = jpegData( + from: cgImage, + compressionQuality: compressionQuality + ) else { continue } + guard jpegData.count <= maximumByteCount else { continue } + + return PreparedFeedbackComposerAttachment( + fileName: optimizedFileName(for: attachment), + mimeType: optimizedAttachmentMIMEType, + data: jpegData + ) + } + } + + throw FeedbackComposerSubmissionError.attachmentPreparationFailed + } + + private static func downsampledImage( + from imageSource: CGImageSource, + maxPixelDimension: Int + ) -> CGImage? { + CGImageSourceCreateThumbnailAtIndex( + imageSource, + 0, + [ + kCGImageSourceCreateThumbnailFromImageAlways: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceShouldCache: false, + kCGImageSourceShouldCacheImmediately: false, + kCGImageSourceThumbnailMaxPixelSize: maxPixelDimension, + ] as CFDictionary + ) + } + + private static func jpegData( + from image: CGImage, + compressionQuality: CGFloat + ) -> Data? { + let bitmap = NSBitmapImageRep(cgImage: image) + return bitmap.representation( + using: .jpeg, + properties: [ + .compressionFactor: compressionQuality, + ] + ) + } + + private static func optimizedFileName( + for attachment: FeedbackComposerAttachment + ) -> String { + let baseName = (attachment.fileName as NSString).deletingPathExtension + return "\(baseName.isEmpty ? "feedback-image" : baseName).jpg" + } + + private static func appendFile( + named fieldName: String, + attachment: PreparedFeedbackComposerAttachment, + to body: inout Data, + boundary: String + ) { + let sanitizedFileName = attachment.fileName.replacingOccurrences(of: "\"", with: "") + + body.append(Data("--\(boundary)\r\n".utf8)) + body.append( + Data( + "Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(sanitizedFileName)\"\r\n".utf8 + ) + ) + body.append(Data("Content-Type: \(attachment.mimeType)\r\n\r\n".utf8)) + body.append(attachment.data) + body.append(Data("\r\n".utf8)) + } +} + enum SidebarDragLifecycleNotification { static let stateDidChange = Notification.Name("cmux.sidebarDragStateDidChange") static let requestClear = Notification.Name("cmux.sidebarDragRequestClear") @@ -6231,19 +6585,832 @@ private final class SidebarShortcutHintModifierMonitor: ObservableObject { } } +private struct SidebarFooter: View { + @ObservedObject var updateViewModel: UpdateViewModel + let onSendFeedback: () -> Void + + var body: some View { +#if DEBUG + SidebarDevFooter(updateViewModel: updateViewModel, onSendFeedback: onSendFeedback) +#else + SidebarFooterButtons(updateViewModel: updateViewModel, onSendFeedback: onSendFeedback) + .padding(.leading, 6) + .padding(.trailing, 10) + .padding(.bottom, 6) +#endif + } +} + +private struct SidebarFooterButtons: View { + @ObservedObject var updateViewModel: UpdateViewModel + let onSendFeedback: () -> Void + + var body: some View { + HStack(spacing: 4) { + SidebarHelpMenuButton(onSendFeedback: onSendFeedback) + UpdatePill(model: updateViewModel) + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +private struct FeedbackComposerMessageEditor: NSViewRepresentable { + @Binding var text: String + let placeholder: String + let accessibilityLabel: String + let accessibilityIdentifier: String + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + func makeNSView(context: Context) -> FeedbackComposerMessageEditorView { + let view = FeedbackComposerMessageEditorView() + view.placeholder = placeholder + view.textView.string = text + view.textView.delegate = context.coordinator + view.textView.setAccessibilityLabel(accessibilityLabel) + view.textView.setAccessibilityIdentifier(accessibilityIdentifier) + view.setAccessibilityIdentifier(accessibilityIdentifier) + return view + } + + func updateNSView(_ nsView: FeedbackComposerMessageEditorView, context: Context) { + if nsView.textView.string != text { + nsView.textView.string = text + } + nsView.placeholder = placeholder + nsView.textView.setAccessibilityLabel(accessibilityLabel) + nsView.textView.setAccessibilityIdentifier(accessibilityIdentifier) + nsView.setAccessibilityIdentifier(accessibilityIdentifier) + } + + final class Coordinator: NSObject, NSTextViewDelegate { + var parent: FeedbackComposerMessageEditor + + init(parent: FeedbackComposerMessageEditor) { + self.parent = parent + } + + func textDidChange(_ notification: Notification) { + guard let textView = notification.object as? NSTextView else { return } + parent.text = textView.string + } + } +} + +private final class FeedbackComposerPassthroughLabel: NSTextField { + override func hitTest(_ point: NSPoint) -> NSView? { nil } +} + +private final class FeedbackComposerMessageScrollView: NSScrollView { + weak var focusTextView: NSTextView? + + override func mouseDown(with event: NSEvent) { + if let focusTextView { + _ = window?.makeFirstResponder(focusTextView) + } + super.mouseDown(with: event) + } +} + +private final class FeedbackComposerMessageEditorView: NSView { + private static let textInset = NSSize(width: 10, height: 10) + + let scrollView = FeedbackComposerMessageScrollView() + let textView = NSTextView() + private let placeholderField = FeedbackComposerPassthroughLabel(labelWithString: "") + + var placeholder: String = "" { + didSet { + placeholderField.stringValue = placeholder + updatePlaceholderVisibility() + } + } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + wantsLayer = true + layer?.cornerRadius = 8 + layer?.borderWidth = 1 + layer?.borderColor = NSColor.separatorColor.cgColor + layer?.backgroundColor = NSColor.textBackgroundColor.cgColor + + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.borderType = .noBorder + scrollView.drawsBackground = false + scrollView.automaticallyAdjustsContentInsets = false + scrollView.hasVerticalScroller = true + scrollView.focusTextView = textView + + textView.translatesAutoresizingMaskIntoConstraints = false + textView.isEditable = true + textView.isSelectable = true + textView.isRichText = false + textView.importsGraphics = false + textView.isHorizontallyResizable = false + textView.isVerticallyResizable = true + textView.autoresizingMask = [.width] + textView.backgroundColor = .clear + textView.drawsBackground = false + textView.font = .systemFont(ofSize: 12) + textView.textColor = .labelColor + textView.insertionPointColor = .labelColor + textView.textContainerInset = Self.textInset + textView.textContainer?.lineFragmentPadding = 0 + textView.textContainer?.containerSize = NSSize(width: 0, height: CGFloat.greatestFiniteMagnitude) + textView.textContainer?.widthTracksTextView = true + textView.minSize = .zero + textView.maxSize = NSSize( + width: CGFloat.greatestFiniteMagnitude, + height: CGFloat.greatestFiniteMagnitude + ) + + scrollView.documentView = textView + addSubview(scrollView) + + placeholderField.translatesAutoresizingMaskIntoConstraints = false + placeholderField.font = .systemFont(ofSize: 12) + placeholderField.textColor = .secondaryLabelColor + placeholderField.lineBreakMode = .byWordWrapping + placeholderField.maximumNumberOfLines = 0 + scrollView.contentView.addSubview(placeholderField) + + NotificationCenter.default.addObserver( + self, + selector: #selector(textDidChange(_:)), + name: NSText.didChangeNotification, + object: textView + ) + + NSLayoutConstraint.activate([ + scrollView.topAnchor.constraint(equalTo: topAnchor), + scrollView.bottomAnchor.constraint(equalTo: bottomAnchor), + scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), + + placeholderField.topAnchor.constraint( + equalTo: scrollView.contentView.topAnchor, + constant: Self.textInset.height + ), + placeholderField.leadingAnchor.constraint( + equalTo: scrollView.contentView.leadingAnchor, + constant: Self.textInset.width + ), + placeholderField.trailingAnchor.constraint( + lessThanOrEqualTo: scrollView.contentView.trailingAnchor, + constant: -Self.textInset.width + ), + ]) + + updatePlaceholderVisibility() + } + + override func layout() { + super.layout() + syncTextViewFrameToContentSize() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc + private func textDidChange(_ notification: Notification) { + updatePlaceholderVisibility() + } + + private func updatePlaceholderVisibility() { + placeholderField.isHidden = textView.string.isEmpty == false + } + + private func syncTextViewFrameToContentSize() { + let contentSize = scrollView.contentSize + guard contentSize.width > 0, contentSize.height > 0 else { return } + + textView.minSize = NSSize(width: 0, height: contentSize.height) + textView.textContainer?.containerSize = NSSize( + width: contentSize.width, + height: CGFloat.greatestFiniteMagnitude + ) + + let targetSize = NSSize( + width: contentSize.width, + height: max(textView.frame.height, contentSize.height) + ) + if textView.frame.size != targetSize { + textView.frame = NSRect(origin: .zero, size: targetSize) + } + } +} + +private enum SidebarHelpMenuAction { + case keyboardShortcuts + case docs + case changelog + case github + case githubIssues + case checkForUpdates + case sendFeedback +} + +private struct SidebarFeedbackComposerSheet: View { + @AppStorage(FeedbackComposerSettings.storedEmailKey) private var email = "" + @Environment(\.dismiss) private var dismiss + + @State private var message = "" + @State private var attachments: [FeedbackComposerAttachment] = [] + @State private var isSubmitting = false + @State private var submissionErrorMessage: String? + @State private var didSend = false + + private var trimmedMessage: String { + message.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private var canSubmit: Bool { + isValidEmail(email) && + !trimmedMessage.isEmpty && + message.count <= FeedbackComposerSettings.maxMessageLength && + !isSubmitting && + !didSend + } + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text(String(localized: "sidebar.help.feedback.title", defaultValue: "Send Feedback")) + .font(.title3.weight(.semibold)) + + if didSend { + successView + } else { + formView + } + } + .padding(20) + .frame(width: 520) + .accessibilityIdentifier("SidebarFeedbackDialog") + } + + private var successView: some View { + VStack(alignment: .leading, spacing: 12) { + Text(String(localized: "sidebar.help.feedback.successTitle", defaultValue: "Thanks for the feedback.")) + .font(.headline) + Text( + String( + localized: "sidebar.help.feedback.successBody", + defaultValue: "A human will read this! You can also reach us at founders@manaflow.com." + ) + ) + .font(.system(size: 12)) + .foregroundStyle(.secondary) + + HStack { + Spacer() + Button(String(localized: "sidebar.help.feedback.done", defaultValue: "Done")) { + dismiss() + } + .keyboardShortcut(.defaultAction) + } + } + } + + private var formView: some View { + VStack(alignment: .leading, spacing: 14) { + Text( + String( + localized: "sidebar.help.feedback.note", + defaultValue: "A human will read this! You can also reach us at founders@manaflow.com." + ) + ) + .font(.system(size: 12)) + .foregroundStyle(.secondary) + + VStack(alignment: .leading, spacing: 6) { + Text(String(localized: "sidebar.help.feedback.email", defaultValue: "Your Email")) + .font(.system(size: 12, weight: .medium)) + TextField( + String(localized: "sidebar.help.feedback.emailPlaceholder", defaultValue: "you@example.com"), + text: $email + ) + .textFieldStyle(.roundedBorder) + .accessibilityLabel(String(localized: "sidebar.help.feedback.email", defaultValue: "Your Email")) + .accessibilityIdentifier("SidebarFeedbackEmailField") + } + + VStack(alignment: .leading, spacing: 6) { + HStack(alignment: .firstTextBaseline) { + Text(String(localized: "sidebar.help.feedback.message", defaultValue: "Message")) + .font(.system(size: 12, weight: .medium)) + Spacer(minLength: 0) + Text("\(message.count)/\(FeedbackComposerSettings.maxMessageLength)") + .font(.system(size: 11)) + .foregroundStyle( + message.count > FeedbackComposerSettings.maxMessageLength + ? Color.red + : Color.secondary + ) + } + + FeedbackComposerMessageEditor( + text: $message, + placeholder: String( + localized: "sidebar.help.feedback.messagePlaceholder", + defaultValue: "Share feedback, feature requests, or issues." + ), + accessibilityLabel: String(localized: "sidebar.help.feedback.message", defaultValue: "Message"), + accessibilityIdentifier: "SidebarFeedbackMessageEditor" + ) + .frame(minHeight: 180) + } + + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 10) { + Button { + chooseAttachments() + } label: { + Label( + String(localized: "sidebar.help.feedback.attachImages", defaultValue: "Attach Images"), + systemImage: "paperclip" + ) + } + .accessibilityIdentifier("SidebarFeedbackAttachButton") + + Text( + String( + localized: "sidebar.help.feedback.attachmentsHint", + defaultValue: "Up to 10 images. Large images will be optimized before sending." + ) + ) + .font(.system(size: 11)) + .foregroundStyle(.secondary) + } + + if attachments.isEmpty == false { + VStack(alignment: .leading, spacing: 6) { + ForEach(attachments) { attachment in + HStack(spacing: 8) { + Image(systemName: "photo") + .foregroundStyle(.secondary) + Text(attachment.fileName) + .font(.system(size: 12)) + .lineLimit(1) + .truncationMode(.middle) + Spacer(minLength: 0) + Text(attachment.displaySize) + .font(.system(size: 11)) + .foregroundStyle(.secondary) + Button( + String(localized: "sidebar.help.feedback.removeAttachment", defaultValue: "Remove") + ) { + removeAttachment(attachment) + } + .buttonStyle(.link) + } + } + } + .padding(10) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color.primary.opacity(0.04)) + ) + } + } + + if let submissionErrorMessage, submissionErrorMessage.isEmpty == false { + Text(submissionErrorMessage) + .font(.system(size: 12)) + .foregroundStyle(.red) + } + + HStack { + Spacer() + Button(String(localized: "sidebar.help.feedback.cancel", defaultValue: "Cancel")) { + dismiss() + } + .keyboardShortcut(.cancelAction) + + Button { + Task { await submitFeedback() } + } label: { + if isSubmitting { + ProgressView() + .controlSize(.small) + } else { + Text(String(localized: "sidebar.help.feedback.send", defaultValue: "Send")) + } + } + .keyboardShortcut(.defaultAction) + .disabled(!canSubmit) + .accessibilityIdentifier("SidebarFeedbackSendButton") + } + } + } + + private func chooseAttachments() { + let panel = NSOpenPanel() + panel.canChooseFiles = true + panel.canChooseDirectories = false + panel.allowsMultipleSelection = true + panel.allowedContentTypes = [.image] + panel.title = String( + localized: "sidebar.help.feedback.attachImages.title", + defaultValue: "Attach Images" + ) + panel.prompt = String( + localized: "sidebar.help.feedback.attachImages.prompt", + defaultValue: "Attach" + ) + + guard panel.runModal() == .OK else { return } + + var updatedAttachments = attachments + var knownPaths = Set(updatedAttachments.map(\.standardizedPath)) + var firstIssue: String? + + for url in panel.urls { + let normalizedPath = url.standardizedFileURL.path + if knownPaths.contains(normalizedPath) { + continue + } + if updatedAttachments.count >= FeedbackComposerSettings.maxAttachmentCount { + firstIssue = String( + localized: "sidebar.help.feedback.tooManyImages", + defaultValue: "You can attach up to 10 images." + ) + break + } + + guard let attachment = try? FeedbackComposerAttachment(url: url) else { + firstIssue = String( + localized: "sidebar.help.feedback.invalidImageSelection", + defaultValue: "One of the selected files could not be attached." + ) + continue + } + updatedAttachments.append(attachment) + knownPaths.insert(normalizedPath) + } + + attachments = updatedAttachments + submissionErrorMessage = firstIssue + } + + private func removeAttachment(_ attachment: FeedbackComposerAttachment) { + attachments.removeAll { $0.id == attachment.id } + submissionErrorMessage = nil + } + + private func submitFeedback() async { + let trimmedEmail = email.trimmingCharacters(in: .whitespacesAndNewlines) + let normalizedMessage = trimmedMessage + + guard isValidEmail(trimmedEmail) else { + submissionErrorMessage = String( + localized: "sidebar.help.feedback.invalidEmail", + defaultValue: "Enter a valid email address." + ) + return + } + + guard normalizedMessage.isEmpty == false else { + submissionErrorMessage = String( + localized: "sidebar.help.feedback.emptyMessage", + defaultValue: "Enter a message before sending." + ) + return + } + + guard message.count <= FeedbackComposerSettings.maxMessageLength else { + submissionErrorMessage = String( + localized: "sidebar.help.feedback.messageTooLong", + defaultValue: "Your message is too long." + ) + return + } + + await MainActor.run { + email = trimmedEmail + submissionErrorMessage = nil + isSubmitting = true + } + + do { + try await FeedbackComposerClient.submit( + email: trimmedEmail, + message: normalizedMessage, + attachments: attachments + ) + await MainActor.run { + isSubmitting = false + didSend = true + attachments = [] + } + } catch { + await MainActor.run { + isSubmitting = false + submissionErrorMessage = userFacingErrorMessage(for: error) + } + } + } + + private func isValidEmail(_ rawValue: String) -> Bool { + let email = rawValue.trimmingCharacters(in: .whitespacesAndNewlines) + guard email.isEmpty == false else { return false } + let pattern = #"^[A-Z0-9a-z._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$"# + return NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: email) + } + + private func userFacingErrorMessage(for error: Error) -> String { + guard let submissionError = error as? FeedbackComposerSubmissionError else { + return String( + localized: "sidebar.help.feedback.genericError", + defaultValue: "Couldn't send feedback. Please try again." + ) + } + + switch submissionError { + case .invalidEndpoint: + return String( + localized: "sidebar.help.feedback.endpointError", + defaultValue: "Feedback is unavailable right now. Email founders@manaflow.com instead." + ) + case .invalidResponse: + return String( + localized: "sidebar.help.feedback.genericError", + defaultValue: "Couldn't send feedback. Please try again." + ) + case .attachmentReadFailed: + return String( + localized: "sidebar.help.feedback.invalidImageSelection", + defaultValue: "One of the selected files could not be attached." + ) + case .attachmentPreparationFailed: + return String( + localized: "sidebar.help.feedback.totalImagesTooLarge", + defaultValue: "These images are too large to send together. Remove a few and try again." + ) + case .transport(let transportError): + if transportError.code == .notConnectedToInternet || transportError.code == .networkConnectionLost { + return String( + localized: "sidebar.help.feedback.connectionError", + defaultValue: "Couldn't send feedback. Check your connection and try again." + ) + } + return String( + localized: "sidebar.help.feedback.genericError", + defaultValue: "Couldn't send feedback. Please try again." + ) + case .rejected(let statusCode): + switch statusCode { + case 400, 413, 415: + return String( + localized: "sidebar.help.feedback.validationError", + defaultValue: "Check your message and attachments, then try again." + ) + case 429: + return String( + localized: "sidebar.help.feedback.rateLimited", + defaultValue: "Too many feedback attempts. Please try again later." + ) + case 503: + return String( + localized: "sidebar.help.feedback.endpointError", + defaultValue: "Feedback is unavailable right now. Email founders@manaflow.com instead." + ) + default: + return String( + localized: "sidebar.help.feedback.genericError", + defaultValue: "Couldn't send feedback. Please try again." + ) + } + } + } +} + +private struct SidebarHelpMenuButton: View { + private let docsURL = URL(string: "https://cmux.dev/docs") + private let changelogURL = URL(string: "https://cmux.dev/docs/changelog") + private let githubURL = URL(string: "https://github.com/manaflow-ai/cmux") + private let githubIssuesURL = URL(string: "https://github.com/manaflow-ai/cmux/issues") + private let helpTitle = String(localized: "sidebar.help.button", defaultValue: "Help") + private let buttonSize: CGFloat = 22 + private let iconSize: CGFloat = 11 + + let onSendFeedback: () -> Void + + @State private var isPopoverPresented = false + + var body: some View { + Button { + isPopoverPresented.toggle() + } label: { + Image(systemName: "questionmark.circle") + .symbolRenderingMode(.monochrome) + .font(.system(size: iconSize, weight: .medium)) + .foregroundStyle(Color(nsColor: .secondaryLabelColor)) + .frame(width: buttonSize, height: buttonSize, alignment: .center) + } + .buttonStyle(SidebarFooterIconButtonStyle()) + .frame(width: buttonSize, height: buttonSize, alignment: .center) + .popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) { + helpPopover + } + .accessibilityElement(children: .ignore) + .help(helpTitle) + .accessibilityLabel(helpTitle) + .accessibilityIdentifier("SidebarHelpMenuButton") + } + + private var helpPopover: some View { + VStack(alignment: .leading, spacing: 2) { + helpOptionButton( + title: String(localized: "sidebar.help.sendFeedback", defaultValue: "Send Feedback"), + action: .sendFeedback, + accessibilityIdentifier: "SidebarHelpMenuOptionSendFeedback", + isExternalLink: false, + trailingSystemImage: "bubble.left.and.text.bubble.right" + ) + helpOptionButton( + title: String(localized: "settings.section.keyboardShortcuts", defaultValue: "Keyboard Shortcuts"), + action: .keyboardShortcuts, + accessibilityIdentifier: "SidebarHelpMenuOptionKeyboardShortcuts", + isExternalLink: false + ) + if docsURL != nil { + helpOptionButton( + title: String(localized: "about.docs", defaultValue: "Docs"), + action: .docs, + accessibilityIdentifier: "SidebarHelpMenuOptionDocs", + isExternalLink: true + ) + } + if changelogURL != nil { + helpOptionButton( + title: String(localized: "sidebar.help.changelog", defaultValue: "Changelog"), + action: .changelog, + accessibilityIdentifier: "SidebarHelpMenuOptionChangelog", + isExternalLink: true + ) + } + if githubURL != nil { + helpOptionButton( + title: String(localized: "about.github", defaultValue: "GitHub"), + action: .github, + accessibilityIdentifier: "SidebarHelpMenuOptionGitHub", + isExternalLink: true + ) + } + if githubIssuesURL != nil { + helpOptionButton( + title: String(localized: "sidebar.help.githubIssues", defaultValue: "GitHub Issues"), + action: .githubIssues, + accessibilityIdentifier: "SidebarHelpMenuOptionGitHubIssues", + isExternalLink: true + ) + } + helpOptionButton( + title: String(localized: "command.checkForUpdates.title", defaultValue: "Check for Updates"), + action: .checkForUpdates, + accessibilityIdentifier: "SidebarHelpMenuOptionCheckForUpdates", + isExternalLink: false + ) + } + .padding(8) + .frame(minWidth: 200) + } + + private func helpOptionButton( + title: String, + action: SidebarHelpMenuAction, + accessibilityIdentifier: String, + isExternalLink: Bool, + trailingSystemImage: String? = nil + ) -> some View { + Button { + isPopoverPresented = false + perform(action) + } label: { + HStack(spacing: 8) { + Text(title) + .font(.system(size: 12)) + Spacer(minLength: 0) + if let trailingSystemImage { + helpOptionTrailingIcon(systemName: trailingSystemImage) + } + if isExternalLink { + helpOptionTrailingIcon(systemName: "arrow.up.right", size: 8) + } + } + .padding(.horizontal, 8) + .frame(height: 24) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .accessibilityIdentifier(accessibilityIdentifier) + } + + private func helpOptionTrailingIcon(systemName: String, size: CGFloat = 13) -> some View { + Image(systemName: systemName) + .resizable() + .scaledToFit() + .frame(width: size, height: size) + .foregroundStyle(Color(nsColor: .secondaryLabelColor)) + } + + private func perform(_ action: SidebarHelpMenuAction) { + switch action { + case .keyboardShortcuts: + Task { @MainActor in + if let appDelegate = AppDelegate.shared { + appDelegate.openPreferencesWindow( + debugSource: "sidebarHelpMenu.keyboardShortcuts", + navigationTarget: .keyboardShortcuts + ) + } else { + AppDelegate.presentPreferencesWindow(navigationTarget: .keyboardShortcuts) + } + } + case .docs: + guard let docsURL else { return } + NSWorkspace.shared.open(docsURL) + case .changelog: + guard let changelogURL else { return } + NSWorkspace.shared.open(changelogURL) + case .github: + guard let githubURL else { return } + NSWorkspace.shared.open(githubURL) + case .githubIssues: + guard let githubIssuesURL else { return } + NSWorkspace.shared.open(githubIssuesURL) + case .checkForUpdates: + Task { @MainActor in + AppDelegate.shared?.checkForUpdates(nil) + } + case .sendFeedback: + isPopoverPresented = false + onSendFeedback() + } + } +} + +private struct SidebarFooterIconButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + SidebarFooterIconButtonStyleBody(configuration: configuration) + } +} + +private struct SidebarFooterIconButtonStyleBody: View { + let configuration: SidebarFooterIconButtonStyle.Configuration + + @Environment(\.isEnabled) private var isEnabled + @State private var isHovered = false + + private var backgroundOpacity: Double { + guard isEnabled else { return 0.0 } + if configuration.isPressed { return 0.16 } + if isHovered { return 0.08 } + return 0.0 + } + + var body: some View { + configuration.label + .background( + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(Color.primary.opacity(backgroundOpacity)) + ) + .onHover { hovering in + isHovered = hovering + } + .animation(.easeOut(duration: 0.12), value: isHovered) + .animation(.easeOut(duration: 0.08), value: configuration.isPressed) + } +} + #if DEBUG private struct SidebarDevFooter: View { @ObservedObject var updateViewModel: UpdateViewModel + let onSendFeedback: () -> Void + @AppStorage(DevBuildBannerDebugSettings.sidebarBannerVisibleKey) + private var showSidebarDevBuildBanner = DevBuildBannerDebugSettings.defaultShowSidebarBanner var body: some View { VStack(alignment: .leading, spacing: 6) { - UpdatePill(model: updateViewModel) - Text("THIS IS A DEV BUILD") - .font(.system(size: 11, weight: .semibold)) - .foregroundColor(.red) + SidebarFooterButtons(updateViewModel: updateViewModel, onSendFeedback: onSendFeedback) + if showSidebarDevBuildBanner { + Text(String(localized: "debug.devBuildBanner.title", defaultValue: "THIS IS A DEV BUILD")) + .font(.system(size: 11, weight: .semibold)) + .foregroundColor(.red) + } } - .padding(.horizontal, 10) - .padding(.bottom, 10) + .padding(.leading, 6) + .padding(.trailing, 10) + .padding(.bottom, 6) } } #endif diff --git a/Sources/KeyboardShortcutSettings.swift b/Sources/KeyboardShortcutSettings.swift index dae65482..f06c255b 100644 --- a/Sources/KeyboardShortcutSettings.swift +++ b/Sources/KeyboardShortcutSettings.swift @@ -10,6 +10,7 @@ enum KeyboardShortcutSettings { case newWindow case closeWindow case openFolder + case sendFeedback case showNotifications case jumpToUnread case triggerFlash @@ -50,6 +51,7 @@ enum KeyboardShortcutSettings { case .newWindow: return String(localized: "shortcut.newWindow.label", defaultValue: "New Window") case .closeWindow: return String(localized: "shortcut.closeWindow.label", defaultValue: "Close Window") case .openFolder: return String(localized: "shortcut.openFolder.label", defaultValue: "Open Folder") + case .sendFeedback: return String(localized: "sidebar.help.sendFeedback", defaultValue: "Send Feedback") case .showNotifications: return String(localized: "shortcut.showNotifications.label", defaultValue: "Show Notifications") case .jumpToUnread: return String(localized: "shortcut.jumpToUnread.label", defaultValue: "Jump to Latest Unread") case .triggerFlash: return String(localized: "shortcut.flashFocusedPanel.label", defaultValue: "Flash Focused Panel") @@ -84,6 +86,7 @@ enum KeyboardShortcutSettings { case .newWindow: return "shortcut.newWindow" case .closeWindow: return "shortcut.closeWindow" case .openFolder: return "shortcut.openFolder" + case .sendFeedback: return "shortcut.sendFeedback" case .showNotifications: return "shortcut.showNotifications" case .jumpToUnread: return "shortcut.jumpToUnread" case .triggerFlash: return "shortcut.triggerFlash" @@ -123,6 +126,8 @@ enum KeyboardShortcutSettings { return StoredShortcut(key: "w", command: true, shift: false, option: false, control: true) case .openFolder: return StoredShortcut(key: "o", command: true, shift: false, option: false, control: false) + case .sendFeedback: + return StoredShortcut(key: "f", command: true, shift: false, option: true, control: false) case .showNotifications: return StoredShortcut(key: "i", command: true, shift: false, option: false, control: false) case .jumpToUnread: diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 528669a6..27a23f3f 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -3676,6 +3676,7 @@ extension Notification.Name { static let commandPaletteMoveSelection = Notification.Name("cmux.commandPaletteMoveSelection") static let commandPaletteRenameInputInteractionRequested = Notification.Name("cmux.commandPaletteRenameInputInteractionRequested") static let commandPaletteRenameInputDeleteBackwardRequested = Notification.Name("cmux.commandPaletteRenameInputDeleteBackwardRequested") + static let feedbackComposerRequested = Notification.Name("cmux.feedbackComposerRequested") static let ghosttyDidSetTitle = Notification.Name("ghosttyDidSetTitle") static let ghosttyDidFocusTab = Notification.Name("ghosttyDidFocusTab") static let ghosttyDidFocusSurface = Notification.Name("ghosttyDidFocusSurface") diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 00cd360b..40645ea6 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -14,6 +14,8 @@ struct cmuxApp: App { @AppStorage(AppearanceSettings.appearanceModeKey) private var appearanceMode = AppearanceSettings.defaultMode.rawValue @AppStorage("titlebarControlsStyle") private var titlebarControlsStyle = TitlebarControlsStyle.classic.rawValue @AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints + @AppStorage(DevBuildBannerDebugSettings.sidebarBannerVisibleKey) + private var showSidebarDevBuildBanner = DevBuildBannerDebugSettings.defaultShowSidebarBanner @AppStorage(SocketControlSettings.appStorageKey) private var socketControlMode = SocketControlSettings.defaultMode.rawValue @AppStorage(KeyboardShortcutSettings.Action.toggleSidebar.defaultsKey) private var toggleSidebarShortcutData = Data() @AppStorage(KeyboardShortcutSettings.Action.newTab.defaultsKey) private var newWorkspaceShortcutData = Data() @@ -360,6 +362,10 @@ struct cmuxApp: App { } Toggle("Always Show Shortcut Hints", isOn: $alwaysShowShortcutHints) + Toggle( + String(localized: "debug.devBuildBanner.show", defaultValue: "Show Dev Build Banner"), + isOn: $showSidebarDevBuildBanner + ) Divider() @@ -1321,6 +1327,7 @@ private enum DebugWindowConfigSnapshot { sidebarCornerRadius=\(String(format: "%.1f", doubleValue(defaults, key: "sidebarCornerRadius", fallback: 0.0))) sidebarBranchVerticalLayout=\(boolValue(defaults, key: SidebarBranchLayoutSettings.key, fallback: SidebarBranchLayoutSettings.defaultVerticalLayout)) sidebarActiveTabIndicatorStyle=\(stringValue(defaults, key: SidebarActiveTabIndicatorSettings.styleKey, fallback: SidebarActiveTabIndicatorSettings.defaultStyle.rawValue)) + sidebarDevBuildBannerVisible=\(boolValue(defaults, key: DevBuildBannerDebugSettings.sidebarBannerVisibleKey, fallback: DevBuildBannerDebugSettings.defaultShowSidebarBanner)) shortcutHintSidebarXOffset=\(String(format: "%.1f", doubleValue(defaults, key: ShortcutHintDebugSettings.sidebarHintXKey, fallback: ShortcutHintDebugSettings.defaultSidebarHintX))) shortcutHintSidebarYOffset=\(String(format: "%.1f", doubleValue(defaults, key: ShortcutHintDebugSettings.sidebarHintYKey, fallback: ShortcutHintDebugSettings.defaultSidebarHintY))) shortcutHintTitlebarXOffset=\(String(format: "%.1f", doubleValue(defaults, key: ShortcutHintDebugSettings.titlebarHintXKey, fallback: ShortcutHintDebugSettings.defaultTitlebarHintX))) @@ -1775,7 +1782,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate { fatalError("init(coder:) has not been implemented") } - func show() { + func show(navigationTarget: SettingsNavigationTarget? = nil) { guard let window else { return } #if DEBUG dlog("settings.window.show requested isVisible=\(window.isVisible ? 1 : 0) isKey=\(window.isKeyWindow ? 1 : 0)") @@ -1785,12 +1792,39 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate { window.center() } window.makeKeyAndOrderFront(nil) + if let navigationTarget { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + SettingsNavigationRequest.post(navigationTarget) + } + } #if DEBUG dlog("settings.window.show completed isVisible=\(window.isVisible ? 1 : 0) isKey=\(window.isKeyWindow ? 1 : 0)") #endif } } +enum SettingsNavigationTarget: String { + case keyboardShortcuts +} + +enum SettingsNavigationRequest { + static let notificationName = Notification.Name("cmux.settings.navigate") + private static let targetKey = "target" + + static func post(_ target: SettingsNavigationTarget) { + NotificationCenter.default.post( + name: notificationName, + object: nil, + userInfo: [targetKey: target.rawValue] + ) + } + + static func target(from notification: Notification) -> SettingsNavigationTarget? { + guard let rawValue = notification.userInfo?[targetKey] as? String else { return nil } + return SettingsNavigationTarget(rawValue: rawValue) + } +} + private final class SidebarDebugWindowController: NSWindowController, NSWindowDelegate { static let shared = SidebarDebugWindowController() @@ -1931,6 +1965,8 @@ private struct SidebarDebugView: View { @AppStorage(ShortcutHintDebugSettings.paneHintXKey) private var paneShortcutHintXOffset = ShortcutHintDebugSettings.defaultPaneHintX @AppStorage(ShortcutHintDebugSettings.paneHintYKey) private var paneShortcutHintYOffset = ShortcutHintDebugSettings.defaultPaneHintY @AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints + @AppStorage(DevBuildBannerDebugSettings.sidebarBannerVisibleKey) + private var showSidebarDevBuildBanner = DevBuildBannerDebugSettings.defaultShowSidebarBanner @AppStorage(SidebarActiveTabIndicatorSettings.styleKey) private var sidebarActiveTabIndicatorStyle = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue @@ -2154,6 +2190,7 @@ private struct SidebarDebugView: View { sidebarCornerRadius=\(String(format: "%.1f", sidebarCornerRadius)) sidebarBranchVerticalLayout=\(sidebarBranchVerticalLayout) sidebarActiveTabIndicatorStyle=\(sidebarActiveTabIndicatorStyle) + sidebarDevBuildBannerVisible=\(showSidebarDevBuildBanner) shortcutHintSidebarXOffset=\(String(format: "%.1f", ShortcutHintDebugSettings.clamped(sidebarShortcutHintXOffset))) shortcutHintSidebarYOffset=\(String(format: "%.1f", ShortcutHintDebugSettings.clamped(sidebarShortcutHintYOffset))) shortcutHintTitlebarXOffset=\(String(format: "%.1f", ShortcutHintDebugSettings.clamped(titlebarShortcutHintXOffset))) @@ -3168,7 +3205,8 @@ struct SettingsView: View { } var body: some View { - ZStack(alignment: .top) { + ScrollViewReader { proxy in + ZStack(alignment: .top) { ScrollView { VStack(alignment: .leading, spacing: 14) { SettingsSectionHeader(title: String(localized: "settings.section.app", defaultValue: "App")) @@ -3864,6 +3902,8 @@ struct SettingsView: View { } SettingsSectionHeader(title: String(localized: "settings.section.keyboardShortcuts", defaultValue: "Keyboard Shortcuts")) + .id(SettingsNavigationTarget.keyboardShortcuts) + .accessibilityIdentifier("SettingsKeyboardShortcutsSection") SettingsCard { SettingsCardRow( String(localized: "settings.shortcuts.showHints", defaultValue: "Show Cmd/Ctrl-Hold Shortcut Hints"), @@ -3894,6 +3934,7 @@ struct SettingsView: View { .font(.caption) .foregroundColor(.secondary) .padding(.leading, 2) + .accessibilityIdentifier("ShortcutRecordingHint") SettingsSectionHeader(title: String(localized: "settings.section.reset", defaultValue: "Reset")) SettingsCard { @@ -4013,6 +4054,14 @@ struct SettingsView: View { .onReceive(NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)) { _ in reloadWorkspaceTabColorSettings() } + .onReceive(NotificationCenter.default.publisher(for: SettingsNavigationRequest.notificationName)) { notification in + guard let target = SettingsNavigationRequest.target(from: notification) else { return } + DispatchQueue.main.async { + withAnimation(.easeInOut(duration: 0.2)) { + proxy.scrollTo(target, anchor: .top) + } + } + } .confirmationDialog( String(localized: "settings.browser.history.clearDialog.title", defaultValue: "Clear browser history?"), isPresented: $showClearBrowserHistoryConfirmation, @@ -4061,6 +4110,7 @@ struct SettingsView: View { } message: { Text(notificationCustomSoundErrorAlertMessage) } + } } private func relaunchApp() { diff --git a/cmuxTests/AppDelegateShortcutRoutingTests.swift b/cmuxTests/AppDelegateShortcutRoutingTests.swift index 22b09b39..63ff111f 100644 --- a/cmuxTests/AppDelegateShortcutRoutingTests.swift +++ b/cmuxTests/AppDelegateShortcutRoutingTests.swift @@ -2071,9 +2071,11 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { func testPresentPreferencesWindowShowsCustomSettingsWindowAndActivates() { var showFallbackSettingsWindowCallCount = 0 var activateApplicationCallCount = 0 + var receivedNavigationTargets: [SettingsNavigationTarget?] = [] AppDelegate.presentPreferencesWindow( - showFallbackSettingsWindow: { + showFallbackSettingsWindow: { navigationTarget in + receivedNavigationTargets.append(navigationTarget) showFallbackSettingsWindowCallCount += 1 }, activateApplication: { @@ -2083,14 +2085,17 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { XCTAssertEqual(showFallbackSettingsWindowCallCount, 1) XCTAssertEqual(activateApplicationCallCount, 1) + XCTAssertEqual(receivedNavigationTargets, [nil]) } func testPresentPreferencesWindowSupportsRepeatedCalls() { var showFallbackSettingsWindowCallCount = 0 var activateApplicationCallCount = 0 + var receivedNavigationTargets: [SettingsNavigationTarget?] = [] AppDelegate.presentPreferencesWindow( - showFallbackSettingsWindow: { + showFallbackSettingsWindow: { navigationTarget in + receivedNavigationTargets.append(navigationTarget) showFallbackSettingsWindowCallCount += 1 }, activateApplication: { @@ -2099,7 +2104,8 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { ) AppDelegate.presentPreferencesWindow( - showFallbackSettingsWindow: { + showFallbackSettingsWindow: { navigationTarget in + receivedNavigationTargets.append(navigationTarget) showFallbackSettingsWindowCallCount += 1 }, activateApplication: { @@ -2109,6 +2115,25 @@ final class AppDelegateShortcutRoutingTests: XCTestCase { XCTAssertEqual(showFallbackSettingsWindowCallCount, 2) XCTAssertEqual(activateApplicationCallCount, 2) + XCTAssertEqual(receivedNavigationTargets, [nil, nil]) + } + + func testPresentPreferencesWindowForwardsNavigationTarget() { + var receivedNavigationTarget: SettingsNavigationTarget? + var activateApplicationCallCount = 0 + + AppDelegate.presentPreferencesWindow( + navigationTarget: .keyboardShortcuts, + showFallbackSettingsWindow: { navigationTarget in + receivedNavigationTarget = navigationTarget + }, + activateApplication: { + activateApplicationCallCount += 1 + } + ) + + XCTAssertEqual(receivedNavigationTarget, .keyboardShortcuts) + XCTAssertEqual(activateApplicationCallCount, 1) } private func makeKeyDownEvent( diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 9e34690f..fd4b699b 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -3590,6 +3590,35 @@ final class ShortcutHintDebugSettingsTests: XCTestCase { } } +final class DevBuildBannerDebugSettingsTests: XCTestCase { + func testShowSidebarBannerDefaultsToVisible() { + let suiteName = "DevBuildBannerDebugSettingsTests.Default.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { defaults.removePersistentDomain(forName: suiteName) } + + defaults.removeObject(forKey: DevBuildBannerDebugSettings.sidebarBannerVisibleKey) + XCTAssertTrue(DevBuildBannerDebugSettings.showSidebarBanner(defaults: defaults)) + } + + func testShowSidebarBannerRespectsStoredValue() { + let suiteName = "DevBuildBannerDebugSettingsTests.Stored.\(UUID().uuidString)" + guard let defaults = UserDefaults(suiteName: suiteName) else { + XCTFail("Failed to create isolated UserDefaults suite") + return + } + defer { defaults.removePersistentDomain(forName: suiteName) } + + defaults.set(false, forKey: DevBuildBannerDebugSettings.sidebarBannerVisibleKey) + XCTAssertFalse(DevBuildBannerDebugSettings.showSidebarBanner(defaults: defaults)) + + defaults.set(true, forKey: DevBuildBannerDebugSettings.sidebarBannerVisibleKey) + XCTAssertTrue(DevBuildBannerDebugSettings.showSidebarBanner(defaults: defaults)) + } +} + final class ShortcutHintLanePlannerTests: XCTestCase { func testAssignLanesKeepsSeparatedIntervalsOnSingleLane() { let intervals: [ClosedRange] = [0...20, 28...40, 48...64] diff --git a/cmuxUITests/SidebarHelpMenuUITests.swift b/cmuxUITests/SidebarHelpMenuUITests.swift new file mode 100644 index 00000000..9ba6cb4a --- /dev/null +++ b/cmuxUITests/SidebarHelpMenuUITests.swift @@ -0,0 +1,296 @@ +import XCTest + +private func sidebarHelpPollUntil( + timeout: TimeInterval, + pollInterval: TimeInterval = 0.05, + condition: () -> Bool +) -> Bool { + let start = ProcessInfo.processInfo.systemUptime + while true { + if condition() { + return true + } + if (ProcessInfo.processInfo.systemUptime - start) >= timeout { + return false + } + RunLoop.current.run(until: Date().addingTimeInterval(pollInterval)) + } +} + +final class SidebarHelpMenuUITests: XCTestCase { + override func setUp() { + super.setUp() + continueAfterFailure = false + } + + func testHelpMenuOpensKeyboardShortcutsSection() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + launchAndActivate(app) + + XCTAssertTrue(waitForWindowCount(atLeast: 1, app: app, timeout: 6.0)) + + let helpButton = requireElement( + candidates: helpButtonCandidates(in: app), + timeout: 6.0, + description: "sidebar help button" + ) + helpButton.click() + + let keyboardShortcutsItem = requireElement( + candidates: helpMenuItemCandidates(in: app, identifier: "SidebarHelpMenuOptionKeyboardShortcuts", title: "Keyboard Shortcuts"), + timeout: 3.0, + description: "Keyboard Shortcuts help menu item" + ) + keyboardShortcutsItem.click() + + XCTAssertTrue(app.staticTexts["ShortcutRecordingHint"].waitForExistence(timeout: 6.0)) + } + + func testHelpMenuCheckForUpdatesTriggersSidebarUpdatePill() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + app.launchEnvironment["CMUX_UI_TEST_FEED_URL"] = "https://cmux.test/appcast.xml" + app.launchEnvironment["CMUX_UI_TEST_FEED_MODE"] = "available" + app.launchEnvironment["CMUX_UI_TEST_UPDATE_VERSION"] = "9.9.9" + app.launchEnvironment["CMUX_UI_TEST_AUTO_ALLOW_PERMISSION"] = "1" + launchAndActivate(app) + + XCTAssertTrue(waitForWindowCount(atLeast: 1, app: app, timeout: 6.0)) + + let helpButton = requireElement( + candidates: helpButtonCandidates(in: app), + timeout: 6.0, + description: "sidebar help button" + ) + helpButton.click() + + let checkForUpdatesItem = requireElement( + candidates: helpMenuItemCandidates(in: app, identifier: "SidebarHelpMenuOptionCheckForUpdates", title: "Check for Updates"), + timeout: 3.0, + description: "Check for Updates help menu item" + ) + checkForUpdatesItem.click() + + let updatePill = app.buttons["UpdatePill"] + XCTAssertTrue(updatePill.waitForExistence(timeout: 6.0)) + XCTAssertEqual(updatePill.label, "Update Available: 9.9.9") + } + + func testHelpMenuSendFeedbackOpensComposerSheet() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + launchAndActivate(app) + + XCTAssertTrue(waitForWindowCount(atLeast: 1, app: app, timeout: 6.0)) + + let helpButton = requireElement( + candidates: helpButtonCandidates(in: app), + timeout: 6.0, + description: "sidebar help button" + ) + helpButton.click() + + let sendFeedbackItem = requireElement( + candidates: helpMenuItemCandidates(in: app, identifier: "SidebarHelpMenuOptionSendFeedback", title: "Send Feedback"), + timeout: 3.0, + description: "Send Feedback help menu item" + ) + sendFeedbackItem.click() + + XCTAssertTrue(app.staticTexts["Send Feedback"].waitForExistence(timeout: 3.0)) + XCTAssertTrue( + firstExistingElement( + candidates: [ + app.textFields["SidebarFeedbackEmailField"], + app.textFields["Your Email"], + ], + timeout: 2.0 + ) != nil + ) + XCTAssertTrue( + firstExistingElement( + candidates: [ + app.buttons["SidebarFeedbackAttachButton"], + app.buttons["Attach Images"], + ], + timeout: 2.0 + ) != nil + ) + XCTAssertTrue( + firstExistingElement( + candidates: [ + app.buttons["SidebarFeedbackSendButton"], + app.buttons["Send"], + ], + timeout: 2.0 + ) != nil + ) + XCTAssertTrue( + app.staticTexts[ + "A human will read this! You can also reach us at founders@manaflow.com." + ].waitForExistence(timeout: 2.0) + ) + + let messageEditor = requireElement( + candidates: [ + app.textViews["SidebarFeedbackMessageEditor"], + app.scrollViews["SidebarFeedbackMessageEditor"], + app.otherElements["SidebarFeedbackMessageEditor"], + app.textViews["Message"], + ], + timeout: 2.0, + description: "feedback message editor" + ) + messageEditor.click() + app.typeText("hello") + XCTAssertTrue(app.staticTexts["5/4000"].waitForExistence(timeout: 2.0)) + } + + private func waitForWindowCount(atLeast count: Int, app: XCUIApplication, timeout: TimeInterval) -> Bool { + sidebarHelpPollUntil(timeout: timeout) { + app.windows.count >= count + } + } + + private func helpButtonCandidates(in app: XCUIApplication) -> [XCUIElement] { + let sidebar = app.otherElements["Sidebar"] + return [ + app.buttons["SidebarHelpMenuButton"], + app.buttons["Help"], + sidebar.buttons["SidebarHelpMenuButton"], + sidebar.buttons["Help"], + ] + } + + private func helpMenuItemCandidates( + in app: XCUIApplication, + identifier: String, + title: String + ) -> [XCUIElement] { + [ + app.buttons[identifier], + app.buttons[title], + ] + } + + private func firstExistingElement( + candidates: [XCUIElement], + timeout: TimeInterval + ) -> XCUIElement? { + var match: XCUIElement? + let found = sidebarHelpPollUntil(timeout: timeout) { + for candidate in candidates where candidate.exists { + match = candidate + return true + } + return false + } + return found ? match : nil + } + + private func requireElement( + candidates: [XCUIElement], + timeout: TimeInterval, + description: String + ) -> XCUIElement { + guard let element = firstExistingElement(candidates: candidates, timeout: timeout) else { + XCTFail("Expected \(description) to exist") + return candidates[0] + } + return element + } + + private func launchAndActivate(_ app: XCUIApplication, activateTimeout: TimeInterval = 2.0) { + app.launch() + let activated = sidebarHelpPollUntil(timeout: activateTimeout) { + guard app.state != .runningForeground else { + return true + } + app.activate() + return app.state == .runningForeground + } + if !activated { + app.activate() + } + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 2.0) { app.state == .runningForeground }, + "App did not reach runningForeground before UI interactions" + ) + } +} + +final class FeedbackComposerShortcutUITests: XCTestCase { + override func setUp() { + super.setUp() + continueAfterFailure = false + } + + func testCmdOptionFOpensFeedbackComposer() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + app.launch() + app.activate() + + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { + app.windows.count >= 1 + } + ) + + app.typeKey("f", modifierFlags: [.command, .option]) + + XCTAssertTrue(app.staticTexts["Send Feedback"].waitForExistence(timeout: 3.0)) + XCTAssertTrue( + app.textFields["SidebarFeedbackEmailField"].waitForExistence(timeout: 2.0) + || app.textFields["Your Email"].waitForExistence(timeout: 2.0) + ) + } + + func testCmdOptionFWorksWithHiddenSidebar() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + app.launch() + app.activate() + + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { + app.windows.count >= 1 + } + ) + + app.typeKey("b", modifierFlags: [.command]) + + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 3.0) { + !app.buttons["SidebarHelpMenuButton"].exists && !app.buttons["Help"].exists + } + ) + + app.typeKey("f", modifierFlags: [.command, .option]) + + XCTAssertTrue(app.staticTexts["Send Feedback"].waitForExistence(timeout: 3.0)) + } + + func testCmdOptionFWorksFromSettingsWindow() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1" + app.launchEnvironment["CMUX_UI_TEST_SHOW_SETTINGS"] = "1" + app.launch() + app.activate() + + XCTAssertTrue( + sidebarHelpPollUntil(timeout: 6.0) { + app.windows.count >= 2 + } + ) + + app.typeKey("f", modifierFlags: [.command, .option]) + + XCTAssertTrue(app.staticTexts["Send Feedback"].waitForExistence(timeout: 3.0)) + XCTAssertTrue( + app.textFields["SidebarFeedbackEmailField"].waitForExistence(timeout: 2.0) + || app.textFields["Your Email"].waitForExistence(timeout: 2.0) + ) + } +} diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 00000000..389f57d6 --- /dev/null +++ b/web/.env.example @@ -0,0 +1,3 @@ +RESEND_API_KEY= +CMUX_FEEDBACK_FROM_EMAIL= +CMUX_FEEDBACK_RATE_LIMIT_ID= diff --git a/web/app/api/feedback/route.ts b/web/app/api/feedback/route.ts new file mode 100644 index 00000000..9b3d85b3 --- /dev/null +++ b/web/app/api/feedback/route.ts @@ -0,0 +1,340 @@ +import { checkRateLimit } from "@vercel/firewall"; +import { NextResponse } from "next/server"; +import { Resend } from "resend"; +import { z } from "zod"; + +import { env } from "@/app/env"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +const feedbackRecipient = "founders@manaflow.com"; +const maxAttachmentCount = 10; +const maxAttachmentBytes = 4 * 1024 * 1024; +// Keep multipart requests below Vercel Functions' 4.5 MB request-body limit. +const maxTotalAttachmentBytes = 4 * 1024 * 1024; +const allowedImageTypes = new Set([ + "image/gif", + "image/heic", + "image/heif", + "image/jpeg", + "image/png", + "image/tiff", + "image/webp", +]); + +const feedbackSchema = z.object({ + email: z.string().trim().email().max(320), + message: z.string().trim().min(1).max(4000), + appVersion: z.string().trim().max(120).optional().default(""), + appBuild: z.string().trim().max(120).optional().default(""), + appCommit: z.string().trim().max(120).optional().default(""), + bundleIdentifier: z.string().trim().max(200).optional().default(""), + osVersion: z.string().trim().max(200).optional().default(""), + locale: z.string().trim().max(120).optional().default(""), +}); + +type PreparedAttachment = { + content: Buffer; + contentType: string; + filename: string; + size: number; +}; + +export async function POST(request: Request) { + const feedbackConfig = resolveFeedbackConfig(); + if (!feedbackConfig) { + return jsonError("Feedback endpoint is not configured", 503); + } + + if (process.env.VERCEL === "1") { + const { error, rateLimited } = await checkRateLimit( + feedbackConfig.rateLimitId, + { request }, + ); + + if (rateLimited || error === "blocked") { + return jsonError("Rate limit exceeded", 429); + } + + if (error === "not-found") { + console.error( + "feedback.route.rate_limit_not_found", + feedbackConfig.rateLimitId, + ); + } else if (error) { + console.error("feedback.route.rate_limit_error", error); + } + } + + let formData: FormData; + try { + formData = await request.formData(); + } catch { + return jsonError("Invalid multipart payload", 400); + } + + const parsed = feedbackSchema.safeParse({ + email: getString(formData, "email"), + message: getString(formData, "message"), + appVersion: getString(formData, "appVersion"), + appBuild: getString(formData, "appBuild"), + appCommit: getString(formData, "appCommit"), + bundleIdentifier: getString(formData, "bundleIdentifier"), + osVersion: getString(formData, "osVersion"), + locale: getString(formData, "locale"), + }); + + if (!parsed.success) { + return jsonError("Invalid feedback payload", 400); + } + + const attachmentsResult = await prepareAttachments( + formData.getAll("attachments"), + ); + if ("errorResponse" in attachmentsResult) { + return attachmentsResult.errorResponse; + } + + const { appBuild, appCommit, appVersion, bundleIdentifier, email, locale, message, osVersion } = + parsed.data; + const subject = buildSubject(email, message, appVersion); + const attachments = attachmentsResult.attachments; + const resend = new Resend(feedbackConfig.resendApiKey); + + const { error } = await resend.emails.send({ + from: `cmux feedback <${feedbackConfig.fromEmail}>`, + to: [feedbackRecipient], + replyTo: email, + subject, + text: buildTextBody({ + email, + message, + appVersion, + appBuild, + appCommit, + bundleIdentifier, + osVersion, + locale, + attachments, + }), + html: buildHtmlBody({ + email, + message, + appVersion, + appBuild, + appCommit, + bundleIdentifier, + osVersion, + locale, + attachments, + }), + attachments: attachments.map((attachment) => ({ + content: attachment.content, + contentType: attachment.contentType, + filename: attachment.filename, + })), + }); + + if (error) { + console.error("feedback.route.resend_failed", error); + return jsonError("Failed to send feedback", 502); + } + + return NextResponse.json( + { ok: true }, + { + headers: { + "Cache-Control": "no-store", + }, + }, + ); +} + +function resolveFeedbackConfig() { + const resendApiKey = env.RESEND_API_KEY; + const fromEmail = env.CMUX_FEEDBACK_FROM_EMAIL; + const rateLimitId = env.CMUX_FEEDBACK_RATE_LIMIT_ID; + + if (!resendApiKey || !fromEmail || !rateLimitId) { + return null; + } + + return { + resendApiKey, + fromEmail, + rateLimitId, + }; +} + +function getString(formData: FormData, key: string) { + const value = formData.get(key); + return typeof value === "string" ? value.trim() : ""; +} + +async function prepareAttachments(values: FormDataEntryValue[]) { + const files = values.filter( + (value): value is File => value instanceof File && value.name.length > 0, + ); + + if (files.length > maxAttachmentCount) { + return { + errorResponse: jsonError("Too many images attached", 400), + }; + } + + let totalSize = 0; + const attachments: PreparedAttachment[] = []; + + for (const file of files) { + if (!allowedImageTypes.has(file.type)) { + return { + errorResponse: jsonError("Unsupported image attachment type", 415), + }; + } + + if (file.size > maxAttachmentBytes) { + return { + errorResponse: jsonError("Image attachment is too large", 413), + }; + } + + totalSize += file.size; + if (totalSize > maxTotalAttachmentBytes) { + return { + errorResponse: jsonError("Total image attachment size is too large", 413), + }; + } + + attachments.push({ + content: Buffer.from(await file.arrayBuffer()), + contentType: file.type, + filename: sanitizeFilename(file.name), + size: file.size, + }); + } + + return { attachments }; +} + +function buildSubject(email: string, message: string, appVersion: string) { + const firstNonEmptyLine = + message + .split(/\r?\n/) + .map((line) => line.trim()) + .find(Boolean) ?? "Feedback"; + const summary = + firstNonEmptyLine.length > 72 + ? `${firstNonEmptyLine.slice(0, 69)}...` + : firstNonEmptyLine; + const versionSuffix = appVersion ? ` (v${appVersion})` : ""; + + return `cmux feedback from ${email}${versionSuffix}: ${summary}`; +} + +function buildTextBody(input: { + email: string; + message: string; + appVersion: string; + appBuild: string; + appCommit: string; + bundleIdentifier: string; + osVersion: string; + locale: string; + attachments: PreparedAttachment[]; +}) { + const attachmentLines = + input.attachments.length === 0 + ? "Attachments: none" + : [ + "Attachments:", + ...input.attachments.map( + (attachment) => + `- ${attachment.filename} (${attachment.contentType}, ${attachment.size} bytes)`, + ), + ].join("\n"); + + return [ + `From: ${input.email}`, + `App version: ${input.appVersion || "unknown"}`, + `App build: ${input.appBuild || "unknown"}`, + `App commit: ${input.appCommit || "unknown"}`, + `Bundle identifier: ${input.bundleIdentifier || "unknown"}`, + `macOS: ${input.osVersion || "unknown"}`, + `Locale: ${input.locale || "unknown"}`, + attachmentLines, + "", + "Message:", + input.message, + ].join("\n"); +} + +function buildHtmlBody(input: { + email: string; + message: string; + appVersion: string; + appBuild: string; + appCommit: string; + bundleIdentifier: string; + osVersion: string; + locale: string; + attachments: PreparedAttachment[]; +}) { + const attachmentMarkup = + input.attachments.length === 0 + ? "

Attachments: none

" + : `

Attachments:

    ${input.attachments + .map( + (attachment) => + `
  • ${escapeHtml(attachment.filename)} (${escapeHtml( + attachment.contentType, + )}, ${attachment.size} bytes)
  • `, + ) + .join("")}
`; + + return ` +
+

cmux feedback

+

From: ${escapeHtml(input.email)}

+

App version: ${escapeHtml(input.appVersion || "unknown")}

+

App build: ${escapeHtml(input.appBuild || "unknown")}

+

App commit: ${escapeHtml(input.appCommit || "unknown")}

+

Bundle identifier: ${escapeHtml( + input.bundleIdentifier || "unknown", + )}

+

macOS: ${escapeHtml(input.osVersion || "unknown")}

+

Locale: ${escapeHtml(input.locale || "unknown")}

+ ${attachmentMarkup} +

Message

+
${escapeHtml(
+        input.message,
+      )}
+
+ `.trim(); +} + +function sanitizeFilename(fileName: string) { + const cleaned = fileName.replace(/[\r\n"]/g, "").trim(); + return cleaned.length > 0 ? cleaned : "attachment"; +} + +function escapeHtml(value: string) { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function jsonError(message: string, status: number) { + return NextResponse.json( + { error: message }, + { + status, + headers: { + "Cache-Control": "no-store", + }, + }, + ); +} diff --git a/web/app/env.ts b/web/app/env.ts new file mode 100644 index 00000000..18ba7bcf --- /dev/null +++ b/web/app/env.ts @@ -0,0 +1,18 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod"; + +export const env = createEnv({ + server: { + RESEND_API_KEY: z.string().min(1), + CMUX_FEEDBACK_FROM_EMAIL: z.string().email(), + CMUX_FEEDBACK_RATE_LIMIT_ID: z.string().min(1), + }, + runtimeEnv: { + RESEND_API_KEY: process.env.RESEND_API_KEY, + CMUX_FEEDBACK_FROM_EMAIL: process.env.CMUX_FEEDBACK_FROM_EMAIL, + CMUX_FEEDBACK_RATE_LIMIT_ID: process.env.CMUX_FEEDBACK_RATE_LIMIT_ID, + }, + skipValidation: + process.env.SKIP_ENV_VALIDATION === "1" || + process.env.VERCEL_ENV === "preview", +}); diff --git a/web/bun.lock b/web/bun.lock index abaa7c3f..d466e4f1 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -5,6 +5,8 @@ "": { "name": "web", "dependencies": { + "@t3-oss/env-nextjs": "^0.13.10", + "@vercel/firewall": "^1.1.2", "next": "16.1.6", "next-themes": "^0.4.6", "posthog-js": "^1.350.0", @@ -12,7 +14,9 @@ "react-dom": "19.2.3", "react-tweet": "^3.3.0", "react-wrap-balancer": "^1.1.1", + "resend": "^6.9.3", "shiki": "^3.22.0", + "zod": "^4.3.6", }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -249,8 +253,14 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@t3-oss/env-core": ["@t3-oss/env-core@0.13.10", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g=="], + + "@t3-oss/env-nextjs": ["@t3-oss/env-nextjs@0.13.10", "", { "dependencies": { "@t3-oss/env-core": "0.13.10" }, "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], @@ -363,6 +373,8 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "@vercel/firewall": ["@vercel/firewall@1.1.2", "", {}, "sha512-h0sdBVrloWx8TitvWla/rGj3AnJ5JEYfL5LaGHNNOWkyMuzNqfCcGTvJgnjL2A5eSpAAzoN7Xt609YQ0L7xZdw=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -543,6 +555,8 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -819,6 +833,8 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + "postal-mime": ["postal-mime@2.7.3", "", {}, "sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "posthog-js": ["posthog-js@1.350.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.23.0", "@posthog/types": "1.350.0", "core-js": "^3.38.1", "dompurify": "^3.3.1", "fflate": "^0.4.8", "preact": "^10.28.2", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.1.0" } }, "sha512-Ab+dyQdlKUTrfUZ12+fvcBo75S4jw/3o2gMleDga21B1v9c15yybiX4S3JrX66uh5L1DYG1H8sxtd4BXIIodjQ=="], @@ -859,6 +875,8 @@ "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "resend": ["resend@6.9.3", "", { "dependencies": { "postal-mime": "2.7.3", "svix": "1.84.1" }, "peerDependencies": { "@react-email/render": "*" }, "optionalPeers": ["@react-email/render"] }, "sha512-GRXjH9XZBJA+daH7bBVDuTShr22iWCxXA8P7t495G4dM/RC+d+3gHBK/6bz9K6Vpcq11zRQKmD+B+jECwQlyGQ=="], + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -907,6 +925,8 @@ "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], @@ -933,6 +953,8 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "svix": ["svix@1.84.1", "", { "dependencies": { "standardwebhooks": "1.0.0", "uuid": "^10.0.0" } }, "sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ=="], + "swr": ["swr@2.4.0", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw=="], "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], @@ -987,6 +1009,8 @@ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], diff --git a/web/next.config.ts b/web/next.config.ts index 52213e80..06164a93 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -1,3 +1,4 @@ +import "./app/env"; import type { NextConfig } from "next"; const nextConfig: NextConfig = { diff --git a/web/package-lock.json b/web/package-lock.json index a7b86b58..9f3d7da0 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -9,13 +9,18 @@ "version": "0.1.0", "license": "AGPL-3.0-or-later", "dependencies": { + "@t3-oss/env-nextjs": "^0.13.10", + "@vercel/firewall": "^1.1.2", "next": "16.1.6", "next-themes": "^0.4.6", "posthog-js": "^1.350.0", "react": "19.2.3", "react-dom": "19.2.3", + "react-tweet": "^3.3.0", "react-wrap-balancer": "^1.1.1", - "shiki": "^3.22.0" + "resend": "^6.9.3", + "shiki": "^3.22.0", + "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -1630,6 +1635,12 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1639,6 +1650,61 @@ "tslib": "^2.8.0" } }, + "node_modules/@t3-oss/env-core": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.13.10.tgz", + "integrity": "sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g==", + "license": "MIT", + "peerDependencies": { + "arktype": "^2.1.0", + "typescript": ">=5.0.0", + "valibot": "^1.0.0-beta.7 || ^1.0.0", + "zod": "^3.24.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "arktype": { + "optional": true + }, + "typescript": { + "optional": true + }, + "valibot": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@t3-oss/env-nextjs": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/@t3-oss/env-nextjs/-/env-nextjs-0.13.10.tgz", + "integrity": "sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ==", + "license": "MIT", + "dependencies": { + "@t3-oss/env-core": "0.13.10" + }, + "peerDependencies": { + "arktype": "^2.1.0", + "typescript": ">=5.0.0", + "valibot": "^1.0.0-beta.7 || ^1.0.0", + "zod": "^3.24.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "arktype": { + "optional": true + }, + "typescript": { + "optional": true + }, + "valibot": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz", @@ -2559,6 +2625,15 @@ "win32" ] }, + "node_modules/@vercel/firewall": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@vercel/firewall/-/firewall-1.1.2.tgz", + "integrity": "sha512-h0sdBVrloWx8TitvWla/rGj3AnJ5JEYfL5LaGHNNOWkyMuzNqfCcGTvJgnjL2A5eSpAAzoN7Xt609YQ0L7xZdw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -3055,6 +3130,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4031,6 +4115,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -6044,6 +6134,12 @@ "node": ">= 0.4" } }, + "node_modules/postal-mime": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.3.tgz", + "integrity": "sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==", + "license": "MIT-0" + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -6225,6 +6321,21 @@ "dev": true, "license": "MIT" }, + "node_modules/react-tweet": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-tweet/-/react-tweet-3.3.0.tgz", + "integrity": "sha512-gSIG2169ZK7UH6rBzuU+j1xnQbH3IlOTLEkuGrRiJJTMgETik+h+26yHyyVKrLkzwrOaYPk4K3OtEKycqKgNLw==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.3", + "clsx": "^2.0.0", + "swr": "^2.2.4" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/react-wrap-balancer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/react-wrap-balancer/-/react-wrap-balancer-1.1.1.tgz", @@ -6302,6 +6413,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resend": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.3.tgz", + "integrity": "sha512-GRXjH9XZBJA+daH7bBVDuTShr22iWCxXA8P7t495G4dM/RC+d+3gHBK/6bz9K6Vpcq11zRQKmD+B+jECwQlyGQ==", + "license": "MIT", + "dependencies": { + "postal-mime": "2.7.3", + "svix": "1.84.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -6695,6 +6827,16 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6908,6 +7050,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svix": { + "version": "1.84.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.84.1.tgz", + "integrity": "sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==", + "license": "MIT", + "dependencies": { + "standardwebhooks": "1.0.0", + "uuid": "^10.0.0" + } + }, + "node_modules/swr": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz", + "integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwindcss": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz", @@ -7140,7 +7305,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7343,6 +7508,28 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -7515,7 +7702,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/web/package.json b/web/package.json index c69800e5..4540ac42 100644 --- a/web/package.json +++ b/web/package.json @@ -10,6 +10,8 @@ "lint": "eslint" }, "dependencies": { + "@t3-oss/env-nextjs": "^0.13.10", + "@vercel/firewall": "^1.1.2", "next": "16.1.6", "next-themes": "^0.4.6", "posthog-js": "^1.350.0", @@ -17,7 +19,9 @@ "react-dom": "19.2.3", "react-tweet": "^3.3.0", "react-wrap-balancer": "^1.1.1", - "shiki": "^3.22.0" + "resend": "^6.9.3", + "shiki": "^3.22.0", + "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/postcss": "^4", From 33af2cd6a4618a2adc7750dd1130d8b5325eab13 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:17:58 -0800 Subject: [PATCH 134/232] Add regression test for background workspace git refresh --- ...w_workspace_external_git_branch_refresh.py | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 tests_v2/test_cli_new_workspace_external_git_branch_refresh.py diff --git a/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py b/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py new file mode 100644 index 00000000..87625710 --- /dev/null +++ b/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +"""Regression: background workspaces should refresh git branch after external repo changes.""" + +from __future__ import annotations + +import glob +import os +import shutil +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) +from cmux import cmux, cmuxError + + +SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") + + +def _must(cond: bool, msg: str) -> None: + if not cond: + raise cmuxError(msg) + + +def _find_cli_binary() -> str: + env_cli = os.environ.get("CMUXTERM_CLI") + if env_cli and os.path.isfile(env_cli) and os.access(env_cli, os.X_OK): + return env_cli + + fixed = os.path.expanduser("~/Library/Developer/Xcode/DerivedData/cmux-tests-v2/Build/Products/Debug/cmux") + if os.path.isfile(fixed) and os.access(fixed, os.X_OK): + return fixed + + candidates = glob.glob( + os.path.expanduser("~/Library/Developer/Xcode/DerivedData/**/Build/Products/Debug/cmux"), + recursive=True, + ) + candidates += glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux") + candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)] + if not candidates: + raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI") + candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True) + return candidates[0] + + +def _run_cli(cli: str, args: list[str]) -> str: + env = dict(os.environ) + env.pop("CMUX_WORKSPACE_ID", None) + env.pop("CMUX_SURFACE_ID", None) + env.pop("CMUX_TAB_ID", None) + + cmd = [cli, "--socket", SOCKET_PATH] + args + proc = subprocess.run(cmd, capture_output=True, text=True, check=False, env=env) + if proc.returncode != 0: + merged = f"{proc.stdout}\n{proc.stderr}".strip() + raise cmuxError(f"CLI failed ({' '.join(cmd)}): {merged}") + return (proc.stdout or "").strip() + + +def _parse_sidebar_state(text: str) -> dict[str, str]: + parsed: dict[str, str] = {} + for raw in text.splitlines(): + line = raw.strip() + if not line or "=" not in line: + continue + key, value = line.split("=", 1) + parsed[key.strip()] = value.strip() + return parsed + + +def _wait_for_sidebar_branch( + cli: str, + workspace: str, + expected_branch: str, + timeout: float = 15.0, +) -> dict[str, str]: + deadline = time.time() + timeout + last_state = "" + + while time.time() < deadline: + state_text = _run_cli(cli, ["sidebar-state", "--workspace", workspace]) + last_state = state_text + state = _parse_sidebar_state(state_text) + raw_branch = state.get("git_branch", "") + observed_branch = raw_branch.split(" ", 1)[0] + if observed_branch == expected_branch: + return state + time.sleep(0.1) + + raise cmuxError( + f"Timed out waiting for branch {expected_branch!r} on workspace {workspace}. " + f"Last sidebar-state: {last_state!r}" + ) + + +def _create_git_repo(root: Path) -> Path: + repo = root / "repo" + repo.mkdir(parents=True, exist_ok=True) + + subprocess.run( + ["git", "-c", "init.defaultBranch=main", "init"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + ["git", "config", "user.name", "cmux-test"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + ["git", "config", "user.email", "cmux-test@example.com"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + (repo / "README.md").write_text("issue 915 external refresh\n", encoding="utf-8") + subprocess.run( + ["git", "add", "README.md"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + ["git", "-c", "commit.gpgsign=false", "commit", "-m", "init"], + cwd=repo, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return repo + + +def main() -> int: + cli = _find_cli_binary() + temp_root = Path(tempfile.mkdtemp(prefix="cmux_issue_915_external_git_")) + created_workspace: str | None = None + + try: + repo_path = _create_git_repo(temp_root) + + with cmux(SOCKET_PATH) as client: + baseline_workspace = client.current_workspace() + + created = _run_cli(cli, ["new-workspace", "--cwd", str(repo_path)]) + _must(created.startswith("OK "), f"new-workspace expected OK response, got: {created!r}") + created_workspace = created.removeprefix("OK ").strip() + _must(bool(created_workspace), f"new-workspace returned no workspace handle: {created!r}") + + _must( + client.current_workspace() == baseline_workspace, + "new-workspace --cwd should preserve selected workspace", + ) + + initial_state = _wait_for_sidebar_branch(cli, created_workspace, "main") + _must( + initial_state.get("cwd", "") == str(repo_path), + f"Expected sidebar cwd={repo_path!r}, got {initial_state.get('cwd', '')!r}", + ) + + subprocess.run( + ["git", "checkout", "-b", "feature/external-refresh"], + cwd=repo_path, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + refreshed_state = _wait_for_sidebar_branch( + cli, + created_workspace, + "feature/external-refresh", + timeout=15.0, + ) + _must( + refreshed_state.get("cwd", "") == str(repo_path), + f"Expected refreshed sidebar cwd={repo_path!r}, got {refreshed_state.get('cwd', '')!r}", + ) + + _must( + client.current_workspace() == baseline_workspace, + "external git branch refresh should not switch selected workspace", + ) + finally: + if created_workspace: + try: + _run_cli(cli, ["close-workspace", "--workspace", created_workspace]) + except Exception: + pass + shutil.rmtree(temp_root, ignore_errors=True) + + print("PASS: background workspace git branch refreshes after external repo checkout") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 129177659710c5c2f14266dbaf7cf4b2cde6ac7f Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:18:04 -0800 Subject: [PATCH 135/232] Refresh background workspace git metadata after external checkout --- Sources/TabManager.swift | 163 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 07259451..37b07947 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -558,6 +558,11 @@ fileprivate func cmuxVsyncIOSurfaceTimelineCallback( @MainActor class TabManager: ObservableObject { + private struct InitialWorkspaceGitMetadataSnapshot: Equatable { + let branch: String? + let isDirty: Bool + } + /// The window that owns this TabManager. Set by AppDelegate.registerMainWindow(). /// Used to apply title updates to the correct window instead of NSApp.keyWindow. weak var window: NSWindow? @@ -569,6 +574,7 @@ class TabManager: ObservableObject { /// Global monotonically increasing counter for CMUX_PORT ordinal assignment. /// Static so port ranges don't overlap across multiple windows (each window has its own TabManager). private static var nextPortOrdinal: Int = 0 + private static let initialWorkspaceGitProbeDelays: [TimeInterval] = [0, 0.5, 1.5, 3.0, 6.0, 10.0] @Published var selectedTabId: UUID? { didSet { guard selectedTabId != oldValue else { return } @@ -624,6 +630,11 @@ class TabManager: ObservableObject { private var pendingPanelTitleUpdates: [PanelTitleUpdateKey: String] = [:] private let panelTitleUpdateCoalescer = NotificationBurstCoalescer(delay: 1.0 / 30.0) private var recentlyClosedBrowsers = RecentlyClosedBrowserStack(capacity: 20) + private let initialWorkspaceGitProbeQueue = DispatchQueue( + label: "com.cmux.initial-workspace-git-probe", + qos: .utility + ) + private var initialWorkspaceGitProbeGenerationByWorkspace: [UUID: UUID] = [:] // Recent tab history for back/forward navigation (like browser history) private var tabHistory: [UUID] = [] @@ -804,7 +815,8 @@ class TabManager: ObservableObject { placementOverride: NewWorkspacePlacement? = nil ) -> Workspace { sentryBreadcrumb("workspace.create", data: ["tabCount": tabs.count + 1]) - let workingDirectory = normalizedWorkingDirectory(overrideWorkingDirectory) ?? preferredWorkingDirectoryForNewTab() + let explicitWorkingDirectory = normalizedWorkingDirectory(overrideWorkingDirectory) + let workingDirectory = explicitWorkingDirectory ?? preferredWorkingDirectoryForNewTab() let inheritedConfig = inheritedTerminalConfigForNewWorkspace() let ordinal = Self.nextPortOrdinal Self.nextPortOrdinal += 1 @@ -821,6 +833,14 @@ class TabManager: ObservableObject { } else { tabs.append(newWorkspace) } + if let explicitWorkingDirectory, + let terminalPanel = newWorkspace.focusedTerminalPanel { + scheduleInitialWorkspaceGitMetadataRefresh( + workspaceId: newWorkspace.id, + panelId: terminalPanel.id, + directory: explicitWorkingDirectory + ) + } if eagerLoadTerminal { requestBackgroundWorkspaceLoad(for: newWorkspace.id) newWorkspace.requestBackgroundTerminalSurfaceStartIfNeeded() @@ -843,6 +863,145 @@ class TabManager: ObservableObject { return newWorkspace } + private func scheduleInitialWorkspaceGitMetadataRefresh( + workspaceId: UUID, + panelId: UUID, + directory: String + ) { + let normalizedDirectory = normalizeDirectory(directory) + let generation = UUID() + initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] = generation + +#if DEBUG + dlog( + "workspace.gitProbe.schedule workspace=\(workspaceId.uuidString.prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) dir=\(normalizedDirectory)" + ) +#endif + + let delays = Self.initialWorkspaceGitProbeDelays + for (index, delay) in delays.enumerated() { + let isLastAttempt = index == delays.count - 1 + initialWorkspaceGitProbeQueue.asyncAfter(deadline: .now() + delay) { + let snapshot = Self.initialWorkspaceGitMetadataSnapshot(for: normalizedDirectory) + Task { @MainActor [weak self] in + self?.applyInitialWorkspaceGitMetadataSnapshot( + snapshot, + generation: generation, + workspaceId: workspaceId, + panelId: panelId, + expectedDirectory: normalizedDirectory, + isLastAttempt: isLastAttempt + ) + } + } + } + } + + private func applyInitialWorkspaceGitMetadataSnapshot( + _ snapshot: InitialWorkspaceGitMetadataSnapshot, + generation: UUID, + workspaceId: UUID, + panelId: UUID, + expectedDirectory: String, + isLastAttempt: Bool + ) { + defer { + if isLastAttempt, + initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation { + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + } + } + + guard initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation else { return } + guard let workspace = tabs.first(where: { $0.id == workspaceId }) else { + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + return + } + guard workspace.panels[panelId] != nil else { + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + return + } + + let currentDirectory = normalizedWorkingDirectory( + workspace.panelDirectories[panelId] ?? workspace.currentDirectory + ) + if let currentDirectory, currentDirectory != expectedDirectory { + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) +#if DEBUG + dlog( + "workspace.gitProbe.skip workspace=\(workspaceId.uuidString.prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) reason=directoryChanged " + + "expected=\(expectedDirectory) current=\(currentDirectory)" + ) +#endif + return + } + + workspace.updatePanelDirectory(panelId: panelId, directory: expectedDirectory) + + let previousBranch = Self.normalizedBranchName(workspace.panelGitBranches[panelId]?.branch) + let nextBranch = snapshot.branch + if let nextBranch { + workspace.updatePanelGitBranch(panelId: panelId, branch: nextBranch, isDirty: snapshot.isDirty) + } else { + workspace.clearPanelGitBranch(panelId: panelId) + } + + if previousBranch != nextBranch || (nextBranch == nil && workspace.panelPullRequests[panelId] != nil) { + workspace.clearPanelPullRequest(panelId: panelId) + } + +#if DEBUG + let branchLabel = snapshot.branch ?? "none" + dlog( + "workspace.gitProbe.apply workspace=\(workspaceId.uuidString.prefix(5)) " + + "panel=\(panelId.uuidString.prefix(5)) branch=\(branchLabel) dirty=\(snapshot.isDirty ? 1 : 0)" + ) +#endif + } + + private nonisolated static func initialWorkspaceGitMetadataSnapshot( + for directory: String + ) -> InitialWorkspaceGitMetadataSnapshot { + let branch = normalizedBranchName(runGitCommand(directory: directory, arguments: ["branch", "--show-current"])) + guard let branch else { + return InitialWorkspaceGitMetadataSnapshot(branch: nil, isDirty: false) + } + + let statusOutput = runGitCommand(directory: directory, arguments: ["status", "--porcelain", "-uno"]) + let isDirty = !(statusOutput?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true) + return InitialWorkspaceGitMetadataSnapshot(branch: branch, isDirty: isDirty) + } + + private nonisolated static func runGitCommand(directory: String, arguments: [String]) -> String? { + let process = Process() + let stdout = Pipe() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = ["git", "-C", directory] + arguments + process.standardOutput = stdout + process.standardError = Pipe() + + do { + try process.run() + } catch { + return nil + } + + process.waitUntilExit() + guard process.terminationStatus == 0 else { + return nil + } + + let data = stdout.fileHandleForReading.readDataToEndOfFile() + return String(data: data, encoding: .utf8) + } + + private nonisolated static func normalizedBranchName(_ branch: String?) -> String? { + let trimmed = branch?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return trimmed.isEmpty ? nil : trimmed + } + func requestBackgroundWorkspaceLoad(for workspaceId: UUID) { guard pendingBackgroundWorkspaceLoadIds.insert(workspaceId).inserted else { return } } @@ -1037,6 +1196,7 @@ class TabManager: ObservableObject { guard tabs.count > 1 else { return } guard let index = tabs.firstIndex(where: { $0.id == workspace.id }) else { return } sentryBreadcrumb("workspace.close", data: ["tabCount": tabs.count - 1]) + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspace.id) AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: workspace.id) unwireClosedBrowserTracking(for: workspace) @@ -1058,6 +1218,7 @@ class TabManager: ObservableObject { @discardableResult func detachWorkspace(tabId: UUID) -> Workspace? { guard let index = tabs.firstIndex(where: { $0.id == tabId }) else { return nil } + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: tabId) let removed = tabs.remove(at: index) unwireClosedBrowserTracking(for: removed) From 6f01acfb5ff24fcf79e501d7a3c1d4fc6f4c1afd Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:18:39 -0800 Subject: [PATCH 136/232] Speed up command palette search --- GhosttyTabs.xcodeproj/project.pbxproj | 4 + Sources/ContentView.swift | 293 ++++++++++++++---- .../CommandPaletteSearchEngineTests.swift | 265 ++++++++++++++++ 3 files changed, 499 insertions(+), 63 deletions(-) create mode 100644 cmuxTests/CommandPaletteSearchEngineTests.swift diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index 9b977a9b..03578fe1 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */; }; F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */; }; A5008381 /* BrowserFindJavaScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008380 /* BrowserFindJavaScriptTests.swift */; }; + A5008383 /* CommandPaletteSearchEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008382 /* CommandPaletteSearchEngineTests.swift */; }; DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000001 /* Localizable.xcstrings */; }; DA7A10CA710E000000000004 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000002 /* InfoPlist.xcstrings */; }; /* End PBXBuildFile section */ @@ -228,6 +229,7 @@ F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentViewVisibilityTests.swift; sourceTree = ""; }; F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlPasswordStoreTests.swift; sourceTree = ""; }; A5008380 /* BrowserFindJavaScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserFindJavaScriptTests.swift; sourceTree = ""; }; + A5008382 /* CommandPaletteSearchEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandPaletteSearchEngineTests.swift; sourceTree = ""; }; DA7A10CA710E000000000001 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; DA7A10CA710E000000000002 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -453,6 +455,7 @@ F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */, F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */, A5008380 /* BrowserFindJavaScriptTests.swift */, + A5008382 /* CommandPaletteSearchEngineTests.swift */, ); path = cmuxTests; sourceTree = ""; @@ -687,6 +690,7 @@ F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */, F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */, A5008381 /* BrowserFindJavaScriptTests.swift in Sources */, + A5008383 /* CommandPaletteSearchEngineTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index a289f0eb..d240da98 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1261,6 +1261,10 @@ struct ContentView: View { @State private var commandPaletteScrollTargetIndex: Int? @State private var commandPaletteScrollTargetAnchor: UnitPoint? @State private var commandPaletteRestoreFocusTarget: CommandPaletteRestoreFocusTarget? + @State private var commandPaletteSearchCorpus: [CommandPaletteSearchCorpusEntry] = [] + @State private var cachedCommandPaletteResults: [CommandPaletteSearchResult] = [] + @State private var cachedCommandPaletteScope: CommandPaletteListScope? + @State private var cachedCommandPaletteFingerprint: Int? @State private var commandPaletteUsageHistoryByCommandId: [String: CommandPaletteUsageEntry] = [:] @AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey) private var commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus @@ -2729,7 +2733,7 @@ struct ContentView: View { } private var commandPaletteCommandListView: some View { - let visibleResults = Array(commandPaletteResults) + let visibleResults = cachedCommandPaletteResults let selectedIndex = commandPaletteSelectedIndex(resultCount: visibleResults.count) let commandPaletteListMaxHeight: CGFloat = 450 let commandPaletteRowHeight: CGFloat = 24 @@ -2868,7 +2872,8 @@ struct ContentView: View { } .onAppear { commandPaletteHoveredResultIndex = nil - updateCommandPaletteScrollTarget(resultCount: visibleResults.count, animated: false) + refreshCommandPaletteResults(forceSearchCorpusRefresh: true) + updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) resetCommandPaletteSearchFocus() } .onChange(of: commandPaletteQuery) { _ in @@ -2876,12 +2881,17 @@ struct ContentView: View { commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil + refreshCommandPaletteResults() syncCommandPaletteDebugStateForObservedWindow() } - .onChange(of: visibleResults.count) { _ in - commandPaletteSelectedResultIndex = commandPaletteSelectedIndex(resultCount: visibleResults.count) - updateCommandPaletteScrollTarget(resultCount: visibleResults.count, animated: false) - if let hoveredIndex = commandPaletteHoveredResultIndex, hoveredIndex >= visibleResults.count { + .onChange(of: commandPaletteCurrentSearchFingerprint) { _ in + refreshCommandPaletteResults(forceSearchCorpusRefresh: true) + syncCommandPaletteDebugStateForObservedWindow() + } + .onChange(of: cachedCommandPaletteResults.count) { _ in + commandPaletteSelectedResultIndex = commandPaletteSelectedIndex(resultCount: cachedCommandPaletteResults.count) + updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) + if let hoveredIndex = commandPaletteHoveredResultIndex, hoveredIndex >= cachedCommandPaletteResults.count { commandPaletteHoveredResultIndex = nil } syncCommandPaletteDebugStateForObservedWindow() @@ -2999,6 +3009,10 @@ struct ContentView: View { return .switcher } + private var commandPaletteCurrentSearchFingerprint: Int { + commandPaletteEntriesFingerprint(for: commandPaletteListScope) + } + private var commandPaletteSearchPlaceholder: String { switch commandPaletteListScope { case .commands: @@ -3027,8 +3041,8 @@ struct ContentView: View { } } - private var commandPaletteEntries: [CommandPaletteCommand] { - switch commandPaletteListScope { + private func commandPaletteEntries(for scope: CommandPaletteListScope) -> [CommandPaletteCommand] { + switch scope { case .commands: return commandPaletteCommands() case .switcher: @@ -3036,39 +3050,98 @@ struct ContentView: View { } } - private var commandPaletteResults: [CommandPaletteSearchResult] { - let entries = commandPaletteEntries - let query = commandPaletteQueryForMatching - let queryIsEmpty = query.isEmpty + private func refreshCommandPaletteSearchCorpus(force: Bool = false) { + let scope = commandPaletteListScope + let fingerprint = commandPaletteEntriesFingerprint(for: scope) + guard force || cachedCommandPaletteScope != scope || cachedCommandPaletteFingerprint != fingerprint else { + return + } - let results: [CommandPaletteSearchResult] = queryIsEmpty - ? entries.map { entry in - CommandPaletteSearchResult( - command: entry, - score: commandPaletteHistoryBoost(for: entry.id, queryIsEmpty: true), - titleMatchIndices: [] - ) - } - : entries.compactMap { entry in - guard let fuzzyScore = CommandPaletteFuzzyMatcher.score(query: query, candidates: entry.searchableTexts) else { - return nil - } - return CommandPaletteSearchResult( - command: entry, - score: fuzzyScore + commandPaletteHistoryBoost(for: entry.id, queryIsEmpty: false), - titleMatchIndices: CommandPaletteFuzzyMatcher.matchCharacterIndices( - query: query, - candidate: entry.title - ) - ) - } + let entries = commandPaletteEntries(for: scope) + commandPaletteSearchCorpus = entries.map { entry in + CommandPaletteSearchCorpusEntry( + payload: entry, + rank: entry.rank, + title: entry.title, + searchableTexts: entry.searchableTexts + ) + } + cachedCommandPaletteScope = scope + cachedCommandPaletteFingerprint = fingerprint + } - return results - .sorted { lhs, rhs in - if lhs.score != rhs.score { return lhs.score > rhs.score } - if lhs.command.rank != rhs.command.rank { return lhs.command.rank < rhs.command.rank } - return lhs.command.title.localizedCaseInsensitiveCompare(rhs.command.title) == .orderedAscending + private func refreshCommandPaletteResults(forceSearchCorpusRefresh: Bool = false) { + refreshCommandPaletteSearchCorpus(force: forceSearchCorpusRefresh) + cachedCommandPaletteResults = CommandPaletteSearchEngine.search( + entries: commandPaletteSearchCorpus, + query: commandPaletteQueryForMatching + ) { command, queryIsEmpty in + commandPaletteHistoryBoost(for: command.id, queryIsEmpty: queryIsEmpty) + } + .map { result in + CommandPaletteSearchResult( + command: result.payload, + score: result.score, + titleMatchIndices: result.titleMatchIndices + ) + } + } + + private func commandPaletteEntriesFingerprint(for scope: CommandPaletteListScope) -> Int { + switch scope { + case .commands: + return commandPaletteCommandsFingerprint() + case .switcher: + return commandPaletteSwitcherEntriesFingerprint() + } + } + + private func commandPaletteCommandsFingerprint() -> Int { + let panelContext = focusedPanelContext + let focusedDirectory: String? = { + guard let panelContext else { return nil } + if let directory = panelContext.workspace.panelDirectories[panelContext.panelId] { + return directory } + return panelContext.workspace.currentDirectory + }() + var hasher = Hasher() + hasher.combine(tabManager.sessionAutosaveFingerprint()) + hasher.combine(AppDelegate.shared?.isCmuxCLIInstalledInPATH() ?? false) + hasher.combine(panelContext?.panelId) + hasher.combine(panelContext?.panel.panelType.rawValue) + hasher.combine(panelContext?.workspace.id) + hasher.combine(panelContext?.workspace.manualUnreadPanelIds.count ?? 0) + hasher.combine(panelContext?.workspace.sidebarPullRequestsInDisplayOrder().count ?? 0) + hasher.combine(focusedDirectory) + hasher.combine(caseIsUpdateAvailable(updateViewModel.effectiveState)) + let availableTargets = TerminalDirectoryOpenTarget.cachedLiveAvailableTargets + .map(\.rawValue) + .sorted() + for target in availableTargets { + hasher.combine(target) + } + return hasher.finalize() + } + + private func commandPaletteSwitcherEntriesFingerprint() -> Int { + let windowContexts = commandPaletteSwitcherWindowContexts() + var hasher = Hasher() + hasher.combine(windowContexts.count) + for context in windowContexts { + hasher.combine(context.windowId) + hasher.combine(context.windowLabel) + hasher.combine(context.selectedWorkspaceId) + hasher.combine(context.tabManager.sessionAutosaveFingerprint()) + } + return hasher.finalize() + } + + private func caseIsUpdateAvailable(_ state: UpdateState) -> Bool { + if case .updateAvailable = state { + return true + } + return false } private func commandPaletteHighlightedTitleText(_ title: String, matchedIndices: Set) -> Text { @@ -4426,7 +4499,7 @@ struct ContentView: View { } private func moveCommandPaletteSelection(by delta: Int) { - let count = commandPaletteResults.count + let count = cachedCommandPaletteResults.count guard count > 0 else { NSSound.beep() return @@ -4490,7 +4563,7 @@ struct ContentView: View { } private func runSelectedCommandPaletteResult(visibleResults: [CommandPaletteSearchResult]? = nil) { - let visibleResults = visibleResults ?? Array(commandPaletteResults) + let visibleResults = visibleResults ?? cachedCommandPaletteResults guard !visibleResults.isEmpty else { NSSound.beep() return @@ -4589,7 +4662,7 @@ struct ContentView: View { private func syncCommandPaletteDebugStateForObservedWindow() { guard let window = observedWindow ?? NSApp.keyWindow ?? NSApp.mainWindow else { return } AppDelegate.shared?.setCommandPaletteVisible(isCommandPalettePresented, for: window) - let visibleResultCount = commandPaletteResults.count + let visibleResultCount = cachedCommandPaletteResults.count let selectedIndex = isCommandPalettePresented ? commandPaletteSelectedIndex(resultCount: visibleResultCount) : 0 AppDelegate.shared?.setCommandPaletteSelectionIndex(selectedIndex, for: window) AppDelegate.shared?.setCommandPaletteSnapshot(commandPaletteDebugSnapshot(), for: window) @@ -4608,7 +4681,7 @@ struct ContentView: View { mode = "rename_confirm" } - let rows = Array(commandPaletteResults.prefix(20)).map { result in + let rows = Array(cachedCommandPaletteResults.prefix(20)).map { result in CommandPaletteDebugResultRow( commandId: result.command.id, title: result.command.title, @@ -4653,6 +4726,7 @@ struct ContentView: View { commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil + refreshCommandPaletteResults(forceSearchCorpusRefresh: true) resetCommandPaletteSearchFocus() syncCommandPaletteDebugStateForObservedWindow() } @@ -4670,6 +4744,10 @@ struct ContentView: View { isCommandPaletteSearchFocused = false isCommandPaletteRenameFocused = false commandPaletteRestoreFocusTarget = nil + commandPaletteSearchCorpus = [] + cachedCommandPaletteResults = [] + cachedCommandPaletteScope = nil + cachedCommandPaletteFingerprint = nil if let window = observedWindow { _ = window.makeFirstResponder(nil) } @@ -5166,23 +5244,49 @@ enum CommandPaletteSwitcherSearchIndexer { enum CommandPaletteFuzzyMatcher { private static let tokenBoundaryChars: Set = [" ", "-", "_", "/", ".", ":"] + struct PreparedQuery { + let normalizedText: String + let tokens: [String] + + var isEmpty: Bool { + tokens.isEmpty + } + } + + static func preparedQuery(_ query: String) -> PreparedQuery { + let normalizedQuery = normalizeForSearch(query) + return PreparedQuery( + normalizedText: normalizedQuery, + tokens: normalizedQuery.split(separator: " ").map(String.init).filter { !$0.isEmpty } + ) + } + + static func normalizeForSearch(_ text: String) -> String { + text + .trimmingCharacters(in: .whitespacesAndNewlines) + .folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current) + .lowercased() + } + static func score(query: String, candidate: String) -> Int? { score(query: query, candidates: [candidate]) } static func score(query: String, candidates: [String]) -> Int? { - let normalizedQuery = normalize(query) - guard !normalizedQuery.isEmpty else { return 0 } - let tokens = normalizedQuery.split(separator: " ").map(String.init).filter { !$0.isEmpty } - guard !tokens.isEmpty else { return 0 } + score( + preparedQuery: preparedQuery(query), + normalizedCandidates: candidates + .map(normalizeForSearch) + .filter { !$0.isEmpty } + ) + } - let normalizedCandidates = candidates - .map(normalize) - .filter { !$0.isEmpty } + static func score(preparedQuery: PreparedQuery, normalizedCandidates: [String]) -> Int? { + guard !preparedQuery.isEmpty else { return 0 } guard !normalizedCandidates.isEmpty else { return nil } var totalScore = 0 - for token in tokens { + for token in preparedQuery.tokens { var bestTokenScore: Int? for candidate in normalizedCandidates { guard let candidateScore = scoreToken(token, in: candidate) else { continue } @@ -5195,19 +5299,19 @@ enum CommandPaletteFuzzyMatcher { } static func matchCharacterIndices(query: String, candidate: String) -> Set { - let normalizedQuery = normalize(query) - guard !normalizedQuery.isEmpty else { return [] } + matchCharacterIndices(preparedQuery: preparedQuery(query), candidate: candidate) + } - let tokens = normalizedQuery.split(separator: " ").map(String.init).filter { !$0.isEmpty } - guard !tokens.isEmpty else { return [] } + static func matchCharacterIndices(preparedQuery: PreparedQuery, candidate: String) -> Set { + guard !preparedQuery.isEmpty else { return [] } - let loweredCandidate = normalize(candidate) + let loweredCandidate = normalizeForSearch(candidate) guard !loweredCandidate.isEmpty else { return [] } let candidateChars = Array(loweredCandidate) var matched: Set = [] - for token in tokens { + for token in preparedQuery.tokens { if token == loweredCandidate { matched.formUnion(0.. String { - text - .trimmingCharacters(in: .whitespacesAndNewlines) - .folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current) - .lowercased() - } - private static func scoreToken(_ token: String, in candidate: String) -> Int? { guard !token.isEmpty else { return 0 } @@ -5613,6 +5710,76 @@ enum CommandPaletteFuzzyMatcher { } } +struct CommandPaletteSearchCorpusEntry { + let payload: Payload + let rank: Int + let title: String + let normalizedSearchableTexts: [String] + + init(payload: Payload, rank: Int, title: String, searchableTexts: [String]) { + self.payload = payload + self.rank = rank + self.title = title + self.normalizedSearchableTexts = searchableTexts + .map(CommandPaletteFuzzyMatcher.normalizeForSearch) + .filter { !$0.isEmpty } + } +} + +struct CommandPaletteSearchCorpusResult { + let payload: Payload + let rank: Int + let title: String + let score: Int + let titleMatchIndices: Set +} + +enum CommandPaletteSearchEngine { + static func search( + entries: [CommandPaletteSearchCorpusEntry], + query: String, + historyBoost: (Payload, Bool) -> Int + ) -> [CommandPaletteSearchCorpusResult] { + let preparedQuery = CommandPaletteFuzzyMatcher.preparedQuery(query) + let queryIsEmpty = preparedQuery.isEmpty + + let results: [CommandPaletteSearchCorpusResult] = queryIsEmpty + ? entries.map { entry in + CommandPaletteSearchCorpusResult( + payload: entry.payload, + rank: entry.rank, + title: entry.title, + score: historyBoost(entry.payload, true), + titleMatchIndices: [] + ) + } + : entries.compactMap { entry in + guard let fuzzyScore = CommandPaletteFuzzyMatcher.score( + preparedQuery: preparedQuery, + normalizedCandidates: entry.normalizedSearchableTexts + ) else { + return nil + } + return CommandPaletteSearchCorpusResult( + payload: entry.payload, + rank: entry.rank, + title: entry.title, + score: fuzzyScore + historyBoost(entry.payload, false), + titleMatchIndices: CommandPaletteFuzzyMatcher.matchCharacterIndices( + preparedQuery: preparedQuery, + candidate: entry.title + ) + ) + } + + return results.sorted { lhs, rhs in + if lhs.score != rhs.score { return lhs.score > rhs.score } + if lhs.rank != rhs.rank { return lhs.rank < rhs.rank } + return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending + } + } +} + private struct SidebarResizerAccessibilityModifier: ViewModifier { let accessibilityIdentifier: String? diff --git a/cmuxTests/CommandPaletteSearchEngineTests.swift b/cmuxTests/CommandPaletteSearchEngineTests.swift new file mode 100644 index 00000000..337d27b2 --- /dev/null +++ b/cmuxTests/CommandPaletteSearchEngineTests.swift @@ -0,0 +1,265 @@ +import XCTest + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +final class CommandPaletteSearchEngineTests: XCTestCase { + private struct FixtureEntry { + let id: String + let rank: Int + let title: String + let searchableTexts: [String] + } + + private struct FixtureResult: Equatable { + let id: String + let rank: Int + let title: String + let score: Int + let titleMatchIndices: Set + } + + private func makeCommandEntries(count: Int) -> [FixtureEntry] { + (0.. [FixtureEntry] { + (0.. [FixtureResult] { + let corpus = entries.map { entry in + CommandPaletteSearchCorpusEntry( + payload: entry.id, + rank: entry.rank, + title: entry.title, + searchableTexts: entry.searchableTexts + ) + } + + return CommandPaletteSearchEngine.search(entries: corpus, query: query) { _, _ in 0 } + .map { + FixtureResult( + id: $0.payload, + rank: $0.rank, + title: $0.title, + score: $0.score, + titleMatchIndices: $0.titleMatchIndices + ) + } + } + + private func legacyResults( + entries: [FixtureEntry], + query: String + ) -> [FixtureResult] { + let queryIsEmpty = query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + let results: [FixtureResult] = queryIsEmpty + ? entries.map { entry in + FixtureResult(id: entry.id, rank: entry.rank, title: entry.title, score: 0, titleMatchIndices: []) + } + : entries.compactMap { entry in + guard let fuzzyScore = CommandPaletteFuzzyMatcher.score( + query: query, + candidates: entry.searchableTexts + ) else { + return nil + } + return FixtureResult( + id: entry.id, + rank: entry.rank, + title: entry.title, + score: fuzzyScore, + titleMatchIndices: CommandPaletteFuzzyMatcher.matchCharacterIndices( + query: query, + candidate: entry.title + ) + ) + } + + return results.sorted { lhs, rhs in + if lhs.score != rhs.score { return lhs.score > rhs.score } + if lhs.rank != rhs.rank { return lhs.rank < rhs.rank } + return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending + } + } + + private func benchmarkElapsedMs(operation: () -> Void) -> Double { + let start = DispatchTime.now().uptimeNanoseconds + operation() + let elapsed = DispatchTime.now().uptimeNanoseconds - start + return Double(elapsed) / 1_000_000 + } + + private func repeatedQueries(_ baseQueries: [String], repetitions: Int) -> [String] { + Array(repeating: baseQueries, count: repetitions).flatMap { $0 } + } + + func testOptimizedSearchMatchesLegacyPipeline() { + let commandEntries = makeCommandEntries(count: 96) + let switcherEntries = makeSwitcherEntries(count: 64) + let queries = [ + "rename", + "rename tab", + "workspace", + "feature-12", + "3004", + "toggle side", + "open dir", + "phoenix", + "apply update", + ] + + for query in queries { + XCTAssertEqual( + optimizedResults(entries: commandEntries, query: query), + legacyResults(entries: commandEntries, query: query), + "Command corpus mismatch for query \(query)" + ) + XCTAssertEqual( + optimizedResults(entries: switcherEntries, query: query), + legacyResults(entries: switcherEntries, query: query), + "Switcher corpus mismatch for query \(query)" + ) + } + } + + func testCommandSearchBenchmarkBeatsLegacyPipeline() { + let entries = makeCommandEntries(count: 900) + let corpus = entries.map { entry in + CommandPaletteSearchCorpusEntry( + payload: entry.id, + rank: entry.rank, + title: entry.title, + searchableTexts: entry.searchableTexts + ) + } + let queries = repeatedQueries( + ["rename", "rename tab", "open dir", "toggle side", "apply update", "notif", "split right", "cmux"], + repetitions: 12 + ) + + for query in queries.prefix(8) { + _ = legacyResults(entries: entries, query: query) + _ = CommandPaletteSearchEngine.search(entries: corpus, query: query) { _, _ in 0 } + } + + let legacyMs = benchmarkElapsedMs { + for query in queries { + _ = legacyResults(entries: entries, query: query) + } + } + let optimizedMs = benchmarkElapsedMs { + for query in queries { + _ = CommandPaletteSearchEngine.search(entries: corpus, query: query) { _, _ in 0 } + } + } + + print(String(format: "BENCH cmd+shift+p legacy=%.2fms optimized=%.2fms", legacyMs, optimizedMs)) + XCTAssertLessThan(optimizedMs, legacyMs) + } + + func testSwitcherSearchBenchmarkBeatsLegacyPipeline() { + let entries = makeSwitcherEntries(count: 400) + let corpus = entries.map { entry in + CommandPaletteSearchCorpusEntry( + payload: entry.id, + rank: entry.rank, + title: entry.title, + searchableTexts: entry.searchableTexts + ) + } + let queries = repeatedQueries( + ["workspace 12", "phoenix", "feature-18", "rename-tab", "3007", "9202", "switch", "worktrees"], + repetitions: 12 + ) + + for query in queries.prefix(8) { + _ = legacyResults(entries: entries, query: query) + _ = CommandPaletteSearchEngine.search(entries: corpus, query: query) { _, _ in 0 } + } + + let legacyMs = benchmarkElapsedMs { + for query in queries { + _ = legacyResults(entries: entries, query: query) + } + } + let optimizedMs = benchmarkElapsedMs { + for query in queries { + _ = CommandPaletteSearchEngine.search(entries: corpus, query: query) { _, _ in 0 } + } + } + + print(String(format: "BENCH cmd+p legacy=%.2fms optimized=%.2fms", legacyMs, optimizedMs)) + XCTAssertLessThan(optimizedMs, legacyMs) + } +} From dd2eeae503e4e46c852294c9b4ee6e2801aca880 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:19:08 -0800 Subject: [PATCH 137/232] Stop reload scripts from stealing window focus (#988) Use `open -g` (background) and remove `osascript activate` calls across reload.sh, reloadp.sh, and reloads.sh so rebuilds no longer yank focus away from the active window. --- scripts/reload.sh | 7 +++---- scripts/reloadp.sh | 3 +-- scripts/reloads.sh | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/scripts/reload.sh b/scripts/reload.sh index 3cd2bb63..43a58863 100755 --- a/scripts/reload.sh +++ b/scripts/reload.sh @@ -318,14 +318,13 @@ OPEN_CLEAN_ENV=( if [[ -n "${TAG_SLUG:-}" && -n "${CMUX_SOCKET:-}" ]]; then # Ensure tag-specific socket paths win even if the caller has CMUX_* overrides. - "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" open "$APP_PATH" + "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" open -g "$APP_PATH" elif [[ -n "${TAG_SLUG:-}" ]]; then - "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" open "$APP_PATH" + "${OPEN_CLEAN_ENV[@]}" CMUX_TAG="$TAG_SLUG" CMUX_DEBUG_LOG="$CMUX_DEBUG_LOG" open -g "$APP_PATH" else echo "/tmp/cmux-debug.log" > /tmp/cmux-last-debug-log-path || true - "${OPEN_CLEAN_ENV[@]}" open "$APP_PATH" + "${OPEN_CLEAN_ENV[@]}" open -g "$APP_PATH" fi -osascript -e "tell application id \"${BUNDLE_ID}\" to activate" || true # Safety: ensure only one instance is running. sleep 0.2 diff --git a/scripts/reloadp.sh b/scripts/reloadp.sh index fbb75fe8..62bc0597 100755 --- a/scripts/reloadp.sh +++ b/scripts/reloadp.sh @@ -17,5 +17,4 @@ if [[ -z "${APP_PATH}" ]]; then fi # Dev shells (including CI/Codex) often force-disable paging by exporting these. # Don't leak that into cmux, otherwise `git diff` won't page even with PAGER=less. -env -u GIT_PAGER -u GH_PAGER open "$APP_PATH" -osascript -e 'tell application "cmux" to activate' || true +env -u GIT_PAGER -u GH_PAGER open -g "$APP_PATH" diff --git a/scripts/reloads.sh b/scripts/reloads.sh index f2c2dfad..f06bc246 100755 --- a/scripts/reloads.sh +++ b/scripts/reloads.sh @@ -251,8 +251,7 @@ OPEN_CLEAN_ENV=( # Always inject staging socket paths via env to ensure they take effect # (LSEnvironment requires app restart to pick up plist changes). -"${OPEN_CLEAN_ENV[@]}" CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" open "$APP_PATH" -osascript -e "tell application id \"${BUNDLE_ID}\" to activate" || true +"${OPEN_CLEAN_ENV[@]}" CMUX_SOCKET_PATH="$CMUX_SOCKET" CMUXD_UNIX_PATH="$CMUXD_SOCKET" open -g "$APP_PATH" # Safety: ensure only one instance is running. sleep 0.2 From d1f042c3461e243f67788cfc7439dc2154074287 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:22:19 -0800 Subject: [PATCH 138/232] test: cover command palette browser click focus restore --- .../BrowserPaneNavigationKeybindUITests.swift | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift index 9cd9f038..8dc8e521 100644 --- a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift +++ b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift @@ -275,6 +275,69 @@ final class BrowserPaneNavigationKeybindUITests: XCTestCase { ) } + func testClickingBrowserDismissesCommandPaletteAndKeepsBrowserFocus() { + let app = XCUIApplication() + app.launchEnvironment["CMUX_SOCKET_PATH"] = socketPath + app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_SETUP"] = "1" + app.launchEnvironment["CMUX_UI_TEST_GOTO_SPLIT_PATH"] = dataPath + app.launchEnvironment["CMUX_UI_TEST_FOCUS_SHORTCUTS"] = "1" + launchAndEnsureForeground(app) + + XCTAssertTrue( + waitForData(keys: ["browserPanelId", "terminalPaneId", "webViewFocused"], timeout: 10.0), + "Expected goto_split setup data to be written" + ) + + guard let setup = loadData() else { + XCTFail("Missing goto_split setup data") + return + } + + guard let expectedBrowserPanelId = setup["browserPanelId"] else { + XCTFail("Missing browserPanelId in goto_split setup data") + return + } + + guard let expectedTerminalPaneId = setup["terminalPaneId"] else { + XCTFail("Missing terminalPaneId in goto_split setup data") + return + } + + // Move focus away from browser to terminal first so Cmd+R opens the rename overlay. + app.typeKey("h", modifierFlags: [.command, .control]) + XCTAssertTrue( + waitForDataMatch(timeout: 5.0) { data in + data["lastMoveDirection"] == "left" && data["focusedPaneId"] == expectedTerminalPaneId + }, + "Expected Cmd+Ctrl+H to move focus to left pane (terminal)" + ) + + let renameField = app.textFields["CommandPaletteRenameField"].firstMatch + app.typeKey("r", modifierFlags: [.command]) + XCTAssertTrue( + renameField.waitForExistence(timeout: 5.0), + "Expected Cmd+R to open the rename command palette while terminal is focused" + ) + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 2.0), "Expected main window for browser-pane click") + window.coordinate(withNormalizedOffset: CGVector(dx: 0.82, dy: 0.78)).click() + RunLoop.current.run(until: Date().addingTimeInterval(0.2)) + XCTAssertFalse(renameField.exists, "Expected clicking the browser pane to dismiss the command palette") + + // Cmd+L behavior is context-aware: + // - If terminal is still focused: opens a new browser in that pane. + // - If the original browser took focus: focuses that existing browser's omnibar. + app.typeKey("l", modifierFlags: [.command]) + XCTAssertTrue( + waitForDataMatch(timeout: 5.0) { data in + guard data["webViewFocusedAfterAddressBarFocus"] == "false" else { return false } + return data["webViewFocusedAfterAddressBarFocusPanelId"] == expectedBrowserPanelId + }, + "Expected clicking browser content to dismiss the palette and keep focus on the existing browser pane" + ) + } + func testCmdDSplitsRightWhenWebViewFocused() { let app = XCUIApplication() app.launchEnvironment["CMUX_SOCKET_PATH"] = socketPath From 82d6086d69891d0fca53f50df83c9276befe59d7 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:22:22 -0800 Subject: [PATCH 139/232] fix: restore browser focus on command palette backdrop click --- Sources/ContentView.swift | 187 +++++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 4 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index a289f0eb..6acab274 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1111,6 +1111,23 @@ private final class WindowCommandPaletteOverlayController: NSObject { containerView.isHidden = true } } + + func underlyingResponder(atWindowPoint windowPoint: NSPoint) -> NSResponder? { + guard let window, + let contentView = window.contentView, + let themeFrame = contentView.superview else { + return nil + } + + let previousCapturesMouseEvents = containerView.capturesMouseEvents + containerView.capturesMouseEvents = false + defer { + containerView.capturesMouseEvents = previousCapturesMouseEvents + } + + let pointInTheme = themeFrame.convert(windowPoint, from: nil) + return themeFrame.hitTest(pointInTheme) + } } @MainActor @@ -1123,6 +1140,39 @@ private func commandPaletteWindowOverlayController(for window: NSWindow) -> Wind return controller } +private func commandPaletteOwningWebView(for responder: NSResponder?) -> WKWebView? { + guard let responder else { return nil } + + if let webView = responder as? WKWebView { + return webView + } + + if let view = responder as? NSView { + var current: NSView? = view + while let candidate = current { + if let webView = candidate as? WKWebView { + return webView + } + current = candidate.superview + } + } + + if let textView = responder as? NSTextView, + let delegateView = textView.delegate as? NSView { + return commandPaletteOwningWebView(for: delegateView) + } + + var currentResponder = responder.nextResponder + while let next = currentResponder { + if let webView = commandPaletteOwningWebView(for: next) { + return webView + } + currentResponder = next.nextResponder + } + + return nil +} + enum WorkspaceMountPolicy { // Keep only the selected workspace mounted to minimize layer-tree traversal. static let maxMountedWorkspaces = 1 @@ -2694,9 +2744,18 @@ struct ContentView: View { Color.clear .ignoresSafeArea() .contentShape(Rectangle()) - .onTapGesture { - dismissCommandPalette() - } + .gesture( + DragGesture(minimumDistance: 0) + .onEnded { value in + handleCommandPaletteBackdropClick(atContentPoint: value.location) + } + ) + + Color.clear + .ignoresSafeArea() + .contentShape(Rectangle()) + .allowsHitTesting(false) + .accessibilityIdentifier("CommandPaletteBackdrop") VStack(spacing: 0) { switch commandPaletteMode { @@ -2745,6 +2804,7 @@ struct ContentView: View { .font(.system(size: 13, weight: .regular)) .tint(Color(nsColor: sidebarActiveForegroundNSColor(opacity: 1.0))) .focused($isCommandPaletteSearchFocused) + .accessibilityIdentifier("CommandPaletteSearchField") .onSubmit { runSelectedCommandPaletteResult(visibleResults: visibleResults) } @@ -2898,6 +2958,7 @@ struct ContentView: View { .font(.system(size: 13, weight: .regular)) .tint(Color(nsColor: sidebarActiveForegroundNSColor(opacity: 1.0))) .focused($isCommandPaletteRenameFocused) + .accessibilityIdentifier("CommandPaletteRenameField") .backport.onKeyPress(.delete) { modifiers in handleCommandPaletteRenameDeleteBackward(modifiers: modifiers) } @@ -4658,7 +4719,14 @@ struct ContentView: View { } private func dismissCommandPalette(restoreFocus: Bool = true) { - let focusTarget = commandPaletteRestoreFocusTarget + dismissCommandPalette(restoreFocus: restoreFocus, preferredFocusTarget: nil) + } + + private func dismissCommandPalette( + restoreFocus: Bool, + preferredFocusTarget: CommandPaletteRestoreFocusTarget? + ) { + let focusTarget = preferredFocusTarget ?? commandPaletteRestoreFocusTarget isCommandPalettePresented = false commandPaletteMode = .commands commandPaletteQuery = "" @@ -4679,6 +4747,117 @@ struct ContentView: View { restoreCommandPaletteFocus(target: focusTarget, attemptsRemaining: 6) } + private func handleCommandPaletteBackdropClick(atContentPoint contentPoint: CGPoint) { + let clickedFocusTarget = commandPaletteBackdropFocusTarget(atContentPoint: contentPoint) +#if DEBUG + if let clickedFocusTarget { + dlog( + "palette.dismiss.backdrop focusTarget panel=\(clickedFocusTarget.panelId.uuidString.prefix(5)) " + + "workspace=\(clickedFocusTarget.workspaceId.uuidString.prefix(5)) intent=\(clickedFocusTarget.intent == .browserAddressBar ? "addressBar" : "panel")" + ) + } else { + dlog("palette.dismiss.backdrop focusTarget=nil") + } +#endif + dismissCommandPalette(restoreFocus: true, preferredFocusTarget: clickedFocusTarget) + } + + private func commandPaletteBackdropFocusTarget(atContentPoint contentPoint: CGPoint) -> CommandPaletteRestoreFocusTarget? { + guard let window = observedWindow, + let contentView = window.contentView else { + return nil + } + + let contentPoint = NSPoint(x: contentPoint.x, y: contentPoint.y) + let windowPoint = contentView.convert(contentPoint, to: nil) + return commandPaletteBackdropFocusTarget(atWindowPoint: windowPoint, in: window) + } + + private func commandPaletteBackdropFocusTarget( + atWindowPoint windowPoint: NSPoint, + in window: NSWindow + ) -> CommandPaletteRestoreFocusTarget? { + let overlayController = commandPaletteWindowOverlayController(for: window) + if let responder = overlayController.underlyingResponder(atWindowPoint: windowPoint), + let target = commandPaletteBackdropFocusTarget(for: responder) { + return target + } + + if let webView = BrowserWindowPortalRegistry.webViewAtWindowPoint(windowPoint, in: window), + let target = commandPaletteBrowserFocusTarget(for: webView) { + return target + } + + if let terminalView = TerminalWindowPortalRegistry.terminalViewAtWindowPoint(windowPoint, in: window), + let workspaceId = terminalView.tabId, + let panelId = terminalView.terminalSurface?.id, + tabManager.tabs.contains(where: { $0.id == workspaceId }) { + return CommandPaletteRestoreFocusTarget( + workspaceId: workspaceId, + panelId: panelId, + intent: .panel + ) + } + + return nil + } + + private func commandPaletteBackdropFocusTarget(for responder: NSResponder) -> CommandPaletteRestoreFocusTarget? { + if let terminalView = cmuxOwningGhosttyView(for: responder), + let workspaceId = terminalView.tabId, + let panelId = terminalView.terminalSurface?.id, + tabManager.tabs.contains(where: { $0.id == workspaceId }) { + return CommandPaletteRestoreFocusTarget( + workspaceId: workspaceId, + panelId: panelId, + intent: .panel + ) + } + + if let webView = commandPaletteOwningWebView(for: responder), + let target = commandPaletteBrowserFocusTarget(for: webView) { + return target + } + + return nil + } + + private func commandPaletteBrowserFocusTarget(for webView: WKWebView) -> CommandPaletteRestoreFocusTarget? { + if let selectedWorkspace = tabManager.selectedWorkspace, + let target = commandPaletteBrowserFocusTarget(in: selectedWorkspace, for: webView) { + return target + } + + let selectedWorkspaceId = tabManager.selectedTabId + for workspace in tabManager.tabs where workspace.id != selectedWorkspaceId { + if let target = commandPaletteBrowserFocusTarget(in: workspace, for: webView) { + return target + } + } + + return nil + } + + private func commandPaletteBrowserFocusTarget( + in workspace: Workspace, + for webView: WKWebView + ) -> CommandPaletteRestoreFocusTarget? { + for (panelId, panel) in workspace.panels { + guard let browserPanel = panel as? BrowserPanel, + browserPanel.webView === webView else { + continue + } + + return CommandPaletteRestoreFocusTarget( + workspaceId: workspace.id, + panelId: panelId, + intent: .panel + ) + } + + return nil + } + private func restoreCommandPaletteFocus( target: CommandPaletteRestoreFocusTarget, attemptsRemaining: Int From 335aaaececdfc668f621fb323783ca9c99fc7a54 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:24:25 -0800 Subject: [PATCH 140/232] Fix send_workspace routing in inactive windows --- Sources/TerminalController.swift | 50 +++++++++++++++++-- .../MultiWindowNotificationsUITests.swift | 8 ++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 0269522e..323cd891 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -9345,7 +9345,7 @@ class TerminalController { sidebar_overlay_gate [active|inactive] - Return true/false if sidebar outside-drop overlay would capture (test-only) terminal_drop_overlay_probe [deferred|direct] - Trigger focused terminal drop-overlay show path and report animation counts (test-only) activate_app - Bring app + main window to front (test-only) - send_workspace - Send text to a workspace's focused terminal (test-only) + send_workspace - Send text to a workspace's selected terminal (test-only) is_terminal_focused - Return true/false if terminal surface is first responder (test-only) read_terminal_text [id|idx] - Read visible terminal text (base64, test-only) render_stats [id|idx] - Read terminal render stats (draw counters, test-only) @@ -11617,9 +11617,13 @@ class TerminalController { error = "ERROR: Workspace not found" return } - guard let tab = targetManager.tabs.first(where: { $0.id == workspaceId }), - let terminalPanel = tab.focusedTerminalPanel else { - error = "ERROR: No focused terminal in workspace" + guard let tab = targetManager.tabs.first(where: { $0.id == workspaceId }) else { + error = "ERROR: Workspace not found" + return + } + + guard let terminalPanel = sendableWorkspaceTerminalPanel(in: tab) else { + error = "ERROR: No selected terminal in workspace" return } @@ -11641,6 +11645,44 @@ class TerminalController { return success ? "OK" : "ERROR: Failed to send input" } + private func sendableWorkspaceTerminalPanel(in workspace: Workspace) -> TerminalPanel? { + func selectedTerminalPanel(in paneId: PaneID) -> TerminalPanel? { + guard let selectedTab = workspace.bonsplitController.selectedTab(inPane: paneId), + let panelId = workspace.panelIdFromSurfaceId(selectedTab.id), + let terminalPanel = workspace.panels[panelId] as? TerminalPanel else { + return nil + } + return terminalPanel + } + + func isSelectedTerminalPanel(_ terminalPanel: TerminalPanel) -> Bool { + guard let surfaceId = workspace.surfaceIdFromPanelId(terminalPanel.id) else { + return false + } + return workspace.bonsplitController.allPaneIds.contains { paneId in + workspace.bonsplitController.selectedTab(inPane: paneId)?.id == surfaceId + } + } + + if let focusedPane = workspace.bonsplitController.focusedPaneId, + let terminalPanel = selectedTerminalPanel(in: focusedPane) { + return terminalPanel + } + + if let rememberedTerminal = workspace.lastRememberedTerminalPanelForConfigInheritance(), + isSelectedTerminalPanel(rememberedTerminal) { + return rememberedTerminal + } + + for paneId in workspace.bonsplitController.allPaneIds { + if let terminalPanel = selectedTerminalPanel(in: paneId) { + return terminalPanel + } + } + + return nil + } + private func sendInputToSurface(_ args: String) -> String { guard let tabManager = tabManager else { return "ERROR: TabManager not available" } let parts = args.split(separator: " ", maxSplits: 1).map(String.init) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 5753173f..2ad740ce 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -289,8 +289,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { "2>\(shellSingleQuote(commandStderrPath));", "printf '%s' $? >\(shellSingleQuote(commandStatusPath))) >/dev/null 2>&1 &" ].joined(separator: " ") - guard socketCommand("send_workspace \(tabId1) \(notifyCommand)\\n") == "OK" else { - XCTFail("Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1)") + let sendWorkspaceResponse = socketCommand("send_workspace \(tabId1) \(notifyCommand)\\n") + guard sendWorkspaceResponse == "OK" else { + XCTFail( + "Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1). " + + "response=\(sendWorkspaceResponse ?? "")" + ) return } From ab1ca8130a926074fe3cf27445eeda010173989e Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:28:25 -0800 Subject: [PATCH 141/232] test: wait for command palette rename field to dismiss --- .../BrowserPaneNavigationKeybindUITests.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift index 8dc8e521..d6138266 100644 --- a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift +++ b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift @@ -322,8 +322,10 @@ final class BrowserPaneNavigationKeybindUITests: XCTestCase { let window = app.windows.firstMatch XCTAssertTrue(window.waitForExistence(timeout: 2.0), "Expected main window for browser-pane click") window.coordinate(withNormalizedOffset: CGVector(dx: 0.82, dy: 0.78)).click() - RunLoop.current.run(until: Date().addingTimeInterval(0.2)) - XCTAssertFalse(renameField.exists, "Expected clicking the browser pane to dismiss the command palette") + XCTAssertTrue( + waitForNonExistence(renameField, timeout: 5.0), + "Expected clicking the browser pane to dismiss the command palette" + ) // Cmd+L behavior is context-aware: // - If terminal is still focused: opens a new browser in that pane. @@ -707,6 +709,12 @@ final class BrowserPaneNavigationKeybindUITests: XCTestCase { return false } + private func waitForNonExistence(_ element: XCUIElement, timeout: TimeInterval) -> Bool { + let predicate = NSPredicate(format: "exists == false") + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) + return XCTWaiter().wait(for: [expectation], timeout: timeout) == .completed + } + private func loadData() -> [String: String]? { guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)) else { return nil From 15c7c0cc3cf410b60493ef0621f2e2ec17599d25 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 5 Mar 2026 21:28:31 -0800 Subject: [PATCH 142/232] Fix side-docked dev tools resize (#712) * wip * Fix side-docked dev tools resize --- Sources/AppDelegate.swift | 37 +- Sources/BrowserWindowPortal.swift | 598 ++++++++++- Sources/Panels/BrowserPanel.swift | 68 +- Sources/Panels/BrowserPanelView.swift | 568 ++++++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 929 +++++++++++++++++- 5 files changed, 2145 insertions(+), 55 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 400b5d14..88b76d11 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -9314,12 +9314,32 @@ private extension NSWindow { if let webView = candidate as? CmuxWebView { return webView } + if String(describing: type(of: candidate)).contains("WindowBrowserSlotView"), + let portalWebView = cmuxUniqueBrowserWebView(in: candidate) { + return portalWebView + } current = candidate.superview } return nil } + private static func cmuxUniqueBrowserWebView(in root: NSView) -> CmuxWebView? { + var stack: [NSView] = [root] + var found: CmuxWebView? + while let current = stack.popLast() { + if let webView = current as? CmuxWebView { + if found == nil { + found = webView + } else if found !== webView { + return nil + } + } + stack.append(contentsOf: current.subviews) + } + return found + } + private static func cmuxCurrentEvent(for _: NSWindow) -> NSEvent? { #if DEBUG if let override = cmuxFirstResponderGuardCurrentEventOverride { @@ -9335,7 +9355,22 @@ private extension NSWindow { return override } #endif - return window.contentView?.hitTest(event.locationInWindow) + guard let contentView = window.contentView else { return nil } + + if contentView.className == "NSGlassEffectView" { + let pointInContent = contentView.convert(event.locationInWindow, from: nil) + return contentView.hitTest(pointInContent) + } + + if let themeFrame = contentView.superview { + let pointInTheme = themeFrame.convert(event.locationInWindow, from: nil) + if let hit = themeFrame.hitTest(pointInTheme) { + return hit + } + } + + let pointInContent = contentView.convert(event.locationInWindow, from: nil) + return contentView.hitTest(pointInContent) } private static func cmuxTrackFieldEditor(_ fieldEditor: NSTextView, owningWebView webView: CmuxWebView?) { diff --git a/Sources/BrowserWindowPortal.swift b/Sources/BrowserWindowPortal.swift index da7be546..291b6f6f 100644 --- a/Sources/BrowserWindowPortal.swift +++ b/Sources/BrowserWindowPortal.swift @@ -24,6 +24,28 @@ final class WindowBrowserHostView: NSView { let isVertical: Bool } + private struct DividerHit { + let kind: DividerCursorKind + let isInHostedContent: Bool + } + + private struct HostedInspectorDividerHit { + let slotView: WindowBrowserSlotView + let containerView: NSView + let pageView: NSView + let inspectorView: NSView + } + + private struct HostedInspectorDividerDragState { + let slotView: WindowBrowserSlotView + let containerView: NSView + let pageView: NSView + let inspectorView: NSView + let initialWindowX: CGFloat + let initialPageFrame: NSRect + let initialInspectorFrame: NSRect + } + private enum DividerCursorKind: Equatable { case vertical case horizontal @@ -39,10 +61,54 @@ final class WindowBrowserHostView: NSView { override var isOpaque: Bool { false } private static let sidebarLeadingEdgeEpsilon: CGFloat = 1 private static let minimumVisibleLeadingContentWidth: CGFloat = 24 + private static let hostedInspectorDividerHitExpansion: CGFloat = 6 + private static let minimumHostedInspectorWidth: CGFloat = 120 private var cachedSidebarDividerX: CGFloat? private var sidebarDividerMissCount = 0 private var trackingArea: NSTrackingArea? private var activeDividerCursorKind: DividerCursorKind? + private var hostedInspectorDividerDrag: HostedInspectorDividerDragState? + +#if DEBUG + private static func shouldLogPointerEvent(_ event: NSEvent?) -> Bool { + switch event?.type { + case .leftMouseDown, .leftMouseDragged, .leftMouseUp: + return true + default: + return false + } + } + + private func debugLogPointerRouting( + stage: String, + point: NSPoint, + titlebarPassThrough: Bool, + sidebarPassThrough: Bool, + dividerHit: DividerHit?, + hitView: NSView? + ) { + let event = NSApp.currentEvent + guard Self.shouldLogPointerEvent(event) else { return } + + let hitDesc: String = { + guard let hitView else { return "nil" } + return "\(type(of: hitView))@\(browserPortalDebugToken(hitView))" + }() + let dividerDesc: String = { + guard let dividerHit else { return "nil" } + let kind = dividerHit.kind == .vertical ? "vertical" : "horizontal" + return "kind=\(kind),hosted=\(dividerHit.isInHostedContent ? 1 : 0)" + }() + let windowPoint = convert(point, to: nil) + dlog( + "browser.portal.pointer stage=\(stage) event=\(String(describing: event?.type)) " + + "host=\(browserPortalDebugToken(self)) point=\(browserPortalDebugFrame(NSRect(origin: point, size: .zero))) " + + "windowPoint=\(browserPortalDebugFrame(NSRect(origin: windowPoint, size: .zero))) " + + "titlebar=\(titlebarPassThrough ? 1 : 0) sidebar=\(sidebarPassThrough ? 1 : 0) " + + "divider=\(dividerDesc) hit=\(hitDesc)" + ) + } +#endif override func viewDidMoveToWindow() { super.viewDidMoveToWindow() @@ -62,9 +128,29 @@ final class WindowBrowserHostView: NSView { window?.invalidateCursorRects(for: self) } + override func layout() { + super.layout() + reapplyHostedInspectorDividersIfNeeded(reason: "host.layout") + } + + override func didAddSubview(_ subview: NSView) { + super.didAddSubview(subview) + guard let slot = subview as? WindowBrowserSlotView else { return } + slot.onHostedInspectorLayout = { [weak self] slotView in + self?.reapplyHostedInspectorDividerIfNeeded(in: slotView, reason: "slot.layout") + } + } + + override func willRemoveSubview(_ subview: NSView) { + if let slot = subview as? WindowBrowserSlotView { + slot.onHostedInspectorLayout = nil + } + super.willRemoveSubview(subview) + } + override func resetCursorRects() { super.resetCursorRects() - guard let window, let rootView = window.contentView else { return } + guard let rootView = dividerSearchRootView() else { return } var regions: [DividerRegion] = [] Self.collectSplitDividerRegions(in: rootView, into: ®ions) let expansion: CGFloat = 4 @@ -113,18 +199,57 @@ final class WindowBrowserHostView: NSView { } override func hitTest(_ point: NSPoint) -> NSView? { - updateDividerCursor(at: point) + let dividerHit = splitDividerHit(at: point) + let hostedInspectorHit = dividerHit == nil ? hostedInspectorDividerHit(at: point) : nil + updateDividerCursor(at: point, dividerHit: dividerHit, hostedInspectorHit: hostedInspectorHit) - if shouldPassThroughToTitlebar(at: point) { - return nil - } - if shouldPassThroughToSidebarResizer(at: point) { - return nil - } - if shouldPassThroughToSplitDivider(at: point) { - return nil - } + let titlebarPassThrough = shouldPassThroughToTitlebar(at: point) + let sidebarPassThrough = shouldPassThroughToSidebarResizer( + at: point, + dividerHit: dividerHit, + hostedInspectorHit: hostedInspectorHit + ) + let splitPassThrough = dividerHit.map { !$0.isInHostedContent } ?? false + if titlebarPassThrough { +#if DEBUG + debugLogPointerRouting( + stage: "hitTest.titlebarPass", + point: point, + titlebarPassThrough: true, + sidebarPassThrough: sidebarPassThrough, + dividerHit: dividerHit, + hitView: nil + ) +#endif + return nil + } + if sidebarPassThrough { +#if DEBUG + debugLogPointerRouting( + stage: "hitTest.sidebarPass", + point: point, + titlebarPassThrough: false, + sidebarPassThrough: true, + dividerHit: dividerHit, + hitView: nil + ) +#endif + return nil + } + if splitPassThrough { +#if DEBUG + debugLogPointerRouting( + stage: "hitTest.splitPass", + point: point, + titlebarPassThrough: false, + sidebarPassThrough: false, + dividerHit: dividerHit, + hitView: nil + ) +#endif + return nil + } // Mirror terminal portal routing: while tab-reorder drags are active, // pass through to SwiftUI drop targets behind the portal host. // Browser hover routing also arrives as cursor/enter events and may not @@ -135,10 +260,143 @@ final class WindowBrowserHostView: NSView { ) { return nil } + + if let hostedInspectorHit { + if let nativeHit = nativeHostedInspectorHit(at: point, hostedInspectorHit: hostedInspectorHit) { +#if DEBUG + debugLogPointerRouting( + stage: "hitTest.hostedInspectorNative", + point: point, + titlebarPassThrough: false, + sidebarPassThrough: false, + dividerHit: DividerHit(kind: .vertical, isInHostedContent: true), + hitView: nativeHit + ) +#endif + return nativeHit + } +#if DEBUG + debugLogPointerRouting( + stage: "hitTest.hostedInspectorManual", + point: point, + titlebarPassThrough: false, + sidebarPassThrough: false, + dividerHit: DividerHit(kind: .vertical, isInHostedContent: true), + hitView: hostedInspectorHit.inspectorView + ) +#endif + return self + } let hitView = super.hitTest(point) +#if DEBUG + debugLogPointerRouting( + stage: "hitTest.result", + point: point, + titlebarPassThrough: false, + sidebarPassThrough: false, + dividerHit: dividerHit, + hitView: hitView === self ? nil : hitView + ) +#endif return hitView === self ? nil : hitView } + override func mouseDown(with event: NSEvent) { + let point = convert(event.locationInWindow, from: nil) + guard let hostedInspectorHit = hostedInspectorDividerHit(at: point) else { + super.mouseDown(with: event) + return + } + + hostedInspectorDividerDrag = HostedInspectorDividerDragState( + slotView: hostedInspectorHit.slotView, + containerView: hostedInspectorHit.containerView, + pageView: hostedInspectorHit.pageView, + inspectorView: hostedInspectorHit.inspectorView, + initialWindowX: event.locationInWindow.x, + initialPageFrame: hostedInspectorHit.pageView.frame, + initialInspectorFrame: hostedInspectorHit.inspectorView.frame + ) +#if DEBUG + dlog( + "browser.portal.manualInspectorDrag stage=start slot=\(browserPortalDebugToken(hostedInspectorHit.slotView)) " + + "page=\(browserPortalDebugToken(hostedInspectorHit.pageView)) " + + "inspector=\(browserPortalDebugToken(hostedInspectorHit.inspectorView)) " + + "pageFrame=\(browserPortalDebugFrame(hostedInspectorHit.pageView.frame)) " + + "inspectorFrame=\(browserPortalDebugFrame(hostedInspectorHit.inspectorView.frame))" + ) +#endif + } + + override func mouseDragged(with event: NSEvent) { + guard let dragState = hostedInspectorDividerDrag else { + super.mouseDragged(with: event) + return + } + guard dragState.slotView.window === window else { + hostedInspectorDividerDrag = nil + super.mouseDragged(with: event) + return + } + + let containerBounds = dragState.containerView.bounds + let minimumInspectorWidth = min( + Self.minimumHostedInspectorWidth, + max(60, dragState.initialInspectorFrame.width) + ) + let minDividerX = max(containerBounds.minX, dragState.initialPageFrame.minX) + let maxDividerX = max(minDividerX, containerBounds.maxX - minimumInspectorWidth) + let proposedDividerX = dragState.initialInspectorFrame.minX + (event.locationInWindow.x - dragState.initialWindowX) + let clampedDividerX = max(minDividerX, min(maxDividerX, proposedDividerX)) + let inspectorWidth = max(0, containerBounds.maxX - clampedDividerX) + + dragState.slotView.preferredHostedInspectorWidth = inspectorWidth + let appliedFrames = applyHostedInspectorDividerWidth( + inspectorWidth, + to: HostedInspectorDividerHit( + slotView: dragState.slotView, + containerView: dragState.containerView, + pageView: dragState.pageView, + inspectorView: dragState.inspectorView + ), + reason: "drag" + ) + updateDividerCursor( + at: convert(event.locationInWindow, from: nil), + dividerHit: nil, + hostedInspectorHit: HostedInspectorDividerHit( + slotView: dragState.slotView, + containerView: dragState.containerView, + pageView: dragState.pageView, + inspectorView: dragState.inspectorView + ) + ) +#if DEBUG + dlog( + "browser.portal.manualInspectorDrag stage=update slot=\(browserPortalDebugToken(dragState.slotView)) " + + "dividerX=\(String(format: "%.1f", clampedDividerX)) " + + "pageFrame=\(browserPortalDebugFrame(appliedFrames.pageFrame)) " + + "inspectorFrame=\(browserPortalDebugFrame(appliedFrames.inspectorFrame))" + ) +#endif + } + + override func mouseUp(with event: NSEvent) { + if let dragState = hostedInspectorDividerDrag { +#if DEBUG + dlog( + "browser.portal.manualInspectorDrag stage=end slot=\(browserPortalDebugToken(dragState.slotView)) " + + "pageFrame=\(browserPortalDebugFrame(dragState.pageView.frame)) " + + "inspectorFrame=\(browserPortalDebugFrame(dragState.inspectorView.frame))" + ) +#endif + scheduleHostedInspectorDividerReapply(in: dragState.slotView, reason: "dragEndAsync") + } + hostedInspectorDividerDrag = nil + updateDividerCursor(at: convert(event.locationInWindow, from: nil)) + super.mouseUp(with: event) + } + private func shouldPassThroughToTitlebar(at point: NSPoint) -> Bool { guard let window else { return false } // Window-level portal hosts sit above SwiftUI content. Never intercept @@ -152,6 +410,31 @@ final class WindowBrowserHostView: NSView { } private func shouldPassThroughToSidebarResizer(at point: NSPoint) -> Bool { + let dividerHit = splitDividerHit(at: point) + let hostedInspectorHit = dividerHit == nil ? hostedInspectorDividerHit(at: point) : nil + return shouldPassThroughToSidebarResizer( + at: point, + dividerHit: dividerHit, + hostedInspectorHit: hostedInspectorHit + ) + } + + private func shouldPassThroughToSidebarResizer( + at point: NSPoint, + dividerHit: DividerHit?, + hostedInspectorHit: HostedInspectorDividerHit? = nil + ) -> Bool { + // If WebKit has a hosted vertical inspector split collapsed to the pane edge, + // prefer that divider over the app/sidebar resize hit zone. + if let dividerHit, + dividerHit.isInHostedContent, + dividerHit.kind == .vertical { + return false + } + if hostedInspectorHit != nil { + return false + } + // Browser portal host sits above SwiftUI content. Allow pointer/mouse events // to reach the SwiftUI sidebar divider resizer zone. let visibleSlots = subviews.compactMap { $0 as? WindowBrowserSlotView } @@ -202,13 +485,24 @@ final class WindowBrowserHostView: NSView { return point.x >= regionMinX && point.x <= regionMaxX } - private func updateDividerCursor(at point: NSPoint) { - if shouldPassThroughToSidebarResizer(at: point) { + private func updateDividerCursor( + at point: NSPoint, + dividerHit: DividerHit? = nil, + hostedInspectorHit: HostedInspectorDividerHit? = nil + ) { + let resolvedDividerHit = dividerHit ?? splitDividerHit(at: point) + let resolvedHostedInspectorHit = resolvedDividerHit == nil ? (hostedInspectorHit ?? hostedInspectorDividerHit(at: point)) : nil + if shouldPassThroughToSidebarResizer( + at: point, + dividerHit: resolvedDividerHit, + hostedInspectorHit: resolvedHostedInspectorHit + ) { clearActiveDividerCursor(restoreArrow: false) return } - guard let nextKind = splitDividerCursorKind(at: point) else { + let nextKind = resolvedDividerHit?.kind ?? (resolvedHostedInspectorHit == nil ? nil : .vertical) + guard let nextKind else { clearActiveDividerCursor(restoreArrow: true) return } @@ -216,6 +510,26 @@ final class WindowBrowserHostView: NSView { nextKind.cursor.set() } + private func nativeHostedInspectorHit( + at point: NSPoint, + hostedInspectorHit: HostedInspectorDividerHit + ) -> NSView? { + guard let nativeHit = super.hitTest(point), nativeHit !== self else { return nil } + if nativeHit === hostedInspectorHit.pageView || + nativeHit.isDescendant(of: hostedInspectorHit.pageView) { + return nil + } + if nativeHit === hostedInspectorHit.inspectorView || + nativeHit.isDescendant(of: hostedInspectorHit.inspectorView) { + return nativeHit + } + if hostedInspectorHit.inspectorView.isDescendant(of: nativeHit), + !(hostedInspectorHit.pageView === nativeHit || hostedInspectorHit.pageView.isDescendant(of: nativeHit)) { + return nativeHit + } + return nil + } + private func clearActiveDividerCursor(restoreArrow: Bool) { guard activeDividerCursorKind != nil else { return } window?.invalidateCursorRects(for: self) @@ -225,15 +539,25 @@ final class WindowBrowserHostView: NSView { } } - private func splitDividerCursorKind(at point: NSPoint) -> DividerCursorKind? { - guard let window else { return nil } + private func splitDividerHit(at point: NSPoint) -> DividerHit? { + guard window != nil else { return nil } let windowPoint = convert(point, to: nil) - guard let rootView = window.contentView else { return nil } - return Self.dividerCursorKind(at: windowPoint, in: rootView) + guard let rootView = dividerSearchRootView() else { return nil } + return Self.dividerHit(at: windowPoint, in: rootView, hostView: self) + } + + private func dividerSearchRootView() -> NSView? { + if let container = superview { + return container + } + return window?.contentView } private func shouldPassThroughToSplitDivider(at point: NSPoint) -> Bool { - splitDividerCursorKind(at: point) != nil + guard let dividerHit = splitDividerHit(at: point) else { return false } + // Portal host should pass split-divider events through to app layout splits, + // but keep WebKit inspector/internal split dividers interactive. + return !dividerHit.isInHostedContent } static func shouldPassThroughToDragTargets( @@ -261,7 +585,188 @@ final class WindowBrowserHostView: NSView { } } - private static func dividerCursorKind(at windowPoint: NSPoint, in view: NSView) -> DividerCursorKind? { + private func hostedInspectorDividerHit(at point: NSPoint) -> HostedInspectorDividerHit? { + let visibleSlots = subviews.compactMap { $0 as? WindowBrowserSlotView } + .filter { !$0.isHidden && $0.window != nil && $0.frame.height > 1 } + + for slot in visibleSlots { + let pointInSlot = slot.convert(point, from: self) + guard slot.bounds.contains(pointInSlot), + let hit = hostedInspectorDividerCandidate(in: slot) else { + continue + } + + if hostedInspectorDividerHitRect(for: hit).contains(pointInSlot) { + return hit + } + } + + return nil + } + + private func hostedInspectorDividerCandidate(in slot: WindowBrowserSlotView) -> HostedInspectorDividerHit? { + let inspectorCandidates = Self.visibleDescendants(in: slot) + .filter { Self.isVisibleHostedInspectorCandidate($0) && Self.isInspectorView($0) } + .sorted { lhs, rhs in + let lhsFrame = slot.convert(lhs.bounds, from: lhs) + let rhsFrame = slot.convert(rhs.bounds, from: rhs) + return lhsFrame.minX < rhsFrame.minX + } + + var bestHit: HostedInspectorDividerHit? + var bestScore = -CGFloat.greatestFiniteMagnitude + + for inspectorCandidate in inspectorCandidates { + guard let candidate = hostedInspectorDividerCandidate(in: slot, startingAt: inspectorCandidate) else { + continue + } + let score = hostedInspectorDividerCandidateScore(candidate) + if score > bestScore { + bestScore = score + bestHit = candidate + } + } + + return bestHit + } + + private func hostedInspectorDividerCandidate( + in slot: WindowBrowserSlotView, + startingAt inspectorLeaf: NSView + ) -> HostedInspectorDividerHit? { + var current: NSView? = inspectorLeaf + var bestHit: HostedInspectorDividerHit? + + while let inspectorView = current, inspectorView !== slot { + guard let containerView = inspectorView.superview else { break } + + let pageCandidates = containerView.subviews.filter { candidate in + guard Self.isVisibleHostedInspectorSiblingCandidate(candidate) else { return false } + guard candidate !== inspectorView else { return false } + guard candidate.frame.maxX <= inspectorView.frame.minX + 1 else { return false } + return Self.verticalOverlap(between: candidate.frame, and: inspectorView.frame) > 8 + } + + if let pageView = pageCandidates.max(by: { + hostedInspectorPageCandidateScore($0, inspectorView: inspectorView) + < hostedInspectorPageCandidateScore($1, inspectorView: inspectorView) + }) { + bestHit = HostedInspectorDividerHit( + slotView: slot, + containerView: containerView, + pageView: pageView, + inspectorView: inspectorView + ) + } + + current = containerView + } + + return bestHit + } + + private func hostedInspectorDividerHitRect(for hit: HostedInspectorDividerHit) -> NSRect { + let slotBounds = hit.slotView.bounds + let pageFrame = hit.slotView.convert(hit.pageView.bounds, from: hit.pageView) + let inspectorFrame = hit.slotView.convert(hit.inspectorView.bounds, from: hit.inspectorView) + let minY = max(slotBounds.minY, min(pageFrame.minY, inspectorFrame.minY)) + let maxY = min(slotBounds.maxY, max(pageFrame.maxY, inspectorFrame.maxY)) + return NSRect( + x: inspectorFrame.minX - Self.hostedInspectorDividerHitExpansion, + y: minY, + width: Self.hostedInspectorDividerHitExpansion * 2, + height: max(0, maxY - minY) + ) + } + + private func hostedInspectorDividerCandidateScore(_ hit: HostedInspectorDividerHit) -> CGFloat { + let pageFrame = hit.slotView.convert(hit.pageView.bounds, from: hit.pageView) + let inspectorFrame = hit.slotView.convert(hit.inspectorView.bounds, from: hit.inspectorView) + let overlap = Self.verticalOverlap(between: pageFrame, and: inspectorFrame) + let coverageWidth = max(pageFrame.maxX, inspectorFrame.maxX) - min(pageFrame.minX, inspectorFrame.minX) + return (overlap * 1_000) + coverageWidth + pageFrame.width + } + + private func hostedInspectorPageCandidateScore(_ pageView: NSView, inspectorView: NSView) -> CGFloat { + let overlap = Self.verticalOverlap(between: pageView.frame, and: inspectorView.frame) + let coverageWidth = max(pageView.frame.maxX, inspectorView.frame.maxX) - min(pageView.frame.minX, inspectorView.frame.minX) + return (overlap * 1_000) + coverageWidth + pageView.frame.width + } + + private func reapplyHostedInspectorDividersIfNeeded(reason: String) { + let visibleSlots = subviews.compactMap { $0 as? WindowBrowserSlotView } + .filter { !$0.isHidden && $0.window != nil && $0.frame.height > 1 } + for slot in visibleSlots { + reapplyHostedInspectorDividerIfNeeded(in: slot, reason: reason) + } + } + + private func scheduleHostedInspectorDividerReapply(in slot: WindowBrowserSlotView, reason: String) { + guard slot.preferredHostedInspectorWidth != nil else { return } + DispatchQueue.main.async { [weak self, weak slot] in + guard let self, let slot, slot.isDescendant(of: self) else { return } + self.reapplyHostedInspectorDividerIfNeeded(in: slot, reason: reason) + } + } + + fileprivate func reapplyHostedInspectorDividerIfNeeded(in slot: WindowBrowserSlotView, reason: String) { + guard let preferredWidth = slot.preferredHostedInspectorWidth else { return } + guard let hit = hostedInspectorDividerCandidate(in: slot) else { return } + _ = applyHostedInspectorDividerWidth(preferredWidth, to: hit, reason: reason) + } + + @discardableResult + private func applyHostedInspectorDividerWidth( + _ preferredWidth: CGFloat, + to hit: HostedInspectorDividerHit, + reason: String + ) -> (pageFrame: NSRect, inspectorFrame: NSRect) { + let containerBounds = hit.containerView.bounds + let maximumInspectorWidth = max(0, containerBounds.maxX - hit.pageView.frame.minX) + let clampedInspectorWidth = max(0, min(maximumInspectorWidth, preferredWidth)) + let dividerX = max(hit.pageView.frame.minX, containerBounds.maxX - clampedInspectorWidth) + + var pageFrame = hit.pageView.frame + pageFrame.size.width = max(0, dividerX - pageFrame.minX) + + var inspectorFrame = hit.inspectorView.frame + inspectorFrame.origin.x = dividerX + inspectorFrame.size.width = max(0, containerBounds.maxX - dividerX) + + let pageChanged = !Self.rectApproximatelyEqual(pageFrame, hit.pageView.frame, epsilon: 0.5) + let inspectorChanged = !Self.rectApproximatelyEqual(inspectorFrame, hit.inspectorView.frame, epsilon: 0.5) + guard pageChanged || inspectorChanged else { + return (pageFrame, inspectorFrame) + } + + hit.slotView.isApplyingHostedInspectorLayout = true + CATransaction.begin() + CATransaction.setDisableActions(true) + hit.pageView.frame = pageFrame + hit.inspectorView.frame = inspectorFrame + CATransaction.commit() + hit.slotView.isApplyingHostedInspectorLayout = false + + hit.pageView.needsLayout = true + hit.inspectorView.needsLayout = true + hit.containerView.needsLayout = true + hit.slotView.needsLayout = true +#if DEBUG + dlog( + "browser.portal.manualInspectorDrag stage=reapply slot=\(browserPortalDebugToken(hit.slotView)) " + + "container=\(browserPortalDebugToken(hit.containerView)) reason=\(reason) " + + "preferredWidth=\(String(format: "%.1f", preferredWidth)) " + + "pageFrame=\(browserPortalDebugFrame(pageFrame)) " + + "inspectorFrame=\(browserPortalDebugFrame(inspectorFrame))" + ) +#endif + return (pageFrame, inspectorFrame) + } + private static func dividerHit( + at windowPoint: NSPoint, + in view: NSView, + hostView: WindowBrowserHostView + ) -> DividerHit? { guard !view.isHidden else { return nil } if let splitView = view as? NSSplitView { @@ -299,21 +804,62 @@ final class WindowBrowserHostView: NSView { } let expanded = dividerRect.insetBy(dx: -expansion, dy: -expansion) if expanded.contains(pointInSplit) { - return splitView.isVertical ? .vertical : .horizontal + return DividerHit( + kind: splitView.isVertical ? .vertical : .horizontal, + isInHostedContent: splitView.isDescendant(of: hostView) + ) } } } } for subview in view.subviews.reversed() { - if let kind = dividerCursorKind(at: windowPoint, in: subview) { - return kind + if let hit = dividerHit(at: windowPoint, in: subview, hostView: hostView) { + return hit } } return nil } + private static func verticalOverlap(between lhs: NSRect, and rhs: NSRect) -> CGFloat { + max(0, min(lhs.maxY, rhs.maxY) - max(lhs.minY, rhs.minY)) + } + + private static func rectApproximatelyEqual(_ lhs: NSRect, _ rhs: NSRect, epsilon: CGFloat = 0.01) -> Bool { + abs(lhs.origin.x - rhs.origin.x) <= epsilon && + abs(lhs.origin.y - rhs.origin.y) <= epsilon && + abs(lhs.size.width - rhs.size.width) <= epsilon && + abs(lhs.size.height - rhs.size.height) <= epsilon + } + + private static func visibleDescendants(in root: NSView) -> [NSView] { + var descendants: [NSView] = [] + var stack = Array(root.subviews.reversed()) + while let view = stack.popLast() { + descendants.append(view) + stack.append(contentsOf: view.subviews.reversed()) + } + return descendants + } + + private static func isInspectorView(_ view: NSView) -> Bool { + String(describing: type(of: view)).contains("WKInspector") + } + + private static func isVisibleHostedInspectorCandidate(_ view: NSView) -> Bool { + !view.isHidden && + view.alphaValue > 0 && + view.frame.width > 1 && + view.frame.height > 1 + } + + private static func isVisibleHostedInspectorSiblingCandidate(_ view: NSView) -> Bool { + !view.isHidden && + view.alphaValue > 0 && + view.frame.height > 1 + } + private static func collectSplitDividerRegions(in view: NSView, into result: inout [DividerRegion]) { guard !view.isHidden else { return } @@ -674,6 +1220,9 @@ final class WindowBrowserSlotView: NSView { private var displayedDropZone: DropZone? private var dropZoneOverlayAnimationGeneration: UInt64 = 0 private var isRefreshingInteractionLayers = false + var preferredHostedInspectorWidth: CGFloat? + var onHostedInspectorLayout: ((WindowBrowserSlotView) -> Void)? + fileprivate var isApplyingHostedInspectorLayout = false override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -703,6 +1252,8 @@ final class WindowBrowserSlotView: NSView { super.layout() paneDropTargetView.frame = bounds applyResolvedDropZoneOverlay() + guard !isApplyingHostedInspectorLayout else { return } + onHostedInspectorLayout?(self) } func setDropZoneOverlay(zone: DropZone?) { @@ -1805,6 +2356,7 @@ final class WindowBrowserPortal: NSObject { reason: "\(source):" + refreshReasons.joined(separator: ",") ) } + hostView.reapplyHostedInspectorDividerIfNeeded(in: containerView, reason: "portal.sync") #if DEBUG dlog( "browser.portal.sync.result web=\(browserPortalDebugToken(webView)) source=\(source) " + diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 89858475..f7b8ed95 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -2587,7 +2587,7 @@ extension BrowserPanel { /// while its container is off-window. Avoid detaching in that transient phase if /// DevTools is intended to remain open, because detach/reattach can blank inspector content. func shouldPreserveWebViewAttachmentDuringTransientHide() -> Bool { - preferredDeveloperToolsVisible + preferredDeveloperToolsVisible && !hasSideDockedDeveloperToolsLayout() } func requestDeveloperToolsRefreshAfterNextAttach(reason: String) { @@ -3045,6 +3045,7 @@ extension BrowserPanel { let containerType = container.map { String(describing: type(of: $0)) } ?? "nil" return "webFrame=\(Self.debugRectDescription(webFrame)) webBounds=\(Self.debugRectDescription(webView.bounds)) webWin=\(webView.window?.windowNumber ?? -1) super=\(Self.debugObjectToken(container)) superType=\(containerType) superBounds=\(Self.debugRectDescription(containerBounds)) inspectorHApprox=\(String(format: "%.1f", inspectorHeightApprox)) inspectorInsets=\(String(format: "%.1f", inspectorInsets)) inspectorOverflow=\(String(format: "%.1f", inspectorOverflow)) inspectorSubviews=\(inspectorSubviews)" } + } #endif @@ -3069,6 +3070,71 @@ private extension BrowserPanel { } return false } + + func hasSideDockedDeveloperToolsLayout() -> Bool { + guard let container = webView.superview else { return false } + return Self.visibleDescendants(in: container) + .filter { Self.isVisibleSideDockInspectorCandidate($0) && Self.isInspectorView($0) } + .contains { inspectorCandidate in + hasSideDockedInspectorSibling(startingAt: inspectorCandidate, root: container) + } + } + + func hasSideDockedInspectorSibling(startingAt inspectorLeaf: NSView, root: NSView) -> Bool { + var current: NSView? = inspectorLeaf + + while let inspectorView = current, inspectorView !== root { + guard let containerView = inspectorView.superview else { break } + let hasSideDockedSibling = containerView.subviews.contains { candidate in + guard Self.isVisibleSideDockSiblingCandidate(candidate) else { return false } + guard candidate !== inspectorView else { return false } + let horizontallyAdjacent = + candidate.frame.maxX <= inspectorView.frame.minX + 1 || + candidate.frame.minX >= inspectorView.frame.maxX - 1 + guard horizontallyAdjacent else { return false } + return Self.verticalOverlap(between: candidate.frame, and: inspectorView.frame) > 8 + } + if hasSideDockedSibling { + return true + } + + current = containerView + } + + return false + } + + static func visibleDescendants(in root: NSView) -> [NSView] { + var descendants: [NSView] = [] + var stack = Array(root.subviews.reversed()) + while let view = stack.popLast() { + descendants.append(view) + stack.append(contentsOf: view.subviews.reversed()) + } + return descendants + } + + static func isInspectorView(_ view: NSView) -> Bool { + String(describing: type(of: view)).contains("WKInspector") + } + + static func isVisibleSideDockInspectorCandidate(_ view: NSView) -> Bool { + !view.isHidden && + view.alphaValue > 0 && + view.frame.width > 1 && + view.frame.height > 1 + } + + static func isVisibleSideDockSiblingCandidate(_ view: NSView) -> Bool { + !view.isHidden && + view.alphaValue > 0 && + view.frame.width > 1 && + view.frame.height > 1 + } + + static func verticalOverlap(between lhs: NSRect, and rhs: NSRect) -> CGFloat { + max(0, min(lhs.maxY, rhs.maxY) - max(lhs.minY, rhs.minY)) + } } private extension WKWebView { diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 07295066..46ca0ff7 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -3055,44 +3055,360 @@ struct WebViewRepresentable: NSViewRepresentable { var searchOverlayHostingView: NSHostingView? } - private final class HostContainerView: NSView { + final class HostContainerView: NSView { var onDidMoveToWindow: (() -> Void)? var onGeometryChanged: (() -> Void)? + private struct HostedInspectorDividerHit { + let containerView: NSView + let pageView: NSView + let inspectorView: NSView + } + + private struct HostedInspectorDividerDragState { + let containerView: NSView + let pageView: NSView + let inspectorView: NSView + let initialWindowX: CGFloat + let initialPageFrame: NSRect + let initialInspectorFrame: NSRect + } + + private enum DividerCursorKind: Equatable { + case vertical + + var cursor: NSCursor { .resizeLeftRight } + } + + private static let hostedInspectorDividerHitExpansion: CGFloat = 6 + private static let minimumHostedInspectorWidth: CGFloat = 120 + private var trackingArea: NSTrackingArea? + private var activeDividerCursorKind: DividerCursorKind? + private var hostedInspectorDividerDrag: HostedInspectorDividerDragState? + private var preferredHostedInspectorWidth: CGFloat? + private var isApplyingHostedInspectorLayout = false +#if DEBUG + private var lastLoggedHostedInspectorFrames: (page: NSRect, inspector: NSRect)? + private var hasLoggedMissingHostedInspectorCandidate = false +#endif + +#if DEBUG + private static func shouldLogPointerEvent(_ event: NSEvent?) -> Bool { + switch event?.type { + case .leftMouseDown, .leftMouseDragged, .leftMouseUp: + return true + default: + return false + } + } + + private func debugLogHitTest(stage: String, point: NSPoint, passThrough: Bool, hitView: NSView?) { + let event = NSApp.currentEvent + guard Self.shouldLogPointerEvent(event) else { return } + + let hitDesc: String = { + guard let hitView else { return "nil" } + let token = Unmanaged.passUnretained(hitView).toOpaque() + return "\(type(of: hitView))@\(token)" + }() + let hostRectInContent: NSRect = { + guard let window, let contentView = window.contentView else { return .zero } + return contentView.convert(bounds, from: self) + }() + dlog( + "browser.panel.host stage=\(stage) event=\(String(describing: event?.type)) " + + "point=\(String(format: "%.1f,%.1f", point.x, point.y)) pass=\(passThrough ? 1 : 0) " + + "hostFrameInContent=\(String(format: "%.1f,%.1f %.1fx%.1f", hostRectInContent.origin.x, hostRectInContent.origin.y, hostRectInContent.width, hostRectInContent.height)) " + + "hit=\(hitDesc)" + ) + } + + private static func debugObjectID(_ object: AnyObject?) -> String { + guard let object else { return "nil" } + return String(describing: Unmanaged.passUnretained(object).toOpaque()) + } + + private static func debugRect(_ rect: NSRect) -> String { + String(format: "%.1f,%.1f %.1fx%.1f", rect.origin.x, rect.origin.y, rect.width, rect.height) + } + + private static func rectApproximatelyEqual(_ lhs: NSRect, _ rhs: NSRect, epsilon: CGFloat = 0.5) -> Bool { + abs(lhs.origin.x - rhs.origin.x) <= epsilon && + abs(lhs.origin.y - rhs.origin.y) <= epsilon && + abs(lhs.width - rhs.width) <= epsilon && + abs(lhs.height - rhs.height) <= epsilon + } + + private func debugLogHostedInspectorFrames( + stage: String, + point: NSPoint? = nil, + hit: HostedInspectorDividerHit + ) { + let pointDesc = point.map { String(format: "%.1f,%.1f", $0.x, $0.y) } ?? "nil" + let preferredWidthDesc = preferredHostedInspectorWidth.map { String(format: "%.1f", $0) } ?? "nil" + dlog( + "browser.panel.hostedInspector stage=\(stage) point=\(pointDesc) " + + "host=\(Self.debugObjectID(self)) container=\(Self.debugObjectID(hit.containerView)) " + + "page=\(Self.debugObjectID(hit.pageView)) inspector=\(Self.debugObjectID(hit.inspectorView)) " + + "preferredWidth=\(preferredWidthDesc) " + + "hostFrame=\(Self.debugRect(frame)) hostBounds=\(Self.debugRect(bounds)) " + + "containerBounds=\(Self.debugRect(hit.containerView.bounds)) " + + "pageFrame=\(Self.debugRect(hit.pageView.frame)) " + + "inspectorFrame=\(Self.debugRect(hit.inspectorView.frame))" + ) + } + + private func debugLogHostedInspectorLayoutIfNeeded(reason: String) { + guard let hit = hostedInspectorDividerCandidate() else { + if !hasLoggedMissingHostedInspectorCandidate, + lastLoggedHostedInspectorFrames != nil || preferredHostedInspectorWidth != nil { + let preferredWidthDesc = preferredHostedInspectorWidth.map { + String(format: "%.1f", $0) + } ?? "nil" + lastLoggedHostedInspectorFrames = nil + hasLoggedMissingHostedInspectorCandidate = true + dlog( + "browser.panel.hostedInspector stage=\(reason).candidateMissing " + + "host=\(Self.debugObjectID(self)) preferredWidth=\(preferredWidthDesc)" + ) + } + return + } + hasLoggedMissingHostedInspectorCandidate = false + + let nextFrames = (page: hit.pageView.frame, inspector: hit.inspectorView.frame) + if let lastLoggedHostedInspectorFrames, + Self.rectApproximatelyEqual(lastLoggedHostedInspectorFrames.page, nextFrames.page), + Self.rectApproximatelyEqual(lastLoggedHostedInspectorFrames.inspector, nextFrames.inspector) { + return + } + + lastLoggedHostedInspectorFrames = nextFrames + debugLogHostedInspectorFrames(stage: "\(reason).layout", hit: hit) + } +#endif override func viewDidMoveToWindow() { super.viewDidMoveToWindow() + if window == nil { + clearActiveDividerCursor(restoreArrow: false) + } else { + reapplyHostedInspectorDividerIfNeeded(reason: "viewDidMoveToWindow") + } + window?.invalidateCursorRects(for: self) onDidMoveToWindow?() onGeometryChanged?() +#if DEBUG + debugLogHostedInspectorLayoutIfNeeded(reason: "viewDidMoveToWindow") +#endif } override func viewDidMoveToSuperview() { super.viewDidMoveToSuperview() + reapplyHostedInspectorDividerIfNeeded(reason: "viewDidMoveToSuperview") onGeometryChanged?() +#if DEBUG + debugLogHostedInspectorLayoutIfNeeded(reason: "viewDidMoveToSuperview") +#endif } override func layout() { super.layout() + reapplyHostedInspectorDividerIfNeeded(reason: "layout") onGeometryChanged?() +#if DEBUG + debugLogHostedInspectorLayoutIfNeeded(reason: "layout") +#endif } override func setFrameOrigin(_ newOrigin: NSPoint) { super.setFrameOrigin(newOrigin) + window?.invalidateCursorRects(for: self) + reapplyHostedInspectorDividerIfNeeded(reason: "setFrameOrigin") onGeometryChanged?() +#if DEBUG + debugLogHostedInspectorLayoutIfNeeded(reason: "setFrameOrigin") +#endif } override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) + window?.invalidateCursorRects(for: self) + reapplyHostedInspectorDividerIfNeeded(reason: "setFrameSize") onGeometryChanged?() +#if DEBUG + debugLogHostedInspectorLayoutIfNeeded(reason: "setFrameSize") +#endif + } + + override func resetCursorRects() { + super.resetCursorRects() + guard let hostedInspectorHit = hostedInspectorDividerCandidate() else { return } + let clipped = hostedInspectorDividerHitRect(for: hostedInspectorHit).intersection(bounds) + guard !clipped.isNull, clipped.width > 0, clipped.height > 0 else { return } + addCursorRect(clipped, cursor: NSCursor.resizeLeftRight) + } + + override func updateTrackingAreas() { + if let trackingArea { + removeTrackingArea(trackingArea) + } + let options: NSTrackingArea.Options = [ + .inVisibleRect, + .activeAlways, + .cursorUpdate, + .mouseMoved, + .mouseEnteredAndExited, + .enabledDuringMouseDrag, + ] + let next = NSTrackingArea(rect: .zero, options: options, owner: self, userInfo: nil) + addTrackingArea(next) + trackingArea = next + super.updateTrackingAreas() + } + + override func cursorUpdate(with event: NSEvent) { + updateDividerCursor(at: convert(event.locationInWindow, from: nil)) + } + + override func mouseMoved(with event: NSEvent) { + updateDividerCursor(at: convert(event.locationInWindow, from: nil)) + } + + override func mouseExited(with event: NSEvent) { + clearActiveDividerCursor(restoreArrow: true) } override func hitTest(_ point: NSPoint) -> NSView? { - if shouldPassThroughToSidebarResizer(at: point) { + let hostedInspectorHit = hostedInspectorDividerHit(at: point) + updateDividerCursor(at: point, hostedInspectorHit: hostedInspectorHit) + let passThrough = shouldPassThroughToSidebarResizer(at: point, hostedInspectorHit: hostedInspectorHit) + if passThrough { +#if DEBUG + debugLogHitTest(stage: "hitTest.pass", point: point, passThrough: true, hitView: nil) +#endif return nil } - return super.hitTest(point) + if let hostedInspectorHit { + if let nativeHit = nativeHostedInspectorHit(at: point, hostedInspectorHit: hostedInspectorHit) { +#if DEBUG + debugLogHitTest(stage: "hitTest.hostedInspectorNative", point: point, passThrough: false, hitView: nativeHit) +#endif + return nativeHit + } +#if DEBUG + debugLogHitTest(stage: "hitTest.hostedInspectorManual", point: point, passThrough: false, hitView: hostedInspectorHit.inspectorView) +#endif + return self + } + let hit = super.hitTest(point) +#if DEBUG + debugLogHitTest(stage: "hitTest.result", point: point, passThrough: false, hitView: hit) +#endif + return hit } - private func shouldPassThroughToSidebarResizer(at point: NSPoint) -> Bool { + override func mouseDown(with event: NSEvent) { + let point = convert(event.locationInWindow, from: nil) + guard let hostedInspectorHit = hostedInspectorDividerHit(at: point) else { + super.mouseDown(with: event) + return + } + + hostedInspectorDividerDrag = HostedInspectorDividerDragState( + containerView: hostedInspectorHit.containerView, + pageView: hostedInspectorHit.pageView, + inspectorView: hostedInspectorHit.inspectorView, + initialWindowX: event.locationInWindow.x, + initialPageFrame: hostedInspectorHit.pageView.frame, + initialInspectorFrame: hostedInspectorHit.inspectorView.frame + ) +#if DEBUG + debugLogHostedInspectorFrames(stage: "drag.start", point: point, hit: hostedInspectorHit) +#endif + } + + override func mouseDragged(with event: NSEvent) { + guard let dragState = hostedInspectorDividerDrag else { + super.mouseDragged(with: event) + return + } + + let containerBounds = dragState.containerView.bounds + let minimumInspectorWidth = min( + Self.minimumHostedInspectorWidth, + max(60, dragState.initialInspectorFrame.width) + ) + let minDividerX = max(containerBounds.minX, dragState.initialPageFrame.minX) + let maxDividerX = max(minDividerX, containerBounds.maxX - minimumInspectorWidth) + let proposedDividerX = dragState.initialInspectorFrame.minX + (event.locationInWindow.x - dragState.initialWindowX) + let clampedDividerX = max(minDividerX, min(maxDividerX, proposedDividerX)) + let inspectorWidth = max(0, containerBounds.maxX - clampedDividerX) + preferredHostedInspectorWidth = inspectorWidth + _ = applyHostedInspectorDividerWidth( + inspectorWidth, + to: HostedInspectorDividerHit( + containerView: dragState.containerView, + pageView: dragState.pageView, + inspectorView: dragState.inspectorView + ), + reason: "drag" + ) +#if DEBUG + debugLogHostedInspectorFrames( + stage: "drag.update", + point: convert(event.locationInWindow, from: nil), + hit: HostedInspectorDividerHit( + containerView: dragState.containerView, + pageView: dragState.pageView, + inspectorView: dragState.inspectorView + ) + ) +#endif + updateDividerCursor( + at: convert(event.locationInWindow, from: nil), + hostedInspectorHit: HostedInspectorDividerHit( + containerView: dragState.containerView, + pageView: dragState.pageView, + inspectorView: dragState.inspectorView + ) + ) + } + + override func mouseUp(with event: NSEvent) { + let finalDragState = hostedInspectorDividerDrag + hostedInspectorDividerDrag = nil + updateDividerCursor(at: convert(event.locationInWindow, from: nil)) + scheduleHostedInspectorDividerReapply(reason: "dragEndAsync") +#if DEBUG + if let finalDragState { + let finalHit = HostedInspectorDividerHit( + containerView: finalDragState.containerView, + pageView: finalDragState.pageView, + inspectorView: finalDragState.inspectorView + ) + debugLogHostedInspectorFrames( + stage: "drag.end", + point: convert(event.locationInWindow, from: nil), + hit: finalHit + ) + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.reapplyHostedInspectorDividerIfNeeded(reason: "drag.end.async") + self.debugLogHostedInspectorFrames(stage: "drag.end.async", hit: finalHit) + self.debugLogHostedInspectorLayoutIfNeeded(reason: "dragEndAsync") + } + } +#endif + super.mouseUp(with: event) + } + + private func shouldPassThroughToSidebarResizer( + at point: NSPoint, + hostedInspectorHit: HostedInspectorDividerHit? = nil + ) -> Bool { + if hostedInspectorHit != nil { + return false + } // Pass through a narrow leading-edge band so the shared sidebar divider // handle can receive hover/click even when WKWebView is attached here. // Keeping this deterministic avoids flicker from dynamic left-edge scans. @@ -3105,6 +3421,250 @@ struct WebViewRepresentable: NSViewRepresentable { let hostRectInContent = contentView.convert(bounds, from: self) return hostRectInContent.minX > 1 } + + private func updateDividerCursor( + at point: NSPoint, + hostedInspectorHit: HostedInspectorDividerHit? = nil + ) { + let resolvedHostedInspectorHit = hostedInspectorHit ?? hostedInspectorDividerHit(at: point) + if shouldPassThroughToSidebarResizer(at: point, hostedInspectorHit: resolvedHostedInspectorHit) { + clearActiveDividerCursor(restoreArrow: false) + return + } + guard resolvedHostedInspectorHit != nil else { + clearActiveDividerCursor(restoreArrow: true) + return + } + activeDividerCursorKind = .vertical + NSCursor.resizeLeftRight.set() + } + + private func clearActiveDividerCursor(restoreArrow: Bool) { + guard activeDividerCursorKind != nil else { return } + window?.invalidateCursorRects(for: self) + activeDividerCursorKind = nil + if restoreArrow { + NSCursor.arrow.set() + } + } + + private func nativeHostedInspectorHit( + at point: NSPoint, + hostedInspectorHit: HostedInspectorDividerHit + ) -> NSView? { + guard let nativeHit = super.hitTest(point), nativeHit !== self else { return nil } + if nativeHit === hostedInspectorHit.pageView || + nativeHit.isDescendant(of: hostedInspectorHit.pageView) { + return nil + } + if nativeHit === hostedInspectorHit.inspectorView || + nativeHit.isDescendant(of: hostedInspectorHit.inspectorView) { + return nativeHit + } + if hostedInspectorHit.inspectorView.isDescendant(of: nativeHit), + !(hostedInspectorHit.pageView === nativeHit || hostedInspectorHit.pageView.isDescendant(of: nativeHit)) { + return nativeHit + } + return nil + } + + private func hostedInspectorDividerHit(at point: NSPoint) -> HostedInspectorDividerHit? { + guard let hit = hostedInspectorDividerCandidate(), + hostedInspectorDividerHitRect(for: hit).contains(point) else { + return nil + } + return hit + } + + private func hostedInspectorDividerCandidate() -> HostedInspectorDividerHit? { + let inspectorCandidates = Self.visibleDescendants(in: self) + .filter { Self.isVisibleHostedInspectorCandidate($0) && Self.isInspectorView($0) } + .sorted { lhs, rhs in + let lhsFrame = convert(lhs.bounds, from: lhs) + let rhsFrame = convert(rhs.bounds, from: rhs) + return lhsFrame.minX < rhsFrame.minX + } + + var bestHit: HostedInspectorDividerHit? + var bestScore = -CGFloat.greatestFiniteMagnitude + + for inspectorCandidate in inspectorCandidates { + guard let candidate = hostedInspectorDividerCandidate(startingAt: inspectorCandidate) else { + continue + } + let score = hostedInspectorDividerCandidateScore(candidate) + if score > bestScore { + bestScore = score + bestHit = candidate + } + } + + return bestHit + } + + private func hostedInspectorDividerHitRect(for hit: HostedInspectorDividerHit) -> NSRect { + let pageFrame = convert(hit.pageView.bounds, from: hit.pageView) + let inspectorFrame = convert(hit.inspectorView.bounds, from: hit.inspectorView) + let minY = max(bounds.minY, min(pageFrame.minY, inspectorFrame.minY)) + let maxY = min(bounds.maxY, max(pageFrame.maxY, inspectorFrame.maxY)) + return NSRect( + x: inspectorFrame.minX - Self.hostedInspectorDividerHitExpansion, + y: minY, + width: Self.hostedInspectorDividerHitExpansion * 2, + height: max(0, maxY - minY) + ) + } + + private func hostedInspectorDividerCandidate(startingAt inspectorLeaf: NSView) -> HostedInspectorDividerHit? { + var current: NSView? = inspectorLeaf + var bestHit: HostedInspectorDividerHit? + + while let inspectorView = current, inspectorView !== self { + guard let containerView = inspectorView.superview else { break } + + let pageCandidates = containerView.subviews.filter { candidate in + guard Self.isVisibleHostedInspectorSiblingCandidate(candidate) else { return false } + guard candidate !== inspectorView else { return false } + guard candidate.frame.maxX <= inspectorView.frame.minX + 1 else { return false } + return Self.verticalOverlap(between: candidate.frame, and: inspectorView.frame) > 8 + } + + if let pageView = pageCandidates.max(by: { + hostedInspectorPageCandidateScore($0, inspectorView: inspectorView) + < hostedInspectorPageCandidateScore($1, inspectorView: inspectorView) + }) { + bestHit = HostedInspectorDividerHit( + containerView: containerView, + pageView: pageView, + inspectorView: inspectorView + ) + } + + current = containerView + } + + return bestHit + } + + private func hostedInspectorDividerCandidateScore(_ hit: HostedInspectorDividerHit) -> CGFloat { + let pageFrame = convert(hit.pageView.bounds, from: hit.pageView) + let inspectorFrame = convert(hit.inspectorView.bounds, from: hit.inspectorView) + let overlap = Self.verticalOverlap(between: pageFrame, and: inspectorFrame) + let coverageWidth = max(pageFrame.maxX, inspectorFrame.maxX) - min(pageFrame.minX, inspectorFrame.minX) + return (overlap * 1_000) + coverageWidth + pageFrame.width + } + + private func hostedInspectorPageCandidateScore(_ pageView: NSView, inspectorView: NSView) -> CGFloat { + let overlap = Self.verticalOverlap(between: pageView.frame, and: inspectorView.frame) + let coverageWidth = max(pageView.frame.maxX, inspectorView.frame.maxX) - min(pageView.frame.minX, inspectorView.frame.minX) + return (overlap * 1_000) + coverageWidth + pageView.frame.width + } + + private func scheduleHostedInspectorDividerReapply(reason: String) { + guard preferredHostedInspectorWidth != nil else { return } + DispatchQueue.main.async { [weak self] in + self?.reapplyHostedInspectorDividerIfNeeded(reason: reason) + } + } + + private func reapplyHostedInspectorDividerIfNeeded(reason: String) { + guard !isApplyingHostedInspectorLayout else { return } + guard let preferredWidth = preferredHostedInspectorWidth else { return } + guard let hit = hostedInspectorDividerCandidate() else { +#if DEBUG + if !hasLoggedMissingHostedInspectorCandidate { + hasLoggedMissingHostedInspectorCandidate = true + dlog( + "browser.panel.hostedInspector stage=\(reason).reapplyMissingCandidate " + + "host=\(Self.debugObjectID(self)) preferredWidth=\(String(format: "%.1f", preferredWidth))" + ) + } +#endif + return + } +#if DEBUG + hasLoggedMissingHostedInspectorCandidate = false +#endif + _ = applyHostedInspectorDividerWidth(preferredWidth, to: hit, reason: reason) + } + + @discardableResult + private func applyHostedInspectorDividerWidth( + _ preferredWidth: CGFloat, + to hit: HostedInspectorDividerHit, + reason: String + ) -> (pageFrame: NSRect, inspectorFrame: NSRect) { + let containerBounds = hit.containerView.bounds + let maximumInspectorWidth = max(0, containerBounds.maxX - hit.pageView.frame.minX) + let clampedInspectorWidth = max(0, min(maximumInspectorWidth, preferredWidth)) + let dividerX = max(hit.pageView.frame.minX, containerBounds.maxX - clampedInspectorWidth) + + var pageFrame = hit.pageView.frame + pageFrame.size.width = max(0, dividerX - pageFrame.minX) + + var inspectorFrame = hit.inspectorView.frame + inspectorFrame.origin.x = dividerX + inspectorFrame.size.width = max(0, containerBounds.maxX - dividerX) + + let pageChanged = !Self.rectApproximatelyEqual(pageFrame, hit.pageView.frame, epsilon: 0.5) + let inspectorChanged = !Self.rectApproximatelyEqual(inspectorFrame, hit.inspectorView.frame, epsilon: 0.5) + guard pageChanged || inspectorChanged else { + return (pageFrame, inspectorFrame) + } + + isApplyingHostedInspectorLayout = true + CATransaction.begin() + CATransaction.setDisableActions(true) + hit.pageView.frame = pageFrame + hit.inspectorView.frame = inspectorFrame + CATransaction.commit() + isApplyingHostedInspectorLayout = false + + hit.pageView.needsLayout = true + hit.inspectorView.needsLayout = true + hit.containerView.needsLayout = true + needsLayout = true +#if DEBUG + dlog( + "browser.panel.hostedInspector stage=\(reason).reapply " + + "host=\(Self.debugObjectID(self)) preferredWidth=\(String(format: "%.1f", preferredWidth)) " + + "container=\(Self.debugObjectID(hit.containerView)) " + + "pageFrame=\(Self.debugRect(pageFrame)) inspectorFrame=\(Self.debugRect(inspectorFrame))" + ) +#endif + return (pageFrame, inspectorFrame) + } + + private static func visibleDescendants(in root: NSView) -> [NSView] { + var descendants: [NSView] = [] + var stack = Array(root.subviews.reversed()) + while let view = stack.popLast() { + descendants.append(view) + stack.append(contentsOf: view.subviews.reversed()) + } + return descendants + } + + private static func isInspectorView(_ view: NSView) -> Bool { + String(describing: type(of: view)).contains("WKInspector") + } + + private static func isVisibleHostedInspectorCandidate(_ view: NSView) -> Bool { + !view.isHidden && + view.alphaValue > 0 && + view.frame.width > 1 && + view.frame.height > 1 + } + + private static func isVisibleHostedInspectorSiblingCandidate(_ view: NSView) -> Bool { + !view.isHidden && + view.alphaValue > 0 && + view.frame.height > 1 + } + + private static func verticalOverlap(between lhs: NSRect, and rhs: NSRect) -> CGFloat { + max(0, min(lhs.maxY, rhs.maxY) - max(lhs.minY, rhs.minY)) + } } #if DEBUG diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index fd4b699b..e06895aa 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -412,7 +412,7 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase { } @MainActor - func testWindowFirstResponderGuardAllowsPointerInitiatedClickFocusForPortalHostedWebView() { + func testWindowFirstResponderGuardAllowsPointerInitiatedClickFocusFromPortalHostedInspectorSibling() { _ = NSApplication.shared AppDelegate.installWindowResponderSwizzlesForTesting() @@ -422,40 +422,51 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase { backing: .buffered, defer: false ) - let container = NSView(frame: window.contentRect(forFrameRect: window.frame)) - window.contentView = container - - let anchor = NSView(frame: NSRect(x: 80, y: 60, width: 240, height: 150)) - container.addSubview(anchor) - - let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) - let descendant = FirstResponderView(frame: NSRect(x: 0, y: 0, width: 10, height: 10)) - webView.addSubview(descendant) + let contentView = NSView(frame: window.contentRect(forFrameRect: window.frame)) + window.contentView = contentView window.makeKeyAndOrderFront(nil) - container.layoutSubtreeIfNeeded() - RunLoop.current.run(until: Date().addingTimeInterval(0.05)) - BrowserWindowPortalRegistry.bind(webView: webView, to: anchor, visibleInUI: true, zPriority: 1) - BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) - defer { - BrowserWindowPortalRegistry.detach(webView: webView) AppDelegate.clearWindowFirstResponderGuardTesting() window.orderOut(nil) } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: host.bounds) + slot.autoresizingMask = [.width, .height] + host.addSubview(slot) + + let webView = CmuxWebView(frame: slot.bounds, configuration: WKWebViewConfiguration()) + webView.autoresizingMask = [.width, .height] + slot.addSubview(webView) + + let inspector = FirstResponderView(frame: NSRect(x: 440, y: 0, width: 200, height: slot.bounds.height)) + inspector.autoresizingMask = [.minXMargin, .height] + slot.addSubview(inspector) + webView.allowsFirstResponderAcquisition = false _ = window.makeFirstResponder(nil) - XCTAssertFalse(window.makeFirstResponder(descendant), "Expected blocked focus without pointer click context") + XCTAssertFalse( + window.makeFirstResponder(inspector), + "Expected portal-hosted inspector focus to stay blocked without pointer click context" + ) - let timestamp = ProcessInfo.processInfo.systemUptime - let pointerPointInContent = NSPoint(x: anchor.frame.midX, y: anchor.frame.midY) - let pointerPointInWindow = container.convert(pointerPointInContent, to: nil) + let pointInInspector = NSPoint(x: inspector.bounds.midX, y: inspector.bounds.midY) + let pointInWindow = inspector.convert(pointInInspector, to: nil) let pointerDownEvent = NSEvent.mouseEvent( with: .leftMouseDown, - location: pointerPointInWindow, + location: pointInWindow, modifierFlags: [], - timestamp: timestamp, + timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, eventNumber: 1, @@ -467,8 +478,83 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase { AppDelegate.setWindowFirstResponderGuardTesting(currentEvent: pointerDownEvent, hitView: nil) _ = window.makeFirstResponder(nil) XCTAssertTrue( - window.makeFirstResponder(descendant), - "Expected portal-hosted pointer click context to bypass blocked policy" + window.makeFirstResponder(inspector), + "Expected portal-hosted inspector click to bypass blocked policy using the overlay hit target" + ) + } + + @MainActor + func testWindowFirstResponderGuardAllowsPointerInitiatedClickFocusFromBoundPortalInspectorSiblingWhenHitTestMisses() { + _ = NSApplication.shared + AppDelegate.installWindowResponderSwizzlesForTesting() + + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 640, height: 420), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + let contentView = NSView(frame: window.contentRect(forFrameRect: window.frame)) + window.contentView = contentView + + let anchor = NSView(frame: NSRect(x: 80, y: 60, width: 480, height: 260)) + contentView.addSubview(anchor) + + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + + window.makeKeyAndOrderFront(nil) + contentView.layoutSubtreeIfNeeded() + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + BrowserWindowPortalRegistry.bind(webView: webView, to: anchor, visibleInUI: true, zPriority: 1) + BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) + + defer { + BrowserWindowPortalRegistry.detach(webView: webView) + AppDelegate.clearWindowFirstResponderGuardTesting() + window.orderOut(nil) + } + + guard let slot = webView.superview as? WindowBrowserSlotView else { + XCTFail("Expected bound portal slot") + return + } + + let inspector = FirstResponderView(frame: NSRect(x: 320, y: 0, width: 160, height: slot.bounds.height)) + inspector.autoresizingMask = [.minXMargin, .height] + slot.addSubview(inspector) + + webView.allowsFirstResponderAcquisition = false + _ = window.makeFirstResponder(nil) + XCTAssertFalse( + window.makeFirstResponder(inspector), + "Expected bound portal inspector focus to stay blocked without pointer click context" + ) + + let pointInInspector = NSPoint(x: inspector.bounds.midX, y: inspector.bounds.midY) + let pointInWindow = inspector.convert(pointInInspector, to: nil) + XCTAssertTrue( + BrowserWindowPortalRegistry.webViewAtWindowPoint(pointInWindow, in: window) === webView, + "Expected portal registry to resolve the owning web view from a click inside inspector chrome" + ) + + let pointerDownEvent = NSEvent.mouseEvent( + with: .leftMouseDown, + location: pointInWindow, + modifierFlags: [], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + eventNumber: 1, + clickCount: 1, + pressure: 1.0 + ) + XCTAssertNotNil(pointerDownEvent) + + AppDelegate.setWindowFirstResponderGuardTesting(currentEvent: pointerDownEvent, hitView: nil) + _ = window.makeFirstResponder(nil) + XCTAssertTrue( + window.makeFirstResponder(inspector), + "Expected bound portal inspector click to bypass blocked policy through portal registry fallback" ) } @@ -2300,6 +2386,8 @@ final class BrowserSessionHistoryRestoreTests: XCTestCase { @MainActor final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { + private final class WKInspectorProbeView: NSView {} + private final class FakeInspector: NSObject { private(set) var showCount = 0 private(set) var closeCount = 0 @@ -2498,6 +2586,42 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { XCTAssertNotNil(panel.webView.superview) window.orderOut(nil) } + + func testTransientHideAttachmentPreserveDisablesForSideDockedInspectorLayout() { + let (panel, _) = makePanelWithInspector() + XCTAssertTrue(panel.showDeveloperTools()) + + let host = NSView(frame: NSRect(x: 0, y: 0, width: 320, height: 240)) + panel.webView.frame = NSRect(x: 0, y: 0, width: 120, height: host.bounds.height) + host.addSubview(panel.webView) + + let inspectorContainer = NSView( + frame: NSRect(x: 120, y: 0, width: host.bounds.width - 120, height: host.bounds.height) + ) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + host.addSubview(inspectorContainer) + + XCTAssertFalse(panel.shouldPreserveWebViewAttachmentDuringTransientHide()) + } + + func testTransientHideAttachmentPreserveStaysEnabledForBottomDockedInspectorLayout() { + let (panel, _) = makePanelWithInspector() + XCTAssertTrue(panel.showDeveloperTools()) + + let host = NSView(frame: NSRect(x: 0, y: 0, width: 320, height: 240)) + panel.webView.frame = NSRect(x: 0, y: 80, width: host.bounds.width, height: host.bounds.height - 80) + host.addSubview(panel.webView) + + let inspectorContainer = NSView(frame: NSRect(x: 0, y: 0, width: host.bounds.width, height: 80)) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + host.addSubview(inspectorContainer) + + XCTAssertTrue(panel.shouldPreserveWebViewAttachmentDuringTransientHide()) + } } final class WorkspaceShortcutMapperTests: XCTestCase { @@ -7936,8 +8060,56 @@ final class WindowBrowserHostViewTests: XCTestCase { } } + private final class PrimaryPageProbeView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + bounds.contains(point) ? self : nil + } + } + + private final class WKInspectorProbeView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + bounds.contains(point) ? self : nil + } + } + + private final class EdgeTransparentWKInspectorProbeView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + let localPoint = convert(point, from: superview) + guard bounds.contains(localPoint) else { return nil } + return localPoint.x <= 12 ? nil : self + } + } + private final class BonsplitMockSplitDelegate: NSObject, NSSplitViewDelegate {} + private func makeMouseEvent(type: NSEvent.EventType, location: NSPoint, window: NSWindow) -> NSEvent { + guard let event = NSEvent.mouseEvent( + with: type, + location: location, + modifierFlags: [], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + eventNumber: 0, + clickCount: 1, + pressure: 1.0 + ) else { + fatalError("Failed to create \(type) mouse event") + } + return event + } + + private func isInspectorOwnedHit(_ hit: NSView?, inspectorView: NSView, pageView: NSView) -> Bool { + guard let hit else { return false } + if hit === pageView || hit.isDescendant(of: pageView) { + return false + } + if hit === inspectorView || hit.isDescendant(of: inspectorView) { + return true + } + return inspectorView.isDescendant(of: hit) && !(pageView === hit || pageView.isDescendant(of: hit)) + } + func testHostViewPassesThroughDividerWhenAdjacentPaneIsCollapsed() { let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 300, height: 180), @@ -7966,12 +8138,18 @@ final class WindowBrowserHostViewTests: XCTestCase { splitView.adjustSubviews() contentView.layoutSubtreeIfNeeded() - let host = WindowBrowserHostView(frame: contentView.bounds) + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) host.autoresizingMask = [.width, .height] let child = CapturingView(frame: host.bounds) child.autoresizingMask = [.width, .height] host.addSubview(child) - contentView.addSubview(host) + container.addSubview(host, positioned: .above, relativeTo: contentView) let dividerPointInSplit = NSPoint( x: splitView.arrangedSubviews[0].frame.maxX + (splitView.dividerThickness * 0.5), @@ -8023,6 +8201,705 @@ final class WindowBrowserHostViewTests: XCTestCase { ) ) } + + func testHostViewKeepsHostedInspectorDividerInteractive() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + // Underlying app layout split that should still be pass-through. + let appSplit = NSSplitView(frame: contentView.bounds) + appSplit.autoresizingMask = [.width, .height] + appSplit.isVertical = true + appSplit.dividerStyle = .thin + let appSplitDelegate = BonsplitMockSplitDelegate() + appSplit.delegate = appSplitDelegate + let leading = NSView(frame: NSRect(x: 0, y: 0, width: 210, height: contentView.bounds.height)) + let trailing = NSView(frame: NSRect(x: 211, y: 0, width: 209, height: contentView.bounds.height)) + appSplit.addSubview(leading) + appSplit.addSubview(trailing) + contentView.addSubview(appSplit) + appSplit.adjustSubviews() + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + // WebKit inspector uses an internal split (page + console). Divider drags + // here must stay in hosted content, not pass through to appSplit behind it. + let inspectorSplit = NSSplitView(frame: host.bounds) + inspectorSplit.autoresizingMask = [.width, .height] + inspectorSplit.isVertical = false + inspectorSplit.dividerStyle = .thin + let inspectorDelegate = BonsplitMockSplitDelegate() + inspectorSplit.delegate = inspectorDelegate + let pageView = CapturingView(frame: NSRect(x: 0, y: 0, width: host.bounds.width, height: 160)) + let consoleView = CapturingView(frame: NSRect(x: 0, y: 161, width: host.bounds.width, height: 99)) + inspectorSplit.addSubview(pageView) + inspectorSplit.addSubview(consoleView) + host.addSubview(inspectorSplit) + inspectorSplit.setPosition(160, ofDividerAt: 0) + inspectorSplit.adjustSubviews() + contentView.layoutSubtreeIfNeeded() + + let appDividerPointInSplit = NSPoint( + x: appSplit.arrangedSubviews[0].frame.maxX + (appSplit.dividerThickness * 0.5), + y: appSplit.bounds.midY + ) + let appDividerPointInWindow = appSplit.convert(appDividerPointInSplit, to: nil) + let appDividerPointInHost = host.convert(appDividerPointInWindow, from: nil) + XCTAssertNil( + host.hitTest(appDividerPointInHost), + "Underlying app split divider should still pass through with a hosted inspector split present" + ) + + let dividerPointInInspector = NSPoint( + x: inspectorSplit.bounds.midX, + y: inspectorSplit.arrangedSubviews[0].frame.maxY + (inspectorSplit.dividerThickness * 0.5) + ) + let dividerPointInWindow = inspectorSplit.convert(dividerPointInInspector, to: nil) + let dividerPointInHost = host.convert(dividerPointInWindow, from: nil) + let hit = host.hitTest(dividerPointInHost) + + XCTAssertNotNil( + hit, + "Inspector divider should receive hit-testing in hosted content, not pass through" + ) + XCTAssertFalse(hit === host) + if let hit { + XCTAssertTrue( + hit === inspectorSplit || hit.isDescendant(of: inspectorSplit), + "Expected hit to remain inside inspector split subtree" + ) + } + } + + func testHostViewKeepsHostedVerticalInspectorDividerInteractiveAtSlotLeadingEdge() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: NSRect(x: 180, y: 0, width: 240, height: host.bounds.height)) + slot.autoresizingMask = [.minXMargin, .height] + host.addSubview(slot) + + let inspectorSplit = NSSplitView(frame: slot.bounds) + inspectorSplit.autoresizingMask = [.width, .height] + inspectorSplit.isVertical = true + inspectorSplit.dividerStyle = .thin + let inspectorDelegate = BonsplitMockSplitDelegate() + inspectorSplit.delegate = inspectorDelegate + let pageView = CapturingView(frame: NSRect(x: 0, y: 0, width: 1, height: slot.bounds.height)) + let inspectorView = CapturingView( + frame: NSRect(x: 2, y: 0, width: slot.bounds.width - 2, height: slot.bounds.height) + ) + inspectorSplit.addSubview(pageView) + inspectorSplit.addSubview(inspectorView) + slot.addSubview(inspectorSplit) + inspectorSplit.setPosition(1, ofDividerAt: 0) + inspectorSplit.adjustSubviews() + contentView.layoutSubtreeIfNeeded() + + let dividerPointInSplit = NSPoint( + x: inspectorSplit.arrangedSubviews[0].frame.maxX + (inspectorSplit.dividerThickness * 0.5), + y: inspectorSplit.bounds.midY + ) + let dividerPointInWindow = inspectorSplit.convert(dividerPointInSplit, to: nil) + let dividerPointInHost = host.convert(dividerPointInWindow, from: nil) + + XCTAssertLessThanOrEqual(inspectorSplit.arrangedSubviews[0].frame.width, 1.5) + XCTAssertTrue( + abs(dividerPointInHost.x - slot.frame.minX) <= SidebarResizeInteraction.hitWidthPerSide, + "Expected collapsed hosted divider to overlap the browser slot leading-edge resizer zone" + ) + + let hit = host.hitTest(dividerPointInHost) + XCTAssertNotNil( + hit, + "Hosted vertical inspector divider should stay interactive even when collapsed onto the slot edge" + ) + XCTAssertFalse(hit === host) + if let hit { + XCTAssertTrue( + hit === inspectorSplit || hit.isDescendant(of: inspectorSplit), + "Expected hit to remain inside hosted inspector split subtree at the slot edge" + ) + } + } + + func testHostViewPrefersNativeHostedInspectorSiblingDividerHit() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: NSRect(x: 180, y: 0, width: 240, height: host.bounds.height)) + slot.autoresizingMask = [.minXMargin, .height] + host.addSubview(slot) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 92, height: slot.bounds.height)) + let inspectorView = WKInspectorProbeView( + frame: NSRect(x: 92, y: 0, width: slot.bounds.width - 92, height: slot.bounds.height) + ) + slot.addSubview(pageView) + slot.addSubview(inspectorView) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInSlot = NSPoint(x: inspectorView.frame.minX + 2, y: slot.bounds.midY) + let dividerPointInWindow = slot.convert(dividerPointInSlot, to: nil) + let dividerPointInHost = host.convert(dividerPointInWindow, from: nil) + let bodyPointInSlot = NSPoint(x: inspectorView.frame.minX + 18, y: slot.bounds.midY) + let bodyPointInWindow = slot.convert(bodyPointInSlot, to: nil) + let bodyPointInHost = host.convert(bodyPointInWindow, from: nil) + + let dividerHit = host.hitTest(dividerPointInHost) + XCTAssertTrue( + isInspectorOwnedHit(dividerHit, inspectorView: inspectorView, pageView: pageView), + "Hosted right-docked inspector divider should stay on the native WebKit hit path when WebKit exposes a hittable inspector-side view. actual=\(String(describing: dividerHit))" + ) + let interiorHit = host.hitTest(bodyPointInHost) + XCTAssertTrue( + isInspectorOwnedHit(interiorHit, inspectorView: inspectorView, pageView: pageView), + "Only the divider edge should be claimed; interior inspector hits should still reach WebKit content. actual=\(String(describing: interiorHit))" + ) + } + + func testHostViewPrefersNativeNestedHostedInspectorSiblingDividerHit() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: NSRect(x: 180, y: 0, width: 240, height: host.bounds.height)) + slot.autoresizingMask = [.minXMargin, .height] + host.addSubview(slot) + + let wrapper = NSView(frame: slot.bounds) + wrapper.autoresizingMask = [.width, .height] + slot.addSubview(wrapper) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 92, height: wrapper.bounds.height)) + let inspectorContainer = NSView( + frame: NSRect(x: 92, y: 0, width: wrapper.bounds.width - 92, height: wrapper.bounds.height) + ) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + wrapper.addSubview(pageView) + wrapper.addSubview(inspectorContainer) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInSlot = NSPoint(x: inspectorContainer.frame.minX + 2, y: slot.bounds.midY) + let dividerPointInWindow = slot.convert(dividerPointInSlot, to: nil) + let dividerPointInHost = host.convert(dividerPointInWindow, from: nil) + let bodyPointInSlot = NSPoint(x: inspectorContainer.frame.minX + 18, y: slot.bounds.midY) + let bodyPointInWindow = slot.convert(bodyPointInSlot, to: nil) + let bodyPointInHost = host.convert(bodyPointInWindow, from: nil) + + let dividerHit = host.hitTest(dividerPointInHost) + XCTAssertTrue( + isInspectorOwnedHit(dividerHit, inspectorView: inspectorView, pageView: pageView), + "Portal host should prefer the native nested WebKit hit target on the right-docked divider when available. actual=\(String(describing: dividerHit))" + ) + let interiorHit = host.hitTest(bodyPointInHost) + XCTAssertTrue( + isInspectorOwnedHit(interiorHit, inspectorView: inspectorView, pageView: pageView), + "Only the divider edge should be claimed; interior nested inspector hits should still reach WebKit content. actual=\(String(describing: interiorHit))" + ) + } + + func testHostViewReappliesStoredHostedInspectorWidthAfterSlotLayoutReset() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: NSRect(x: 180, y: 0, width: 240, height: host.bounds.height)) + slot.autoresizingMask = [.minXMargin, .height] + host.addSubview(slot) + + let wrapper = NSView(frame: slot.bounds) + wrapper.autoresizingMask = [.width, .height] + slot.addSubview(wrapper) + + let originalPageFrame = NSRect(x: 0, y: 0, width: 92, height: wrapper.bounds.height) + let originalInspectorFrame = NSRect( + x: 92, + y: 0, + width: wrapper.bounds.width - 92, + height: wrapper.bounds.height + ) + let pageView = PrimaryPageProbeView(frame: originalPageFrame) + let inspectorContainer = NSView(frame: originalInspectorFrame) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + wrapper.addSubview(pageView) + wrapper.addSubview(inspectorContainer) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInSlot = NSPoint(x: inspectorContainer.frame.minX, y: slot.bounds.midY) + let dividerPointInWindow = slot.convert(dividerPointInSlot, to: nil) + + let down = makeMouseEvent(type: .leftMouseDown, location: dividerPointInWindow, window: window) + host.mouseDown(with: down) + let drag = makeMouseEvent( + type: .leftMouseDragged, + location: NSPoint(x: dividerPointInWindow.x + 48, y: dividerPointInWindow.y), + window: window + ) + host.mouseDragged(with: drag) + host.mouseUp(with: makeMouseEvent(type: .leftMouseUp, location: drag.locationInWindow, window: window)) + + let draggedPageWidth = pageView.frame.width + let draggedInspectorMinX = inspectorContainer.frame.minX + XCTAssertGreaterThan(draggedPageWidth, originalPageFrame.width) + XCTAssertGreaterThan(draggedInspectorMinX, originalInspectorFrame.minX) + + pageView.frame = originalPageFrame + inspectorContainer.frame = originalInspectorFrame + slot.needsLayout = true + slot.layoutSubtreeIfNeeded() + host.layoutSubtreeIfNeeded() + + XCTAssertEqual(pageView.frame.width, draggedPageWidth, accuracy: 0.5) + XCTAssertEqual(inspectorContainer.frame.minX, draggedInspectorMinX, accuracy: 0.5) + } + + func testHostViewFallsBackToManualHostedInspectorDragWhenNativeDividerHitIsUnavailable() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: NSRect(x: 180, y: 0, width: 240, height: host.bounds.height)) + slot.autoresizingMask = [.minXMargin, .height] + host.addSubview(slot) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 92, height: slot.bounds.height)) + let inspectorView = EdgeTransparentWKInspectorProbeView( + frame: NSRect(x: 92, y: 0, width: slot.bounds.width - 92, height: slot.bounds.height) + ) + slot.addSubview(pageView) + slot.addSubview(inspectorView) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInSlot = NSPoint(x: inspectorView.frame.minX + 2, y: slot.bounds.midY) + let dividerPointInWindow = slot.convert(dividerPointInSlot, to: nil) + let dividerPointInHost = host.convert(dividerPointInWindow, from: nil) + + let dividerHit = host.hitTest(dividerPointInHost) + XCTAssertTrue( + dividerHit === host, + "Host should only take the manual fallback path when the right-docked divider edge is not natively hittable. actual=\(String(describing: dividerHit))" + ) + + let down = makeMouseEvent(type: .leftMouseDown, location: dividerPointInWindow, window: window) + host.mouseDown(with: down) + let drag = makeMouseEvent( + type: .leftMouseDragged, + location: NSPoint(x: dividerPointInWindow.x + 40, y: dividerPointInWindow.y), + window: window + ) + host.mouseDragged(with: drag) + host.mouseUp(with: makeMouseEvent(type: .leftMouseUp, location: drag.locationInWindow, window: window)) + + XCTAssertGreaterThan(pageView.frame.width, 92) + XCTAssertGreaterThan(inspectorView.frame.minX, 92) + } + + func testHostViewClaimsCollapsedHostedInspectorSiblingDividerAtSlotLeadingEdge() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + guard let container = contentView.superview else { + XCTFail("Expected content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowBrowserHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let slot = WindowBrowserSlotView(frame: NSRect(x: 180, y: 0, width: 240, height: host.bounds.height)) + slot.autoresizingMask = [.minXMargin, .height] + host.addSubview(slot) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 0, height: slot.bounds.height)) + let inspectorView = WKInspectorProbeView(frame: slot.bounds) + slot.addSubview(pageView) + slot.addSubview(inspectorView) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInSlot = NSPoint(x: inspectorView.frame.minX + 2, y: slot.bounds.midY) + let dividerPointInWindow = slot.convert(dividerPointInSlot, to: nil) + let dividerPointInHost = host.convert(dividerPointInWindow, from: nil) + + XCTAssertLessThanOrEqual(dividerPointInHost.x - slot.frame.minX, SidebarResizeInteraction.hitWidthPerSide) + let dividerHit = host.hitTest(dividerPointInHost) + XCTAssertTrue( + isInspectorOwnedHit(dividerHit, inspectorView: inspectorView, pageView: pageView), + "Collapsed right-docked hosted inspector divider should stay on the native WebKit hit path while still beating the sidebar-resizer overlap zone. actual=\(String(describing: dividerHit))" + ) + } +} + +@MainActor +final class BrowserPanelHostContainerViewTests: XCTestCase { + private final class PrimaryPageProbeView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + bounds.contains(point) ? self : nil + } + } + + private final class WKInspectorProbeView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + bounds.contains(point) ? self : nil + } + } + + private final class EdgeTransparentWKInspectorProbeView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + let localPoint = convert(point, from: superview) + guard bounds.contains(localPoint) else { return nil } + return localPoint.x <= 12 ? nil : self + } + } + + private func makeMouseEvent(type: NSEvent.EventType, location: NSPoint, window: NSWindow) -> NSEvent { + guard let event = NSEvent.mouseEvent( + with: type, + location: location, + modifierFlags: [], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + eventNumber: 0, + clickCount: 1, + pressure: 1.0 + ) else { + fatalError("Failed to create \(type) mouse event") + } + return event + } + + func testBrowserPanelHostPrefersNativeHostedInspectorSiblingDividerHit() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let host = WebViewRepresentable.HostContainerView(frame: NSRect(x: 180, y: 0, width: 240, height: contentView.bounds.height)) + host.autoresizingMask = [.minXMargin, .height] + contentView.addSubview(host) + + let webViewRoot = NSView(frame: host.bounds) + webViewRoot.autoresizingMask = [.width, .height] + host.addSubview(webViewRoot) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 92, height: webViewRoot.bounds.height)) + let inspectorContainer = NSView( + frame: NSRect(x: 92, y: 0, width: webViewRoot.bounds.width - 92, height: webViewRoot.bounds.height) + ) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + webViewRoot.addSubview(pageView) + webViewRoot.addSubview(inspectorContainer) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInHost = NSPoint(x: inspectorContainer.frame.minX + 2, y: host.bounds.midY) + let bodyPointInHost = NSPoint(x: inspectorContainer.frame.minX + 18, y: host.bounds.midY) + let interiorHit = host.hitTest(bodyPointInHost) + + XCTAssertTrue( + host.hitTest(dividerPointInHost) === host, + "Browser panel host should claim the right-docked divider edge for the manual resize path" + ) + XCTAssertTrue( + interiorHit == nil || interiorHit !== host, + "Only the divider edge should be claimed; interior inspector hits should not be stolen by the host. actual=\(String(describing: interiorHit))" + ) + } + + func testBrowserPanelHostClaimsCollapsedHostedInspectorSiblingDividerAtLeadingEdge() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let host = WebViewRepresentable.HostContainerView(frame: NSRect(x: 180, y: 0, width: 240, height: contentView.bounds.height)) + host.autoresizingMask = [.minXMargin, .height] + contentView.addSubview(host) + + let webViewRoot = NSView(frame: host.bounds) + webViewRoot.autoresizingMask = [.width, .height] + host.addSubview(webViewRoot) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 0, height: webViewRoot.bounds.height)) + let inspectorContainer = NSView(frame: webViewRoot.bounds) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + webViewRoot.addSubview(pageView) + webViewRoot.addSubview(inspectorContainer) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInHost = NSPoint(x: inspectorContainer.frame.minX + 2, y: host.bounds.midY) + let dividerPointInWindow = host.convert(dividerPointInHost, to: nil) + + XCTAssertTrue( + host.hitTest(dividerPointInHost) === host, + "Collapsed right-docked divider should stay on the manual browser-panel resize path while beating the sidebar-resizer overlap" + ) + + let down = makeMouseEvent(type: .leftMouseDown, location: dividerPointInWindow, window: window) + host.mouseDown(with: down) + let drag = makeMouseEvent( + type: .leftMouseDragged, + location: NSPoint(x: dividerPointInWindow.x + 36, y: dividerPointInWindow.y), + window: window + ) + host.mouseDragged(with: drag) + host.mouseUp(with: makeMouseEvent(type: .leftMouseUp, location: drag.locationInWindow, window: window)) + + XCTAssertGreaterThan(pageView.frame.width, 0) + XCTAssertGreaterThan(inspectorContainer.frame.minX, 0) + } + + func testBrowserPanelHostFallsBackToManualHostedInspectorDragWhenNativeDividerHitIsUnavailable() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let host = WebViewRepresentable.HostContainerView(frame: NSRect(x: 180, y: 0, width: 240, height: contentView.bounds.height)) + host.autoresizingMask = [.minXMargin, .height] + contentView.addSubview(host) + + let webViewRoot = NSView(frame: host.bounds) + webViewRoot.autoresizingMask = [.width, .height] + host.addSubview(webViewRoot) + + let pageView = PrimaryPageProbeView(frame: NSRect(x: 0, y: 0, width: 92, height: webViewRoot.bounds.height)) + let inspectorContainer = EdgeTransparentWKInspectorProbeView( + frame: NSRect(x: 92, y: 0, width: webViewRoot.bounds.width - 92, height: webViewRoot.bounds.height) + ) + webViewRoot.addSubview(pageView) + webViewRoot.addSubview(inspectorContainer) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInHost = NSPoint(x: inspectorContainer.frame.minX + 2, y: host.bounds.midY) + let dividerPointInWindow = host.convert(dividerPointInHost, to: nil) + + XCTAssertTrue( + host.hitTest(dividerPointInHost) === host, + "Browser panel host should only take the manual fallback path when the divider edge is not natively hittable" + ) + + let down = makeMouseEvent(type: .leftMouseDown, location: dividerPointInWindow, window: window) + host.mouseDown(with: down) + let drag = makeMouseEvent( + type: .leftMouseDragged, + location: NSPoint(x: dividerPointInWindow.x + 40, y: dividerPointInWindow.y), + window: window + ) + host.mouseDragged(with: drag) + host.mouseUp(with: makeMouseEvent(type: .leftMouseUp, location: drag.locationInWindow, window: window)) + + XCTAssertGreaterThan(pageView.frame.width, 92) + XCTAssertGreaterThan(inspectorContainer.frame.minX, 92) + } + + func testBrowserPanelHostReappliesStoredHostedInspectorWidthAfterLayoutReset() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let host = WebViewRepresentable.HostContainerView( + frame: NSRect(x: 180, y: 0, width: 240, height: contentView.bounds.height) + ) + host.autoresizingMask = [.minXMargin, .height] + contentView.addSubview(host) + + let webViewRoot = NSView(frame: host.bounds) + webViewRoot.autoresizingMask = [.width, .height] + host.addSubview(webViewRoot) + + let originalPageFrame = NSRect(x: 0, y: 0, width: 92, height: webViewRoot.bounds.height) + let originalInspectorFrame = NSRect( + x: 92, + y: 0, + width: webViewRoot.bounds.width - 92, + height: webViewRoot.bounds.height + ) + let pageView = PrimaryPageProbeView(frame: originalPageFrame) + let inspectorContainer = NSView(frame: originalInspectorFrame) + let inspectorView = WKInspectorProbeView(frame: inspectorContainer.bounds) + inspectorView.autoresizingMask = [.width, .height] + inspectorContainer.addSubview(inspectorView) + webViewRoot.addSubview(pageView) + webViewRoot.addSubview(inspectorContainer) + contentView.layoutSubtreeIfNeeded() + + let dividerPointInHost = NSPoint(x: inspectorContainer.frame.minX + 2, y: host.bounds.midY) + let dividerPointInWindow = host.convert(dividerPointInHost, to: nil) + + let down = makeMouseEvent(type: .leftMouseDown, location: dividerPointInWindow, window: window) + host.mouseDown(with: down) + let drag = makeMouseEvent( + type: .leftMouseDragged, + location: NSPoint(x: dividerPointInWindow.x + 48, y: dividerPointInWindow.y), + window: window + ) + host.mouseDragged(with: drag) + host.mouseUp(with: makeMouseEvent(type: .leftMouseUp, location: drag.locationInWindow, window: window)) + + let draggedPageWidth = pageView.frame.width + let draggedInspectorMinX = inspectorContainer.frame.minX + XCTAssertGreaterThan(draggedPageWidth, originalPageFrame.width) + XCTAssertGreaterThan(draggedInspectorMinX, originalInspectorFrame.minX) + + pageView.frame = originalPageFrame + inspectorContainer.frame = originalInspectorFrame + host.needsLayout = true + host.layoutSubtreeIfNeeded() + + XCTAssertEqual(pageView.frame.width, draggedPageWidth, accuracy: 0.5) + XCTAssertEqual(inspectorContainer.frame.minX, draggedInspectorMinX, accuracy: 0.5) + } } @MainActor From 44910d03e81def5de62bed5d27937a74305d46c9 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:29:58 -0800 Subject: [PATCH 143/232] fix: preserve browser target lookup on palette dismiss --- Sources/ContentView.swift | 9 +++++---- Sources/Panels/BrowserPanelView.swift | 6 ++++++ cmuxUITests/BrowserPaneNavigationKeybindUITests.swift | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 6acab274..27b9644d 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1158,8 +1158,9 @@ private func commandPaletteOwningWebView(for responder: NSResponder?) -> WKWebVi } if let textView = responder as? NSTextView, - let delegateView = textView.delegate as? NSView { - return commandPaletteOwningWebView(for: delegateView) + let delegateView = textView.delegate as? NSView, + let webView = commandPaletteOwningWebView(for: delegateView) { + return webView } var currentResponder = responder.nextResponder @@ -4768,8 +4769,8 @@ struct ContentView: View { return nil } - let contentPoint = NSPoint(x: contentPoint.x, y: contentPoint.y) - let windowPoint = contentView.convert(contentPoint, to: nil) + let nsContentPoint = NSPoint(x: contentPoint.x, y: contentPoint.y) + let windowPoint = contentView.convert(nsContentPoint, to: nil) return commandPaletteBackdropFocusTarget(atWindowPoint: windowPoint, in: window) } diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 07295066..648531bb 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -298,6 +298,10 @@ struct BrowserPanelView: View { ) } + private var browserContentAccessibilityIdentifier: String { + "BrowserPanelContent.\(panel.id.uuidString)" + } + private var omnibarPillBackgroundColor: NSColor { resolvedBrowserOmnibarPillBackgroundColor( for: browserChromeColorScheme, @@ -749,6 +753,7 @@ struct BrowserPanelView: View { // BrowserPanel replaces its underlying WKWebView after process termination. .id(panel.webViewInstanceID) .contentShape(Rectangle()) + .accessibilityIdentifier(browserContentAccessibilityIdentifier) .simultaneousGesture(TapGesture().onEnded { // Chrome-like behavior: clicking web content while editing the // omnibar should commit blur and revert transient edits. @@ -759,6 +764,7 @@ struct BrowserPanelView: View { } else { Color(nsColor: browserChromeBackgroundColor) .contentShape(Rectangle()) + .accessibilityIdentifier(browserContentAccessibilityIdentifier) .onTapGesture { onRequestPanelFocus() if addressBarFocused { diff --git a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift index d6138266..f1c6b630 100644 --- a/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift +++ b/cmuxUITests/BrowserPaneNavigationKeybindUITests.swift @@ -319,9 +319,9 @@ final class BrowserPaneNavigationKeybindUITests: XCTestCase { "Expected Cmd+R to open the rename command palette while terminal is focused" ) - let window = app.windows.firstMatch - XCTAssertTrue(window.waitForExistence(timeout: 2.0), "Expected main window for browser-pane click") - window.coordinate(withNormalizedOffset: CGVector(dx: 0.82, dy: 0.78)).click() + let browserPane = app.otherElements["BrowserPanelContent.\(expectedBrowserPanelId)"].firstMatch + XCTAssertTrue(browserPane.waitForExistence(timeout: 5.0), "Expected browser pane content for click target") + browserPane.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).click() XCTAssertTrue( waitForNonExistence(renameField, timeout: 5.0), "Expected clicking the browser pane to dismiss the command palette" From a7cef78725003a528363e44143e64aeb39d00047 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Thu, 5 Mar 2026 21:31:13 -0800 Subject: [PATCH 144/232] Revert "Fix notification unread persistence when workspaces regain focus (#971)" (#992) This reverts commit 5f43a3fc32045cf63cd4bab97befe8f0756c6c16. --- Sources/AppDelegate.swift | 36 +++++ Sources/TabManager.swift | 12 +- Sources/TerminalNotificationStore.swift | 43 +++-- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 151 ------------------ tests/test_focus_notification_dismiss.py | 12 +- tests/test_notifications.py | 95 ++++------- tests_v2/test_focus_notification_dismiss.py | 12 +- tests_v2/test_notifications.py | 95 ++++------- 8 files changed, 140 insertions(+), 316 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 88b76d11..95452827 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1838,6 +1838,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let tab = tabManager.tabs.first(where: { $0.id == tabId }) { tab.triggerNotificationFocusFlash(panelId: surfaceId, requiresSplit: false, shouldFocus: false) } + notificationStore.markRead(forTabId: tabId, surfaceId: surfaceId) } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -8167,6 +8168,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) #endif + if let notificationId, let store = notificationStore { + markReadIfFocused( + notificationId: notificationId, + tabId: tabId, + surfaceId: surfaceId, + tabManager: context.tabManager, + notificationStore: store + ) + } + #if DEBUG recordMultiWindowNotificationFocusIfNeeded( windowId: context.windowId, @@ -8220,6 +8231,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ) #endif + if let notificationId, let store = notificationStore { + markReadIfFocused( + notificationId: notificationId, + tabId: tabId, + surfaceId: surfaceId, + tabManager: tabManager, + notificationStore: store + ) + } #if DEBUG if ProcessInfo.processInfo.environment["CMUX_UI_TEST_JUMP_UNREAD_SETUP"] == "1" { writeJumpUnreadTestData(["jumpUnreadOpenInFallback": "1", "jumpUnreadOpenResult": "1"]) @@ -8279,6 +8299,22 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) } + private func markReadIfFocused( + notificationId: UUID, + tabId: UUID, + surfaceId: UUID?, + tabManager: TabManager, + notificationStore: TerminalNotificationStore + ) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + guard tabManager.selectedTabId == tabId else { return } + if let surfaceId { + guard tabManager.focusedSurfaceId(for: tabId) == surfaceId else { return } + } + notificationStore.markRead(id: notificationId) + } + } + #if DEBUG private func recordMultiWindowNotificationOpenFailureIfNeeded( tabId: UUID, diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 27a23f3f..96e7bdc4 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -599,7 +599,7 @@ class TabManager: ObservableObject { self.focusSelectedTabPanel(previousTabId: previousTabId) self.updateWindowTitleForSelectedTab() if let selectedTabId = self.selectedTabId { - self.flashFocusedPanelIfUnreadAndActive(tabId: selectedTabId) + self.markFocusedPanelReadIfActive(tabId: selectedTabId) } #if DEBUG let dtMs = self.debugWorkspaceSwitchStartTime > 0 @@ -671,7 +671,7 @@ class TabManager: ObservableObject { guard let self else { return } guard let tabId = notification.userInfo?[GhosttyNotificationKey.tabId] as? UUID else { return } guard let surfaceId = notification.userInfo?[GhosttyNotificationKey.surfaceId] as? UUID else { return } - flashPanelIfUnreadAndActive(tabId: tabId, panelId: surfaceId) + markPanelReadOnFocusIfActive(tabId: tabId, panelId: surfaceId) } }) @@ -1596,16 +1596,16 @@ class TabManager: ObservableObject { selectedTabId != pendingTabId } - private func flashFocusedPanelIfUnreadAndActive(tabId: UUID) { + private func markFocusedPanelReadIfActive(tabId: UUID) { let shouldSuppressFlash = suppressFocusFlash suppressFocusFlash = false guard !shouldSuppressFlash else { return } guard AppFocusState.isAppActive() else { return } guard let panelId = focusedPanelId(for: tabId) else { return } - flashPanelIfUnreadAndActive(tabId: tabId, panelId: panelId) + markPanelReadOnFocusIfActive(tabId: tabId, panelId: panelId) } - private func flashPanelIfUnreadAndActive(tabId: UUID, panelId: UUID) { + private func markPanelReadOnFocusIfActive(tabId: UUID, panelId: UUID) { guard selectedTabId == tabId else { return } guard !suppressFocusFlash else { return } guard AppFocusState.isAppActive() else { return } @@ -1614,6 +1614,7 @@ class TabManager: ObservableObject { if let tab = tabs.first(where: { $0.id == tabId }) { tab.triggerNotificationFocusFlash(panelId: panelId, requiresSplit: false, shouldFocus: false) } + notificationStore.markRead(forTabId: tabId, surfaceId: panelId) } private func enqueuePanelTitleUpdate(tabId: UUID, panelId: UUID, title: String) { @@ -1739,6 +1740,7 @@ class TabManager: ObservableObject { guard let notificationStore = AppDelegate.shared?.notificationStore else { return } guard notificationStore.hasUnreadNotification(forTabId: tabId, surfaceId: targetPanelId) else { return } tab.triggerNotificationFocusFlash(panelId: targetPanelId, requiresSplit: false, shouldFocus: true) + notificationStore.markRead(forTabId: tabId, surfaceId: targetPanelId) } } diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 1f1dac18..5bb768cb 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -833,9 +833,16 @@ final class TerminalNotificationStore: ObservableObject { let isFocusedSurface = surfaceId == nil || focusedSurfaceId == surfaceId let isFocusedPanel = isActiveTab && isFocusedSurface let isAppFocused = AppFocusState.isAppFocused() - let suppressNativeDelivery = isAppFocused && isFocusedPanel + if isAppFocused && isFocusedPanel { + if !idsToClear.isEmpty { + notifications = updated + center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) + center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) + } + return + } - if WorkspaceAutoReorderSettings.isEnabled() && !suppressNativeDelivery { + if WorkspaceAutoReorderSettings.isEnabled() { AppDelegate.shared?.tabManager?.moveTabToTop(tabId) } @@ -855,11 +862,7 @@ final class TerminalNotificationStore: ObservableObject { center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } - if suppressNativeDelivery { - Self.runNotificationCustomCommand(notification) - } else { - scheduleUserNotification(notification) - } + scheduleUserNotification(notification) } func markRead(id: UUID) { @@ -990,7 +993,10 @@ final class TerminalNotificationStore: ObservableObject { guard let self, authorized else { return } let content = UNMutableNotificationContent() - content.title = Self.notificationDisplayTitle(notification) + let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String + ?? "cmux" + content.title = notification.title.isEmpty ? appName : notification.title content.subtitle = notification.subtitle content.body = notification.body content.sound = NotificationSoundSettings.sound() @@ -1013,27 +1019,16 @@ final class TerminalNotificationStore: ObservableObject { if let error { NSLog("Failed to schedule notification: \(error)") } else { - Self.runNotificationCustomCommand(notification) + NotificationSoundSettings.runCustomCommand( + title: content.title, + subtitle: content.subtitle, + body: content.body + ) } } } } - nonisolated private static func notificationDisplayTitle(_ notification: TerminalNotification) -> String { - let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String - ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String - ?? "cmux" - return notification.title.isEmpty ? appName : notification.title - } - - nonisolated private static func runNotificationCustomCommand(_ notification: TerminalNotification) { - NotificationSoundSettings.runCustomCommand( - title: notificationDisplayTitle(notification), - subtitle: notification.subtitle, - body: notification.body - ) - } - private func ensureAuthorization( origin: AuthorizationRequestOrigin, _ completion: @escaping (Bool) -> Void diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index e06895aa..71d7ed96 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -6909,8 +6909,6 @@ final class NotificationDockBadgeTests: XCTestCase { } override func tearDown() { - AppFocusState.overrideIsFocused = nil - AppDelegate.shared = nil TerminalNotificationStore.shared.resetNotificationSettingsPromptHooksForTesting() TerminalNotificationStore.shared.replaceNotificationsForTesting([]) super.tearDown() @@ -7414,155 +7412,6 @@ final class NotificationDockBadgeTests: XCTestCase { XCTAssertEqual(store.latestNotification(forTabId: tabB)?.id, notificationBUnread.id) } - func testFocusedTabNotificationIsStoredWhenNativeDeliveryIsSuppressed() { - let store = TerminalNotificationStore.shared - store.replaceNotificationsForTesting([]) - - let appDelegate = AppDelegate() - let tabManager = TabManager() - appDelegate.tabManager = tabManager - AppDelegate.shared = appDelegate - AppFocusState.overrideIsFocused = true - - guard let tabId = tabManager.selectedTabId else { - XCTFail("Expected selected tab for notification test") - return - } - - store.addNotification( - tabId: tabId, - surfaceId: nil, - title: "Needs input", - subtitle: "", - body: "agent requires user action" - ) - - XCTAssertEqual(store.unreadCount(forTabId: tabId), 1) - guard let latest = store.latestNotification(forTabId: tabId) else { - XCTFail("Expected notification to be stored for focused tab") - return - } - XCTAssertEqual(latest.tabId, tabId) - XCTAssertEqual(latest.title, "Needs input") - XCTAssertEqual(latest.body, "agent requires user action") - XCTAssertFalse(latest.isRead) - } - - func testApplicationDidBecomeActiveDoesNotMarkFocusedNotificationRead() { - let store = TerminalNotificationStore.shared - let appDelegate = AppDelegate() - let tabManager = TabManager() - appDelegate.tabManager = tabManager - appDelegate.notificationStore = store - AppDelegate.shared = appDelegate - AppFocusState.overrideIsFocused = true - - guard let tabId = tabManager.selectedTabId, - let surfaceId = tabManager.focusedSurfaceId(for: tabId) else { - XCTFail("Expected selected tab and focused surface for activation test") - return - } - - let notification = TerminalNotification( - id: UUID(), - tabId: tabId, - surfaceId: surfaceId, - title: "Unread", - subtitle: "", - body: "should persist across app activation", - createdAt: Date(), - isRead: false - ) - store.replaceNotificationsForTesting([notification]) - - appDelegate.applicationDidBecomeActive( - Notification(name: NSApplication.didBecomeActiveNotification) - ) - - XCTAssertTrue(store.hasUnreadNotification(forTabId: tabId, surfaceId: surfaceId)) - XCTAssertFalse(store.notifications[0].isRead) - } - - func testSelectingWorkspaceDoesNotMarkFocusedNotificationRead() { - let store = TerminalNotificationStore.shared - let appDelegate = AppDelegate() - let tabManager = TabManager() - appDelegate.tabManager = tabManager - appDelegate.notificationStore = store - AppDelegate.shared = appDelegate - AppFocusState.overrideIsFocused = true - - guard let originalTabId = tabManager.selectedTabId, - let originalSurfaceId = tabManager.focusedSurfaceId(for: originalTabId) else { - XCTFail("Expected selected tab and focused surface for workspace selection test") - return - } - guard let originalWorkspace = tabManager.tabs.first(where: { $0.id == originalTabId }) else { - XCTFail("Expected original workspace for workspace selection test") - return - } - - let notification = TerminalNotification( - id: UUID(), - tabId: originalTabId, - surfaceId: originalSurfaceId, - title: "Unread", - subtitle: "", - body: "should persist across workspace selection", - createdAt: Date(), - isRead: false - ) - store.replaceNotificationsForTesting([notification]) - - _ = tabManager.addWorkspace(select: true) - tabManager.selectWorkspace(originalWorkspace) - - let drained = expectation(description: "workspace selection side effects drained") - DispatchQueue.main.async { drained.fulfill() } - wait(for: [drained], timeout: 1.0) - - XCTAssertEqual(tabManager.selectedTabId, originalTabId) - XCTAssertTrue(store.hasUnreadNotification(forTabId: originalTabId, surfaceId: originalSurfaceId)) - XCTAssertFalse(store.notifications[0].isRead) - } - - func testNotificationFocusNavigationDoesNotMarkNotificationRead() { - let store = TerminalNotificationStore.shared - let appDelegate = AppDelegate() - let tabManager = TabManager() - appDelegate.tabManager = tabManager - appDelegate.notificationStore = store - AppDelegate.shared = appDelegate - AppFocusState.overrideIsFocused = true - - guard let tabId = tabManager.selectedTabId, - let surfaceId = tabManager.focusedSurfaceId(for: tabId) else { - XCTFail("Expected selected tab and focused surface for notification focus test") - return - } - - let notification = TerminalNotification( - id: UUID(), - tabId: tabId, - surfaceId: surfaceId, - title: "Unread", - subtitle: "", - body: "should persist after notification focus", - createdAt: Date(), - isRead: false - ) - store.replaceNotificationsForTesting([notification]) - - tabManager.focusTabFromNotification(tabId, surfaceId: surfaceId) - - let drained = expectation(description: "notification focus drained") - DispatchQueue.main.async { drained.fulfill() } - wait(for: [drained], timeout: 1.0) - - XCTAssertTrue(store.hasUnreadNotification(forTabId: tabId, surfaceId: surfaceId)) - XCTAssertFalse(store.notifications[0].isRead) - } - func testNotificationIndexesUpdateAfterReadAndClearMutations() { let tab = UUID() let surfaceUnread = UUID() diff --git a/tests/test_focus_notification_dismiss.py b/tests/test_focus_notification_dismiss.py index 14cef434..d7569b65 100755 --- a/tests/test_focus_notification_dismiss.py +++ b/tests/test_focus_notification_dismiss.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -E2E: focusing a panel preserves its notification and triggers a flash. +E2E: focusing a panel clears its notification and triggers a flash. Note: This uses the socket focus command (no assistive access needed). """ @@ -74,12 +74,8 @@ def main() -> int: client.send("x") time.sleep(0.2) - if wait_for_notification(client, surface_id, is_read=True, timeout=2.0): - print("FAIL: Notification became read after focus") - return 1 - items = client.list_notifications() - if not any(item["surface_id"] == surface_id and not item["is_read"] for item in items): - print("FAIL: Notification did not remain present and unread after focus") + if not wait_for_notification(client, surface_id, is_read=True, timeout=2.0): + print("FAIL: Notification did not become read after focus") return 1 final_flash = client.flash_count(term_b) @@ -97,7 +93,7 @@ def main() -> int: except Exception: pass - print("PASS: Focus preserves notification and flashes panel") + print("PASS: Focus clears notification and flashes panel") return 0 except (cmuxError, RuntimeError) as exc: print(f"FAIL: {exc}") diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 23b4bf10..1ac25c4b 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -58,15 +58,6 @@ def wait_for_flash_count(client: cmux, surface: str, minimum: int = 1, timeout: return last -def wait_for_current_workspace(client: cmux, expected: str, timeout: float = 2.0) -> bool: - start = time.time() - while time.time() - start < timeout: - if client.current_workspace() == expected: - return True - time.sleep(0.05) - return client.current_workspace() == expected - - def ensure_two_surfaces(client: cmux) -> list[tuple[int, str, bool]]: surfaces = client.list_surfaces() if len(surfaces) < 2: @@ -224,8 +215,8 @@ def test_rxvt_notification_osc777(client: cmux) -> TestResult: return result -def test_preserve_unread_on_focus_change(client: cmux) -> TestResult: - result = TestResult("Preserve Unread On Panel Focus") +def test_mark_read_on_focus_change(client: cmux) -> TestResult: + result = TestResult("Mark Read On Panel Focus") try: client.clear_notifications() client.reset_flash_counts() @@ -238,88 +229,81 @@ def test_preserve_unread_on_focus_change(client: cmux) -> TestResult: client.set_app_focus(False) client.notify_surface(other[0], "focusread") - items = wait_for_notifications(client, 1) - target = next((n for n in items if n["surface_id"] == other[1]), None) - if target is None or target["is_read"]: - result.failure("Expected unread notification for target surface before focus") - return result + time.sleep(0.1) client.set_app_focus(True) client.focus_surface(other[0]) - count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) - if count < 1: - result.failure("Expected flash on panel focus") - return result + time.sleep(0.1) items = client.list_notifications() target = next((n for n in items if n["surface_id"] == other[1]), None) if target is None: result.failure("Expected notification for target surface") - elif target["is_read"]: - result.failure("Expected notification to remain unread on focus") + elif not target["is_read"]: + result.failure("Expected notification to be marked read on focus") else: - result.success("Notification persisted across panel focus") + count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) + if count < 1: + result.failure("Expected flash on panel focus dismissal") + else: + result.success("Notification marked read on focus") except Exception as e: result.failure(f"Exception: {e}") return result -def test_preserve_unread_on_app_active(client: cmux) -> TestResult: - result = TestResult("Preserve Unread On App Active") +def test_mark_read_on_app_active(client: cmux) -> TestResult: + result = TestResult("Mark Read On App Active") try: client.clear_notifications() client.set_app_focus(False) client.notify("activate") - items = wait_for_notifications(client, 1) + time.sleep(0.1) + + items = client.list_notifications() if not items or items[0]["is_read"]: result.failure("Expected unread notification before activation") return result client.simulate_app_active() - items = wait_for_notifications(client, 1) + time.sleep(0.1) + + items = client.list_notifications() if not items: result.failure("Expected notification to remain after activation") - elif items[0]["is_read"]: - result.failure("Expected notification to remain unread on app active") + elif not items[0]["is_read"]: + result.failure("Expected notification to be marked read on app active") else: - result.success("Notification persisted across app activation") + result.success("Notification marked read on app active") except Exception as e: result.failure(f"Exception: {e}") return result -def test_preserve_unread_on_tab_switch(client: cmux) -> TestResult: - result = TestResult("Preserve Unread On Tab Switch") +def test_mark_read_on_tab_switch(client: cmux) -> TestResult: + result = TestResult("Mark Read On Tab Switch") try: client.clear_notifications() client.set_app_focus(False) tab1 = client.current_workspace() client.notify("tabswitch") - items = wait_for_notifications(client, 1) - target = next((n for n in items if n["workspace_id"] == tab1), None) - if target is None or target["is_read"]: - result.failure("Expected unread notification for original tab before switching") - return result + time.sleep(0.1) tab2 = client.new_workspace() - if not wait_for_current_workspace(client, tab2): - result.failure("Expected new workspace to become selected") - return result + time.sleep(0.1) client.set_app_focus(True) client.select_workspace(tab1) - if not wait_for_current_workspace(client, tab1): - result.failure("Expected original workspace to become selected again") - return result + time.sleep(0.1) - items = wait_for_notifications(client, 1) + items = client.list_notifications() target = next((n for n in items if n["workspace_id"] == tab1), None) if target is None: result.failure("Expected notification for original tab") - elif target["is_read"]: - result.failure("Expected notification to remain unread on tab switch") + elif not target["is_read"]: + result.failure("Expected notification to be marked read on tab switch") else: - result.success("Notification persisted across tab switch") + result.success("Notification marked read on tab switch") except Exception as e: result.failure(f"Exception: {e}") return result @@ -387,20 +371,11 @@ def test_focus_on_notification_click(client: cmux) -> TestResult: result.failure("Expected notification surface to be focused") return result - items = client.list_notifications() - notification = next((n for n in items if n["surface_id"] == other[1]), None) - if notification is None: - result.failure("Expected notification to remain listed after notification click") - return result - if notification["is_read"]: - result.failure("Expected notification click to preserve unread state") - return result - count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) if count < 1: result.failure(f"Expected flash count >= 1, got {count}") else: - result.success("Notification click focuses, flashes, and preserves unread state") + result.success("Notification click focuses and flashes panel") except Exception as e: result.failure(f"Exception: {e}") return result @@ -480,9 +455,9 @@ def run_tests() -> int: results.append(test_kitty_notification_simple(client)) results.append(test_kitty_notification_chunked(client)) results.append(test_rxvt_notification_osc777(client)) - results.append(test_preserve_unread_on_focus_change(client)) - results.append(test_preserve_unread_on_app_active(client)) - results.append(test_preserve_unread_on_tab_switch(client)) + results.append(test_mark_read_on_focus_change(client)) + results.append(test_mark_read_on_app_active(client)) + results.append(test_mark_read_on_tab_switch(client)) results.append(test_flash_on_tab_switch(client)) results.append(test_focus_on_notification_click(client)) results.append(test_restore_focus_on_tab_switch(client)) diff --git a/tests_v2/test_focus_notification_dismiss.py b/tests_v2/test_focus_notification_dismiss.py index 14cef434..d7569b65 100755 --- a/tests_v2/test_focus_notification_dismiss.py +++ b/tests_v2/test_focus_notification_dismiss.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -E2E: focusing a panel preserves its notification and triggers a flash. +E2E: focusing a panel clears its notification and triggers a flash. Note: This uses the socket focus command (no assistive access needed). """ @@ -74,12 +74,8 @@ def main() -> int: client.send("x") time.sleep(0.2) - if wait_for_notification(client, surface_id, is_read=True, timeout=2.0): - print("FAIL: Notification became read after focus") - return 1 - items = client.list_notifications() - if not any(item["surface_id"] == surface_id and not item["is_read"] for item in items): - print("FAIL: Notification did not remain present and unread after focus") + if not wait_for_notification(client, surface_id, is_read=True, timeout=2.0): + print("FAIL: Notification did not become read after focus") return 1 final_flash = client.flash_count(term_b) @@ -97,7 +93,7 @@ def main() -> int: except Exception: pass - print("PASS: Focus preserves notification and flashes panel") + print("PASS: Focus clears notification and flashes panel") return 0 except (cmuxError, RuntimeError) as exc: print(f"FAIL: {exc}") diff --git a/tests_v2/test_notifications.py b/tests_v2/test_notifications.py index 23b4bf10..1ac25c4b 100644 --- a/tests_v2/test_notifications.py +++ b/tests_v2/test_notifications.py @@ -58,15 +58,6 @@ def wait_for_flash_count(client: cmux, surface: str, minimum: int = 1, timeout: return last -def wait_for_current_workspace(client: cmux, expected: str, timeout: float = 2.0) -> bool: - start = time.time() - while time.time() - start < timeout: - if client.current_workspace() == expected: - return True - time.sleep(0.05) - return client.current_workspace() == expected - - def ensure_two_surfaces(client: cmux) -> list[tuple[int, str, bool]]: surfaces = client.list_surfaces() if len(surfaces) < 2: @@ -224,8 +215,8 @@ def test_rxvt_notification_osc777(client: cmux) -> TestResult: return result -def test_preserve_unread_on_focus_change(client: cmux) -> TestResult: - result = TestResult("Preserve Unread On Panel Focus") +def test_mark_read_on_focus_change(client: cmux) -> TestResult: + result = TestResult("Mark Read On Panel Focus") try: client.clear_notifications() client.reset_flash_counts() @@ -238,88 +229,81 @@ def test_preserve_unread_on_focus_change(client: cmux) -> TestResult: client.set_app_focus(False) client.notify_surface(other[0], "focusread") - items = wait_for_notifications(client, 1) - target = next((n for n in items if n["surface_id"] == other[1]), None) - if target is None or target["is_read"]: - result.failure("Expected unread notification for target surface before focus") - return result + time.sleep(0.1) client.set_app_focus(True) client.focus_surface(other[0]) - count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) - if count < 1: - result.failure("Expected flash on panel focus") - return result + time.sleep(0.1) items = client.list_notifications() target = next((n for n in items if n["surface_id"] == other[1]), None) if target is None: result.failure("Expected notification for target surface") - elif target["is_read"]: - result.failure("Expected notification to remain unread on focus") + elif not target["is_read"]: + result.failure("Expected notification to be marked read on focus") else: - result.success("Notification persisted across panel focus") + count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) + if count < 1: + result.failure("Expected flash on panel focus dismissal") + else: + result.success("Notification marked read on focus") except Exception as e: result.failure(f"Exception: {e}") return result -def test_preserve_unread_on_app_active(client: cmux) -> TestResult: - result = TestResult("Preserve Unread On App Active") +def test_mark_read_on_app_active(client: cmux) -> TestResult: + result = TestResult("Mark Read On App Active") try: client.clear_notifications() client.set_app_focus(False) client.notify("activate") - items = wait_for_notifications(client, 1) + time.sleep(0.1) + + items = client.list_notifications() if not items or items[0]["is_read"]: result.failure("Expected unread notification before activation") return result client.simulate_app_active() - items = wait_for_notifications(client, 1) + time.sleep(0.1) + + items = client.list_notifications() if not items: result.failure("Expected notification to remain after activation") - elif items[0]["is_read"]: - result.failure("Expected notification to remain unread on app active") + elif not items[0]["is_read"]: + result.failure("Expected notification to be marked read on app active") else: - result.success("Notification persisted across app activation") + result.success("Notification marked read on app active") except Exception as e: result.failure(f"Exception: {e}") return result -def test_preserve_unread_on_tab_switch(client: cmux) -> TestResult: - result = TestResult("Preserve Unread On Tab Switch") +def test_mark_read_on_tab_switch(client: cmux) -> TestResult: + result = TestResult("Mark Read On Tab Switch") try: client.clear_notifications() client.set_app_focus(False) tab1 = client.current_workspace() client.notify("tabswitch") - items = wait_for_notifications(client, 1) - target = next((n for n in items if n["workspace_id"] == tab1), None) - if target is None or target["is_read"]: - result.failure("Expected unread notification for original tab before switching") - return result + time.sleep(0.1) tab2 = client.new_workspace() - if not wait_for_current_workspace(client, tab2): - result.failure("Expected new workspace to become selected") - return result + time.sleep(0.1) client.set_app_focus(True) client.select_workspace(tab1) - if not wait_for_current_workspace(client, tab1): - result.failure("Expected original workspace to become selected again") - return result + time.sleep(0.1) - items = wait_for_notifications(client, 1) + items = client.list_notifications() target = next((n for n in items if n["workspace_id"] == tab1), None) if target is None: result.failure("Expected notification for original tab") - elif target["is_read"]: - result.failure("Expected notification to remain unread on tab switch") + elif not target["is_read"]: + result.failure("Expected notification to be marked read on tab switch") else: - result.success("Notification persisted across tab switch") + result.success("Notification marked read on tab switch") except Exception as e: result.failure(f"Exception: {e}") return result @@ -387,20 +371,11 @@ def test_focus_on_notification_click(client: cmux) -> TestResult: result.failure("Expected notification surface to be focused") return result - items = client.list_notifications() - notification = next((n for n in items if n["surface_id"] == other[1]), None) - if notification is None: - result.failure("Expected notification to remain listed after notification click") - return result - if notification["is_read"]: - result.failure("Expected notification click to preserve unread state") - return result - count = wait_for_flash_count(client, other[1], minimum=1, timeout=2.0) if count < 1: result.failure(f"Expected flash count >= 1, got {count}") else: - result.success("Notification click focuses, flashes, and preserves unread state") + result.success("Notification click focuses and flashes panel") except Exception as e: result.failure(f"Exception: {e}") return result @@ -480,9 +455,9 @@ def run_tests() -> int: results.append(test_kitty_notification_simple(client)) results.append(test_kitty_notification_chunked(client)) results.append(test_rxvt_notification_osc777(client)) - results.append(test_preserve_unread_on_focus_change(client)) - results.append(test_preserve_unread_on_app_active(client)) - results.append(test_preserve_unread_on_tab_switch(client)) + results.append(test_mark_read_on_focus_change(client)) + results.append(test_mark_read_on_app_active(client)) + results.append(test_mark_read_on_tab_switch(client)) results.append(test_flash_on_tab_switch(client)) results.append(test_focus_on_notification_click(client)) results.append(test_restore_focus_on_tab_switch(client)) From 7bee9ddc1336a3781bc215bdaeb4296fdfc4958b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:43:20 -0800 Subject: [PATCH 145/232] Increase notify regression socket timeout --- .../MultiWindowNotificationsUITests.swift | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 2ad740ce..c1c00575 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -289,7 +289,10 @@ final class MultiWindowNotificationsUITests: XCTestCase { "2>\(shellSingleQuote(commandStderrPath));", "printf '%s' $? >\(shellSingleQuote(commandStatusPath))) >/dev/null 2>&1 &" ].joined(separator: " ") - let sendWorkspaceResponse = socketCommand("send_workspace \(tabId1) \(notifyCommand)\\n") + let sendWorkspaceResponse = socketCommand( + "send_workspace \(tabId1) \(notifyCommand)\\n", + responseTimeout: 8.0 + ) guard sendWorkspaceResponse == "OK" else { XCTFail( "Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1). " + @@ -985,20 +988,21 @@ final class MultiWindowNotificationsUITests: XCTestCase { return socketCommand("ping") == "PONG" } - private func socketCommand(_ cmd: String) -> String? { - if let response = ControlSocketClient(path: socketPath).sendLine(cmd) { + private func socketCommand(_ cmd: String, responseTimeout: TimeInterval = 2.0) -> String? { + if let response = ControlSocketClient(path: socketPath, responseTimeout: responseTimeout).sendLine(cmd) { return response } - return socketCommandViaNetcat(cmd) + return socketCommandViaNetcat(cmd, responseTimeout: responseTimeout) } - private func socketCommandViaNetcat(_ cmd: String) -> String? { + private func socketCommandViaNetcat(_ cmd: String, responseTimeout: TimeInterval = 2.0) -> String? { let nc = "/usr/bin/nc" guard FileManager.default.isExecutableFile(atPath: nc) else { return nil } let proc = Process() proc.executableURL = URL(fileURLWithPath: "/bin/sh") - let script = "printf '%s\\n' \(shellSingleQuote(cmd)) | \(nc) -U \(shellSingleQuote(socketPath)) -w 2 2>/dev/null" + let timeoutSeconds = max(1, Int(ceil(responseTimeout))) + let script = "printf '%s\\n' \(shellSingleQuote(cmd)) | \(nc) -U \(shellSingleQuote(socketPath)) -w \(timeoutSeconds) 2>/dev/null" proc.arguments = ["-lc", script] let outPipe = Pipe() @@ -1036,9 +1040,11 @@ final class MultiWindowNotificationsUITests: XCTestCase { private final class ControlSocketClient { private let path: String + private let responseTimeout: TimeInterval - init(path: String) { + init(path: String, responseTimeout: TimeInterval = 2.0) { self.path = path + self.responseTimeout = responseTimeout } func sendLine(_ line: String) -> String? { @@ -1101,7 +1107,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { } guard wrote else { return nil } - let deadline = Date().addingTimeInterval(2.0) + let deadline = Date().addingTimeInterval(responseTimeout) var buf = [UInt8](repeating: 0, count: 4096) var accum = "" while Date() < deadline { From 2427a2a73669f687f2bd8cd7400bd83cf7b1fe07 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 21:55:26 -0800 Subject: [PATCH 146/232] Fix browser portal anchor churn during pane drag --- Sources/BrowserWindowPortal.swift | 299 +++++++++++++++--- Sources/Panels/BrowserPanel.swift | 10 + Sources/Panels/BrowserPanelView.swift | 99 ++++-- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 70 +++- 4 files changed, 408 insertions(+), 70 deletions(-) diff --git a/Sources/BrowserWindowPortal.swift b/Sources/BrowserWindowPortal.swift index da7be546..32d54478 100644 --- a/Sources/BrowserWindowPortal.swift +++ b/Sources/BrowserWindowPortal.swift @@ -1,6 +1,7 @@ import AppKit import Bonsplit import ObjectiveC +import SwiftUI import WebKit private var cmuxWindowBrowserPortalKey: UInt8 = 0 @@ -359,6 +360,14 @@ private final class BrowserDropZoneOverlayView: NSView { } } +struct BrowserPortalSearchOverlayConfiguration { + let panelId: UUID + let searchState: BrowserSearchState + let onNext: () -> Void + let onPrevious: () -> Void + let onClose: () -> Void +} + struct BrowserPaneDropContext: Equatable { let workspaceId: UUID let panelId: UUID @@ -419,16 +428,23 @@ enum BrowserPaneDropAction: Equatable { } enum BrowserPaneDropRouting { - static func zone(for location: CGPoint, in size: CGSize) -> DropZone { + private static let padding: CGFloat = 4 + + private static func fullPaneSize(for slotSize: CGSize, topChromeHeight: CGFloat) -> CGSize { + CGSize(width: slotSize.width, height: slotSize.height + max(0, topChromeHeight)) + } + + static func zone(for location: CGPoint, in size: CGSize, topChromeHeight: CGFloat = 0) -> DropZone { + let fullPaneSize = fullPaneSize(for: size, topChromeHeight: topChromeHeight) let edgeRatio: CGFloat = 0.25 - let horizontalEdge = max(80, size.width * edgeRatio) - let verticalEdge = max(80, size.height * edgeRatio) + let horizontalEdge = max(80, fullPaneSize.width * edgeRatio) + let verticalEdge = max(80, fullPaneSize.height * edgeRatio) if location.x < horizontalEdge { return .left - } else if location.x > size.width - horizontalEdge { + } else if location.x > fullPaneSize.width - horizontalEdge { return .right - } else if location.y > size.height - verticalEdge { + } else if location.y > fullPaneSize.height - verticalEdge { return .top } else if location.y < verticalEdge { return .bottom @@ -437,6 +453,47 @@ enum BrowserPaneDropRouting { } } + static func overlayFrame(for zone: DropZone, in size: CGSize, topChromeHeight: CGFloat = 0) -> CGRect { + let fullPaneSize = fullPaneSize(for: size, topChromeHeight: topChromeHeight) + switch zone { + case .center: + return CGRect( + x: padding, + y: padding, + width: fullPaneSize.width - padding * 2, + height: fullPaneSize.height - padding * 2 + ) + case .left: + return CGRect( + x: padding, + y: padding, + width: fullPaneSize.width / 2 - padding, + height: fullPaneSize.height - padding * 2 + ) + case .right: + return CGRect( + x: fullPaneSize.width / 2, + y: padding, + width: fullPaneSize.width / 2 - padding, + height: fullPaneSize.height - padding * 2 + ) + case .top: + return CGRect( + x: padding, + y: fullPaneSize.height / 2, + width: fullPaneSize.width - padding * 2, + height: fullPaneSize.height / 2 - padding + ) + case .bottom: + return CGRect( + x: padding, + y: padding, + width: fullPaneSize.width - padding * 2, + height: fullPaneSize.height / 2 - padding + ) + } + } + static func action( for transfer: BrowserPaneDragTransfer, target: BrowserPaneDropContext, @@ -556,7 +613,11 @@ final class BrowserPaneDropTargetView: NSView { } let location = convert(sender.draggingLocation, from: nil) - let zone = BrowserPaneDropRouting.zone(for: location, in: bounds.size) + let zone = BrowserPaneDropRouting.zone( + for: location, + in: bounds.size, + topChromeHeight: slotView?.effectivePaneTopChromeHeight() ?? 0 + ) guard let action = BrowserPaneDropRouting.action( for: transfer, target: dropContext, @@ -612,7 +673,11 @@ final class BrowserPaneDropTargetView: NSView { } let location = convert(sender.draggingLocation, from: nil) - let zone = BrowserPaneDropRouting.zone(for: location, in: bounds.size) + let zone = BrowserPaneDropRouting.zone( + for: location, + in: bounds.size, + topChromeHeight: slotView?.effectivePaneTopChromeHeight() ?? 0 + ) activeZone = zone slotView?.setPortalDragDropZone(zone) #if DEBUG @@ -669,11 +734,13 @@ final class WindowBrowserSlotView: NSView { override var isOpaque: Bool { false } private let paneDropTargetView = BrowserPaneDropTargetView(frame: .zero) private let dropZoneOverlayView = BrowserDropZoneOverlayView(frame: .zero) + private var searchOverlayHostingView: NSHostingView? private var forwardedDropZone: DropZone? private var portalDragDropZone: DropZone? private var displayedDropZone: DropZone? private var dropZoneOverlayAnimationGeneration: UInt64 = 0 private var isRefreshingInteractionLayers = false + private var paneTopChromeHeight: CGFloat = 0 override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -691,7 +758,6 @@ final class WindowBrowserSlotView: NSView { dropZoneOverlayView.layer?.cornerRadius = 8 dropZoneOverlayView.isHidden = true addSubview(paneDropTargetView, positioned: .above, relativeTo: nil) - addSubview(dropZoneOverlayView, positioned: .above, relativeTo: nil) } @available(*, unavailable) @@ -705,6 +771,12 @@ final class WindowBrowserSlotView: NSView { applyResolvedDropZoneOverlay() } + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + attachDropZoneOverlayIfNeeded() + applyResolvedDropZoneOverlay() + } + func setDropZoneOverlay(zone: DropZone?) { forwardedDropZone = zone applyResolvedDropZoneOverlay() @@ -719,9 +791,62 @@ final class WindowBrowserSlotView: NSView { paneDropTargetView.dropContext = context } + func setPaneTopChromeHeight(_ height: CGFloat) { + let resolvedHeight = max(0, height) + guard abs(paneTopChromeHeight - resolvedHeight) > 0.5 else { return } + paneTopChromeHeight = resolvedHeight + applyResolvedDropZoneOverlay() + } + + func setSearchOverlay(_ configuration: BrowserPortalSearchOverlayConfiguration?) { + guard let configuration else { + searchOverlayHostingView?.removeFromSuperview() + searchOverlayHostingView = nil + return + } + + let rootView = BrowserSearchOverlay( + panelId: configuration.panelId, + searchState: configuration.searchState, + onNext: configuration.onNext, + onPrevious: configuration.onPrevious, + onClose: configuration.onClose + ) + + if let overlay = searchOverlayHostingView { + overlay.rootView = rootView + if overlay.superview !== self { + overlay.removeFromSuperview() + addSubview(overlay) + NSLayoutConstraint.activate([ + overlay.topAnchor.constraint(equalTo: topAnchor), + overlay.bottomAnchor.constraint(equalTo: bottomAnchor), + overlay.leadingAnchor.constraint(equalTo: leadingAnchor), + overlay.trailingAnchor.constraint(equalTo: trailingAnchor), + ]) + } + return + } + + let overlay = NSHostingView(rootView: rootView) + overlay.translatesAutoresizingMaskIntoConstraints = false + addSubview(overlay) + NSLayoutConstraint.activate([ + overlay.topAnchor.constraint(equalTo: topAnchor), + overlay.bottomAnchor.constraint(equalTo: bottomAnchor), + overlay.leadingAnchor.constraint(equalTo: leadingAnchor), + overlay.trailingAnchor.constraint(equalTo: trailingAnchor), + ]) + searchOverlayHostingView = overlay + } + + func effectivePaneTopChromeHeight() -> CGFloat { + paneTopChromeHeight + } + override func didAddSubview(_ subview: NSView) { super.didAddSubview(subview) - guard subview !== paneDropTargetView, subview !== dropZoneOverlayView else { return } + guard subview !== paneDropTargetView else { return } bringInteractionLayersToFrontIfNeeded() } @@ -729,6 +854,17 @@ final class WindowBrowserSlotView: NSView { portalDragDropZone ?? forwardedDropZone } + private func overlayContainerView() -> NSView { + superview ?? self + } + + private func attachDropZoneOverlayIfNeeded() { + let container = overlayContainerView() + guard dropZoneOverlayView.superview !== container else { return } + dropZoneOverlayView.removeFromSuperview() + container.addSubview(dropZoneOverlayView, positioned: .above, relativeTo: nil) + } + private func applyResolvedDropZoneOverlay() { let resolvedZone = activeDropZone if resolvedZone != nil, (bounds.width <= 2 || bounds.height <= 2) { @@ -764,6 +900,7 @@ final class WindowBrowserSlotView: NSView { } return } + attachDropZoneOverlayIfNeeded() let targetFrame = dropZoneOverlayFrame(for: zone, in: bounds.size) let needsFrameUpdate = !Self.rectApproximatelyEqual(previousFrame, targetFrame) @@ -805,7 +942,6 @@ final class WindowBrowserSlotView: NSView { private func interactionLayerPriority(of view: NSView) -> Int { if view === paneDropTargetView { return 1 } - if view === dropZoneOverlayView { return 2 } return 0 } @@ -817,8 +953,11 @@ final class WindowBrowserSlotView: NSView { if paneDropTargetView.superview !== self { addSubview(paneDropTargetView, positioned: .above, relativeTo: nil) } - if dropZoneOverlayView.superview !== self { - addSubview(dropZoneOverlayView, positioned: .above, relativeTo: nil) + let overlayContainer = overlayContainerView() + if dropZoneOverlayView.superview !== overlayContainer { + attachDropZoneOverlayIfNeeded() + } else if overlayContainer.subviews.last !== dropZoneOverlayView { + overlayContainer.addSubview(dropZoneOverlayView, positioned: .above, relativeTo: nil) } let context = Unmanaged.passUnretained(self).toOpaque() @@ -841,19 +980,13 @@ final class WindowBrowserSlotView: NSView { } private func dropZoneOverlayFrame(for zone: DropZone, in size: CGSize) -> CGRect { - let padding: CGFloat = 4 - switch zone { - case .center: - return CGRect(x: padding, y: padding, width: size.width - padding * 2, height: size.height - padding * 2) - case .left: - return CGRect(x: padding, y: padding, width: size.width / 2 - padding, height: size.height - padding * 2) - case .right: - return CGRect(x: size.width / 2, y: padding, width: size.width / 2 - padding, height: size.height - padding * 2) - case .top: - return CGRect(x: padding, y: size.height / 2, width: size.width - padding * 2, height: size.height / 2 - padding) - case .bottom: - return CGRect(x: padding, y: padding, width: size.width - padding * 2, height: size.height / 2 - padding) - } + let localFrame = BrowserPaneDropRouting.overlayFrame( + for: zone, + in: size, + topChromeHeight: paneTopChromeHeight + ) + guard let superview else { return localFrame } + return superview.convert(localFrame, from: self) } private static func rectApproximatelyEqual(_ lhs: CGRect, _ rhs: CGRect, epsilon: CGFloat = 0.5) -> Bool { @@ -884,6 +1017,9 @@ final class WindowBrowserPortal: NSObject { var zPriority: Int var dropZone: DropZone? var paneDropContext: BrowserPaneDropContext? + var searchOverlay: BrowserPortalSearchOverlayConfiguration? + var paneTopChromeHeight: CGFloat + var transientRecoveryReason: String? var transientRecoveryRetriesRemaining: Int } @@ -1142,10 +1278,14 @@ final class WindowBrowserPortal: NSObject { private func ensureContainerView(for entry: Entry, webView: WKWebView) -> WindowBrowserSlotView { if let existing = entry.containerView { existing.setPaneDropContext(entry.paneDropContext) + existing.setSearchOverlay(entry.searchOverlay) + existing.setPaneTopChromeHeight(entry.paneTopChromeHeight) return existing } let created = WindowBrowserSlotView(frame: .zero) created.setPaneDropContext(entry.paneDropContext) + created.setSearchOverlay(entry.searchOverlay) + created.setPaneTopChromeHeight(entry.paneTopChromeHeight) #if DEBUG dlog( "browser.portal.container.create web=\(browserPortalDebugToken(webView)) " + @@ -1281,6 +1421,25 @@ final class WindowBrowserPortal: NSObject { entry.containerView?.setPaneDropContext(context) } + func updateSearchOverlay( + forWebViewId webViewId: ObjectIdentifier, + configuration: BrowserPortalSearchOverlayConfiguration? + ) { + guard var entry = entriesByWebViewId[webViewId] else { return } + entry.searchOverlay = configuration + entriesByWebViewId[webViewId] = entry + entry.containerView?.setSearchOverlay(configuration) + } + + func updatePaneTopChromeHeight(forWebViewId webViewId: ObjectIdentifier, height: CGFloat) { + guard var entry = entriesByWebViewId[webViewId] else { return } + let resolvedHeight = max(0, height) + guard abs(entry.paneTopChromeHeight - resolvedHeight) > 0.5 else { return } + entry.paneTopChromeHeight = resolvedHeight + entriesByWebViewId[webViewId] = entry + entry.containerView?.setPaneTopChromeHeight(resolvedHeight) + } + func bind(webView: WKWebView, to anchorView: NSView, visibleInUI: Bool, zPriority: Int = 0) { guard ensureInstalled() else { return } @@ -1296,6 +1455,9 @@ final class WindowBrowserPortal: NSObject { zPriority: 0, dropZone: nil, paneDropContext: nil, + searchOverlay: nil, + paneTopChromeHeight: 0, + transientRecoveryReason: nil, transientRecoveryRetriesRemaining: 0 ), webView: webView @@ -1329,6 +1491,9 @@ final class WindowBrowserPortal: NSObject { zPriority: zPriority, dropZone: previousEntry?.dropZone, paneDropContext: previousEntry?.paneDropContext, + searchOverlay: previousEntry?.searchOverlay, + paneTopChromeHeight: previousEntry?.paneTopChromeHeight ?? 0, + transientRecoveryReason: previousEntry?.transientRecoveryReason, transientRecoveryRetriesRemaining: previousEntry?.transientRecoveryRetriesRemaining ?? 0 ) @@ -1446,7 +1611,8 @@ final class WindowBrowserPortal: NSObject { } private func resetTransientRecoveryRetryIfNeeded(forWebViewId webViewId: ObjectIdentifier, entry: inout Entry) { - guard entry.transientRecoveryRetriesRemaining != 0 else { return } + guard entry.transientRecoveryRetriesRemaining != 0 || entry.transientRecoveryReason != nil else { return } + entry.transientRecoveryReason = nil entry.transientRecoveryRetriesRemaining = 0 entriesByWebViewId[webViewId] = entry } @@ -1457,9 +1623,18 @@ final class WindowBrowserPortal: NSObject { webView: WKWebView, reason: String ) -> Bool { - if entry.transientRecoveryRetriesRemaining == 0 { + if entry.transientRecoveryReason != reason { + entry.transientRecoveryReason = reason entry.transientRecoveryRetriesRemaining = Self.transientRecoveryRetryBudget } +#if DEBUG + if entry.transientRecoveryRetriesRemaining <= 0 { + dlog( + "browser.portal.sync.deferRecover.skip web=\(browserPortalDebugToken(webView)) " + + "reason=\(reason) exhausted=1" + ) + } +#endif guard entry.transientRecoveryRetriesRemaining > 0 else { return false } entry.transientRecoveryRetriesRemaining -= 1 @@ -1494,15 +1669,31 @@ final class WindowBrowserPortal: NSObject { } return } - guard let anchorView = entry.anchorView, let window else { - if entry.visibleInUI { - _ = scheduleTransientRecoveryRetryIfNeeded( - forWebViewId: webViewId, - entry: &entry, - webView: webView, - reason: "missingAnchorOrWindow" + func scheduleTransientDetachRecovery(reason: String) -> Bool { + guard entry.visibleInUI else { return false } + let didSchedule = scheduleTransientRecoveryRetryIfNeeded( + forWebViewId: webViewId, + entry: &entry, + webView: webView, + reason: reason + ) + let shouldPreserve = didSchedule && !containerView.isHidden +#if DEBUG + if shouldPreserve { + dlog( + "browser.portal.hidden.deferKeep web=\(browserPortalDebugToken(webView)) " + + "reason=\(reason) frame=\(browserPortalDebugFrame(containerView.frame))" ) - } else { + } +#endif + return shouldPreserve + } + guard let anchorView = entry.anchorView, let window else { + if scheduleTransientDetachRecovery(reason: "missingAnchorOrWindow") { + containerView.setDropZoneOverlay(zone: nil) + return + } + if !entry.visibleInUI { resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) } #if DEBUG @@ -1513,11 +1704,17 @@ final class WindowBrowserPortal: NSObject { ) } #endif + containerView.setPaneTopChromeHeight(0) + containerView.setSearchOverlay(nil) containerView.setDropZoneOverlay(zone: nil) containerView.isHidden = true return } guard anchorView.window === window else { + if scheduleTransientDetachRecovery(reason: "anchorWindowMismatch") { + containerView.setDropZoneOverlay(zone: nil) + return + } #if DEBUG if !containerView.isHidden { dlog( @@ -1527,16 +1724,11 @@ final class WindowBrowserPortal: NSObject { ) } #endif - if entry.visibleInUI { - _ = scheduleTransientRecoveryRetryIfNeeded( - forWebViewId: webViewId, - entry: &entry, - webView: webView, - reason: "anchorWindowMismatch" - ) - } else { + if !entry.visibleInUI { resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) } + containerView.setPaneTopChromeHeight(0) + containerView.setSearchOverlay(nil) containerView.setDropZoneOverlay(zone: nil) containerView.isHidden = true return @@ -1617,6 +1809,7 @@ final class WindowBrowserPortal: NSObject { } else { resetTransientRecoveryRetryIfNeeded(forWebViewId: webViewId, entry: &entry) } + containerView.setSearchOverlay(nil) containerView.setDropZoneOverlay(zone: nil) containerView.isHidden = true if entry.visibleInUI { @@ -1629,6 +1822,7 @@ final class WindowBrowserPortal: NSObject { } else { scheduleDeferredFullSynchronizeAll() } + containerView.setPaneTopChromeHeight(0) return } let oldFrame = containerView.frame @@ -1788,6 +1982,8 @@ final class WindowBrowserPortal: NSObject { #endif containerView.isHidden = false } + containerView.setPaneTopChromeHeight(shouldHide ? 0 : entry.paneTopChromeHeight) + containerView.setSearchOverlay(shouldHide ? nil : entry.searchOverlay) containerView.setDropZoneOverlay(zone: containerView.isHidden ? nil : entry.dropZone) if revealedForDisplay { refreshReasons.append("reveal") @@ -2011,6 +2207,23 @@ enum BrowserWindowPortalRegistry { portal.updatePaneDropContext(forWebViewId: webViewId, context: context) } + static func updateSearchOverlay( + for webView: WKWebView, + configuration: BrowserPortalSearchOverlayConfiguration? + ) { + let webViewId = ObjectIdentifier(webView) + guard let windowId = webViewToWindowId[webViewId], + let portal = portalsByWindowId[windowId] else { return } + portal.updateSearchOverlay(forWebViewId: webViewId, configuration: configuration) + } + + static func updatePaneTopChromeHeight(for webView: WKWebView, height: CGFloat) { + let webViewId = ObjectIdentifier(webView) + guard let windowId = webViewToWindowId[webViewId], + let portal = portalsByWindowId[windowId] else { return } + portal.updatePaneTopChromeHeight(forWebViewId: webViewId, height: height) + } + static func detach(webView: WKWebView) { let webViewId = ObjectIdentifier(webView) guard let windowId = webViewToWindowId.removeValue(forKey: webViewId) else { return } diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 885dd16d..76514838 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1244,6 +1244,15 @@ final class BrowserSearchState: ObservableObject { } } +final class BrowserPortalAnchorView: NSView { + override var acceptsFirstResponder: Bool { false } + override var isOpaque: Bool { false } + + override func hitTest(_ point: NSPoint) -> NSView? { + nil + } +} + @MainActor final class BrowserPanel: Panel, ObservableObject { /// Shared process pool for cookie sharing across all browser panels @@ -1481,6 +1490,7 @@ final class BrowserPanel: Panel, ObservableObject { } } private var searchNeedleCancellable: AnyCancellable? + let portalAnchorView = BrowserPortalAnchorView(frame: .zero) private var webViewCancellables = Set() private var navigationDelegate: BrowserNavigationDelegate? private var uiDelegate: BrowserUIDelegate? diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 73f8e3e5..827771bc 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -230,6 +230,7 @@ struct BrowserPanelView: View { @State private var focusFlashOpacity: Double = 0.0 @State private var focusFlashAnimationGeneration: Int = 0 @State private var omnibarPillFrame: CGRect = .zero + @State private var addressBarHeight: CGFloat = 0 @State private var lastHandledAddressBarFocusRequestId: UUID? @State private var isBrowserThemeMenuPresented = false @State private var ghosttyBackgroundGeneration: Int = 0 @@ -306,6 +307,8 @@ struct BrowserPanelView: View { } var body: some View { + // Layering contract: browser Cmd+F UI is mounted in the portal-hosted AppKit + // container. Rendering it here can hide it behind the portal-hosted WKWebView. VStack(spacing: 0) { addressBar webView @@ -317,17 +320,6 @@ struct BrowserPanelView: View { .padding(FocusFlashPattern.ringInset) .allowsHitTesting(false) } - .overlay { - if let searchState = panel.searchState { - BrowserSearchOverlay( - panelId: panel.id, - searchState: searchState, - onNext: { panel.findNext() }, - onPrevious: { panel.findPrevious() }, - onClose: { panel.hideFind() } - ) - } - } .overlay(alignment: .topLeading) { if addressBarFocused, !omnibarState.suggestions.isEmpty, omnibarPillFrame.width > 0 { OmnibarSuggestionsView( @@ -354,6 +346,9 @@ struct BrowserPanelView: View { .onPreferenceChange(OmnibarPillFramePreferenceKey.self) { frame in omnibarPillFrame = frame } + .onPreferenceChange(BrowserAddressBarHeightPreferenceKey.self) { height in + addressBarHeight = height + } .onReceive(NotificationCenter.default.publisher(for: .webViewDidReceiveClick).filter { [weak panel] note in // Only handle clicks from our own webview. guard let webView = note.object as? CmuxWebView else { return false } @@ -495,6 +490,15 @@ struct BrowserPanelView: View { .padding(.horizontal, 8) .padding(.vertical, addressBarVerticalPadding) .background(browserChromeBackground) + .background { + GeometryReader { geo in + Color.clear + .preference( + key: BrowserAddressBarHeightPreferenceKey.self, + value: geo.size.height + ) + } + } // Keep the omnibar stack above WKWebView so the suggestions popup is visible. .zIndex(1) .environment(\.colorScheme, browserChromeColorScheme) @@ -739,7 +743,17 @@ struct BrowserPanelView: View { shouldFocusWebView: isFocused && !addressBarFocused, isPanelFocused: isFocused, portalZPriority: portalPriority, - paneDropZone: paneDropZone + paneDropZone: paneDropZone, + searchOverlay: panel.searchState.map { searchState in + BrowserPortalSearchOverlayConfiguration( + panelId: panel.id, + searchState: searchState, + onNext: { panel.findNext() }, + onPrevious: { panel.findPrevious() }, + onClose: { panel.hideFind() } + ) + }, + paneTopChromeHeight: addressBarHeight ) // Keep the host stable for normal pane churn, but force a remount when // BrowserPanel replaces its underlying WKWebView after process termination. @@ -1935,6 +1949,14 @@ private struct OmnibarPillFramePreferenceKey: PreferenceKey { } } +private struct BrowserAddressBarHeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = max(value, nextValue()) + } +} + // MARK: - Omnibar State Machine struct OmnibarState: Equatable { @@ -3039,6 +3061,8 @@ struct WebViewRepresentable: NSViewRepresentable { let isPanelFocused: Bool let portalZPriority: Int let paneDropZone: DropZone? + let searchOverlay: BrowserPortalSearchOverlayConfiguration? + let paneTopChromeHeight: CGFloat final class Coordinator { weak var panel: BrowserPanel? @@ -3199,6 +3223,18 @@ struct WebViewRepresentable: NSViewRepresentable { host.onGeometryChanged = nil } + private static func installPortalAnchorView(_ anchorView: NSView, in host: NSView) { + if anchorView.superview !== host { + anchorView.removeFromSuperview() + anchorView.frame = host.bounds + anchorView.translatesAutoresizingMaskIntoConstraints = true + anchorView.autoresizingMask = [.width, .height] + host.addSubview(anchorView) + } else if anchorView.frame != host.bounds { + anchorView.frame = host.bounds + } + } + private func updateUsingWindowPortal(_ nsView: NSView, context: Context, webView: WKWebView) { guard let host = nsView as? HostContainerView else { return } @@ -3210,25 +3246,35 @@ struct WebViewRepresentable: NSViewRepresentable { coordinator.attachGeneration += 1 let generation = coordinator.attachGeneration let paneDropContext = shouldAttachWebView ? currentPaneDropContext() : nil + let activeSearchOverlay = shouldAttachWebView ? searchOverlay : nil + let portalAnchorView = panel.portalAnchorView + Self.installPortalAnchorView(portalAnchorView, in: host) - host.onDidMoveToWindow = { [weak host, weak webView, weak coordinator] in - guard let host, let webView, let coordinator else { return } + host.onDidMoveToWindow = { [weak host, weak webView, weak coordinator, weak portalAnchorView] in + guard let host, let webView, let coordinator, let portalAnchorView else { return } guard coordinator.attachGeneration == generation else { return } + Self.installPortalAnchorView(portalAnchorView, in: host) guard host.window != nil else { return } BrowserWindowPortalRegistry.bind( webView: webView, - to: host, + to: portalAnchorView, visibleInUI: coordinator.desiredPortalVisibleInUI, zPriority: coordinator.desiredPortalZPriority ) + BrowserWindowPortalRegistry.updatePaneTopChromeHeight( + for: webView, + height: coordinator.desiredPortalVisibleInUI ? paneTopChromeHeight : 0 + ) BrowserWindowPortalRegistry.updatePaneDropContext(for: webView, context: paneDropContext) + BrowserWindowPortalRegistry.updateSearchOverlay(for: webView, configuration: activeSearchOverlay) coordinator.lastPortalHostId = ObjectIdentifier(host) } - host.onGeometryChanged = { [weak host, weak coordinator] in - guard let host, let coordinator else { return } + host.onGeometryChanged = { [weak host, weak coordinator, weak portalAnchorView] in + guard let host, let coordinator, let portalAnchorView else { return } guard coordinator.attachGeneration == generation else { return } guard coordinator.lastPortalHostId == ObjectIdentifier(host) else { return } - BrowserWindowPortalRegistry.synchronizeForAnchor(host) + Self.installPortalAnchorView(portalAnchorView, in: host) + BrowserWindowPortalRegistry.synchronizeForAnchor(portalAnchorView) } if !shouldAttachWebView { @@ -3245,15 +3291,21 @@ struct WebViewRepresentable: NSViewRepresentable { previousVisible != shouldAttachWebView || previousZPriority != portalZPriority if shouldBindNow { + Self.installPortalAnchorView(portalAnchorView, in: host) BrowserWindowPortalRegistry.bind( webView: webView, - to: host, + to: portalAnchorView, visibleInUI: coordinator.desiredPortalVisibleInUI, zPriority: coordinator.desiredPortalZPriority ) coordinator.lastPortalHostId = hostId } - BrowserWindowPortalRegistry.synchronizeForAnchor(host) + BrowserWindowPortalRegistry.updatePaneTopChromeHeight( + for: webView, + height: shouldAttachWebView ? paneTopChromeHeight : 0 + ) + BrowserWindowPortalRegistry.updateSearchOverlay(for: webView, configuration: activeSearchOverlay) + BrowserWindowPortalRegistry.synchronizeForAnchor(portalAnchorView) } else { // Bind is deferred until host moves into a window. Keep the current // portal entry's desired state in sync so stale callbacks cannot keep @@ -3269,10 +3321,15 @@ struct WebViewRepresentable: NSViewRepresentable { for: webView, zone: shouldAttachWebView ? paneDropZone : nil ) + BrowserWindowPortalRegistry.updatePaneTopChromeHeight( + for: webView, + height: shouldAttachWebView ? paneTopChromeHeight : 0 + ) BrowserWindowPortalRegistry.updatePaneDropContext( for: webView, context: paneDropContext ) + BrowserWindowPortalRegistry.updateSearchOverlay(for: webView, configuration: activeSearchOverlay) panel.restoreDeveloperToolsAfterAttachIfNeeded() @@ -3391,7 +3448,9 @@ struct WebViewRepresentable: NSViewRepresentable { // rearrangement. Do not detach the portal-hosted WKWebView here; explicit detach // still happens on real web view replacement and panel teardown. BrowserWindowPortalRegistry.updateDropZoneOverlay(for: webView, zone: nil) + BrowserWindowPortalRegistry.updatePaneTopChromeHeight(for: webView, height: 0) BrowserWindowPortalRegistry.updatePaneDropContext(for: webView, context: nil) + BrowserWindowPortalRegistry.updateSearchOverlay(for: webView, configuration: nil) coordinator.lastPortalHostId = nil } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index e5f4b40f..950589bc 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2450,7 +2450,9 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { shouldFocusWebView: false, isPanelFocused: true, portalZPriority: 0, - paneDropZone: nil + paneDropZone: nil, + searchOverlay: nil, + paneTopChromeHeight: 0 ) let coordinator = representable.makeCoordinator() coordinator.webView = panel.webView @@ -2487,7 +2489,9 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { shouldFocusWebView: false, isPanelFocused: true, portalZPriority: 0, - paneDropZone: nil + paneDropZone: nil, + searchOverlay: nil, + paneTopChromeHeight: 0 ) let coordinator = representable.makeCoordinator() coordinator.webView = panel.webView @@ -7776,6 +7780,27 @@ final class BrowserPaneDropRoutingTests: XCTestCase { ) } + func testTopChromeHeightPushesTopSplitThresholdIntoWebView() { + let size = CGSize(width: 240, height: 180) + + XCTAssertEqual( + BrowserPaneDropRouting.zone( + for: CGPoint(x: size.width * 0.5, y: 110), + in: size, + topChromeHeight: 36 + ), + .center + ) + XCTAssertEqual( + BrowserPaneDropRouting.zone( + for: CGPoint(x: size.width * 0.5, y: 150), + in: size, + topChromeHeight: 36 + ), + .top + ) + } + func testHitTestingCapturesOnlyForRelevantDragEvents() { XCTAssertTrue( BrowserPaneDropTargetView.shouldCaptureHitTesting( @@ -7873,22 +7898,24 @@ final class WindowBrowserSlotViewTests: XCTestCase { } func testDropZoneOverlayStaysAboveContentWithoutBlockingHits() { - let slot = WindowBrowserSlotView(frame: NSRect(x: 0, y: 0, width: 200, height: 100)) + let container = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 100)) + let slot = WindowBrowserSlotView(frame: container.bounds) + container.addSubview(slot) let child = CapturingView(frame: slot.bounds) child.autoresizingMask = [.width, .height] slot.addSubview(child) slot.setDropZoneOverlay(zone: .right) - slot.layoutSubtreeIfNeeded() + container.layoutSubtreeIfNeeded() - guard let overlay = slot.subviews.first(where: { - $0 !== child && String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") + guard let overlay = container.subviews.first(where: { + $0 !== slot && String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") }) else { XCTFail("Expected browser slot drop-zone overlay") return } - XCTAssertTrue(slot.subviews.last === overlay, "Overlay should stay above the hosted web view") + XCTAssertTrue(container.subviews.last === overlay, "Overlay should stay above the hosted web view") XCTAssertFalse(overlay.isHidden) XCTAssertEqual(overlay.frame.origin.x, 100, accuracy: 0.5) XCTAssertEqual(overlay.frame.origin.y, 4, accuracy: 0.5) @@ -7901,6 +7928,35 @@ final class WindowBrowserSlotViewTests: XCTestCase { advanceAnimations() XCTAssertTrue(overlay.isHidden, "Clearing the drop zone should hide the overlay") } + + func testTopDropZoneOverlayUsesFullBrowserContentHeight() { + let container = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 100)) + let slot = WindowBrowserSlotView(frame: container.bounds) + container.addSubview(slot) + + slot.setPaneTopChromeHeight(20) + slot.setDropZoneOverlay(zone: .top) + container.layoutSubtreeIfNeeded() + + guard let overlay = container.subviews.first(where: { + String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") + }) else { + XCTFail("Expected browser slot drop-zone overlay") + return + } + + XCTAssertFalse(overlay.isHidden) + XCTAssertEqual(overlay.frame.origin.x, 4, accuracy: 0.5) + XCTAssertEqual(overlay.frame.origin.y, 60, accuracy: 0.5) + XCTAssertEqual(overlay.frame.size.width, 192, accuracy: 0.5) + XCTAssertEqual(overlay.frame.size.height, 56, accuracy: 0.5) + XCTAssertGreaterThan(overlay.frame.maxY, slot.frame.maxY) + XCTAssertEqual(slot.layer?.masksToBounds, true) + + slot.setDropZoneOverlay(zone: nil) + advanceAnimations() + XCTAssertEqual(slot.layer?.masksToBounds, true) + } } @MainActor From 21bb31dcfb76c90e16c987052d1102aa5ebaebaa Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:58:38 -0800 Subject: [PATCH 147/232] Avoid blocking notify regression socket replies --- Sources/TerminalController.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 323cd891..7059092f 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -11632,11 +11632,17 @@ class TerminalController { .replacingOccurrences(of: "\\r", with: "\r") .replacingOccurrences(of: "\\t", with: "\t") - if let surface = terminalPanel.surface.surface { - sendSocketText(unescaped, surface: surface) - } else { - terminalPanel.sendText(unescaped) - terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() + // This DEBUG-only command is used by UI tests to enqueue shell work in an + // existing workspace. Return once the input is queued on main so a long + // payload does not hold the control-socket response open in CI. + DispatchQueue.main.async { [weak self] in + guard let self else { return } + if let surface = terminalPanel.surface.surface { + self.sendSocketText(unescaped, surface: surface) + } else { + terminalPanel.sendText(unescaped) + terminalPanel.surface.requestBackgroundSurfaceStartIfNeeded() + } } success = true } From 8a05c7d1da7a02a44f34b2fd572b38750e3480eb Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:08:09 -0800 Subject: [PATCH 148/232] Decouple command palette search from typing --- Sources/ContentView.swift | 258 ++++++++++++++---- .../CommandPaletteSearchEngineTests.swift | 26 ++ 2 files changed, 236 insertions(+), 48 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index d240da98..56713bff 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1261,10 +1261,17 @@ struct ContentView: View { @State private var commandPaletteScrollTargetIndex: Int? @State private var commandPaletteScrollTargetAnchor: UnitPoint? @State private var commandPaletteRestoreFocusTarget: CommandPaletteRestoreFocusTarget? - @State private var commandPaletteSearchCorpus: [CommandPaletteSearchCorpusEntry] = [] + @State private var commandPaletteSearchCorpus: [CommandPaletteSearchCorpusEntry] = [] + @State private var commandPaletteSearchCommandsByID: [String: CommandPaletteCommand] = [:] @State private var cachedCommandPaletteResults: [CommandPaletteSearchResult] = [] @State private var cachedCommandPaletteScope: CommandPaletteListScope? @State private var cachedCommandPaletteFingerprint: Int? + @State private var commandPaletteSearchTask: Task? + @State private var commandPaletteSearchRequestID: UInt64 = 0 + @State private var commandPaletteResolvedSearchRequestID: UInt64 = 0 + @State private var isCommandPaletteSearchPending = false + @State private var commandPaletteDeferredSubmitRequestID: UInt64? + @State private var commandPaletteResultsRevision: UInt64 = 0 @State private var commandPaletteUsageHistoryByCommandId: [String: CommandPaletteUsageEntry] = [:] @AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey) private var commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus @@ -1377,7 +1384,7 @@ struct ContentView: View { } } - private struct CommandPaletteUsageEntry: Codable { + private struct CommandPaletteUsageEntry: Codable, Sendable { var useCount: Int var lastUsedAt: TimeInterval } @@ -2734,6 +2741,7 @@ struct ContentView: View { private var commandPaletteCommandListView: some View { let visibleResults = cachedCommandPaletteResults + let isSearchPending = isCommandPaletteSearchPending let selectedIndex = commandPaletteSelectedIndex(resultCount: visibleResults.count) let commandPaletteListMaxHeight: CGFloat = 450 let commandPaletteRowHeight: CGFloat = 24 @@ -2750,7 +2758,7 @@ struct ContentView: View { .tint(Color(nsColor: sidebarActiveForegroundNSColor(opacity: 1.0))) .focused($isCommandPaletteSearchFocused) .onSubmit { - runSelectedCommandPaletteResult(visibleResults: visibleResults) + runSelectedCommandPaletteResult() } .backport.onKeyPress(.downArrow) { _ in moveCommandPaletteSelection(by: 1) @@ -2773,6 +2781,10 @@ struct ContentView: View { handleCommandPaletteControlNavigationKey(modifiers: modifiers, delta: -1) } + if isSearchPending { + ProgressView() + .controlSize(.small) + } } .padding(.horizontal, 9) .padding(.vertical, 7) @@ -2782,12 +2794,22 @@ struct ContentView: View { ScrollView { LazyVStack(spacing: 0) { if visibleResults.isEmpty { - Text(commandPaletteEmptyStateText) - .font(.system(size: 13, weight: .regular)) - .foregroundStyle(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 12) + if isSearchPending { + HStack { + Spacer() + ProgressView() + .controlSize(.small) + Spacer() + } .padding(.vertical, 12) + } else { + Text(commandPaletteEmptyStateText) + .font(.system(size: 13, weight: .regular)) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 12) + .padding(.vertical, 12) + } } else { ForEach(Array(visibleResults.enumerated()), id: \.element.id) { index, result in let isSelected = index == selectedIndex @@ -2797,7 +2819,7 @@ struct ContentView: View { : (isHovered ? Color.primary.opacity(0.08) : .clear) Button { - runCommandPaletteCommand(result.command) + runCommandPaletteResult(commandID: result.id) } label: { HStack(spacing: 8) { commandPaletteHighlightedTitleText( @@ -2832,6 +2854,7 @@ struct ContentView: View { .contentShape(Rectangle()) } .buttonStyle(.plain) + .disabled(isSearchPending) .id(index) .onHover { hovering in if hovering { @@ -2872,7 +2895,6 @@ struct ContentView: View { } .onAppear { commandPaletteHoveredResultIndex = nil - refreshCommandPaletteResults(forceSearchCorpusRefresh: true) updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) resetCommandPaletteSearchFocus() } @@ -2881,14 +2903,14 @@ struct ContentView: View { commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil - refreshCommandPaletteResults() + scheduleCommandPaletteResultsRefresh() syncCommandPaletteDebugStateForObservedWindow() } .onChange(of: commandPaletteCurrentSearchFingerprint) { _ in - refreshCommandPaletteResults(forceSearchCorpusRefresh: true) + scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: true) syncCommandPaletteDebugStateForObservedWindow() } - .onChange(of: cachedCommandPaletteResults.count) { _ in + .onChange(of: commandPaletteResultsRevision) { _ in commandPaletteSelectedResultIndex = commandPaletteSelectedIndex(resultCount: cachedCommandPaletteResults.count) updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) if let hoveredIndex = commandPaletteHoveredResultIndex, hoveredIndex >= cachedCommandPaletteResults.count { @@ -3058,9 +3080,10 @@ struct ContentView: View { } let entries = commandPaletteEntries(for: scope) + commandPaletteSearchCommandsByID = Dictionary(uniqueKeysWithValues: entries.map { ($0.id, $0) }) commandPaletteSearchCorpus = entries.map { entry in CommandPaletteSearchCorpusEntry( - payload: entry, + payload: entry.id, rank: entry.rank, title: entry.title, searchableTexts: entry.searchableTexts @@ -3070,20 +3093,75 @@ struct ContentView: View { cachedCommandPaletteFingerprint = fingerprint } - private func refreshCommandPaletteResults(forceSearchCorpusRefresh: Bool = false) { + private func cancelCommandPaletteSearch() { + commandPaletteSearchTask?.cancel() + commandPaletteSearchTask = nil + } + + private func scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: Bool = false) { refreshCommandPaletteSearchCorpus(force: forceSearchCorpusRefresh) - cachedCommandPaletteResults = CommandPaletteSearchEngine.search( - entries: commandPaletteSearchCorpus, - query: commandPaletteQueryForMatching - ) { command, queryIsEmpty in - commandPaletteHistoryBoost(for: command.id, queryIsEmpty: queryIsEmpty) - } - .map { result in - CommandPaletteSearchResult( - command: result.payload, - score: result.score, - titleMatchIndices: result.titleMatchIndices + + commandPaletteSearchRequestID &+= 1 + let requestID = commandPaletteSearchRequestID + commandPaletteDeferredSubmitRequestID = nil + isCommandPaletteSearchPending = true + let query = commandPaletteQueryForMatching + let scope = commandPaletteListScope + let fingerprint = cachedCommandPaletteFingerprint + let searchCorpus = commandPaletteSearchCorpus + let usageHistory = commandPaletteUsageHistoryByCommandId + let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty + let historyTimestamp = Date().timeIntervalSince1970 + + cancelCommandPaletteSearch() + commandPaletteSearchTask = Task.detached(priority: .userInitiated) { + let results = CommandPaletteSearchEngine.search( + entries: searchCorpus, + query: query, + historyBoost: { commandId, _ in + Self.commandPaletteHistoryBoost( + for: commandId, + queryIsEmpty: queryIsEmpty, + history: usageHistory, + now: historyTimestamp + ) + }, + shouldCancel: { Task.isCancelled } ) + + guard !Task.isCancelled else { return } + + await MainActor.run { + guard commandPaletteSearchRequestID == requestID, + isCommandPalettePresented, + commandPaletteListScope == scope, + commandPaletteQueryForMatching == query, + cachedCommandPaletteFingerprint == fingerprint else { + return + } + + cachedCommandPaletteResults = results.compactMap { result in + guard let command = commandPaletteSearchCommandsByID[result.payload] else { return nil } + return CommandPaletteSearchResult( + command: command, + score: result.score, + titleMatchIndices: result.titleMatchIndices + ) + } + commandPaletteResolvedSearchRequestID = requestID + isCommandPaletteSearchPending = false + let shouldRunDeferredSubmit = commandPaletteDeferredSubmitRequestID == requestID + if shouldRunDeferredSubmit { + commandPaletteDeferredSubmitRequestID = nil + } + commandPaletteResultsRevision &+= 1 + if commandPaletteSearchRequestID == requestID { + commandPaletteSearchTask = nil + } + if shouldRunDeferredSubmit { + runSelectedCommandPaletteResult() + } + } } } @@ -4562,8 +4640,27 @@ struct ContentView: View { return .handled } - private func runSelectedCommandPaletteResult(visibleResults: [CommandPaletteSearchResult]? = nil) { - let visibleResults = visibleResults ?? cachedCommandPaletteResults + private var commandPaletteHasCurrentResolvedResults: Bool { + !isCommandPaletteSearchPending && commandPaletteResolvedSearchRequestID == commandPaletteSearchRequestID + } + + private func runCommandPaletteResult(commandID: String) { + guard commandPaletteHasCurrentResolvedResults, + let command = cachedCommandPaletteResults.first(where: { $0.id == commandID })?.command else { + return + } + runCommandPaletteCommand(command) + } + + private func runSelectedCommandPaletteResult() { + guard commandPaletteHasCurrentResolvedResults else { + if isCommandPalettePresented { + commandPaletteDeferredSubmitRequestID = commandPaletteSearchRequestID + } + return + } + + let visibleResults = cachedCommandPaletteResults guard !visibleResults.isEmpty else { NSSound.beep() return @@ -4726,13 +4823,15 @@ struct ContentView: View { commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil - refreshCommandPaletteResults(forceSearchCorpusRefresh: true) + scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: true) resetCommandPaletteSearchFocus() syncCommandPaletteDebugStateForObservedWindow() } private func dismissCommandPalette(restoreFocus: Bool = true) { let focusTarget = commandPaletteRestoreFocusTarget + cancelCommandPaletteSearch() + commandPaletteSearchRequestID &+= 1 isCommandPalettePresented = false commandPaletteMode = .commands commandPaletteQuery = "" @@ -4745,9 +4844,14 @@ struct ContentView: View { isCommandPaletteRenameFocused = false commandPaletteRestoreFocusTarget = nil commandPaletteSearchCorpus = [] + commandPaletteSearchCommandsByID = [:] cachedCommandPaletteResults = [] cachedCommandPaletteScope = nil cachedCommandPaletteFingerprint = nil + commandPaletteResolvedSearchRequestID = commandPaletteSearchRequestID + isCommandPaletteSearchPending = false + commandPaletteDeferredSubmitRequestID = nil + commandPaletteResultsRevision &+= 1 if let window = observedWindow { _ = window.makeFirstResponder(nil) } @@ -4910,10 +5014,14 @@ struct ContentView: View { persistCommandPaletteUsageHistory(history) } - private func commandPaletteHistoryBoost(for commandId: String, queryIsEmpty: Bool) -> Int { - guard let entry = commandPaletteUsageHistoryByCommandId[commandId] else { return 0 } + nonisolated private static func commandPaletteHistoryBoost( + for commandId: String, + queryIsEmpty: Bool, + history: [String: CommandPaletteUsageEntry], + now: TimeInterval + ) -> Int { + guard let entry = history[commandId] else { return 0 } - let now = Date().timeIntervalSince1970 let ageDays = max(0, now - entry.lastUsedAt) / 86_400 let recencyBoost = max(0, 320 - Int(ageDays * 20)) let countBoost = min(180, entry.useCount * 12) @@ -4922,6 +5030,15 @@ struct ContentView: View { return queryIsEmpty ? totalBoost : max(0, totalBoost / 3) } + private func commandPaletteHistoryBoost(for commandId: String, queryIsEmpty: Bool) -> Int { + Self.commandPaletteHistoryBoost( + for: commandId, + queryIsEmpty: queryIsEmpty, + history: commandPaletteUsageHistoryByCommandId, + now: Date().timeIntervalSince1970 + ) + } + private func beginRenameWorkspaceFlow() { guard let workspace = tabManager.selectedWorkspace else { NSSound.beep() @@ -5710,7 +5827,7 @@ enum CommandPaletteFuzzyMatcher { } } -struct CommandPaletteSearchCorpusEntry { +struct CommandPaletteSearchCorpusEntry: Sendable where Payload: Sendable { let payload: Payload let rank: Int let title: String @@ -5726,7 +5843,7 @@ struct CommandPaletteSearchCorpusEntry { } } -struct CommandPaletteSearchCorpusResult { +struct CommandPaletteSearchCorpusResult: Sendable where Payload: Sendable { let payload: Payload let rank: Int let title: String @@ -5735,42 +5852,87 @@ struct CommandPaletteSearchCorpusResult { } enum CommandPaletteSearchEngine { - static func search( + static func search( entries: [CommandPaletteSearchCorpusEntry], query: String, historyBoost: (Payload, Bool) -> Int + ) -> [CommandPaletteSearchCorpusResult] { + search( + entries: entries, + query: query, + historyBoost: historyBoost, + shouldCancel: nil + ) + } + + static func search( + entries: [CommandPaletteSearchCorpusEntry], + query: String, + historyBoost: (Payload, Bool) -> Int, + shouldCancel: @escaping () -> Bool + ) -> [CommandPaletteSearchCorpusResult] { + search( + entries: entries, + query: query, + historyBoost: historyBoost, + shouldCancel: Optional(shouldCancel) + ) + } + + private static func search( + entries: [CommandPaletteSearchCorpusEntry], + query: String, + historyBoost: (Payload, Bool) -> Int, + shouldCancel: (() -> Bool)? ) -> [CommandPaletteSearchCorpusResult] { let preparedQuery = CommandPaletteFuzzyMatcher.preparedQuery(query) let queryIsEmpty = preparedQuery.isEmpty + var results: [CommandPaletteSearchCorpusResult] = [] + results.reserveCapacity(entries.count) - let results: [CommandPaletteSearchCorpusResult] = queryIsEmpty - ? entries.map { entry in - CommandPaletteSearchCorpusResult( + func shouldCancelSearch(at index: Int) -> Bool { + guard let shouldCancel else { return false } + return index % 16 == 0 && shouldCancel() + } + + if queryIsEmpty { + for (index, entry) in entries.enumerated() { + if shouldCancelSearch(at: index) { return [] } + results.append( + CommandPaletteSearchCorpusResult( payload: entry.payload, rank: entry.rank, title: entry.title, score: historyBoost(entry.payload, true), titleMatchIndices: [] ) + ) } - : entries.compactMap { entry in + } else { + for (index, entry) in entries.enumerated() { + if shouldCancelSearch(at: index) { return [] } guard let fuzzyScore = CommandPaletteFuzzyMatcher.score( preparedQuery: preparedQuery, normalizedCandidates: entry.normalizedSearchableTexts ) else { - return nil + continue } - return CommandPaletteSearchCorpusResult( - payload: entry.payload, - rank: entry.rank, - title: entry.title, - score: fuzzyScore + historyBoost(entry.payload, false), - titleMatchIndices: CommandPaletteFuzzyMatcher.matchCharacterIndices( - preparedQuery: preparedQuery, - candidate: entry.title + results.append( + CommandPaletteSearchCorpusResult( + payload: entry.payload, + rank: entry.rank, + title: entry.title, + score: fuzzyScore + historyBoost(entry.payload, false), + titleMatchIndices: CommandPaletteFuzzyMatcher.matchCharacterIndices( + preparedQuery: preparedQuery, + candidate: entry.title + ) ) ) } + } + + if shouldCancel?() == true { return [] } return results.sorted { lhs, rhs in if lhs.score != rhs.score { return lhs.score > rhs.score } diff --git a/cmuxTests/CommandPaletteSearchEngineTests.swift b/cmuxTests/CommandPaletteSearchEngineTests.swift index 337d27b2..182f536d 100644 --- a/cmuxTests/CommandPaletteSearchEngineTests.swift +++ b/cmuxTests/CommandPaletteSearchEngineTests.swift @@ -193,6 +193,32 @@ final class CommandPaletteSearchEngineTests: XCTestCase { } } + func testSearchCancellationReturnsNoResults() { + let entries = makeCommandEntries(count: 512) + let corpus = entries.map { entry in + CommandPaletteSearchCorpusEntry( + payload: entry.id, + rank: entry.rank, + title: entry.title, + searchableTexts: entry.searchableTexts + ) + } + var cancellationChecks = 0 + + let results = CommandPaletteSearchEngine.search( + entries: corpus, + query: "rename" + ) { _, _ in + 0 + } shouldCancel: { + cancellationChecks += 1 + return cancellationChecks >= 4 + } + + XCTAssertTrue(results.isEmpty) + XCTAssertGreaterThanOrEqual(cancellationChecks, 4) + } + func testCommandSearchBenchmarkBeatsLegacyPipeline() { let entries = makeCommandEntries(count: 900) let corpus = entries.map { entry in From 5f82a1ae2a9802e6c917b46001a53dde691ecd44 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:18:14 -0800 Subject: [PATCH 149/232] Run notify regression through a real terminal --- Sources/AppDelegate.swift | 6 +- .../MultiWindowNotificationsUITests.swift | 67 +++++++++++++------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 093f2a6c..6b4acc59 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5741,7 +5741,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let contexts = Array(self.mainWindowContexts.values) guard let window2 = contexts.first(where: { $0.windowId != window1.windowId }) else { return } guard let tabId2 = window2.tabManager.selectedTabId ?? window2.tabManager.tabs.first?.id else { return } - waitForSurfaceId(on: window2.tabManager, tabId: tabId2) { [weak self] surfaceId2 in + waitForSurfaceId(on: window1.tabManager, tabId: tabId1) { [weak self] surfaceId1 in + guard let self else { return } + waitForSurfaceId(on: window2.tabManager, tabId: tabId2) { [weak self] surfaceId2 in guard let self else { return } guard let store = self.notificationStore else { return } @@ -5767,6 +5769,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "window2InitialSidebarSelection": "notifications", "tabId1": tabId1.uuidString, "tabId2": tabId2.uuidString, + "surfaceId1": surfaceId1.uuidString, "surfaceId2": surfaceId2.uuidString, "notifId1": notif1?.id.uuidString ?? "", "notifId2": notif2?.id.uuidString ?? "", @@ -5775,6 +5778,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent ], at: path) self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) } + } } } } diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index c1c00575..43696454 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -228,6 +228,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTFail("Missing source workspace id") return } + guard let window1Id = setup["window1Id"], !window1Id.isEmpty else { + XCTFail("Missing source window id") + return + } + guard let sourceSurfaceId = setup["surfaceId1"], !sourceSurfaceId.isEmpty else { + XCTFail("Missing source surface id") + return + } if let expectedSocketPath = setup["socketExpectedPath"], !expectedSocketPath.isEmpty { socketPath = expectedSocketPath } @@ -263,10 +271,14 @@ final class MultiWindowNotificationsUITests: XCTestCase { let commandStderrPath = FileManager.default.temporaryDirectory .appendingPathComponent("cmux-ui-test-notify-\(commandResultStem).stderr") .path + let commandScriptPath = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-ui-test-notify-\(commandResultStem).sh") + .path defer { try? FileManager.default.removeItem(atPath: commandStatusPath) try? FileManager.default.removeItem(atPath: commandStdoutPath) try? FileManager.default.removeItem(atPath: commandStderrPath) + try? FileManager.default.removeItem(atPath: commandScriptPath) } guard let bundledCLIPath = resolveCmuxCLIPaths(strategy: .bundledOnly).first else { @@ -274,32 +286,32 @@ final class MultiWindowNotificationsUITests: XCTestCase { return } - let notifyCommand = [ - "rm -f \(shellSingleQuote(commandStatusPath)) \(shellSingleQuote(commandStdoutPath)) \(shellSingleQuote(commandStderrPath));", - "(sleep 1;", - "\(shellSingleQuote(bundledCLIPath))", - "--socket \(shellSingleQuote(socketPath))", - "notify", - "--workspace \(shellSingleQuote(tabId2))", - "--surface \(shellSingleQuote(surfaceId))", - "--title \(shellSingleQuote(title))", - "--subtitle \(shellSingleQuote("ui-test"))", - "--body \(shellSingleQuote("focus-regression"))", - ">\(shellSingleQuote(commandStdoutPath))", - "2>\(shellSingleQuote(commandStderrPath));", - "printf '%s' $? >\(shellSingleQuote(commandStatusPath))) >/dev/null 2>&1 &" - ].joined(separator: " ") - let sendWorkspaceResponse = socketCommand( - "send_workspace \(tabId1) \(notifyCommand)\\n", - responseTimeout: 8.0 - ) - guard sendWorkspaceResponse == "OK" else { + let notifyScript = [ + "#!/bin/sh", + "sleep 1", + "rm -f \(shellSingleQuote(commandStatusPath)) \(shellSingleQuote(commandStdoutPath)) \(shellSingleQuote(commandStderrPath))", + "\(shellSingleQuote(bundledCLIPath)) --socket \(shellSingleQuote(socketPath)) notify --workspace \(shellSingleQuote(tabId2)) --surface \(shellSingleQuote(surfaceId)) --title \(shellSingleQuote(title)) --subtitle \(shellSingleQuote("ui-test")) --body \(shellSingleQuote("focus-regression")) >\(shellSingleQuote(commandStdoutPath)) 2>\(shellSingleQuote(commandStderrPath))", + "printf '%s' $? >\(shellSingleQuote(commandStatusPath))" + ].joined(separator: "\n") + do { + try notifyScript.write(toFile: commandScriptPath, atomically: true, encoding: .utf8) + } catch { XCTFail( - "Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1). " + - "response=\(sendWorkspaceResponse ?? "")" + "Failed to write delayed bundled `cmux notify` script. " + + "path=\(commandScriptPath) error=\(error)" ) return } + XCTAssertEqual(socketCommand("focus_window \(window1Id)"), "OK", "Expected source window to be focusable") + XCTAssertEqual(socketCommand("select_workspace \(tabId1)"), "OK", "Expected source workspace to be selectable") + XCTAssertEqual(socketCommand("focus_surface \(sourceSurfaceId)"), "OK", "Expected source terminal to be focusable") + XCTAssertTrue( + waitForTerminalFocus(surfaceId: sourceSurfaceId, timeout: 4.0), + "Expected source terminal surface to own first responder before typing" + ) + + app.typeText("sh \(commandScriptPath)") + app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: []) let finder = XCUIApplication(bundleIdentifier: "com.apple.finder") finder.activate() @@ -447,6 +459,17 @@ final class MultiWindowNotificationsUITests: XCTestCase { return socketCommand("ping") ?? lastResponse } + private func waitForTerminalFocus(surfaceId: String, timeout: TimeInterval) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if socketCommand("is_terminal_focused \(surfaceId)") == "true" { + return true + } + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + return socketCommand("is_terminal_focused \(surfaceId)") == "true" + } + private func waitForCmuxPing(timeout: TimeInterval) -> (stdout: String?, stderr: String?) { let deadline = Date().addingTimeInterval(timeout) var lastStdout: String? From 46e810fef21301e0acc4a16a50e6ceba306be94f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:18:48 -0800 Subject: [PATCH 150/232] Move rectApproximatelyEqual out of #if DEBUG in HostContainerView (#1000) The function was defined inside a #if DEBUG block but called from non-DEBUG code, breaking Release builds (nightly CI). --- Sources/Panels/BrowserPanelView.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 61138911..7c5f0c74 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -3137,13 +3137,6 @@ struct WebViewRepresentable: NSViewRepresentable { String(format: "%.1f,%.1f %.1fx%.1f", rect.origin.x, rect.origin.y, rect.width, rect.height) } - private static func rectApproximatelyEqual(_ lhs: NSRect, _ rhs: NSRect, epsilon: CGFloat = 0.5) -> Bool { - abs(lhs.origin.x - rhs.origin.x) <= epsilon && - abs(lhs.origin.y - rhs.origin.y) <= epsilon && - abs(lhs.width - rhs.width) <= epsilon && - abs(lhs.height - rhs.height) <= epsilon - } - private func debugLogHostedInspectorFrames( stage: String, point: NSPoint? = nil, @@ -3193,6 +3186,13 @@ struct WebViewRepresentable: NSViewRepresentable { } #endif + private static func rectApproximatelyEqual(_ lhs: NSRect, _ rhs: NSRect, epsilon: CGFloat = 0.5) -> Bool { + abs(lhs.origin.x - rhs.origin.x) <= epsilon && + abs(lhs.origin.y - rhs.origin.y) <= epsilon && + abs(lhs.width - rhs.width) <= epsilon && + abs(lhs.height - rhs.height) <= epsilon + } + override func viewDidMoveToWindow() { super.viewDidMoveToWindow() if window == nil { From 6d9504435509ac7d9030b03de79e50c8ce0ed578 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 22:32:16 -0800 Subject: [PATCH 151/232] Hide stale browser portals during workspace handoff --- Sources/BrowserWindowPortal.swift | 33 ++++++++----- Sources/ContentView.swift | 7 +-- Sources/Workspace.swift | 13 +++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 48 +++++++++++++++++-- 4 files changed, 82 insertions(+), 19 deletions(-) diff --git a/Sources/BrowserWindowPortal.swift b/Sources/BrowserWindowPortal.swift index 32d54478..3b11f816 100644 --- a/Sources/BrowserWindowPortal.swift +++ b/Sources/BrowserWindowPortal.swift @@ -1407,6 +1407,14 @@ final class WindowBrowserPortal: NSObject { entriesByWebViewId[webViewId] = entry } + func hideWebView(withId webViewId: ObjectIdentifier, source: String = "externalHide") { + guard var entry = entriesByWebViewId[webViewId] else { return } + entry.visibleInUI = false + entry.zPriority = 0 + entriesByWebViewId[webViewId] = entry + synchronizeWebView(withId: webViewId, source: source) + } + func updateDropZoneOverlay(forWebViewId webViewId: ObjectIdentifier, zone: DropZone?) { guard var entry = entriesByWebViewId[webViewId] else { return } entry.dropZone = zone @@ -1671,26 +1679,19 @@ final class WindowBrowserPortal: NSObject { } func scheduleTransientDetachRecovery(reason: String) -> Bool { guard entry.visibleInUI else { return false } - let didSchedule = scheduleTransientRecoveryRetryIfNeeded( + return scheduleTransientRecoveryRetryIfNeeded( forWebViewId: webViewId, entry: &entry, webView: webView, reason: reason ) - let shouldPreserve = didSchedule && !containerView.isHidden -#if DEBUG - if shouldPreserve { - dlog( - "browser.portal.hidden.deferKeep web=\(browserPortalDebugToken(webView)) " + - "reason=\(reason) frame=\(browserPortalDebugFrame(containerView.frame))" - ) - } -#endif - return shouldPreserve } guard let anchorView = entry.anchorView, let window else { if scheduleTransientDetachRecovery(reason: "missingAnchorOrWindow") { + containerView.setPaneTopChromeHeight(0) + containerView.setSearchOverlay(nil) containerView.setDropZoneOverlay(zone: nil) + containerView.isHidden = true return } if !entry.visibleInUI { @@ -1712,7 +1713,10 @@ final class WindowBrowserPortal: NSObject { } guard anchorView.window === window else { if scheduleTransientDetachRecovery(reason: "anchorWindowMismatch") { + containerView.setPaneTopChromeHeight(0) + containerView.setSearchOverlay(nil) containerView.setDropZoneOverlay(zone: nil) + containerView.isHidden = true return } #if DEBUG @@ -2193,6 +2197,13 @@ enum BrowserWindowPortalRegistry { portal.updateEntryVisibility(forWebViewId: webViewId, visibleInUI: visibleInUI, zPriority: zPriority) } + static func hide(webView: WKWebView, source: String = "externalHide") { + let webViewId = ObjectIdentifier(webView) + guard let windowId = webViewToWindowId[webViewId], + let portal = portalsByWindowId[windowId] else { return } + portal.hideWebView(withId: webViewId, source: source) + } + static func updateDropZoneOverlay(for webView: WKWebView, zone: DropZone?) { let webViewId = ObjectIdentifier(webView) guard let windowId = webViewToWindowId[webViewId], diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index f886eb22..84e4a13a 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -2662,13 +2662,14 @@ struct ContentView: View { workspaceHandoffFallbackTask = nil let retiring = retiringWorkspaceId - // Hide terminal portal views for the retiring workspace BEFORE clearing + // Hide portal-hosted views for the retiring workspace BEFORE clearing // retiringWorkspaceId. Once cleared, reconcileMountedWorkspaceIds unmounts // the workspace — but dismantleNSView intentionally doesn't hide portal views - // (to avoid blackouts during transient bonsplit dismantles). Hiding here - // prevents stale portal-hosted terminals from covering browser panes. + // during transient rebuilds. Hiding here prevents stale terminal/browser + // portals from covering the newly selected workspace. if let retiring, let workspace = tabManager.tabs.first(where: { $0.id == retiring }) { workspace.hideAllTerminalPortalViews() + workspace.hideAllBrowserPortalViews() } retiringWorkspaceId = nil diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index a4a870ea..2e5c3bd9 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -3251,6 +3251,19 @@ final class Workspace: Identifiable, ObservableObject { } } + /// Hide all browser portal views for this workspace. + /// Called before the workspace is unmounted so a portal-hosted WKWebView + /// cannot remain visible after this workspace stops being selected. + func hideAllBrowserPortalViews() { + for panel in panels.values { + guard let browser = panel as? BrowserPanel else { continue } + BrowserWindowPortalRegistry.hide( + webView: browser.webView, + source: "workspaceRetire" + ) + } + } + // MARK: - Utility /// Create a new terminal panel (used when replacing the last panel) diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 950589bc..76482978 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -9376,8 +9376,11 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { } private func dropZoneOverlay(in slot: WindowBrowserSlotView, excluding webView: WKWebView) -> NSView? { - slot.subviews.first(where: { - $0 !== webView && String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") + let candidates = slot.subviews + (slot.superview?.subviews ?? []) + return candidates.first(where: { + $0 !== slot && + $0 !== webView && + String(describing: type(of: $0)).contains("BrowserDropZoneOverlayView") }) } @@ -9688,9 +9691,9 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { portal.updateDropZoneOverlay(forWebViewId: ObjectIdentifier(webView), zone: .right) slot.layoutSubtreeIfNeeded() XCTAssertFalse(overlay.isHidden) - XCTAssertTrue(slot.subviews.last === overlay, "Overlay should remain above the hosted web view") - XCTAssertEqual(overlay.frame.origin.x, 110, accuracy: 0.5) - XCTAssertEqual(overlay.frame.origin.y, 4, accuracy: 0.5) + XCTAssertTrue(slot.superview?.subviews.last === overlay, "Overlay should remain above the hosted web view") + XCTAssertEqual(overlay.frame.origin.x, slot.frame.origin.x + 110, accuracy: 0.5) + XCTAssertEqual(overlay.frame.origin.y, slot.frame.origin.y + 4, accuracy: 0.5) XCTAssertEqual(overlay.frame.size.width, 106, accuracy: 0.5) XCTAssertEqual(overlay.frame.size.height, 152, accuracy: 0.5) @@ -9827,6 +9830,41 @@ final class BrowserWindowPortalLifecycleTests: XCTestCase { BrowserWindowPortalRegistry.detach(webView: webView) XCTAssertNil(webView.superview) } + + func testRegistryHideKeepsPortalHostedWebViewAttachedButHidden() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + realizeWindowLayout(window) + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let anchor = NSView(frame: NSRect(x: 20, y: 20, width: 180, height: 120)) + contentView.addSubview(anchor) + let webView = CmuxWebView(frame: .zero, configuration: WKWebViewConfiguration()) + + BrowserWindowPortalRegistry.bind(webView: webView, to: anchor, visibleInUI: true) + BrowserWindowPortalRegistry.synchronizeForAnchor(anchor) + advanceAnimations() + + guard let slot = webView.superview as? WindowBrowserSlotView else { + XCTFail("Expected browser slot") + return + } + XCTAssertFalse(slot.isHidden) + + BrowserWindowPortalRegistry.hide(webView: webView, source: "unitTest") + advanceAnimations() + + XCTAssertTrue(webView.superview === slot, "Hiding should preserve the hosted WKWebView attachment") + XCTAssertTrue(slot.isHidden, "Hiding should immediately hide the existing portal slot") + } } @MainActor From e04787789effbb74a1a755df89ef6de8bbf83e85 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:38:10 -0800 Subject: [PATCH 152/232] Fix markdown panel text click focus (#991) * Add markdown click regression test * Fix markdown panel click focus * Preserve markdown text selection * Make markdown observer tests deterministic --- Sources/Panels/MarkdownPanelView.swift | 74 ++++++++++++- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 104 ++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/Sources/Panels/MarkdownPanelView.swift b/Sources/Panels/MarkdownPanelView.swift index b3b7a971..b96325db 100644 --- a/Sources/Panels/MarkdownPanelView.swift +++ b/Sources/Panels/MarkdownPanelView.swift @@ -1,3 +1,4 @@ +import AppKit import SwiftUI import MarkdownUI @@ -30,9 +31,12 @@ struct MarkdownPanelView: View { .padding(FocusFlashPattern.ringInset) .allowsHitTesting(false) } - .contentShape(Rectangle()) - .onTapGesture { - onRequestPanelFocus() + .overlay { + if isVisibleInUI { + // Observe left-clicks without intercepting them so markdown text + // selection and link activation continue to use the native path. + MarkdownPointerObserver(onPointerDown: onRequestPanelFocus) + } } .onChange(of: panel.focusFlashToken) { _ in triggerFocusFlashAnimation() @@ -283,3 +287,67 @@ struct MarkdownPanelView: View { } } } + +private struct MarkdownPointerObserver: NSViewRepresentable { + let onPointerDown: () -> Void + + func makeNSView(context: Context) -> MarkdownPanelPointerObserverView { + let view = MarkdownPanelPointerObserverView() + view.onPointerDown = onPointerDown + return view + } + + func updateNSView(_ nsView: MarkdownPanelPointerObserverView, context: Context) { + nsView.onPointerDown = onPointerDown + } +} + +final class MarkdownPanelPointerObserverView: NSView { + var onPointerDown: (() -> Void)? + private var eventMonitor: Any? + + override var mouseDownCanMoveWindow: Bool { false } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + installEventMonitorIfNeeded() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + if let eventMonitor { + NSEvent.removeMonitor(eventMonitor) + } + } + + override func hitTest(_ point: NSPoint) -> NSView? { + nil + } + + func shouldHandle(_ event: NSEvent) -> Bool { + guard event.type == .leftMouseDown, + let window, + event.window === window, + !isHiddenOrHasHiddenAncestor else { return false } + let point = convert(event.locationInWindow, from: nil) + return bounds.contains(point) + } + + func handleEventIfNeeded(_ event: NSEvent) -> NSEvent { + guard shouldHandle(event) else { return event } + DispatchQueue.main.async { [weak self] in + self?.onPointerDown?() + } + return event + } + + private func installEventMonitorIfNeeded() { + guard eventMonitor == nil else { return } + eventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown]) { [weak self] event in + self?.handleEventIfNeeded(event) ?? event + } + } +} diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 71d7ed96..3226755f 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -10830,6 +10830,110 @@ final class FileDropOverlayViewTests: XCTestCase { } } +@MainActor +final class MarkdownPanelPointerObserverViewTests: XCTestCase { + private func makeWindow() -> NSWindow { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 320, height: 180), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + window.makeKeyAndOrderFront(nil) + window.displayIfNeeded() + window.contentView?.layoutSubtreeIfNeeded() + return window + } + + private func makeMouseEvent( + type: NSEvent.EventType, + location: NSPoint, + window: NSWindow, + eventNumber: Int = 1 + ) -> NSEvent { + guard let event = NSEvent.mouseEvent( + with: type, + location: location, + modifierFlags: [], + timestamp: ProcessInfo.processInfo.systemUptime, + windowNumber: window.windowNumber, + context: nil, + eventNumber: eventNumber, + clickCount: 1, + pressure: 1.0 + ) else { + fatalError("Expected to create mouse event") + } + return event + } + + func testObserverTriggersFocusForVisibleLeftClickInsideBounds() { + let window = makeWindow() + defer { window.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let overlay = MarkdownPanelPointerObserverView(frame: contentView.bounds) + overlay.autoresizingMask = [.width, .height] + let focusExpectation = expectation(description: "observer forwards focus callback") + var pointerDownCount = 0 + overlay.onPointerDown = { + pointerDownCount += 1 + focusExpectation.fulfill() + } + contentView.addSubview(overlay) + + _ = overlay.handleEventIfNeeded( + makeMouseEvent(type: .leftMouseDown, location: NSPoint(x: 60, y: 60), window: window) + ) + wait(for: [focusExpectation], timeout: 1.0) + + XCTAssertEqual(pointerDownCount, 1) + } + + func testObserverIgnoresOutsideOrForeignWindowClicks() { + let window = makeWindow() + defer { window.orderOut(nil) } + let otherWindow = makeWindow() + defer { otherWindow.orderOut(nil) } + guard let contentView = window.contentView else { + XCTFail("Expected content view") + return + } + + let overlay = MarkdownPanelPointerObserverView(frame: contentView.bounds) + overlay.autoresizingMask = [.width, .height] + let noFocusExpectation = expectation(description: "observer ignores invalid clicks") + noFocusExpectation.isInverted = true + var pointerDownCount = 0 + overlay.onPointerDown = { + pointerDownCount += 1 + noFocusExpectation.fulfill() + } + contentView.addSubview(overlay) + + _ = overlay.handleEventIfNeeded( + makeMouseEvent(type: .leftMouseDown, location: NSPoint(x: 400, y: 400), window: window) + ) + _ = overlay.handleEventIfNeeded( + makeMouseEvent(type: .leftMouseDown, location: NSPoint(x: 60, y: 60), window: otherWindow, eventNumber: 2) + ) + _ = overlay.handleEventIfNeeded( + makeMouseEvent(type: .leftMouseDragged, location: NSPoint(x: 60, y: 60), window: window, eventNumber: 3) + ) + wait(for: [noFocusExpectation], timeout: 0.1) + + XCTAssertEqual(pointerDownCount, 0) + } + + func testObserverDoesNotParticipateInHitTesting() { + let overlay = MarkdownPanelPointerObserverView(frame: NSRect(x: 0, y: 0, width: 200, height: 100)) + XCTAssertNil(overlay.hitTest(NSPoint(x: 40, y: 30))) + } +} + final class BrowserLinkOpenSettingsTests: XCTestCase { private var suiteName: String! private var defaults: UserDefaults! From ae4781ef6646fd17aea832d8515b4db8d4ae17e8 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:39:26 -0800 Subject: [PATCH 153/232] Prepare notify regression source terminal in app setup --- Sources/AppDelegate.swift | 72 +++++++++++++++++++ .../MultiWindowNotificationsUITests.swift | 37 +++++----- 2 files changed, 88 insertions(+), 21 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 6b4acc59..1b979eac 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -5776,6 +5776,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "expectedLatestWindowId": window1.windowId.uuidString, "expectedLatestTabId": tabId1.uuidString, ], at: path) + self.prepareMultiWindowNotificationSourceTerminalIfNeeded( + at: path, + windowId: window1.windowId, + tabManager: window1.tabManager, + tabId: tabId1, + surfaceId: surfaceId1 + ) self.publishMultiWindowNotificationSocketStateIfNeeded(at: path) } } @@ -5783,6 +5790,71 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + private func prepareMultiWindowNotificationSourceTerminalIfNeeded( + at path: String, + windowId: UUID, + tabManager: TabManager, + tabId: UUID, + surfaceId: UUID + ) { + let env = ProcessInfo.processInfo.environment + guard env["CMUX_UI_TEST_NOTIFY_SOURCE_TERMINAL_READY"] == "1" else { return } + + writeMultiWindowNotificationTestData([ + "sourceTerminalReady": "pending", + "sourceTerminalFocusFailure": "", + ], at: path) + + let deadline = Date().addingTimeInterval(8.0) + + func publish(ready: Bool, failure: String = "") { + writeMultiWindowNotificationTestData([ + "sourceTerminalReady": ready ? "1" : "0", + "sourceTerminalFocusFailure": failure, + ], at: path) + } + + func poll() { + guard let workspace = tabManager.tabs.first(where: { $0.id == tabId }) else { + publish(ready: false, failure: "workspace_missing") + return + } + guard let terminalPanel = workspace.terminalPanel(for: surfaceId) else { + publish(ready: false, failure: "terminal_missing") + return + } + + let isWindowFrontmost = { + guard let window = self.mainWindow(for: windowId) else { return false } + return NSApp.keyWindow === window || NSApp.mainWindow === window + }() + if isWindowFrontmost && terminalPanel.hostedView.isSurfaceViewFirstResponder() { + publish(ready: true) + return + } + + guard Date() < deadline else { + publish( + ready: false, + failure: isWindowFrontmost ? "terminal_not_first_responder" : "window_not_frontmost" + ) + return + } + + _ = self.focusMainWindow(windowId: windowId) + if let tab = tabManager.tabs.first(where: { $0.id == tabId }) { + tabManager.selectTab(tab) + tabManager.focusSurface(tabId: tabId, surfaceId: surfaceId) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + poll() + } + } + + poll() + } + private func publishMultiWindowNotificationSocketStateIfNeeded(at path: String) { let env = ProcessInfo.processInfo.environment guard env["CMUX_UI_TEST_SOCKET_SANITY"] == "1" else { return } diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 43696454..632ad44d 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -199,6 +199,7 @@ final class MultiWindowNotificationsUITests: XCTestCase { app.launchEnvironment["CMUX_SOCKET_MODE"] = "allowAll" app.launchEnvironment["CMUX_SOCKET_ENABLE"] = "1" app.launchEnvironment["CMUX_UI_TEST_SOCKET_SANITY"] = "1" + app.launchEnvironment["CMUX_UI_TEST_NOTIFY_SOURCE_TERMINAL_READY"] = "1" app.launchEnvironment["CMUX_UI_TEST_ENABLE_DUPLICATE_LAUNCH_OBSERVER"] = "1" app.launchEnvironment["CMUX_TAG"] = launchTag app.launch() @@ -211,9 +212,15 @@ final class MultiWindowNotificationsUITests: XCTestCase { let tabId2 = data["tabId2"] ?? "" let surfaceId2 = data["surfaceId2"] ?? "" let socketReady = data["socketReady"] ?? "" - return !tabId2.isEmpty && !surfaceId2.isEmpty && !socketReady.isEmpty && socketReady != "pending" + let sourceTerminalReady = data["sourceTerminalReady"] ?? "" + return !tabId2.isEmpty && + !surfaceId2.isEmpty && + !socketReady.isEmpty && + socketReady != "pending" && + !sourceTerminalReady.isEmpty && + sourceTerminalReady != "pending" }, - "Expected multi-window notification setup data and socket readiness" + "Expected multi-window notification setup data, socket readiness, and source terminal focus" ) guard let setup = loadData() else { @@ -224,18 +231,6 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTFail("Missing setup workspace id") return } - guard let tabId1 = setup["tabId1"], !tabId1.isEmpty else { - XCTFail("Missing source workspace id") - return - } - guard let window1Id = setup["window1Id"], !window1Id.isEmpty else { - XCTFail("Missing source window id") - return - } - guard let sourceSurfaceId = setup["surfaceId1"], !sourceSurfaceId.isEmpty else { - XCTFail("Missing source surface id") - return - } if let expectedSocketPath = setup["socketExpectedPath"], !expectedSocketPath.isEmpty { socketPath = expectedSocketPath } @@ -257,6 +252,13 @@ final class MultiWindowNotificationsUITests: XCTestCase { XCTFail("Missing target surface id for workspace \(tabId2)") return } + guard setup["sourceTerminalReady"] == "1" else { + XCTFail( + "Expected source terminal to be focused before typing. " + + "failure=\(setup["sourceTerminalFocusFailure"] ?? "")" + ) + return + } XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0)) @@ -302,13 +304,6 @@ final class MultiWindowNotificationsUITests: XCTestCase { ) return } - XCTAssertEqual(socketCommand("focus_window \(window1Id)"), "OK", "Expected source window to be focusable") - XCTAssertEqual(socketCommand("select_workspace \(tabId1)"), "OK", "Expected source workspace to be selectable") - XCTAssertEqual(socketCommand("focus_surface \(sourceSurfaceId)"), "OK", "Expected source terminal to be focusable") - XCTAssertTrue( - waitForTerminalFocus(surfaceId: sourceSurfaceId, timeout: 4.0), - "Expected source terminal surface to own first responder before typing" - ) app.typeText("sh \(commandScriptPath)") app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: []) From 6993c8ceeffdd0ec2ec2042ecc0f85ed8642d3b1 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:53:37 -0800 Subject: [PATCH 154/232] Allow notify_target across windows --- Sources/TerminalController.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 7059092f..af7faec9 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -10583,7 +10583,13 @@ class TerminalController { var result = "OK" DispatchQueue.main.sync { - guard let tab = resolveTab(from: tabArg, tabManager: tabManager) else { + let tab: Tab? + if let tabId = UUID(uuidString: tabArg) { + tab = tabForSidebarMutation(id: tabId) + } else { + tab = resolveTab(from: tabArg, tabManager: tabManager) + } + guard let tab else { result = "ERROR: Tab not found" return } From 38a89df65b7e973fc30420b4516b152e8ae6d900 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 22:54:26 -0800 Subject: [PATCH 155/232] Fix stale browser web view test initializer --- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 41f18fbc..67004deb 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2534,7 +2534,6 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { let representable = WebViewRepresentable( panel: panel, - browserSearchState: nil, shouldAttachWebView: true, shouldFocusWebView: false, isPanelFocused: true, @@ -2574,7 +2573,6 @@ final class BrowserDeveloperToolsVisibilityPersistenceTests: XCTestCase { let representable = WebViewRepresentable( panel: panel, - browserSearchState: nil, shouldAttachWebView: true, shouldFocusWebView: false, isPanelFocused: true, From 5041796eb28de0ba0dd02a160011542b5ae804a3 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:58:49 -0800 Subject: [PATCH 156/232] Polish feedback help menu copy (#1003) --- Resources/Localizable.xcstrings | 12 +++++------ Sources/ContentView.swift | 38 +++++++++++++++++++++++++++++---- web/app/api/feedback/route.ts | 2 +- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index d7943f25..d022b0f2 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -726,13 +726,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "Up to 10 images. Large images will be optimized before sending." + "value": "Up to 10 images." } }, "ja": { "stringUnit": { "state": "translated", - "value": "画像は最大10枚まで添付できます。大きな画像は送信前に最適化されます。" + "value": "画像は最大10枚まで添付できます。" } } } @@ -981,13 +981,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "A human will read this! You can also reach us at founders@manaflow.com." + "value": "You can also reach us at founders@manaflow.com." } }, "ja": { "stringUnit": { "state": "translated", - "value": "人間がこれを読みます。founders@manaflow.com 宛てに直接ご連絡いただくこともできます。" + "value": "founders@manaflow.com 宛てに直接ご連絡いただくこともできます。" } } } @@ -1049,13 +1049,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "A human will read this! You can also reach us at founders@manaflow.com." + "value": "You can also reach us at founders@manaflow.com." } }, "ja": { "stringUnit": { "state": "translated", - "value": "人間がこれを読みます。founders@manaflow.com 宛てに直接ご連絡いただくこともできます。" + "value": "founders@manaflow.com 宛てに直接ご連絡いただくこともできます。" } } } diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index fba50959..ee26a149 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -6069,7 +6069,7 @@ enum DevBuildBannerDebugSettings { private enum FeedbackComposerSettings { static let storedEmailKey = "sidebarHelpFeedbackEmail" static let endpointEnvironmentKey = "CMUX_FEEDBACK_API_URL" - static let defaultEndpoint = "https://cmux.dev/api/feedback" + static let defaultEndpoint = "https://www.cmux.dev/api/feedback" static let foundersEmail = "founders@manaflow.com" static let maxMessageLength = 4_000 static let maxAttachmentCount = 10 @@ -7044,7 +7044,7 @@ private struct SidebarFeedbackComposerSheet: View { Text( String( localized: "sidebar.help.feedback.successBody", - defaultValue: "A human will read this! You can also reach us at founders@manaflow.com." + defaultValue: "You can also reach us at founders@manaflow.com." ) ) .font(.system(size: 12)) @@ -7124,7 +7124,7 @@ private struct SidebarFeedbackComposerSheet: View { Text( String( localized: "sidebar.help.feedback.attachmentsHint", - defaultValue: "Up to 10 images. Large images will be optimized before sending." + defaultValue: "Up to 10 images." ) ) .font(.system(size: 11)) @@ -7358,7 +7358,7 @@ private struct SidebarFeedbackComposerSheet: View { localized: "sidebar.help.feedback.rateLimited", defaultValue: "Too many feedback attempts. Please try again later." ) - case 503: + case 500...599: return String( localized: "sidebar.help.feedback.endpointError", defaultValue: "Feedback is unavailable right now. Email founders@manaflow.com instead." @@ -7381,11 +7381,19 @@ private struct SidebarHelpMenuButton: View { private let helpTitle = String(localized: "sidebar.help.button", defaultValue: "Help") private let buttonSize: CGFloat = 22 private let iconSize: CGFloat = 11 + @AppStorage(KeyboardShortcutSettings.Action.sendFeedback.defaultsKey) private var sendFeedbackShortcutData = Data() let onSendFeedback: () -> Void @State private var isPopoverPresented = false + private var sendFeedbackShortcutHint: String { + decodeShortcut( + from: sendFeedbackShortcutData, + fallback: KeyboardShortcutSettings.Action.sendFeedback.defaultShortcut + ).displayString + } + var body: some View { Button { isPopoverPresented.toggle() @@ -7414,6 +7422,7 @@ private struct SidebarHelpMenuButton: View { action: .sendFeedback, accessibilityIdentifier: "SidebarHelpMenuOptionSendFeedback", isExternalLink: false, + shortcutHint: sendFeedbackShortcutHint, trailingSystemImage: "bubble.left.and.text.bubble.right" ) helpOptionButton( @@ -7470,6 +7479,7 @@ private struct SidebarHelpMenuButton: View { action: SidebarHelpMenuAction, accessibilityIdentifier: String, isExternalLink: Bool, + shortcutHint: String? = nil, trailingSystemImage: String? = nil ) -> some View { Button { @@ -7480,6 +7490,9 @@ private struct SidebarHelpMenuButton: View { Text(title) .font(.system(size: 12)) Spacer(minLength: 0) + if let shortcutHint { + helpOptionShortcutHint(text: shortcutHint) + } if let trailingSystemImage { helpOptionTrailingIcon(systemName: trailingSystemImage) } @@ -7495,6 +7508,15 @@ private struct SidebarHelpMenuButton: View { .accessibilityIdentifier(accessibilityIdentifier) } + private func helpOptionShortcutHint(text: String) -> some View { + Text(text) + .lineLimit(1) + .fixedSize(horizontal: true, vertical: false) + .font(.system(size: 10, weight: .regular, design: .rounded)) + .monospacedDigit() + .foregroundStyle(Color(nsColor: .secondaryLabelColor)) + } + private func helpOptionTrailingIcon(systemName: String, size: CGFloat = 13) -> some View { Image(systemName: systemName) .resizable() @@ -7537,6 +7559,14 @@ private struct SidebarHelpMenuButton: View { onSendFeedback() } } + + private func decodeShortcut(from data: Data, fallback: StoredShortcut) -> StoredShortcut { + guard !data.isEmpty, + let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else { + return fallback + } + return shortcut + } } private struct SidebarFooterIconButtonStyle: ButtonStyle { diff --git a/web/app/api/feedback/route.ts b/web/app/api/feedback/route.ts index 9b3d85b3..b121bb69 100644 --- a/web/app/api/feedback/route.ts +++ b/web/app/api/feedback/route.ts @@ -103,7 +103,7 @@ export async function POST(request: Request) { const resend = new Resend(feedbackConfig.resendApiKey); const { error } = await resend.emails.send({ - from: `cmux feedback <${feedbackConfig.fromEmail}>`, + from: `Manaflow <${feedbackConfig.fromEmail}>`, to: [feedbackRecipient], replyTo: email, subject, From 0285cd1f51abdef4930c9a929b2b3e85140998fe Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 5 Mar 2026 23:09:45 -0800 Subject: [PATCH 157/232] Address PR review feedback --- Sources/ContentView.swift | 9 +++- Sources/TabManager.swift | 42 +++++++++++++++---- ...w_workspace_external_git_branch_refresh.py | 12 +++++- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index b974081b..2dcd5c89 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -2590,6 +2590,11 @@ struct ContentView: View { case completed(reason: String) } + private enum BackgroundWorkspacePrimePolicy { + static let timeoutSeconds: TimeInterval = 2.0 + static let pollIntervalNanoseconds: UInt64 = 50_000_000 + } + private func primeBackgroundWorkspaceIfNeeded(workspaceId: UUID) async { let shouldPrime = await MainActor.run { tabManager.pendingBackgroundWorkspaceLoadIds.contains(workspaceId) @@ -2601,7 +2606,7 @@ struct ContentView: View { dlog("workspace.backgroundPrime.start workspace=\(workspaceId.uuidString.prefix(5))") #endif - let timeout = Date().addingTimeInterval(2.0) + let timeout = Date().addingTimeInterval(BackgroundWorkspacePrimePolicy.timeoutSeconds) while !Task.isCancelled { let state = await MainActor.run { stepBackgroundWorkspacePrime(workspaceId: workspaceId) @@ -2609,7 +2614,7 @@ struct ContentView: View { switch state { case .pending: if Date() < timeout { - try? await Task.sleep(nanoseconds: 50_000_000) + try? await Task.sleep(nanoseconds: BackgroundWorkspacePrimePolicy.pollIntervalNanoseconds) continue } await MainActor.run { diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 37b07947..64863659 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -635,6 +635,7 @@ class TabManager: ObservableObject { qos: .utility ) private var initialWorkspaceGitProbeGenerationByWorkspace: [UUID: UUID] = [:] + private var initialWorkspaceGitProbeTimersByWorkspace: [UUID: [DispatchSourceTimer]] = [:] // Recent tab history for back/forward navigation (like browser history) private var tabHistory: [UUID] = [] @@ -870,6 +871,7 @@ class TabManager: ObservableObject { ) { let normalizedDirectory = normalizeDirectory(directory) let generation = UUID() + cancelInitialWorkspaceGitProbeTimers(workspaceId: workspaceId) initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] = generation #if DEBUG @@ -880,9 +882,12 @@ class TabManager: ObservableObject { #endif let delays = Self.initialWorkspaceGitProbeDelays + var timers: [DispatchSourceTimer] = [] for (index, delay) in delays.enumerated() { let isLastAttempt = index == delays.count - 1 - initialWorkspaceGitProbeQueue.asyncAfter(deadline: .now() + delay) { + let timer = DispatchSource.makeTimerSource(queue: initialWorkspaceGitProbeQueue) + timer.schedule(deadline: .now() + delay, repeating: .never) + timer.setEventHandler { [weak self] in let snapshot = Self.initialWorkspaceGitMetadataSnapshot(for: normalizedDirectory) Task { @MainActor [weak self] in self?.applyInitialWorkspaceGitMetadataSnapshot( @@ -895,7 +900,25 @@ class TabManager: ObservableObject { ) } } + timers.append(timer) + timer.resume() } + initialWorkspaceGitProbeTimersByWorkspace[workspaceId] = timers + } + + private func cancelInitialWorkspaceGitProbeTimers(workspaceId: UUID) { + guard let timers = initialWorkspaceGitProbeTimersByWorkspace.removeValue(forKey: workspaceId) else { + return + } + for timer in timers { + timer.setEventHandler {} + timer.cancel() + } + } + + private func clearInitialWorkspaceGitProbe(workspaceId: UUID) { + initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + cancelInitialWorkspaceGitProbeTimers(workspaceId: workspaceId) } private func applyInitialWorkspaceGitMetadataSnapshot( @@ -909,17 +932,17 @@ class TabManager: ObservableObject { defer { if isLastAttempt, initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation { - initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + clearInitialWorkspaceGitProbe(workspaceId: workspaceId) } } guard initialWorkspaceGitProbeGenerationByWorkspace[workspaceId] == generation else { return } guard let workspace = tabs.first(where: { $0.id == workspaceId }) else { - initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + clearInitialWorkspaceGitProbe(workspaceId: workspaceId) return } guard workspace.panels[panelId] != nil else { - initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + clearInitialWorkspaceGitProbe(workspaceId: workspaceId) return } @@ -927,7 +950,7 @@ class TabManager: ObservableObject { workspace.panelDirectories[panelId] ?? workspace.currentDirectory ) if let currentDirectory, currentDirectory != expectedDirectory { - initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspaceId) + clearInitialWorkspaceGitProbe(workspaceId: workspaceId) #if DEBUG dlog( "workspace.gitProbe.skip workspace=\(workspaceId.uuidString.prefix(5)) " + @@ -980,7 +1003,7 @@ class TabManager: ObservableObject { process.executableURL = URL(fileURLWithPath: "/usr/bin/env") process.arguments = ["git", "-C", directory] + arguments process.standardOutput = stdout - process.standardError = Pipe() + process.standardError = FileHandle.nullDevice do { try process.run() @@ -988,12 +1011,13 @@ class TabManager: ObservableObject { return nil } + // Drain stdout while the subprocess is active so large repos cannot fill the pipe buffer. + let data = stdout.fileHandleForReading.readDataToEndOfFile() process.waitUntilExit() guard process.terminationStatus == 0 else { return nil } - let data = stdout.fileHandleForReading.readDataToEndOfFile() return String(data: data, encoding: .utf8) } @@ -1196,7 +1220,7 @@ class TabManager: ObservableObject { guard tabs.count > 1 else { return } guard let index = tabs.firstIndex(where: { $0.id == workspace.id }) else { return } sentryBreadcrumb("workspace.close", data: ["tabCount": tabs.count - 1]) - initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: workspace.id) + clearInitialWorkspaceGitProbe(workspaceId: workspace.id) AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: workspace.id) unwireClosedBrowserTracking(for: workspace) @@ -1218,7 +1242,7 @@ class TabManager: ObservableObject { @discardableResult func detachWorkspace(tabId: UUID) -> Workspace? { guard let index = tabs.firstIndex(where: { $0.id == tabId }) else { return nil } - initialWorkspaceGitProbeGenerationByWorkspace.removeValue(forKey: tabId) + clearInitialWorkspaceGitProbe(workspaceId: tabId) let removed = tabs.remove(at: index) unwireClosedBrowserTracking(for: removed) diff --git a/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py b/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py index 87625710..9e83ee0f 100644 --- a/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py +++ b/tests_v2/test_cli_new_workspace_external_git_branch_refresh.py @@ -5,6 +5,7 @@ from __future__ import annotations import glob import os +import re import shutil import subprocess import sys @@ -16,7 +17,16 @@ sys.path.insert(0, str(Path(__file__).parent)) from cmux import cmux, cmuxError -SOCKET_PATH = os.environ.get("CMUX_SOCKET", "/tmp/cmux-debug.sock") +def _resolve_socket_path() -> str: + socket_path = os.environ.get("CMUX_SOCKET", "").strip() + if not socket_path: + raise cmuxError("CMUX_SOCKET is required (expected /tmp/cmux-debug-.sock)") + if not re.fullmatch(r"/tmp/cmux-debug-[^/]+\.sock", socket_path): + raise cmuxError(f"CMUX_SOCKET must be a tagged debug socket, got: {socket_path!r}") + return socket_path + + +SOCKET_PATH = _resolve_socket_path() def _must(cond: bool, msg: str) -> None: From c37ace5deb6257bce85fcf6b0c844d07a8fa62ac Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:26:05 -0800 Subject: [PATCH 158/232] Cache Swift packages across CI runs (#998) * Cache Swift packages across CI runs Add actions/cache for the SPM cloned source packages directory so subsequent runs skip fetching Sparkle, sentry-cocoa, swift-markdown-ui, posthog-ios, and NetworkImage from GitHub each time. - nightly/release: replace the no-op SwiftPM cache step with actions/cache + -clonedSourcePackagesDirPath on xcodebuild - ci/ci-macos-compat/test-e2e: add actions/cache before the existing resolve step, stop deleting the cache dir each run * Include runner in test-e2e cache key Consistent with ci-macos-compat.yml which uses matrix.os in the key. --- .github/workflows/ci-macos-compat.yml | 8 +++++++- .github/workflows/ci.yml | 16 ++++++++++++++-- .github/workflows/nightly.yml | 17 +++++++++-------- .github/workflows/release.yml | 12 +++++++++++- .github/workflows/test-e2e.yml | 8 +++++++- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci-macos-compat.yml b/.github/workflows/ci-macos-compat.yml index 463e5a56..a51a96c0 100644 --- a/.github/workflows/ci-macos-compat.yml +++ b/.github/workflows/ci-macos-compat.yml @@ -73,11 +73,17 @@ jobs: - name: Clean DerivedData run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache Swift packages + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .ci-source-packages + key: spm-${{ matrix.os }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm-${{ matrix.os }}- + - name: Resolve Swift packages run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - rm -rf "$SOURCE_PACKAGES_DIR" mkdir -p "$SOURCE_PACKAGES_DIR" for attempt in 1 2 3; do diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c106a18a..6c1de0eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,11 +101,17 @@ jobs: # Remove stale build cache to avoid incremental build errors rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache Swift packages + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .ci-source-packages + key: spm-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm- + - name: Resolve Swift packages run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - rm -rf "$SOURCE_PACKAGES_DIR" mkdir -p "$SOURCE_PACKAGES_DIR" for attempt in 1 2 3; do @@ -226,11 +232,17 @@ jobs: - name: Clean DerivedData run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache Swift packages + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .ci-source-packages + key: spm-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm- + - name: Resolve Swift packages run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - rm -rf "$SOURCE_PACKAGES_DIR" mkdir -p "$SOURCE_PACKAGES_DIR" for attempt in 1 2 3; do diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index da320e73..3b9a0866 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -142,13 +142,12 @@ jobs: rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework - - name: Configure SwiftPM cache - run: | - set -euo pipefail - CACHE_DIR="${RUNNER_TEMP}/swiftpm-cache/${GITHUB_RUN_ID}" - rm -rf "$CACHE_DIR" - mkdir -p "$CACHE_DIR" - echo "SWIFTPM_CACHE_PATH=$CACHE_DIR" >> "$GITHUB_ENV" + - name: Cache Swift packages + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .spm-cache + key: spm-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm- - name: Derive Sparkle public key from private key env: @@ -164,7 +163,9 @@ jobs: - name: Build app (Release) run: | - xcodebuild -scheme cmux -configuration Release -derivedDataPath build CODE_SIGNING_ALLOWED=NO ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon-Nightly build + xcodebuild -scheme cmux -configuration Release -derivedDataPath build \ + -clonedSourcePackagesDirPath .spm-cache \ + CODE_SIGNING_ALLOWED=NO ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon-Nightly build - name: Inject nightly identity and metadata run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57bfb154..200f003a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,6 +129,14 @@ jobs: rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework + - name: Cache Swift packages + if: steps.guard_release_assets.outputs.skip_all != 'true' + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .spm-cache + key: spm-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm- + - name: Derive Sparkle public key from private key if: steps.guard_release_assets.outputs.skip_all != 'true' env: @@ -145,7 +153,9 @@ jobs: - name: Build app (Release) if: steps.guard_release_assets.outputs.skip_all != 'true' run: | - xcodebuild -scheme cmux -configuration Release -derivedDataPath build CODE_SIGNING_ALLOWED=NO build + xcodebuild -scheme cmux -configuration Release -derivedDataPath build \ + -clonedSourcePackagesDirPath .spm-cache \ + CODE_SIGNING_ALLOWED=NO build - name: Inject Sparkle keys into Info.plist if: steps.guard_release_assets.outputs.skip_all != 'true' diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 48b5e4de..23d595c7 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -151,11 +151,17 @@ jobs: - name: Clean DerivedData run: rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* + - name: Cache Swift packages + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .ci-source-packages + key: spm-${{ inputs.runner || 'macos-15' }}-${{ hashFiles('GhosttyTabs.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + restore-keys: spm-${{ inputs.runner || 'macos-15' }}- + - name: Resolve Swift packages run: | set -euo pipefail SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - rm -rf "$SOURCE_PACKAGES_DIR" mkdir -p "$SOURCE_PACKAGES_DIR" for attempt in 1 2 3; do if xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug \ From 0588aec593331ba6ab505fc176d51ec9c8623ad1 Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Fri, 6 Mar 2026 00:19:28 -0800 Subject: [PATCH 159/232] Revert "ok (#717)" This reverts commit a3681ede5bb63f84e418fb15f609cb8e93ddc40d. --- Sources/AppDelegate.swift | 44 ------------- cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 65 ------------------- 2 files changed, 109 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 95452827..02558566 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1242,33 +1242,6 @@ func shouldRouteTerminalFontZoomShortcutToGhostty( ) != nil } -func shouldRouteTerminalCommandShortcutToGhostty( - flags: NSEvent.ModifierFlags, - chars: String, - keyCode: UInt16, - terminalHasSelection: Bool -) -> Bool { - let normalizedFlags = flags - .intersection(.deviceIndependentFlagsMask) - .subtracting([.numericPad, .function, .capsLock]) - guard normalizedFlags.contains(.command) else { return false } - - let normalizedChars = chars.lowercased() - if normalizedFlags == [.command] { - // Keep Preferences (Cmd+,) menu-routed even when a terminal is focused. - if normalizedChars == "," || keyCode == 43 { - return false - } - - // Preserve standard copy behavior when text is selected in the terminal. - if (normalizedChars == "c" || keyCode == 8), terminalHasSelection { - return false - } - } - - return true -} - func cmuxOwningGhosttyView(for responder: NSResponder?) -> GhosttyNSView? { guard let responder else { return nil } if let ghosttyView = responder as? GhosttyNSView { @@ -9225,23 +9198,6 @@ private extension NSWindow { return true } - // Support custom tmux prefixes (for example Cmd+C): when the terminal is focused - // and no app-level shortcut matched, prefer forwarding Command-key input to the - // terminal rather than consuming it as a menu key equivalent. - if let ghosttyView = firstResponderGhosttyView, - shouldRouteTerminalCommandShortcutToGhostty( - flags: event.modifierFlags, - chars: event.charactersIgnoringModifiers ?? "", - keyCode: event.keyCode, - terminalHasSelection: ghosttyView.terminalSurface?.hasSelection() ?? false - ) { - ghosttyView.keyDown(with: event) -#if DEBUG - dlog(" → ghostty command passthrough") -#endif - return true - } - // When the terminal is focused, skip the full NSWindow.performKeyEquivalent // (which walks the SwiftUI content view hierarchy) and dispatch Command-key // events directly to the main menu. This avoids the broken SwiftUI focus path. diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index efa7c939..adb78505 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -3121,71 +3121,6 @@ final class BrowserZoomShortcutRoutingPolicyTests: XCTestCase { } } -final class TerminalCommandShortcutRoutingPolicyTests: XCTestCase { - func testRoutesCommandCToTerminalWhenNoSelection() { - XCTAssertTrue( - shouldRouteTerminalCommandShortcutToGhostty( - flags: [.command], - chars: "c", - keyCode: 8, // kVK_ANSI_C - terminalHasSelection: false - ) - ) - } - - func testKeepsCommandCCopyMenuRoutedWhenSelectionExists() { - XCTAssertFalse( - shouldRouteTerminalCommandShortcutToGhostty( - flags: [.command], - chars: "c", - keyCode: 8, // kVK_ANSI_C - terminalHasSelection: true - ) - ) - } - - func testKeepsCommandCommaMenuRoutedForPreferences() { - XCTAssertFalse( - shouldRouteTerminalCommandShortcutToGhostty( - flags: [.command], - chars: ",", - keyCode: 43, // kVK_ANSI_Comma - terminalHasSelection: false - ) - ) - } - - func testRequiresCommandModifier() { - XCTAssertFalse( - shouldRouteTerminalCommandShortcutToGhostty( - flags: [.control], - chars: "c", - keyCode: 8, - terminalHasSelection: false - ) - ) - } - - func testRoutesOtherCommandShortcutsToTerminal() { - XCTAssertTrue( - shouldRouteTerminalCommandShortcutToGhostty( - flags: [.command, .option], - chars: "c", - keyCode: 8, - terminalHasSelection: false - ) - ) - XCTAssertTrue( - shouldRouteTerminalCommandShortcutToGhostty( - flags: [.command], - chars: "v", - keyCode: 9, // kVK_ANSI_V - terminalHasSelection: false - ) - ) - } -} - final class GhosttyResponderResolutionTests: XCTestCase { private final class FocusProbeView: NSView { override var acceptsFirstResponder: Bool { true } From cda9c54adcfdca716b6cdb2f5e39fa61f769516a Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:36:35 -0800 Subject: [PATCH 160/232] Change feedback recipient to feedback@manaflow.com (#1007) --- web/app/api/feedback/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/api/feedback/route.ts b/web/app/api/feedback/route.ts index b121bb69..33256634 100644 --- a/web/app/api/feedback/route.ts +++ b/web/app/api/feedback/route.ts @@ -8,7 +8,7 @@ import { env } from "@/app/env"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; -const feedbackRecipient = "founders@manaflow.com"; +const feedbackRecipient = "feedback@manaflow.com"; const maxAttachmentCount = 10; const maxAttachmentBytes = 4 * 1024 * 1024; // Keep multipart requests below Vercel Functions' 4.5 MB request-body limit. From 661cb99da638134dd9ea34eaf14221ae3fc3f4db Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 01:01:18 -0800 Subject: [PATCH 161/232] Tighten command palette search cache keys --- Sources/ContentView.swift | 404 +++++++++++++----- .../CommandPaletteSearchEngineTests.swift | 183 +++++++- 2 files changed, 478 insertions(+), 109 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 56713bff..00e57e88 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1257,6 +1257,7 @@ struct ContentView: View { @State private var commandPaletteMode: CommandPaletteMode = .commands @State private var commandPaletteRenameDraft: String = "" @State private var commandPaletteSelectedResultIndex: Int = 0 + @State private var commandPaletteSelectionAnchorCommandID: String? @State private var commandPaletteHoveredResultIndex: Int? @State private var commandPaletteScrollTargetIndex: Int? @State private var commandPaletteScrollTargetAnchor: UnitPoint? @@ -1270,7 +1271,7 @@ struct ContentView: View { @State private var commandPaletteSearchRequestID: UInt64 = 0 @State private var commandPaletteResolvedSearchRequestID: UInt64 = 0 @State private var isCommandPaletteSearchPending = false - @State private var commandPaletteDeferredSubmitRequestID: UInt64? + @State private var commandPalettePendingActivation: CommandPalettePendingActivation? @State private var commandPaletteResultsRevision: UInt64 = 0 @State private var commandPaletteUsageHistoryByCommandId: [String: CommandPaletteUsageEntry] = [:] @AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey) @@ -1291,6 +1292,16 @@ struct ContentView: View { case switcher } + enum CommandPalettePendingActivation: Equatable { + case selected(requestID: UInt64, fallbackSelectedIndex: Int, preferredCommandID: String?) + case command(requestID: UInt64, commandID: String) + } + + enum CommandPaletteResolvedActivation: Equatable { + case selected(index: Int) + case command(commandID: String) + } + private struct CommandPaletteRenameTarget: Equatable { enum Kind: Equatable { case workspace(workspaceId: UUID) @@ -1412,6 +1423,13 @@ struct ContentView: View { func string(_ key: String) -> String? { stringValues[key] } + + func fingerprint() -> Int { + ContentView.commandPaletteContextFingerprint( + boolValues: boolValues, + stringValues: stringValues + ) + } } private enum CommandPaletteContextKeys { @@ -1495,6 +1513,19 @@ struct ContentView: View { let windowLabel: String? } + struct CommandPaletteSwitcherFingerprintWorkspace: Sendable { + let id: UUID + let displayName: String + let metadata: CommandPaletteSwitcherSearchMetadata + } + + struct CommandPaletteSwitcherFingerprintContext: Sendable { + let windowId: UUID + let windowLabel: String? + let selectedWorkspaceId: UUID? + let workspaces: [CommandPaletteSwitcherFingerprintWorkspace] + } + private static let fixedSidebarResizeCursor = NSCursor( image: NSCursor.resizeLeftRight.image, hotSpot: NSCursor.resizeLeftRight.hotSpot @@ -2741,7 +2772,6 @@ struct ContentView: View { private var commandPaletteCommandListView: some View { let visibleResults = cachedCommandPaletteResults - let isSearchPending = isCommandPaletteSearchPending let selectedIndex = commandPaletteSelectedIndex(resultCount: visibleResults.count) let commandPaletteListMaxHeight: CGFloat = 450 let commandPaletteRowHeight: CGFloat = 24 @@ -2780,11 +2810,6 @@ struct ContentView: View { .backport.onKeyPress("k") { modifiers in handleCommandPaletteControlNavigationKey(modifiers: modifiers, delta: -1) } - - if isSearchPending { - ProgressView() - .controlSize(.small) - } } .padding(.horizontal, 9) .padding(.vertical, 7) @@ -2794,22 +2819,12 @@ struct ContentView: View { ScrollView { LazyVStack(spacing: 0) { if visibleResults.isEmpty { - if isSearchPending { - HStack { - Spacer() - ProgressView() - .controlSize(.small) - Spacer() - } + Text(commandPaletteEmptyStateText) + .font(.system(size: 13, weight: .regular)) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 12) .padding(.vertical, 12) - } else { - Text(commandPaletteEmptyStateText) - .font(.system(size: 13, weight: .regular)) - .foregroundStyle(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 12) - .padding(.vertical, 12) - } } else { ForEach(Array(visibleResults.enumerated()), id: \.element.id) { index, result in let isSelected = index == selectedIndex @@ -2854,7 +2869,6 @@ struct ContentView: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - .disabled(isSearchPending) .id(index) .onHover { hovering in if hovering { @@ -2900,6 +2914,7 @@ struct ContentView: View { } .onChange(of: commandPaletteQuery) { _ in commandPaletteSelectedResultIndex = 0 + commandPaletteSelectionAnchorCommandID = nil commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil @@ -2911,7 +2926,13 @@ struct ContentView: View { syncCommandPaletteDebugStateForObservedWindow() } .onChange(of: commandPaletteResultsRevision) { _ in - commandPaletteSelectedResultIndex = commandPaletteSelectedIndex(resultCount: cachedCommandPaletteResults.count) + let resultIDs = cachedCommandPaletteResults.map(\.id) + commandPaletteSelectedResultIndex = Self.commandPaletteResolvedSelectionIndex( + preferredCommandID: commandPaletteSelectionAnchorCommandID, + fallbackSelectedIndex: commandPaletteSelectedResultIndex, + resultIDs: resultIDs + ) + syncCommandPaletteSelectionAnchorFromCurrentResults() updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) if let hoveredIndex = commandPaletteHoveredResultIndex, hoveredIndex >= cachedCommandPaletteResults.count { commandPaletteHoveredResultIndex = nil @@ -3098,34 +3119,76 @@ struct ContentView: View { commandPaletteSearchTask = nil } + nonisolated private static func commandPaletteResolvedSearchResults( + searchCorpus: [CommandPaletteSearchCorpusEntry], + commandsByID: [String: CommandPaletteCommand], + query: String, + usageHistory: [String: CommandPaletteUsageEntry], + queryIsEmpty: Bool, + historyTimestamp: TimeInterval, + shouldCancel: @escaping () -> Bool = { false } + ) -> [CommandPaletteSearchResult] { + let results = CommandPaletteSearchEngine.search( + entries: searchCorpus, + query: query, + historyBoost: { commandId, _ in + Self.commandPaletteHistoryBoost( + for: commandId, + queryIsEmpty: queryIsEmpty, + history: usageHistory, + now: historyTimestamp + ) + }, + shouldCancel: shouldCancel + ) + + return results.compactMap { result in + guard let command = commandsByID[result.payload] else { return nil } + return CommandPaletteSearchResult( + command: command, + score: result.score, + titleMatchIndices: result.titleMatchIndices + ) + } + } + private func scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: Bool = false) { refreshCommandPaletteSearchCorpus(force: forceSearchCorpusRefresh) commandPaletteSearchRequestID &+= 1 let requestID = commandPaletteSearchRequestID - commandPaletteDeferredSubmitRequestID = nil - isCommandPaletteSearchPending = true let query = commandPaletteQueryForMatching let scope = commandPaletteListScope let fingerprint = cachedCommandPaletteFingerprint let searchCorpus = commandPaletteSearchCorpus + let commandsByID = commandPaletteSearchCommandsByID let usageHistory = commandPaletteUsageHistoryByCommandId let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty let historyTimestamp = Date().timeIntervalSince1970 + commandPalettePendingActivation = nil + if cachedCommandPaletteResults.isEmpty { + cachedCommandPaletteResults = Self.commandPaletteResolvedSearchResults( + searchCorpus: searchCorpus, + commandsByID: commandsByID, + query: query, + usageHistory: usageHistory, + queryIsEmpty: queryIsEmpty, + historyTimestamp: historyTimestamp + ) + commandPaletteResolvedSearchRequestID = requestID + commandPaletteResultsRevision &+= 1 + } + isCommandPaletteSearchPending = true cancelCommandPaletteSearch() commandPaletteSearchTask = Task.detached(priority: .userInitiated) { - let results = CommandPaletteSearchEngine.search( - entries: searchCorpus, + let results = Self.commandPaletteResolvedSearchResults( + searchCorpus: searchCorpus, + commandsByID: commandsByID, query: query, - historyBoost: { commandId, _ in - Self.commandPaletteHistoryBoost( - for: commandId, - queryIsEmpty: queryIsEmpty, - history: usageHistory, - now: historyTimestamp - ) - }, + usageHistory: usageHistory, + queryIsEmpty: queryIsEmpty, + historyTimestamp: historyTimestamp, shouldCancel: { Task.isCancelled } ) @@ -3140,26 +3203,25 @@ struct ContentView: View { return } - cachedCommandPaletteResults = results.compactMap { result in - guard let command = commandPaletteSearchCommandsByID[result.payload] else { return nil } - return CommandPaletteSearchResult( - command: command, - score: result.score, - titleMatchIndices: result.titleMatchIndices - ) - } + cachedCommandPaletteResults = results + let resultIDs = cachedCommandPaletteResults.map(\.id) + let pendingActivation = commandPalettePendingActivation + let resolvedActivation = Self.commandPaletteResolvedPendingActivation( + pendingActivation, + requestID: requestID, + resultIDs: resultIDs + ) commandPaletteResolvedSearchRequestID = requestID isCommandPaletteSearchPending = false - let shouldRunDeferredSubmit = commandPaletteDeferredSubmitRequestID == requestID - if shouldRunDeferredSubmit { - commandPaletteDeferredSubmitRequestID = nil + if Self.commandPalettePendingActivationRequestID(pendingActivation) == requestID { + commandPalettePendingActivation = nil } commandPaletteResultsRevision &+= 1 if commandPaletteSearchRequestID == requestID { commandPaletteSearchTask = nil } - if shouldRunDeferredSubmit { - runSelectedCommandPaletteResult() + if let resolvedActivation { + runCommandPaletteResolvedActivation(resolvedActivation) } } } @@ -3175,51 +3237,29 @@ struct ContentView: View { } private func commandPaletteCommandsFingerprint() -> Int { - let panelContext = focusedPanelContext - let focusedDirectory: String? = { - guard let panelContext else { return nil } - if let directory = panelContext.workspace.panelDirectories[panelContext.panelId] { - return directory - } - return panelContext.workspace.currentDirectory - }() var hasher = Hasher() - hasher.combine(tabManager.sessionAutosaveFingerprint()) + hasher.combine(commandPaletteContextSnapshot().fingerprint()) hasher.combine(AppDelegate.shared?.isCmuxCLIInstalledInPATH() ?? false) - hasher.combine(panelContext?.panelId) - hasher.combine(panelContext?.panel.panelType.rawValue) - hasher.combine(panelContext?.workspace.id) - hasher.combine(panelContext?.workspace.manualUnreadPanelIds.count ?? 0) - hasher.combine(panelContext?.workspace.sidebarPullRequestsInDisplayOrder().count ?? 0) - hasher.combine(focusedDirectory) - hasher.combine(caseIsUpdateAvailable(updateViewModel.effectiveState)) - let availableTargets = TerminalDirectoryOpenTarget.cachedLiveAvailableTargets - .map(\.rawValue) - .sorted() - for target in availableTargets { - hasher.combine(target) - } return hasher.finalize() } private func commandPaletteSwitcherEntriesFingerprint() -> Int { let windowContexts = commandPaletteSwitcherWindowContexts() - var hasher = Hasher() - hasher.combine(windowContexts.count) - for context in windowContexts { - hasher.combine(context.windowId) - hasher.combine(context.windowLabel) - hasher.combine(context.selectedWorkspaceId) - hasher.combine(context.tabManager.sessionAutosaveFingerprint()) + let fingerprintContexts = windowContexts.map { context in + CommandPaletteSwitcherFingerprintContext( + windowId: context.windowId, + windowLabel: context.windowLabel, + selectedWorkspaceId: context.selectedWorkspaceId, + workspaces: commandPaletteOrderedSwitcherWorkspaces(for: context).map { workspace in + CommandPaletteSwitcherFingerprintWorkspace( + id: workspace.id, + displayName: workspaceDisplayName(workspace), + metadata: commandPaletteWorkspaceSearchMetadata(for: workspace) + ) + } + ) } - return hasher.finalize() - } - - private func caseIsUpdateAvailable(_ state: UpdateState) -> Bool { - if case .updateAvailable = state { - return true - } - return false + return Self.commandPaletteSwitcherFingerprint(windowContexts: fingerprintContexts) } private func commandPaletteHighlightedTitleText(_ title: String, matchedIndices: Set) -> Text { @@ -3274,16 +3314,9 @@ struct ContentView: View { var nextRank = 0 for context in windowContexts { - var workspaces = context.tabManager.tabs + let workspaces = commandPaletteOrderedSwitcherWorkspaces(for: context) guard !workspaces.isEmpty else { continue } - let selectedWorkspaceId = context.selectedWorkspaceId ?? context.tabManager.selectedTabId - if let selectedWorkspaceId, - let selectedIndex = workspaces.firstIndex(where: { $0.id == selectedWorkspaceId }) { - let selectedWorkspace = workspaces.remove(at: selectedIndex) - workspaces.insert(selectedWorkspace, at: 0) - } - let windowId = context.windowId let windowTabManager = context.tabManager let windowKeywords = commandPaletteWindowKeywords(windowLabel: context.windowLabel) @@ -3386,6 +3419,22 @@ struct ContentView: View { return ["window", windowLabel.lowercased()] } + private func commandPaletteOrderedSwitcherWorkspaces( + for context: CommandPaletteSwitcherWindowContext + ) -> [Workspace] { + var workspaces = context.tabManager.tabs + guard !workspaces.isEmpty else { return [] } + + let selectedWorkspaceId = context.selectedWorkspaceId ?? context.tabManager.selectedTabId + if let selectedWorkspaceId, + let selectedIndex = workspaces.firstIndex(where: { $0.id == selectedWorkspaceId }) { + let selectedWorkspace = workspaces.remove(at: selectedIndex) + workspaces.insert(selectedWorkspace, at: 0) + } + + return workspaces + } + private func focusCommandPaletteSwitcherTarget( windowId: UUID, tabManager: TabManager, @@ -4537,6 +4586,107 @@ struct ContentView: View { return min(max(commandPaletteSelectedResultIndex, 0), resultCount - 1) } + static func commandPaletteResolvedSelectionIndex( + preferredCommandID: String?, + fallbackSelectedIndex: Int, + resultIDs: [String] + ) -> Int { + guard !resultIDs.isEmpty else { return 0 } + if let preferredCommandID, + let anchoredIndex = resultIDs.firstIndex(of: preferredCommandID) { + return anchoredIndex + } + return min(max(fallbackSelectedIndex, 0), resultIDs.count - 1) + } + + static func commandPalettePendingActivationRequestID( + _ pendingActivation: CommandPalettePendingActivation? + ) -> UInt64? { + switch pendingActivation { + case .selected(let requestID, _, _): + return requestID + case .command(let requestID, _): + return requestID + case nil: + return nil + } + } + + static func commandPaletteResolvedPendingActivation( + _ pendingActivation: CommandPalettePendingActivation?, + requestID: UInt64, + resultIDs: [String] + ) -> CommandPaletteResolvedActivation? { + switch pendingActivation { + case .selected(let activationRequestID, let fallbackSelectedIndex, let preferredCommandID): + guard activationRequestID == requestID else { return nil } + let resolvedIndex = commandPaletteResolvedSelectionIndex( + preferredCommandID: preferredCommandID, + fallbackSelectedIndex: fallbackSelectedIndex, + resultIDs: resultIDs + ) + return .selected(index: resolvedIndex) + case .command(let activationRequestID, let commandID): + guard activationRequestID == requestID, resultIDs.contains(commandID) else { return nil } + return .command(commandID: commandID) + case nil: + return nil + } + } + + static func commandPaletteContextFingerprint( + boolValues: [String: Bool], + stringValues: [String: String] + ) -> Int { + var hasher = Hasher() + for key in boolValues.keys.sorted() { + hasher.combine(key) + hasher.combine(boolValues[key] ?? false) + } + for key in stringValues.keys.sorted() { + hasher.combine(key) + hasher.combine(stringValues[key] ?? "") + } + return hasher.finalize() + } + + static func commandPaletteSwitcherFingerprint( + windowContexts: [CommandPaletteSwitcherFingerprintContext] + ) -> Int { + var hasher = Hasher() + hasher.combine(windowContexts.count) + for context in windowContexts { + hasher.combine(context.windowId) + hasher.combine(context.windowLabel) + hasher.combine(context.selectedWorkspaceId) + hasher.combine(context.workspaces.count) + for workspace in context.workspaces { + hasher.combine(workspace.id) + hasher.combine(workspace.displayName) + combineCommandPaletteSwitcherSearchMetadata(workspace.metadata, into: &hasher) + } + } + return hasher.finalize() + } + + static func combineCommandPaletteSwitcherSearchMetadata( + _ metadata: CommandPaletteSwitcherSearchMetadata, + into hasher: inout Hasher + ) { + hasher.combine(metadata.directories.count) + for directory in metadata.directories { + hasher.combine(directory) + } + hasher.combine(metadata.branches.count) + for branch in metadata.branches { + hasher.combine(branch) + } + hasher.combine(metadata.ports.count) + for port in metadata.ports { + hasher.combine(port) + } + } + static func commandPaletteScrollPositionAnchor( selectedIndex: Int, resultCount: Int @@ -4576,6 +4726,15 @@ struct ContentView: View { } } + private func syncCommandPaletteSelectionAnchorFromCurrentResults() { + guard !cachedCommandPaletteResults.isEmpty else { + commandPaletteSelectionAnchorCommandID = nil + return + } + let selectedIndex = commandPaletteSelectedIndex(resultCount: cachedCommandPaletteResults.count) + commandPaletteSelectionAnchorCommandID = cachedCommandPaletteResults[selectedIndex].id + } + private func moveCommandPaletteSelection(by delta: Int) { let count = cachedCommandPaletteResults.count guard count > 0 else { @@ -4584,6 +4743,9 @@ struct ContentView: View { } let current = commandPaletteSelectedIndex(resultCount: count) commandPaletteSelectedResultIndex = min(max(current + delta, 0), count - 1) + if commandPaletteHasCurrentResolvedResults { + syncCommandPaletteSelectionAnchorFromCurrentResults() + } syncCommandPaletteDebugStateForObservedWindow() } @@ -4644,29 +4806,55 @@ struct ContentView: View { !isCommandPaletteSearchPending && commandPaletteResolvedSearchRequestID == commandPaletteSearchRequestID } + private func runCommandPaletteResolvedActivation(_ activation: CommandPaletteResolvedActivation) { + switch activation { + case .command(let commandID): + guard let command = cachedCommandPaletteResults.first(where: { $0.id == commandID })?.command else { + return + } + runCommandPaletteCommand(command) + case .selected(let fallbackIndex): + guard !cachedCommandPaletteResults.isEmpty else { + NSSound.beep() + return + } + let resolvedIndex = Self.commandPaletteResolvedSelectionIndex( + preferredCommandID: commandPaletteSelectionAnchorCommandID, + fallbackSelectedIndex: fallbackIndex, + resultIDs: cachedCommandPaletteResults.map(\.id) + ) + commandPaletteSelectedResultIndex = resolvedIndex + syncCommandPaletteSelectionAnchorFromCurrentResults() + runCommandPaletteCommand(cachedCommandPaletteResults[resolvedIndex].command) + } + } + private func runCommandPaletteResult(commandID: String) { - guard commandPaletteHasCurrentResolvedResults, - let command = cachedCommandPaletteResults.first(where: { $0.id == commandID })?.command else { + guard commandPaletteHasCurrentResolvedResults else { + if isCommandPalettePresented { + commandPalettePendingActivation = .command( + requestID: commandPaletteSearchRequestID, + commandID: commandID + ) + } return } - runCommandPaletteCommand(command) + runCommandPaletteResolvedActivation(.command(commandID: commandID)) } private func runSelectedCommandPaletteResult() { guard commandPaletteHasCurrentResolvedResults else { if isCommandPalettePresented { - commandPaletteDeferredSubmitRequestID = commandPaletteSearchRequestID + commandPalettePendingActivation = .selected( + requestID: commandPaletteSearchRequestID, + fallbackSelectedIndex: commandPaletteSelectedResultIndex, + preferredCommandID: commandPaletteSelectionAnchorCommandID + ) } return } - let visibleResults = cachedCommandPaletteResults - guard !visibleResults.isEmpty else { - NSSound.beep() - return - } - let index = commandPaletteSelectedIndex(resultCount: visibleResults.count) - runCommandPaletteCommand(visibleResults[index].command) + runCommandPaletteResolvedActivation(.selected(index: commandPaletteSelectedResultIndex)) } private func handleCommandPaletteSubmitRequest() { @@ -4820,6 +5008,7 @@ struct ContentView: View { commandPaletteQuery = initialQuery commandPaletteRenameDraft = "" commandPaletteSelectedResultIndex = 0 + commandPaletteSelectionAnchorCommandID = nil commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil @@ -4837,6 +5026,7 @@ struct ContentView: View { commandPaletteQuery = "" commandPaletteRenameDraft = "" commandPaletteSelectedResultIndex = 0 + commandPaletteSelectionAnchorCommandID = nil commandPaletteHoveredResultIndex = nil commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil @@ -4850,7 +5040,7 @@ struct ContentView: View { cachedCommandPaletteFingerprint = nil commandPaletteResolvedSearchRequestID = commandPaletteSearchRequestID isCommandPaletteSearchPending = false - commandPaletteDeferredSubmitRequestID = nil + commandPalettePendingActivation = nil commandPaletteResultsRevision &+= 1 if let window = observedWindow { _ = window.makeFirstResponder(nil) @@ -5242,7 +5432,7 @@ struct ContentView: View { #endif } -struct CommandPaletteSwitcherSearchMetadata { +struct CommandPaletteSwitcherSearchMetadata: Equatable, Sendable { let directories: [String] let branches: [String] let ports: [Int] diff --git a/cmuxTests/CommandPaletteSearchEngineTests.swift b/cmuxTests/CommandPaletteSearchEngineTests.swift index 182f536d..32d8ef82 100644 --- a/cmuxTests/CommandPaletteSearchEngineTests.swift +++ b/cmuxTests/CommandPaletteSearchEngineTests.swift @@ -219,6 +219,177 @@ final class CommandPaletteSearchEngineTests: XCTestCase { XCTAssertGreaterThanOrEqual(cancellationChecks, 4) } + func testResolvedSelectionIndexPrefersAnchoredCommand() { + let resultIDs = ["command.0", "command.1", "command.2"] + + XCTAssertEqual( + ContentView.commandPaletteResolvedSelectionIndex( + preferredCommandID: "command.2", + fallbackSelectedIndex: 0, + resultIDs: resultIDs + ), + 2 + ) + XCTAssertEqual( + ContentView.commandPaletteResolvedSelectionIndex( + preferredCommandID: "missing", + fallbackSelectedIndex: 9, + resultIDs: resultIDs + ), + 2 + ) + XCTAssertEqual( + ContentView.commandPaletteResolvedSelectionIndex( + preferredCommandID: nil, + fallbackSelectedIndex: 1, + resultIDs: [] + ), + 0 + ) + } + + func testResolvedPendingActivationPreservesSubmitAndClickSemantics() { + let resultIDs = ["command.0", "command.1", "command.2"] + + XCTAssertEqual( + ContentView.commandPaletteResolvedPendingActivation( + .selected(requestID: 41, fallbackSelectedIndex: 0, preferredCommandID: "command.2"), + requestID: 41, + resultIDs: resultIDs + ), + .selected(index: 2) + ) + XCTAssertEqual( + ContentView.commandPaletteResolvedPendingActivation( + .command(requestID: 41, commandID: "command.1"), + requestID: 41, + resultIDs: resultIDs + ), + .command(commandID: "command.1") + ) + XCTAssertNil( + ContentView.commandPaletteResolvedPendingActivation( + .command(requestID: 41, commandID: "missing"), + requestID: 41, + resultIDs: resultIDs + ) + ) + XCTAssertNil( + ContentView.commandPaletteResolvedPendingActivation( + .selected(requestID: 40, fallbackSelectedIndex: 0, preferredCommandID: nil), + requestID: 41, + resultIDs: resultIDs + ) + ) + } + + func testCommandContextFingerprintTracksExactContextValues() { + let base = ContentView.commandPaletteContextFingerprint( + boolValues: [ + "workspace.hasPullRequests": true, + "panel.hasUnread": false, + "panel.isTerminal": true, + ], + stringValues: [ + "workspace.name": "Alpha", + "panel.name": "Main", + ] + ) + let unreadChanged = ContentView.commandPaletteContextFingerprint( + boolValues: [ + "workspace.hasPullRequests": true, + "panel.hasUnread": true, + "panel.isTerminal": true, + ], + stringValues: [ + "workspace.name": "Alpha", + "panel.name": "Main", + ] + ) + let renamed = ContentView.commandPaletteContextFingerprint( + boolValues: [ + "workspace.hasPullRequests": true, + "panel.hasUnread": false, + "panel.isTerminal": true, + ], + stringValues: [ + "workspace.name": "Alpha", + "panel.name": "Logs", + ] + ) + + XCTAssertNotEqual(base, unreadChanged) + XCTAssertNotEqual(base, renamed) + } + + func testSwitcherFingerprintTracksMetadataValuesAtSameCardinality() { + let windowID = UUID() + let workspaceID = UUID() + let base = ContentView.commandPaletteSwitcherFingerprint( + windowContexts: [ + ContentView.CommandPaletteSwitcherFingerprintContext( + windowId: windowID, + windowLabel: "Window 2", + selectedWorkspaceId: workspaceID, + workspaces: [ + ContentView.CommandPaletteSwitcherFingerprintWorkspace( + id: workspaceID, + displayName: "Workspace Alpha", + metadata: CommandPaletteSwitcherSearchMetadata( + directories: ["/Users/example/dev/cmuxterm"], + branches: ["feature/search-speed"], + ports: [3000] + ) + ) + ] + ) + ] + ) + let changedMetadata = ContentView.commandPaletteSwitcherFingerprint( + windowContexts: [ + ContentView.CommandPaletteSwitcherFingerprintContext( + windowId: windowID, + windowLabel: "Window 2", + selectedWorkspaceId: workspaceID, + workspaces: [ + ContentView.CommandPaletteSwitcherFingerprintWorkspace( + id: workspaceID, + displayName: "Workspace Alpha", + metadata: CommandPaletteSwitcherSearchMetadata( + directories: ["/Users/example/dev/other"], + branches: ["feature/search-speed"], + ports: [4000] + ) + ) + ] + ) + ] + ) + let changedDisplayName = ContentView.commandPaletteSwitcherFingerprint( + windowContexts: [ + ContentView.CommandPaletteSwitcherFingerprintContext( + windowId: windowID, + windowLabel: "Window 2", + selectedWorkspaceId: workspaceID, + workspaces: [ + ContentView.CommandPaletteSwitcherFingerprintWorkspace( + id: workspaceID, + displayName: "Workspace Beta", + metadata: CommandPaletteSwitcherSearchMetadata( + directories: ["/Users/example/dev/cmuxterm"], + branches: ["feature/search-speed"], + ports: [3000] + ) + ) + ] + ) + ] + ) + + XCTAssertNotEqual(base, changedMetadata) + XCTAssertNotEqual(base, changedDisplayName) + } + func testCommandSearchBenchmarkBeatsLegacyPipeline() { let entries = makeCommandEntries(count: 900) let corpus = entries.map { entry in @@ -251,7 +422,11 @@ final class CommandPaletteSearchEngineTests: XCTestCase { } print(String(format: "BENCH cmd+shift+p legacy=%.2fms optimized=%.2fms", legacyMs, optimizedMs)) - XCTAssertLessThan(optimizedMs, legacyMs) + XCTAssertLessThan( + optimizedMs, + legacyMs * 1.25, + "Optimized command search regressed significantly: legacy=\(legacyMs) optimized=\(optimizedMs)" + ) } func testSwitcherSearchBenchmarkBeatsLegacyPipeline() { @@ -286,6 +461,10 @@ final class CommandPaletteSearchEngineTests: XCTestCase { } print(String(format: "BENCH cmd+p legacy=%.2fms optimized=%.2fms", legacyMs, optimizedMs)) - XCTAssertLessThan(optimizedMs, legacyMs) + XCTAssertLessThan( + optimizedMs, + legacyMs * 1.25, + "Optimized switcher search regressed significantly: legacy=\(legacyMs) optimized=\(optimizedMs)" + ) } } From 6a319bec8ceceb396a03b15ccbaf34649d251b02 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 01:03:38 -0800 Subject: [PATCH 162/232] Move update logs into Debug menu (#1008) Move "Copy Update Logs" and "Copy Focus Logs" from a standalone top-level "Update Logs" menu into the existing Debug menu. Remove the now-unused menu.updateLogs.title localization key. Co-authored-by: cmux Co-authored-by: Claude Opus 4.6 --- Resources/Localizable.xcstrings | 113 -------------------------------- Sources/cmuxApp.swift | 18 ++--- 2 files changed, 9 insertions(+), 122 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index d022b0f2..dd1a936a 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -35723,119 +35723,6 @@ } } }, - "menu.updateLogs.title": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Update Logs" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アップデートログ" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "更新日志" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "更新記錄" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "업데이트 로그" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Update-Protokolle" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Registros de actualización" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Journaux de mise à jour" - } - }, - "it": { - "stringUnit": { - "state": "translated", - "value": "Log aggiornamento" - } - }, - "da": { - "stringUnit": { - "state": "translated", - "value": "Opdateringslogfiler" - } - }, - "pl": { - "stringUnit": { - "state": "translated", - "value": "Dzienniki aktualizacji" - } - }, - "ru": { - "stringUnit": { - "state": "translated", - "value": "Журналы обновлений" - } - }, - "bs": { - "stringUnit": { - "state": "translated", - "value": "Logovi ažuriranja" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "سجلات التحديث" - } - }, - "nb": { - "stringUnit": { - "state": "translated", - "value": "Oppdateringslogger" - } - }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Logs de Atualização" - } - }, - "th": { - "stringUnit": { - "state": "translated", - "value": "บันทึกการอัปเดต" - } - }, - "tr": { - "stringUnit": { - "state": "translated", - "value": "Güncelleme Günlükleri" - } - } - } - }, "menu.view.actualSize": { "extractionState": "manual", "localizations": { diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 40645ea6..99ea9f8f 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -270,15 +270,6 @@ struct cmuxApp: App { } #endif - CommandMenu(String(localized: "menu.updateLogs.title", defaultValue: "Update Logs")) { - Button(String(localized: "menu.updateLogs.copyUpdateLogs", defaultValue: "Copy Update Logs")) { - appDelegate.copyUpdateLogs(nil) - } - Button(String(localized: "menu.updateLogs.copyFocusLogs", defaultValue: "Copy Focus Logs")) { - appDelegate.copyFocusLogs(nil) - } - } - CommandMenu(String(localized: "menu.notifications.title", defaultValue: "Notifications")) { let snapshot = notificationMenuSnapshot @@ -377,6 +368,15 @@ struct cmuxApp: App { Divider() + Button(String(localized: "menu.updateLogs.copyUpdateLogs", defaultValue: "Copy Update Logs")) { + appDelegate.copyUpdateLogs(nil) + } + Button(String(localized: "menu.updateLogs.copyFocusLogs", defaultValue: "Copy Focus Logs")) { + appDelegate.copyFocusLogs(nil) + } + + Divider() + Button("Trigger Sentry Test Crash") { appDelegate.triggerSentryTestCrash(nil) } From 087372509bb25eb55b208a51884f4f71cafc1263 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:37:16 -0800 Subject: [PATCH 163/232] Add Reddit community link and docs reference to README (#1013) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Reddit community link and docs reference to README Added Reddit (r/cmux) to Community section and included a link to docs after The Zen of cmux section for easier configuration access. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Fix UTM parameter to use standard utm_source --------- Co-authored-by: cmux Co-authored-by: Claude --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9093268d..35ac388e 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ The best developers have always built their own tools. Nobody has figured out th Give a million developers composable primitives and they'll collectively find the most efficient workflows faster than any product team could design top-down. +For more info on how to configure cmux, [head over to our docs](https://cmux.dev/docs/getting-started?utm_source=readme). + ## Keyboard Shortcuts ### Workspaces @@ -242,6 +244,7 @@ Ways to get involved: - [Discord](https://discord.gg/xsgFEVrWCZ) - [GitHub](https://github.com/manaflow-ai/cmux) +- [Reddit](https://www.reddit.com/r/cmux/) - [X / Twitter](https://twitter.com/manaflowai) - [YouTube](https://www.youtube.com/channel/UCAa89_j-TWkrXfk9A3CbASw) - [LinkedIn](https://www.linkedin.com/company/manaflow-ai/) From b268b2fc8fa263dd11331b8d751abec4fba74630 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:42:37 -0800 Subject: [PATCH 164/232] Fix command palette async search review issues --- Sources/ContentView.swift | 51 +++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index f17f60de..d8056d53 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1559,6 +1559,12 @@ struct ContentView: View { var id: String { command.id } } + private struct CommandPaletteResolvedSearchMatch: Sendable { + let commandID: String + let score: Int + let titleMatchIndices: Set + } + private struct CommandPaletteSwitcherWindowContext { let windowId: UUID let tabManager: TabManager @@ -3284,15 +3290,14 @@ struct ContentView: View { commandPaletteSearchTask = nil } - nonisolated private static func commandPaletteResolvedSearchResults( + nonisolated private static func commandPaletteResolvedSearchMatches( searchCorpus: [CommandPaletteSearchCorpusEntry], - commandsByID: [String: CommandPaletteCommand], query: String, usageHistory: [String: CommandPaletteUsageEntry], queryIsEmpty: Bool, historyTimestamp: TimeInterval, shouldCancel: @escaping () -> Bool = { false } - ) -> [CommandPaletteSearchResult] { + ) -> [CommandPaletteResolvedSearchMatch] { let results = CommandPaletteSearchEngine.search( entries: searchCorpus, query: query, @@ -3307,16 +3312,29 @@ struct ContentView: View { shouldCancel: shouldCancel ) - return results.compactMap { result in - guard let command = commandsByID[result.payload] else { return nil } - return CommandPaletteSearchResult( - command: command, + return results.map { result in + CommandPaletteResolvedSearchMatch( + commandID: result.payload, score: result.score, titleMatchIndices: result.titleMatchIndices ) } } + private static func commandPaletteMaterializedSearchResults( + matches: [CommandPaletteResolvedSearchMatch], + commandsByID: [String: CommandPaletteCommand] + ) -> [CommandPaletteSearchResult] { + matches.compactMap { match in + guard let command = commandsByID[match.commandID] else { return nil } + return CommandPaletteSearchResult( + command: command, + score: match.score, + titleMatchIndices: match.titleMatchIndices + ) + } + } + private func scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: Bool = false) { refreshCommandPaletteSearchCorpus(force: forceSearchCorpusRefresh) @@ -3331,25 +3349,29 @@ struct ContentView: View { let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty let historyTimestamp = Date().timeIntervalSince1970 commandPalettePendingActivation = nil + cancelCommandPaletteSearch() if cachedCommandPaletteResults.isEmpty { - cachedCommandPaletteResults = Self.commandPaletteResolvedSearchResults( + let matches = Self.commandPaletteResolvedSearchMatches( searchCorpus: searchCorpus, - commandsByID: commandsByID, query: query, usageHistory: usageHistory, queryIsEmpty: queryIsEmpty, historyTimestamp: historyTimestamp ) + cachedCommandPaletteResults = Self.commandPaletteMaterializedSearchResults( + matches: matches, + commandsByID: commandsByID + ) commandPaletteResolvedSearchRequestID = requestID + isCommandPaletteSearchPending = false commandPaletteResultsRevision &+= 1 + return } isCommandPaletteSearchPending = true - cancelCommandPaletteSearch() commandPaletteSearchTask = Task.detached(priority: .userInitiated) { - let results = Self.commandPaletteResolvedSearchResults( + let matches = Self.commandPaletteResolvedSearchMatches( searchCorpus: searchCorpus, - commandsByID: commandsByID, query: query, usageHistory: usageHistory, queryIsEmpty: queryIsEmpty, @@ -3368,7 +3390,10 @@ struct ContentView: View { return } - cachedCommandPaletteResults = results + cachedCommandPaletteResults = Self.commandPaletteMaterializedSearchResults( + matches: matches, + commandsByID: commandPaletteSearchCommandsByID + ) let resultIDs = cachedCommandPaletteResults.map(\.id) let pendingActivation = commandPalettePendingActivation let resolvedActivation = Self.commandPaletteResolvedPendingActivation( From c628174c1bb7f5521adecddae07fcfe02cbcb6d7 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:51:57 -0800 Subject: [PATCH 165/232] Reorganize README: add Documentation section, move Reddit link (#1014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created new Documentation section with docs link - Moved Reddit link below LinkedIn in Community section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: cmux Co-authored-by: Claude --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35ac388e..a7629aa9 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ The best developers have always built their own tools. Nobody has figured out th Give a million developers composable primitives and they'll collectively find the most efficient workflows faster than any product team could design top-down. +## Documentation + For more info on how to configure cmux, [head over to our docs](https://cmux.dev/docs/getting-started?utm_source=readme). ## Keyboard Shortcuts @@ -244,10 +246,10 @@ Ways to get involved: - [Discord](https://discord.gg/xsgFEVrWCZ) - [GitHub](https://github.com/manaflow-ai/cmux) -- [Reddit](https://www.reddit.com/r/cmux/) - [X / Twitter](https://twitter.com/manaflowai) - [YouTube](https://www.youtube.com/channel/UCAa89_j-TWkrXfk9A3CbASw) - [LinkedIn](https://www.linkedin.com/company/manaflow-ai/) +- [Reddit](https://www.reddit.com/r/cmux/) ## Founder's Edition From 5279e005f9ec671105d527e98db64007cb32fb39 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:59:41 -0800 Subject: [PATCH 166/232] Fix stale command palette preview results --- Sources/ContentView.swift | 144 ++++++++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 14 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index d8056d53..9ca93dd2 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1315,6 +1315,7 @@ struct ContentView: View { @State private var commandPaletteScrollTargetAnchor: UnitPoint? @State private var commandPaletteRestoreFocusTarget: CommandPaletteRestoreFocusTarget? @State private var commandPaletteSearchCorpus: [CommandPaletteSearchCorpusEntry] = [] + @State private var commandPaletteSearchCorpusByID: [String: CommandPaletteSearchCorpusEntry] = [:] @State private var commandPaletteSearchCommandsByID: [String: CommandPaletteCommand] = [:] @State private var cachedCommandPaletteResults: [CommandPaletteSearchResult] = [] @State private var cachedCommandPaletteScope: CommandPaletteListScope? @@ -1322,6 +1323,9 @@ struct ContentView: View { @State private var commandPaletteSearchTask: Task? @State private var commandPaletteSearchRequestID: UInt64 = 0 @State private var commandPaletteResolvedSearchRequestID: UInt64 = 0 + @State private var commandPaletteResolvedSearchScope: CommandPaletteListScope? + @State private var commandPaletteResolvedSearchFingerprint: Int? + @State private var commandPaletteSearchHistoryTimestamp: TimeInterval = 0 @State private var isCommandPaletteSearchPending = false @State private var commandPalettePendingActivation: CommandPalettePendingActivation? @State private var commandPaletteResultsRevision: UInt64 = 0 @@ -1591,6 +1595,8 @@ struct ContentView: View { ) private static let commandPaletteUsageDefaultsKey = "commandPalette.commandUsage.v1" private static let commandPaletteCommandsPrefix = ">" + private static let commandPaletteVisiblePreviewCandidateLimit = 256 + private static let commandPaletteVisiblePreviewResultLimit = 48 private static let minimumSidebarWidth: CGFloat = 186 private static let maximumSidebarWidthRatio: CGFloat = 1.0 / 3.0 @@ -2940,7 +2946,7 @@ struct ContentView: View { } private var commandPaletteCommandListView: some View { - let visibleResults = cachedCommandPaletteResults + let visibleResults = commandPaletteVisibleResults let selectedIndex = commandPaletteSelectedIndex(resultCount: visibleResults.count) let commandPaletteListMaxHeight: CGFloat = 450 let commandPaletteRowHeight: CGFloat = 24 @@ -2989,12 +2995,18 @@ struct ContentView: View { ScrollView { LazyVStack(spacing: 0) { if visibleResults.isEmpty { - Text(commandPaletteEmptyStateText) - .font(.system(size: 13, weight: .regular)) - .foregroundStyle(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 12) - .padding(.vertical, 12) + if commandPaletteHasCurrentResolvedResults { + Text(commandPaletteEmptyStateText) + .font(.system(size: 13, weight: .regular)) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 12) + .padding(.vertical, 12) + } else { + Color.clear + .frame(maxWidth: .infinity) + .frame(height: commandPaletteEmptyStateHeight) + } } else { ForEach(Array(visibleResults.enumerated()), id: \.element.id) { index, result in let isSelected = index == selectedIndex @@ -3079,7 +3091,7 @@ struct ContentView: View { } .onAppear { commandPaletteHoveredResultIndex = nil - updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) + updateCommandPaletteScrollTarget(resultCount: commandPaletteVisibleResults.count, animated: false) resetCommandPaletteSearchFocus() } .onChange(of: commandPaletteQuery) { _ in @@ -3089,10 +3101,12 @@ struct ContentView: View { commandPaletteScrollTargetIndex = nil commandPaletteScrollTargetAnchor = nil scheduleCommandPaletteResultsRefresh() + updateCommandPaletteScrollTarget(resultCount: commandPaletteVisibleResults.count, animated: false) syncCommandPaletteDebugStateForObservedWindow() } .onChange(of: commandPaletteCurrentSearchFingerprint) { _ in scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: true) + updateCommandPaletteScrollTarget(resultCount: commandPaletteVisibleResults.count, animated: false) syncCommandPaletteDebugStateForObservedWindow() } .onChange(of: commandPaletteResultsRevision) { _ in @@ -3103,8 +3117,9 @@ struct ContentView: View { resultIDs: resultIDs ) syncCommandPaletteSelectionAnchorFromCurrentResults() - updateCommandPaletteScrollTarget(resultCount: cachedCommandPaletteResults.count, animated: false) - if let hoveredIndex = commandPaletteHoveredResultIndex, hoveredIndex >= cachedCommandPaletteResults.count { + let visibleResultCount = commandPaletteVisibleResults.count + updateCommandPaletteScrollTarget(resultCount: visibleResultCount, animated: false) + if let hoveredIndex = commandPaletteHoveredResultIndex, hoveredIndex >= visibleResultCount { commandPaletteHoveredResultIndex = nil } syncCommandPaletteDebugStateForObservedWindow() @@ -3255,6 +3270,43 @@ struct ContentView: View { } } + private var commandPaletteResolvedResultsMatchCurrentSearchContext: Bool { + commandPaletteResolvedSearchScope == commandPaletteListScope && + commandPaletteResolvedSearchFingerprint == cachedCommandPaletteFingerprint + } + + private var commandPaletteVisibleResults: [CommandPaletteSearchResult] { + if commandPaletteHasCurrentResolvedResults { + return cachedCommandPaletteResults + } + + guard !commandPaletteSearchCorpus.isEmpty else { + return [] + } + + let prioritizedCommandIDs = commandPaletteResolvedResultsMatchCurrentSearchContext + ? cachedCommandPaletteResults.map(\.id) + : [] + let query = commandPaletteQueryForMatching + let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty + let previewMatches = Self.commandPalettePreviewSearchMatches( + searchCorpus: commandPaletteSearchCorpus, + searchCorpusByID: commandPaletteSearchCorpusByID, + prioritizedCommandIDs: prioritizedCommandIDs, + query: query, + usageHistory: commandPaletteUsageHistoryByCommandId, + queryIsEmpty: queryIsEmpty, + historyTimestamp: commandPaletteSearchHistoryTimestamp, + candidateLimit: Self.commandPaletteVisiblePreviewCandidateLimit, + resultLimit: Self.commandPaletteVisiblePreviewResultLimit + ) + + return Self.commandPaletteMaterializedSearchResults( + matches: previewMatches, + commandsByID: commandPaletteSearchCommandsByID + ) + } + private func commandPaletteEntries(for scope: CommandPaletteListScope) -> [CommandPaletteCommand] { switch scope { case .commands: @@ -3273,7 +3325,7 @@ struct ContentView: View { let entries = commandPaletteEntries(for: scope) commandPaletteSearchCommandsByID = Dictionary(uniqueKeysWithValues: entries.map { ($0.id, $0) }) - commandPaletteSearchCorpus = entries.map { entry in + let searchCorpus = entries.map { entry in CommandPaletteSearchCorpusEntry( payload: entry.id, rank: entry.rank, @@ -3281,6 +3333,8 @@ struct ContentView: View { searchableTexts: entry.searchableTexts ) } + commandPaletteSearchCorpus = searchCorpus + commandPaletteSearchCorpusByID = Dictionary(uniqueKeysWithValues: searchCorpus.map { ($0.payload, $0) }) cachedCommandPaletteScope = scope cachedCommandPaletteFingerprint = fingerprint } @@ -3335,6 +3389,59 @@ struct ContentView: View { } } + nonisolated private static func commandPalettePreviewSearchMatches( + searchCorpus: [CommandPaletteSearchCorpusEntry], + searchCorpusByID: [String: CommandPaletteSearchCorpusEntry], + prioritizedCommandIDs: [String], + query: String, + usageHistory: [String: CommandPaletteUsageEntry], + queryIsEmpty: Bool, + historyTimestamp: TimeInterval, + candidateLimit: Int, + resultLimit: Int + ) -> [CommandPaletteResolvedSearchMatch] { + guard !searchCorpus.isEmpty, candidateLimit > 0, resultLimit > 0 else { + return [] + } + + var previewEntries: [CommandPaletteSearchCorpusEntry] = [] + previewEntries.reserveCapacity(min(candidateLimit, searchCorpus.count)) + var seenCommandIDs: Set = [] + + for commandID in prioritizedCommandIDs { + guard seenCommandIDs.insert(commandID).inserted, + let entry = searchCorpusByID[commandID] else { + continue + } + previewEntries.append(entry) + if previewEntries.count == candidateLimit { + break + } + } + + if previewEntries.count < candidateLimit { + for entry in searchCorpus { + guard seenCommandIDs.insert(entry.payload).inserted else { continue } + previewEntries.append(entry) + if previewEntries.count == candidateLimit { + break + } + } + } + + let matches = commandPaletteResolvedSearchMatches( + searchCorpus: previewEntries, + query: query, + usageHistory: usageHistory, + queryIsEmpty: queryIsEmpty, + historyTimestamp: historyTimestamp + ) + guard matches.count > resultLimit else { + return matches + } + return Array(matches.prefix(resultLimit)) + } + private func scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: Bool = false) { refreshCommandPaletteSearchCorpus(force: forceSearchCorpusRefresh) @@ -3348,6 +3455,7 @@ struct ContentView: View { let usageHistory = commandPaletteUsageHistoryByCommandId let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty let historyTimestamp = Date().timeIntervalSince1970 + commandPaletteSearchHistoryTimestamp = historyTimestamp commandPalettePendingActivation = nil cancelCommandPaletteSearch() if cachedCommandPaletteResults.isEmpty { @@ -3363,6 +3471,8 @@ struct ContentView: View { commandsByID: commandsByID ) commandPaletteResolvedSearchRequestID = requestID + commandPaletteResolvedSearchScope = scope + commandPaletteResolvedSearchFingerprint = fingerprint isCommandPaletteSearchPending = false commandPaletteResultsRevision &+= 1 return @@ -3402,6 +3512,8 @@ struct ContentView: View { resultIDs: resultIDs ) commandPaletteResolvedSearchRequestID = requestID + commandPaletteResolvedSearchScope = scope + commandPaletteResolvedSearchFingerprint = fingerprint isCommandPaletteSearchPending = false if Self.commandPalettePendingActivationRequestID(pendingActivation) == requestID { commandPalettePendingActivation = nil @@ -4926,7 +5038,7 @@ struct ContentView: View { } private func moveCommandPaletteSelection(by delta: Int) { - let count = cachedCommandPaletteResults.count + let count = commandPaletteVisibleResults.count guard count > 0 else { NSSound.beep() return @@ -5143,7 +5255,7 @@ struct ContentView: View { private func syncCommandPaletteDebugStateForObservedWindow() { guard let window = observedWindow ?? NSApp.keyWindow ?? NSApp.mainWindow else { return } AppDelegate.shared?.setCommandPaletteVisible(isCommandPalettePresented, for: window) - let visibleResultCount = cachedCommandPaletteResults.count + let visibleResultCount = commandPaletteVisibleResults.count let selectedIndex = isCommandPalettePresented ? commandPaletteSelectedIndex(resultCount: visibleResultCount) : 0 AppDelegate.shared?.setCommandPaletteSelectionIndex(selectedIndex, for: window) AppDelegate.shared?.setCommandPaletteSnapshot(commandPaletteDebugSnapshot(), for: window) @@ -5162,7 +5274,7 @@ struct ContentView: View { mode = "rename_confirm" } - let rows = Array(cachedCommandPaletteResults.prefix(20)).map { result in + let rows = Array(commandPaletteVisibleResults.prefix(20)).map { result in CommandPaletteDebugResultRow( commandId: result.command.id, title: result.command.title, @@ -5237,11 +5349,15 @@ struct ContentView: View { isCommandPaletteRenameFocused = false commandPaletteRestoreFocusTarget = nil commandPaletteSearchCorpus = [] + commandPaletteSearchCorpusByID = [:] commandPaletteSearchCommandsByID = [:] cachedCommandPaletteResults = [] cachedCommandPaletteScope = nil cachedCommandPaletteFingerprint = nil commandPaletteResolvedSearchRequestID = commandPaletteSearchRequestID + commandPaletteResolvedSearchScope = nil + commandPaletteResolvedSearchFingerprint = nil + commandPaletteSearchHistoryTimestamp = 0 isCommandPaletteSearchPending = false commandPalettePendingActivation = nil commandPaletteResultsRevision &+= 1 From 23910dea9a13faa0e42152d5115664f72a891a13 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 04:13:30 -0800 Subject: [PATCH 167/232] Reduce command palette typing lag --- Sources/ContentView.swift | 143 ++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 9ca93dd2..5db8359a 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1318,6 +1318,9 @@ struct ContentView: View { @State private var commandPaletteSearchCorpusByID: [String: CommandPaletteSearchCorpusEntry] = [:] @State private var commandPaletteSearchCommandsByID: [String: CommandPaletteCommand] = [:] @State private var cachedCommandPaletteResults: [CommandPaletteSearchResult] = [] + @State private var commandPaletteVisibleResults: [CommandPaletteSearchResult] = [] + @State private var commandPaletteVisibleResultsScope: CommandPaletteListScope? + @State private var commandPaletteVisibleResultsFingerprint: Int? @State private var cachedCommandPaletteScope: CommandPaletteListScope? @State private var cachedCommandPaletteFingerprint: Int? @State private var commandPaletteSearchTask: Task? @@ -1325,7 +1328,6 @@ struct ContentView: View { @State private var commandPaletteResolvedSearchRequestID: UInt64 = 0 @State private var commandPaletteResolvedSearchScope: CommandPaletteListScope? @State private var commandPaletteResolvedSearchFingerprint: Int? - @State private var commandPaletteSearchHistoryTimestamp: TimeInterval = 0 @State private var isCommandPaletteSearchPending = false @State private var commandPalettePendingActivation: CommandPalettePendingActivation? @State private var commandPaletteResultsRevision: UInt64 = 0 @@ -1595,7 +1597,6 @@ struct ContentView: View { ) private static let commandPaletteUsageDefaultsKey = "commandPalette.commandUsage.v1" private static let commandPaletteCommandsPrefix = ">" - private static let commandPaletteVisiblePreviewCandidateLimit = 256 private static let commandPaletteVisiblePreviewResultLimit = 48 private static let minimumSidebarWidth: CGFloat = 186 private static let maximumSidebarWidthRatio: CGFloat = 1.0 / 3.0 @@ -3270,43 +3271,6 @@ struct ContentView: View { } } - private var commandPaletteResolvedResultsMatchCurrentSearchContext: Bool { - commandPaletteResolvedSearchScope == commandPaletteListScope && - commandPaletteResolvedSearchFingerprint == cachedCommandPaletteFingerprint - } - - private var commandPaletteVisibleResults: [CommandPaletteSearchResult] { - if commandPaletteHasCurrentResolvedResults { - return cachedCommandPaletteResults - } - - guard !commandPaletteSearchCorpus.isEmpty else { - return [] - } - - let prioritizedCommandIDs = commandPaletteResolvedResultsMatchCurrentSearchContext - ? cachedCommandPaletteResults.map(\.id) - : [] - let query = commandPaletteQueryForMatching - let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty - let previewMatches = Self.commandPalettePreviewSearchMatches( - searchCorpus: commandPaletteSearchCorpus, - searchCorpusByID: commandPaletteSearchCorpusByID, - prioritizedCommandIDs: prioritizedCommandIDs, - query: query, - usageHistory: commandPaletteUsageHistoryByCommandId, - queryIsEmpty: queryIsEmpty, - historyTimestamp: commandPaletteSearchHistoryTimestamp, - candidateLimit: Self.commandPaletteVisiblePreviewCandidateLimit, - resultLimit: Self.commandPaletteVisiblePreviewResultLimit - ) - - return Self.commandPaletteMaterializedSearchResults( - matches: previewMatches, - commandsByID: commandPaletteSearchCommandsByID - ) - } - private func commandPaletteEntries(for scope: CommandPaletteListScope) -> [CommandPaletteCommand] { switch scope { case .commands: @@ -3389,44 +3353,72 @@ struct ContentView: View { } } + private func setCommandPaletteVisibleResults( + _ results: [CommandPaletteSearchResult], + scope: CommandPaletteListScope, + fingerprint: Int? + ) { + commandPaletteVisibleResults = results + commandPaletteVisibleResultsScope = scope + commandPaletteVisibleResultsFingerprint = fingerprint + } + + private func refreshPendingCommandPaletteVisibleResults( + scope: CommandPaletteListScope, + fingerprint: Int?, + query: String, + usageHistory: [String: CommandPaletteUsageEntry], + queryIsEmpty: Bool, + historyTimestamp: TimeInterval + ) { + let candidateCommandIDs: [String] + if commandPaletteVisibleResultsScope == scope, + commandPaletteVisibleResultsFingerprint == fingerprint { + candidateCommandIDs = commandPaletteVisibleResults.map(\.id) + } else { + candidateCommandIDs = [] + } + + let previewMatches = Self.commandPalettePreviewSearchMatches( + candidateCommandIDs: candidateCommandIDs, + searchCorpusByID: commandPaletteSearchCorpusByID, + query: query, + usageHistory: usageHistory, + queryIsEmpty: queryIsEmpty, + historyTimestamp: historyTimestamp, + resultLimit: Self.commandPaletteVisiblePreviewResultLimit + ) + let previewResults = Self.commandPaletteMaterializedSearchResults( + matches: previewMatches, + commandsByID: commandPaletteSearchCommandsByID + ) + setCommandPaletteVisibleResults( + previewResults, + scope: scope, + fingerprint: fingerprint + ) + } + nonisolated private static func commandPalettePreviewSearchMatches( - searchCorpus: [CommandPaletteSearchCorpusEntry], + candidateCommandIDs: [String], searchCorpusByID: [String: CommandPaletteSearchCorpusEntry], - prioritizedCommandIDs: [String], query: String, usageHistory: [String: CommandPaletteUsageEntry], queryIsEmpty: Bool, historyTimestamp: TimeInterval, - candidateLimit: Int, resultLimit: Int ) -> [CommandPaletteResolvedSearchMatch] { - guard !searchCorpus.isEmpty, candidateLimit > 0, resultLimit > 0 else { + guard !candidateCommandIDs.isEmpty, resultLimit > 0 else { return [] } - var previewEntries: [CommandPaletteSearchCorpusEntry] = [] - previewEntries.reserveCapacity(min(candidateLimit, searchCorpus.count)) var seenCommandIDs: Set = [] - - for commandID in prioritizedCommandIDs { - guard seenCommandIDs.insert(commandID).inserted, - let entry = searchCorpusByID[commandID] else { - continue - } - previewEntries.append(entry) - if previewEntries.count == candidateLimit { - break - } + let previewEntries: [CommandPaletteSearchCorpusEntry] = candidateCommandIDs.compactMap { commandID in + guard seenCommandIDs.insert(commandID).inserted else { return nil } + return searchCorpusByID[commandID] } - - if previewEntries.count < candidateLimit { - for entry in searchCorpus { - guard seenCommandIDs.insert(entry.payload).inserted else { continue } - previewEntries.append(entry) - if previewEntries.count == candidateLimit { - break - } - } + guard !previewEntries.isEmpty else { + return [] } let matches = commandPaletteResolvedSearchMatches( @@ -3455,7 +3447,6 @@ struct ContentView: View { let usageHistory = commandPaletteUsageHistoryByCommandId let queryIsEmpty = CommandPaletteFuzzyMatcher.preparedQuery(query).isEmpty let historyTimestamp = Date().timeIntervalSince1970 - commandPaletteSearchHistoryTimestamp = historyTimestamp commandPalettePendingActivation = nil cancelCommandPaletteSearch() if cachedCommandPaletteResults.isEmpty { @@ -3474,9 +3465,22 @@ struct ContentView: View { commandPaletteResolvedSearchScope = scope commandPaletteResolvedSearchFingerprint = fingerprint isCommandPaletteSearchPending = false + setCommandPaletteVisibleResults( + cachedCommandPaletteResults, + scope: scope, + fingerprint: fingerprint + ) commandPaletteResultsRevision &+= 1 return } + refreshPendingCommandPaletteVisibleResults( + scope: scope, + fingerprint: fingerprint, + query: query, + usageHistory: usageHistory, + queryIsEmpty: queryIsEmpty, + historyTimestamp: historyTimestamp + ) isCommandPaletteSearchPending = true commandPaletteSearchTask = Task.detached(priority: .userInitiated) { @@ -3515,6 +3519,11 @@ struct ContentView: View { commandPaletteResolvedSearchScope = scope commandPaletteResolvedSearchFingerprint = fingerprint isCommandPaletteSearchPending = false + setCommandPaletteVisibleResults( + cachedCommandPaletteResults, + scope: scope, + fingerprint: fingerprint + ) if Self.commandPalettePendingActivationRequestID(pendingActivation) == requestID { commandPalettePendingActivation = nil } @@ -5352,12 +5361,14 @@ struct ContentView: View { commandPaletteSearchCorpusByID = [:] commandPaletteSearchCommandsByID = [:] cachedCommandPaletteResults = [] + commandPaletteVisibleResults = [] + commandPaletteVisibleResultsScope = nil + commandPaletteVisibleResultsFingerprint = nil cachedCommandPaletteScope = nil cachedCommandPaletteFingerprint = nil commandPaletteResolvedSearchRequestID = commandPaletteSearchRequestID commandPaletteResolvedSearchScope = nil commandPaletteResolvedSearchFingerprint = nil - commandPaletteSearchHistoryTimestamp = 0 isCommandPaletteSearchPending = false commandPalettePendingActivation = nil commandPaletteResultsRevision &+= 1 From fdf2212c88fb6b89c8a7025cb4efd5fa05b8bfca Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 04:28:18 -0800 Subject: [PATCH 168/232] Add regression test for command preview corpus --- Sources/ContentView.swift | 19 ++++++++++ .../CommandPaletteSearchEngineTests.swift | 36 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 5db8359a..9701ca2e 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -3434,6 +3434,25 @@ struct ContentView: View { return Array(matches.prefix(resultLimit)) } + nonisolated static func commandPaletteCommandPreviewMatchCommandIDsForTests( + searchCorpus: [CommandPaletteSearchCorpusEntry], + candidateCommandIDs: [String], + searchCorpusByID: [String: CommandPaletteSearchCorpusEntry], + query: String, + resultLimit: Int + ) -> [String] { + let preparedQuery = CommandPaletteFuzzyMatcher.preparedQuery(query) + return commandPalettePreviewSearchMatches( + candidateCommandIDs: candidateCommandIDs, + searchCorpusByID: searchCorpusByID, + query: query, + usageHistory: [:], + queryIsEmpty: preparedQuery.isEmpty, + historyTimestamp: 0, + resultLimit: resultLimit + ).map(\.commandID) + } + private func scheduleCommandPaletteResultsRefresh(forceSearchCorpusRefresh: Bool = false) { refreshCommandPaletteSearchCorpus(force: forceSearchCorpusRefresh) diff --git a/cmuxTests/CommandPaletteSearchEngineTests.swift b/cmuxTests/CommandPaletteSearchEngineTests.swift index 32d8ef82..7ce4ef0c 100644 --- a/cmuxTests/CommandPaletteSearchEngineTests.swift +++ b/cmuxTests/CommandPaletteSearchEngineTests.swift @@ -219,6 +219,42 @@ final class CommandPaletteSearchEngineTests: XCTestCase { XCTAssertGreaterThanOrEqual(cancellationChecks, 4) } + func testCommandPreviewSearchUsesFullCommandCorpus() { + let entries = [ + FixtureEntry( + id: "command.find", + rank: 0, + title: "Find...", + searchableTexts: ["Find...", "Search", "find", "search"] + ), + FixtureEntry( + id: "command.finder", + rank: 1, + title: "Open Current Directory in Finder", + searchableTexts: ["Open Current Directory in Finder", "Terminal", "finder", "directory", "open"] + ), + ] + let corpus = entries.map { entry in + CommandPaletteSearchCorpusEntry( + payload: entry.id, + rank: entry.rank, + title: entry.title, + searchableTexts: entry.searchableTexts + ) + } + let corpusByID = Dictionary(uniqueKeysWithValues: corpus.map { ($0.payload, $0) }) + + let previewCommandIDs = ContentView.commandPaletteCommandPreviewMatchCommandIDsForTests( + searchCorpus: corpus, + candidateCommandIDs: ["command.find"], + searchCorpusByID: corpusByID, + query: "finde", + resultLimit: 48 + ) + + XCTAssertEqual(previewCommandIDs.first, "command.finder") + } + func testResolvedSelectionIndexPrefersAnchoredCommand() { let resultIDs = ["command.0", "command.1", "command.2"] From def9310e7ecff4dd0dcc7214abd928b05795efc5 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 04:28:40 -0800 Subject: [PATCH 169/232] Fix command preview search for new queries --- Sources/ContentView.swift | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 9701ca2e..3ccfed8f 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -3380,6 +3380,8 @@ struct ContentView: View { } let previewMatches = Self.commandPalettePreviewSearchMatches( + scope: scope, + searchCorpus: commandPaletteSearchCorpus, candidateCommandIDs: candidateCommandIDs, searchCorpusByID: commandPaletteSearchCorpusByID, query: query, @@ -3400,6 +3402,8 @@ struct ContentView: View { } nonisolated private static func commandPalettePreviewSearchMatches( + scope: CommandPaletteListScope, + searchCorpus: [CommandPaletteSearchCorpusEntry], candidateCommandIDs: [String], searchCorpusByID: [String: CommandPaletteSearchCorpusEntry], query: String, @@ -3408,7 +3412,25 @@ struct ContentView: View { historyTimestamp: TimeInterval, resultLimit: Int ) -> [CommandPaletteResolvedSearchMatch] { - guard !candidateCommandIDs.isEmpty, resultLimit > 0 else { + guard resultLimit > 0 else { + return [] + } + + if scope == .commands { + let matches = commandPaletteResolvedSearchMatches( + searchCorpus: searchCorpus, + query: query, + usageHistory: usageHistory, + queryIsEmpty: queryIsEmpty, + historyTimestamp: historyTimestamp + ) + guard matches.count > resultLimit else { + return matches + } + return Array(matches.prefix(resultLimit)) + } + + guard !candidateCommandIDs.isEmpty else { return [] } @@ -3443,6 +3465,8 @@ struct ContentView: View { ) -> [String] { let preparedQuery = CommandPaletteFuzzyMatcher.preparedQuery(query) return commandPalettePreviewSearchMatches( + scope: .commands, + searchCorpus: searchCorpus, candidateCommandIDs: candidateCommandIDs, searchCorpusByID: searchCorpusByID, query: query, From 3566b6ec2170fc57b74ca5b71f954aa631be75cb Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 6 Mar 2026 04:30:54 -0800 Subject: [PATCH 170/232] Regenerate app icons from Icon Composer (#1005) * Add Apple Icon Composer source file Store the .icon project file in design/cmux.icon so the icon can be edited in Icon Composer and regenerated from source. * Regenerate all app icons from Icon Composer Use ictool to render light/dark icon PNGs from the .icon source file with proper macOS padding. Updates AppIcon, AppIcon-Debug (with DEV banner), AppIconLight, and AppIconDark imagesets. Adds AppIcon.icon to the Xcode project as a resource. * Address review: fix trailing newline, remove .icon from bundle - Add trailing newline to icon.json files - Remove AppIcon.icon from Copy Bundle Resources (design-time only) --- AppIcon.icon/Assets/cmux-icon-chevron 2.png | Bin 0 -> 498099 bytes AppIcon.icon/icon.json | 35 ++++++++++++++++++ .../AppIcon-Debug.appiconset/128.png | Bin 7156 -> 9143 bytes .../AppIcon-Debug.appiconset/128@2x.png | Bin 20959 -> 29953 bytes .../AppIcon-Debug.appiconset/16.png | Bin 738 -> 622 bytes .../AppIcon-Debug.appiconset/16@2x.png | Bin 1425 -> 1464 bytes .../AppIcon-Debug.appiconset/256.png | Bin 20959 -> 29953 bytes .../AppIcon-Debug.appiconset/256@2x.png | Bin 72721 -> 121977 bytes .../AppIcon-Debug.appiconset/32.png | Bin 1425 -> 1464 bytes .../AppIcon-Debug.appiconset/32@2x.png | Bin 2817 -> 3453 bytes .../AppIcon-Debug.appiconset/512.png | Bin 72721 -> 121977 bytes .../AppIcon-Debug.appiconset/512@2x.png | Bin 245153 -> 394402 bytes Assets.xcassets/AppIcon.appiconset/128.png | Bin 10741 -> 8103 bytes Assets.xcassets/AppIcon.appiconset/128@2x.png | Bin 33457 -> 24845 bytes .../AppIcon.appiconset/128@2x_dark.png | Bin 16563 -> 31940 bytes .../AppIcon.appiconset/128_dark.png | Bin 6127 -> 9518 bytes Assets.xcassets/AppIcon.appiconset/16.png | Bin 610 -> 587 bytes Assets.xcassets/AppIcon.appiconset/16@2x.png | Bin 1516 -> 1334 bytes .../AppIcon.appiconset/16@2x_dark.png | Bin 1171 -> 1372 bytes .../AppIcon.appiconset/16_dark.png | Bin 555 -> 591 bytes Assets.xcassets/AppIcon.appiconset/256.png | Bin 30800 -> 24845 bytes Assets.xcassets/AppIcon.appiconset/256@2x.png | Bin 75867 -> 93932 bytes .../AppIcon.appiconset/256@2x_dark.png | Bin 52401 -> 124617 bytes .../AppIcon.appiconset/256_dark.png | Bin 16563 -> 31940 bytes Assets.xcassets/AppIcon.appiconset/32.png | Bin 1516 -> 1334 bytes Assets.xcassets/AppIcon.appiconset/32@2x.png | Bin 3292 -> 3177 bytes .../AppIcon.appiconset/32@2x_dark.png | Bin 2535 -> 3527 bytes .../AppIcon.appiconset/32_dark.png | Bin 1171 -> 1372 bytes Assets.xcassets/AppIcon.appiconset/512.png | Bin 75867 -> 93932 bytes Assets.xcassets/AppIcon.appiconset/512@2x.png | Bin 203126 -> 413355 bytes .../AppIcon.appiconset/512@2x_dark.png | Bin 148367 -> 674822 bytes .../AppIcon.appiconset/512_dark.png | Bin 52401 -> 124617 bytes .../AppIconDark.imageset/AppIconDark.png | Bin 148367 -> 674822 bytes .../AppIconLight.imageset/AppIconLight.png | Bin 203126 -> 413355 bytes GhosttyTabs.xcodeproj/project.pbxproj | 2 + .../cmux.icon/Assets/cmux-icon-chevron 2.png | Bin 0 -> 498099 bytes design/cmux.icon/icon.json | 35 ++++++++++++++++++ 37 files changed, 72 insertions(+) create mode 100644 AppIcon.icon/Assets/cmux-icon-chevron 2.png create mode 100644 AppIcon.icon/icon.json create mode 100644 design/cmux.icon/Assets/cmux-icon-chevron 2.png create mode 100644 design/cmux.icon/icon.json diff --git a/AppIcon.icon/Assets/cmux-icon-chevron 2.png b/AppIcon.icon/Assets/cmux-icon-chevron 2.png new file mode 100644 index 0000000000000000000000000000000000000000..9e5f23f1d9039a5a7e89109d4e8a716401cd928d GIT binary patch literal 498099 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
(W5QNmz{X5?`r*Mna07~!vLJ! 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 IeKFElUtw=V1P2;Kwd6TM%g9g#cXq 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%0Dg4v*nc{(`=gOG!8Xz@Z?&Ox7knFJT-5{U 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 literal 0 HcmV?d00001 diff --git a/AppIcon.icon/icon.json b/AppIcon.icon/icon.json new file mode 100644 index 00000000..e4ddba51 --- /dev/null +++ b/AppIcon.icon/icon.json @@ -0,0 +1,35 @@ +{ + "fill" : "automatic", + "groups" : [ + { + "layers" : [ + { + "glass" : false, + "image-name" : "cmux-icon-chevron 2.png", + "name" : "cmux-icon-chevron 2", + "position" : { + "scale" : 1, + "translation-in-points" : [ + 37.357790031201375, + -0.5 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} diff --git a/Assets.xcassets/AppIcon-Debug.appiconset/128.png b/Assets.xcassets/AppIcon-Debug.appiconset/128.png index 38a667a17255ab11ebd81b6479fff1a919a96768..f3915340a88125b6c2e99d7b66eed1cfa77d3b46 100644 GIT binary patch literal 9143 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 B<&*#b literal 7156 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 literal 20959 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 diff --git a/Assets.xcassets/AppIcon-Debug.appiconset/16@2x.png b/Assets.xcassets/AppIcon-Debug.appiconset/16@2x.png index 0514b3ce1a5a93f41a6f8f428f390e41957fa941..03df358abb991f60708868c4cd7eaed16b870bb6 100644 GIT binary patch delta 1457 zcmV;i1y1^r3%CoA8Gi-<0047(dh`GQ1%F9IK~#90t(QxW9aj;@e|1jx?R)2ro*cM0Cox9a)&R`)NLtmW4q?bT}LyP1E#rjB&xtJ{Cfl zHM8zC&<%SxvvCOFHn6O!e~mG|BqA3p-_x0N_Xtpy<&RW##s5#Ds;gyL{-haWr{F(r z*cR?yR;qWXwSQgF0CJ`kMExSq^MyuWZQG4~I0j5rYam~3#tcm$F~(;A%nWxA?oO@a z(ir86siC&Gx>eXHqQ%@k<;wYi@PCgL=dt$+9+?l!^&%od*wuKV z26OP>{J}%ORY2C;a5NgtCP~s&RopkXvIC_-FM%P#**Aryca+m7j1&7KQ34_GK_aS3 z2q6pOFk8P|2hiYfXm)|cb-B8`R z7V22r7^o29lZS2SI|)2+IB=w^{PK$Nx79!tCrjY>7o3aN3Qm97SU48xY9I*P*w~ud zO-MF@XhZH!h{C7#M}BaRbNXmW8k}JP{SMr?>3=+bw%`}P%egTuF=_9Tt+JhgmPJhw zU~ez-?c>HL_9(x)09UR-mH;E?)mKX{|66(L(Ui~K9Z4ETM`JFuGqBs91}BJc@4>*r zJj4iU5F*SZ@UQd6xy1r?!|je3Xy&ON56wh6j^OURJ9J+D5B&BG!Ad8IFv^9Q8F=bZ z;eV0O2SmZ6gHL?5F|g%0-auKenHV_kf&r?=(tFNJ7X$xXhRmHHFvx{Z9(BI+73b8) zBVCEaP;X&63%jZTGccUBk@1JOgjbdmZmq&>#~2PLV84F9^3BgWhx?Hzabqx5o%NxM zuuj6t&^db}a&9Fhh|x=o+iSw%J;oFF7k_+VUYLmqA#9B1rmtxrng9wd)vI%0J^A&+ z^1xXfCv-cIkA-m|eDR}WzICkN?!A#1hs!Eb1Mg6)7U}$cIh;+<0ZcPr8 z+?&%4a4Qe=J?Dw}0S_E;_H?K3dJDTQB2pBEWmz_CGEiH*53GquXDf9AB;pKmIDg5I z$L|_&e19NKlF9tvA#ei#VNw`_D%J#mh}5*-0x*!6719=417?63*>v0|3@IB}K|)|p%LPjWa)-kwpfd}1 z-3Om}4F32c96Aa2d;?ziKFp=tDOd+##=tiHGfI%H=YdUVZj%7KeF@gyf#nNu{dEW# zTz(z?y9D_VvM@=_G!L?wTPpFVr@@^f4vc`hNu>g&K*rR~Pm})v8luy2csqxP00000 LNkvXXu0mjf($>S@ literal 1425 zcmZXUc~I765XT?nRKy@dNRWs)lxtpJ4iQP^QcyufEJ%kyj(9m<-q9BjL_o+RM3G*?O=MJS$!(0DLH&Upywi3%FF zyC5{LaoZMuPXNFG1VGLLMFYbEjbUhp1oh#@5h76#On@cuc!DhIBw8Kh=o9>AhR7IT zF?5gPWjzFSfoco(yU(_bUcX@cVmW z*Mrfo*UT=dVhqY?!7ohj1d%X6u^dje_h6M4tkc9A)iR%z(?*4qM=5o6kbO(R z$P78sj}05x4=Wk7Yqa$h%JQs&pDRyoqMy7cQf>qm6c}a|sitN45nEpUnomH&Pk-KKe)+I~5Q>8i0^h*U$1V*K#u%C4; zp&U-h*By~Q3ug##rnT+OGDK3EUlm!mSH3S?*JG8iaVg%bf`_k zoUb_ub%&tp08}Qy%}l6HfvRK(Mi7J`3c*jYaApG(hr-1G$aaCf3t=~c5CpL*AXJ4& z1euobv;|7S;6W)wBZyW4kp`4SLiQRs>kWCnkZKI!2x8{J9t07n!(z&SyKkjEn>Tu( zqpHiixMaS_D{vn|nv3U530)}A=ZflLZy!(faV0}F-twxam605C!P~>t-|0bUu)cqK zuFiG)#dexj25Xbl5*GQ|MhKrT*uS4rsHz+m8JJj`Sen~@y28vR^PY+@aCMTZr&+k} zPKOE0`aTEy+M0N=RHC)8a_L>0u&|gtQ4zurL3oAb2_Mx{H76?yn=`MRu0CF#zh<$c zor_Cz)4>$XD$mQzaKDk1m*wMNZ|!2jw{$jlv^9#(ndVuTnwymsmzI1J#t+$Nd2`zr z3BJNjL8-1GI?78_qug4}N_@lL=zhPm&mnJZ$jpxw<#Cac=qRBeR`br8kqF_`=)`!! z#MryHMLeCyDZ)PQ(`8#6@*NMhbq010xaIE>$qH`G{>fMe)VFI#>x&-P233g%XLPO3 z58gOzvb^ETilFL*p=5!fKrJuWG3I$qpzaQ4iN-)fER$W>m@qY1r>bRqVxTb&lw#^W zQOR^)FPfgty>dUh$SG;O*VVK|v{b3P;PXUsw{`P5+pljO(+;awQ-2co+$Dazzx7$- zWKd(ac6hy}y1aWhZF>6Q9~S2>u65TK_Vg3UCkIEi7TvBtWRj+bIxoFCbj)o%_o0xP z*fT&J79tLJ4&58h1;pp^9c_3kZTL?9JV$4HCucs-ipO*2@wN&B#Qy)!1-9D}?)eLa45 H3(oipGD{06 diff --git a/Assets.xcassets/AppIcon-Debug.appiconset/256.png b/Assets.xcassets/AppIcon-Debug.appiconset/256.png index d58bd7edfe355b69b879aa977b5a1f91a7066a75..7e65f28a0dd1ddac180f6ed792c313a90f2bc896 100644 GIT binary patch literal 29953 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 literal 20959 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 R{N*C#q`i|}>7VGo{|9%0m-GMt literal 72721 zcmeEu^;cA1`0kk?hM|Yfp}R#uYDj4iq&oxzC8VWhU=Tr2x&>5FX%uOY0V$CZDWwKb zP&yf;MEHW(MgOKI zaC!AAZhw_3td+_X}J23#2!%&TJ{u_?$SoZ>?}}tj=fW4o2ioMQQF8 zBKkh@bn!S$AIiwMxN9S+Nugkjcidl}BGmtU4&itK5Qb1lKMESM{~4R2{@1KU+px5 z!UajMT$ODRMB28Qa&t|EcgL1~(lR2(P>uB0l|#oss6$z6pHBJeSgon7N2}6@Ddl{4 z0Q)g=bs0Mi2O4MFm04JLnz3{JY)C0*d^?0Q9qU23(oO(Mg7jMmtx%8VD>?nQ(5>RT zqI3c~T_=kpeA`xZb!p_{?{9njQ2u?Jrf@2t{c~7Nu%F$U59vI?TS7ayl>kiRqf^PD zCwn*_$3VR}sefh3-_lR3Zc*#R?d!xg4K#-oC4hoS1XukyzY&8mxNs2RIqVWEY|h)I zhLYZ8&gq)6E?t6Ich4xEJkN7Z(md)DXIqoH*><%SI}SGm0gQg9t=hk;ckR#+*VCen z@U^JzkcEdjUzoNIx91N3GIYUSTp9F;OC3bY*0c6!n)Zx2ImoDtzSm&Qq2p(0`zUA2 z{3(TP`VETMewD-!To=hv2Xd|qOG~ib{3}u-q-1v{k!pyB-Nwg%5kFCt5a!$TXv`Gu zBhA;fkfr)h&N-C7hpTo-{EDoJUqG*J1tQ#v+@GEZ9j_8GX?XjhC_gf3@y$XR+5K&< z-(r_v|9n`4_GXk{^HqYK4-eO)cIGjsbSwWeP09bRH2`@+Rcp!cM;7uj*<=_8Ke^o~ zuf|*c(`NL)?GHYVk9-{z;6*#!+BpY5WCSU|j9%%+!zaYd1VI=SUOC1D08_AJf7Jgx z!y>cSI3)@Y@8b}7vvb@gmCw`VpQ;Bk@pBUS@UDo{~zP1Fw`L0%$ zgqjRhjCr=;doe6%(V?tzTk`?^nHKuA?L_6ty(#s@DD+tP}eaV8GPE= z`Gcs;`Gq%Nmc-iCAS>=$Lx>U|o9zou8ZrrU;x3-61%MD1VAM$;gr|7&gR0+vHan!` zhBgJ{@2OP9=xZO9^1?WVDo*qnKXDGz=P+)iPn_Mz5RL}h8q#T9X-It08{CF1>Wt9@$c=hpVSZhJWsOPrpxrL)y%?qQHu7F`>zAc||hQC61c zU*1#kd3_?bYkV`WXUs|j51Uz{GQM(b2_8B$>Td9-?4(Yd%}C4h;;iVh<&mnLkX)dY z1X#PiE#(=CVb42OddBOey;h=d*BaH@yF>$E46M`!DtO77}>jd9+9}1;J%P zND1{t_}}JZTD&sA|EbP9%Qa2v+1rQTWvA6x8K~n?wu-pkX~2xPU-z<}eB$KN(D~)S z#T1v|pQut|G?rL69<>hpdpF_fP1GXARi1!g!PHE>=TAy2I({XeCmx}J;i`$E?_R)q zsKoL8=BbCojOObhh|{G#db@Fjv4c^L)d0UrwYaMt;eS?*_5)cqo`Oq;-}QJ&D2f*krZeXw+1$GlihfI#bky z%)v!0g{ zlnwBTi@r_;$mEdbqT2xYlY=6V=y%v{F<8#Hv~iE+$o(EQdCVVdeGLRv9>ipfmS&{F z#1Z3`E*dkmsdd9$DH!_?f2ijq{csX`_7thc6W`-x&niEK5pcUon#PDL@9_+GHKQsv zp3$IB(=j@3tpwa3<42hFjjWLM5NSyKU`}((a%!{Hl35yUXUV?{mpR3|Wek^z75Kd) zm?@Gp&mZXwStfd%mKs38=%>9%LwDY%4rGbr`;!gLyKOArZp-qnH}Orko~f_=_n^j% zgu7nvY@387KItH};W^a()7HCp)Wr#SsJnN8d(&X?FoX9{-88A(kgVXAspLN84Z|KNoe7xuPISKa& zP+g%Q{Vy}c68z6(G0sfjF)G#oIL!xHFt)=`5@*T0Ik~m9u;|_%ZVt4ZdvBuz+IRyE z2cBdtJ|~@~sny4+NCoo_piEafR%piQRQRCc+Gno9Geb^t3WbR&Mo4WTt^$orinjdZ9p9*4ug7-g0tKo=tt;G*x4%f6dT;lL_z2$R3H*dfrXk}3$+&4{ICa0<>+ z0`+@sdZS}Ve;&h1m`QbO5|At;Ts%Cwg2*pKrM!t)OJu(~@EUq7jz!(|_Pm?=DzNhv z7lwjQGy{D#8*V!EI+>$^N9_5w{dw2LcAuv+XL*sBcgn3eRgdaDQ+jk5Xqd5gF3#UK z>;B~$1zutVb{U_aG_r}A0&Vd3-US;8-~(L>+?-U^GJ!75r-F~|%0Y?bM#ur#m1nfw zB;K>W_e1!X~EmNZhbE`3K ziVAo6C#dUkD`|J+C@CG*BnOfU=qg~=r@iB>N^_{YVl=>rOdHkU4qDE)M0yVtI~WVM zq0@$I20%{jP_IVkW#VF8#=*Dsp#JJ_VUWbHj&w;a)v%*7XcMc2u*4(jt4hMH4r=nj z*ZXlMe@`Sn_9cHn9t!WC8dq^Qo$5F%lyP;|y|m5oPlX-1?}7(K_ves!0Dl?vY-a+p z7~L^)d`#IiE#+gdca(%dxEB~<*hY3wP9e;Mw?lVziSBtgr`z~~=>u$Lc3aV;d(uSxyhT;)7mzM3E~Mc%gC zd~bd4Py2!15R=ymbz?{Bg{D=OSM*!H=sICieg46M2?Rb7l3;|12hOyJKZY0XNO<(t zY-8?_6nJzEvHLYhQX>4)teU}CAt`?MA%>K3)kVF47m zvlbp#{Pb3Dhs_|fIWF4|RmM3;LF2cA&1H_)x=?gvbdM?5RWQ9f%yPr#hyy3TtqA7R-T%^DjRH!e0rR|C+_EwP> z7$?OCzk5x18VaW1~jU;Y*3j%KV-nV#dXevwl#J3-$FS%E%06V!!wK88fzALKvgMvLxRid4aSu zf0kE_9h{U;GN5=xFm8|bh6U5f^#3-ULT0W$;6tZN10v)Q|-v%=5L1oAE zS3gb>5x?C9wYPa~3AN~oHqnwKWH;@rtc%&!ko2_A=RT_%j>OOtJ+SDoK1p-(fT|<* zW((ruKwj`g>8dkj(~|y-SXSU85&rT$?^)g8@l6F+=dOQ7E#!rpOm7nW@|eCr3(&O! zG~)&=N0!wn(_g)fZ2V$A86Q?rV<~Y#OeJZ|Ko~E5ABT%tQ>&bH zy`P-?(C-+&|BGxE{afJwPHfSWrI<6`e=T=}xWDK2n}Lf)oLahge>v1M8^!PoSyxKc zkoH-0@k^5RqtLk#CVA?eOVHx>3idL{U8&Knt%1jmN!L#=M?s%h-KD@P^0IpxlPKrp zLi4kP7xjm{AFmm=ko`bpD%~CACZ_IoKw^3qi60}HFaykydKN^D4o}mBQq~(j`k;5o zzfiEwX*QzOs0n$O!1LJMv`f9Ef*moJNZ6p|mrV8i*^$R=A}~Dcnvw~#q$g3Pcyo{3 zxKo6L<)N|YJh}tC7!O}8y3<=zyF*Qx>mLB7RCIFhx*HP;p>?idR7q_)53qPrG+-@b zx*RO{9W+|oalm3#vJ$z~*FL)4lPo+u~$5OYW2LPjyp%8T# z0idD?g#cmO6*Hxgct>^m&Ck=BYHcr(AYh|WbW+t8Q_M>SZXH)E;#9q{s*h#Ru-ktF z%p>s~9{6o4?BzperCiLXr~WdC6_`~p?*S>luEuO#J&U^^?Hw1>!zgg=+wP$h+OE%I z7dmaA+$K7z0lRQY>Zug;0dH-?CrvETkF1<=xC>mn{w79Tq;etV9A*22n(K-w&?4+8 zsCPossTi;AMjx2feNm7BQHd40@?i}7JsxL@{fSoZ5-I31I(F=Jl6e2rrs6sz$La)p z#sygz!d^~bcD0DJ;{4yGmt*3|3os>2w_FN%@tUv*v+}J-5wSMEp+qMEO!E$CO{Ihj zD~zim24$Lp+$TIX#dU(;%^x^pH9J(A2lmFR7-cz$nXu3S;}?V_oT9wS8a5Rs9gSFTjO4 zf5a*7c&IxxiH}@TB-gi4^LSMkyI*LPk)_*@A$N+gT3gzi3hcReH)fWO%BG)%f{(ZM zHCF5v*`FU$yfqcP?|fM43nQpx8TbN{vN}1K7=JbC*(BQI0@O- zq4PSQhfm=qhYZpMxm#J=zNRH~he8G+`z+&R7)fqqm9bBt3crW)>GSJ{uV@QXHa*DC z(CEv)w4G%8p9g|$-&O4@_lBRE^9V&GE5hRUH&>tgZYAW z_(BG{;+)1-G)wNL8N?ko4s6zlYTE0IXeGUbx}l`0|2CbsZ|KGkjlUv`ISdzsU~s2b zK^hWBzcstkd*KMna+z2U3GQ+zr3@d!+|Ie~+PpCXKVJ#0cOPC;`fj>WV4Hs}XoPX> zGjAy-037v%Q0#$AZW)#Lj7GSg?xuGL7YAZj=U#PeIxLO9U!4|u2S&nM#aK+(`K8d7 zwHDp}ZF>hQoJ-Wd#=O%LoA-hER8{!0=$=^FYd8xi9e}}q{?L-5crC(#6>&#n347wu z%PWACsN!&M_e4c$Fm`wHHwlo!RFFx67ru}cg1&{SIhr<6L(anvCZh$;PB#yEWYhbB zY%6qL99046*cMQ=A-%*hcSyerg#d1DqU3C8*BEV>rO6gU)ni&cxsd!J$;=iG32Qf+_%&_03YQcVvhLF85tjEpfyQyt zS_)wP#|Jfe6iQpfQ>@EYI9sq1K6y=b8Tvb-x#Uuz6jTN7GLk>}&|T&ITbW5rxS301 zj7S1F8-Xt>=I$8FENcvgiXp`L@$S0xZZI0Zt}+gRH%T_MRywf>R!>;0U!$qn5CAU> zBwBI@{kSBR`cUnmOyIRhBA@F37ucijQi*)1~ik?R%W}_){x+&pT>-)9G`3 z?BgW+e1s_!g1eMNTtqpatrYHp+}W7q-438`y-HG92_HhKYdwg)^n2GkQhz>_h_@s| zWeFk6`;8heo%thH3w9)=mpwjY>P~vl(ar1%2pCN7K^SjnJ}3(8_nCIxkxFbkK9^k7Ni@ zu}z2yT>kaFX(3uZ!xW$)QGQesH02Z{wQ@6;B!6`R#+GV19tv8s^QL$sRbaV~sz4+z z5mK@U`PmU8*3lmAxB&mWTkEP+Vwgnv!+Pffp;YO4X4ss=u)5zkNUPmViKXw{NAjxA zw}JRyd~PB2dX0Kyj1aYS5RFju9Sa)HA|oIG{kfQzU;j09n`7kHmbNqBUqRpPu&c$z z2ei-B429I`_c=H%TugbLg=o-Pe)cHEh&^_U*lQ-0E=u3}HL0V(Wmr*X{fMcD=&5@5 z`pbEMct;Vw*qXaD642Yn6l$+ex{!m8>p|3_0@?41@&*_{B|R2NQbglQ)o39FKkADs z=jj$M8*#JDNQ%2g~_vN8($)Qak3P&dXh59v2zt zZK<7_VMLUx^Vwd)Yq+okvW|}0CYbjH2oPX79VxkNN(<5$4{!=mx7!mcDV1!MV!7eu zM*Ugf{oCkjW{62tX^^D>3$c_I+WOGggzt+#q`~LpZTH@e_0F;e0hr7O{D&}WnuZ#} z749W|)rj zChDHssgw#TfXnCSC~Zyl>;-8i|) z{dnT_nQ(~3$TNLi(f_e4f?(GcXAKC`eev%g+v+8rc&RRHb7;o2Mbwd2z$}Pl75}VW z_ve0e@|w?Z841qbuMkDImQr<i?uvF@&+ya6Rgj;pw_>-4ajHu6 zcie|d3V^!`O2)YFyz7l5xZG3$CsY2*lD^cE1Y!Gxx zQwx+y>R<=RZoBaK>oR6Q$-gg%9zJLtIQRTU1fo!@WH|2+pi3kWcepF!?h&RY8n^0& ztB@GKxB7M3=RK{rJ6A6V7_)j*Y5b03Tkk=)ZC&iXbt=iNc5qKDrKcJ@%T|H)zv-zjciwhq;SF2j?J5TfvZnGezkzA@atTV2#2mX-J<1ZQ8b7aT`@xd3c@EUvgxisGX7sXK#7HP^;=@rcuOI zeO3oOlZRY>V#iy$zKg0o;&{OT6`mr!DXcRRW0gr3eK+!Dp;{_@5c zQSy+n9jmnaFPpb-P-`T$;&xfR8tOzQL@(?m;0L_FJjW#|P~WTTA5i86wZEj5Or#_t zh$aLA+D;~kG}2OPGSTiJNqNCG7{|twHV`N~V9%#~O5NG}FX?9#^YYv&6Zy}T#})U0 z=;}|?9jeS?>-uy4=fAdOtF{_PFxw;8g|^O6j-)9jc>~-%Wg=uxtkGltm$G1fPa6*F z0VL1e*#%(%{whgN*;KRYqmsWM6Iwtj+cq6bY*iWIvU=#N4&*7Mtlj5BNmISYazAF$ z74Rw$wSNsXOnz04y%GhtRS3@MzSDe`9p+_e@NXj2i*I!j%W@s>&i@4}sUNiQZefqC zvW+5H8r9?C&~q3rj!X5H;M_ZTY4H%-GB`rJtO(z^Y|}bU&fSH3kk9K50n5ZcMGfPm zOQ4CMDMYH_P6p@|}er@Agio6!-Y*e6Xwesh^mC?_o;kBt>I^ zcse;1kN07;k;ac?-* zZoj^eP`mrG*o^WS?C?r(AR)Wb8{U-xqrVhK`7~-MSEXqZ|gz(5O4K-4uzhkxvDkIcJCD*m&&K=_A|u(3cjVylP?b!tM7%gR*B zPZT2T-?rZqfX)u%Iy$tDJBfDIsiahDJ%l)Jw*g5Cy1T_@#76u=mtUwmi(u3A z9B2s+-mVC4OC?epc4}ETQbPnvri;p?6X6&yg$sAq21>h`{`wTA*tS$6j?~*c;Y4gG zHK|_7fb#^XLk;-NzTaw;X^6WNHm_W0Ot}mVaJ<%)nYXR7;uCFAeZn&(e?veOmri84 z9O`-rQtL|p$$x%6GK}FI#73V{@BEQTnBojh%2(`zP3y9}prZo6*6e93oGvHwT2-G^ zM-^gLNfY0;dYP+vGnHJ#m@4UfBdQ;JW=w^BK%Uky5BgIVC>^L8k{Y*5;UB9{(54BB zRS1+|IiT{F`=`CFD7ow%snOoHgU}CRsgYfPy|VtIu&c$Nh&f;7{O5AmkCr#q?AlD# z+maOZaU#seMGhoUyD__8s6fx8{rWBRrd#0PH6=o zlHlby-6J;0t$wC^05YKWw&~u_Q0uwNv0o#y`Z$Ze6RzzCL$QIa}z(*4JS77&gjKzhSw z_bi{d?ukra=VT~a8r5G++s3$!nlu1$kF|i8sy@#t^+Mh`KsOj-nG9R-Yhg`#Rs@#x zZ2W3R*PDW09gPQ_Q8}dkKH81OY)buNyMdY8A&DR@uG?X?Jm>3UOa4B)ns!aC&gHw< z@K;x={)9?BTo*3-BSpj!__&WsMkSPZaFCFW-|Jgl?TS-|V@^v-P=LZ+S+#Pg+B%J- zT3l`2w3vNLXHLPTiuZF&nmDPL*uA@Gw>dJtW2bp^Nnu>!BXrdLxAtR8YR#geqjAtK zR)U1xeuZP5D`=f+ppUga4>i1V4wen5;+b|7f;*Lj?({+{J}8IW;wk+p+=G4;1S#`<+MUaatVA})^j&Gz&tI3%ScUl_iZhT+>iw7y@>p$)Hy~}TH!S5t)pi_?XrMl1;lf~C9YZu z{W`QfqaV~)Dg4H61hh|l3hsK&Fez$#1rCiVt_txi)t46IKYQxZ&uGsJWA+sq?z2ttyX2$sKgBGe(IRaG%MQt(gxJ2NA90-$U154jzVi@= zHX!Q!7G6eGZ=ee{ZJfrA%f%(|iWc|8nlktTQhL=LDHnXMI4dRv zOJ8)Az}*03g(#t8Mf@QceLDdiyj?#--AuHn=b^#&cioDiqCE4}YUdj`Aptc$x(cNR zC7{9|n5MYixzN3=tnceK_uzEvP~w97FhTzY^V#ex#xp5>fBBR-IiXjI!JNMxZmQE= zlh1=iuhFxz+OTk$OW&!vT_64H%m@}53A)%6!5wXZ9HZz$lbGWTa*nWZ)?-8hRh+0X zm+1k>FX)C|pIYX`$@5N~C;p%k#EUSC#}nrxT|7DuFJ+&h+zC}lgtXhKFDiCzm>x%31 zecThBgU)B6?5D1HsH;vrn_jXdb;l6yX3&c_Hs?xP(Ks;BsUUff^{$)Tt^7}ng(aY3 zUn`urqP07ebruqyQG7SCl8lzZr)x5jT`TNL&b;c&dR5z`${GHrTGS(%B(|imy*c~y z(twX8{WTUnpVxGt_u@Ck`?h9W9nV`V5KWw?VAQ-+yCK`gtVl&5iJWMSZUX;z-fr#% zf^EZyFNXAS*&M`Ce{PwBE8G2uujuU(z8DO<$6c|Fm_6=5zBj6n_x4r(M)iSy?_Isc zKo5=x_ThT}kVsDk{qT878>F`G*dDI7e>|Pz0IT);T3QG^zoC9LS3;zLfMrpD^GL3< z_5m;*x+mpYrAn3Y@>(1OPo%+7bThVdCelrJYUJ#-X!j%N@4FPwx&7`fyjoWz^RHB* znY~OR7vmemakf#O{boB(?!!Qi8Q;~ijnqIBS{5R46 zQ27i;goOUh?vLcvT}2Z<2UYV)py4G8_n8L&wyJ=4n}&F2`4K+e_^2WkrVfA8{6wIJ zC{%gM=C|kXlcUy@Z$T~llHSMHKntoQ7kx!Q$6vvneJTh6q7M>+iy(0D8b<78$m*j^ zNf97Ly*%+8uc)X;twy)VAD0-`9)S7*Pc<<<@+u{rxpPq?kk`~i>KXo2@pygc`rQy) z;(4@d!N?9_FJ?(FDn_5&J&c0YtaZD|@Fc7r@8?fudmRt%L=rh5#SehM z%1if`!xXANY6?giTUlV$V;8j+n%b)qO~dPdp3Sli;&jvUfR25#omb&F)zE_7zhxOT zQJ&xxcIVtd9+Xv%UmBWShF|Kyyw}|z_nKJEyeo06EFFL=g_8+M-V(d`B%&$$xUwF!Q#ie5?!u;ruqJ4AMuDgr-{5I!DH|IYu7GGA{9TlQA^mGn%XeYrH z%{XyY3XSN3FSe>n;1_GII1f&mMWIFDz9_VU8R?99PWJD)o)@vJi+GG_7EiNj2meoI zEN>!u-f?UPU2@PoN+sff%4hse^gU9BFYUb`u|*K|c$K#R?n@e5`|f6*mMUSBMwoGv zdy^?U+6edkwM)S*Mh?jbD4Lici81z)90PiW`^YAaSA}mE{4xbw^Y75>_=tOpOZca4L^p^!gP)`jNNL$CkR@3TK~+#~>QASGnk$}aG=GO23JZu3rvo-; zzO$s#+f38g-lS*AyM?RtYBW_gV+C7lV?#m8q_ThD{hf4aV?_D8#Vu5J*igRw4nL`iZ$|=elfv8YpP~X*19=^!Z)+R84@xv% zq)a+wrl!sJZgbgOA@4GhtyYc9-2K$LL~;CE!{vZ8D=Dn6zaYi=PYmv^-ZUgn*i-5< zDWN`<+WdtWA9lT**24|{`**u>ZpuWgOFdLiXwkXFiN4>$&6D0mmEeM_u zrzXrAb)~wKm^**k+H840TU9(5c{*m;wv-yw!ALIBkD-)jr<_tB z|H?0}ssKOB7^w1cr38B4=R5sk3U=Bc#X}8ry8y(S2cpt41vU>}y}M{v*YcW6*Yn{Q zo{R!p@~Lmz?#WeJQDW0}X8C1rL|*$k&-)E^kW?%9BE{Gb&sj#Ej5I_9NAn~$ptF`Z;4lOw! z(r@lc-~9A7MY=}SMwk&_DR8{KQa*8iHtn?X0b8B7EPS8xF&uqe%O_Vm?qy4QWAAmf z#V;0z~c$! zI#c;>bih#K0bro^_U8U-P*RZ&Pu;U#aM-cY>Tb@%!gZ|{yPc^G5S+_yW%%e|cjiOW zb&I;N!53quR4xx>)$IN)wDg4T7VL@`)2f`mwOZN-&(3NHlM3fYJKthEG?75R2MM(I zmN*m=RX;;ZZ62s-w38+z!%b_rGS8H=#Yw&NU{4N=OGBvvw|&jOFW9~{5q$WcKuk;O<&H9_y0 zT~|T_dT7U$jzvvzq<1GcxNCvc$z;sZ!Vpsx-`4ngTC~$EnO{2l!KuR*g}ak&gReYz zQt?)Z$~mPGK85pI%BO2N+hYex54H*mRx&Hq-2>Cf2}ES|^P8)0)cwZFZ#YKNhwh3m zTNd$0{G>5t+;~F5*DFn9lvDc7M6W-iRKuB8Jl-5;_jgu-#>5o3^^M3Zd0RiEvxFIv z3C{g44KL?CvWD>Y+#g~QKx@&ogwKa&G|hK>J+c2P%Rt&Dan;1HhS2D-lC-Ut3xB~+ zyQt>IzQUv+^4!R=OEb+^BL@ebJ?HR*(+$yAei5Cvu_YPYeYL85vt1poI1z<1en99KGRO?OJH(& z?-L3ajXVLE-`kC%=BG~i<*+)8=v+H7uKrTA3#2hz3iXtJaQi@`E7pWFQV=#Tn-)vl zXUX`X^&0I%dQx$nAyJf|aWWGaB9?pUgOgBPui-N<>st+tQd8LK}!kFhzg z%HIAMP`P^aPg>RCvBgpIljS4E3Fj*6K#LtGr8+9lrf#}Ck^SI_Cp;d!RC!5`(DDp9@>k1;*X?VO7t+;zLc%LezlBOsJ9qfmD zcPtb2=Nu;AlL$1UUSkemA9cj{>-%*N_}ETL0qv~b(65i zKawH<iwhXvRMuHLM6r_I%_{!=m846?nVHsX%3#=;h3uT=iWgt!VLssA()edg?`fRoP}d zxsxg=!2N9T8Jcr}IO?A@{;s9cZ%Mv_O?2rs2HF_B!#}nHX?~iioNL|b*8N2WynkX` zW+1#HeJ>p-sZ*@JkV5XaAT^Jtv^8HJ^f%W0VNCyG5rL@B%6d>qqG8pkW1nRVC9N5%H&7r%V&S zMZQe(c*j_$f4DCFgJG?pO8tXkI)?Gpr#(C_hL80STv_AV;VMrWyE7@>L;6^ar)XlF zwrSy7$epOG<}vC>wW9muw+fTY-_MC@-=9=8f5GbvJSeJKQFPVA+H)5eBz-fGFJihR zG(9~azVM))2mBJBj!cPwf*mHUh%eeVBiGu3bbl3CT710A%(F6&jJbXD zecC)=&mwqV#5I`SUH;IL?&&#nHXpNasq>`QP?2CVi zeA{qBd5$HoFalmx2$e^!kh;;L|u54BFaUh_Ow=gG4v5Fb?CF0oZN+zYnUT$i=O3^`va&W-_LFR z`VwYUa9q-u%XJT~vpbJoCnxkY0&Ch??>`*5%a22t@rtdtv~yml^fX7*P{+3LiUq;0|T<8s%;$P zg6r?gayCvq2)wsmCgk1wHZh}!%9QOf^YKPHwkZ73&hG?4f4LAWD`GLQPkx5R2(jo! zvcW}>5Ugto2$25y#sW`eK%|XWo2i%4J$ea6%e>BT>d7?K)5#Loxu+Xr6Zn?Q`IbS2 z9P*Er%>vBwW;yN6geQFi%X%^ALmLQEC4iI)Crcdm!qKkA?~&6fzsQ3geRUopw||-A zks{v#>AMdC40-Q+gz$R|J5oSZY;GGw4pCj%jlzue0Ljm#a# zFR-henSq@e#6Itvma-G1>O#RrvM#EfT{n}2Z0QNkpwH;`@}V0zttBZ-lyElC>%pIx zr|l_RiA0dJnDPiH;&58l@^cEHSuq-iKY0Hh&kF;=mX z+|_bEL2y`(lc@Sl>x+8~Ol77i^?xNu&0IF>=TCDV(n?#}FwrbifK@_(!gi~jjt$z2 z2Ldr}>J$QaY0&)h&}A3SIM7px&-4ws_-(AGKt|)zzU`ZjJqXX?PrCj~o9}K?jaO+Q z21e^=jB#H=Fqg+6%tg_UcK&&E&&bHsqNO~thGT@d>Pl$0^!WiC;_hqoppVa4S_eU6 zSKs(H2q5sa<*BCXOv9|mxW`Sjc4ri|lNpE4t;^~2I=AVZhu1|x(VX@rcOwymhSjz{ z0?fb*o{WYjyfCZp>K@X=iD6ORMj=)a~HPL0>97PVuokveaK(D4Fqm}A@w!4qpjzqD?GwGyHM4b~+ zm!sB44yGyC*j(v6IGYs*g5EV9bmEH6Q+B&paS|#dN~e;NMRkFUvwWh8NC{>*=^I@Z z_~x~~qI5d63+caSIGtQQlFo#(yOJ525W%h+uzllp5G^=`7Bul|`x_AfS&iMTr6pC~ zFAB1+Quw-Zz7{2-zv25a+(?~5kuEpuD^%-tP0RbiF;1e&xi|QKCV(;oKfr&nhLl# zUo$_U`gqQ_?_!-!mP6&~8#&kfh2zk{xnbXBxe8~=#G_|T(1zq$LpF}fw6MoB-VRmY zJPm_p+JHBRM3K`fik^R)uOh11; zz=J&C6D=GW;MCEw7j%VHcl*8E3l}r2n!0tv{3Q+kS0Oo8u1bZRx($sB2d*?$dZ5_m zhbJ9SUkSVcYJ~x>Uv~}UJ^WYQ&1jK{xU^EpTmf?cKcTCw2onzXlYksiGBJR;q6ZT2sbsej)Q51UWU0dFAj->C?&q0?K@<*->E6c&;A>gL` zH@8d;{!N=88+6b^vYciNEz7oAU7CAJqRhv?m$5~cCS7J!G{Gsa)#ZY*bE3Lrq#4Kk(@-{K< znNp@~^Gn(*cU9BG0^t1VG4iUvRRs?68m1v0P!Ug~q=C9JWn$ydc{# z&(`J6f$F#KYi7^#U!eU9(Aum`6wPkS#JqUL8fk~-I=udf;qfs7RmitaXhuzgeR+C~ zRa1mY-4>>w2hhs!n+rewAepL&a8!DG<7{Z@O&-JB7rY`IyffaJI9dijIic(@_OMa= znoRoNe+2M6U2|e0@zT_x0O98fie2ayHVil#klv46CsIr9-+A}ry#@M)2#~36wD)2!-?5lGd)JO z^73EU4|Z5{2-HT}wK=M*I!ZV`qd9442#2@jhWkZnXf3Zbwq4W)MX3aD5$M=fM%?!K zTnuN2(eY9|Er+73D9M-s)h?ZAx)5}U0>}L_()5MmyZQuoQMbg>P+|VG0Sxjpht*$S z`aK4Yok~Dt)6VxVc;Bh^i7!33=DEf}VRoF*5b@gi0y&(#&5NLeC<$Jj1N@r%#XWOR zCy@zOD>OS&D~djmp7in+74;{^=@L%6V64sRUK_~kR{C2b4W*|tNWWE5c+fh_$q?E( z{~8=G3@5D;{B#4cMdLS6KVGSO_%y%%> zlInhE#x6*5px%03M%ExEktkrAC!Kj)@i?E6Ni~dTnNdakxPO^;Pr-ne!OCh(l@ZW) zDWI+;!3cl@v@Q6Yz0)GP^o6Y{=TRrCis|v%;3o z$?!&M7!5#d7|bTZ^lAVgkb}MKv}U_gl(=|V`yCR*B5xg5s4eKG`CA{GwypRzuBI68 zwuO)M>c1iV`B^P8?^-z<%Msyfk%Q$6XH|*}%?P*3FuGsy6a`yo+ts9nbJkwtLXZmK z7`+PDuLxk@=)qYwdx`*Bh9~wN&pwn_vilT5!F4A+M|5P|Ez#xOG=X$GQN0PhrX{Yw zLg?UDGjquC;so!@r)-DcpGVI&|DAh~QBTH)+~Gi_0FY0K=mWXrmXmCTiF2zv_rzr# z>SslC{|`%N84%U?ef^nXhykR#yE_DFP>=@c?nVibt^tOU?k)uZ>24U1k{0Q1lpH|1 z{`37k&%1eh=iIx`*?X<^Ih^`>uF!zDHyE6o;h>eg zkPfVEWaW_rc#{ksql!`?)1MpH+I_Ug#ZzNAcGOz_hEhZuEAVhSe}4DcUDUYb2NOM1QGIZ9Advmd}Gua)$=Oj)i|Gnl|a%rL@u1DQ5o zCw!iDw|?8o547)qHAuQC$ENg)W?|gFSrS-!+Li+d)vtlykr9?9g3A%=1X}f{npPI$ zcxC-E4G)H+*dFDIe_~LHL52H3AJoDtMiIaH-ey!cL==*aUt<e$iy0=%(N#fr~oqH^j$5BblBLSPBu%!zdN+ z%jejCoeJbQF0MLqoyPn9M%Z6Q8It0KIU~>uh!S99c9gFYQ5ZCwmr){Zyik9E3sc#b z#frO4o`E+5O6`6rJh!%=A>DdwvTrcxY7g9+FFPny#*N3H?Z8rKjbA9r-}W?rLVd$u zMz8OFTw}88B)8O=is8u0YmJYgRF6Vnjot^YyE(-}glC=2gq$sF_lXcv$+~Ui9HUZB zq4l8iTo2B!wSLltwl4(pw(8qY_5WJr?Y%Mk6#U4m#`xu3>xPzYK@NX~^@or6J{O%w zZA%(j(pwrmd1rmj$aNOlERTd{k&Cb@M!=cg^kQ2m!BUFznE<)qW>{`s!8+QGE-NN=R0+(!n< z%8&t?pIe`SF(4g!`r{v{W|tc50<*a9WVr$^(HsH+zFAuAEIQ6;4kMXZ_t zqAKvkH$D!mDtDAEU7}xJ+$=8z(UgdJ(T6k6Pq=jJQir~>xd^2M} zh7!WXkLdpb0&L-x>kVtK4cZGiNau2vPI7GfvXe6mCs|!FPLCC5{47yI3Xq9v43igj znJJi_f%GKSBJTn>ZKLqGe6`OBN)E&l512HPJvt z-#KP0nWt>LSnG@O9j7ICm6pR?;7^9cwoywin>{(K0bu{hD~c7@p1pVSe2Cw2NTs{t zD@LsHzl)-O>0F9*fur^Hl`%7dn>C;xD-VlnUP_AkXkLweoaoM_uffICi6qw1DE^@^ zFnobMEI>r?1aI>~aJxMGZ28R%B^IO>A0cK2&1mf6JN5y7^*nf=4p{i8xihnWdbzCtVG>~We*E8XzIaFfh3L^BN~HK z!#%xnlw2N1b`&1d+T3_;a74n}L@IeiE*TX6M5Y2Po2 z#YLV^UeX~cMrf`hfDb0A=LFolhO=A@q&OA?qnyqS8Q8v?M&F<t(wr3; zvitgP#S}6 zGakq*0r)6PU;L>_pCxv=o=tW(gUZF~`Kt2Y$pa9bZWNE9x+fu~=67!BgN3XA?p>#*%c}pbD6f9yxEf8bPq>{r43rr-@u;g|_?aoIjHZ zFZcKMBzTA)9y2*PVk?OVf`#2TP(MnkWm%oxMAHVIsv(=Z;fFs~ z_jMdVcxKR=87nVB^J9S-qKTd!_+FeS3c>`CAp z^X|ERZ|!fMy%33e$ME)T7YT~W>Vw#yW^+HYJgpyL>+g@f#5d5TfBNiR#!dzW^bJX8 zCN^pGtEC@7J$N)34bk#;z;DH`nrMmxmPsX@t zV-0gW1KH9OSy7b>fFBmKs|IR<=03vaMCb%NFhd1eba71#jDBG%CA#&-UXZtzN}`_7 z6qmwR4euZeuI8o~!DFy+XrAGZE{h!wlNe}iYaK5D+_)pY+Tw~0b`xJ4yghS>_Vwh- z;Lq9Vd6H;<XB->u@AAN5#Z={^0xnZc zjirg4MxVq|douoCom6_f@NB9$0r^~y58(ox%d8Ur(Up+JOwA+>?@zR~9;8a2Y??_~ zW?4dhwWK`xJ4>h+J~(kEw7&iLL@>p`Zkk|Dmrf@Jo1{XQGp@cnQ81AOAB?0siA}C5 zAtwpuIpmWQvFqay({9?8{SBvgz;(-FNpPEM|M=T=$nh=Js3@5glRPG5heXNVlMpK$6T-#y96@l^|^iH53imChMqE6n!RSeHm$O@Epb^< zWEw-0XU!+KO%|{;o!ftY`!ziSi8QhoKY#9orh?{7f(fVeXciP=rBPGTa2d9bVUL^yB>PDPz0ftm1@jjBw_972!19r=_3g~4eOvZ^B_9u2Imn* zXr6EvChEfF)<>L>Jh3>~v%}G!Yeu=iodHLp&^T+8mo?XKG(~R8aw#~3>fK8eVLq3*V7or zZ_Y{xxPQF6&hmvUJmw24aDlYfjKVo(kpGujZ{b*_8F+f#gPkKaZGam!yx+ODHlU!W z6B^FX3qwiqBC%P?DS=KQ8|;a()&{(E^8eiP6Uhikgcz?l4pO!Y6<40mHz~k?-H%1lEA9TNtHy zA30D&qu6H(sa3K3j0PaMPrkEJU+~v9;lZU*&kw z>FB4L7KDS#9&!?SD=Oh8uTU6c(HLi8n$;5+DLBwnpVm91Kn_dUF<`2rwYzb^(HiTH$020jyMt?%hqKr&X zmqUXqnGl*#|Gq7sV6!-lu-ikmD`$&)EnO>x`ch1 z#*LS@c=+A9P@AROYs>S7@$|4jyLa$us8%@r9^c#M)T0Cu2=|eKM`Ytt6p>P>OgT`sPY`o-w zr<}D|rJ%_ZsedckU(eDRuX*tPt8=!gq?5M11oZ;nx4q+?F%81s2^+1(yxUjQ`CemK z2Ddu23M^TY*EG*px^DRni%z~`1k~%%3JiK~!TPJ!C5nG+6r1iby@u8?BHuo!1_8nb z)cq0Aodgjt(?!3D2))eqF7{2z#f5IHC1C6LM< zJIH`{6QMBZ;S!M!Xdmd~vBhX5E>wQ1-%DTmRQ3%^KpY3)6cv-0l=xZB=C0b^e&Mm> zFzxI4pPQzxP)}N2FbYxy4-p9&97Z4Hr4l$#UEh

`F$;1mgchbFML`yW~7|U|8En)h?<0gKQmdRX>m7UL*4{CuGR^B@<+vW)_Yfo;EmD=<1=I7hy@N(f9OBMX7N?U99=ADWW z!OzdBf1NhbRhxUdy3q}xMy)fO2Nz2rnP+;iqDYz zYxpP$`byxp_q6zUIWf$ieu1p*cnbRtdSY1qjlgdmg7R$uztdm?fARjgJUS(oCSfKO z*aYyqhM9eqhXWwohp-{&V`UKC-LM~QtmWYuF;B*;=98f@^!ppMS%(vUt-fV6a4>P@ zAxlW!lb3aTs8&MJT;pzgj_NPv>R-DmoJnvy$t8i4z zS|&45!{S;2_<&#>>O9ktO)%&H?pep~Wh$RELW|)-kH9*(5^*|Ev8BaExx_RRFoQAE zQMaw>w~y`bHxl_^)-Sw1GH1}#0%N8{Ed?4PvgnIv7j~&)KRdsMW50ZG^+5IP^b2xrv7P?2 zTD%84Q-~{FY1*{%KJ1dtTJaNd)Y67jc;Q;;E-w=Ynm>|AB>=es+d4Q zvoLmq#;|-Gq=ae0?%PO}ggR`58!fPeI&Z2*?&^12*oRwT*f7l9tR;$|_}ONpn^6`B z7{WFvKjsT%Y)KF@-5}UK2AQ$YIB?}EQ%upvgYG5t0dy&-8q#Sx+q8o17)qXfX4x4kwyP?l+(ukH>A$lT3&Y1-UuaEwF%ehv9uQU+SA@dclCvz(+&f0($-H ztYi)AXT5MOdG%?Qa zZ{CTN&y<(*CFpy}0M4hE58{8pR4{*w3}8iTR*PD%+sB(%-a2+@CWH1Pp=5XdVeCxz ztW<)k4rI@Elh>qVn#4gMx2D}%6>obPcmhu3?Jrj0}cJisvmNg7Z+HN!sd{N*2)X6lzH) zsemAQ% z#^6cqT6X!f^3NmF>-qq!lD?(3?*i*qc_+m)@!x@3a?jOc%bPEcRU(!4%+Fi4uWGli z0a?Fo{^@J?UmUl1p7LzVqHRl1TxB>HBd>k`X5}we`k}2h+N$~UsrB>vy)AqP)zxnO zxUKK@PpS8kmSQEfEH!!(fXTxbn`Sv!L8f)rI#2R&oHAM%iW|P|gjGW<^8|tsjDjFn zah4k$W*bmwhgd&WlD3#(Fd}CMt#Tr{O9nB>P9(qVMg&*v-7EQD5nF#nR0tXs_CSa%jUd>*ny$GrQj0 zQing{<9jAQ_bJS;K)DAy+3$OU%mP+KFHVYo}AX# z-?`%QGr0;OFa!5lGx*mF)9)XBOALcTo_{2YW?-IgI28RWABheyzpKHM zV6&yw{@S*E1oJF&OEDkdUpxWZ2<`9r{=TdYl>>lpeoE^P^jdz_i1SZjZQyp+*Fy*@ z1A#RJ7|c5>+QJmPbxP@HFl%fM0m=Z03~K|!2GYzYt}bwMsPsY9vTJ4n%^Buj0727; zb(}Zt3e3I}@M|o-4gu<(*(7yE(SrjBV+-)N)@uaANdn411)ZiVY@w3=2nO9qGaU>t zof?56Xa=wg8IpVwDFDD6=fz+Uc7RErkF5#Y0C9N`G+1C>sgMYAlm)seK&3DTTjq}i z2n>~Bg%B1nX*$1i#d^Xz%#Dpgn1#VatlEyck_PJHGO#5lh@~n>D?8x}j`M+ut>v z=dJ;y~xO!ZPP)8}9NMS&6O#6BP=zKeO=PXLiKIi==(LM;KAR+jfASu&MCU3u@u z+QoB3+I<+F^{{8!MtvfQeh8_L2?xpmm6e+Y=YV^b3gEi)#V;R7Zfg*o-N3wsmDbdAW z6LWA+%-)?_>T5Tyx!S;GS-53s&C04FuIV=_UByki9@tw72&ZH0uAdZI&E-b1i?Z|J zkbl@9@TJ-JKXRJO*|Jj=z|VESqSqA;tsdQcW2&0(%8c17v=v#DxjQ%JABa14XTD7B ziOXR8?9R8vT$k(e zI$Z>Yu-)Bem$;i6I&m;Vv-CR*&uPDFt{4r!MgF;q^Mn)rvCqw7o zLTao7>3IYpd9FvQt!3y3IB2zU`q88;%ak^aW@h~;wk}F9jn%0jQKKv|T{F<9q1lfI ziAzN=#(9ntn@?B-6y{*qXmb;U*@QtJpy~2FHT3ymbpfVX?wJ zs6*8ttE#S(#Uo~Hl#d~2z_0Y<^V(3J*Kga*Y*e+IwU!8>;G+05Ie7TPtNulJ=T zuiS07-VM&$S4=vyz18RT3#T5NUjr`df%U^zjz0^sO_@%plP;97s1?CN+El&7O4tfC-|h9LA&2=A$fRb)cbPEfLrNSVE*xD38oy zz>EUDri3Ac0n1D>8v&C-hYa#n;>hSP^uWe!wu0&liPy2E?|n7_~+1x*1SJonodt_1BfnR6p@#REQ9WR+m5F$NOSjp!Au`|Net%E2!D%d3)Kzb-=H z_uK#CUH!>7-VIn6nCGH9;Ev3T8t~6g599Zp-Q8U^1$(n`lQTD_BCqmIPG{UWS?Zj2 zo)V@o`&!Q(*q{H0H7R*H{YOs~*Co#+{GwyZ;?=VbJ_mv)80Y5GS8j@~C)9Kl`a7cO z<42P&f4>fmtAFmAO9uU=uZkh{PBZW)zbWdw_rMgsRuuC-4-+oK`u|`W5W;hg+xJ!b z{mlLlU-5+%QPcpr{*y;c7JAP8PvP<8NzsNg@wc2cLE2m-p=$ZZC+?M&ghBcxK zc|C%#fJVysBbX=#&`>+=;+!-C8wfL+Og6f`;N$0FOunbj>t3)AJ-yTEK-`Vh85SM^ zQahmT4nfxdGuUcD$DOd|a2y`H(o9VV+qkF;%w*6Iyetn#-x~&k3>gXBX=oFtnt8ko z3`G|04JptQ*lWYg6%{qH3frA5oY3b7c3d4n#>4a*n2?1+BBp}@G03!DVQfsml5i{~ z#~=@2P}rLl%)W)+5#DBG>6n-cEK<6GGqzG_q^-kyDI2=F}>{Y0QL&L-k-_ z^Dcf@TV4A!>=_{<#6PST{M4-EW_+YxJGbVZ7+6Y6|Li~OKQm`mw(L_@16bL%j0oPJ zJh5lbi2UXKGggBEyeKZEjLQw%?u-4wK4j!34B{9ILM(sozXs`mZTFvwe*Z%d36Nm- z#x7kLQ@+Ue#u9w~`;vceQ-a>vJNB*ZEA$DpFbC^{4H6X?$GqD1B1-}rZ#WB>V*z&n z0Np+}ZrFLhDiya{`n1~MIbW;>gUrY5f0(A=m(1UPg2~r>OXhF6)9HL2{SVx>>2Wta zxmK>*^GC z-YD_`GEd+c049d|@14G6K0ce+3vb_d$7S~={{*;i<s21ZH1cSCWLOvY=hj7?JL{|% z@-c*(00f@yZc75AO`tm_W`IbuZTr&783+QQYu8|kR;v)uKzP7|NGN?aX9} z#5N0(II%GY2o{%T8W?~+))qC|xFaJlkcg1t-Ak^KejAu-)t(s z|Jrjdj<*Z#skhU2D6XV>Spp2_1EEv{X+-9L(!US!v9UKi)}DRkfcmSH`cwqSSPyio z@M@3+fob$0bvE*sucvb4S?6SMzun% z+93QQSV7yY+LL7gnY<>@Rs>US17_bys850uYy}~#!OdW7GtI03j3{GS@01S*rdgG% zFrngTM4NORbek${*+{Q68bPERF4t&+(=p>foHPA=L(>JtL`pNO!2mLdm5i93fEf&O zD5Xb7j6hL1M_WfR;2*XhLJl(_bPFuuFdi!g1UK{;jG`zegnriS+EWV4Ly@(v3g|=K zcWJg(pkA*C%fa<zy?=XGeea{YY6X&vk(7J$W=F9DN=tVD=;EyTCn)%CyCzS%#8VLlTxb(>-vTc(*m}{xN-Czh8!|6+?JslM zkt$fkvIBkBJ=)x#93X@__~!Hwmlo#RMU@anaeV@+p5@}kZOQ)#W?Ytny8~73$%W!N zS8mh#dqrOV@KS#15)jNv;S;hAt&>_ID}=}&Ka^ZA$9V+Z9!w+6z$qC3n=_8s*f3vVcpX5XO)wK=w$T>TX^I|YWEPlvkbkfh z=a9$|eT^hxDhir|1DJn-^oFbgg1>Pi3=@Jt3s8tYFwQM&%M&0nSD;KKR&u~DQ10O5$)AhDXHhcQnP1O{ySe%{*5f@zYBVT4-9MDx_$Y@ zSMHgwK=<2Z^e?Hhytr3D4uO$r4SPUe!2oe9qBOg*8#wW zV&v~SfFXb5toZB4^i7T(0PIIi+g(e7-bzb1IzX8Pd?Q(2zDwKqF$E6sFY*7(|2*B)Lvws53tQET)3l zlWQ&jgw;W~eeWHZ5SYgJGa@btG^X3voM{&GyOi?bN50m^|eoL>w5z+o76H{A$M3*2jL$p#I<2^{n6B9 zLBVAvN_E1X>cgZlqHzZE!y#)8kHh`kRb>k3Cv(6US_niJ45C}mgD0@a#A+pK>BKbc z(Ij@|m*|gUp=F0rQxC_9rAauNG+ZHfmTsbnrma#4^HjN70ubC`OLU}Eh;3@(e0rd| zXY|9#8=0YX2lF}%M>%ZEtuWLEd?PD^WG2ioia_o#B^Xmtgk&3NJkBxTfN6vk+U2Q^ zGHheiFmnTH2ATwRJ#GeX+rXYHI@9uZI_n2hP-knVRGDs*d8hP_Rh4DVY4HbpZ@ZN_ zgI8X|AAG$*yAp7*;knNAktO|1rrRp*@J`-e3;yirjCwCO7T8a_iXaN6U*NYyLtBfXtT750 z=U{!{G++kNTu&Mb>YQTW2xK6Xx(a>}jm;`CYm?~;z*U+S zm139{#DIQ;!aJ4&Fo7Fn1e3Puu8W;K#*^FFv!7~q_1(Oa(Qo=Sn1}B}aXy1zD%pU& zmK8#dgh6*oO=LCyj2s6)#IMHjYmmhs!@BX>r=jOU#4+mklHLynjZLQHvfXD;tS^f? zh5hc_7IRS^%GTuz>e=L>{pH6Kfs1Ko=B-K9a)%oQ(3|zo8`J)H z1J(u3mz{fdJ}ES$GJn#JRDb<>nu2$3@7WvIt`z|ww{96GgJ!07;=4U)WS<8ESQhdA z-USIZz9IG#Q2GqrXV|{bIq)*}-M-**0mcA+mp^yGu1q2wf(2w=K%$kc<`WgHJ+XfS zY0LU?oX)LmTLy%@mN3hfs7nBT8yh00x{#{?ggqEUVNEfE;gO;1@(LKhwN}rSJJMO# zURvuZM!mhVInroizSZf_za~3oXl~Fp8vuMvoEi@&Hc44~4acQTDD)6+lx`7Bz*($P zV5|&6R$NI>PymgAq*(&tniz)b9Vdf4gs|2y|2BfuuCR<#3^ET4A8bW-C8XDMmghEz z8-dM%?y^|26xYck%(F6xfb~oBXrcp28x4#AMo&O1U?j+6Nc0$j9ENcI(Spr{{|pQ< zWC{~leHfj1&>H9eK1k)SxuPhYuFPn zJ>9kXj4S6}(uSD@)nDiRE8?4x=hvTsrV7}q0>2lrA8IM^n}d6isvqcVt17{@%*!}f zSzt@Ks4M3!{_%ru>_YqJ-lZ!?`9C`QvH54MOr45o_5}+V!0ek#4hyO<1ydW!R~uri z4V{jTAq~)a?J@1bfx&v{i($}gMXE&_p0$Fzp=vZ$-UtF_Z2_{9-q!?v+W>yQ!*PVw z&#%%u31ic56|alHkACS)2ebQa=CCaS843LSidUSz`IYPD=AG+q-OhB~uFNBT>))Xd z{ARt-R{POxy{gX7N_qVZn}UC4Z|o;$Dl3C5(=Li%vV#02Gx$QSr8c>3EOW!-yqtjk zwB!9hba>ai0Uy1<*9&7^)-U3Dc7Fe+_;6we=Z;SbZ|6Vln175{H{J0-8 zB@Dpf;CJqt!++9(pt+#l|5I^k0r~)UD*#R(!dyTgNE0yYOVTAU8b7iRX-c>Uy9%r% zED-!0>p#1B7<~M6o$9aZM2zud9} zM2E7`D1t*~eYfoAl@F=#+b{aq&n^R~EmKR#XYO0}#l3fJKIOm~FxIefNt+GpM!EiI?E- zfa~xjlPzRvxbl2Z@`U~%+c@vP$1+X9$um)}0r=fJc%(kw2=!^%cIMyB{xc=dw%l<& zd!RplZ6kk$PSr5=20gLkkB)=hrc#f`E5?kERj0G6Sq|npE$c9qzGRmIf%-HXPhS8k>jS#fwpEfdF9m;cBG5T08T2w>`67T* zEL-nBlxG?&j;tg+u3X+K?_UG{F&9DFS^^DZ1WUH0?p`6Hj2JrEBW z*&0f+i|p%Q5&P zImD_Ypz{Wd7qIcdswx@u#)Rfx%^ZNnkgm&O>v9L?%DTMFE+RNo_>hAjhvnF0s*y4w z)?|_;Eg-lYV#ol9Wwn9oc@bX^Nu!mGWDI+=R$l@?yKS`tNh3jnS;TS z37i(mo=xREsdTmDZF}dIW^VT9*lI8Xym&Mn)Dfu=q`fN;TSma`X?x_7+&YOjBm4kc?ieSh(0U#<_qoG%d#h z<`P9<{j#Dia3gc>LaYWNjW{=85f#86s6()Vcw-@`iA#Ayy=ln;E*Wfnuux}Nraa1v zL7OKQmjRXfdLN{SEKeme_dT0}wL18j(LkyyeXYo{G#|^_v9Me@W*$+hYoKy=on_~) zpZ^6Q>7-{>0FW2#x1`<60JgvH?EQLRc($+d-@jdGr>&jyB!5G$p4UdM?<4bWVrzq} zf8R@n%4N@O!Mng8ne7ya`|JE^Nk6o+4QdYjrw<0mZE$=_{YigA{|)N`lkJ02mSB6P zL!B_uU#7xIp5-ayJ_!2KTqmv2fVso|0DW=F@DQ6@6*&LDD6;sBLu}1hBAotu_W#e` zzXjQKoo9mJzt+B;Yu=CvAVGkDAdnOR(bY69yJV@FuuJ1;byzc9H4RR9`l08Ej!?wR z)5Q1@iATlM%hXILqVzKTGT{g!qNXFHC|6ahY_KhrOOhekrU05G1OX(71dxfmo!h>y z)!)DN<*dEWNyyb*RU%cn$;3HlU)Eat_OI{%{+r_L0K`s%(EKFRN3KSbiZcs@7zQVq zXCJ$kD<3`x7#sebTIs$boS^)jqW-~S4U-+h$?7Z%_m1Iz%ffdS-shOJCg)E+%m)T1|yAQ^vk z4q7zJbt^T@hW#vDr7AL57ffIi0NN43l?-f%gn1F>Wg3`#`>z%V6m+!wCtv2VfM&rr z)^IlFXfowzbJ?JJwUBGYZv2oNKzOYV{J4GoUWU~dwYTYw?|w^OKJ}u$@vtwx`uW!+ zXJ_Zk?OPW?(VvI*$7SK3{@^~^xs$*2%>K1k@|;jF{CP-z)vU{GRh&Z-CV#=!xd z01K$xp=gIa@iQ=hYl~p~Ak+o`g6nP`2*S+YO0OfrtgS~JUkiq^bwDO3V9$@_APe+9 zgyrf|%Ymswu;%!(<0d9B#066rqc5)W$);b`;^F|p z$v8o@^o;y(rR0Yy6D^;-kn0UdCKB1~>o9C}ne}r32xHig*}&{@^$#IDm-7S~BM_i4 zz#!NHFO!@nkUTIFumu4Kx?*YK)34)+Sv~BTm;pxBWUA}?RQok%S6ydW=7FJS0iXE* zMnAK?$|OpM^fA+R0aPrII3L96x#QE;*y{jF&^&O+tLhaj4(qRR&gBfF^_>07pb~4H zwSSEj(~Yq=`LC`h^o#~sGQBS`6=mhww0CrU#2`>~oO~P3(^g{Ys{xk`n+pmB8#P znDOI8?uyb>NSKR3+;J5U{5g30p){(4NHc>-P}B?vjMGWxwmkMxO=$u&XfllAk5f>2 z&}d4S^ay~%CX5iSAE8!$Dt>X7E(Q36FuoXCy{g!6Hwv~Vh1QOdf zreU-LnFpPh9E?e(V@=}@jmO*6TVB=Ubfo*73vh3!%e;VF)XPo*7}DTlwBOfGTkFwK zsfFW0FdOJ5OJ-d69_{GP(n?mObfs0u?(-1Q57)t5h7YjMX$nU7bksd3KH}qU-_vyV z9C4+naDCH#HyUNHp1i=>C+HFggoo$z^F3A6qc=GL+mx|?`PbfrDfn7p_PxQk??w&; zU%zbX*_ZdR^a(8IvyI~Ku%ODuV^Jv^)jKU3`_)?c)yxAZ=xF(+E>n70&&)kdpTH(4 zw1Hkjbeatt*QRM;t`=+{5zPdL+`0ui{jDqd*7fURYwe175icQYU>ur%g)Nz{=j`7f z;AQ2At*Vn-d3hgB7|mv2!vNm9XEv?xo-=*1Ie^o|aK5NujhuHsdaRv`C-jR0l3UoG z-rW{%f7Lh}pXmdHLE~g!z#e4e0lu>@;Axns-`U@%69PJUtIu^e#Rz7LV}hI~E%pTj zI~cM<@wTJ;LNEwy15lpwNpf49F@I6DW#0?smPw@A68(P5q-9`VKo8)r3oh>|H0-5* z;Ka-T#w>DuFqE+Yb(}DeowN0X>jQfR0Ky2wdAQgSP;d?~t`!W^kRRd`c466u`F9`o z+~t-V3}6y&$*eU&X zp8D9x>Zkd8h5@pGh^PW#O7m{xob@w+$(-}5eYZfFz2CH5P0eXY6n*GJ`V%zUr?%Ry z#`O&FM`hpDNmA{;&-!B?GKR)+&G9yY==3l>Kl)ir0q?y2?SAHMw`&Az>%6AhOJBqM zyN#nudtcc<)UMIvGw#2P1HGHV6aWP*bvP5W(i2z^!0yWsQjg>NV;>k1>|PwJBx$pf zr@a{OBh0`~2Es4y_%1h@a)RhoraA|IY3BH@0fJGgvnAM>pN2YF>9_)lXVM01##RJo z02$<^2Iy%MpvFMZOF>97=wtIR)J&Q9st4K1KyZhGj19k>9Gd-?ImH#9^1%+I4}f21 zv8|a2gq1;ax~x^0WLxf7Jj^;N*A(8`f2>dRy6jR-+hGqT=7DB&@DfheY4>$!G7u3| z)WyX`@&LAeI@nR1s;lqsig&{0bSc@E?6k87na%#(rze~G@p7h5v>b74Z(TU60Kl9P z0o%4l`5k0mK>j>)oD`dMLzsUV2=c;iMK5B0_HYOH6b2sY2Eji1T%oNa6rcn z7ECGu27w$IeBkDNwZNIFa4fbDv|ecmx#V}`E`;E4I5B`VoDeN8aZTWs_A?FyX$g+1 z<2u2|Km+VU=ng2N9yDxU1|nozA z%z)G8c=aL27JZj}iW!6_sA+1qD}dC}+m!7!E~A)jk!qA%0x6UYaNsD=i`q--W3RLSXu`81~!5b-W~l z*>?hlu;)rPBBopq7z}?V60msMi6w(W06)kE_WjLbu!WM-zXW7JVFXjK2XdcN#Ds~9 z*Ef+-gjd<;dOuoEH~<6+rB4ZHM$eS5?6zXTN$SUupY;IVVlEHFIZPogR@v-32vr0C z$P8fOv=qxb_BUjIK!NX1!2q6#xq?SWbvq*y1hO4!#TgXtN2C_gyiV{VfWH?2x;W?R zFxfYkh}p%7x2X0X;gYR@QG^+ICtTH>ENtcUn%Lu-!1S2B$qBPR=YeI}{tD)Xdkg?q zaohuzACZ+r;T%2;1qaZ0cqAk=LMIV&6Nj!h8%3?&144Mfdk!qsQ-J8*BuI_CH4M_GcdG z(|GHL?@_w>H8#ARx^q1=I5a{F`ufi>cC8R65)FuB6EolZMzK>)6= zh0boMO?42Ea2(O*K+|V)FU3h@)Vl?~ARG=TH8f@+IiYI+Q6yYXcVUg)%ANCgTA+3t zrr$Wxj^m~=JLdUX_9udWDoz^XwaEnskoZpKfNo4Y7rXMb~jq1-11dsLj5v_V31Z7tQfnO*B{7c?DEGSJZHB8%+B5s%Q>H#C(!hfEI<2u*?-X)s0)vucm8$O z61xt2?Q;H3tvH%X;vEDobfmA@qMNv$?|2~$#Zjg*M`OLvT3*5I0vSp&CNe z`qT?xAfDur!5v8ejS)>E04rE5x~?QxLNY<2T=ZMI1JJhzrINwl1ze9E0LD|Wx`Y`_ zy%-0KLvd2Z9`jwTAj9G)Dw;8=Eb#csQ^3aE^l^L==GEO8|cFvDN1+$#eMr_%CrB&gvtM z2H^<6voPrb_%Zv(a|^o{U50qN1q;(}txiGLBv&KD9|%6fpSXU=XhB_%#5*AHe*}Vg8)i{gnYZBLK?>(D;@f<>>?7#Y=wd7|G)yNhFx{0ycUfh@j(7=p$~(2F|7s7yq)qbOr=-tAXpUQ)EsspW z^CuHdgvvo~BWJ`NLB(xDQ=S|^V9E)DJn@v%z&Ly^ZEE#|2*CX50}=}mPP<81)c_t~ zw__`T9IyxXh;bj8@*R1iXwKpv3$Fj$ci6JyQAfa13vwXkdY%)HeQq3%3}zob*&>IF z!)Hn4Wf0b_7H5RXyoq!GCPgdd`oLOv5m-M=1i=9KZqZfUh~32uz5_6nS)*UyR=tZldQIm<4J#- zx6Rr*#{_5#Bw+y|UL?`X*vocm;7^x56#o|BrruA3;cB+q_+AGf*7oM*ssUGF0j}vw zY=aydl#rfYx9Yw$#%7rS^>pHKUGPTQe-}1YKWQ#2` zETW((K-6=%a+vwf!828QK1)|p288reudJ0cz|(qBJ4;k+Y-6N3SaTqs~T z7WP*eDaFZGK-@{_ZJ_Nq8y+xV>?^$Co)^3LgvpkD_@ELxafd^QlQd;bPI#$e1(0Qc zf>sEVuTCd?D|Es#g6jnf-&2{7mmB-pKS{5RQczn6*l-51c?oiY(GS2e1R5 zrXKJl)UxfX1=%tsF5@geVVDV2=)?TGvqMp&RcGlKr;m;nto$h;ap%!OX?W`fiaEwoDHF^GA7}S6+^t zDYZdh~<2N4}N?{|L*56sP|z};^2=h5D5Iq?nKwmlm6-x zFoS1tJ{Kvwa@_ztXZiO5WWNI|(?!zX-MuMagI)L!zmpdxD3^`$vv89lYpyJxg$1rKUF^<2ah>no)YD$~J%B z;77SIA#d>dtM#nu_8%`CK7W}^_`NB_&3~K+kWR75_ojFfkb0wdk(u>lZEe|#)?&w9 z_Srb!&c`-ff!25Xo@UU;)j`f-9Vdi-0x8MmgFMD#5NcU#OWePZAG5u>O`HUJ=c6sX z7bzVbUsNAq9LIX3e|VobH4Hr&A1JN{GJ(+DI-ra-S568YLZF4ZYs$jKKw0CWB;3Ak zXR_B)tQB)MgD7q(_Ko2T`hz5d#vV}zLX^wH1sukk1`5(tvCw7T9N+i>vKL@X0R%GV z(aN-&1ps{P@B=*^+@Y7U|4tMqDT^Bkfa|C<<*ER#yPVrJs9=sfvnF!8Jy9H|iDtN%=I^TcVym|L67#y#<-}bFT)HuwycvsUng<@V zt_e9Y50ntif=^NZZf8reFOwEETvlBZs?OC;@?3qJf<7T?m)-_C_SkA5vGF|zMMV;2 z>)MnxlN*DojkN~U&ARfs>ltRG{37Qev|?1cfsjR`LjzomiMfKtbAe=eUNz7w^7_qF zc&BHE(4nCuuwGG&9uL;qrY(S!|Zi;lCDajIsknE2tVJr>IxNbjbO=je57hg zty-P~4HEjJRGd>@_)$v0O6Jn|qcD|@sp6AC2?S4{3J;BeDI^zft7IjS1HoMB36nMo z23}YYI7q zApM}GE{Y%~c_NeYg=NRc{r(9O&qKj|@idsjpOfOMAfGvz&j}B3RepMpQ^Kr^l{=43 zo>y3n?OoIH^nced$Ji zceyCRSMI_}MW)J!^Zw>*dK18p=PXWN#wlUSvT(d?6!T*l>B~Q^<&T50mR&1f!ScDG zsr=zbhv(OlQQp|NTB#1dDuEw@j+Rf%Qc}tGC$BI4|H}Q!2eMpm(z-N)LCU27fR#+H z0OM5~PZGWH)^v_6Yb%z&w{G7MgNN@6*0R3x=4&P)^i^nMTpO4f!W)#oxKvVsSWZC* z+$vJTeD9q*`UTi>dpi&WXYa`egLC?Y4+@50YK)8+$=<}(h zmuyp4{#@uf^HYJ18SP?bc~)RTjTM`-%?1F%4ohl+tvUTv+x4@@Q)_2VyA{yXRP$-m zbB+~<^v^$M^B2zVKR?Sus)Oji%X#E4_!Jb)@x!Be*Lr%shxvBaqH3)VG3}`U(PHMA zn0MaIafqUSnm4w_>Uv4R=|1*kJkyV`_OS$*IR|?i&HAo3?_I6Vb+ImW#PDE6{Pn?_ zd=Gpc^f8s-9ZZ#ieU(eUDT>pKYj}%jAuzc@0n;eWB*k1dBvWa^!61rNJFt5ybu#Hd z(GyT2u^tu!&M7Fkj0k6P566k_fcYDy2^c`wo1sX`6jIK`);OV{?ddd)6^zcBbM$p( z9t^^H0ZgK32#loNR{^XFT;qHVa%h`<5TPFew3#X)PbNBBf^3VrPC#|kgYJdKavKLnvU~-DBK5;O}FgehR3oz}D7SuTo1%?cM;F03IfXw_s zMAP2RW3_trgfM>GkmcL=Fg`GSTp6SfGczmuyp63u90bVMn}b14zydVt=1d|7f-srD zq{5YgK;YlIufe)>Gbb60wQ19oH?0^y&Rc8Tmx|xj=hYnO73)lUSE*ht%YH0t&z_)~ zZ*Q3wuy8LpTws1mmLu@@DOl`uFin>h{<%>GhmiH4(v;S&J%uP>+k{PVZS?ntHzB{v{7jpqZ@ix8X5z?T27eE~N4l&E!ugy=qkCZf&e4Mb>6g!u z!-8NFeWYmrV)$4Nwi6Y19r55Cpw8nH#LmQbG85%nK;mG6!4Bg{u08pr(!rTnl#F1|gLammibT}tgHpzh< zO?GHug?;Y80YbQPZD5WB?!s)#reM~#xe$Bg#G>N{GEQ?%oRdQ9B8eRAvVtwx9QeUC zJ-Gm}3zWttVh8+q2 z0wUFc10!^lq+kuWNT|TjxNtpit3rdsppdvS$U@@Nmx;@tl&r@L(>4Vdwi(SvMRUQ$ zJg_6CEX@?KPyw23@Mi-JnwuAJ9(yb{c)^(pX)+GzQQEj z!rv&Oj4c+iigX)1V-Q6YyJ!oa+mj$;ycPreu_?IAK_P8eR)*Ue@Q-$fu!jKk9mijA zFqV5ml`Wj@Y4+>p>L4(5>xcMz33Bvj`+M1GDBN5jCgKYkac=mONyz5IIfeDLL0Nc;%k?RWn>n4Sxh^7~u? z1ak>j7B$hp7eP$i$df|9woc-YmPvhgHzzH=g6~|$l!F(`lrXj*VAKDPmupmy_$8VJ zM1>?|2_t^hl0F}qeLn?DUMD#JZY&w~`7Gb3@x6SQuhF!OX1T5WWDr-HgExxxW&S@K z`0*#dSX>bV?Q&GG{=;v9HTw<;e(An-)9fb)?~{0)@|TuC*T*lCbLR!qmf}d@-;2*> ztm*wOOvfCI5kM>CXy6NI`~B=hMyF4kF>v&+v$SdYXJKM01mhppE}f4_e>5QF##+Oq zoUsb}Qddd_ePg$8ve%DOt-YlaYKM+z9d{^BL2!*7Q>QZ91@L3-R`_kxeyoW#bH!;A zIEBI!DDOds145vYwoL9W#YtA%EFd4Z|QNqW_<7J%=Yy6400` zbeL#N0a_84SPC5W1IjX(gCmdyW-`zjOvcddwVZJE0GOI$@C`8SN;dCC5}&R}Fhy`^ z2`t;G!aUvS}G=WQP@t(*7AQvL(FOm6zQ56E{+vb|E{W^;(&R;PnV-8b{2EStPeS{ zibA)qj}839bXeu@dB9B<;}+I=7fsbJwXjZeW*l~d)drd~=wjEhonl~lP3Hm(iD}W zd}{u;@1dyfb7!P3oV}O+qwZdIR)KYwczd$9J(@ZD59lDCtav7+Wc+&JtDj4u)_AtXrZIesc8_{z#4`OpJh2pW8{66ppV z(e`~X`du?%qja`>Z0iX~8RLUIh%xLilI;|550;82`}@W}IRx-yO9o`31f>TaG8C{v zh>TNUO(t{_28ot~_MsGaAZ;G+;FzGJ+lxXBhMeFKN-E6RL&(qGBxeGZFT_5^aq9yJ z$`N0x9s21`nz4Hh*9d<8GbA_f=D?4$I~>CmJKrbyDzxH%{jW)~yH6B=2^{3uV~x$g z0C?Z~=cJ$iO%i|l|GRCnNCZzvvj5*#(FW%O{fRw?R#^FQ!BH8#!UIZO2 zpZKL&T-pYLB^6z!h_SWeCNQ)GcK#ydQNZO6b~G^mU9s3dvs^XD(8a5G@&1sbu=3O| zoC1aeK_;(IcKK~+TzEOZ#QFb<8o}k^{1X45VA?p?W9=pM_kid}0D&+6_>S)VgA4NR zdEm0Ur0?&NcyW14z8fmN3idCBz*yo`FL!V-JMBT}q%4TyWzFS?dJr%t)*%2OsE)(T z`d*s%`xyw_O_SNa$90T^dOG4H`MM<}n8%J<>5OoNI4*CHNs9~5jrQu*=F|~o&)C;C>qJv9v-|b1a#-Pj` zQ#^6=OqW*bmwEloO!D(4{5ik#b#u;hP9N3&hwM8OXkfo!7U-&jm}gh`uz~8l?engO zc-A&SbC|k%l?!T)r@1uyupa~6I7ZLj{}OQ5f=q~J8MtZ;`HvaKv3YJZ`zYDl%XLkF zY4=fko~Qxj?dvI~>&xpoSQ3BzThv4ihP|#$E_mUaFB#@~}lsj=Gbi5R79~8PV zG)3tJiKA2|**?GuT~Qv6nZ7pm0f5Y8s#z@*spH0Oi%rOo3IKi?)_d5213|c497so< zO3$!>oDPQ5wQzYj6AZ!z0E$fsxqiV03Qi;i8P&!%=|OP?7b)c;_Jh>$TKXUfRTOK( zt_c$$(xwiSU&uAZHGG*33>~dxOB*xrxJU={WYSZ8d>QT>=mlD!#f1Zyft5PP*Ql%Y zGA+|bKPPqmIe0iGm z*1G4^rAsE2EU_Qp<>@V=t6&Jl?~`-oHH`PVAr}Pplbn+5;>9gwe<+moX3h9kFWXNbiFF}(B(7pXThXVCf zjsWi3?G@r0nSpAXzG0@7kqY&S6S)y_T_Y0P5S+c1aKP@Fe1Pm3!=NvYWAa>Iy6hVW z!;pyx?m*iQ6MXkf89V+CaR}3K>M;NWL~}V3P;g{0SOG2%2cYOk7Yux5zM*5 zWgkGU3X&>`@1PYN@*|zPEm(!Y{&`XgAIyWRIeQIE!Ku<7%)<-@7h{)x3wr8ee&44L z&^$n4FHMkAO|->)Dw>n3y}de4&jJ>rx*2#5pqcJ#J@E7Px0epVdG8 z-9conO?%AN8>ky~g{`sw`;6`IaE9LwRBAa@@4tJ)AW1NQ5nxBh%p zebx4(`R5^M24oV|`?G)7j&HPSKNBOqCls*J!*lMRBrUNQX~h|!@OrWz zpaQs)CO06LjdM9h7L;Bly*QyPZi$SqD#yvDiF8t(q^3^r0vbp~kZ?|_Bu-%&cD3i_ z;1H$()qx~Pm8&^7AZM16eh&T^7u3BZ22%A^ z3nt(^DRih})zWOF4xqFim^^?J-AaTAVJYE&xRyH+U0>_S=_nnIgat@4)m6`G4d7JvxW~xKb2dfXaTBG(e;{15+@Q32S9UFeZ|% z8+n3gc0FRYkgJYxB4~~RzIpRC`X?YK-hSKM*4&?vnG0fT*Hzto4)kHq22JNu`x&(T z|A?lRxlhw_WcK|OE>@(*jOEHu)nfY4Zcv@ivA>rx-lU1)z!Avpx31()vt^=8{$5h& z8^y6E$>!VUg;KXJUe=t4X?b^|xeO1pfWObsva(_lchRT-}Ye#WlsikGH+QyQVlT6aznI0JmWS{%~tKbGoFt4(0yi z9ep3>PLcp=Jvw+a!qz)kj;xP{7 z^Co(+8;Ul79}7!X`MIc@BLlV26@v2t!X~44Mv>x-`~YyKP`gmE7SMFZX{-bcMPU+W zq331B+?#ktv*!q6Fo0R2-8j`y(=$rAG*k%uNSt26f#KG55{~2zS}+x76w6e?ybCig zK5@68!e27JBA=w7-bP#-?exw!3w0Yk|FD+K;GpG6gqOd{&C%9B@`1Yqwz^ zQwM(3H~}$pU*?TxIw%W#&JLwI$8rp{eRTb~s1$+fnu@~#wY9qZEiJP@-ofsfpQZop z+C%jl(55;7c`aB6+ezm5(7M_xQ=CnzjqBZ#juR*__@2OL=5UH8KhX*_UhMgv8z?2* z7?$5KPDL1I?3yWkk-2GRTytTIg&^RdF;hR>b6mC{Bv2xGJ6jAm9ih%kPI>@-?0nea zr1LNVw^ZVF-N4i zVN>uHn89w?6K$7G!9%V^=?nn)x?W#&U^nIxoSc0B!4ooRU`)AA(0&a3&h&RBS8qxN z+u}o*kHe2lEnnw{VCo1o;ED66bH)E0An+?N{SKH7JZEzMeGYB2Z{TfEdJO!&b@#UT z()Vtfe1Pmf4vd$X+<>p;6+v#ELjPEK*~(48iph$L7q1I8U2PO)K+0!w zgYt|Rw)`;buhk!wvW#5C7FXY=6+k|1%cpYCM@zmmqk@~-he@b_Z5x#50Tji4wjgaz zh}US!6x!wCnoAi3!a(pc-H;sW@#6YT@vY6*^qcrRwK>qd?guO6{1(i?H;UufFYs$$ zM%PbFDjCVqz~|nk^w!1OlJHh{U%x1y0;_G`fob3%Hw_G6&dqa2kvEKq%G!vgHY_TH_bdjj#-cP4nyxcIwX$pZ(|YK>({a@MDEqM|?P-2MT?cBq z1P{}S7S!m&?q}MsphKC*#@MFobp0Hx<7}`j=Co;puo{G#Merh$0o9&c`>Uz8L_9@3|-)F5Gz_i$>KAz(E+IYn*a^XKD0?lAKQ11ZQq4c&|jYj93G$<^8*6+{HBFLBvatYQAmWW@CTFwWu* z^Y~h49aBz@vMAND9A$Y}B)`};f zGuw*G!?Az}Ve(~#xeIC80V0gtw(3D%!u+f`7-YohwFG7BNlqmF?On<^O_%KM=yQ-p z11RD@i&yr|?rR%?lWA0Jfe+BX$Drx}vUIu}1RjA27=5nD#UnnAh zhp&P(z7x@9roG3_Fa;En9d^%Y_UXGZ*Pw~=>I%2=SQX@Ci^hTcFXoHMr>NM~J$6ag4 z*m9EGps5;T(!p2(A@(0fQr#~60WZ^Kb%VG9$UtAYcwPU&n*)6VYq5?D@OwK4eV2J> zXk_2U3(GI>CA_G8e80}X&ny@5?%$(hPhpw>@OybBZ~rl~fYAOpi6|EU`7Z(dI0&To znKjA5A#7&Yrxo??lO+??HUR@Dd!&zb7WHJL)cDwfx-;%7Iyj(3Xsw>xQ(WR`VuC+< z1ZkurST79W#7>m)H&gxA(rmcf@(NYs= z={f!7LV60F>Y!nE;uo`;Q*or-^DJP>Svg_K(Cj(YHj9~ol>HQkH@m9uOLG9%oS)kEHPo}uP8p8UytaByf8{w=f90b@Q<-zc^ zU;~!I_W=|GC56{>{Q#O;;JC^kX^0(wxxf{E%Mo5H5MIiKw>bMdE0IwAI3M7_BrytS zo)<8clgQKLbvTi|#EY%nHrT-^maJgL;1Lx8!pRs0ff($ckZwRQlEknnM6&M*!whn8 z2GG-PKC&cbC&RDb-gt!m$aFF)o-3LPh`u4-27!0tF9ylBX zvN-~Y8SIG{oCBxVU6FTpv2taFmV#Ar(Iw~7Z<6?}7YmmjeC~V%xqgf!n}Ang+V$hd z4u=3oumJHsYzlsPkmm`+Ttk2IliURSuwefx;0Iv$UB)u(YIKv!uG=BG6hFJf+B zFj)BiiHp~($X?L|elsAjAWIBTES_C9_uAq^h)-|u$EOst9xXXWE0aRknZLOYVN-)W zWS6qD*! zd@FC`B(T^!98-4>+8o)zai}c4JUw z`Y}ulpIu)S>(QS4X|SX?804iTay|x%y0=I4a+i~wlDxydBMU=k)ascU!5kCPT_Ult zJ9a--I$K}tx-^-Xtja6|7u(Ra+bv#2EF78tWAh2PHW@*$cRAlORO^VZ87EtH0&&2p zU|J?0i-=NA>;Ov=J7%{C_$$#Fn{qg!Pvs^Y1y+!qyR(V#w4XvCyDm=#6OIf-y8x#S zPZD4oors|$mlzg_9|!?vkE@EPAkm)mrQ+IkfRW4uLiTHA(!rn$_6c+x$alr%Mji1P zCF$4j;JCIKvJP;XB9W#OVPkc<-hR|7imQ1Xi8?c=>%G~`;7q%7`mFt~5b6{zmK8TD ze>Iap_HOD=he~)IFG*KVlY*5K`43*!bE}^cqUDxl& z-|0Hiy!&{_zSe!5$57Uu6)}5`dhR{2wp(K;zuUlgx^H!t=U&eoRM*F)?R_ozIqzCj zL$t;@>4~2`IWB*kcHLd5cc~(WiLey!SgN=>2wrqYYIeVkU66Bel*K-{{!r^Ea59w& zC*cYq8a7_RwShAybin&*<)pA9b3Q+cUCLr60OLgW`L}T)X`_?GjHKfw6cP-uNkRwA zCQ6Ru@H2KpiJ_2kl6qzYTOCdb1M>psKn?wD5ta-N_}~;VJ7X}87<%O8wShqd64*#B z7u%Zi0CJyfE9v6Dt#kw#*41n?S%M-x+GQ(GpT?j#PqqiL4?y_yu%Eu%BRT5qK>=S< z9HVzPDz4mWE~{CA-!TdPiwaK{NX6SE*e~$Gg9mi_^l4KO^}YlL{vH|FeZyZLuZ~d$T>^Z&-4E zqtuM}pCTbNGl6C57i_Q6z?8q)69bH&VlC?m0N`8y>@`y*IxpdN#$;6aWlCf3`P9zd1P_WEsiBA~> z*)Gg1Enutwl-+1d>=mAIvQ{43(=bk%3FH(p(B=W@z&JuYOOSdT{pWaWLTY8wanoUF zvdn}s1J;889w$1|m@vRBBrW`fk({LL3z+F>lIpQ1GfX-fbBml9*3W#W3Hu3VZ1l$E zf}S5bU=ejncSNH94IZ5F;WDd%HgxTS=BWeKd~EHMGc{JamO9Thb8OjweVnbkRP6|w zVZ>^_UuNGj-<&Rv)fF40>xVW6UTu)p*zI$+M?Z&5tH5M=9u*^J0l$3EG<7^8>qPCg zOVD96ip9mKKvDINwVj%&ZbZdm)#x+(IMJx0E9!mg>9D8U3Q*m=272a)b-#7}lxKYw zsg|p19w?g*T~HJF+HEyKtPR>LV612FV=bVF>g}|L`m<;5XaDu`;{?)>%PtBiUecs| zffm>T@M@*0@PPsSOc8LO6NE~!0tQ75EMGz{2Xuv;7-j-@zf0=I z4WhTNk;zr|)|D&z${W{Z5&vQ7F2+Sl8{k)1)vgtf7K1|b2?C@nlu*3RM3u4Z)8+ej z1pa=7mT7soQUkF8VmWzC!_)2gr2u@o%5h{p$z~n?nEAVYg@Zh1r~E4xD7bmjrk`cFu(DL4u!^8hIO?~vMjkHnt0YNE>^l+*xBUk{omM*!amDRm$; zVNz8L0AET-LQ?8eToP{FS1J^u;|bA&COZk%V65rrZg)g+yPdfp29NQ8+GF;IW(rRG zMSWmX8ZHji2nLgm4AdwrYHLE6a@qyX3z%uo3f0RZ;hOm#d;u|4)2Cs}AY8G}AZ zsR)O}Fc<+8st7hHh;;!*LWVBPznXi(jDQ;*W4hdviT;%@|orfF@1Kb2giEG ztljF7=+=5oR5NqVLZ(L3TGL@twQkO%I!F+8P-$bU5)9bCtMP~i0MJzV(bYgfvBEw# z67_c~|Fl3SO+lzVI$ge>*1?UR7TaZCbh28Rj@2Hd4ac-Kp7KWI*C8K(w)&{mR<3}O zHkruF#zb=%4S-q$s^xWUpl;nbzIm_h>rh*->!0~UHAVlz27LB&Vcjs@1l{%+^mK3~ z02OoEm6`4yd!oL4@_zP38D|R&_yF?SIS>Tn?n#IM(Q*ta$K~OurIhmVqPw0aT1g_@ zw(A5@>PU{@PgNYGAP6B$+;&E;a$8OZyRs^W1N>OKFyne+(eb>#2kU|GI4iuG;AlsgBh5&+G54gS96R;te?`sjcCt3@V)4-ey$o=dJz$@f#>Msc{ z&&dHH89*PWfZ_Bn?6! zlfJ$}f-8LR*uL~V(rgC4@+4>dU`^6nH%NRPCf{#f!Q2D$@1?I}``1Z-`yq)p-oWj{ z-xW+!8#D!Xg}kU9r4sD%g8d9b$e&Drlqp|IH>=`dnlHpY!ON!#sU9uAc*%o6%J0-* z4C>!WHzu=2TN_kk_VQh?O=~BXrR5sOOlt*mgcShb#@DZitCvar#vhW?KLz&h^&4j2 zhyBZ<^4T~~(nJOqCgHMD-23m`bJn`&#E(Yz^yPEs=)ElnAeaLB@unR3>&ibx&Y(|f zG{+(BdNP?=M2K!3-@1&f~FG zosKYRVOlUXb3n-5J<=~Nk~VkzR``GNHTCH>gsazq4od1ur!f=^jpW!2i_a@Y4_if69NC3{D{tGz>U3Cm<{9x>A z2UD0(K$J`^Ui`fyjIbwm^YBNOKQD*B4Fz#=$EpeRw$5S1=D}nezCz;A}XU z1*Cvsnb@eLeb|Eou!o$6@Pu~7uVbH%eg@#GP0-e)YUW8BcT@;NOo4;VdfBv$^)QM? zyLLb2ar07`(9Cgmn}?fOD^RBo@6R47HQy&qHCA00jZMwW({r>z)=U7#A!BWxo3ysi z>wC)CS^HSO>j5(J?uQNPtnoI+p_|u(nmPNXfhO8&i|oEh*RHEI>Pmju_sYKZRSUZO zXPQ1o)NJNr22^V664io1G*MH2&O@Hl`&?e{e92rf`uM!_FGCIn(OlMz)55UVhE5;~ zB97|MQs&jM{lN|WV!w~MeaXWp2!|#kz**q;gv|$zLe~%od@7Fmi{B3B17lDAl1O{LM z?=UNf!$x7wW;6H;U{dZwKJEc@_3-;C1PwbN8&fc}&|R>C3c&EV95|1T_0`GP>?^wA zP&{T|Kz4Qxi#fp^iDNKtsF?Eub^yC3dGh82gc{cg?zZy%4oEOF$Qk*busM#}AdwgSiv+wd5 zKCE7D^E~FZ+kA%_{oBBE+TZLr?Q?DdvF7$euU7-0%$!dFxTijWv+d7~ekM9R>ekw> z=k0F^nR(ZFMgf?oiBkQ1X$}ypU!N(6)ONqN#!K~7FM3`(M9=1M%IhZVb~H5;+8iv2 zzkYPu`AcbhoN2|SVK&% zz)>2jE@Y5nQds#a;od1SUd#NOEgZsYX||LMoKS@szC#G@I}_D`yn8sN?3g=dIi#P4 zMZ)Mn>r<;e{djMn1uQF^_~z7P0fLKb1EWt^UL+@;g3Ogr%DD{OnP^wO6E3H#gZzr} zI&TSL72xkIt`IYSe}VNE-Xq-}+|g_b`rv~*WKzVu0JG3niO&2fsc*q@#7Ut!58&6g zuIuZUt~hrA1pnwRDGvNFAn-OhZ^5#34Rg*dx-BnZ{$U_^`-(C3+HYU&8QY+#Zy+yK zIn~C4lPe2(_Ki;i06YSJzrstwKvefQmxF8)E+UhJCBSourcH$K)p{{(`@yfRN8CCtVlW64d$*V!b@AN1Yb^ODQLaX<*CeBr!JyI}t|2RV(;4!n22myh8*UhKQ) z^Y6$1Z4T}@AvBi;;_pK)6#fdCAQ6rp3pp;B^27&tg#> zqz7=g12A|1p|pU{PMaj>@H-%*nHry)5j0yBjzuf$$q7fisjg&G#~7^N4z%BwTCR7% z`TzVj12xWy?U3e=nE)I*4rT@>?6TD7q|hm#9+!=B9pR;#14=TmfPj3!pqeX#BQsbA)UlG!vVG#7A`!5y0D8G#de&aoB? zYPa14H}-E`UbpFQdl56ha31)Yy{?CW<`OX3=U9!Wah^7VmLG2m6y3NUH1|H*&r@w1 zQGGVn^Y^iLw(l*K=TbXwLGwUn?U`+iYi38PZ!?!m>8h{tT4#%kyFb4YcRF8u}y1wS* z&@B$`z$e!S{&*5*$D!0pFoF@=3$V>p1_1-eK_DxTUz{lV<4CJskXHlQcA>t8J(HP% z7tXQzc-&X+F;eTvf#ixHT#j=aq-ft0f@=bwh?HIkq&SFH%q8^z067q(yZu1j@17Q~ zt+PMhCiS1Lt5W7~-M**k>^Wjrpe4xvGXbfaU<|@9x5cf=Mg1nepBfDGt;?77-JAKG zaUMX>Oh32tDpQv@`Sa^APyrZXUQ(Ercpg$jHEds73>akaw*jly#uNlLt!%}!H|I(F z2BhXRG2xeu|At`*KZ-~~=OdVV#d|Ca z!2X^7`w$54roqR`(AOyp#ty?hvvMIO)qYgVz zXIzGt9RP!XCc{WjYmr0ggf~I^0DMC><_L29RvvBa!ouT0<4r&bc4Q)21gkA4b7hBa zptDq&U=wLFy}}GU0*`=6#l`)UEuTED5)y-6@ICTk?MO&CpR0mkCnrsq?Ne@^V!~lS z2k@AB8G}S2xz=b9XA10}4q$cm@CQ>c^|VJ{)9ZgVB zfq)qhNA*P?&V0>)uVS*dt6t1)$J#a*@I3=GHJP^(fHfJ-*>-b~VuMI+?I#+iVFT<^ zajT8H0*CX!pEZgmz_q}*eJv`6>o9;y^>Z#a%+2%HY}eXObIcL*o~J{vyP!Itwp*Cn zwwQSi?AgBAM`QC$UddV`wQG-`Qp9^J^w*D0cz*-Nowd0HHdHWqB7vf4@&aOg3d{xz zFr{1}1X3fCimO+G4|Kf*O{N?X%((zPcAIn94v^_l45I>6J!S&E78e}GZ3F;xK^#WX zHNMk6R}UUS5$39KLYqYNTq(qugN^?&q(|Z_zfUxdw8;w?z%<-P7n3YVITBc^P{$+a zi~xWKasq)OIPH3(U!hq5bALymtX~pH>Qpo@A zeNxXC#R4X;+!GJpNpX2l?#7o(09wK6RfXZRs`V3$v4k0Uso6DV#eR!w2q^< z0Rq7cau#<6gBOd{TsrOokl!8rJq9og)<`cwD}M0r^2jiE1T(}_Ql}v>x~)~^0`vv@ znOYCmGzS8o2=m{=L6)ZoDBAU(QZ{_bCYn9^Y-=Dlp-uKC1Mwszwb1J7J{Z3+?5fc1 zs&SF{bbkc4GE%DNv{e_v50YH1w23pq&^`p_aAHh86ho@I!D438DiHhAz?6f(J`2`_ z)APl;M=>)yiYtY1Le(_o3^Z)Q$&$U^Ac0%xTsm`ry;W&SH^ zfe*=krVn^;2tHA6*Tyy01$6%Uk*jpZR(EziWr$nB&M6OusF_Iuif_Ap})a2GGx1rXmP& z_CDBExr`$idQU5`_8FH9_8keSBeP({9)zG3RhBw3Gc>>Bhu{Gf^gw8B()CzX6kHdW z(@yDtEzQ^u76b)NFP=aFm7cLQunVvUg_QT}dV%!XzKEPa46;B^SgD0+I0M+@+UX&M z#K4c!!35kc2fj_(^ptYl@I!|!x+Cf=_C(O?$#JWvx80svqoLFD7Q|;3q;tM6onGe3 zHrT%t`0E)94D8?dJO!nmW%SaSJPGvU(F1)B3jF8Kk-$74agLhPzw9lM{PHV#-q?kU zYxWD6rX=Q6WbKUo9V_ z#x37yzLo{>noDH!6>@*ii(nLpdPxm@PW2bNr%nTIy0Uu>Q3$gkiP^Z%tA0EaOBUjHt-{b!WH9PIqlyLo-!8`$ebFn>3J z0MA26ahjM5c*6$|xVL-60l{lsS8R<=>jwaXrIynE;*YvM%ZM#2< zR`qt6N11T|2UXE^PFXu}-*ak8(^_ZOW<0DD{kqzx_h$=*hVQ)j9>j=9lZ6#b+SV~Vaq?I^U!-ga@m_f*T z_I%`G#Ev|f3Gpf>xK8ca&@|&PPu6<-;d&+`WX8$fj) zsCm}!d41GaKAN5vf5>XTDf4$&zCwFEwf5(X*Y0D=s9NKx{VrC_@p+qfjmz(4Gxm9x zZ9Z#kQ!uJcFp9Fw>-P1bW}nS=ihiV+-M@LvyzNuKSDd@GOD%9k*y9kh=Vp&hG@dP* z*SD_iX9HI|G9EVF;r^=ptDWQGJs4*rymVQjvXG1Zzygp)U;Nbbll`YgEP+;y~_mACmI{Liv=gn|Y3_(>O7~AgpY%0?9f5 z%($RbPGW78Q!Yj6gY7frDP1zz2vV?tteis5WtK`y>EVD15 zz<%Q~_ixTT)SYZ?5vM*H(GIDs6M`fJ7<@b?#aR~m{XRXJ>?=+--yxi;uc<|+`3gp z``X{DeN7Mu^QlM7-_i03T=>DP{Jt$ARcbDW0YCFyh(nVZnPkw7{Wlu>u%8512Dxu;0fy1?Iu$(Mm2m-XSOu6G_#_D-L&bH~rA zR|e2;Pv;4tPeU8LvrB?2gq%WyoF3-Ir^nO zAY3>AWMQ-?I42-GD7)UGioydmOb;~I0{-a^TN+qc4z+_mwjz$O(`vP2gsx^VX#8Tp z1ac^E%N1NTSq~xn60m|T2xZO%=s;av?P~Jv0knRU37qLs9z_b>K$@ z5D=mQBjxsai)fCa(MOGOExt^_SUytGq(!aoGv_UeTDHRK#|E6DaXam?7Axh<*z>d0 zjZSN4+&FzZg1WruO;E3iW|Y$^FeNB1 zwt1=Utv<@WeCp@w^=_orG7YP$a5@sYrI4!agAfCG*72oi!w}4Mih>C{_(}>Uq`>Am zp6fV1D{(<93x+W82CnOO+ah2$5H#R=9oLnP%e8>z0i!bofel3ND(H4#`y$1jEuJy^ zwtXM)Fp*rSxZ@MM{fam?$p8drEf`?TX%sn`8%O|5=_!Z>83|Ti0|36|9!?daYcD1h zq|5|%IKQA^MHto(IpU;%-7c-R`ck_+kq6KX#IW7hfBEP`)oug*PNdj_33v}Omv)zl zm2&=DIt`7ppC^E>F6=o@C4=9P_gwe)!S9jt$~wspu<1qt{0?pdgx+R5(QBrd09OZr z{x7}(#*YI(Ogmf}?(4=HCI9jiW!JY%?mns6?0c2yt?Pyzl;yqNpgi{XmU#c)cC zS2=f@bsOf6&wr^uVaumtkVngZoTW?)le5@mhH}l}>orGKUW%8%uzL1H^sMjKc>ik! z0KA&7VCJt35FwgwyKWgEa}(+f%6jn_SKa=8}_GH{dur*g+1qhhp_u&j`5@{y7Hn%@jrt zCX$&yHm2@Emg;ySTD=vMKo!E5bI1#300Soitrt5_6RrGq0CNk84s`NF`8ux+Orl%V z367$~%&l$Lla5Lx`vP)OSB@Rze1>W2(U|jlIjNVJvQWa5hT;q{VD~s6*il~Qfid7R z@tg|A@wg6fW|GO`D-?eP0>E*7*r72k=l}+32hTttlrTPm8RZNiCczib!^>n+#n6ne z+Vla-|C!eArP^}naYVHs2Ai>(w+|iCcP-_Ko@Ju!W6G%0xd9(ld-e9`_FeTeKX9ZP zLxmuViX%2Snb+r>@!0!SeuG7u_Sm$Zy^izxX|ks?`&cjejNeF5c=1r(I#cKPUGA^r3(X=_{BN z#02Qn=d{%mc2|jM2*b#G$(SMKJ=G8p7J$ff?`>$5#TJzq218M+-ARyu>ftbz=qA=gyr=2LOIw{rnyI z{Y?k~27Yhn$6bcjW)ef=h5olJ1a1+XdXe;ByhzTg!@Qr*oqQnoKGTw~b~gprhBOC0 zAd>=~^G6Lz;emIZffLsR`B@k8Y_g*FyUTj*%(6LVID)zz@9CfI=K{e2`!@o2#_V68 zD}%H&1ReDA)=;l4jz#FO&>QL9Q9Fz9os&SbpI^ssYmSObaURV6F#zNUI+1BTcKcBO z11>m1#~2hwI^%@UY+7L<$pU%INgo_f^PeaLw|2a=0~!&;p^7J*`T@)#fN7FZk|r)Q zcPanjKR{0hL>~;q3Y??IPUbo`W+qLO1ZCKZOPkt5{)vbofv6^Vd*-aOM@`8nY|c#B`_z$0^_*F(P_?1lE~YDT z;Vh=Rh_WxMA29_j&1Hr>Ywo6P7N|B9dzZ$iuKJtxNTj)#rP*e=u6pOhyrIoIs>0>7 zAeOA_)i@XX{x*RhS?x{liKfcbs^^WpK47hzU#y?i1-3U>ZHXrA6SZCH_r(I{wQGlIv;HHKtM6{TJXiAFrN1wzD^%+>E*>@2CIO>iy(5z}vSyDcip1bim59BVJ2o z#Pxrn;);M{xLmFmtVICL4^l@2QRX;c-`bgUJ(WT^^U3Qw84SS&G%;>u-wsEHsgnTV z<>CO=V0IJ6CntwC83&jZ1oH>&5DXyaqh|jwa5)Je+7&dRsw9>eg5enE}jl22eqGFcH`ZhhlRuAhs!-=nM@0IOUnt zSRw?_<1{eqkao|XfGjLnzk|Ng3iP}H+3Q3l2~o~rSAop! zcjK!4#E7XPaMfZ0KMl*LWSB?Gf2_rh3NDv2DmdT4%4<{}L$fkiG(KzXS?lbi&>Pl1 zoHLLCAQ#?d1o(|VyeZ#&lL&&s9QyG6%YthGb5IBeeXPMO{K{!^nEB)F%>EgM@Q0xB zFW#2tTg&d?>AGS+z_pVkLHyB=AS}|tXC!WIihkdfgZI}|0wKV{00P%wtohll)}7_P$o8Me(!+qxcl+nl>^V?2kkPU4W`%B8+eTL4SX?91Qf9JaG+%j!oT(x;f|U(d9m=N;47-el>X zb#CQ&n*PeBEnQFYtZDZ8R&1Q0n%m2iPb$YV9YXE83woxXAXQ+#J_g;mpXJxg6~hM? z+j0;bpYaeIZj7$Nw|qKRL^HZLS$x~ z44PeY?m&|~1I^cY3d;(aocrW^A5h*SZ&gS1QVn5Ccbu3sH?DNM9n-lJ!trI6s(CGyh zHbm#vSH#mhJ2VNF6s!^S!Tp^5W8ri!%Fj~V#K0nlA z$^w?pO^$b_({BxW2~kav#dBCZKhv3Uo+8m)<}qzElXcaqJt^swcgYvSm z)Lax$v{77x!zh8dVAukdHs}&of5OIebFM)NHuGp&J=13nkZ6YO)b+#3?m`?k_F_C@ z&eocL=A3(~X|}^=GJ ziKY}~{pM7q;fU%dUtA0~)xEQSoBX#YKTH7BKr6q$AX)w6G)o$mRw!8b-2#qlJ3@Cn zDOyUA&#oF$s#c_=I{~y(Y39Z3!UDxCg(?W1(DNJ@uuOOlz_e{P&i_p+> zp5agGas>X4mc~*7zoN$P2Gyg2DgVyNppAo|sIz(eWy5-DEOE$Qv!=~vgcSCtJpbSH z`!{dj5F1x7tBaSfYpxCa{&xW00QkQD9TH!8lN2Y2?qeO-1?D!`6wEJo52oM8@7&SP z_b!?-b=OJ%?r)P}06EH+&A;!Wo%jEOvi763e&;F83++Y?xC!b~YxXo#�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 AppIconMode { + guard let raw = defaults.string(forKey: modeKey), + let mode = AppIconMode(rawValue: raw) else { + return defaultMode + } + return mode + } + + static func applyIcon(_ mode: AppIconMode) { + switch mode { + case .automatic: + // Let the asset catalog handle appearance-based icon selection (macOS 15+). + // Reset to the default bundle icon. + NSApplication.shared.applicationIconImage = nil + case .light: + if let icon = NSImage(named: "AppIconLight") { + NSApplication.shared.applicationIconImage = icon + } + case .dark: + if let icon = NSImage(named: "AppIconDark") { + NSApplication.shared.applicationIconImage = icon + } + } + } +} + enum QuitWarningSettings { static let warnBeforeQuitKey = "warnBeforeQuitShortcut" static let defaultWarnBeforeQuit = true @@ -2661,6 +2715,7 @@ struct SettingsView: View { private let pickerColumnWidth: CGFloat = 196 @AppStorage(AppearanceSettings.appearanceModeKey) private var appearanceMode = AppearanceSettings.defaultMode.rawValue + @AppStorage(AppIconSettings.modeKey) private var appIconMode = AppIconSettings.defaultMode.rawValue @AppStorage(SocketControlSettings.appStorageKey) private var socketControlMode = SocketControlSettings.defaultMode.rawValue @AppStorage(ClaudeCodeIntegrationSettings.hooksEnabledKey) private var claudeCodeHooksEnabled = ClaudeCodeIntegrationSettings.defaultHooksEnabled @@ -2834,6 +2889,16 @@ struct SettingsView: View { SettingsCardDivider() + AppIconPickerRow( + selectedMode: appIconMode, + onSelect: { mode in + appIconMode = mode.rawValue + AppIconSettings.applyIcon(mode) + } + ) + + SettingsCardDivider() + SettingsCardRow( "New Workspace Placement", subtitle: selectedWorkspacePlacement.description, @@ -3526,6 +3591,8 @@ struct SettingsView: View { private func resetAllSettings() { appearanceMode = AppearanceSettings.defaultMode.rawValue + appIconMode = AppIconSettings.defaultMode.rawValue + AppIconSettings.applyIcon(.automatic) socketControlMode = SocketControlSettings.defaultMode.rawValue claudeCodeHooksEnabled = ClaudeCodeIntegrationSettings.defaultHooksEnabled sendAnonymousTelemetry = TelemetrySettings.defaultSendAnonymousTelemetry @@ -3740,6 +3807,79 @@ private struct SettingsCardNote: View { } } +private struct AppIconPickerRow: View { + let selectedMode: String + let onSelect: (AppIconMode) -> Void + + private let iconSize: CGFloat = 48 + private let autoIconSize: CGFloat = 36 + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + Text("App Icon") + .font(.system(size: 13, weight: .medium)) + + HStack(spacing: 12) { + ForEach(AppIconMode.allCases) { mode in + let isSelected = selectedMode == mode.rawValue + Button { + onSelect(mode) + } label: { + VStack(spacing: 6) { + Group { + if mode == .automatic { + // Show both icons overlapping + ZStack { + Image("AppIconLight") + .resizable() + .interpolation(.high) + .frame(width: autoIconSize, height: autoIconSize) + .clipShape(RoundedRectangle(cornerRadius: autoIconSize * 0.22, style: .continuous)) + .offset(x: -10) + Image("AppIconDark") + .resizable() + .interpolation(.high) + .frame(width: autoIconSize, height: autoIconSize) + .clipShape(RoundedRectangle(cornerRadius: autoIconSize * 0.22, style: .continuous)) + .offset(x: 10) + } + .frame(width: iconSize, height: iconSize) + } else { + Image(mode.imageName ?? "AppIconLight") + .resizable() + .interpolation(.high) + .frame(width: iconSize, height: iconSize) + .clipShape(RoundedRectangle(cornerRadius: iconSize * 0.22, style: .continuous)) + } + } + + Text(mode.displayName) + .font(.system(size: 11)) + .foregroundColor(isSelected ? .primary : .secondary) + } + .padding(.vertical, 8) + .padding(.horizontal, 12) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(isSelected + ? Color.accentColor.opacity(0.12) + : Color.clear) + ) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 2) + ) + } + .buttonStyle(.plain) + } + } + } + .padding(.horizontal, 14) + .padding(.vertical, 9) + .frame(maxWidth: .infinity, alignment: .leading) + } +} + private struct ShortcutSettingRow: View { let action: KeyboardShortcutSettings.Action @State private var shortcut: StoredShortcut diff --git a/design/cmux-icon-chevron.png b/design/cmux-icon-chevron.png new file mode 100644 index 0000000000000000000000000000000000000000..9e5f23f1d9039a5a7e89109d4e8a716401cd928d GIT binary patch literal 498099 zcmeFYWm8;R*EJm6JwOOfaCetrfewv?pus)3ySqz}27(hH3GVLJI0^0!4M7|0#+oPB zIp_WX@Ar3B?b@|=)&4TqTvNsvD^^2I2@m@<_KO!U@BqpRnlD};2fuiM!jFmm_Y2kY z_ou%vEEi>c_ZKg4NdEUAz4)5@>hDJ+cTFX^7u8c#M}HTnHnOU+FJ9Co<2;(7{axdG zDC&89bhh^JHgmIj@y^!9(Ja3XWbpzcn;W1YtL1}q^5pNrw3x>n^E4%v?YrsoO@p;q zPXmh`@52tTL8Vwnb&_yq=5+qkwc64(yJlFcJENZr9%X5M_=tL z{F>)1EHO;*iGp&STY?y|sJv>jg0}l7t;xEC7Bm8{;4k!XgWIID%ta zk3tSa+>7wHKqgOX>pLAj#e(IpL(&Id# zbH*=JjP)$BR+ppdRgjp1@PBN;nM;j?!o6(L5A+AbWE_s_oZ^!w(&!?Ku!hsvn(-7TmE-G0_WqYV=P274bt+$!cakapbS_a z@@EMl{gH_Y<6n#JaSDm}RoHHYj=7RHS6A8OH+HKUYaF$dEh{Hli_a7>>Evws#Nz1g z@(9;Mk3FIjE6I8_Lq|lVfv?V<{GDs3fmFfYTAfHw2|@L*1vx5@D7{xx8-(By=g8K3 zlOc)Uj1gAMj-Mc1W967JWKn(O4~!$UKFFT%pSfeclJ0Ow@%fsja48za+u(F7AU&!^ zlOXoUORSuYn%*!s6UwnOQuv=EO{mpYGFhs|qyayr`9My;n5h}1ty4H>0IA-Zeer-C z3OYx@)(yI4N=BChT+iRRs^T&ZtAZE4nrM95bPezKV@uMI>ix+m5b^VUbH*~=nrfu~ z=h+Im%_%8IC_XXBCy(<;f+PPNmFQc;yfCv9o3Dh*PfEt9;oJ>GTFiPHr|H#Z_=}*W zd+>t$fxA^^o?=8nx_-by8@1$lcr5dOp}%#JDXViNkyDaOy=jA<&LM7}Cor<6Kgc8D zd?EF-%fGUb>HM2O%toQM|3ZCVV7?+M#wdjDiy&?NlT32<9+tnr?%1Kh^K7JM;eZBo0Ph~JHfZ8(^}VuSmm@gyGR(( z*QmrkaTiRhuOy>nncQ4_7F=g~ZQf6hvni&l8SJZ%u?Lx->`@H<`XcjRJXqvvC7aK{ zO7vb%qKX>djb- zS8Lo-cUcXiwBdU^{*F`Z0f9QivGxGSf2klQZI4mq5k2!J(}qM)?T0EfXNvKCxZQFz zk%IfJO1D$Oj+va(NwW`4PrZK2XuZe%#LestnMra>Htx`lxujz~U({%G#WL>BABw@r zTs3yz8Ax@$nNsl5#atWJ2tulqTlx!Q{m!Qh{hRa3E7kuTMvIdg6#hkeFOrZs2+FDs z9tUL}p<-=mqeHCb-|)SC?Gr}MW8s1IO$A!=^f8e<4PcyJX~TcMZ@a;!Zr;`PUWe0cAf zTGOKz_1#xyeosmSqI*)OQ%5u*A^lET`QZ+fsd(=Tx~Gk<@BehaJabbIVKvgl;aE!6|WK zXOh(Q#vsl7W-@a3$rLD1x9dtMe8145{0Q0Xpy%%KP&F}n1AU5S3dN)r48Dqx$+uA(R(gd20kz z3=^;z1i+*7+I6)TW!>HU1jub84KvfP(gc~mu&D1s#YR(&X}Fp6U(csOAcObgD68X6 zkhyyXd2V&5NyZLwSJhcp`Wesva*H&3(3?>col}6aN&n&$LlE1Cm}nMEIZ3d_d%7b* zsEU{QBEb8T>NL){D^E9TCK(xn@i;Mi$dIJYUT^8lZrM89KIBP2C zVyzZ^#$&u3B9Gk&m@T&vm!2X$vHrj~JNOWM>JN{QF<#){PyzKa7hGjM)|qFOdxA@x zm&>ybICp^A?%GY(?WQsd>J=uV;It; zjUmiKmKW?=iW?%+1`)(<{b0O)IC0wh6e%72(|Q2^sxE-jROSlD($Usoe5G+-TT=z+ z)-a2Ot#l{B7Yq?)T6x9ozP3q0cEz@zQng`M%*kz~yp5G{4v28RzzBI0`tKnQCJOpC zVgiKU6SyUvCZm-i#E(#95>E|25sMo^IIVajYQEMnrrm)Hf_*a>NoaLoTlxLcMo4Ui zb0+%CHx`6l$BVKnsXh|S@xYg2(&rsHwT>o({ypYcd9B#<=LcTE@~jkXUm3at;P67K zl`hM}gX-u`BnRDBEMJTLb5Fwh8q*G$VSgDJ)j#_jI;G$2HAr}pQ(N7$Ct7+?ia{fh zY}>orB6SGq1+nMD=2yX9+&j}le`ssK3OCz=Sh#PPn}N7@4IhiCn4u9sg;T`LMTgeV z_@_P<7R8R;DE4>E0t}$!h^+^MxDI}^ z8*jD)4#d+1c;9qg`T-Biv!2CcR=v5we^!J#Z_fvXxu-h}V_@z#u9dt&J;dtIj~INe zoxb>gXy!VE^xCv5-SNhI5@D;bjo3|rIOw9CrSaxjaJmTZd8q^_W@Vqe_VIyFY>q>; zdPvudglBj#Wtp6|#?f-&Zlqb?k=hN^Job~)cf6bZJQ%g|C0dnabQ+F*`=tu!ud`MUsVhQ$W5;Ni4-Cgq zk+3Ixuk)=jzsHVwO;UUO%Enwcc3ZCa+6tTOVRFbl&h)wlnR@lVJN$SZUs{(V4pG=%JebEQ1LYPziyyPg4%Q4q%L(zewFS& z-E7QNVc#s>f17FBY{*hs4HKuxiYGby(P}fDQmAH_8xrIjgdOUm<-HNBEqZJ#en%jEVkiUNXe`IvgcZKB3|< z>zMTIM4}aQh$yd(oP^Jd(QE)QmnwdydUG?U>*RHiRM@X3>k8$JRcKI>D=iKlQaUfg z5PSVUvI=_MOFn&@4%C)zfhExcYs{8$G9i;ypByA~p?*wt3BEKHzCXoBP_N9Gq?-#Y zg2&TbkujwUe2A`$hipE@k_!8|W*koL!QbABUg<5rlGEYcMSXciJaS}RoG@H)jc&A* zkY;@0)sF0W(J$h1Mpk*ssfV{G(B0T*DG+$Kch!w+B0|Xj=?{nh{NAND2C*DS0Jck< zmw-}PhU!YkA*P1?e(YZ3J!%`i$F1=pM;JHrKeNv^jJk)d`5Y=Qsk#+IK73qarL0-Z za-U7d(69oH_RZ7TW8nI_KX}kIh{+h`xWSJ3MPE{_k}#f*OV|vBahO?J1Gq=FyHBP| zVm`F1znY;fVw^nR<~+%h_4Gt}iEb%i9K8#5dU}C+dQA8Em9!y)>vMFARRu4(s#6+O z;BJ)fse5u=&$KBH?XN}NNBUv1Y5%zQ9}ja|67I4qW160aqa zh)M*~4aDh2|EH0LISIcw*i)pR~l47I%R&pLF5r-L!`N@te4v$7(TlMR&TJ-~#R~Wht_#A1ii|=rDGDJQdARtjz6+5w~9jfcC*LqVkoKVqg(La*G z1_4nAha1x)l2Tpruh#6d4rSp1fff=~zH#{?f8-7&aKUI>eIO3A}Fu zM==T}jktKXx~xQ>~!|nVeQq|iEqptootqd5=$=M*$iQp9cyFoht1OBA2r{z zn+7=FfIqq&)`$J$uAt!U-zEZTWG+eH(?qM%EV(V7@yRHwtrt{cw~lkO+h2jFm?#Q| zKXtuiFdFir*ax>oLLcOW~MS?tCjmG^ZjnnZ_{X_ zQR7)^m8~8H-@uI@#5s*wgC{~(a_r56(kdNQ2n^=#)`XyAJb=x6bnunYtSEl_O{u34 zRCALngnzABDX>6dxA~m4}NG$$jPm7 zRyaNc1*wbBWfQ9*7gt$cu>iF5Jg_8lZ~%U5Aax8ad0BHTEzqMTF#K@UxtBYN9e(;j z=f#6zBEaY7fH=+NlYtTGxO4YUyw(2LjGoT9mgouq(3*5SvmS<&#~3mS$5P80e58I3 zsy!ERtq>)nn0?h71b8);PWmqt27l@|>A1|)ez?Dy8e1#bMG2T8xmQ@D9dcYE6QBw` z8Vt}JMr*EHkFA1WXu|FoIY8R&nL5hu4pa+GW5(@I86_v!HDD_pvJJ*^sZM(1ZkI`y zcbwBb&)xvgvE^9^Az$`NaBM`Ah4TlGo^UHVmm5A}E{EvYWhtP5Eyp^ORKbZ23aaxS zk*)FYmCqBb7U2;)IPrRfIT+tOGvn|%5*CDXt_p!(ROmhbHV$~b(cOs8;S0*|>kjgf z&WANAF^)+lWwaSlaYVp7ulo%|CJ>o5u@bdj)9c+_IW=ceJ z@_mrRD#KMjr$&>pVNhwGq>~ER*-9v-%9O^2-{;CYSG2x)p(kA>R-A=D&geCHmbO>s z(Fw!4%{s=dF(F@91%5ha{Wx$F;kh^czjCq-Bkch>Y|b5=E?TKniHUP>ak74RR(NzV zQOjQ)=ldy4rUkm#K}m`Nex+^4>z@z;8sNB8coD;kQW_T0Wl|&+02n&IeTVE z{PzPhBzBv zdBxp`pHc)sUA3(6@+o;(*;Ra$ya?NEqKFCfDE+sE7`Vy`68XSrFrAvU7Nv+dY;J8| z6v~63eCe1Iicf|Jtq$`Tg)?=&UKC1@p@)!~poAI=VR4lScKCRbLOdjzLwX2GuP+fo zNhpnR^ov@1;#}kNX)xmq&13JQzo4s{%O<`s?Tj$@P@$?rODz=(@rN=OAHQjTC*kZf7Hl)p>i+ubs#XIs zVUu+mddQV;BH-}T4SCo*7;rC;^+Yw-t4LUFV^@@IgducUOE3`}plnBNsSyr`N)LnG zZy%?3BIiG&55|Ex=z=7H89geewzEh?xsnB$+qY4lL$6hnwtN;AZK3+9^718FA=RPV z`^*-k7vHDBgUOCIt4OF|gq6dyfat-@deHYTb8%;;9nK=kQ`_>csUy1Q(?{RKeW&g- zWcm<@eaA~tl{*lZBMEtjFWx|OM%)%npD-KaPl}OSQI>d@vWtD3MO5-}WBvGJbhjKy z2U(5EPuM}FiylAQEwOxs+IqjgxNk|1#U5dPvFy*$1xDWh5%*}_jU$#uOzGl!{{xaq zddOBq0GFaSDNXIT9MZ7_lCL&1e1F8>`OFN%nJ79d;Q&`D9TaljaP6K(&(}S%28Db? z^*1L6yW&1nk-tm$DYH93};S%&n9iTJ<4#PkrFLPJbnG?#!kG^@5f^9yQ{_ z0Bge>sBIOd>s!b_S6|uGgLikuScB<8Nitm%mM+{gZS;yr)~OK*4UJG1g&gOUJF%GM zS*y--YGV1NV0#W}BPw-K!JZ-~BP8e`VEfL2wC73^D+xzZl;HBS1-{KS?K74l@NJ7M za&SGM_%V4`wD0@*&Z}U|Rk&Dgvb!Pw!Rylj8si`H6Ok)I2F7J!j)54k*~z{99pu?u zM^#tCGOl-y5SFJeVkkND3v+fgI~m$26}RX0)ckBE`)SS3X)k-qJ=1p!XAt~vX5_Y) z)%cp}JcYXjKf0_f_x^(>^i;rwB)4`_9^pu9Jka{ws(mF|n!{+j<84NxO&c>h(fS@;jLGh(V6f$A|kcQCQX<`Lp`VDn!jl2Qu+sEakam znC836{Hp9zm8;a<+l7O+3;yhf=ReK4vGd#fdz2;|Y_dCwm{4PZMOBCFxW zqT9(&Dw$_;cMtMZ=LP0^E5vQnYT5WP#;43j52FudZ(nS~H3;6+%wnogzxGD9YEXT zIy1I#5qWJ`5U(=k68$B^N>kT_aZ)O@#;A>+oxD2Rc@n%wP1e==6@dA3##T#U-+ZDs zkdJjWFN^F+uh&DFMGjxOvZ}BlRjvZIM1fl$tN0-G-^^VG#j^!sOGuPd(~GQqB&sMj}z zC8-6Qd>a)T-Ag71e>E?~Bg)F+De0mF=)!Vd;;QCwS47bH0g)jr%4cB?P?{`UVcydg z;Yl5f72fPh)Dn)}oJ)EgAFqaQ-p?*N-7P^9J9=`WGg@%z?07N4U-*C|jsSd+3mZUl zds_2Y({h%iZc+Wu>Si`uOcxp!G+IF+%N?=L&Hg1dWp!u2nKF71eWTybxlH8bK zRHU|4QhdX5t@{(qs8ju3C?$_4F=R_MtkYo-;!y3m3S78yYv8kJr}Ni+d%aZNm4iVv z0B^{GofT9cLyz=3rmKdd%t{lv*65o`t^MEAXeVHuseOH1N~#nr@u?+0>Pi*1c-1n% z5JeXZ#y}y4@6f{ktPdy?>_>i{1|FW#Yss>PNMmk_V*YXBe`Ja(7`T|eV4m+6F8h8& zykOr50C@>t2U3l{q_y`S@VI)9N-}GGz%a7nnDqRn5Aen?HL7a65r{{NY(sP<*<9x? z8r>{7xtT*w`*b&EraZ$E1lusF@V(95XDoXTD{ZHTJPC&5@VmVyhkU||F;fgy*rR*ln`J0g? z@)#t^4|MLyvVZVpuk$4cNU@PO{}w?ii1qHaW(WDWX#P3Qs=c4Tn{huElQVWS^1MB< z-Dcpeq3tl@Y1b^Zg+_Bw!3FvD^sb6)#ZfhTe7jlFLG7#3M+FNZR&Vz->k}6P)PXE8 z?GCwtAkNMC;bladXZ-b`!(&~+`o_m|wIsy`I(sE~x&-Oig7Xj8a*=z&pmR^G)jG%Z zLp3}*nuU(0WLWq`;(lF0$I6LdAsLAA#593GF$!ywYJ>$$?-Yv3e^=q+dgzJ0iQ$?D z=f8b&XH8oQa`7M7duIdu-bnA3CSXl=)x3dr^spryxtqUb+b*b*8~V7mk*msYwiGn@ zATkljvFNaU=(yC))4Q@iNNMb3!CMjA6yTXbA`{aPnMtNoh|W#Sa=d0Jrg~e?!yNw9 z?8=uEtpdKXDD^4C+cFh)0bS(Hzvj;8Q6rT5hvbUhv<}!wZtOE}hkCq&s;G4SN}J`( ze88ez58Ljl23v(0u z?4**dI=n{=(S1+mib^s@PqdpS8g$FF9PPnck63jZt9*!G5oQb`1`Kx`REwD4r+o{4 z?-x0Gc;N9QKPbe47>;<5xV}5Wf))}64_`lZPHhD8Pph5$#JJ(>^7bBA&>(=doFB#V z7?G69&nPMUVRLZz&I1xCxZ0p%)8Dqm2PU!%zy%yesac~4agTCEsHx`wZuE9K2r||r zd(IGe6TAD4KA}A~f7jDa`&y8@j?5EGdCi}NTMw^k{=_6a&Km_HZOfEN?0P2CSs$p@ z?c%#}(aE0maIC^{FuxC2JW_~F`1~ZsJ$lcvD(%HlI(Zx;&T5I57iXJR!B9Ph2%woJ zWvw&Zk>*RQB$wjiCV3>8w18!AI-HM>ku~AGye?+GkL$$wJHPEaNX;m9N;`T|U`g|v zCH8|;2id&6);=ZCEn~SyWCTzbkfiwWcgCdcq*C8t^5~DuRjpvjZnl;(Ydo-&Ta~;t z&pC6`voHeiIe%jaP7r(PoU-jc0nPkI7qlklv~QT})9{{;{yC_Wfs6&>NaUCg8@tA< zQ;r`TOpc{D*}N-l?cq)9&Ol<&n_LqJA3Ps?%51Bd;k?|vW9}ws=nDO~qhzxNK1$B? z`1&86KYoEF-8r+=-I@Vf*MA3X0MN`Q%vz%h2=0b;JFG8bK;!FXGSfY`Ur9sJN7hsn z{2WQKRW)0jnds3S(A+=RoGJ7`e*ic#w(M4!q@^NRmx{JKuu2?u%xmfy*C@@gyhBulo^Ln zr||5=y@*$|1#~($JL)Z}W{K0is79412L0_l>K@&a>=Hqb6h7@i`5vgqciDUf`HD`a z%e=SS86#TA#Ads1=h}ytD;y0$i20I^nlyd5rgF#dx66G4=idZv8b)O7Afe1&$ z(SeR2lH$dzfZXFX@61QKSc@g=#M51Dk}=1xgTZt?M24ppT2U=~D`x642Km3wx83ES z4X%kMtzs3M=w`VzJMOizIkoWxcOUs-!;ZLR8&Ccv!*x~K4&QWQ$NNiGIVaYd$q?is z2Ro>FB8!$mejA1xh9tTsxr@Lx-Av`4ZgYtwjfFEK;K?RCHAk>8UrZ*?4N2fAcn<%yO*B#E%_MVBK!T(lzBe=qd$z#aGCyQ)G0(breb}38=9?Fygr@tug6uv46 zny7%s5I>UUw<^T_|46@ap5dk^pZh-+l{lBZO%KN1Z_(qv0R1lzhv%ueX2s3;q)yml za|`WK8<8Ci_s@I;tF5vJc&vdmQ4LwsLh#m?D&&l{3+w5PVye3uw^3KXOVcj5<%}x0 zs8`g8%ZV{6GW-=zb^`R0Lx6EUKRMG+c9Gvb1u9|0pF-e2GK^GF<=^1b=csGukf_VE z{vH#?l-{_W*$3Ti5ZE1zc__M^(Aj)8nH)r>v3b-4zR=~LmE5x=?DKpAsoi2AYJY{D z)N9?S*nhYr$Z#mw`ndj?3h5uk37o6nMC@*S_}Z$Xq7I%~9>|8&NRegREA>0p&ryNp z_}&O(xxyIDwSOT0bj^8gCqa`LodeJ%bGGs&wWskT#_qOOohW&1$u8;xEaMIYI=|DjUX;*K=x}jyjJ%@NpTWx(DLey46*40+8MNlPs8O zZ4*vC45NTywE3iqOB%;Qi#4=lo%T0y!LF3(BL+Q3^uy&&+Xq)#uTx*v#BCNYU{Ueb=YvhleNTm0TVzPo=Cp7E}x`9gBI3y)cAYCE1{$h%md%2WhnwA&GBV< zqr(GT;hz_AA<*<7N1%WO7KBioDbqR!y!EK=wrri83$IB8e`T)zow422x+Q8yf26 zr0RMH=a6yGd-wVgcpnJPG_u_z!69q+9mqB6V_oOcQRgJ=Ler*&OM5KQ(Sp{ZaQ6qI zyxERipJ?BR?baP)#iP0QD?Cm9koouCCrMgHlb~lV{xuP}*?tt);G|_rT9D{zhq%kR z)%TCcK8BPoVpEvR;vL+kaUZdi%uNH`4cRVUC83J{qqh*=Cec7>MptbrWklP)vsySK zJ>c;nv`Oz>G=o-~8C1L~=)__=G;rlH=3bis)x0<|nDDqV%}5zV2H!jlOb_XIX(R}G z7ycMH*l!Q4pc86%<%T27%=IYEJc|p{K$w}f{FSw;4!EranZ&*Vr4i0i4dxsw50k91F4{pxX-~5JazqVzT+Yt7U zcyr}xJru#jJHfT_iq0rkkO z^&^V0vcemQv76VzoH}~#hQY}s46S4|BotG7R<9e97Rlf5-I(?m_X!8lXkPou$E7ie zmYr`C#bR%?G4w~YmmPWc(iixcl`ECUQm{Bn;6jrW(@r${c45vq{-l|Kuvf;J zI+pv^-Q9m3sA>_6V0|g?2-qP^QXD`}B1XGXml%G;29K6o#=xYj&v)SfHM;;|)=;Gk zK9c&dm}wN_RZF}1&S{pUDFS)lO~WDL0?aV!Y;RYm=VemO2EFfR zUp9<$_-F${j$xa!s(~O>*mKtfilK~QVZl_&PP40s(^}!eJK%=zJq@PBCa&S2|6dJI zX-!Ll)fH5VK@1vh0;mV`mD{YRqF_;RD(a>^IHtf$ugYyQsQCL!@?`5{X`f1%|Dzxh zhgr-YR`6`JQg>}<)en?WE@piy`%uc6uiBb7A8+G)EuQNwsgoO&db-r_6EokwuO2G@ za=L=l59SNXy&FGX8ceENjZ^vJu*o!e^N{sI8t3xzNzQyfjF4Ko>~pn1~euhAUJ)P4zH!2cj5I-;nVA_+?j_Pn(CqRRaoc^2C%vuVB*;M zZK*uc4DRK&UuP`t;q_e#8aI8rc{1Jpuh9Ih~|BY@=1OY@8bj$Vdnyp zmQuI)H{q#^+726Q+4tkp2EQL}{e0Fx*b_Y?C3#ET1#kh~tuOk}DYRp2C}(@Ki4xp7 z+Vxq#h=?>gdLyradr?L$Q@fsGu7O`E5{^Nu7L>t>Q<*5g+k$_ z)^N03&=?*+Nzk4DovZo}gc>dwTz%h`SEu}B<_lg%CH%9g1eQ;Wpjvw1OvFlfXpe2! zL27^sz9t7)bp9}{3ejsnC2_R?V-xHcNbo=P0E}c-k#_DY z(!8LEgRiXtp@ZJWaU?RWAlkI9ba5=H{7MbkpndlN8glb9vKGbkO|o2~ads0fM)w!{C+7Rg zNqZ$rt0+k3JGu;aI4Ko04%*B?yujFuF5;2dmmoztv)rW*1LDyapW`8=ocz!?CwP0@ zEXQ%NMo8rwOdoWIelX*p{cz2aAzIi+VGe?yKfaQ-L1X#ewJ%jU1hIEhC@;cM{~##L zSf&J+kC!itf$>8v`gI2p9gmtyc0KwH*n2P8hDxn^RgY#vA^UOPF3FQ&cr1llZ;?4Z z<}v|yAap@31c1$>lPqLu92@+G^~vRt4{~4qF-`r{BS?dsCgQfs}tvhU|0cn7Wyj%nf^C!ZWtUvoAL8SjVc)5{>NMV#hqVgE?6Di>HpP%?m7O@p>Y`H zjfGmoi*QX%?2^#&dKVG~i9dJiZBqVju=u!eyH-m5^@iodzxU>F$~pjdI^oMblfz;y zd0N+(y(=mvKEJ>7x>mI^CuJNt?vTYwc(HCoJ%(G^s!(XgkH(MWSef6%jhSlbXT4Ad_!5;6iTESc=wa6#c?x zqu98`C+or_WZqm&T)FyE4kHjH2gtu=iAhGR%)KO5wAiWFm-9)*l1e+{uUh<9)1SSJ zooXVjFRec=J}+MF#Al|`i~yKLz(^Ydu9AV7g9NgSJ-0Q=h;W-Cd^fTSODc>p8DirC z6W(0ZspXz@8On(cCI4_ciGY(J2oXL)^7*EEWj0%&;Slpk)Vda>~R1X>2c! z!7Rpm*|dH~vjW<{`uDWoR=gum1GsnlBXgH8ssHY#q2V^hz4pyyL0UUH)5vW|uQ}%nKhRytLZwA;~>{kYXnYDR)kg|+%~0e7wdXcFM!pIo*dy2H=a6QVd~w=tp&e8et&<4dkk ziu#agaZ_#h`h&c@yr6bM?9%tK5`5WTa;&~X+*%3~YeTd^}BMV+n>!jfWqS;crsUvx}dwDivtg+kEUMDFX;64K;cUi(H_?iZJ z-cR68nEcxE{NyXv)WjX+C$hm@5%{n)rT8Q1uI25N>|?76W?ld@CINSO%9@!)gD`k2V2_f zmX*|)2#QzH9sYZtV89t&nn>l(1ew#0rI+)9^|uL+j2#`P#h$SFVBr1LCgaT&&dcQ& z&qJa)B4_xrJNz+y*qJy;M0$Em_e#$sngvCx9?muPOQQJ^JKm)dlywa7Pl^>xEiA?;0fSkPt(4kc7V!=_?42X?iQvI{FfJ3PDB=E= zvV%uK^B)qDU$%rj7!nv|R0%O=gxyq_OBURv^}1;v-;nl|uY1LN&~3H2JsRgcT%@kq zr$q??C0?oRWv48%jbXI~F7cM`Jqi|GQV@I>+H24+&ek;W6i2IoJJP-u5HlpJ8fa(3 z#i6fD8g9q?5y_=J1L|->OkGz%qe7F?(E8Mk!C4js4W|Xni<+xfRHOpSf0? z{e>-uJ~tR&=K{SFE&^~iVQ6#O>{H#fK3&Ri%7xd-yl&Z+fN7_$xt%Mua`S`?x>vFo z^u+TF>relcxf-hm8P;-1c9|MnD9-QCj?&pW_J*$BDij_5IU?-c{gf`*ovzof!jSYv zvirNh-gzVcA?81K8wP4<3s~>s!KY4i!M(OB?`^aMLa)QIXpdY2t+Myz`1=&U^7L3H z3%%~5vVnHzUdJ{yZFb{cqLo6ZQ^=Wv$=%(*HUzpu9-ge6B^iSy*=8nI_hz_(sNjy+ zd#tkBP~X1_C2sYvID!Jp5{C{swZd%g$^egc0@Zck5~c7*YOloX(APS*(YxyY5h&Ld z{z8(3{3gVyy??TN6wTzzN-CGwVvXc~f0X{)qguWSM)dPa6D%2XlvCVG|4zfk*o3j}&w zKcm+d%utZ)PHCZ+nOP`JldkgHE3x=05}qrG{9(T8jTxSOiauJ-J=N6W*|zK;nYWne z%bT-qRmSBD8zGjEgtc{&weF zCM>SoU%RF~JexKtqIih4_5@D1a;`YCdOp{e8r8!fg{La%cb6r5N%tU^*Jr<;@fj(W5QNmz{X5?`r*Mna07~!vLJ! zU4<@BRyFGC*cP5+h|GOP}4Clg(xgj5^(P<7p~%*pnv%f z8#mRq-+W&ITYBA$t7fw?hJAa*ptX-^Qbq4Uyk}m@kq|!XXjTOqtSIxyywo!Qg zD>ig#c0S*~w*KkgG3X z%0`bL4BxhU#SgZa&_DgTWLMq~^Q9h4mKZctpOa3yGpN2LkrPGmB%%Q0<*w@gV(;%3 zcz)ZGO03E;Qj&b$^k$&ZxFBaB!={G1|L;x~+hBPK-9zjTusAvNHH#Le@Q}TN7N8)p zEX7IcXAHF2>SaxCRA045sMxS*S>1fGQSIAU$3Q#7ANZI%(xBMhEaB!1uyFk7-HOFd z%)SMkzdsOJ_61rQWoIqYQI%E+$mY6KV}v?~O0x{`BhCRwL>4h~T=*8TVID~Ng1aP~Z5 zGX9xq&JZ7xlgnt7ML*<;)Fvtj##mwa{T#PPUXU2Cpzh?3eo>xpqE}n+dm%2MVe^hj zG2`CK+=*E}zrZX1C$q4NXD`06&E^ivE{bALt?yrDhMeAf^Oz~{MVjaTA$G?DEbkEY z`@Az!t~yM~Is(h-DmJ`|X9v(>`&wPudt;O0lwCyOWMgi-z-h zUFB1(YjaIrbapV=)K629pe=P4if+R$->aC3P0qIw_u(bqx#k#?t{#SX-U8i9T6MX} zy`+_wR7Kea2M%fv6q4NO0u=uOaFWt9Y4Y-RoQo;m*%wW1*Rd%BTtBn297C2A=QLg3 zvSDNMZ#vBush8nB2yc=Uqg`gAd=Mz@WPM8^@kc{&KTXnMuxcGY`O{bJUSwm%guRRG z_I0?AuQVz4?GpSve^~5vu%rgF+$c8C_Y#g|Ag!nn*LWz4e=&0Eu6;{R=syO2cJP-l>}4mja?YEPm&n zB3=TNABKf;(Gwe6WZ7 z=6#=#fY|iHId)Yg=ghp&T5NNK&*rVsL`t`ZaRxuCi!Vj_Rw`fi>kJ$Eii=X%(3H_fifvackejmLSc;6iueeKPiwzvIh zW)?=+(Y|kY zDSP*C&X5A=R6+^Q=bO4O($gtdhqc0pa@N11-1)Cb&WI7p#C)EM{GAg$v$*$4lCWy+ z^9Qe6fctXB4`n;KrYQ8xm(#S*M#Gj#qs@f4fl&NQ)iPfmS6u#6yJ-ID2Y6u$> zX}_eu(yes~Op*IF&KqIA^zofYA3{&alK8tUoo|qTZ z>#}C5>EhQ#7`fJbHaqa%^AqW$mBU$I*t5iR>Y78HDsX4|77HBWuDKeXE@tf}P=_t* z+R7Cr%=|{jQbZPYmYA=tq=_cYiw5%KF1aO!@6 zWtRF=ha%gpGX)@O<|HaJVd)W(0NA-G!( zd1HP4E4h5G?VP}1Gk!;6pft#RgR#FYf~sbI z(AYL+TqLq7psk>8QzR*yr4KXckdN}RO@yw=29+58(&^BuZ45u%Mz09dnyad{z0_bo zu8nMSDhze=9W4)6WzXO!PqhDFpQ(uC<bG)sXvo1dNgQYqp5jm`epC^G+;Uyr1b=GZ-2bQYctjpQE#!v!)$H6&no_4xT_>e z@Qgi}oF-qL|E4YE*!L-_3XiS2TaJ;6PRA+8m;%Ut2V-{d;V0SPDUS;jOJYxBgL3={LH8ctHpRO%{0X{fU=Fmpo z@hzb;vuR493}SbmG8l08nr3P9aeA~9!Ijc3WYD@?I|I(nS>!Vu41db&pcA1ZN>pJi zKw9pAtIV8tE6^;0#$_1RrPcr9k7r|j`rZCLvwdT&{IC~ZxlR49aj9>}rq{;WL?*8W81cE8y%-(n;lfxu`9N1 zcDiHRwr$(?$@6~aFVwDU?YZYY#=ROg!&-F7KZ31l8E=r-ij7@)Sj`|(+%hTLP(Vqq z9H|@}TJn5XW)EkN>v3OFn;v?$auTyB*!cP7zPYZ$-4EUgN@Jd~H)O7OZR0YKzMlg*F)L&6_p2OA9K{HNOO@k+Y_^6 zV8_r~)3afiDChfqJ^B9yrnv}0@oB<`u7P*9EA$ z7l5R5znLlLF!bqTh+<6mOxSWx$FXjNBQ`d+4=iZH9lpQH%T3iFJ-0D9j99+cS0HGE zA0ZQ?PebK0m|d+!bqL|yfXc7ovKsb@n<6oty#0_$Mrgf!2J)vTzFLHugs&TXKOjO) zyUB-eU~L3iuxk-cUg|I?m3#qoCorW14m62BP7NG(Do7C3Xd~smVU}WTGziq9`ma_} zz^{ci)Mm2#zDiuCUcQPGJ4shat^#}v2{P-<1Q22Hzwi+rH`~ooibaW_VXi;@eTy>0Y{Xr{zIfxNkAMdg~>Mcc0+r|@RBJUSvHAJ(2%cL#7x@IoZv=;bZnnR#Y3y|JZC&iTSmAs#xts= z-Tt*LJKtjX&CQ0hatJkgGcAPhiTqYxH4BpPqax=q~pNHF%1{S(xn$jt017rE!v* zqBEm7Vw+9~r>ZTt1SEsQTPcA8CbxN+_N+oN=a9Tpg~(+7PVaIy!O2^7o0Ro-TuLh<2G0=V=Nx8dzM?bhBhwlhTvcO|FTeK1h!@u(df(<8j zXtP=YDnF5NC+C?EhX&8xLjHWj(;q8v?td?TBw}d4gNqH72jEx9znthdaz4AWl$5Dz z(Q7%_7rEUsTf;@kaliOq_ms;n@DxX>yZ)ca9FJyi7CFHM zcOD5@G}kE1M0NqsH3w^%kUO9!YZ(~@L`me!{`X~}W{XYRy}b?X$iXz_1S2#&wL$iU zLgiP)s6Majar2*XX-qOB1L<-k+`7(QCm}u+qs;5=Bw;3gZXvDaN0ClW z48VqXmZm!r6}un9fQs<^SIK{^-Nt5^lw-GGR=B1xio0W0|dgLDyn;< z$kId#C3S%8$wLM1014>gtWA8FM$wGY7Xj&-kd89(+=LhusmWH|)`tw{UDA2UVD$|Z zmjTa`FC_$w;z0y-;TP4v;I(2K@rlq)nG`q1Q^z&5xN`BxiaR3Qm2lW3v&4m(Y%_`N zE;65p;x<>hAI{Fl^j8b}E=&Io3oe`tJemC#DS(D|BSWr@Idg?xiiagmz5EMFIH65C zbv}ug0wRd=d2LCNcD2z3GuvKcchwe2;%nIVw%Y9043Cf~cnNb$7iOu$#s)wfF1m`w zr)Z%va`BZcdZI+mokyg|gi)FJ{aO9wjdl{}Q;Y(;gKnj_E%Hl39!m8g%TNBM`K4cs zK{bMw`kb6``d5=lcLJ+QoFa^iyYedD61uI|lL*j;s2ouwHvr-O|xhp0P98K$Em#yIzo(hjcarRt~ElW=%egJHjVC93x{OFao>#D5ZbL zTdZ<{fXRbt^a5?5Rmu}&LlA{@CR^TlmPmej^irClx}}@d_n0PEK6eqUnp5UY#*WSO zFvXY)CwOtUFZ7^sDP#nXc{BBDPQngVVg`()8|a?z)#2EZr_ztgPzov%SoOdW9qi61 z!NZgIwhQ_$v?;JM6E1Z z$~*mosP!$HXPI9Ngyy&4!9mBSfA&@a<5cUMWO;%c#07E+gVHwv&G+0F1}!N8b}IM5 zjLgt%qp*T?sbM+k8*0Qx#E1^&RKTqJn}KL8na|H9nd*zNDoA{?Ea`P935ypsH4hPw1>;sOTYFYqXz7uJoGJ`UTaVq;`FCTnh&D>a8I8coqtM<(&A@{9&=7tdc3Mi5v=fan>7m_nMPpVCbJAYtb#|%_0&7MW13+X?NGXg^n$q#E!@RID>}H?} zUdwAJjwOeUbtdU@&fM3h#D+S4A`b^LmQTTx=7UV`t}ZsePvTV4ZVmp894xiih$_@WRh(o&|gizPGVNJMKdK<{t2^cQekZYy5_r6h4$CGF)8qQSa zIClU$)9RR`A9IxnfqfVI&k$V?M^wYh{T}462LKvr7;!4pPFmNUdj~jLb@I8CsOV~- zuBV45T7T>>o*@X+emxr5mvqwNK{;}Z*8U^Yuo&A|gGRz&Gs-hoX$7JnHWYINbPXn9?W`tsCIs3- z;{X@MrUH$BG2j*tkbrzG>bJ5|zm&6fKXKFWIj5b8*dF7x-O=IFvmx5a5n2k8MHNcOl+pu*l4OhcwyGm)|OQ6g~Fl>=NKy3Zq{?^ z5zB#Bq07lX8vG1(JV~Vx(75k2o1%LZ;F-d)1m*va6!Z4>CR^#W%I>mFPvw|Xra-RT zW5WfGN07E~#XXD_>ucm)Z@loO+i=@0V{E~zS!an!?=VV znSSKYzOO4zjIUqv0>>Y!c8_^I#YO(p)FeL27#_y`qeKgy|H!MU4Ff{OK*!%?xjpA; z0|y#{pm$<=OGC{KrjmyLgpCct`Io%ug(vukjml6w1if>xU)lD@SuG2-{+ncd{vN$BJ&$_rr0$QEn=3F_Z6HM_YFa zO8mh`SQ;`wwYdlq%$N}wABXHTFx?Gyn7}Gu!i#I!CXZTHw$8h+NeCyE_elthG935} z4NtT+;*M4B*X~I*p3xM6y83#Fsf?DFh>wO0f@|CQxxC$GZ9f`9ag&QTW<=BR^h4j^|QkqPAnvySw@= z93&nX{Y!u2;{R&yG~~;U3HyB7cGuSVq@e}lk$zURfcTT?2={--bZN{BYT~2WxZA=+;48_xC8Jh$d;Ia^aK;ql0dQ-31I`I|EcCdv|b{V%pV`890l!LJLas+<}8I><8 z3?#j`)e#brBAN(+OL0b8&# z1dq+uuKY0hx(6b4A-BFg4(h@@H7k}LO(I`)6JwuW#rj2mz%WxBSDmP4Utp?~j=@TG zl|UyLuym?za zl4cS?-GAW4HOujOt4!Emij>1)dI%>P0!4fHGq>bKFc~-4T;h`RChC*a|8?_z-xmMk zI3Eidm3*(JF8tv$?hE~Yva=8Y@t$$KKvs-;2Tu=q*|{SE@sK2IP2C8Qb}o0_9vyN@ zten;Ig|FXhNHbKU+Ya3DHg5vgA~N!k4Yxs|uI8s!MJIknnV0k7Nm2jUuyc_vK82mL zezO}lq-B7H5xm{q;I*aGCKh~3W1Ms72(dH-MZ;6xyN^ZEd>`3L??n46)cK}xzYsY} zl7f5EdYPsd;*4~Rhk#~6hlbqCMGUa;Z-K0V1`YN)z;(ckby!&7c;p5Qn~rpvMXRIQ zb**EVzO?lEnEl01KBq1vA2LoNrBox7ExfrbTA2-PaA(Z2gB`ql=Q#Kb( z`Lzd}OK3@>BB-`_8sKYAda2KMA&7H8-b$Ob+jX<})ts48`m+R0?AtsGybdeyUF%U( zP84<1=4#hIYQcFPly&`dfCuu@rDhk2vlW8uwt0k|$>*HX7oDcGlRzFauxnwHMb(3H z5V>Ta0tR@jzeXw%LXm;1ytc%*HoU+JcUFS@5)CNun0zygM=+@M$p85Gumj-hSVEv>Wm1N|MM`>a}B8?i} zDH=w3t93+t9u0n|5;S-Mq{lI*uKvlJIGULHbt(FAK|$7^-uB~$kC=PGsVApu3*$wn z*jNMKy}6bzOD->Vxsc+LjvA?stCTgTpWmUX3u_Txfi<0~yEle~7`6p)W$3 z$U60A?w>q3#-3MDc2rSPLl32RK=;TdF>Zy>45UWh>$M!Q^$a~{yRO)R)f%0oF^n9 zVIA~}#u;;qC6WMS2dKf;z7hh02q_C5JiBdQ9+HkH+U1_=Fk~)LHjQiC78p{d-=A#r zT^rAU?2ULS477$*NJ!cfcoYb{7)CJSU;K-SwE1KT$%J2+7;bFNdr6@ zP@d+P;nLt6H<((FbmVkh zQuA{oo+}@MBCcXfuLDxDzK{pkEG^>S0)=&w%s66GdyPQ(HaU$K$$~BD8U^Y(`&N0` zaWxZhK#-Nav_PpkO|JtE-kt$Gmc8S07Pd^)Q5Wa|YSAT#Y<~37`$`JzmC^Dh+XX*G zOPU+@5?`~(X>YwceWCCQiKj)>cq+Yf!TSsE%RM>T7s3(%vfuuKZf1-mAXRE_vzvgg z`-yl6>^3%Po-QPU;pqya9}GkMQuyQb?6)_k{p6FMwE9nj&F0PG6WuP_-D@ppG01X@ zQ*eu=8ps*R1eRnJ;myhq*@#a8%dh`KUL>EjRywIoVrSDjqwZg->3I)Q@9g~s~iuP!Gsy0W~mp0 z;qe$sfV<{my!?>#^?6J1>(DJaf$@eZ(40(FgHzCgX<+h~gKCItS5Z{EHojw1dU5^f z&USPZ3^o%kv+l7!Chabi@}lA|A3rX&k7!H;llCz#1YR8m+Zs~X+Af{NJQO$D4Yf_C46{}loiPXRV_{c%|pS*#hykTqTWLV z^e2~{thW2j+ZD72y4Oa{JQ`6KaE9{L9L6>Dg$akLT~>2ofR6lRw6N#tT@v&}y(j?F zaqRMVAb%p_ByW`OMnZp(VPfT2#7zl6gQ@&FL=6}2dll|;x$;iwAHXdb;(2^}lCgb1 z;}IQ}O9;*xO!s`&&|^o4d8M) z{CkC!`uN|PO$dT`>`}0CCw1~GE*f!>v+m4%wervX?ZnWVMTr32%BD{@L!k;O!1V33*MiugoOqC&My-TgVv{~144w};@kSSb z(xt3CO?Wv;`a=$EpI)iLZtCe)Stw{=%v?wMq@9Cqi*+=xF8v7u^U`niO&B?c{w^pH zs`x6;A@6J8+4tU(DhQr|f+p((kFiZ@Y55f3fgzcH(-yyjuniMYa^fO(igZBo(1V0T z`SAN#k`eWhDl#XmHQ&l+dLD&}#nz}oVfZ6hvWFY;IGNLqd`P<+RNfi^{8U98Dg)LS z$F+hI-~T>1SOj{+@%zoB2#qk_7rk>tqS$CG7+7uGzSiA%OXAFDZwj{NJLap`7$ilO z5IP_>59O8x;%Lj2+3yG}nZvMlS7}-$acJgJEfjND+UBpwcnqbRv2>icBqj{l#&^C; zZF@bH{Mq;(A#@G|+XyKZ<}IlL-d{P!Ul=FwE;kVbq)$1gTvA2b03LN?T@(V z2f1YUwPT^Wo;@c_@T&?cyrxQor)v`7hpZFC}2e zq3KyHx?oA(xWYOxqfXI+`eeroR~D7%a2}kRx6~g(fQ(l)pq5jtJ|3JF82n_Vs#-o) zGoS@r80v73bv@lhgV}l|6_Wjnz?`~|!>~ogV0h`mj~2Jju!xq_2#3)Ch)aq07ScR> z9X$(bF+e%&jp^1Q@z0@g@GvDxMLi%Vb;5T(B}Pg(sGIB@8K2~Qm>MEF@!PRdzUt8p z_LSP@1vF1&lYn6mOh~(aKY3)*EtFPO*$b-mK z&72*ho$s(wtMGb^Nf2^-Wx=5<_*sCR%s4eN_*q1HdWWfqlD zUwxU14M&iecZp-9hTuj;D_n(ITYQF_@Af5$w3YDcdyb&~r~8L)S-Rh|#tBK(o_;rm zmiW#N`6n1NRu?A{44Gh71=ElGOLK2VaXYA1H+T;4B6=j)yz41!UR({Uco4fsFXcxk ziFWwtAE2kO{~~}uwKjshTg-RBoOC{k1#eqn3)=$K+J>%(@P&iO9|ZJw18-mWET0o5 zP8oLu^8gPYCBOEAX|QyXm;;lxjgeX9)AS71!87r|yH-PDVcE=od`RP-z+SE!|DHMn z=B`4lhhe;{Q5v_PeLPY7(DF&Xyi~~TjxMLXP#3rQ&VNs1Yt&64)up^$OLs*_Qt`)l zf5ph_J!`g`XJx!}g!Y_C;YCux z*q$ud%pVS2#jb~-7{H1t#0d=(+vosMsH=#uFe#_?Fo?(Zb?7-nS+Xf&z=L*i=;XT> z{tL`^`rUs3QDacYQcep3drmk4HaX({)IF_A?irN+zD(reY3d|#86LQaL)||7E49v| zQN*aBTwc0{pUqsSjUwmFE@C<}q)?FB!briMBGu&617!p;!k16{l0kHTI{Zf+d#n|yc@@7Bzms;_u(a``zO1iP36Swu$ARh5i^Zrlg(eIHXG z4{@y|_?VZQGl6)`;%Z(JKfW<^7-_?N>I05pu&T9*p_LA$E~Dv`3uE>dc7v4^S6t#= zDFplPEwsv(!;gm~)-5 zeQ9Qvs+;H+?- zA;BWqS0VdP!>S(b%tP5fdRdN5_^yMPeGEs2pWt3tmM&gZxV-2Lw@cc`Va3(sdjlOx zkQ|<;7tHc;hIdGmHmd2pWA{6eNLZnOdteP%XlAO z&~pGdZ&KZk#$o63ZPIW-CUkJJ;T|@I(ER5u{Tx?~eQ%wtGGlz~Q)~1yPv_Flu)iiF=1RM~5kAou^d&O2 zQMKL&m^kYv4>M(rd6Xdb-!S9rcT)o}96A!Fut#{-V1O^{)L8Y=sV2@THXO!`$UcL0fD^wC-*x;I?B`0pG;ihC8>#gIJC^) z2PArea5|umQ(DJuOCU2Hj>`~5u_V+Yyp$W1;!vTzR5JnKn|}nt?U|22X7iFKM^t+u z>xhYW;2{N-KYMt<^d2qQw1DT@(sMFxuB(sVL8PHzl99g`HvrBmpKiRmi^(1@Wj zRQip63FmkSg)2UIPvmr+3qbBUn-#u&F}V&9Dbhq~pTbdk8DqHIPb`x~S%uHisSQnv*-?Qm6&4M<~mTeIi>9hlLdEk^gEQm)@amD3o#JK8T8sB0i; zNB~r6mn%^|0$k0po6{1%wv^Of!U(A?Po5tR{3`v7E`|VvpVUAzwM1i*YSs*;`8=Y*sQQd z@(8n1fFo$a{}&j%f9{w*JvJaG?$;^@KG*wfx^^Z7CpIlWKIB@4$qdA*1ge1gtt zWhY}&`M$|~EsSCe8j{6E^4|q|TG;5fJEXTqCIjAZjmBiCe7_F4P5?~jO?@}I(2a~Z zLvISHMc?`1O)J5XCW1{ZhuH3vuDL!{BYyrfXNK$7J9ex$ssjWuW^*5IscB}ftL{a^ z-ZB@J2q#LO0rGkAH?0QYwM0FoWL9DDek1zYo@IorE);L}r{by>`LP@AE(kqiU(1jK3RhGWZZAwz?>H#B*LCIk}NyWS*UXb|4L| z%+y&gT5`dGOa5Ur2AQi8_;6?r)Zc})KgP+(GWNEuzp&R_(Quec zr!tSgR#6N(qTTT(Adb^}y4;lcnEEyt_R;>0JB&JKD)SFb(v-43UN3duM?Q-Xru+3f zNzGP0+>syh3JOz6>h)Rv)v6 zxT-0Axkr#XeW-Md@nV>94C>*)0z$u!-%pvg)X1yOIS1kPB^&efS&p3;* zyuQsvqOz8>>MJ)r9t-ku{9T9WXwQQ}GM9y~r*z@={_j4q>X@Z|XR$I)UQCVNOEsH6 zE*f8M>zv6-|o(+9T7VmaB=l*&Zk{Gj#%M}m|jF`Ur>|JH!r5cl`A|=m>+%} zKkCN@@_h2i0humgOy}D6uc3hI<8Jc!?9tWBzF1JQ0wZFViG#t3kPB|<}l$> zfCYL71heiS1n+8)!Q~6wo=MJO9a77+cF7Hc)I=aaqgu%<)tT8ir^IZyiWTLUgSV)+ zxYFy#6;QzW%0PPrKL#nv3)(bWOlWr>r4s%5Zyw2#E{5Y1iZz#2Ia$9rOj-FWY}%US za?gFU=+R`d!+h~C_}u82m|yiQX3c@{_KyycYP-8oWMM(l-nZ@B z!Zj7dpQ{f|jg7-oKTjfYwroW{GAUa>kS@cO*$kfKqhmwa&V7I-;iZ z7~FD&UC>q>OEssg_g=7(2^$J5mdW&#}=I&09w-~<;OY&t;R=) zsFv@7@fm6n_|+TuviDSyl=-qieTscPNpXXxTwj9j=%W$i;qA>4H!Q3`9OMLUht+$! z9gGKv)9rI<@mga9p`+KvwLiOY=8wJuOhIWoJ>S%|As2F_`$&OmQrzKTWL_}2YP7{z zzwtq7q9p+To*toQKT+P%Nvkr*%^lF~!=cv;Y3jgwdrE z{{+OWYHhvDLYF|X}1`4^}q2T=Jg9QOkc33(jXFh-FJODv< zYtMyeb4t4MRxE)%q@}5*Am{daH%&wXit}(m_dIMb&0O)fTFgo9xd`Nb@ASIrGfWV( z|Es2zsJfsnY@oB>pG`J$QMni~kMBRDS3K+Yu&_+Z3JR$dLODl3pFxQ70KWVf%7~WR z%2M>`!?dg5)Ecy8uCiXG&HX5e`|rwug=+9u)G8(=GMAe?|9XnCoeBn-ifQP`!{AZ# zm@vg)kAi)C7Jmfu(ApP*2K%8ORST41#~7=B6XaHv4foP7B4#RI`ew~8zq62!nO=QS z-?Yp*h*>xDRx%>sa|eR?wWnUffrRog$K%!O8AaKNOC{6FerY*g+8{`Yy`EzK)9+Ti zGXD^$t2*lWugE>Hx-r$!|5G2@qGADWC8zpWCM03rEyTXR-*prj=mt%0LE2js4hPf| z-W~MxNFk{w%PSKaal0^vv?n%Vw)*JCD)a^ZgvvPSVbKfXa9?fxgnLYp5gG*d^V`D0 zl33N9t+;DV?A62!xOkfXE);?3z-*D2b9)?p%{{lUASDpJY?=&v9KPm_e&Ug6C} zhI8ol0-^$OYBvuhU8cA((-XS8r(|OJ=s<7P&QWVDd^Xllv^Tuq!Tsh_?w;V(Jo@uY)9;f} zP0M)bnykY11=V?^4Z%N-_~J(z#IxaT$9O>Z7N-KB4@l=A#p&oc5$?q%6(2|X1iH+( zmAx<|*8f%Fzp`C-U|}>7U)2H#^9XK#;O)7M)B9{fFov1kS^k{c*c*%-JEQe^MhmEpz=<*-a#g>GT z>zo0=#@x*DgjhijM1C%elMxwmO-)oK8G;8DSy>% zaCZ`U(-7;Rk&!U(MdBOn%60{^<{K z{%1{Y3`mjreaIUc&ssc;g%g^yA(P&H_X7}Xy!*=r&T4N|BMS(?N=eB$r$;Fh%#AG# z>jgUX`e;1ekQ}(+ui@{)o%0|y+V=r%j`jfW`#z=q?!Txv*2CDnk8VQ^K;cOGg?F7u zp01Aw!ZyiiL_-ZEZ38%MjkA)4eWzT}lb9LstTFSSG0R8$FUAT=8Fe=rqo4TC36xpS z-A&QSjz!0IDWJ_ofFhTCB>rf7`?TKI(pbqzMjsJ0y#gbU54(3+Ik9Suy!3}xBS)Y2 z^}{G4icl-KiC={k&i7ZY$+)|=riP~pYHCTc81*4XqdU=^Our)P;#&xrMRCWy3Y2<( z!$6`GAu5&)zckuLUxzwtVds_u#2k1G9ob;~4rNK41c$RW8Y{q$Im07I9nfHY4`$g5 zlTruZtZ6pWYcUF1F9rh$p~sde!bw9bGHgQ$y<%L@BMheoF;#+-g`&6}KY7n*dCA`+11jAryIqJxQ4a!M z%>(aq0jm8YB%#lwtV@wq%1s!CC6}9xFs-`Q%v*Dra}u_YeTrE4_!3ta6XDEzL&H7% z_)hoH9_)V3;uANI`L8!VW3nVePc@7Jys(gM2}wYY#AC|!qpj}%y3~6VD=0^=;jK>A z2rRBa^&=o1CF69&j%aLbtatHu{%36U?~k6+fO=yGI?|FIeC=Z1F+x+5W9k2Bn!YQt zPZe0=g#a%5k#ltXidm7raF6N#!V#0TuMQQ^G_mvZ7!wS1s6HmD5_dz7hy^fUmN=>~)(OB@+8eS+u;UE)AnKp@SW_$b^G;sVAdI2k9UB-<#<2P0 z9Xt;y*B7GoSJ<1GPiO%uilRMCZrh^fpCM;1czw66U^X7v5HHy!*Pe|0TgR`h5B_EZ z&7KbpX+OUV2<(tUt4W9%5Fa-aV*we|al zntUXM9SbvdC{`fi%JCc-oN&>x8}<1-2$(x0Zr|QT^vGY5v1Oc~A??n7FbXL|4QF?^k_!=)U_9N zvra0v?w3Xvn8Y@jmWD62`bfEbQ%ZV zcI|XF4e6t#tO4DE;mVEeAAE>VFFi05a^NlHa@)98yQ`PU<}%+xS{fYPIKJn}DKdFS zaw-I|36b6$-H1(#)6Yh?rrF0ViNnEiB4=MAhvfy~NBU zMKaN(NoZGvhCRP1OJ1pF&g7?4 z5}vz?69=X*+yLq4P5)$BLF>1Q`=O}#VqiJI!%2qvue2!T;lwQ2fpclyzEln>w5!S@ z{~LB_z0D4o8v<0$xXRuIX$1gEHEiimx&v2qwl$#?Ox}SAy2kYMHrDq}@F~pha18aS zo2|y56gZk?9CCi^#mE2Y|L6%O3sr6sCa&kr*_l>uCT*JB&DHEP?36I-rq$rF!;3-u ztE8+*r`3qXFrR-2aHCp;O5m474?)h^kDGo zkFf*6m-8qIu}BpU`dCKcF`YO~6q{+p_88zOBaO{&k%iIUEot+|a?04JZbGxgMJ-bn zWaKWj7JfWE6^i|f7Do88s#M@tabm?J>@7NsT}J0SbtL`uOLyC69^5%P>T=bC_qh+{ zDGw%O^%rI>S?P+t2HkEaT6P?n253HkH8B@M5pSPQU`woi-thhNj~*7XtrWhIzs>KF z#Qwh;+Ngpe^{zd6P|!JP!$wJhfN}%B9YEprg7_?3U#ItXu8tfeo%%dhZJG)S7h&kz z#6*(8rT$Fx>3^uT(5$-jzUKMyq(E_&&Knsw_4kBX{L!Bq;FA8w_DW0jz29=UPa9|d z*`xR*|rn*FI{7sYG2SGfUVQKIG?~h>zV3H{eZijz8_NmW4O29x0JM zu6KQ=HQmQC1*nU0A^yN`cu36b=+m9KX7eaiCd|!D)MwF&kQ}!ltc!m=p|11YB;~@S zxkbU>Uv#)7l|{g&g;ydxDerJ1f#0$xHd9_rox*~6zbd2wktjAxFzDER`PZQPq+S*D zKgQ2j^BOg|%hwJ02PY!QpF~RP^Nf2lmq_rXn;cWgJxPIcJAMCXw@CFkEuS`}W+)Y& zpAUYDyScr$`UCCENnHIE^xN#ZO7TvM8i}R@?)Y9_jy_#&;R@VrVjK`cJq_*#v~-9<0d48;hHJL{FOc217vi|HQ7 z0(w@Ky`B>zBiMj=!!pF)jJz^I>iQaRe35c1;#)2aRu5Ez)ZArO&38B9w zAcY<)z_pnOhpVZJ+j&fo_)IObnkCmEmI6!0l;U~wOSvPLKrdODusXVETS6fV*&sH3&rd0?tay)56OuGltIW z^0zBO&QgETHSV&TmjYTYZGUbo@gwE3AM(vA(#X6kJ`f(to`yy~Yhtbcktb2pjiQT$ zOBvtEF!+xb7zNl}Z{;={l|D1e`T<#(SQR!f)oF2LkHrA!0pTZn^r9r(7ZzL zP4PPkN#J#e;m=V*Ub~IHytlPPnK~N<9@AE0Ar?@<8!y!zH4j+uHFLO54*5Z7982jn zvixM!gWsC!dxcbmT)|hr0fh9SzBq|WkUTbjCSYz@RGR6u0BM)8e8nn>7Xh%?mvCr5 zvLM(b%)OUkG^*{Q!<MmlMY!$nEgmn;C_;=_Qe$ zR;S=pqJAc>r^O~8FA7uLa9y4)E`HyIQ+UjEx#3*xvQ+~PmlaFzmMzM?~cIZ z?hc+LNCNx2Ag6P^I^POch=?daM0=Ay!#I7vbPtu>*yHBe4D?w6t{1!ZPvbv$sp zO_=zurKKYa+2zhRiEv3DutI?%1@gbci2sHau(b>~YG*WkE7+KlVC0V)P3!32idyeKw8~d~F9ocv|7N^sVKJEbG+IDNxtq}kQgVL>Ia^`EJQH>jz1RnR_hSQ` ziY^Mywpq7!7vTtGM1!Fz+Kz|P-__`x(IfN9MRXwvmMP7GWkJL)s(6f4CB~eHUY%|IsQm%m(;+nF+r)ZEF$g zO~p-U3^Z28XG=>6qR=DiHjxXiL`I!MwBjI1uAh z!9jv)%UaY|b%^UV;nZZKqH0lZU~U-znqa^v_K(ucd(uW9U`GiP+uxx-nKW2d+}1c$ z?-P9qE!~R_RTg+J5j$@Mol+JsS7mNJ;?#LdCd0Bp%Suj-+(x7UhlP z?PKUaHn!N5ePtaoc@|bVG=VXdQB$%xhwdWwDf3=u7>>rrw;u1(>3XT~9Qd_x!~K^8 zYF*4&GzHESUAgx#l>sOkmHi#SbONW5LVRou!~g;fYCvQ4Bo)x%kr_LrGeZX9FVllx zl={mX)v`P{HQHF+(~@={`cpg!AK_i<%O^ko^8bz?B;vhPuh+6KiW#}^jcsuY2i~8S zdmm}bJ3Hp4pUuptLf@8?^NxGzo05T0-WrDok^C8a{@;BkHe!X$Ci}$nO)AIw#tJp7 z*>^C6{YX=<&HDk?m$A2^so2C_iP$iHq4Om(LTlu&DLSPNkZ4n3!!i{2d- z-G-0$iWBan*hB=|juyUh!v)m!2C^!Mdpg^?g*!O7q;6)Y={F!(sv@e|@#8mh>`7@0 zRu;jA!G2An8WF@G@Lv8zJQu1Ltcn;1JxMGW&Wltjd`ydMOE1}JwMo1d>zi^WZKpL$ zIbrNMKbO#|j8SurwL;}2A^0<$hV37|yGozog^SBPyw)BE23Oy7!v6ybLG`{rD2Gu5 zq9;8SwL8?4;DNoS38B?#rFt^rctHVjri|e@L5}$&YJi;kpr2giucSlW1sU{B6AZc$ z1QEA?<5)oI)+p@3rS>`|2OTk&vqWAqVXH-Lqgb!ic}onte%m~Pp!t+)iH-*i(6(!C z(!Mb^_V6MO^`KsiTEnC{3_ylV1miQ+Y#i$0;b9scKBSFpW5!D(cK#vA;0$cxjr&^9 zo*xtMwkhOHUrOr>L4DrepVXH|$$S>~;`{qpHWRmrqq`uZ^Q51Cj>Obk>*B4cS@YHV zH}w6^HTlJV7t0^xoPYq%-=v5;-7x99uu0}2egxgZknP~_UZaR(|E@2S;ktlpYbWo7 z>cBX^xG?mw1-x`3H1y0{$<9mS1gq(HS!BNF12@pg_HcB%>a>G{gM-6wEdJ8%-O|yl z+K`P&%PuE6`3$*wmCV)anExUXlt1&0SD?f}1-S#_#~l%Potes<`0V72>1=v-y51)J z>}N^drw#hU%W+@U7oga2>#qBFh|j>XD}nSe`KuS9^r(uSn1mGwOR&&sh%qTu0mP4` zzG+yNc_=Z{BQPpMWAeSWn2Or6gNL@k_#s#fD}G$ynPbupVfIQ2lnYtQGaos!RNqZh7$NTa4UqrKyYyV}>HJwc8AbPTLm|mAb&(EA?9UCqTEk3(7 zrT*?gk)DQzme)2_g7T0$xps3oP^utd3&YCh^b0K0T-Pt?MNt597&OC3_Ii*#mB$IU zUIZgIACTV*Bc)`RD?lU-AQ5U2DuZ*Sd@cx$YZ!42z&QS|;FV-OQBdb#cUP;QX2>PYetG!4Sa$=B^+d54caf#XYJq zH69zI_3oaDez{}6H;>o0N!`ai(AH*R7OEZY%iU*jZ-V%70?0E^^THwgnBYXB4H8?m z(0qAoUEIHTO_o9YR@et-pk_w<$N8}{>8 z=U3wTm`nY**(${rFb`S%TOPRVHXC^P?7J{sH%y6pBSSoDmT)3FNXcN>ENqP&$eV zjmT55S5+$_l@n1=fY%FRD7C<}_GGTz2Js7}QbPKk@S)s!+#;^WBoOB?>h+ZF_Ea1T z*poOG!Lfl**E|ZL@WKk#x+|knPPQ@RKEhlq+RF=7!YhEx%4CqUvh#eHwheQE^4$XL z+JcUt;IjQ&^h$z*8Z};A=4W)66En(Gri?*Qu7nUpF?!s*}z$! zqxbhcaZul|#mZm6j~otf;Cg^>6iB>uhcuTsT4dGrDp9KtXASeGdE*9uSFh0SRLrD2}1!VVe0{O>csJ8JIIwKWhyW^$WEp*}?X$%SjKB%#S{f1MDk( zX&8uugM)*^Zwo%Q?R(rObSYE7`FN}S(h_II8D;wdr=?z?V3_m&lDzTpxCkz+Et;Q? zGk@#l+q(74oT%hB_5rTJClL_ z$MhD~F!*ve4?5f9`R%X=i7yg87{~Cu!=4F?xN5?q2s|+~VaQaHnh}?eF%Sp^gh4N= z_e=zt&-h#~299$l@>tMn_IgH_73O1~LRM_j z&JcbCfXs&zep}WtpNm2l^7;5PWBFl|q6<)X54ugwWWNYCa1X-nHa_1aYN#TtVTm6G z8@+R31@GZjfIMtAwC2c2&K^I44Lk}ZkP}0=Gz^!7+1uNrUv;z@pO~QOAspj2(XBjw z5NvMp9U(5~r}So0lX7~0!=C=!=!~IRqRBUt0@7e{oc()qo8;(aGF%t%iyN?dp$aZO zPUeFszIG$NRtpzmJD7>$j~7T^U%_`MA%6Ty;{vO68vK1_GMM%YX4S7s13iJycpu*> zocWu!hRgH`Hrc|^S@4gXJ91$(YaYV-tomf(c z_q~|b`J0Xd+_^m#$NGKkYefI#ACuS^!~5olzWfZx8`hs>DE-J%+ECkdm^`MkR zS`?tC6rpyNLfEJG=e+;ul>I8s4z?d8d3IW4xW!vnCFP<;kOF+s6LYp%dqs#)AMJ9g^;pN$0yQF^X$_ z*d^TxjA^T)W`;hln&SP(Ix6Aj`1I5it#8+KT~+jjF>Ln{>GA~G{?MC^1ATRFL_AvC z*A@J@@IVId|M`wCe@e-EZB%m_HEubHf7Ds0^Eupuxw~9%kW6w?X;}Go=3RVcZUmOTe%o&c&e@p$4wJ5i8^u zUV%!unke{5caSQb4ND3au?|@an35vWPmBjl;{XS11fE7e0mM#a?p4<*#KFPA;WrN- zqr*p1HrzEzdt!X_SW{B*-_#&dO& zZ$YKwEZ;UB-fKN=xV_fp84|nvw8bO0z{Kw`CVs5^ZGzJ^q3Up5Ki#g2(lDMuOeD|Y zLywSoN^>pY0{1MVN)#7z=AtCf33UK3u?vN%-s%{?*8yp%+w){>Us{r_j&E9=nW{qB z?1g5?ivo`O^C9!;h?Obklh|)^{7Ps!cwW*+<)9^-p4$`iQiU)%>4WQDuN6^&hl+F$Qt3h>yf|RtgGSL~}hrPvxb-JdJrEx!l`B`3ii?3m|mm z%OJY2Xgx0wJ}K}&73G7#&*@I2z_UHo4W(ZYfp0+Tz^2*$4P@YPuMQlSEe+F+a8x+g zAcVFo4A%iHdS%Hmfo+XFUGCORsLyl}V zrL)1soUIzTOd?|oAKyA>-G}i)GuVRqxS#j&_g|bH?`O*gehhEH!NI}7;giKjjQKnH z4}F%vEFTL|yzzY5a1r3v5tkBa z#_?Z}0Xwk&Hopt zzo`|tgc3cgkthL;OBy3VYq+w?>C-I4$swJg<{d8=C+82^yQeI7>L-32Khy`dalBn| zk}<{wr3Li@I)55p^Zkv^@czP0w|zv!u~CGLKwR+WdlKl zCMRlu4n`CO93$B1hJs%pU;$1m%v?_@c)vY+EJ8tC>~myz9S>m^EhL>CG&!FjX7~70zr&G4o#yixr2)z zbZm>bC?<@w(Y7^tgz=gpI&4UT`=vg*1lXnyMQ`Cg&JbI)Gy(6nIhZQNy zy#vYZ1czbOa%(}B{yG@%Z0sCg^xiIsxel4mXGzqiH|6>z?gnDjJb4iY_tv{0eB2EL zOmOrqGE*CI)-Nl8%U4Np2^h9`fBHPB|LQ*Jg=D*25S_m>4=W2oa8d{qz$N?w16h>w zOZ_-Meu-*-I(siFybOP+r;MGp>1JlzFZZ0w7nU{n)Q;LYa5)d-#;6dSIMlF&J>}VK4sIFOio9Mv z2t~-9<)XMQU_@TTHgDTdGz?jEBtM;4oL!N+3J#z69Y7M;Vr z(>$mfZ=|fxW4u)PTA5k9sJ12LB%fAZktvGXK8kpX4i?{exCFTt$17ePzLXS6tXbR z?clgp;JL>n|H02F`cLMeYT|ptF@XDb)?f#>4DDaj3*TB5liLg2OE``N9JGU%lHoEP zw}-37^G^uLX(&fFHZXg?RI_a!lLZ_Pm=eT(IT%+L(80mM;Wq^zG4{Xe9*D+u611Q4kd6rYh(W z><9L+DD@7#mg)$~4|AgswbRK_2mcj827NG_&Jfpx>v>HmFeNX_MUbdqWcsiU^N|#} zRzyKr>vBX;$`ln%XbM8gl98eSE|S~068vwoojlG8kUz1EP7}Seo|k~XPAPpLutem!SN!L{o)k{bEPfPn|4MM4{|A%L|?FsBjiXtuB5l86ZawwjLUdIKa zHD`1f3b_R^J`?0n6>1%Zk_$>}6XwCnTHP>=q^ErmY7d&Jkfb}Hmj&3s-O%g#HZ{9v z`~J3CJq-&Pd+AZ+dr^-QKrp`t4q3(_@tWK_xEA`ophLDE;9ffiR!(LJ1iuUNSHt!> zXSoA%H-&jeGH<8BMRgl$92bHf%`=lFy@PoW|ERG0SnRZlx-o3@9Ioo)meHbDv4x?V z>Oik=8FTI0C{^kphmZG-S2E_HGG<0_|9Md64k~20^GxXPiST|W?1B89rzu)H#2K6c zaeSZ5G)UkKr?jt8^!6o^FMpZL?GK2iK>Xg`COsd2NAxOjXdk{u_^vEs87qaX{_$sD zeRxA~93ba~RIbzvlfRcPllp#s_{UgU9NNbIE%#OWDwx~SsXOE3J(We8?`y+3 zs+pe_r|wB-13Nf4IQ%ByBmGmH?e4*<-qIPZq_UZWB3lL~^MFYL%`KSxf#iwWLlQ54 z7Z%nHvKSd@rckZ%;+nK<4UigPYVyaBtrssD7Uw zgG~m-?tm4)PHf#V@hc9)9>$}Z#hqNeEt`b`of8GM)oJM=5_auTFZhoGAvfZ&r@uV92JD$gsf z&w2lwY=Xz}InAEC6C+Ph_?|GGC5p!nY;$t@n5dg{{W#m#46M`d<#lrZ`G&qZv}=B@ zBHe(-1p=r;yuZhtKun}T^mI>3(Sw@G)p2?tzadyhFE7b7p$2lAMrg@ks0%r4B~(#M zh2fAC@}R4FObR&{yXbp5>V(h`jrO4e)@{9RE(}BV)#*Ylgfi&oaHP5m+0ln`*Nvud zMS3!T{Kz4gxcD-sxf4i5cDTo~={DPz^mQ?B3X0%PJ|DH~WX8bu*+J&`&%K~&tamMo znr&c#tKEVvjGrU;uTE`KjtuTC_|}GBLqkV2)*CY19i)sO99|K2Zg1a=j*im(dtl0q zJuyCq9nc=NcA%N&rBLlj!)@a7`8ZB5m>@IruVFERY`zCl$MpfD$gr~p!x?N0U|J;JbqR5BaB%o_;UmTT z#s5errC!np1oa6C0NaJgg@6k}X8b<9OX9n|)}OQVVK@q^)KF_2Pri#Ke!Y zB3Te&yLSV_4G_Pvi&Nr{k4W{lK?tWvdyO+F1Z6m0|bLwfe4d!WQsd| zD9_|&2UhZxy~eAsyQ#EZ(^CC3C#441`tg%qoSTu6MXNb=}th&aKh zTO#@J9O+*)_H<=(RB&zRY5Wl3!q9~+l3av*vo<9@otwll5C|X?OQ?ag36-vfA2ELe z#P3-WTn>g?!%gFRQVus{r!*_-&(DiFnu90^+ZV)dWqVDY`|34GH`lBsyznB?D`dY? z>gL0FOAz<(#6P0e_`WzE=mXZ~C3J-`^^U><{KecLCV^*>nNp63DO1=v^J1|2 zH@Z*KwzaacOSU|VVC(mtN3i40ll%&7-?jBkL(|i=c>!c@cT3RuDf{3IVf%LQfQ1|+ z4)G+O^hx2FN*9Mot%LAA4I6K}MBE5g%_P4RK`Gkit}eNg#e_1e4`oF+KDp%Mf?*K}~&LafjO0SgrzVZ6213QeaVl?kIL zk}Bxoe8P5BJ-b+QWgUpQ5%qlbi#yQu0&K8DCdhjxY-5^BON+Qn45UTP)^Af7T>|-I zC9uh%ggUW=F+Z2P^a8Wr;gT>ND0@Tp#{sTQluznHA0D>!sMi*c3@#)UsT34U zJ8YU_u}Js!8k)Pn%w8x9ZcEoXG}a;>&X)~W1!Smv1;#dh7(o5o*gmjlYrH#R?n2Sy z>M!k(splg0X$v)C0!h!(SIZqVi||9;<)+H{0POB_-7} z-n+QcFYj_QQ3!d+FD8Ebu&h$z$FWWHLn4m(o5Tb2A|${24>@jWnksFQpMOAVESM5w zUcB$8&%x3ifod~Mbp8U#MxF~cLpjWGR|gV17&7tW@G%heW%c;qDvjwT2)?n(AgNY2`FL2Fe-I3FD>y8(Ap*K6hp5HfUBtz6QgLUH zZ4keLEe%6rvsR-+FTPhEV0>-o&~R2Tv`CU5e&?Y^bG#oDKelyeC*s289IqN3;&b4x z*;vE+m~ngC6OG|5d8fT4Yf!xD(kv^A;=c{&ln!`bS)Ec7$KX2!^+ieQCpD(nZA=MG% zK1og>kfN+P(Zde|Q3S6ka_JF}3(W;wLZ9m09zUu?2WnLa%Qx5S;?d5_!5g*Iip!Hk zFfobKTu&<^>G>i$9V2zZ>dmZlTv{WuTx8xmz!Oi6pFqMo4P^oYMN9x~-M`Gnvg6tP zYmdr1)82n@I3k`?Z1W1rNfG6Z7I~@hMNTcIb6D-$N*svw4o zF;RQy89iE(it7Q6h^8t)IfnErA9L-=f{?}CvTYA{9`_`-f@1}m-#4X5J;41K_DGJ7 zLam;_&leD~5vbU+_)QT0`Hnc)ATxKF&zTh3hm8>+5J{TXDIJUT)Iv_cq+xzcWtW!#3$5C@}BWNbs<*4Z#H}%i#`*b0Z|~ zf5qJpj{x_%B`Fub=Wziv0Ppqb#fGh9l=Q-+&{J!c12P2)L344 zoN^HLe031E)Oao`Ho;$%^k6Dd!1eCJE)*`US*rp+5Cxnjo9q!8U0DB&_a7dg$K)J+ z*x5I9w#A!O5XHb7PR#p|7{9(fYz9jG#hJb$GaavQPHppqm5gDoL-&UE%)id{!UJQt zQJU^R%?Y{duFd>~^_%x(*ntKcX>D7?8O#e5Y+3lhP~SLHnB8IUK-H_{J#o|tyI^)v zhf}DPXV?bra>7Uts+okcYI+fO014Yhw{b-)rYG4BPFutkb3!z+!*rACQ( z{Bf(Md3&>y6{!yDW*eVtv`~TaToaF5TGzV6W)|vn4P>xhBRwq-)P}D$_X@sK$B&J> zTV`r%iYmi9vUUjF7~`c1=}K)2gm8*#JETYO^J0$T!qA)uvH>OVxu30>wfTAO5Jwy; zaq%2!RspX*Ce0ba3qQd3i9hFy_yP9jn&kDi>9Jm&UoZ=kaWUze`0m*%OhC3R+|4uW z;q?1nO)h;({!V@W<81#-fWb+e4lPYWNsiqG2L}fShffM0qr*o(U^`pZVfi@Rj|9bC zJWeQo>2QfoK1?{?@8&j$-)jl+!#uWsxvkqmvd(=df2yrbZ9mi;BXxk+Gf+9|VZ8qj zZtoF<6*|K7gQOcn3*>+cJeS&dl0Z3XB->(DryUhQ6Y^R@{o|w$>a;=p1c+aUxKOaM zS-qST(p6<9e>q>Yly5@34E9_0ebwoDMoA^CHpIQo@CWLahQ%qRWleFyM<>*naVqig zgavzS*PfyLooVsXNm6G9)=S@atlDMwabg=83714XFLR`(AE|$0v&Y0#^!MG#)6;wB z#J_)7QS&6Z1&4$ZXmZ-9TuhZDXGwF(y#h{fuAr!p14+io$~Qx}uZjYXlSar3lrM5E z&g+n#FQE1fsfcA-)e7wi^<)sT;g79D5$v+{D^yvMhl_{Z_#!k#}>byu55$k)!;@K3mvbCY3}D7;T2$cCCXc6unDzq2R{}#DP$P?X~{dN>O=Y4Vqf@Q6aBKI&02m#b5Ur{0Dc#Q?jYQdbQh%eXX~Q$ zVo^>u)@A*Xjgd7>{I+dB;5Avr_o?taDejfcWt5&C3&hnI=k?_LIa#&DZ`Jdb0Azuf-(cgJYGpDJfX{0}}7Rz76-*#k141 zvIfg{uRUpYT4X+To*RelVIN}@*7wEvBIp$j)6Ei@S+Ivku#Uq6l1%<=eZM@$K>&wA z)mZgA=oIwiP}}2&JGTcr0s$QHyFSPH9rZ|*{ED=-KR^Jv%u7cJ;S~ZKU#2}@cABsl zJ%O!Kt`&Mpn<$6vl;93RB;-;rDjx}qv+@V~6Cz{&?p$B_J3d$9v|^GaX=g2e@`ZWw zq7x6>%(^GkyUcb(c4Nn`aJ*Jl8B9Y22bpEQO}ESY(T$*|xssjj*k(v|VaN7Bh&m=N z{}M$y>P3cnJ-E4n&>+q-YV@cb~>Zn~8 zDql9-4W{JR1Sf(#+&eNChlc3E&Y>AAKn0w|NoX93N6ldlb)famo|(EdW^Un!4OpjP zYxn?XZ>F_rHffqJH8xCTVn%lF;B$kB-QU2Cezb0;=V!$XaUT3kw04!~1=zno`7($e z|44afExsyO=ZOj76%atq0Dftd^c%36xhOO*URisrt4j-1T>$~a^jbpts&vL}hVS(C z*GaCV>nxpY6L&%d%=865rTo#reQs!=D~PKJ=-}Ys@JZn#+P;55V0QA4{qjJ7c$MOZ zge~nt{Ra!b7Ly{+i&sglVBz+Qq_<(+J`E+qgB?3JO6FheZwk)doyMzIm=;_ktz6!d z(;X>)@p!}x@q-RZ#sIp7$h8i8Fke{K1}#JA5RzZ}x$Ad|qkbp-KHfszKH7_(4f0-%sY`^6Y!FgpZfW z%s$VwhqG32vT>7)A0Lky*nVc7eT7sXJuq+i^>72zJ)E+Rf!*sX!CGjSvxB?6URNmz z?ga~&WDlw-s5IwsP?u{0c0n8k7l@8RkUXV3ZPnG>P0x6d=Yd%xeLmQH3QR z>TPIxJ}1;FW0(wvAYh{48$axcBZaGnnFt70u0?iNH>8a-Ah&>@y{caSz-yB*TwIb!;1+OVPPjk3+?ZI0JL z63Ygp)_jH&h4MAi3@b)q`XSrBrTA>CN<8#H{z534Jk0OMO58Xnb3hfW?esLw%n4xpgMWGw8en;QBVJ>|6n!Pmm-Vt{Qxjaf@ExoXrk)@^i z=sK(QFLG9}Eh25Rf~(0Itoprvopky`E?Xe_L{@O}{igw>_xAF@55;r=-?JP>@}vUM zE(_Sf!NK9L79Y9J---5a6#x63T0Me^Up7db;s*y0*#^E!{dgh<6F*J_;qD%5P$sT| z_&wVqHJ>aGe@W2>M2)RRWSIC>Mm<@|L$Sd_ac*pkI@2R~IPTL^cz-=1egzP}(rA1P zto-f33f|cz4K+y?Y*r@dS+6Z}c(oz@UFfwvR{Xkt&QuhXA3rc%uWXti1;fQa9)-Sc za$jEI+Jfdx1U*DIKhwynV#RA`qqz$V(`9RDjb_`l$DSJvu zRJ!bh+Lo+m&Ul|8I-|I5W;@~}*&I|L&+IxP#IZ%3>V9YW*dn`KkIH-F;@_){t1p23 z<~+f*n`I$1CMTqZ-bO(#P(@Fw0;k{lxU4u=xVk#v`hXp+Jg`8&rz1~G&+A2z5;@@y z`Cd+oP)QiYxE`u#giQ>Ir118;J;PbTJk32tI2$-$;yBNq$1UN;JmC++kT$iRtV549 zTo*71?BS2*a>^7ctQJx!pHq)Puxug-C;dR}yHF%Ak9&vvK|}Qw52KdRswKAZ;-+wg zT-+!4E{LD@%HrvKLq2A+J0X6o0CKkRgIa~y3NDS4xbPH7?nJ}=fw>#VGn!Nf_wNMX z`yhb%^Krt-U8sQE+2+oKFF6~y1LF5wUWr@LRP?Mjqu1|pN)#!!ecuN0JAe1K$bI#i z+{N&xK}`6V_}#fo=7psA^V3k(7eVq~n4UM>BbaLivNilICUY;YN)SQG-`)9y=w*8Y zuVn0CTN9Atx}JmI|McI6^nP2)-d>`AWAlJXBrjwV*slY4Txck%4;W_#lY@hUgTr46 zABp^>|L_hHgr$L?Z;tm#o*2Dhzus94(z;k=a_@k8eklpe0>VGHqk*4{IpJr_SjnfQ%D z9qEaj<}xq*Ux!%N$#K{p)nN1aGAEKVBe;E-4MW5Kc@?mdsJPQaFM_beMTkKzKt?%J z)kv)gwy)$&Q!e$wXVw=OH@2dPJ8udbiE~gF7r1;;wfKx_;B*E*Af~8FrbDS9U+f@h~&b$~5pxrh692-62xwq4&Bzj(ZFxDyE9+w^aVhA`ZKk~cj>Cb}pECxrOx+Tub1zDaMs97PLlrOvlMY` z?KiHFu_EA`_!;$sHSy~5s#t~g2N&3?jy4ZuiB5%VoWOG27VgCTny7Wv0ZaJ!---A} zFcWxLepkjNNX!nQ=_iE)HuC2JTGj;sDX@&8zumV{2aV{|3 z!+zKX^g#yT5R&q^>zGgN$k!l;8r0B}u=qe9C4uSHJ2rMr@RKrxAsI%Tv1$`OEK8Db z_H@=6qq7yg6N*S?^n?{mL}za3jFrz2=1$>+1uaghfT=BK2JC5b>iGK+g2y)##|38I zrzMs5FL;Yb1yzvKr>P)091om`Bj}th+!M3;ihK+ekdr|2qNZ*)E4thw(S_$dT^f-?KF%2K<#JC- z+S{XxpCY-3b2pJEv3DP$^Bo}~sCd1C#PpV#!u*GuMBEN8e|y7lM-Z-y$?F$D@ZN3| z?rXMxcV|dVZbQ{O7kAUSRf#X>0$%koD4`gu-6C;)#nuB9i}=FM z^Y?m{B&&1jk2pqlxHvx_fBzu?QlbJ@>2%s|mHLSxyv~xaU!7wznAQUvRF((lIR((c z!NK9<<0EJLo@yPI46CuFYl&YqnU|K0NHYa_oMZkt!*?ZK*TxU=vS~boUWI+zfo=6i zk4e6K9;)9A9)2aR_vZ=O*uaBr+pfz+bJ-euW{A{2hL`YAfhA+CIv-2OOSN$~Jc6x7+h!`I$I*-cH2!Wr&xd z4}mzn&Y5=Y6PEP@^XN2z%#iz2>!H{)5%cG#}=kg?Nd$`_+%pWNg1wwrGxiNJ=M>@oPz4hli0&G}uk-)8= zy)U+GB;geG4wwkyhfZnRJG?q0H*rk0X_7m)$#6FC>|LVgKA*SW@AdU{;_uX*eKL=_ zWVn_l#{hEj$0&Yq<=F~;5i8Ek(Kf9~?iaH1#tq4Rw)saHZ=<>$R~)KRU&Xicli|wR zqOrsezvwc{Oltk6w?vhWV}3b+#AJKDSu&Whf74h%8h|5y4h{|ue>r@_e?-lR^8Gfk zV=1F4s1P>hk2?|EdIb+Ky!{#;O1Iv{0}l_d!tQP3 zeXgW-8n9MBo%aqp6E+Y&Z!>;Lz}2E5Jcz>zR31!o9kntZth0Ei@&lL4aIjKn+T0_y zC4=pjEX$(e`hH#zKX@yg!Pz(x2A*U9Nr!9vu}TCYCv~r(IOb3Jfl@Gqp&)rtx9tt( zDBmGJXf^R*m+*}}DiowHMLo^EC`ysU*0m`qrHfjN!rjmMe?KLEr_??=9jkX*Ve6}L zgUip{81-%4Y=;jqu*~_grX{eh&(*I`Y(7Y=+83a5BM6Ux< z4(*g!qz!vowz+LDRJ*PbI?TgU=emG6{~^~A#OKl&?=`jRa2-Aqg%ONkjrWInKNPwL zAy#5{xF00s!%+0NuC#`fYj}N52H{L#ZUG0=7+1Q-WqVAk>qlkS{PlL%lra)EY`iE} zuhDhzwtPA64%6$lHJ5~`$#@%a5CPF+(~3B>pa3%XI4p$tp~ClAR3fV|>wMIZBG)i& z@0@OVP`bUE;OunO0JR)GB-tG{To&etj~n~au!E0kkp^3)gS|+Pj465GPw2T}lDBO( zFqvLqOPq%#yxV~~4)XUbw)-v=#xKnz*Mx@~T#zWP1<0|1OaNK=`}Qd5HGKb8G5#%l z7kQj5VEj0fFHDnpDP4|v3rn^%lSz|3(jRr%k}!;FW|%DkpS^8Tw_xcE(fj0Ym3~Jd z+k3d$FBa_*K^z<$96laCR)-G;|3hqTpfmpQ&c^rAz>tX_D}Qj4h>2fPTb6EJA+xX& zAJ2trBsQk7-Ak~w{%}q_#JtADjPyIk5QFA1BOfYH5qg~h72xEK5CotJl2MkS zmxl^uL{CH=?%n2zb`%NQ7*@e)#487N2C5(&MNQngf?K+ARbd?&J9ZZFJMmB)Q1b+x z!RJ1Vs*BT;XDNthO`oXYe8O5g!)kugHU;&QLk5Bj&A=ugjt0#k`TYK*da)bOm;q5X zLgqR>0b7^+mX@mB!)XME3X~||eCD_XTtVpq&UIi}T*RdxBhNEY4r-o{tAT42`FZXb z5=P4AW=qctJ$v4v$`)bQiXd;x^^ok~da^CduL6|60>ov${zIzgG3hS`pmvZbyKMhQ zf#G>jA7gvi<0Ozcj+1KxmZ_uKAbW>gG6)th*OA;*1{Xwn-H_$29_N{YW8-r6bA2uLK42)Ab{7s3F7%O!Db5PgSOUHIkbUBfXgT z;b(?T3fat0iS8kZpiRbmj?Cj6tC}15KaQI?|8tyj*uC40by*(SkZZI?KiOOp6@2Ho z(=l0=Yf@<%lprr{n{ zI(hxg!1z+;mJ?^(C-LoM;#?!@Cxs-p*>M6$tOUj{rwTeaI5_;p_!$3CX4JprU+lqu zxR;mv|M*vvc}xf?Gq0KuKnBmeffG`7!oG-wQ{n#|bC=UWhh6L3D>NiZ7;8r#{!g8Xw?L6Ya_4DAs;AMRRa{gOi^E^Tho^YO0T?_M^)LwS~v z3$H^~#$D+CFlPd}1J=j4^7VfHgxD10op86IcF`)wt>~GAZ6L6 z*$!Ye8&Gph)oDh2g&bU6Z@cJlR*y4{4LJ1-UNC}-vM7tPC@(LcAj3z+>7yuI)9zGo z!1lb&c8d9TidbPpWkxCDe55SrngbYi*>n$nNc*AmTUmmWZ z;rr|+NtSsT`CSCrwgL8{jrn_^{nh~d*wdHo{7%sTw_9igy_N};5X{nWBc1qU*`w1R z$8wV`%N)xC84XezJ8tl)d0>A$8RRB;hEt4XA-!$-BDZQ#FOSPGn1XLj;C zmY+lAWJ@W!oW;%{gP!=x(VFWEeZ=FqW#oFlm>(oD!qV_!JK-a7W(>rtBZWh-5gS-K`$ z=;*Z8NOiOPn_cPWz>m8^4E)&9gU6h0j$>jz-rS@0&Si0Zd|94rZ3?yp+`D(#W=|iA zCxc!25v1Rz?}H@-gUG?5lh<-bkS{Ig*+A}MAO9G`n{Q5mUs>+%+$Hu3=4r5h?t3mJ zU@qJ9Uz6H9TYcYli0Xl$3uh6c`ukVQD72G@mzkQO zszHCZXxhea(|n_FS0MYkz|OZT0PvkW0sZg410eO=ByVnz864#>>R(?(kDup_0n5IpLoz=*A{PZ}r7sp~7o#v+ywa`)qk=vHD ze56rm5-;D^kUf1N?*Y_F4(>bxBjh`;U``#D3GhgkVP9K%yp8}+0bQdkr*`9;DXkk( zmV16U`4`i+$|DqyjI8eT3?Ns5rRDj}v(c*TX8t}K z9fA@J=FCj z*!}5|)qBT62S?(kI1hFPVFvKWoZ_y(FE}pXI;3Cvkz>zg8g7a|yGr8TXfJ;ZU;wYa zMfAnaRWXh(i;W>k&J)be9sif_k>$Y990%~+KO=pql|PklKOpn_oFWcVFgt|ollwMB z?i)<>>)(1u;lnpKN{6nk{8?_(RQfG{JH~Ej`U>wAUPM3tg&vfc18FU6AMM8NAXZxw%Se6r#a;K=eA8l25I(%@jajIBgU-r5gu# zUR%OBf$ig%Yz+1YI-P5vRqiG1iM~qEfO}~42{cd3O90r&b|C+Hh5cnFHIhDIS14>R0QaQ$q?VzYk+c}#xcFZi`iw*H#9xsbu2ZoLC z3{#``fK}`~BY6#= z@5OJBdI4a`;a=GLp3N|>02toF`WuvY$X?rI8^BxmUf-ne|Bv^5wiV=H?d~CNK?yX+7Up>hz zvwxh|SB;bVUz|jS z>At8U`zBsstx7~DyQb|Aq?Un!ge#H^LZm(ws{yH$QVtg*hZ*VErTx{^^*jZdhdqPY zQ`7}4V=R3ITa;bf^$bHZfPi#|Al=;{f*>Iv-5o=>G)PK!hZ2HxH$(T(-QC^&@!aos z9M@0S*WTxfb1i6}ocBIei$>5Y&esn+ehr8pEEo^D<$jD76mD;zeue!)J0PZgcX&ry6o7Cg;}*dkg|Nlv z2b7_IR|Y(juuxums~Z(mV&RYkPF=XqZ`3A2xnjQs9%Bqu-u7&}*Lk=gj4M2TzLnu1%M&SVNI$7hA8Stwg!J zx!^ip9|lj90am;SQn#wMTNbi$&riN!{<{IuAt$)iAA-KLBm@iI$-FXElE~FpLcWjW zXLn81aP9K^{hKL%LJ0<^W<6vTBF2?q8LD{}*O%(i4^)D$NrAfznt`k!&5(3C7O0&VA;QV@&*)Zw4K2=a- zA%&m(I1(qrMhdzC^$$xW*hL}r@9X(#&F4XTkaW-&0X8;`;kPc|(Fpf<34WT`UBm9P zbdU%j`~GOgQ!prtEy=IhQ4|L#HrozDw3{%@aZ)gvxC{6@d$-=+DFXy`&nOJ`JU2Xf zf3d!vV=W}MI?I^&P41^zQ4?I01^D}gox%s0$ zurg2Ac+EqI7?444@SNp0A`ENX%^Hivk4LWdtSdu{0`|nyB80sql(sS}rn;dP9$S(*} zbr{-u&W=GvV6EPp_%x!O4bmB=pa~L!ICYI)5Vd(y_-8;Qi@7N>Wtf8+qJDJ#z7$7H zC0`E@>*vaAZ_3tB9|xO^zR)lXTs&12DfC>#ovEA&SZpK`M&gfjFqG~EqIa1PtHWKY%we7Lz0wTHk;~-2cmyq-7hgy1R$O~(k{1e>sUKf+ zD@9b53mWiX!|7Gg%vov-kzZ68jV)b*b1ATtl5P z78!q4Q@}@d1=@qvU(&2ZA(~OQNYMVnwBbAm2=xeaWo|p9OAzr#a^KPO;kvUzM79gg zZZX6|6Q9YkGgZ$XFARSHuMcr5SAKXdV1zTqTWVoc5KVf5h@=lSN{^t%hS>4!GCmjA za51az;+x5_1^MA8;bd|Gt~}xEi2a8lBS4*}$9)RRy@b$pGWesM;2oiWRB6j)_wd~~ zdt-{9v;jz+CH8I}XjxZvx9(kq4~5HWmv~rirx}=R6>h1qjw$uT?CG`B;pWYbHC(%O zIXV^gssT9%+EJ z*|FO%3w`^Wu9lSPnIYI`HY4>EDQ$|}1IdC#u2cx>U0q`|wric#?*ALRUT%^U33ge_ zHR{pA>Pw{LZleA5_n*~o9vUH&-J7%A;QEusujDLw@F8iTJOeB3nEQLeFa?0-fh0%9 zGPMWW#QAr6S4l)4MayH?T-7(IFNMYtDouQ!da5#h)hU7FqDJ7O9LJ#p z7e6TOy50b)Cl>jtfC$#CvLN zS}6S6Fl?~C7UL=rI$G&>$%Bhau?Nc?_ZhtJa}<>_cC9@AYKF}&nE9~LIgoazSZ#WW z$mtW~L+yIUrWTTc{z+zoV9bVIs(ZpNBh^pW)$56e;>f#VxjUt(YhBCuLDCgw8Ebw;n`t> zL?R-4kPq4Nqi65n*q64=xmK(0d2zVRMMSHXkR#_dBQ3|qY2~D9*iUN9mb!+i*aMxp zSWr1NAF<58>3E_e{e)|i>>uyLOr6DKNE+w-hBlseXR?Gql9A6gS#&N_z&*K3-Zh4{ zpc3CT5{W3Xc+;6D(z8AMvUmfYXX$9b8p|fB-iO6U?zmHOzs5ryT#$2H+bc^EFU)_O zIJPy+rTq0u1jO&~>mugsFdWC3F|nA)%-R6DL&F00>hp|Em{Y8vTj7N*|IYeO#u!`t zZJq(J-YK<1w`<666;{)(TxnCv>6Nb)8}D~5cKv@(s4wxaTL(3k$iJ5-{Ij`gs@BHp zU+lIgS3gQL<>0yDRvzvr&kGNt+s2pz8tQ>kkm#0k8;<4M{u4|fwT}eJmS))%E3x-+ z(d|!`G1ChC*)o{{LcubtGfo2&O2!0o1nq>y6NH9lh2 znbhYYy-(6PxW@!rq}|WMbTd(bsu4A4D4UH(1lZ*mi?MGLPdn?{XXH$zzk6+e*yN{d8r_^TG8gdL$LO{$ zFzW~b2P$72)F)BL8-W5;32x3J?OR`o-|%6Tms!-2c`oGjXVUOAw-@nF=tEJhbR`c2 zZO;hqWTSRz6~EjDf(namUHM@(Q>HU^%l@oA=0$#l%S&c(=IxbyL@M+IOyzBpIS__5 zqYIq3F47Pp>orO445fcM^(u9b;mch$33yL_jqbH+@brF{sLdfz9`1Bwd$s;*#>T^w zeD#%NX4D1G&5_=nYS~tMz(sc8iCa@loz1w}JxGV4;dmpmZc<@l3l~>qO*IFUCf>kQA@j*c-4@txphhKJOWuAR1A5))3Pe7NW{mYC4-Y=dY14Hcn$Xxn>b)a#c&K+jW zeb&_{l1H+|P-aHb$edHzd@37mL>vi3h@^5vjcP1z!Eu0)pg97e@KeO?>M$Gr2AJ(G zu29+H~Z!7BBs|mZb14*&KPrYI#Ee= z{-g?#IG&G4U4c`s9KM*HDEaopCH9uV{9tCLN&1ngU(50{wWpJ0Xf_G6wJYlVnfd5Q z;i?8ciTfF_jdGsa^Or#yH?f=PlEop}D&rVt{qNTqe{Z5aZuIJ9=bZ zreBEWo?hN2KY#nms7yi~^I=Q0qXqeh0x85yP)vc`E!vS-TxP&E@MIuen-^RdY&`h7 zqrd`ME$Q#r!xA7i5(z0a`jO5US6cdoRHf9qZoq}*IImhYv@zc8>r{un%1dUOV<$^X z!2hU?Z9DCIjVjjLI46{R8K{EGyN=1l6sGO9V|4jClt5S<_O++0QwgV94F#fxY1-`og<63L?RqwUTZg@_Jz?mrRDQ@gCT za=COOL}%vwP(8(&8Vz~q8$>HucAu0UVs`oRH zWy-m#MYRoI()y}dmuDgwk^c3b{$pWPFT(cAH-T7t{_~qYsDyZXvS=x}H88!dd{KXD ziisFCg;(a`z=LFtbajYj1QFS3)SW+ zm4p?QqEh`8I4DVh$)+?CNrCKk9ztqQh`L*<&eHDc@pVwaFS||3 zwSAJ~`X{JkDKVKL8zxKm(sv5K_4G6A^F#zh3Pz$5L|66eO5bvWmCx+-ZiMk2h&ixZ(=NTSp*GKa9E>f||2bkbRDRW%@h(69; zqq;#e7_)Y}#a}9mU1r!5(@lfEuE$Y(UuaIVqHnX9*-;FjE)qsmi zlhrEKZ{&*2s3{i@uI5qo<>@P_3&<;tD&_t9Epi7FQcNHf`*yj2`Jb=1l&a-8Ria{& zjwiExd(5d$OC^6gBB3XlCI*5MRhXD5sUR`cyk@5B%m9LmONvp8HTr<}!_1<*1rcv# zCzsDx4wGGsHQt*$bW+(CTp8&-k+GVuV=aeQ$!EvjT98&9lF}x6&(B>p!98JARn6czc?9XUxxeSa zo>j-V5=DCX5o@kdDO&|^_lYDyAgi2oEPzP{gje_l4ejBue-^##9Gi ze_ka>$O3e{-U`I;N6Zh;}#{&ZGJ`o=AD) zhH*@wcp)XsKI;d9++}HPod09@46GCw z_Fv&YaDgb+t!T)&(k&0d1Cz1n8~R~ z#+Aoxe9WSbFy8y!r?HUvQZf4qb4N={er0CPz_mYPE9n<2-)%!IhCMaqR1#a#zza*} zT;{=<)$PhNxQQQ2g6VPR~+&0I2+xIC7pp8@$8kn(W5$*-HQW@v}u>#dG8V2kp^a|w!czHM1Iw# z+mDfOrj4Uvi?Ih@xnlwsgTlGcBR}_npo!?O5b1T7*jNl49iz^H$N`UWeQHCeB1V zA*ttW^N=QH&IzlLvB)o7Z$P7KODtyqelPw}$lR*+j+G48r_hfRGec%#x_?Lu?&SqJF>1;J%PC1ufAsU z*V|^<9?U2A>-4y9o14txU=3%PvGy2@n2Nq*0_ua}ClCV7gJ~2hPL!kfs5C?r&U?kP z_E$~f*t)+Q-E({Qnrv(9w_ISItqBwWC3_G0{SS(^s!0@lPy z-X3Fb{UoO7-(0;E&rqbds^l8t6;;p+-XT+MI%9eq@Q(4vBma=Mte3qRX?Ek3k4X{K z%%+6R6}k`j&|MxM@yyJ?svDo-XA@fiR)c+9`=oOR!yZ@p{mi#LLuJ)J;Bb`$Z6{Yf z9Q2lXWJ@@kHZ=UiHkplWkiB*#fA?9()b^W=Yj85*=k93ul`5qs@&+?QDQC{*AxW%P4)C2J@XT^vPVZ z03n1$+WtNIHrbW|%ydMAxK%IOx$izgiSdw=m_<*_=XtuL^6Q3c z1>ksuK0M^*g{I`Y|9l>N{K50$p1Ao2U8a*#Y&G(_FwO=@-X?!VDg(`oB(8ew^1S&Y zZX_y6LLpG8h&X#(OC?OwhwK$X=hu1SH@!>Jp}#`&DEb_|walEgxLkkg7_;-mcI|kO z4P~c=^AUAQ;tQ^5L1tPD1V3}a>PXb=Ww7!Wt_BLqW}9Dz2j2AEe^{aFV>@IUC*O^6 zKaFF|s+4p!O~LjpXqDki5O>QB|7i8}6*zyw&qgj87o8Pr0+?$ObA@koBfU74eDcAK zSdFLqlswLNjKApkH(6bey0Ye{C6|}t7&HS3;}@R`aS9Yzr~CQPhajFGo;t3;B!CIe z;wSpz%efuKrBO#)&MPJlEfy(kBNy(C0c`bEDRItwiuf`ywVP4FjP+>XoziojV92W^ z_GuuiZE24f_DHL7w=LA(?E_L7HZF9lfFHU9Ot(0yl@%9tD0`dDBP$?!?nD)ZG1!>S z&`5C0D>gh_GL6}#)A8dUZ41^osR5Q zJ%-yK!Jig!GFV_W2?=Yq@{9xh)fvmlfu92bU@ugDLF9^-&Ocl4DNq?J^xtX($6`FZ z4L;eye{>I5FT~_|qkA*W>rF7>^yk3NS)XEhUL$CA7~N zdso{XpAy6g5bA}tcO@l=+OaN1V}G38<1>{Cj!}F?;v@Ic2Ln9+xt9Z1_;@J#roF~) zx5>q^SNN82$@kFGGR1Z7WBeB-7gjBXhwzUlwV6Pfv6!9X6As6qS~C<`nH>m&&%_VVNQa92K7AZmtC;oQla;de_qoE15QF zy;Lq{bUdTTomm~V2|sam%1wZ8y3d39qD0JBT|}y?UB3?ra&}Ep zg#%5MJMAS)V2+{=4R(&$r>S-Y_e2|)JJ{-DTw$*%V)xm4MqTz%#6;d|^CV_WFb^wl zcRmKpf1!Ai5PF|6-?g2h0t0q9ZlKMSY3yH%@%s}bWE^+RKXHGc_eA_7<+Ae|I8^*| z_Q|HWL?3rFta@v~5hTbz2ez<2YW`t_6T{af(3ZhQpTAtBtKZne)o?`vxFLklk2Dq- zJsV6V=?m$7v$I?kz#B_pGD2i?V#>KXXQDBf;SqT2nHvSArW@ zN+Hid;avw%8uJg@mBZv?RnhJ5r0hfde;`GKB4PRM5%nTU{rdm&a!j=?3!+UU-@KR1R0aqCDXG5yxVpJlD8Ke0H92s~F_3E)LSm*VR%M zZS~EBft1SH9;dwVNb2Lo*OGnGX{vqep$`rav74RMIi!^C{g=QM=JT~P(t8_K_*($H zN3$LNs3RE;AG3CM78Lwcx7$KI?dI-zku;rbV>iwD^Xtueim055bPU2{{r{w@cCT%m zt((kjHt)sM?XBxQS5u$;X}3pM4ZiQL0(B2DQJ~wK#Q$BU6a3^pvv%7tp(|jsXmT9-TCHIv72VqQ({#XjasiM(Fh>#;+jpii3& zVq+I6S`*>@$g8`4;}a^m%0R{!4d$Sff6rro#`1$FNraS;^w#c~K-NO%1gyQt6K#c@ z>mjM4MEBiA8+XiWE(#s@=gt7NSa}fDpdU}ia4HG7iq1aHrH5dX53MJ~)PTv@!#(2G zdG#k=H)S)^w`k7Eei^3qcja%z4hZkFW~k?79qYz^kdEseggSkbt|A#8*7s~=49<*7 zkK$O@fM#Eg0OfoDy>|6yDXZ55&XmuN3Tw4P_qR28DC}om+y@K;W_TzBSF>uE+rMx> zj_?Ydhw{C0ggK?(*WX&)a(J&(0S><_NBca=^Y8suaY0za-V}W?%%Zz{lduTjFYI@< z$OwL5R-L6^(R$yK#<{Kfq}SQTw$`tUQGg`y5zW#IE?5;6=b_BbOm=)TLoQ_FAA3PM^~0 z>7y=ZKTA44y^l;bi}x^h`!L57-qoTbBz>?O@Z$X{7c&r1t;7F#17eb40V95dcKi*zi6VG=T6or^s)Z@iLFfLnME@P!@yqt zPl*|%CVf#`?E3_(d#LbEh(6(Ydx*au4_qD%KHbR0#1Vph?(I!j zTW<EtQD;$7xn+^wyGJFmxira_YG zyNRr>ILtgiy;Q=yqZy_azyAZ2xn6J~(OyEa@~9Nmx-(JQ)g1CzJ3(XVytbMjrqG7k zUbnL2JVNOGc`5fr28Cbw)`fP7TRZm=Z*DA}_ln#rnpo%Y20%k@6+e6b0ep@SmV`%c zJ+$&_jmB<`zM6fY8DM{zyG-tCst7^c)jhti1Uv2O5k&x;IArXN+sH|BBl$A2_vngd z-t8n{J9h6ce+_q~aAMfD7lIK3M_y~Y*cz{_A)5sPy#6Tt)*P;?=Cs97yMX2HBaYt> zK0Cm6kJ1ETXObP1x8g0nqknRud`i+*4f_t{ zo=c2=8|M#h`}IJ7d{45Mr|WrbQ$#gwPJ0^RonHU4bLu~S+e8nC7fct zCg0Ehe2lA87nKa78yL02<(72 zMkS2=#g0bf^FwlT%Q#E^+r(@@>9f62KqN{zGGPb4Q51!rBl=CL({p1@W(eteA{H4Sd03W!ZT_s%icth6yRofpYe zEww4*J5#JZzm0hmdsp@Fyt<2z3)7IU+6)No?$1fsy_1dysw&2fqDktThsfl!5^}0p zXRmw3*9l7=oavlkPHGiQI}E*x_m6O|XnN{D2$3Z=E4z@OswMB6kWKX}hv&uTtc5ngR7ZQUxz&NZjGF|0-~2cYszFu2CW?0n@^EVQC=nFqNMy=6 z2vGf>7~sXKtUP0!b?FR1WpQk%GNqB_xTa-l;Q5#{#be!u3T2m&U_WBs+W5JOXYOJy zL3_xzQI>DnSrHMj16A98`vQD;qQ1G;3q&}F*4;OLNCsCL*egth~Hs*x)`lzG*rZ)A>E$}$1K5;HuT!QiT5#q6T68UX( zW9F5z_Z$hSeF~B+vl%Af6b7^8#4jH#*E>6}hTlyY*xvS08md8W8X;UhE1HJ8@3N2K z8ZY5U8p)&v^I5QraSaGLmxS1G+>0i^Jw0S^tWuB}E|=A?6AQT#+Ajzn#I7Gv7-JJR zu-RvB7<>w7&tGk-3tqK0@(xP)yldqlii!G6mUInmDWs#hv4~e*%B|flyfB!bduPB3 zc-QhU^Id?9^HF-^V>3&+&a%qw=`IGvY9BjGi;u1sPNtCS!(P=gWdVO3oj`yY4)j#< zg~GQR45(jqv;um29^!Gj!xS1&pBilGLpWr(l5Knr1Xb#mcP_1bD#1W`sBb+JkwM@Ln&PO>&bO)LT0L`=+IEz9Pu0V9GaL@|yhI@$=~IXqJ&3XCcG z!5D$!7-z$l6e+2Mm3Bx384j`-poDskxE$XSuA}22$_*M~FriaHo_1!|%I+ZMx9k$Q zegfZI)RGO2=f86bKK24FtP}Q0;H9m?t~v!#?=6ZAMoB zIsFVy$iV$xWZ=ikb}tN`Sd&w#qFP2VC}w2q-4$GGc~&vzuZpkFhhsB9#9LvG(yr^&5J1cN0N3VwP+eMN@4npIiZKbJJf`OOZ%p7NRHKLM)y z?KLf%m^V(d<5n!{9%`6a`pq$oEq~$U=&)HDOlr{avK(;bX)P+PunN30rnsZSlxewO zz1Uva9a{st9fxEN{dPNVyx4Gg1YagMTa+Mb?7oAL6MZ<|?#SXNKQp$ST(I=De858(_FT7IsCHPO;18cB?;Q{yGF#UU$^TmNOUyP5@#89IjJ8P z2EQnqE*^*wKc?{03Hl0aYuYspH{pLbyZK_C(@s|-Y+(Lwc)Nr`;LQw<&Gu2pK=@sU zT6fNw*yMQ)jB5uJzE7};$74{B<@%n`Ix&E7>)#m4O&^aB&<-<|mxt%@AyvY>YXm81 zc!^!+vwrj#f~K;B)iJRvy#EzM1NFqUW6O{v4T>6laTmX5Z%;>SLhlY4#}EHo7}X;d zO#2OsjqOdStlI48FM~5VCe2Q}Udvdn&C?%$Qov*u46HDJ;s6k(%g9vtlV@b5=wByy z7xjI!;QIrsY}Vaf7;<%UhZY@P*j}S)<2Zry|r&zb>NIPVp!+2+q7+5{FNh z1E-!+x<&69X12%JR83GA@>h)h4z}yan!l^#cg?AYjnDXdPOALuo$jS?G&4||q5pNh z%IWL>Hy4cz(S=^}X0FWFRY8>Mi#9CH3tKN8|5o`6eU)f!i5z?(OTAS}_kFHyFpDt=kkLKppmt=>5m(x{+q@MQJ3K|H4s&(OqP=dlXkY9Ov0eDpWqNyS1Hc1Cmp$}^u<__ zyJ0{7=o!OiXC$&49F}sgLWKz)De?zKvLWw+<0KYvx0vAJimGJ@aP0leVgjvFJg|q; zs=&yknTBffzvt#1EZ4a941aZ$ihO z>5-sW!Pr1+<=)Rqqxs~JD4D5`!mg`;Y9C#qe;-J36%-_@E>FU3hbDQ(UCL#4k!6Jl zG8t~*MLri6h?0dOZ?cwti8cm0>OiRlj5O8BjQcQeqA2Asr`FdgZ)^8@7KmD#es4|7 zI}i5~^XRovM3e0rrp!Fh1nPH5SD?K2cGp}IUBWFd->w(z<=WES9Z?($n1`J%lkJ~I z=*(oJtQkwktSrc*psgA*&8BZtI2zclA{n$x&ZzBY(eY1D=JzU;x}=@>^x&5jV6X!@c!vgNI0 z3y&PyfA4Q2!PuGqCKCOf<&B4p=;fG;B4{X^hO5|`v)r8Pxn~@myv0!x@~{ZW4JgPXdKY&#GV&3h5&M!58VK?DG3S?sZrBq=AE~Lk5ny;IP ztqGB8ZC0lv}!d$~rW_Kg3sEWS*YYIeegnK`3a{n0oZB~R7yhs06%xqK>^#qW1 z5(%b*;?S{+j+pNC|GWC3E#PrSa9OpU+t7qD)0$lvvbi>v;pBM_sK+PYYg=~_agdeTt@--0*Jg-aM!>tN=!d1h2?b5;yg}%5xEwEMvERqje46+)4fQDzaN*}|=dU+R~3J(3bUO{CT;}C2su)+v?8mdO< zRLt+Ay1n{#_WtA4(v?KSV|H7zOuDSg*6aSR?+yrWXSPh3jtXVD6e?3TpC3QnzO-7B z4!eq~k2&|o__4J62g753YpG$p84DMBbB7;Rw2~wJSYhSxD#^hT5TH8v^UZsV&Kron z*!VH}ysu9FTL+wDD8B21ec#t!S__a^!2M=$ZC_hM%v6JeX|rflhbt;{H}IG^6603xix>go!#3dXN$MbC?%#Y*WWhA^@cuN|Iy0mF?ozxWBG#N<8Q>Z7N@2eS<%e zGMFeTj}SUyJ~p@+8h}vEsil^>TfbCT0;oK|IF^IiAPUXYF_zar%*`)Rc~hZQExV3J zWMiKE&qj=o=>d8o*-SZyf#0tMn??qh_|TBqF=uzy#{7hs6;RBi0y(oK6}dU*|SZo*k_18&H@Rju8H*>p9or?H&js?MrA;{gYxq$}Y2k+6C$sxO z1c&x&#?JU*TNaN1kYrzode3*rCb3W=!r;-%p7Gum$Tp1?+6$n z(52&u+QGcgDCdae%V+U~9z|5a?E*JnAnTedJI|o+mq}!-aZKIdvg1+_o^{HlkdqTk zXHSS$@!|E*S=4|Aor+H*I`qF_N801z@Q*Ay$-A({8vqwFDcxS>c zvydF9cpID(P*b*=mwKp8+G1;NIMY-=siT2Sa#kf)re%!Z>k5d9^k>e|t9{Des$T5u zZYpmk-b0ATl;aD#gC8$pt;U5UP6Wv6UrDcMlFy<H^IsiMi)@#RsdMKi>pX9L^5|nTX=zYM>NiL9_bXEez z3tz^&Ik6i6#_wUIKTHZ^z;Bkth#BN5=;##@nbA>TH%&(~dq1~?59{4h=Ur|gZkh#^ zeb`)>g@u(@*BXch2;r;)Dap zuWpAUP4#blkBfEMeKg(aO!)cUc1uZ~3Bx+@Wm*W9)d%>MP0I{09=j)*knBK!Dx|Yg z;tceke3)$PXbo?Qr=QG4+L4=KuekUNf!qX*TX0eIuRZio(NWG_B_;8qTOp4sLYdJ}P(o+0TVX(EMGZ*K1z zZ}S#(X$38x^X1OccUrv2QY4Wh39(_U&y5NErKw3{n-WV$FZ`CU+Nc01?UtKuLXD}B*(Q*LZ3JN zP5($^a51Y`kNhXMhVUej%XYbLDpw_az1D)rz- z0sE-!NE_>k_s7fT@dOkTF}^P^+I|AN@Ky32_3lmh>u?IQdV57y|E=@=x;Sn2f`mj{ z6>X%3WmF~Jmi=?}(UA>2v~tBfX?0p(d7ABb@b-v5`fzo^PXa$mUU~fPSeb3=J z>(~`@3pFKZ^f&uGa^w)Q@_CNb%sfMByf+`>OE}WT{iYXrmBI2{W7jQZj1(67m{QgI zVM_fJzH{6(pBMCHUd4x4qy?AZYy|{Qv(E+!El~U?MiW-U4n{~=M2`765;@72t?2TQE`GgcAnOCP~)`2wS;2oL6|h;yH) zjIy4Z?uwC?L9WKoq2H$Dxaqg!Hs|a?9y&+mj(`r0G$kH&AQ(x zmtf&dR&h8DR0xU!VqCRA6S0PPwD#Z5@D3~Oi3%e+!A;os_8sv5@gn*mZ@kEFDj}It zbe6|z)9w0J6znq`pOEAqnhjp&0A9YKJI|7Oo0QkQ<+8S1 zSrXAkYrFo}hprt5wK7dDZKSTl{r-@I?Rnwgm~uqOD12j*WUIYXZ)C17pDX%0N`e6F zc!-H&Qc-~LBU*2H4+W+Td*9RMQyV0dJUF$lilokbl65q6__7A01|JdNo(psSC_Ij* z88J_AAEry{TV852!XO(yA zrl%U!S$>S)+=Bn^+%n&CI$_*^0oH|)h0RF};m$)0CED9h#gC~F&zW<^cDg=`qj>2m z9p2>izeGj_@NFu9j~2FBhH`;7VK2K`c*ElU$cU-E>*UNmX-)`E{rH%fSj?Z5>Ca5k zxL=vLDU$Mge?fIoW%)jV-w_iCBpM1U^%6<^9!yyW;bbB*0SoamGB5?$x8K{ODQY3Q zTdVA(-MRDT$vBk>lMT$UI3JaQiZ>jzgEy#6O8toq)kthAljwO&-Yxc2vq z7k76ucrHRt-I^^Fhrs0d^{3fh$=&i-8@_;mT=3n)J4vwG+DjbW+^<<*8CTQ$Mj^o2 zCZ8|FdWzL%U*WPMe%@v|V~St#eHf35@NBBX#j&Giki+60c(;Pamq z)LV08^(`cdmBG)%dn#Tl?>q|sS0W&$#=uiW2nc+1a|w}VB85NU#zB0!S5^D${Axi(K08WtELQL zwhDq_ZPA!T-^iR~2RtKEyTi3KVz@dKNneCk2LM#E)VCtfa>3E|OFYn(eR_1)fq9Tf zgn+AGU?s1ET>Oz={we3vp}#}Ccm6X@2UYZdb%x_ z`6%#wQ^5F|XdaW|b%nphknnD~r^QYWblF!9Ab@g+viVGUI+2JMn>H7$Da?yxHLBd3 z6a!BRFPE}W8kbGF!wy!Uu0VwUaKx(QJ$#kj@}JV;Bl(nk32b^vrUr+s=`KgimIPf{ zyH#W`;5a^l;?Ys#{{bFB;l78?2FceikbOk#>~Yzyx1Ngh>Qlk&0kaTmMVvo9$hYsV zf$4(;+$9&nj6=1E5meYVS}_KRk3F=&MPS+P5?DRw`3wF7ppm_OyDD+Z4hrvHZa~@l zmlWq?C6Qa{~?#@8oieb5W;?rC#6bV&4JaW$5D7u!SNI3Vh^1zy>d>-zBds1 zC}-X67(lZnWY=2~Kgai{+4zuF=%N6nIXDun&XKEMt_FwV_{pmMt9QvBeXwq#Loys6 zP)8T?hw&KS;Tr(I0Duo)#W#A9j~?nA`E-Y?@DeRSo%{Ah5`Uc>YuSibLHCdI+v z-=ue(6wKja?t%fl39!idfp1}3P8Ij~TO@AWBFpgs_wU_xaRHlwq03TEvwE?!Gwnxg z;ae`hx$SBH%*nu$>B&6z$IWMZz^X$?`QB0y5Sr`KX~vL6S(HUtlz(^SV^=!Wa0Xnt zl@asHvQzAnLEbjiV{P&LJ4Kk6s_e@)e=nYZO+c@|w?_64DbLjLh<1tNgfSr00DuOK zg&aS%SJyA7?+?gaPe@)|A@$KmXqeYgqmD?NLsR#ZWBe`WSw4cWh;OExY%4mWvC^kP za${&zmiOu?{_D zS^{{`C*2vl^s4{^R&c7YmXf0V@HPJ*HUk*fz+M9Yy7?7gRo%wYe2_v-mZ5?S%imRI zvEFPI+u1_yEmjtD_CJwho5yIZd)Bu31>1?lDvFEA!^;S&%C0qFW@(naJnMI5T~S5I zWanwFI`0g)0!S9D)0EfHw%H_+%;hx?| z_T&$T+Ad$dtcSyg@{66Trt?LTS07TwHhzz;EXz^rx})81QSJfEc7V6o3FP;|_I(ws zA+v)0yLMy$mc0oOh;=Nh4&SJNz_OITOZ8&%z0v$m7uR>&mA3}dd8VTp*elj8I>j6* zp}tHM&czE!cu^K*QU2YOPXPWVQ+c~ssj?=$ZT=YG zjoIk~75Af<_;5CV z!y&D7JBl+K48dNolSTyarvUy&{aE-xBsoZKCnVL6W7lR|&Z+>m#An8Zjp#W!FXk&D zRM_&>h*E3;ET^*Q_!P8@3`1~#Apj!OFw;W&+Ok%I0ir0eGW4wNae!bcMCajW{eRHB z$_b0*_lQy&8<~^?EdDo!Dh^jJZ9REZaUnABTlGN4dEM#|2yo55#CM)%w>j zh^SA7O*?=>#kJ#IlN>%3?BVeu$U{C|eU<2qKG`P+0ES@szi^T40|2@=QE7x=1pBXF z7f;y)ABXQ+RU@#tW2#w25_9X}W&|qDB zt{C{W)=8}Z_;GIE6AmW<_JL*Jr%^8bvdtgszds+49-~nJ{(}gIh6>|;IyRi%Eebh5 z7YD<|Hb8eB53~(8mfTtzYg=CK?9q~F$`xRo=8OxzYt^;&I7Xls1d#7aRaDB{e$ark zF&tOJh}0UxDH%!(XDb62amI96&iY*oc1#U6jA*7cY63s1fK6LiE#-e}yd%vfeXHQ9 z#sb@z(X@A(DV5{eigr80byk6KgB=xfmR0X>Is2veM$p9D*e#qKL(J@_d87Gf>d~ji zJ^7!ep?U=`Y>PPPvk+0HnP4-KQan$EffRV9Obdw6S73`l;zcR+*)-O^CLJh`9ft@n z8+n;vu23uX{AH(%(Fsr@Ixq=)4h6o)d?Lr5P<6S)MgyJS1_%V`$GO?CA~w$nWp@R@=n`v;crWrH(Z}9ktM#VNYas zq$2wC^2pb-VBW)GeVe60#nvRFUf7Z1hije8 z<4vMJJ%8PBbbyb32v+eCnE{To0pNERtKEK&^zFCEaOaYY?Ck8gAoikaw?{PhU2(Bc?G!YY@1mF^3zu;L7iCcv zWl{cJluyX~*^->8NpU=Oy<*Cfse|^jV!JS|-paRU@W=7~-YZhPvFytsU#`AQ9!tJ- zo#>ATWH;`Se2C>&=^@2So(M08svm8bT{IFag=B;VQF5#?ZdeQ?-(OiGbqKkbS--WY zBiX~39X?*P^6Un^4jRW7W7nXK0QdqKx<-)gsZ^LK4w(U=m<2oqAxx!U62ngF;=6@D zxqPij#vwW=&>56C-{VwXP<{#OUls+H+8G*j&lg?xo7CF2ITRp-;?9HDa^}y9IZQzV zP)r_;_{C4H3hJuZ&Ao`~3OlF7~cC5;*vHwdI;8j_$S}R!Du6mNqurIY^2~jY2 zc1mlO3|)gSX&!&(7R@aa8Cg$I?M)uFOv5o=6WudPi z5@|6lW#)mnOCbG1rA;Cw4c?ttNW3LRrU9hf5wnKqTX^U`p#QIKqah4?tq};*b>@_lH22Y1Eq~IQ2 zOuTx|dzdWerDkto-yF-q$m$57Q!GXHIF1YlNngV^)WJax^r_34-Q~^{djP9E{b3jX zhS|Vg1MB`F&bj3CTpnGp%m#k`O72P39RSE;fA1fV;M9-oAkqRG_z_sZ2rM9h1^m))lYD|>e04Nh4JQNNy2X3GWvZUQ3XLqmweCi2F^j@f(cxdMOl>pW6LM%YgWD0%F1nf+O)bJGJd<> zb2GKuds~kAqY3b%Jhv~)zAWQB`Y$GxwPbH!w)^9fuKRMmFc9Z3FWh)^+vKthJi;ml za!@c}d%pp(UD8@a4*VJHgN@c%Zn{WgrN*&Kf5o!zF(fdT<%2jLAlg?3fgFiL0|2tI zyv~j66G*?y-M~-dlrt@ekd36`1kG;9IS~Tw{D(~ojXP+Qtd$8rRLXYHmUeJABB;m_ zz~4Wtu~<}TJwzQq7uru1OEs3&w|DdHRBkF~PJvqu&rK!LGXBb0(ync=i}OHTk0 z3<$JyL86^3lT-(%HGtS?_S`u%0x~y&X!RN=^BRD&3jUhcJl$vIVj9;(M00HraV^8B zum8Dc#34=-x~7ON=~O9|Le2#tXc*{anGi99Lm;Ol<9vbSr#|Fi0~!$TTozhwV;dx) za%^(Op;#g>qrPUc5opbUKr$g`2uX1m$iEq6;TDH25|Dtg3@@`i2Kei+z2BHDgFxOU zh=2o=z=0~Z1!PCwB}@;p*a-Fn25Wej1zFG`(LVu^xdiqcVDE6iS9pLAGcx4zX15)y z4me2%wyihtHH^IiV1jYK;0?t7NmpN4;y~;tblNxO=J^%z5gpUHHss(mmxJlxfR5D~ zw%ez*%R+RYtn0K0I zCjSaLgjWFm{sjB^GQi+hACUR(Vcu!va4>@>ffyZJ*SEIZ1sL`S-Y#qbn{^fkapr7A zE@Ma$z}_w2nkBcZ-|03r>;ZS`HquqQz^Md0m47GuT!6ntS(HWjKe&8s*|+poE4|_P z=T_0{tu}<)m2F(G=dG!f;^tx4DAT*GqS1id`wnEBXXhz5=*IlL6Eq;5%dXPTf!{uH zYPY{aqrtK-@8>6bWF9MQGtL{HuG{;NUSvbd;IEB8trlnE0N?}ABi4H{7_fY7!xYE) z`EbMNf%{f(ROl#yn`J|j68G|%LaFnnXV zK~A+WdUEq>qq)d?0yi4qrMk@8S(dN-T^%ONs=%)tMD1wBx{X{N(-mg{E7kWo`EBdx zZ|7V?b#B$Jv34YpV=u>)3emWJTieT74e(j#D4IxbG9`#A-oVYMZlbZDD%dXXF%V2= z)Kh=|G@@VUfY3O1NWap>f5fX0dWxb`D?MPlC@}(fk07r4KE&KmgEda9VmVi(JP3{V zE|X)+-oe^SS@z8cI1+#_%M_=D8yj~!qyoboN^p3>7rfl^*!c2X5U3D=MX&8%>EXIo(OO}iG0RVCCGLVtLCX%BgvB?DI$BvQ~Bw-J8$3p&nqM{PX&j zKllNehnUau&Qr)cNk@t2b1lsS+uFI7Fq0;zX}aH8z-8wyse`=cxOtzaWp-bD zY|&UwGzTyWnuDzC%gaSNkbfuc$**NXpGz|npf}J03&>eM0!3hlrSz3^5E2n|*}!F^ zHXJuF1)nWK2q8TM~Q5I!U7Ukc*d`#v~)TolGj8Ov?RJWaB$)q%-W_v#l z_I#(Z*BH$n=ln(EVFu{A|MnM1^nMNJ<`myuN?TH_M^aoy#k~pU@8NUOEcr?h3*c1j zr_i8W#BgEU6RZi3BC=!EcJdPgz$YWp%v>ZovM$IoafpeCmcl$M!dfR}8QBb&vY;SO z?dE|641g_7>ViOHOX*3piX6h(BefJ^MUMTKr}X7mf0l#&f$HJ>uf)l@QUgdxyt~HD z`9*ix4HGVkW;SoIYPIR2nGh8qQr$_}z?2z8n%qSFcy^`%t7ZJ4`5<$q*xCJ-;Im@! zJ|pu^$jQlx&jR1-$~6Eer~z&306Cu_Cf77sYUiFlO`ph|oi-70F=UD^#9j3}I#NtP zaX6qhULZgc;4lC{>@c)sBF`5syh|aF9H5o*B?vwxbQpvEYX#Iw!8Q*w&4O+)#QSzK z0*2E};dzQv!{OBjV~CA992<~frLU|400fiGAdtrq0H7DAQl+Fggi#C-=n(;=B`!cT zr;B6OZ`=w6gFt2kkBfl|e>OIa#1W0@RJBAf9z)0;d(8Wtf?hvuw`qN;BU;YpmOEcv z(MP9M*v9GD8>fu!!{6lmaH$(Zw8&}2UH%R;t`@E0Y9}V)y#c=`O{Ug$s z_TF?(Aq?=i+mXGW=#a41h8O z`eXJl7^C^+hD<@~qL67AF&M?R?Smb`>;UkMJ$pH3S_Fv=Ku-c7I^{*7+04*DB?)C- zm;6L1AV5tij?;-qfguzc3Y|lH=(zhz%Hm!%aU1<2Y0}bJD2Hkc(_DsO-p*+ruCY@j zSVfI?{@O1&otZ;7S+ly`-!yGpjeVTpz|SB&MOL?7Si? zPidEzK9R}kH|-m(qHfj}Um2xhQ;qGP7`WG{&*(1oa@h4l)uQ6SIgb0u4&aXZk;)V|1Ka zj9n72?26+No(hih9DCLb;x?zdwrqQQa*S`)>uLfs-vtx+(^EFZB~A*0lK@D5ek?y) zUp659=moy%<>8@t^mA>l{K~rgQIGU1hhW-QH>CUi!5BW@+;q6=>cfY$apS7lrA^Tr z1B^W+y>XG`g;xGHfaNoX56Ca1 zZae=`I)k`Qv}o857K(3kPyCkIg;(&REXtz%+m?@2jnr!=D+JB&g>r>hCR68fy8wXv zy9o7CbNF~;jntq1n9Q}WqLCSre2R*>`!C4u{uU{=@woumm)Sp}3yuX~yT3O!$>Xd9 zFLp^}kaf8+IR>-9>8H-2!FdQk#?uy{aJfzDfPvo#vej@bSD~cs>m_RoJ3ucIw9^{z~t4IYb665cz{?qS z0P#FBsjpIz#(2p>;}~g6dPtrIq_l0|M2%@rfUQP{W$b&pCAf0}jTxYB6#6D`#;rb$ ze9{aE*)ouQNlvrG+0G7AYu3Y%2I+}ObZ7%~n54Ae5ks?-lIR1)3O}+(37J(jayGQi zGng}q@R=9s7uhVZcd>1TMV{anV{3XC%cyHPVDu&!xEFo4oIRrMsSu9`#`ITL^)+_v zdN<$40g&frkF1XNMRb^%-fNo@DzbSx=H-wGb}y560zmjGj&%;Z9#h6FAklsLo$J>r zegq)6ir+wR9mv8#FZUwmEFiD^28k=@$#BMy4amX$O%ng)n`Br5zH#f8{l=YKr?`!O zr8CIwInHcUxcF{S5$#~B0Ebn^uV4UcRcYmw*(pNHh}z3TwSdqx4GF&hf{U^!i}H!e zC&=wvrTpHIZp9b#`n8>gDem4LVEc+fFIo>dU^p36Xcs8+|197 zfnPA(v@G>7@Z))Ud3xTL*{cu8?*u~!;IGYD2hiw%9h5CEU!M)tSM+q7=Lwq*LZSuG zOCTkSc3{&HGkOX6E;p*R93xFZ`i1ajQx~!q*9YcL881s>wAjv>1LTS!<81s4TL5xa z1`sHq3z@ZH1gEVvOaM07vF3Cg_)S5an6qr&bh&O5&X?;d(WH4vUGL<8QhIS`Q>pWzJ%9g8Lvj@`!dR`4LOG!7&iRDZW7)4@HMM^dSR3XuD5SYgqy|qrUjb0lvNTL?$n! zJizZuT`7BXsCc{Xukd&KoX+p>Q%iz0r49Nh^W@$MzEj+V8>kDu5M37R0h|Gl^%9cL z$GiL()EJU;cCahH^Yo#3bcH0_`f&j0cLDq^;jZ$)aR9r&L-rCnoE#YXwXb0xuQ`ui z_5F7*yK~syxGn7M+b%nZqwi>H&s+|ZvQu{oDS_V()uSC%R_Ap^ll9}@r9+6VcpjB8 z0?Rjw7G+TuWl=ss`9wjUT?I6!+Hn=u)@-H7BMSU^NC9OCBKmvfMk2^7@r!Uk>%csog?0 zPFImT_PP}^K1G8AmV*O5#{hi^nn@qr9z$=C28c=7ELUbOjAn*FO_DRxsPlqp5(%Dz2OO z_Rg?tc4kL4hN~EgO(!QgA8ggnp`0%!V5@PAinr~|Wr!(=ZS1;!oCZLgcfY6L*B1GU zi?xa{GLrz;XR&_8Z_zlvdBC{-j4fq8y*}>B{~T}6Ie;uq2OVjld=C1YX#hf*buyy@ zjy?$;7TmP-m8H;NIeP@NEg<_VC5Z{f*l;WqI|o21_WU4T z?Y|_>3;xh@P4dN#C%P-dL8H|TFn@1g{2g@mfCK3}J!gjE%8L;x_6)`$T)oJlU|0_3 zX_kOFQ1my***Q;xK=U)*E<3k5GTe55ck7ZBP`r}Ms; z6-}>lTBL$olto#Te~a>oz@O`p&SCwyH~>hqIel+J_T^z41G9fj{%seG;N!!*=Y0dU zG55yz0RSIj{Oo~H(NI<19JmZA&d=$H(LdRfMBnLUO%mlOszE*A{^2Kl)# z^M}^NN=Q*hy7csoh>h9TIm-YhIKxMl2F6Fj7f|91-%sPjotIZIBU~5&o+`==fS&bT zHUu+9JD#w%3n1vR%4J<{4_6eM2r46WF|Q7MD);RSfK>&4<-9s@X#g5gVfE~+%dqvwy$mt1kgS0(BDs5^y~ZzaVQubb2hh(%?JcVzAzkKuf?DbFI1?c z?}N?r+rDUHVFW4Ij}xtinlpNMUZ}wSrI~~T>|1n9vY0~-c`D%OFB}>sW6%h|21ag0 zfc&$04r^uwTdlyoy6hFqSwY+~PIc%7U;qIip&ol+069Cz8qS8twoSsvzFgc)j5&7Z z2;UcnRpRu(etV?C49C(!vh@2RMCS~>GiK)ZL3ch)2X+tN>hT!B>VP|NJ=^t{Md@U6 z^HZym4zawuYIS%ZE_bBN82Igyz4qil#wp1jn7@z4BrhDI6E@zL%m(td93CpZaaCN! zc$Zb-f3-^w?-H>WFfV5S$ezIL1bp{do)P59t9St4Bm4j0)No%TTgD%~`!;(4llZ6K zBz^O49vUV$T*>P=4rE_WPlv5MmoJ04om~1Y-{0vwVjb(plRHF{>FRenv2V50S2mwN zO%GSRVH_ZEGSH$d%AzdFOUfq*^};>U!hyA@VzwJ?<+fWXpDxq9@jR|H+2*hG_I1GU zt$e-r-UgF4B>4iBGlyolig_>g$RwRTVIcjUL#2KYuX8FlyK;>r$NIYhaCh&B^bjoI zMaVtu7|6k8sfz<({9=i%4{ckr=N5tvaHivU0~C=^*gE5>>#{A>yvM$9mz z`E-azGEv1n6HKWtKTm;gph%)xIAmA(*G2300U_%=>) zL%ohKLa9AqL^ZY^IxW6r379>0Q}l8oKTs_&giBHCCB9F}fxN(Fy?#`wCN(}iO8?*J^I2IJ?8kwX7Wt#n95x+85YM0>p_Uj-A$ z`MA$tBDK*%9e|G2`SXyA10g;F3QPd-vZqIqfnPt(-<(%A4%J_c*6a%Egr7Yr9YRP( zha2X3?3}#B`ZW@JL#WM5o_GXJ`10k;&K7WlZ~pFG?BpTQc>qTa2h-^MR&|HQY8 z&w;^V$qMciwBCf}uCRWSPDlO2sFUA|pwNr5D2wt7%E#^@mO)6T9Zs474Q{%U<-^&> zY|YBRrPBuoTV?Qf^9E!X$iKZln27(G?C2cirW>5)BQI;HtY1at{%5G%(TL!vPa%NKp#t0;Sh}sB;V(!%s`OKSIQu;lOB6E4Fwcn)i;GjxumAI_!F0>&1{w0YNfCszYnsQaWn`WkzW4 zBCBni8*C{==R?l$G3SogD)-t$5xFfJO?&^7d)rvw%`2CSI(<-1TblQ z{E54=ozEzW+WJK!)nNLX=QBBWL0Oi^FVo1C*Hoo9N|0MSUa`As7L%FfwsH>(58r%w zvETCk6fd@@9FNYpj+dPe&HG*XU}m0)b?2Y>3H``V%#YdOgKY-@2D8-Ik;|zbax8x%-6nqkUSx36eV$Kodlmgv_ta_T@7;G*9+I*OjIhQ43;G94L!|-o8 zjOay-b-=Q>*szwy*mr(IRnjuCOEH%Rh8AG|r~@W&(9zdcuy1_3qY*lCt0eCCN%wq~ zlfl-d$JXUT z#a0U^kVRRPMfrv06EJ^bLYi=#stPWdFn~LiHt%NX9d~EiGiK&*f1}b)qSw9u7Mbrq z$>;6wllkdqT<4rPgaZe5W_upC@p-&Q#K4b3y}S+w!Q}{uY8)dBX8A2c^YQ{KcX5t z+T3Gp?}FxdrC08LalFZkU9xsU%$n5CZEz>MncFFvy@P_LPA3zVFi1l2#kj4$YNcHl zU;#OAv|!oSnh*q-pD_!_h7DRoQd0yvE;O3BOAjFYM(;HBX(FS7AyQH4+x0DTKb~;dd_{O`3efPbcqk(F-K&lT{#SuRALt3R@jgLKAJD}HJBXtcO z!WG<{4|c76@RaQRV{J4#l<^DPUhL<6^;8ii0o#-`%+@8Tj44J=?);PCM3* zZ2-#w^=aW8QnG-P;LtmjH*K3{i2Sn8M5WSO4-UPMe-~v@7UkvTV^t1iWiqcZt@d6y zZq<902>E5m1744RJI4=~L^I!Z^^oMF9~a)e|5q$~2;j%VhnVqdzRo#FHiTATyz0s4 zV>E|XE{MnLG}&B z7>uCvazvxW_E3Oz%JTvj7}s~sAgN^03Qey7P2iGGVrI4>}>eiG_2-qN^@FKC?EIPeuG31-fZqxfYU9>xQyJsjJl4WSsXZ$ci> zmIh?wfaU=|R?o0`E61K0lc#;Kf6T%K%D2q)oq_=jy#QyLxcGm`IC1lNUJ7Q9a|R1A z>8$=ThFrpT^Kkm_=50$*chg9v)dEgFHX5T*GJZ82gJ(WjwZK zyxcLTS>HMvUVPI6lJ7t*nJYNg;Q(*cD$%u724jbG+_CoH-~gKOiWOsPU;F%q_~3CK z5IzPYcmzQBI_itw2V~w2z*d3@it!L)(2e;fj_b_jfnZz=ssq#^MO@mo(!&)tp4Ei`-NGm4MF3#;}z?rWidyGn7fc=XK8zugmL9%N5 zPI;riXVEJKpWzcTe$LAmED48sF_6}Hnq@PzyavpROXZen$ZLWfN|Z(mXVEBIzp(jZ zhYwQIdo-U3D(>kSGn<&Mx&36HRX`!;@2|=_mh081Gh07HTRjmf0AiKVn|F*RT9s6L z(`*g=RDs`Q>CAbaHKywC3W%0XhOTyRD$eS{PVPaTafGPponlKG@+H?iQ|#>Igxj<) z+sry=5EF{8j6?AjSiE)IyAbn23)as_^hAUV!3u)sCQi&2;2n)TOA#lS;nZ*mhd5zJ zbV+Qd0Q@FGbVl&rG9w5^JIs6zKp9CtdTejn`R z`{De+H}IC+ygf||SK2(gZHhtcH2QL!?ZaAGz7|`>tFle?FfgK6R&*BFMng>4b(;s> zckhs;nVi$buaENvFUq1U$}e&GL=GQi`XML%PU{|5F>TW{1yVUoq~B)q`Bon5k0--F zCHc9cVs*h;AEL_tH2^yF(i2Ya6q2jN&u?syx3q4LQez)JBYUmb)%`J<#~?G>Xe>Cy z%V~&#=MPDr7VDUFjD{?t0J1L!t%#H2y3k;8j6b%dO2K4+K@o{FfhU*a7fly{VCIbF z6Ha8n#(kbp&e#Vmi_7f`b_I2}SgL)5RKwq9*Vl}4zsBWoL1XDjDMRFif^rRUMS zhs{9GcFuV=A?@Tomf*8;P*Fva`AW5|?VRbe%JiP+ETgEP=cfH(^}LJ{pN>QMJ6ee= z%#mpf23%OILDCt+!X!w&td$uXcmltT%OUJT!SzLELUc+1!bsuW^jgvYID*n+o7Ggv z*mw-XusVAHdz|$HFEBtKPA)c_GdS~3jBizzDh7a@`w2MX}kHy2V{SWF(*O&!vBeE3qACAUy#Y- zJQDwN7uV}4Jx34z`J<dukKMwSad6&TmGW*Aw2bP(| zi1OpH?Ozx`GEOj5sDa3MSsh`4P(029y2R%DGRZSX*=MsGfKA zcM}}vLuHL7eOcTKrdB3#b&Cs_3-FrkV&k~A-<1cHhuEo1*Lk2EAQo$5StkkQ-veOz zqAyKLaI7Lyk&>e2Ne%vrpre{7e_M%P=~i2rR;r=gLaB{><7@{{fh1GyDGra~`yQ|x zKpzJ5V*)cCD=jka&iHHxr~m>}&nHi1&KuY$4tSmiithQu$%h4}iBmzM4)DEju=G|d z@i=ZE1Hd><1T%oE*b9eM8iD-_!1_J!vr|c4=WrnCqru1ylaW~27m~+eFmhhN*6Ron zcIdg)LQB-nnEmUk2YcAh(F$d{FPBm&KWeYo7i$20=rG0r zZG(%X^c52CbCiHfBsgT$In)~;9z6x{+oTsSHVrEaU;HfYR0mbuZwuFaUHQ zaHp8RGwr`@(=--f`5y3?*}<*Z{#NDo4!442^}JyqQJB{~&0LSqM-uN9K7P3D_Kdw~~) z)~IlpX&=|#!jHDr&gQQTP}4@0+lK5LfuxLn{(AV2s?1*X{#9-Msz9$AE~NUPa`tEr zOGncc#XR&^Dbx62Q#-WM=1sh0yXqx0kz@^o%{hL3zXgP}d5hJwg%fl3(>!K1rJb#; zD;meNwe!n2ss%}F9HVw0?3`<`^UtSt4;uF)|2=(@!ZtS+6EEZG{zVg24#L>{uOOFS z2w}k0N$2gGrtASM@si3k6BG;3)KXK1Tbl)*Hse%|C8zWilm?EQR%swJn+Y%Vxs#$} z&|@}@9eeE=Z9UzSBcV0$obRQ?IjenN1Jzk}f^s&1{lo^XzzWWWBlBF?89og@&Cnz^ z^{fqbZX=jxfr&by-2cY zIf(5#7{M3gj(KL-6Syzf4)7A>;3udPWQ*+OjbmKVvgQ20y8xQ!00NKDY4o5PXHX6} zKIr8U_W#g+aRUCIh|BLfvRsr%*&#B+; z-WT^jbDgeWUmOtn6|jiExtn)TIWO=Be?XSg!%+bezy8{r_D0dkWpCiyg$>|)l`$`? z+DDh~&2}Z#Tm4O`*9o;wVzF?ZC-AJP#=;hGQ5NNIYx&se-6jJ~E0b-i2Qb~{nOpU3 z-zzeE+$`&=BGe1ZzMBAjs~qSVz>in>=v6R(eX>6oV%>YF=$D^LmVM1*;zVn7LQ^X` z)bn% zapUcMC;X<=LIv*u075WB3+L&C^QQ{W9FIXbShSu-Ic_gaIhn6g9PdwK&suXHRa!HAA^+;r zQ`G|V$AO@Ym&#M;9q-<9ml?!D4)(lIS_KK2<7WowF?q2xG8CM_!vgdL9197jJjU;l z91Hdq4)sXtAn(A9gSI`SM(BLKH?~ly2co^ip2Rplh9j_eNBBM;J_SR+ zziinpn(KsRT>TorpT@YGkX-2>h|cJN7kST>dSq-Tghl{8*j&FXo?O5Npafq8WRCu$ zJjUo30m?Q$o8SHa1t4(;bpjxHiMpo*cb5SCTIisT!AAD> z-n2aM{CgWD?iJAuoW1i_p7L^=a?n>!v$wG8NarasXFh_R2GL%7KdIKInszMb0>7f` zv0(oeWl{dtmXH1M*F1iy9;b@SPNmYSdi-vezft*>3?S!AXFD0-J;hl*!Yd^Il)dcU z1Ygi6_4Hk`UC6=XU)%FO45jen3zNETU;zN16J5VR-p^6_K1XB1nK#(!BReLkTd43Q zsROVG;VL;p`aD7U!X{ z8GAEjQQ%n+X&KwJ$_y(2&|!aoj_hdc>2Z=;*<$RM<&}VO)~)O)MI-co1ZT!6VgmBA zG$PaGJlR>^!Qv#>Q3ri6&ZX_=ETGFF5~GKvLz^o0R~gfZjkIJ|5Z`31PN7J*&02H{ z+=+UCnLu>xPP2i%bgpZkjr!)*o=^wW#r5PV9y!@hKRL4JIxF^Ko8(W9NG}5%9zVHn z&(Rt^P};l}K|a2=ZdXpw5j@V*NnX5=-`oCs99W(N2bkYnf@;9wqF&7zz@NG`?$c-J zI(;-iM-O$@wXcwPFq%G{H$nJa`X<@$e2c`__gpq383DldE|L9Ruz2_QN!~4-M&5>G zTzN&dsV)s~Gu+$Ss`nA7uC!Fkv@XeI-pO|$rg?(hez&P+zOx7ly(o+Fx37FG@K*+S z&Znu4Bq~44Fb1_@t5UHQEZ{BlhS~0q0pNo}REPk5UkCg5{cgVeXh1ULDE*yXF&z8S z9|0X~Zc?vDGDXGxv`^w9OSs3VoI5VxsQCGF5}YLQ+f}=D8kaDh5k8ugdMoTdR+8PKG&i~sQ*fEEI2^C~t zo#{w&pl8Uw)?_Wsj+9ci052f@nzm4)_3*R)e>Lf;izdjUDSEKg`C^8_s@-g=Ghrpe zWg86L6l^sgxUEQ~qI||Qm4WB9FmwhnH3P0*0uC+J_Ajg9V;amwP0F2f%*i{~SkKNm zx01K3hO?PSz~x5enrDH-2B4cchHZfH%6heMI&K|XdpzDxzh%wt3^Lx}Ie6;p)c49d&H1BUTTrzY7^mkqemEhPHRt!6XJh)G0?F#AvpUmsg zzUW6AhGPPbSNG)-#ZJOqKfEm71ps8}motN84|8d^dvMQoUTw>(A70JA19|t_DhW1M zVI#nIis;ZiFn}&D;5$XAn0=BrH*%19m;b%}HtNE+@avXg77#4o#0jJtBIZspy9-7zU=Q4lYVsd0vaY%;Qbht zK6?Eu`^scmL9+VW<{;1Usk0|y<04!}@LBg_=D}9ZyBZeB-JjCQqd1}_%#qH(Ro zjkR+Q&Y@kjFTNND;w?_OOA5NGAfP210P6%C99jv}G8!08a$sl^C7JNz)VRRVai%@? z4h|s^b6Pk-+E2A`wy&PmJ`XI*TCB#Y<5Ki$GQVf=jYt4E5wa(+&K*KboX5p%1so0r z)A)w)HE|6i4lw3en&1|tk5g%92?rIFzQ>VfgzN=E#^|ke#$yF?QnDm$)3ykPu^b+c z#0#85Vgg0pm2EE_R!IM3>o;`CfG-?fSgj2 zGlN_wjz`~?!_HOl{olZT!1`VMLhcRB!0(?tAoD-;bKrN_x+ge2++A{Yoc{j1q`vdP zJL0uoPuv3VyZhFyNlG{db$2SU0D0ccIoU#7fau2SHhE8qd0RvikKl4a^?fXdQlTk2 zBvnfb(Ex*svM7u4OISYk2cClq>y)^i!XUY}qn)~UTRGe=BwwEIf}nCNtbzgDF6?2r zhx}E{|L#3hsvna0&QlUsF~5IqPcrJ;qfIu=A;Lui)CLvtW*|+hZnJ3mci+L^s71Zch+5> zq;jjSllQ@Ez^XEP1|-aEOAS`3GAWx6wX(uomaD$o+J5ITan)sW*+DydQq6O1{+D)u5tY}o2pKSL)9 zT`%qkF*69MIO}r0V9m}Lp$_9Q0jY{KfrQNLA3AFBkb6*(;k?`lq~H-}Ukpxdl#qxa z0kcOxvw-q=U_0F|4F{+jaNe9Au8--{Q!>l~s#bpYR2x!u7ZU955ju@4rqwx;Ub1oNA_uPD_@VW{o$UuHq`cC{)d<8%5Jd$<6p-OyYG6Y*Ig&|x4%S&vw^VmJAQD_ zc?aLT_snd6?UvXE5WIsr?aPpdITqmE$Nae7p>Kcnf%)#?ZTAVf;Lt^2=sR;9LdwUx zUA_ht2asl`N6>61Wrr%M;mZDu%Lr12gDHYcm+zK1>+4^XMOl_frUG5gnz zNuE3#i!sd&$XK^rpl9Bm8=XN)#x+KuO*kks z&kvHo9mj(f8Mra#r*3^PfSyGIB-)UHTb@>enMTXe&agel`m>xa&Oq^%?#3z?`j4Q* z3H!2|w8SK$d5`BXc#}&0B|9uxFVWnXreMt04wN%uqF~}^&UQo%0QwSkPs};zS)e9r z^Q&8kBF}4Gq&bX>2FWd`BLzjA9?lcC4Qv** zJky3S9Hyxbz+i`(ba29)E^yg>L;lV_DDIUXS1b1;RVyn*#Xd@%!1h@APx zN>8w+9N@cPHz_vZB!2DxiPyQ!|xeCBG zz+DW7U;tZLo-VF8Ako=a5m|63K6+;DA?7XX1K9(6#}e{7fL|Iu7rj+a{$$;kYki!@ z2H)~s?rhrs7hv^Kc3mD_B>5Rofqx9OV*eS*FD*038`;iA8?0eZ+i)u!8J?ueJz)7Q^k9DyX zNrhAbznz_({L{4cH+{hXF3O_(a+XiclNNQ-5<-o z9PWjarhoODWYGInt2fC0@gIWS8<1T27x&H4uS9Z)ium~`clgi%fZR)8Pf0Fuc$ZT& z4CgV7^2+{GE9C71+_CJtVo7mKF_wuhwn$~eBY8AJBM_{h5pnr@<+C`ZPC{zTa#br3 z3HH|t(FkT}1r>RxKz|C2mJ9+fOVR2{(8fg%U-SO!m)-Sh<6e6})o?Er)MiB!#Lcn+;ukENrgy8(7esSexzY=k<}9oK zZn9^hy39>i=9f@t4%1f77L{{zES;Ucf0Jd6n|F4I7+V9NG`R=UX_|LUXC0?{&ei)o z_xRIvb}w@{na>(df6ra7`rFbVNX`Fhv^_*e1YI2BguJO%))__s$t7B_>7Ab_@Ofag zQ_J}Y*(pTZaVUV-I9!YjvQwkuwu-@=s|;YyCK-fYiLv!EcKBesK%uR{XTy< zm1{4NJ>)cUn%r<8TZZFzb~FCYhv=DMe%N8UovtpU0~muT9FbxBUxllFf)3!tRv%Yk z?Oz@)=~vGmx!byee?LAT{plgw0^@wviC)V{{~78SwE_tjk}!Ba{YIC}(aNse`^jay zd024-0lU}zbwKYwy60>HU7*sNH|QSQuiV6d@kU|qx4}8g@1Y|K$@t27Fopn#F3LfX z8t%QqYxvIXN(XvpYX5h8!Upc3PTg`2A=~AOTU+)17zczd;sRFdiJDDt@nTKW!twMD zrnl@o@8CsQltuaFDIXjBmG&Xknueu??aD0odnfP7mSGHB*>ZKED7}2S?1TNgg)w9o zv4_g=E-K2_Wl}%=Lo^Bp4+Xt?m9oQK@#?1|@BZ<+i__Ix&20lezewv`U47pSOpgdY3=%t1JoCTXGRi-aGLbT&;K_9cxy27AU2q#Y-6 zDGXFHN`(wkm%)I&eXU^P1LBy;e2O79gBu};U{Jdv>8mInrEDJ`c`EB>0DZ!+;H2Bq zlkK0q=KrJSU7yEZ)!8Ycd9rW0ZB6?91>kSap~?qJQ@OGJV9VdtV^EcZv$NCHWP~l(iTKl6MWI!kI& z;X#_dig^$5?~9$QhVI>?!Ef9US00h!NPkT59e#xb*gyxYP6iuIC2cE||LltxcjYG+qU2&SR6Ea@%>WK;0YI z{apaSMOl&)^j3MAy?x#M0{Go2*5A10yy5KLn;7T6 zB^%ScewEZ!0Kco4=bdEsXHQaHL}Q>2A_CDzw0B6_pvTSAjhP%&J%O{A(_bRq@KLvnx~spELK)PIpy4%ZZ@ufM0dJ`Z24(ulk5&1$@_mpO`rT zEyaJ3Mc!>3^PC~gL|ep??l`jn_7f(YG7fxMVzOY&T{RXM6==xH6KLVM$c z16c|`W#=a8TzlC&1d=kj-cQV7VZuzN8h9|}yuc%LrZUCB(G~Dz9H*Rj+jXWmHlPck zgbr2W96^MPIV%VT;|sY8`FD&S)LIZKP7P<#rHU%EC!Domn=q;mMY&1x8+Xe=kPzd_4ZKa5CPdb1V5h~F(TI|+`ulfIV^C(@n%gQJij5-(_ussyA@XBw3n)cXsLsQ7gp(nhaExHJhA!aXyl$CWY3wtx#u; zeN4{31{xdJS#79Hz8hINe)Zg{?vHhUb=v??cII9*kMHh*rMlC_ob#l{y`d`6(g4Fv z#&ez%N-25M8TY4o?`o`QYJLRJ^^P_+@z|-umkEGZjNN9!8fO2-VDd60txmN`LM8P$ z(=jyGERfQUj2g4-8+y#zap-cDB!;sIQ|4=O=??{V!#RS%?_qb~(cL^I2`%fvhJ&V$ zeFCB+VrP&M)8b|*Ptmywfhq9}oh*#QVYfhD0C%k}+5P}5UKs+$ha}7>7T+(=8*_%9 zU}O&lrX7$8Agw=jFu9egXRH^myF2}S{M25HE|}hD0bc(+5C6iMh&Ur0+XONPcop+n zzj4(*gS30Ckan5<>!2pM`jE2s00JKzjx2i!-|81{!rMgb5W>-7cz`5qP*-xEU|weE zExg)q=N;Vb$|L4!wh^4X2Mh+cX8T5ydL{SP6UDaE%uLfh(IhTl9uF{g4#}4Z`wKgk zi?S$-@;9Pftryr4hHX<`X&;OYV2#ZFB1H^Je zp<6bNBbQc+2bXM~gUM1%UFPC6-+EyDIHzwEaJsmNgGFn! zWuoOPiGL1ua7s81^-|tgz*V$kx!r%I?VkXsYOh?J0q&Z%a76WO8wA(PIy3Vd8#Qcu zs8;6G^b>G*MkCM!pp)w^Fjm2YlAT#|*5O3V*>`n*l^`tH$Ft6@hLK*@f{C-Xs?V-6 zE7k2pW6Lx4P&r%~dS?2x=BC)Qa~R5(?Ysd5tuH>GdE)=MLsw>pBccABdcfy#)|^T#X*dPqtwH9A~>_0@-tvL-aux4l5x~%kG~rL zKzv>n2-5|6=9D41$GsQ(QYqhsh4BCZt?UYR0I^vh5)2$;vrQrAAQsHT8;skn*i#}_ zM#b1^EV~pdUyohNMrQq9fYOh;ZDBAxR@MxXQ(@8RW1HX8tYe?DCorbj6l5>$+0JSY zvhA`+0LIRr&+DF6XGOB}$3r@z^j8k$ad-rJggL{UZK>njI+z{7R zTjF<~k$eqx-2U4n*$$9FA4|oYBJR#Tmsi-bLx_8T1@Pmt-55~O3Tk8psXqH{*C#Lf zk`?w2^X+yKKBlArSq`35IqtQrboLj(Z&4QIm!^Dd*|+o-tGA|h5>8+z?@iLYFa>4# zHZZ_@W&H&G_q*ujawvoss4Slh$o?T_U%yVS2hBmAlLsT$!!8{?J~+%P!gmd2oWIj~ z{if67oo|t3!*8@$mNNr<3M9dra^YZzZT^A|2suBpCjf*mQm_ta{|0I3vJ|8yu>nAz z2dRn;)H!!4$NYmD>;l=wU=(7V;mDaoFd72{WHU-AGdPx$d~t1tU4Ox4;KadXh&oVf zpqM68R09>wXHeO$3L>k!ox9E4nYJ-&N~O*EyHeBXwwNbofKC&nRT)jk%vElLt!kfS zsp@@f8wZ;KgAEWXsw*{DZroH``P*FW%$rYQR`zY4Q)5vbYm1rlE}dv*Sk^O+G1-4T zFx%{@2&z9pzO|ArfUN#cfUwvtAySfBzF=Whgq}1lV;~Y!)(VC0a7;kF$Pp+)fGz=f z*n~={AP@?AEQP|;0jNBXiKHS-gJB1n^w^s>h{;nN2ax}9j6ksGD%F4yngoNu)0ney zq>z~d!6B8B)SX)n0%an`9B0r^To`T`L**MbfHBoaoR77;`}AWia9k*eGj^db1!U{nf9lhtrl8#nr zX($9|2LX@h;)(W#*zbn`cu@`j55^m|2tGi&M^F{UyEco_8N9qpN7=6Y*B5u?=8`9N z(Sf~wpNM7PFK)EuJ>2#P*Yf{7CbPeB!(9Ii(IY4rH_#dW%@%3458Orv_!f`fBD!~% z1P{#mZPQ(OV+(cbjXT150i(io0itQPes%A{PNko>volG-Ie8sKUX^ce%~ngLJ>aBX znXVzqArDoVOTh~{cu^MRZ(jM>L7sCGYkugGZOR=zwy7T2xjFtu<+#iMe)Sr9z?*rU z#nJNF6OKV(%!bwIUB{?MIlIT_FAy8}*wt0u{yD7&`8Fd^5SO^JhK3_rIpAZ-hiwF1 zo>2TgigUoopl|uQr5wtIGMSnd=sgbq z0<9N^BE^B8*&u33G2fwVrt|%w!F0#t<)lkMQvqrv&S}zhR0mPET$yM_?wl}Q_0368 zqo$|XUi;KpL`)V=Wu}~z$-}<*AOKZj zJTGF)iV%Ph!mUlmlD*;$dqep`C0Pd4NFLN>%u7`mf;z;4mg4U?Aa<bs%nqthsE*-OZ^FgkJOvo+h)9jm$qIZn0#rc6bD+q^$;jBJ9;FFf8 zr(g$l+YaKPXk|nB@OWU4j|Vas4AfE9cbS0>Pf2X7NOce}fE41Ro(KmB7e95|Z2;J$yE)T7@f`F$uKci+mN)%Nd`xC2)3ty@Ij-0X?p z+qo;g4cTPUvEG@=zHa?uD5YP=>h0v-yHtj9nY;)!={LuV1#{@azmzbqVEk&KFG~V| z$~Iw97G+WXrj<`1`?9xhE}WGS=gT%`o9hLxbhhgGeBHt=a%Gzw*=KK3z7EI!TYZxZ zhk1#^9H4Q;{J|4QG$-hd|Cscnd{Y{ZpS$yonE5+LwBkQ^Hh;|g?e3H9vjqGc<34eS zKN>*m=@i>C2t3fIHuNCnoQuS=8jJoZ*^mRxa%eK<3IcA55|`;CWA+cgkG&AbnQ!`F z2ow_++DTFRG_fWDBM0TeH$-#kgL6dOSGULv-)N7gza|zOF0Hv~S_1+TA zVZN&CR#`3kl9em#Oe9BZ=dLt~Q#bGUc6wb+w(QJpDr;0(#{7J0ElUnWUq-FfuHz-E z6pdI_M=fPmJB7bgOUE(Ui=Y|jkI8ZE%T8-@6YRWY4XD-xuww42b_USf<~m$4nf8F{KbINQ~ds`%#RH|9>w|P*!I?!QQ$lF4{V!AAj2wv!30P&#kXol=wxC3 z5+#azQZYc^V35$51Fd749Y81zMa(QC%f^CSJU}1oj>l{f$gw=havDcJ80U3G(#9F$ z>(-}|rC;lH#7al1jTS&**0mSBp0f$IqdvyMg^D`n+;H>?wYpNWISDWz{T0~CtDB@) z{v}3#`$T&Wo{JwneM+78Qp0gvg0Z%7=c-`OkJm1dTECt@j>{N0Px%kmZixOai3lCs zuYZLEGkpvIIV8*#uz}AoeDo?=mVr4q^f$4>mp942efO@DgYROvM|TA?fLw2G7j-Jj zzT4y~)CmaOnRo-|D{WWalXAS113#{Z^X*V?PXaz$b|R@r!bK^aLyEmDfZw7l%HPoP zv1Q*f#T$S1ThlNDbBUg9s^qG?GtK;Qo2JuTSlVERk0Lc@p5LvIeMjd=T*3J1P+LE? z_SIFg9O&6k*R8+0ZlgyX&=Gt?YlICH*=Ffwm0( zq>MbJ+8G!?0wZXJOYe)paJHIIW+sd+agaYBfDHX3<#9l0d~QBoa(~SBe^&Sc8nCgP z@|wFtoF50jJx`scnM|yYu2$x8S~v!YQ2;hjrJnNwX6Gul%D^|sK+t-*tsul#Ck@A- z^B~#E5$7C+6zl*WhBXy?OBxce7kj7p4i7Pt89-+@nD7{t6u=!D|DB$;oeiMYL#YA$ zc>2biC9!0h$N^k3xtqaUCSm0qQ2~=lnB(I}UAt`tFU_NDUw!QQ%J5 zzuB2q0oCtw`;0HA8VaVYuhSeRG?SGCySA@N-Q6 z91yySVW(aRG3!^g0lZ1m9;z$1h}h=u_y2(GJE#Qzkd6NC13)e1`&sW?7U{Y#KRC=x z0@;PLxgV@S>VYhLRM`4G9g?|x9^fk?d(r30oNb8EKrrw_O@9E&VkLkSgvKBqjBLPT zlY1jF7z{YI6o)|rt03F?d@DIrg^<3_nLaY%bC?&jm@>{xoMzUy9Nq%#ALU+uZ06@= zU&z2L4NGVJ=aj4ocxVEGlDTR&&=WSTzzI#S*9jPFZbP#>vovp|0`OG2^_J$dSz-pF z)KP|L>~|iFtTr}H08_vICOWO0+gubg=OB%})PQIOU`~%QJrhx|eUorB_Uwbq+*i?< zcgFeDpIqbKmQ;&q?68z}#gr**p5Kh!Bhei8SWFg_M+OV{`BaJ9T8K4f|6ERB65z(H z%L{Cn1+*dpmlt`yYPFPUOGRNzm^QKFh-JSIHh~ZUVOy4jC8t1Ss|SU?5{FV^Zz#yX z4EC(bDP1}TsuEWL6`0PkMI&T^K6e%exIoO`qduMZ zebgHRVZa1(R*-X|uS{@^n{kYB4gg;q@Nht_G_OB5dl0+(&&3611Tk=);9(z9HR`7i z5BJ0up?-v@|9Jd^KYD17{&H8|pk4I{An+-`9!tN>?A4%UtwUFok^wAUL)Yn_c3w;Qw;a==EXtz%4Je=JQ?+FvQ85c%c)D7_lx4dZO8K|y z@WD2J4Enf)S5TpT2G|b(@O|`@H&KZ_f{t>iQJGloyl5>J`e6S^ez>E{ft4N$WufkZ2FdqBl(oOYD;tx&W z(osP!@;J4d7y6uslv^0zCTvfXMN;{Z=7ulTp^>JoHF2ES76{4~M->d9A`vqxBwG(( z^ZuKeyHa6o%AXDvhM4^qm%=t?NSdQapFzGf% zhbmRpwmlylqF?Ma4++EhfwPb^gA6Q#K^GDxjiF5kHf5XL7$3F->csh{94f|-YzQ5g z*?n{%?RbE7MkA8lmf>(u=Kux)LX8AeJv}|e$87D`!x>VShPzMfwUw^;DCuVG6moHO z59+nS74+=haLM!l1b=R|{uO-OtE;OVl7$9d&hU94C!b;b$|0Fc7=L&b02QLb)Ae2J z{p7N{!<@2mkAdKSOk)2heAge+Za=f1xqjU_eQ7^<>wG1_ z7y_MTfL5GW7=SRyl*jgZfOOi2MjT?JAQ@+hvj79qfw}YSNyaFWPX!+%+Xh1Vm4I#z z_%Q6+C3 zs`!hKIO7OT8FkMA>Lj)U^a1>s`Ez|)W&(i%*=*(9Ig)Rn(^vw(-G8-94?lcJ@u4R! z_dI3|1p~j%2Fde~jyWU@j~Vy<3HJ8^>c(eZBJ zXI}Mp6@HaDfU5wpM&eO}ajLA)WQ(e~dZ*jwpiffa5b+X5s#cn8R)cw~0XI9{S$(~N z^%K=tcO?@@v);9~4!UMjq|GvRR9sUN(v~1o7SXh)fU6#NZ-yDHqCrtVr`h{%8}p}@ zjMMv5WKp)WjG(1C&*7!VqdCmqoO?BApTu|!ETAyr-*6yk0Kr%(nV}WO2SF)V@Uica zHmOY<1BfpOpev064#=!C&LeE3;xN&P2Zpc30ohXzgr5bQm|?eI1$_^yat6}g>Nxdu zOA4X!0r>!Z83sM(fYJ_Ty1c;088M8s0zj(+Sy)2w9ZE7ioa`wDnmcTqGnj`BeAk1N zOv0g@R- z^+#mctR-9}%R!=9*jRm+GWPgo=MO5B5SJnj2=Ee^z`Jh~OT2XV0}|{E!tnq(EgaA1 zF|&a8s7R-|GnIeuRM@|AaF2OwTHS0H3}6NH|DK|E-3#;dY%Pxz8Vn}^1Hg$ZJyH)*k++rs z06EJCKw|dnzyUwgZ3#}~qyhAdiSsNU33~2cDA}H974x%ns7}!^NF_WrLJtLdHTp`k zp_0w~ENF!-NzpKYh=kSzjaTP1P6Hsq4(?D}7~Me&g?_us@bTYHY_-Ict!px5^S~cX zwhb2XJ?PtIYM7OR21Dgd-o zz|wSjuxFg#%fV;9|A`c9Yimv@zjB@0`P$}n(ERI|1H>kPx3Qg=?s^uK5mmCKOaZxg z$ua57^DB=pXrAm_?Ep;ImG?cZf0P>W92_3-9VOD{QZQg1W-|!1KzlNQyc()d*$#=oE-$6LpqJEv&RMawCos?rjcP6 z5tfEyHNrIHU;sgkC>~oQR@;`df^2vl0foW*?1c2@T6w$sw37Dh697Wz5KPCkyu3_L zj&hsdtEhK=1c0{|J;(a%`6lZ*2xL3u&*SGkOyBq{$q$O`AXhQ4ZQx~qKd^q5Z2;Nq zg#lpFC&j-xQ^*^b{sG1t&oDhA^)4jh-@WBJ)@q|5tnXs@_6@SmALMo}1G|p)&h!=7 zE+>ll+r{)&S*hoe@VrmFRwjKNF}u790Jj}MkcYim*aI%gqWtwKANzAGW#1V9QC+8T zfbFW(P+%E=_wBpC9?3V3#-{t~ zb^pO%k$F0HhSDnS0{{;5>-h9V@(!Po_)Eyf7a{#b9g?T#$g)xQE0A3V<5Lj~gd84^ zM7XjhS^5bf6Ql8wqd_bJ6{0*9Kx6$2O#j3Vrjk%*91PvYRwd{}*@zvbh*>)?^8x0-6pG_t zoU{z5hvOju=ri7_7-iO8?R4moH!$LeEQAF5Oqb|q)YaFczI>>A*=64`jP~g0h%Rpk z@kB1`=@D=fYrN6E=V*>!C(FF$^1|M9eErYh5;c1!8>H2$+H=aB|CHMln zS|Z{!Z{3U6<@HUlfE)<=A!VEEBrZHBaexZ>=>gf5HFVwq03V|gKF0b>8zeet7@ne1 z?W0w>aS1G5mY-S#jxJhjQ3sXoA-eYC)5v;Y2sm6B2bVRB1N;P{65AUE&LSNR3%HAn zBb{kIN>!SXIs`ot2Ap3WJVMG@JUGsuGSI1vhqgKpn1#aeon0j8dH1E9Xtec{#E8qMdiAxVPB+}<}{w{OH=@H1JsuH;w<1-JBcc?uRLTs1H5b{ z1kW_!O!c~Db%W};oO#UV`GA5er@H*u8@Ak?5Pk%_>Z{8;T_*?k{z5BT5;sD;D+bjj=5f^rHR>w4G*2>XOo(EFn=SS2?>I-9rIJJxtGwj5Z2A zk48U5#G@PYG&2xTdTBIJ4PiuJrT`gm*`<=1ZAmU!rqZeunaUJJlgSW+WRP(?*L_=S zzJFiNS$m%yOv#qyDmxZ=;_S08Yv2F%{ojAvAgV+IXLo&l_UWl8PS)eKQHq3O1$G58 zmV@&2v8twpIc5Qm+j0B5Z9mg1IMv#U7n!O9&O|FL{6sb=_ZzX7Z4{AaL{oDF*8yg; zZ}tPi?ZLN6a(TBb;umJ$PhC2%#}~*+7AW1{A<0RfPjPjRheRJSr2LTN4g_Wh2Ja^Y z>*u#g-c_Vqt0ZCX0fUFZ2;Yn82#nu|-Saqwi~L|@(iH~=B~Qq3oK@sdJurV#f%(hQ zOpwxG6aqI^Q5p-Gx=x7EDulr}@Hv*);ndXrSd-V5x}zxWghu!r?Tc&B7Hs|%vVH&K z&i|NQ@EXu-x(mjdXKUJ`=)lyj_soy_fNy~`QCnUzUX$e(KoJF^DBu#6zGy@>kSFLl zK6can+Xr6e+MA@2v7QoP9T!=YLb=Z-fMuJ6KYaf+CXE9=Y_^lAF^afy?aa~3gmp}_ z*W47_S_fpRkANtSX`%tx;P8n3nWp`deZ7peAN!it&?C*RGud~pAppkcZ(!LIA=w9z zVxdf!CKOT50>YM?MlNTNaY4j|I&vH)_7q+on1TUVgj5M$LLD2)B|l)%Rk7ncIOPN@ z^K!6%(AJ{JBwSP5w4FGJV%f)BPP@c~MPgL zZ&`Cd$QqcyIaoIC6#jq^b17#qa2K_?u(D?Qe@U8i0%o}lZd2O-m$>LZ%CA3{gF6uw zcj?JAHt7as*|O;Mw}{?)J@*038m3=*pTxiZBBlRilO)!w1b}Q<_x76Q8)!3u>sFb! z4Vp#*PoIfd88s0L)+3Y!nrxIYWVs(8O(KJ7;vY!I?YJGcUrPHMj1wr>KA0_$nv-748#k+R`1Hu7- z9vHw^&*#98+q0)cT#$XsB@Nb4e*~bg4}tvGn$lj2OnY2~R^!wdE(YJ?lLs5{Qmdur zIYsk$%pr-ghk3+IAeVVV8)6`GF_H|U*sv#3lDfJJRw7GYi_tKRQ?B4ETql(rj;IZD zBE&^~4I7H{&vgH$c0KH73(dxXoTz55w6+sfFxJ$>%tRFg+4r=Znt&HfX)^^!m1DEP zBpuOe3n~>d%rv2MnR~D)CCLnPIbp}B39DEuQWboW{mjw~5Hh9&yCwb-ROfCReKqOB z=Vd)jkeg`eP)r!u^1Hm|<#|?)pN_ufwM$%CS6IokiOj4EX2ZoS;;*|#ivI?W^`~&3 z@RDc`kH3!&1IpJfQ<{=#5iO3+OoD$^@L+m7YbP7a4tZuz{5HJjP$aTi9qwUxlA19?P|}A zv5nW_0In=oryOIN-8+&nQeGz%oYv`Ms^z%z0kB;_b7HKXK{zC0r2J$ggETJ<$N2)e z{}z7V84twroK$mQ__#XA{+K@?20yYWTM0>!f20dHlnyk)Tru(^B|3Mzo# ze#ovYFau+qL)uZx=T2qo--R_}^h#bs_|Lbvh{!#;|Fz8ID@Tt>eByFFSY#*%DWXMenbPtD3U$f0Kp!l z>h1CuV1~Gi&lmFFU;6_x9MyK=H4>cYc@L(L(Qg2UVIbK08ku+AA${*2(Zj!lWao)dFXO(>wCv~AP0*xS~&M&OY zF0L@z*dR+-*Q9>^Dr{8oUB7-7R+*VMx$X@Yybc8F`!@D`E3JvB@29ro$-E}}uQffh zov^BUQ2yIc9hB5st)FUrTRO7Yps766>8xp*7rRwTzcpO23V5vSXnHN}-_u^rW4=(Qyr$P0y29@wZk_R(>4*!TmvI!w<2p@Xg0qF3hoOa-Z>nC)4;%1 zDq6A8jKlz6BmklILG{6kOCA8D%bss(46q2)$AEAcrV{34XY8;CVCu#u1n>)$Oj8Hu zUkQ}Q-hv$8>-L~*4MQ#h%)wm_XN94_bCt*e`1|8Ao)Gxq{9ejFO!A6gE zVFK>LBG7gD2ox9E+t9eGvqgNmtBctMWT2ShYOL+6hoY;^LAA9c3H-MwwmIz?u6(3TyTfp6csO`Hbi-s|@=x1E#cFc06H z9Gd4}EpHUe!yAyLUt-qnI+>*lH|6acHw4#H=f#|Pfn9Le51Xg?lVxSttR{tCpFFN+ zGHuIup5C-yGwXFJO}x`8Aodehl8X8CaXOgecHDk>?Pq$~ral|fbT9dW5AU=T5*utf z*qoXjGHtVPL^*w$oJYS2v+b)S9=#1g`UnCT+A9k_)_gf7!WQfyF!LHOB6YSy@;l$p z_das+h~G0^5Css#59XnHbA&I~9L0Rj;nhk9l2bZ(fslb}i4lZ4gfhmOu}pO+J5s2= znAc7~T+tqcL!@@u|0v@0vz}xUA|O}66y@*6BiU(rt*-K02Vv?e4})KrRQ%tpoc2YEwh17(>d0+t4AJJ z`n1rzs2zii8K=vt=P`Ma>ZV=WW@f6_hOFJrn77MG!{$-z7;H0oxkA||Rrhn`^{$;? z31V>!e+9^M6L>9Fq!UikmueG1{fRUu?31iBr4fj@*pZZiKno2=`=$oE5~Wb0z}cyI z5mcK=O$sVdkHc{)5(UB#YL#>n!ByS(f#8ZffP5SQ%<(0WZY8XcaS9mz?ttQp@tq`Y z77fhMFCj1Ggq$1!EL%*?M z@?8#!OS|%v{DT$EreF^Ia8KmxdUv?wFF#T_D4`|Cj>& z+#aT3G2wX(_HJIu%ftah%IjFRu}$Xn*NOhgS4e#IUz27a_o zDlR)YH2Z6pt~w0-HmUI4rUqF>*7Iklh!NH2v-XM=3QJim>Np_exE;4&M*A7fzWld{ z_7%mzSRdGnW@_J)wmHo|Rr;A+zD!~VpA! IeKFElUtw=V1P2;Kwd6TM%g9g#cXq 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