diff --git a/.agents/skills/warp-integration-test/SKILL.md b/.agents/skills/warp-integration-test/SKILL.md index dc39260c66..b0c297817a 100644 --- a/.agents/skills/warp-integration-test/SKILL.md +++ b/.agents/skills/warp-integration-test/SKILL.md @@ -20,7 +20,7 @@ The core pieces are: - `crates/integration/tests/common/mod.rs` - The outer Rust test harness used by `cargo test` and `cargo nextest`. - Shells out to the integration binary. - - Forwards a limited set of env vars (`PATH`, `RUST_*`, `WARP_*`, `WARPUI_*`, `WGPU_*`, display-related vars). + - Forwards a limited set of env vars (`PATH`, `RUST_*`, `ZAPLEX_*`, `WARPUI_*`, `WGPU_*`, display-related vars). - Re-runs tests up to 10 times when the integration binary exits with the special rerun code. - `crates/integration/src/test.rs` - Module hub for integration tests. diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index dc0fb40410..2a96311a11 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -8,7 +8,7 @@ body: Thanks for helping improve **Zap** — a community fork of Zap focused on BYOP (bring-your-own-provider) AI, local credentials, and a privacy-first terminal. **Out of scope (please do not file here):** - - Zap cloud account / billing / Zap AI quota / Warpify SSO / Zap Drive cloud sync — these belong to upstream Zap, not Zap. + - Zap cloud account / billing / Zap AI quota / Zaplexify SSO / Zap Drive cloud sync — these belong to upstream Zap, not Zap. - Issues only reproducible with Zap's first-party cloud agent (Zap ships it disabled). - type: checkboxes attributes: @@ -16,7 +16,7 @@ body: options: - label: "I have [searched Zap issues](https://github.com/zerx-lab/warp/issues?q=is%3Aissue+label%3Abug) and there are no duplicates." required: true - - label: "This bug is in Zap itself — not in upstream Zap's cloud account, billing, Warpify SSO, or cloud-only features." + - label: "This bug is in Zap itself — not in upstream Zap's cloud account, billing, Zaplexify SSO, or cloud-only features." required: true - label: "I have included relevant logs (`RUST_LOG=info` output, panic traces, etc.). Optional, but greatly speeds up triage." required: false diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml index 65a30a0827..0417986397 100644 --- a/.github/ISSUE_TEMPLATE/02_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml @@ -13,7 +13,7 @@ body: - **Preserved Zap UX** — Blocks, Workflows, Keymaps, themes; continuously merged from upstream. - **Localized UI** — English + Simplified Chinese, community-extensible. - **Out of scope:** features that require Zap's cloud account, Zap AI gateway, Zap Drive cloud sync, Warpify SSO, billing, team/enterprise admin, or Zap's first-party telemetry. Those belong to upstream Zap. + **Out of scope:** features that require Zap's cloud account, Zap AI gateway, Zap Drive cloud sync, Zaplexify SSO, billing, team/enterprise admin, or Zap's first-party telemetry. Those belong to upstream Zap. - type: checkboxes attributes: label: "Pre-submit Checks" @@ -22,7 +22,7 @@ body: required: true - label: "I have read the [Zap README](https://github.com/zerx-lab/warp#readme) and this feature is not already supported." required: true - - label: "This request fits Zap's BYOP / local-first / privacy-first direction (it does NOT require Zap cloud account, Zap AI gateway, Zap Drive cloud sync, Warpify SSO, billing, or telemetry)." + - label: "This request fits Zap's BYOP / local-first / privacy-first direction (it does NOT require Zap cloud account, Zap AI gateway, Zap Drive cloud sync, Zaplexify SSO, billing, or telemetry)." required: true - type: textarea id: "describe-solution" diff --git a/.github/ISSUE_TEMPLATE/03_ssh_tmux.yml b/.github/ISSUE_TEMPLATE/03_ssh_tmux.yml index 0d5cc02700..ed48e26d97 100644 --- a/.github/ISSUE_TEMPLATE/03_ssh_tmux.yml +++ b/.github/ISSUE_TEMPLATE/03_ssh_tmux.yml @@ -1,13 +1,13 @@ -name: SSH Warpify (tmux) Issue -description: "SSH Warpification with tmux fails in Zap. Use this template for that specific scenario." +name: SSH Zaplexify (tmux) Issue +description: "SSH Zaplexification with tmux fails in Zap. Use this template for that specific scenario." labels: ["bug", "area:ssh"] body: - type: markdown attributes: value: | - Use this template only when SSH **Warpification** (subshell bootstrap over SSH with tmux) fails in **Zap**. + Use this template only when SSH **Zaplexification** (subshell bootstrap over SSH with tmux) fails in **Zap**. - - Cloud-account or Warpify-SSO issues belong to upstream Zap, not Zap. + - Cloud-account or Zaplexify-SSO issues belong to upstream Zap, not Zap. - For non-tmux SSH bootstrap failures, use the *Legacy SSH* template instead. - type: checkboxes attributes: diff --git a/.github/ISSUE_TEMPLATE/04_ssh_legacy.yml b/.github/ISSUE_TEMPLATE/04_ssh_legacy.yml index 724cd5bae0..c8c6f7c169 100644 --- a/.github/ISSUE_TEMPLATE/04_ssh_legacy.yml +++ b/.github/ISSUE_TEMPLATE/04_ssh_legacy.yml @@ -5,10 +5,10 @@ body: - type: markdown attributes: value: | - Use this template when **Zap** fails to bootstrap a subshell over SSH (the legacy non-tmux Warpify path). + Use this template when **Zap** fails to bootstrap a subshell over SSH (the legacy non-tmux Zaplexify path). - - Cloud-account or Warpify-SSO issues belong to upstream Zap, not Zap. - - If your failure is specifically with **tmux Warpification**, use the *SSH Warpify (tmux)* template instead. + - Cloud-account or Zaplexify-SSO issues belong to upstream Zap, not Zap. + - If your failure is specifically with **tmux Zaplexification**, use the *SSH Zaplexify (tmux)* template instead. - type: checkboxes attributes: label: "Pre-submit Checks" @@ -77,7 +77,7 @@ body: id: "xtrace-output" attributes: label: "Include shell xtrace output" - description: "Run `WARP_DEBUG_MODE=1 ssh YOUR-HOSTNAME-HERE` from inside Zap and paste the full output starting from the SSH command block. Redact secrets before posting." + description: "Run `ZAPLEX_DEBUG_MODE=1 ssh YOUR-HOSTNAME-HERE` from inside Zap and paste the full output starting from the SSH command block. Redact secrets before posting." validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0013b35736..122a9f1338 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -11,4 +11,4 @@ contact_links: about: Architecture notes, upstream-sync plan, and design docs in the repo. - name: Upstream Zap (reference only) url: https://github.com/warpdotdev/warp - about: Zap is an unaffiliated community fork — upstream Zap is shown for reference only. Cloud / billing / account / Warpify SSO issues belong upstream, not here. + about: Zap is an unaffiliated community fork — upstream Zap is shown for reference only. Cloud / billing / account / Zaplexify SSO issues belong upstream, not here. diff --git a/.github/STAKEHOLDERS b/.github/STAKEHOLDERS index e87a680ac7..7b6f9ae207 100644 --- a/.github/STAKEHOLDERS +++ b/.github/STAKEHOLDERS @@ -86,12 +86,12 @@ /crates/input_classifier/ @harryalbert /crates/natural_language_detection/ @harryalbert -# Blocklist UX / modality and cloud mode UI / shell compatibility / completions and bootstrap / warpifying +# Blocklist UX / modality and cloud mode UI / shell compatibility / completions and bootstrap / zaplexifying /app/src/ai/blocklist/ @zachbai /app/src/root_view.rs @zachbai /app/src/terminal/bootstrap.rs @zachbai -/app/src/terminal/warpify/ @zachbai -/app/src/settings_view/warpify_page.rs @zachbai +/app/src/terminal/zaplexify/ @zachbai +/app/src/settings_view/zaplexify_page.rs @zachbai /app/assets/bundled/bootstrap/ @zachbai /crates/warp_completer/ @zachbai @szgupta @alokedesai diff --git a/.github/actions/prepare_environment/action.yml b/.github/actions/prepare_environment/action.yml index f22fe02cfe..580429f8af 100644 --- a/.github/actions/prepare_environment/action.yml +++ b/.github/actions/prepare_environment/action.yml @@ -38,11 +38,11 @@ runs: - name: Add Strawberry Perl to PATH and point openssl-src at it (Windows) if: ${{ inputs.target_os == 'windows' }} shell: bash - # openssl-sys (vendored) 用 openssl-src 跑 `perl ./Configure` 编译 OpenSSL。 - # 仅把 Strawberry 加进 PATH 不可靠:cargo build script spawn 的裸 `perl` 仍会 - # 解析到 Git 自带的 MSYS perl(缺 Locale::Maketext::Simple,Configure 报错退出 2)。 - # 显式设 OPENSSL_SRC_PERL 指向 Strawberry perl 绝对路径,绕开 PATH 解析, - # 与 openssl-src 官方 CI 做法一致。 + # openssl-sys (vendored) uses openssl-src to run `perl ./Configure` to compile OpenSSL. + # Just adding Strawberry to PATH is unreliable; perl spawned by cargo build scripts still + # resolves to the built-in MSYS perl from Git (lacking Locale::Maketext::Simple, Configure fails with exit 2). + # Explicitly set OPENSSL_SRC_PERL to point to the absolute path of Strawberry perl, bypassing PATH resolution, + # consistent with the official openssl-src CI approach. run: | echo "C:/Strawberry/perl/bin" >> $GITHUB_PATH echo "OPENSSL_SRC_PERL=C:/Strawberry/perl/bin/perl" >> $GITHUB_ENV @@ -175,3 +175,16 @@ runs: if: ${{ inputs.target_os == 'macos' && inputs.is_self_hosted != 'true' }} with: xcode-version: '26' + + # Xcode 26 ships the Metal toolchain as a separately downloadable component; + # without it `xcrun metal` is missing and crates/warpui/build.rs fails to + # compile the .metal shaders ("cannot execute tool 'metal' due to missing + # Metal Toolchain"). Must run AFTER setup-xcode so it targets the selected + # Xcode. `script/macos/install_build_deps` runs `xcodebuild -downloadComponent + # MetalToolchain`. + - name: Install macOS build deps (Metal toolchain) + if: ${{ inputs.target_os == 'macos' && inputs.is_self_hosted != 'true' }} + shell: bash + run: | + cd "${{ steps.repo-root.outputs.path }}" + ./script/macos/install_build_deps diff --git a/.github/workflows/build-remote-server.yml b/.github/workflows/build-remote-server.yml new file mode 100644 index 0000000000..d91db0f12a --- /dev/null +++ b/.github/workflows/build-remote-server.yml @@ -0,0 +1,95 @@ +# Build-remote-server — baut NUR das Linux-remote-server-Binary (statisch musl), +# das auf dem SSH-Ziel-Host (z.B. devhost) als Daemon läuft. +# +# Hintergrund: zaplex ist eine macOS-App, aber die persistente Remote-Session +# läuft als Daemon auf dem Linux-Ziel-Host. Der Client (DMG) erwartet das Binary +# unter ~/.zaplex/remote-server/zaplex-. Im Fork-Setup schlägt der +# Auto-Download (zerx-lab/warp releases) fehl, daher wird dieses Binary als +# Artefakt gebaut und manuell auf den Ziel-Host gelegt (check_binary besteht dann, +# der Download-Pfad wird nie genommen). +# +# Aufruf: gh workflow run build-remote-server.yml -R byte5ai/zaplex --ref \ +# [-f dmg_tag=v0.daemontest] +# Danach: gh run download -R byte5ai/zaplex -n zaplex-remote-server-linux-x86_64 +# → Binary nach ~/.zaplex/remote-server/zaplex- auf dem Ziel-Host, +# chmod 755. (muss zum dmg_tag des getesteten DMG passen.) +# +# Statischer musl-Build (zigbuild) → läuft auf jedem Linux x86_64 unabhängig von +# der glibc-Version des Hosts. Spiegelt den CLI-Build aus zap_release.yml. + +name: Build remote-server (Linux) + +on: + workflow_dispatch: + inputs: + dmg_tag: + description: "GIT_RELEASE_TAG — muss zum getesteten DMG passen (kosmetisch für die Binary-Version)" + type: string + default: v0.daemontest + +env: + CARGO_TERM_COLOR: always + CHANNEL: oss + +jobs: + build_remote_server: + name: Build Linux remote-server (musl x86_64) + runs-on: ubuntu-22.04 + timeout-minutes: 180 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Free up disk space + shell: bash + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc \ + /usr/local/.ghcup /opt/hostedtoolcache/CodeQL /usr/share/swift || true + df -h / + + - uses: ./.github/actions/prepare_environment + with: + target_os: linux + is_self_hosted: false + install_release_deps: true + + - name: Install zig + cargo-zigbuild for static musl build + run: | + set -euo pipefail + rustup target add x86_64-unknown-linux-musl + ZIG_VERSION="0.13.0" + ZIG_DIR="$HOME/.local/zig-${ZIG_VERSION}" + if [ ! -x "${ZIG_DIR}/zig" ]; then + mkdir -p "${ZIG_DIR}" + curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz" \ + | tar -xJ -C "${ZIG_DIR}" --strip-components=1 + fi + echo "${ZIG_DIR}" >> "$GITHUB_PATH" + cargo install --locked cargo-zigbuild --version ^0.19 + shell: bash + + - name: Build remote-server CLI (static musl via zigbuild) + id: bundle_cli + run: | + script/bundle --channel "$CHANNEL" --arch x86_64 --artifact cli --packages none \ + --target x86_64-unknown-linux-musl + shell: bash + env: + GIT_RELEASE_TAG: ${{ inputs.dmg_tag }} + APPIMAGE_EXTRACT_AND_RUN: 1 + USE_ZIGBUILD: "true" + PKG_CONFIG_ALLOW_CROSS: 1 + + - name: Stage binary under the name the client expects + # The client looks for ~/.zaplex/remote-server/zaplex- + # (remote_server_binary(): binary_name()=`zaplex` + `-` suffix), so + # stage the artifact under exactly that name for drop-in placement. + run: cp "${{ steps.bundle_cli.outputs.executable_path }}" "zaplex-${{ inputs.dmg_tag }}" + shell: bash + + - name: Upload remote-server binary + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: zaplex-remote-server-linux-x86_64 + path: zaplex-${{ inputs.dmg_tag }} + if-no-files-found: error + retention-days: 7 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index c915b342b6..52ce4d2aa5 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -51,10 +51,10 @@ jobs: run: rustup target add x86_64-unknown-linux-gnu shell: bash - - name: cargo check (default = zap-oss bin) + - name: cargo check (default = zaplex bin) # `cargo check --workspace` would pull in wasm/preview channel-gated # targets; check only the default oss bin + its dependency graph, matching # the release_linux compile target. (Cargo.lock is gitignored, so no # --locked here.) - run: cargo check --bin zap-oss + run: cargo check --bin zaplex shell: bash diff --git a/.github/workflows/test-dmg.yml b/.github/workflows/test-dmg.yml new file mode 100644 index 0000000000..e6bb45db85 --- /dev/null +++ b/.github/workflows/test-dmg.yml @@ -0,0 +1,93 @@ +# Test-DMG-Pipeline — schlanker macOS-Build NUR fürs Testen. +# +# Baut ausschließlich das macOS-Build, das gerade getestet werden soll, und lädt +# es als Workflow-Artefakt hoch. KEIN Windows/Linux/Intel, KEIN GitHub-Release +# (anders als zap_release.yml). Gedacht für den schnellen Test-Loop — analog zu +# test-dispatch.yml für Rust-Tests. +# +# Aufruf: gh workflow run test-dmg.yml -R byte5ai/zaplex --ref \ +# [-f arch=aarch64|x86_64] [-f dmg_tag=v0.daemontest] +# +# Das .dmg ist ad-hoc-signiert (wie zap_release.yml, --selfsign) → beim ersten +# Öffnen auf dem Mac einmal: xattr -rd com.apple.quarantine /Applications/Zap.app +# (bzw. das .dmg), siehe issue #51. Kein Developer-ID/Notarize im OSS-Channel. +# +# `dmg_tag` wird als GIT_RELEASE_TAG eingebrannt und bestimmt den remote-server- +# Pfad, den der Client auf dem Ziel-Host prüft: ~/.zaplex/remote-server/zaplex-. +# Fixer Default, damit das Daemon-Binary auf dem Ziel-Host deterministisch +# vorab platziert werden kann. + +name: Test DMG + +on: + workflow_dispatch: + inputs: + arch: + description: "macOS-Architektur (aarch64 = Apple Silicon, x86_64 = Intel)" + type: choice + default: aarch64 + options: + - aarch64 + - x86_64 + dmg_tag: + description: "GIT_RELEASE_TAG (bestimmt den remote-server-Pfad auf dem Ziel-Host)" + type: string + default: v0.daemontest + fast: + description: "Schnell bauen (Debug-Profil: Minuten statt ~Stunde). Für Funktionstests. Aus = release-lto (langsam, optimiert)." + type: boolean + default: true + +env: + CARGO_TERM_COLOR: always + CHANNEL: oss + +jobs: + build_dmg: + name: Build Test DMG (macOS ${{ inputs.arch }}) + runs-on: ${{ inputs.arch == 'x86_64' && 'macos-26-intel' || 'macos-26' }} + timeout-minutes: 360 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: ./.github/actions/prepare_environment + with: + target_os: macos + cache_key: macos-${{ inputs.arch }} + is_self_hosted: false + install_release_deps: true + + - name: Ensure rust target is installed + run: rustup target add ${{ inputs.arch }}-apple-darwin + shell: bash + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 + with: + go-version: stable + + - name: Install cargo-bundle + run: script/install_cargo_bundle + + - name: Install create-dmg + run: brew install create-dmg + + # --selfsign = ad-hoc-Signatur (kein Developer-ID-Cert nötig), identisch zu + # zap_release.yml. dmg_name_suffix nur fürs Dateinaming. + - name: Build ${{ inputs.arch }} bundle (ad-hoc signed) + id: bundle_app + run: | + script/bundle --channel "$CHANNEL" --arch ${{ inputs.arch }} \ + --dmg-name-suffix "${{ inputs.arch == 'x86_64' && 'intel' || 'arm64' }}" --selfsign \ + ${{ inputs.fast && '--debug' || '' }} + shell: bash + env: + GIT_RELEASE_TAG: ${{ inputs.dmg_tag }} + + - name: Upload DMG artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: zap-test-dmg-${{ inputs.arch }} + path: ${{ steps.bundle_app.outputs.dmg_path }} + if-no-files-found: error + retention-days: 7 diff --git a/.github/workflows/zap_release.yml b/.github/workflows/zap_release.yml index 08eede2498..0e8c941ef4 100644 --- a/.github/workflows/zap_release.yml +++ b/.github/workflows/zap_release.yml @@ -1,33 +1,33 @@ -# Zap 发布流水线 — 在 openWarp 分支上 push tag (v*) 即触发 +# Zap release pipeline — triggered by pushing tag (v*) to the openWarp branch # -# 与上游 create_release.yml 的差异: -# - 仅 macOS arm64 + macOS Intel + Windows x64 + Linux x86_64, -# Web/CLI/Universal/Win-arm64 全砍 -# (macOS 双架构分别产单架构 DMG 与 CLI tarball;Linux 产 AppImage/deb/rpm -# 以及尽力而为的 Arch 包,不发布上游 Zap 仓库源或签名 key) -# - 单 channel: preview(由 release_configurations.json 提供配置) -# - 不签名:不传 --read-passwords-from-env(macOS) / 不设 SIGN_TOOL_CMD(Windows) -# - 不上传 GCS / 第三方 crash 平台 / Slack / changelog,不调 channel-versions repository_dispatch -# - 复用 ./.github/actions/{prepare_environment,get_channel_config} +# Differences from upstream create_release.yml: +# - Only macOS arm64 + macOS Intel + Windows x64 + Linux x86_64, +# all of Web/CLI/Universal/Win-arm64 removed +# (macOS dual-architecture produces single-arch DMG and CLI tarball respectively; Linux produces AppImage/deb/rpm +# plus best-effort Arch package, does not publish upstream Zap repository source or signing keys) +# - Single channel: preview (configuration provided by release_configurations.json) +# - No signing: no --read-passwords-from-env (macOS) / no SIGN_TOOL_CMD (Windows) +# - No upload to GCS / third-party crash platforms / Slack / changelog, no channel-versions repository_dispatch call +# - Reuse ./.github/actions/{prepare_environment,get_channel_config} # -# 流水线结构(先打包后发布,避免出现空 Release): -# prepare_metadata → release_{macos,windows,linux} (上传成 workflow artifact) -# → publish_release (全部 build 成功后下载 artifact 并创建 Release) -# 任意 build job 失败 → publish_release 因 needs 跳过 → 不会留下没有安装包的 Release。 +# Pipeline structure (package first, publish later, avoid creating empty Release): +# prepare_metadata → release_{macos,windows,linux} (upload as workflow artifact) +# → publish_release (download artifact and create Release after all builds succeed) +# If any build job fails → publish_release skips due to needs → no Release without installers left behind. name: Zap Release -# PR 验证由 .github/workflows/pr-check.yml 负责(Linux cargo check)。 -# 本 workflow 只在 push tag / 手动 dispatch 时跑全量三平台打包,避免 PR 跑 ~3h 浪费。 +# PR validation is handled by .github/workflows/pr-check.yml (Linux cargo check). +# This workflow only runs full three-platform packaging when pushing a tag / manual dispatch, avoiding ~3h waste on PRs. on: push: tags: - "v*" workflow_dispatch: -# 创建 release + 上传 asset 需要写权限。默认 GITHUB_TOKEN 在 fork/受限仓库下是只读, -# 这里显式声明 contents: write 兜底 403 "Resource not accessible by integration"。 -# actions: write 用于 publish_release 末尾删除本次 run 的 workflow artifact(释放 Actions storage)。 +# Creating a release + uploading assets requires write permissions. Default GITHUB_TOKEN is read-only on forks/restricted repos, +# explicitly declare contents: write here to avoid 403 "Resource not accessible by integration". +# actions: write is used by publish_release at the end to delete this run's workflow artifacts (free up Actions storage). permissions: contents: write actions: write @@ -35,10 +35,10 @@ permissions: env: CARGO_TERM_COLOR: always CONFIG_FILE: ".github/workflows/release_configurations.json" - # Zap fork 用 oss channel(对应 app/src/bin/oss.rs 内联 ChannelConfig, - # 不依赖 warp-channel-config 私有二进制),preview 等内部 channel 走 - # channel_config::load_config!() 宏 → build.rs 调 warp-channel-config 失败 → - # *_config.json 不生成 → include_str! 编译失败。 + # Zap fork uses oss channel (corresponding to inline ChannelConfig in app/src/bin/oss.rs, + # does not depend on private warp-channel-config binary), internal channels like preview use + # channel_config::load_config!() macro → build.rs calls warp-channel-config fails → + # *_config.json not generated → include_str! compilation fails. CHANNEL: oss jobs: @@ -50,8 +50,8 @@ jobs: release_branch: ${{ steps.compute_tag.outputs.branch }} release_name: ${{ steps.get-config.outputs.release_base_name }} ${{ steps.compute_tag.outputs.tag }} steps: - # fetch-depth: 0 让 git-cliff 能看到完整 tag/commit 历史; - # shallow clone 下 `git describe` 找不到上一个 tag,会退化成空 release notes。 + # fetch-depth: 0 lets git-cliff see the full tag/commit history; + # under shallow clone, `git describe` cannot find the previous tag, degrades to empty release notes. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 @@ -67,8 +67,8 @@ jobs: id: compute_tag shell: bash run: | - # tag-触发场景:GITHUB_REF=refs/tags/ - # workflow_dispatch:GITHUB_REF=refs/heads/,无 tag,生成临时 tag + # tag-triggered scenario: GITHUB_REF=refs/tags/ + # workflow_dispatch: GITHUB_REF=refs/heads/, no tag, generate temporary tag if [[ "$GITHUB_REF" == refs/tags/* ]]; then TAG="${GITHUB_REF#refs/tags/}" BRANCH="$TAG" @@ -80,10 +80,10 @@ jobs: echo "branch=$BRANCH" >> $GITHUB_OUTPUT echo "Release tag: $TAG / build ref: $BRANCH" - # 找上一个 release tag(匹配 v*),用于限定 git-cliff 的 commit 范围。 - # tag-触发:HEAD == 当前 tag,要从 HEAD^ 起找最近的 v* tag。 - # dispatch:HEAD 不是 tag,直接从 HEAD 起找。 - # 找不到(首次发布)时输出空字符串,后面 git-cliff 不限定范围。 + # Find the previous release tag (matching v*) to limit the commit range for git-cliff. + # tag-triggered: HEAD == current tag, find the nearest v* tag starting from HEAD^. + # dispatch: HEAD is not a tag, find directly from HEAD. + # If not found (first release), output empty string, git-cliff won't limit the range afterward. - name: Compute previous release tag id: prev_tag shell: bash @@ -96,10 +96,9 @@ jobs: echo "prev=$PREV" >> $GITHUB_OUTPUT echo "Previous tag: ${PREV:-}" - # 用官方推荐的 orhun/git-cliff-action 生成「上个 tag 到 HEAD」的变更说明。 - # --strip header 去掉模板顶部空 header;--tag 把 unreleased 段标记为本次发布版本。 - # 范围参数 `..HEAD` 通过三元表达式注入:有 prev 时限定范围,无 prev 时让 - # git-cliff 输出全部历史(首次发布场景)。 + # Use the officially recommended orhun/git-cliff-action to generate change notes from the previous tag to HEAD. + # --strip header removes the empty header at the top of the template; --tag marks the unreleased section as this release version. + # Range parameter `..HEAD` is injected via a ternary expression: limit range when prev exists, output full history when absent (first release scenario). - name: Generate release notes via git-cliff uses: orhun/git-cliff-action@98c93442bb05a455a77bee982867857ae748eeea # v4.5.1 with: @@ -112,8 +111,8 @@ jobs: OUTPUT: release-notes.md GITHUB_REPO: ${{ github.repository }} - # 把 release_configurations.json 里的一句固定说明拼到 git-cliff 输出之前, - # 形成最终的 release body。后续 publish_release 用 body_path 注入到 GitHub Release。 + # Prepend a fixed description from release_configurations.json before git-cliff output, + # forming the final release body. Subsequent publish_release injects it into GitHub Release via body_path. - name: Assemble final release body shell: bash env: @@ -125,9 +124,9 @@ jobs: echo "$RELEASE_BODY_TEXT" echo if [[ -n "$PREV_TAG" ]]; then - echo "## 自 \`$PREV_TAG\` 以来的更新" + echo "## Updates since \`$PREV_TAG\`" else - echo "## 首发版本变更" + echo "## First release changes" fi echo cat release-notes.md @@ -147,8 +146,8 @@ jobs: name: Build Release (macOS ${{ matrix.dmg_name_suffix }}) runs-on: ${{ matrix.runner }} needs: prepare_metadata - # macos-26 / macos-26-intel(Pro standard)无 sccache,full cargo build + bundle + create-dmg - # 耗时高;Intel 构建历史上超过 180min,这里放宽到 GitHub-hosted job 6h 执行上限。 + # macos-26 / macos-26-intel (Pro standard) has no sccache, full cargo build + bundle + create-dmg + # is time-consuming; Intel builds historically exceed 180min, relaxing here to GitHub-hosted job 6h limit. timeout-minutes: 360 strategy: fail-fast: false @@ -194,16 +193,15 @@ jobs: - name: Install create-dmg run: brew install create-dmg - # 用 --selfsign 走 ad-hoc 签名(无 Apple Developer cert 时 fallback 到 `-`): - # 1. 让 .app 拥有 cdhash → UNUserNotificationCenter 才肯把 Zap 注册到 - # System Settings → Notifications(否则桌面通知静默失效,见 issue #40)。 - # 2. ad-hoc + Hardened Runtime + Debug-Entitlements.plist(disable-library-validation - # + get-task-allow)是合法组合,可在 macos-26 runner 上无凭证完成。 - # 3. notarize 整段被 `script/macos/bundle` 的 CODESIGN=false 分支跳过;DMG 从 - # BUNDLE_DIR 直接 create-dmg,不进 codesign / staple 步骤。 - # 4. 用户首次打开仍会触发 Gatekeeper 的「未识别开发者」对话框(无 Developer ID - # notarize 的固有限制);issue #51 提供的 `xattr -rd com.apple.quarantine` - # 自助方案继续有效。 + # Use --selfsign for ad-hoc signing (fallback to `-` when no Apple Developer cert): + # 1. Give .app a cdhash → UNUserNotificationCenter registers Zap to + # System Settings → Notifications (otherwise desktop notifications silently fail, see issue #40). + # 2. ad-hoc + Hardened Runtime + Debug-Entitlements.plist (disable-library-validation + # + get-task-allow) is a valid combination, can be completed on macos-26 runner without credentials. + # 3. The entire notarize block is skipped by the CODESIGN=false branch in `script/macos/bundle`; DMG is created directly + # from BUNDLE_DIR with create-dmg, no codesign / staple steps. + # 4. Users still see Gatekeeper's "unidentified developer" dialog on first open (inherent limitation of lacking Developer ID + # notarize); the self-help solution `xattr -rd com.apple.quarantine` provided in issue #51 remains effective. - name: Build ${{ matrix.arch }} bundle (ad-hoc signed) id: bundle_app run: | @@ -287,20 +285,20 @@ jobs: name: Build Release (Linux x86_64) runs-on: ubuntu-22.04 needs: prepare_metadata - # 用 22.04 (glibc 2.35) 而不是 ubuntu-latest (24.04, glibc 2.39), - # AppImage 用 host glibc 链接,22.04 产物能在更多旧发行版上跑 - # (Debian 11 / Ubuntu 20.04 + 等)。 + # Use 22.04 (glibc 2.35) instead of ubuntu-latest (24.04, glibc 2.39), + # AppImage links against host glibc, 22.04 artifacts can run on more older distributions + # (Debian 11 / Ubuntu 20.04 and others). timeout-minutes: 180 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ needs.prepare_metadata.outputs.release_branch }} - # GitHub-hosted ubuntu runner 的 / 余量有限,而本 job 先后在同一磁盘产出 - # release-lto(GUI/AppImage/deb/rpm/arch)与 release-cli(musl CLI)两份完整 - # target,叠加 cargo registry / docker 镜像 / zig 工具链后会撑爆磁盘,musl CLI - # 步骤报 "No space left on device"(见 run 27593938453)。先删掉与本构建无关的 - # 预装工具链(Android SDK / .NET / GHC / CodeQL / Swift),腾出 ~25GB。 + # GitHub-hosted ubuntu runner's / free space is limited, and this job sequentially outputs + # release-lto (GUI/AppImage/deb/rpm/arch) and release-cli (musl CLI) two complete + # targets; combined with cargo registry / docker images / zig toolchain, it exhausts the disk, musl CLI + # step reports "No space left on device" (see run 27593938453). First delete pre-installed + # toolchains unrelated to this build (Android SDK / .NET / GHC / CodeQL / Swift), freeing ~25GB. - name: Free up disk space shell: bash run: | @@ -317,7 +315,7 @@ jobs: - name: Install extra dependencies for deb/rpm and build run: | sudo apt-get update - # `rpm` 包提供 /usr/bin/rpmbuild,给后面的 RPM 打包步骤用。 + # `rpm` package provides /usr/bin/rpmbuild, used by the RPM packaging step below. sudo apt-get install -y libdbus-1-dev fakeroot rpm shell: bash @@ -332,8 +330,8 @@ jobs: GIT_RELEASE_TAG: ${{ needs.prepare_metadata.outputs.release_tag }} - name: Install linuxdeploy - # script/linux/install_linuxdeploy 不被 install_build_deps 调用,需手动跑; - # 它把 linuxdeploy AppImage 放到 ~/.local/bin,加到 PATH 里。 + # script/linux/install_linuxdeploy is not called by install_build_deps, needs to run manually; + # it places linuxdeploy AppImage into ~/.local/bin, added to PATH. run: | ./script/linux/install_linuxdeploy echo "$HOME/.local/bin" >> "$GITHUB_PATH" @@ -345,16 +343,16 @@ jobs: shell: bash env: GIT_RELEASE_TAG: ${{ needs.prepare_metadata.outputs.release_tag }} - # ubuntu-22.04 默认有 libfuse2 但 24.04 没有;统一开 EXTRACT_AND_RUN - # 让 linuxdeploy/appimagetool 不依赖 FUSE 内核模块,跨 runner 版本都稳。 + # ubuntu-22.04 has libfuse2 by default but 24.04 doesn't; uniformly enable EXTRACT_AND_RUN + # so linuxdeploy/appimagetool doesn't depend on FUSE kernel module, stable across runner versions. APPIMAGE_EXTRACT_AND_RUN: 1 - # RPM 包:复用上一步 release-lto 已经 strip 过的 `target/release-lto/zap-oss`, - # 不重新 `cargo build`。`--skip-build` 让 `script/bundle` 跳过编译与 strip, - # 直接进入 staging(`bundle_install` → 复制二进制 + resources)+ rpmbuild。 - # 与 deb 同一台 ubuntu-22.04 runner 上跑,共享 host glibc 链接的产物; - # 用 Fedora 26+ / RHEL 8+ 上的 RPM 4.13+ 安装(spec 用了 boolean dep 语法)。 - # continue-on-error: 即便 rpmbuild 失败也不应阻塞 AppImage/deb 已经成功的发布。 + # RPM package: reuse the already-stripped `target/release-lto/zap-oss` from the previous release-lto step, + # no need to `cargo build` again. `--skip-build` lets `script/bundle` skip compilation and stripping, + # go directly to staging (`bundle_install` → copy binary + resources) + rpmbuild. + # Run on the same ubuntu-22.04 runner as deb, sharing artifacts linked against host glibc; + # install with RPM 4.13+ on Fedora 26+ / RHEL 8+ (spec uses boolean dep syntax). + # continue-on-error: rpmbuild failure should not block AppImage/deb already successful release. - name: Build RPM package (reuse release-lto binary) continue-on-error: true run: script/bundle --skip-build --channel "$CHANNEL" --arch x86_64 --packages rpm @@ -362,33 +360,33 @@ jobs: env: GIT_RELEASE_TAG: ${{ needs.prepare_metadata.outputs.release_tag }} - # Arch 包:复用上一步 release-lto 已经 strip 过的 `target/release-lto/zap-oss`, - # 不重新 `cargo build` 主二进制 —— `--skip-build` 让 `script/bundle` 跳过编译与 strip, - # 直接进入 staging(`bundle_install` → 复制二进制 + resources)+ makepkg。 + # Arch package: reuse the already-stripped `target/release-lto/zap-oss` from the previous release-lto step, + # no need to `cargo build` the main binary again — `--skip-build` lets `script/bundle` skip compilation and stripping, + # go directly to staging (`bundle_install` → copy binary + resources) + makepkg. # - # 为什么放容器里:`makepkg` 是 Arch 独有,Ubuntu runner 没有。 - # 容器里只装 Arch 自带的 `rust` 和 `cargo-about`: - # - SKIP_SETTINGS_SCHEMA=1 后,不再跑 `cargo run --bin generate_settings_schema`, - # 所以容器里 rustc 版本跟项目锁定版(1.92.x) 不一致也无所谓。 - # - `cargo about generate` 只读 Cargo.lock 元数据生成第三方 license, - # 不触发任何 Rust 代码编译。 - # makepkg 拒绝 root 运行(EUID==0),必须切到非 root 用户。 - # continue-on-error: Arch 工具链炸了不影响 AppImage/deb 发布。 + # Why in a container: `makepkg` is Arch-specific, not available on Ubuntu runner. + # Only install Arch's own `rust` and `cargo-about` in the container: + # - After SKIP_SETTINGS_SCHEMA=1, no longer run `cargo run --bin generate_settings_schema`, + # so it doesn't matter if rustc version in the container differs from the project's locked version (1.92.x). + # - `cargo about generate` only reads Cargo.lock metadata to generate third-party licenses, + # doesn't trigger any Rust code compilation. + # makepkg refuses to run as root (EUID==0), must switch to a non-root user. + # continue-on-error: Arch toolchain breakage doesn't affect AppImage/deb release. - name: Build Arch package (reuse release-lto binary) continue-on-error: true run: | set -euo pipefail - # 用 host runner 用户的 uid/gid 在容器里建 builder,免去 chown 整棵 - # /work 与 ~/.cargo 的开销(makepkg 拒绝 root,但 uid != 0 即可, - # 用什么名字、什么具体 uid 都行)。 + # Create builder in the container using the host runner user's uid/gid, avoiding the cost of chowning the entire + # /work and ~/.cargo (makepkg refuses root, but uid != 0 is sufficient, + # any name and specific uid works). HOST_UID=$(id -u) HOST_GID=$(id -g) - # 注意:不要把 GITHUB_ACTIONS=true 透传进容器。 - # script/bundle 看到该变量后会 `echo ... >> "$GITHUB_OUTPUT"`, - # 而容器里没有 GITHUB_OUTPUT,redirect 到空字符串触发 `set -e` 退出, - # Arch 包根本就构建不出来(见 run 26153630745)。 - # 我们也不需要该步骤的 step outputs —— 它 continue-on-error, - # packages_dir 由前面 AppImage 步骤的 bundle_app.outputs 提供。 + # Note: do not pass GITHUB_ACTIONS=true into the container. + # script/bundle will `echo ... >> "$GITHUB_OUTPUT"` when it sees that variable, + # but GITHUB_OUTPUT doesn't exist in the container, redirecting to an empty string triggers `set -e` exit, + # Arch package won't build at all (see run 26153630745). + # We also don't need this step's outputs — it continue-on-error, + # packages_dir is provided by the bundle_app.outputs from the AppImage step above. docker run --rm \ -v "$PWD:/work" \ -v "$HOME/.cargo:/cargo-home" \ @@ -398,21 +396,21 @@ jobs: -e HOST_UID="$HOST_UID" \ -e HOST_GID="$HOST_GID" \ archlinux:latest bash -euxc ' - # 预装 PKGBUILD `depends` 里的运行时依赖,避免 makepkg -s - # 在容器内调 `sudo pacman -S` 装缺包(builder 没有免密 sudo, - # 会因为读不到密码退出 8,Arch 包就此漏掉,见 run 26267606877)。 - # `opengl-driver` 在官方仓库里不存在(NixOS 概念 / AUR 虚拟包), - # 单独用 --assume-installed 让依赖检查跳过它。 + # Pre-install runtime dependencies from PKGBUILD `depends`, avoiding makepkg -s + # calling `sudo pacman -S` inside the container to install missing packages (builder has no passwordless sudo, + # exits with 8 due to not reading password, Arch package gets skipped, see run 26267606877). + # `opengl-driver` doesn't exist in the official repo (NixOS concept / AUR virtual package), + # use --assume-installed separately to skip it in dependency checks. pacman -Sy --noconfirm --needed \ base-devel git sudo rust cargo-about \ curl default-cursors fontconfig libegl libx11 libxcb \ libxcursor libxi libxkbcommon-x11 xdg-utils zlib && - # 用 host uid/gid 建用户,挂进来的 /work 与 /cargo-home owner 就是它, - # 不用 chown,直接 sudo -u 即可读写。 + # Create user with host uid/gid, the mounted /work and /cargo-home owner is it, + # no chown needed, can directly read/write via sudo -u. groupadd -g "$HOST_GID" builder 2>/dev/null || true useradd -u "$HOST_UID" -g "$HOST_GID" -m builder - # 显式给 HOME=/home/builder,否则 `sudo -E` 会保留外层 HOME=/root, - # 导致 `git config --global` 试图写 /root/.gitconfig 报 Permission denied。 + # Explicitly set HOME=/home/builder, otherwise `sudo -E` preserves outer HOME=/root, + # causing `git config --global` to try writing /root/.gitconfig and report Permission denied. sudo -u builder -E env \ HOME=/home/builder \ CARGO_HOME=/cargo-home \ @@ -420,10 +418,10 @@ jobs: bash -c " git config --global --add safe.directory /work cd /work - # 依赖已在外层 pacman 装好;告诉 makepkg `opengl-driver` - # 视为已安装,避免它去 pacman -S 一个仓库里没有的包。 - # 另外带 --noconfirm:容器里没透传 GITHUB_ACTIONS, - # bundle_arch 里原生的那个分支不会生效。 + # Dependencies are already installed by pacman at the outer level; tell makepkg to treat `opengl-driver` + # as already installed, avoiding it running `pacman -S` for a package not in the repo. + # Also include --noconfirm: GITHUB_ACTIONS is not passed into the container, + # the native branch in bundle_arch won't take effect. MAKEPKG_EXTRA_ARGS='\''--noconfirm --assume-installed opengl-driver'\'' \ script/bundle --skip-build --channel oss --arch x86_64 --packages arch " @@ -434,37 +432,36 @@ jobs: uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: zap-linux-x86_64 - # packages_dir 是 target/release-lto/bundle/linux,AppImage/deb/Arch 都落在这里 + # packages_dir is target/release-lto/bundle/linux, AppImage/deb/Arch all end up here path: ${{ steps.bundle_app.outputs.packages_dir }} if-no-files-found: error retention-days: 7 - # release-lto 产物(GUI/deb/rpm/arch)已由上一步 "Upload package artifacts" - # 上传为 workflow artifact,本地不再需要;下面的 musl CLI 走独立的 release-cli - # profile,删掉 release-lto 给它腾磁盘,避免 "No space left on device"。 + # release-lto artifacts (GUI/deb/rpm/arch) have already been uploaded by the previous "Upload package artifacts" + # as workflow artifacts, no longer needed locally; the musl CLI below uses independent release-cli + # profile, delete release-lto to free disk for it, avoiding "No space left on device". - name: Reclaim disk before musl CLI build shell: bash run: | rm -rf target/release-lto df -h / - # CLI/remote-server tarball 用静态 musl 链接,产物不依赖宿主 glibc, - # 可在任意 Linux x86_64 主机(含 CentOS 7 / Amazon Linux 2 等旧 glibc - # 以及 Alpine 等 musl 发行版)上运行。GUI AppImage/deb 仍走上面的 - # glibc 链接构建,不受影响。 + # CLI/remote-server tarball uses static musl linking, artifacts don't depend on host glibc, + # can run on any Linux x86_64 host (including CentOS 7 / Amazon Linux 2 with older glibc + # and Alpine and other musl distributions). GUI AppImage/deb still use the + # glibc linking build above, unaffected. # - # 用 `cargo zigbuild` 做 musl 交叉编译:zig 自带完整的 C/C++ musl - # 工具链(`zig cc` / `zig c++`),不依赖宿主的 `musl-gcc` 或手写的 - # `musl-g++` wrapper —— 历史上为 freetype-sys / harfbuzz-sys 等 *-sys - # 依赖准备的「宿主 g++ + musl specs」hack 因此整条移除。 - # 本地 dev 自动构建路径(`ssh_transport.rs`)也优先用 zigbuild,两边 - # 工具链统一。 + # Use `cargo zigbuild` for musl cross-compilation: zig includes complete C/C++ musl + # toolchain (`zig cc` / `zig c++`), doesn't depend on host's `musl-gcc` or hand-written + # `musl-g++` wrapper — historically, the "host g++ + musl specs" hack prepared for *-sys + # dependencies like freetype-sys / harfbuzz-sys is now completely removed. + # Local dev auto-build paths (`ssh_transport.rs`) also prefer zigbuild, toolchain unified on both sides. - name: Install zig + cargo-zigbuild for static musl CLI build run: | set -euo pipefail rustup target add x86_64-unknown-linux-musl - # zig:用官方预编译 tarball,固定版本以保证构建可复现。 - # 0.13.0 与 cargo-zigbuild >= 0.19 兼容。 + # zig: use official pre-compiled tarball, fixed version to ensure reproducible builds. + # 0.13.0 is compatible with cargo-zigbuild >= 0.19. ZIG_VERSION="0.13.0" ZIG_DIR="$HOME/.local/zig-${ZIG_VERSION}" if [ ! -x "${ZIG_DIR}/zig" ]; then @@ -473,7 +470,7 @@ jobs: | tar -xJ -C "${ZIG_DIR}" --strip-components=1 fi echo "${ZIG_DIR}" >> "$GITHUB_PATH" - # cargo-zigbuild:走 cargo install,首次安装在缓存里之后会复用。 + # cargo-zigbuild: use cargo install, reuse from cache after first install. cargo install --locked cargo-zigbuild --version ^0.19 shell: bash @@ -486,13 +483,13 @@ jobs: env: GIT_RELEASE_TAG: ${{ needs.prepare_metadata.outputs.release_tag }} APPIMAGE_EXTRACT_AND_RUN: 1 - # 让 script/linux/bundle 把 `cargo build` 切到 `cargo zigbuild`。 + # Let script/linux/bundle switch `cargo build` to `cargo zigbuild`. USE_ZIGBUILD: "true" - # libc(musl)被 +crt-static 静态链接进二进制 —— 这是让产物摆脱 - # 宿主 glibc 下限的关键。`libdbus-sys` 现在通过 keyring 的 - # `vendored` feature 从源码内嵌编译(见 Cargo.toml),不再用 pkg-config - # 探测宿主 `dbus-1`;`PKG_CONFIG_ALLOW_CROSS=1` 保留作兜底, - # 万一某个新引入的 -sys crate 还在用 pkg-config 也不会卡。 + # libc (musl) is statically linked into the binary with +crt-static — this is key to freeing artifacts + # from host glibc dependency. `libdbus-sys` now embeds from source via keyring's + # `vendored` feature (see Cargo.toml), no longer uses pkg-config + # to detect host `dbus-1`; `PKG_CONFIG_ALLOW_CROSS=1` is kept as fallback, + # in case a newly introduced -sys crate still uses pkg-config. PKG_CONFIG_ALLOW_CROSS: 1 - name: Package CLI tarball @@ -514,7 +511,7 @@ jobs: publish_release: name: Publish GitHub Release runs-on: ubuntu-latest - # 必须等所有平台都 build 成功才发布;任一失败 → 此 job 跳过 → 不会出现空 Release。 + # Must wait for all platforms to build successfully before publishing; if any fails → this job is skipped → no empty Release. needs: - prepare_metadata - release_macos @@ -527,8 +524,8 @@ jobs: path: dist merge-multiple: true - # release-body artifact 是给 GitHub Release 描述用的,不是要挂到 Release 资产列表的产物。 - # 提前从 dist/ 移到独立路径,避免 `files: dist/*` 把它当成 asset 上传。 + # release-body artifact is for GitHub Release description, not an artifact to attach to Release assets. + # Move it early from dist/ to separate path, avoid `files: dist/*` treating it as asset upload. - name: Extract release body from artifacts shell: bash run: | @@ -552,10 +549,10 @@ jobs: files: dist/* token: ${{ secrets.GITHUB_TOKEN }} - # 发布成功后立刻删本次 run 的 workflow artifact,释放 Actions storage 配额。 - # 安装包已经挂到 GitHub Release(独立存储,不占 Actions storage), - # workflow artifact 只是 build job → publish job 的中间传递管道,留着没用。 - # 仅在 Release 创建成功后执行(if: success()),失败保留 artifact 方便复盘。 + # Delete this run's workflow artifacts immediately after successful release to free up Actions storage quota. + # Installers are already attached to GitHub Release (independent storage, doesn't consume Actions storage), + # workflow artifacts are just an intermediary pipeline from build job → publish job, no point keeping them. + # Only execute after Release creation succeeds (if: success()), keep artifacts on failure for review. - name: Delete workflow artifacts to free Actions storage if: success() env: diff --git a/Cargo.toml b/Cargo.toml index f12769a08e..2228815980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ warp_ssh_manager = { path = "crates/warp_ssh_manager" } warp_terminal = { path = "crates/warp_terminal" } warp_util = { path = "crates/warp_util" } zap_sync = { path = "crates/zap_sync" } +zaplex_cockpit = { path = "crates/zaplex_cockpit" } zaplex_remote_session = { path = "crates/zaplex_remote_session" } warp_web_event_bus = { path = "crates/warp_web_event_bus" } warpui = { path = "crates/warpui" } diff --git a/app/Cargo.toml b/app/Cargo.toml index 6fac05d849..02ba591709 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -1,6 +1,6 @@ [package] -authors = ["Zap Team"] -default-run = "zap-oss" +authors = ["Zaplex Team"] +default-run = "zaplex" description = "The local-first agentic terminal" edition = "2021" autobins = false @@ -13,12 +13,12 @@ license.workspace = true name = "warp" path = "src/lib.rs" -# Zap 只保留两个 bin:打包用的 zap-oss,以及构建期工具 generate_settings_schema。 -# 原 warp 多渠道发布的 dev/stable/preview/local bin 已删除。 +# Zaplex keeps only two binaries: zaplex for packaging, and generate_settings_schema for build-time tools. +# Original warp multi-channel publish binaries (dev/stable/preview/local) have been removed. [[bin]] -name = "zap-oss" -path = "src/bin/zap_oss.rs" +name = "zaplex" +path = "src/bin/zaplex.rs" test = false [[bin]] @@ -87,10 +87,10 @@ genai.workspace = true getset.workspace = true handlebars.workspace = true hex.workspace = true -# Zap BYOP webfetch:HTML→Markdown(Turndown 的 Rust 端口)。 +# Zaplex BYOP webfetch: HTML→Markdown (Rust port of Turndown). htmd.workspace = true -# BYOP system prompt 模板渲染。轻量(运行时引擎),Jinja2 子集。 -# 默认 features(builtins / serde / multi_template / macros / debug ...)够用。 +# BYOP system prompt template rendering. Lightweight runtime engine, Jinja2 subset. +# Default features (builtins / serde / multi_template / macros / debug ...) are sufficient. minijinja = "2" http.workspace = true http_client.workspace = true @@ -234,6 +234,7 @@ warp_isolation_platform.workspace = true warp_ripgrep.workspace = true warp_managed_secrets.workspace = true zap_sync = { path = "../crates/zap_sync" } +zaplex_cockpit.workspace = true zaplex_remote_session = { workspace = true, features = ["server"] } [target.'cfg(target_os = "macos")'.dependencies] @@ -558,7 +559,7 @@ default = [ "revert_to_checkpoints", "rewind_slash_command", "hoa_code_review", - "warpify_footer", + "zaplexify_footer", "agent_toolbar_editor", "configurable_toolbar", "transfer_control_tool", @@ -829,20 +830,20 @@ open_code_notifications = [] transfer_control_tool = [] solo_user_byok = [] configurable_toolbar = [] -warpify_footer = [] +zaplexify_footer = [] hoa_onboarding_flow = [] git_operations_in_code_review = [] hoa_remote_control = [] codex_notifications = [] configurable_context_window = [] -[package.metadata.bundle.bin.zap-oss] +[package.metadata.bundle.bin.zaplex] category = "public.app-category.developer-tools" -copyright = "© 2025-2026, Zap" -identifier = "dev.zap.Zap" -name = "Zap" +copyright = "© 2025-2026, Zaplex" +identifier = "dev.zaplex.Zaplex" +name = "Zaplex" resources = ["assets/onboarding"] -icon = ["channels/oss/icon/padded/512x512.png", "channels/oss/icon/padded/icon.ico"] +icon = ["channels/oss/icon/zaplex.icns", "channels/oss/icon/padded/icon.ico"] short_description = "The open-source, local-first agentic terminal." [package.metadata.cargo-udeps.ignore] diff --git a/app/assets/bundled/bootstrap/bash.sh b/app/assets/bundled/bootstrap/bash.sh index 2556841a0a..65141704cd 100644 --- a/app/assets/bundled/bootstrap/bash.sh +++ b/app/assets/bundled/bootstrap/bash.sh @@ -434,10 +434,10 @@ # we notify the terminal to input these characters in. For the remote case, # we actually start in an interactive non-login shell (i.e. it runs ~/.bashrc), # but it gets replaced by a new shell that we fully control. - read -r -d '' WARP_BOOTSTRAP_VAR << 'EOM' + read -r -d '' ZAPLEX_BOOTSTRAP_VAR << 'EOM' #include bundled/bootstrap/bash_body.sh EOM # We need to restore the line editor before we evaluate the bootstrap logic # or everything freezes up stty sane - eval "$WARP_BOOTSTRAP_VAR"; unset WARP_BOOTSTRAP_VAR + eval "$ZAPLEX_BOOTSTRAP_VAR"; unset ZAPLEX_BOOTSTRAP_VAR diff --git a/app/assets/bundled/bootstrap/bash_body.sh b/app/assets/bundled/bootstrap/bash_body.sh index 2d75ee390c..df2d6c7aea 100644 --- a/app/assets/bundled/bootstrap/bash_body.sh +++ b/app/assets/bundled/bootstrap/bash_body.sh @@ -1,10 +1,10 @@ -# Note that WARP_SESSION_ID is expected to have been set when executing commands to +# Note that ZAPLEX_SESSION_ID is expected to have been set when executing commands to # emit the InitShell payload, which includes the session ID. # # Throughout, command -p is used to call external binaries. command -p resolves the # given command using the system default $PATH, which ensures the shells can locate # the corresponding binaries even if the user has a clobbered value of $PATH. -if [ -z "$WARP_BOOTSTRAPPED" ]; then +if [ -z "$ZAPLEX_BOOTSTRAPPED" ]; then # Byte sequence used to signal the start of a DCS. ([0x1b, 0x50, 0x24] which # maps to , P, $ in ASCII.) DCS_START="$(printf '\eP$')" @@ -37,9 +37,9 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Attempt to cd to the desired initial working directory, swallowing any # errors. If this fails, the user will end up in their home directory. - if [[ ! -z "$WARP_INITIAL_WORKING_DIR" ]]; then - cd "$WARP_INITIAL_WORKING_DIR" >/dev/null 2>&1 - unset WARP_INITIAL_WORKING_DIR + if [[ ! -z "$ZAPLEX_INITIAL_WORKING_DIR" ]]; then + cd "$ZAPLEX_INITIAL_WORKING_DIR" >/dev/null 2>&1 + unset ZAPLEX_INITIAL_WORKING_DIR fi # We configure history to `ignorespace` to avoid leaking our bootstrap script @@ -53,15 +53,15 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # The temporary files used to track generator PIDs. We'll fill these in later, # if we execute any generator commands. - _WARP_GENERATOR_PIDS_STARTED_TMP_FILE="" - _WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE="" + _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE="" + _ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE="" # Make sure we delete generator PID files when the shell exits, if they exist. __warp_generator_pid_file_cleanup() { - if [[ -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then - command -p rm $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + if [[ -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then + command -p rm $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE fi - if [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - command -p rm $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + if [[ -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + command -p rm $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE fi } trap __warp_generator_pid_file_cleanup EXIT @@ -74,8 +74,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # one of the bytes in JSON is 9c (ST) or other (CAN, SUB, ESC). encoded_message=$(warp_hex_encode_string "$1") # We send the InitShell hook via OSCs when on WSL or MSYS2 or SSH from Windows and via DCSs otherwise. - # Note that $WARP_USING_WINDOWS_CON_PTY is set in the init shell script. - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then + # Note that $ZAPLEX_USING_WINDOWS_CON_PTY is set in the init shell script. + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf $OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR$encoded_message$OSC_END else printf $DCS_START$DCS_JSON_MARKER$encoded_message$DCS_END @@ -89,7 +89,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # waits on orphaned slave channels when the user ends their interactive # session. # - # Only relevant for remote SSH shells. WARP_IS_SSH is exported to "1" + # Only relevant for remote SSH shells. ZAPLEX_IS_SSH is exported to "1" # by `warp_ssh_helper` on the remote side of a Warp-managed SSH session # and is unset everywhere else (local shells, subshells, docker # sandboxes, etc.), so the hook only fires where a remote-server-proxy @@ -97,11 +97,11 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # # Installed after warp_send_json_message is defined so the handler is # callable the moment the trap is registered. - if [[ "$WARP_IS_SSH" == "1" ]]; then + if [[ "$ZAPLEX_IS_SSH" == "1" ]]; then __warp_emit_exit_shell() { - if [[ -n "$WARP_SESSION_ID" ]]; then + if [[ -n "$ZAPLEX_SESSION_ID" ]]; then warp_send_json_message \ - "{\"hook\": \"ExitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID}}" + "{\"hook\": \"ExitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID}}" fi } # Bash allows only one handler per signal, so compose with the @@ -115,7 +115,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then fi warp_maybe_send_reset_grid_osc () { - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf $RESET_GRID_OSC fi } @@ -202,7 +202,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then } # Runs the given command in the background, records its PID in - # _WARP_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when + # _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when # the job is completed. _warp_run_generator_command_internal() { # $@ must be double-quoted to prevent word-splitting, which would cause the given command to @@ -211,7 +211,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then _warp_execute_command "$@" & # $! contains the PID of the most recently backgrounded command. local pid=$! - echo $pid >> $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + echo $pid >> $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE wait $pid 2> /dev/null # If the exit code of the backgrounded _warp_execute_command process is non-zero, @@ -228,8 +228,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # # The completed generator PIDs file may not exist if this generator was (by # error) left running/not cancelled properly in warp_preexec. - if [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - echo $pid >> $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + if [[ -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + echo $pid >> $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE fi } @@ -245,14 +245,14 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then warp_run_generator_command() { # Setting this environment variable prevents warp_precmd from emitting the # 'Block started' hook to the Rust app. - _WARP_GENERATOR_COMMAND=1 + _ZAPLEX_GENERATOR_COMMAND=1 # Ensure the started and completed generator PID files exist. - if [[ -z $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE || ! -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then - _WARP_GENERATOR_PIDS_STARTED_TMP_FILE="$(command -p mktemp)" + if [[ -z $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE || ! -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then + _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE="$(command -p mktemp)" fi - if [[ -z $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE || ! -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - _WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE="$(command -p mktemp)" + if [[ -z $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE || ! -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + _ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE="$(command -p mktemp)" fi # To minimize latency and prevent the user from being blocked from entering a command, @@ -277,7 +277,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # history to do so. This means that $1 is not the correct command if the executed command is ignored # by history (e.g. via $HISTCONTROL or $HISTIGNORE); for example, all in-band generators are ignored # by history. - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "Preexec" warp_send_hook_kv_pair "command" "$BASH_COMMAND" warp_send_hook_via_kv_pairs_end @@ -291,14 +291,14 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Since we did not early-return above, this hook is for a user-entered # command. Kill ongoing generator jobs so their output does not interfere # with the user command's output. - if [[ "$BASH_COMMAND" != warp_run_generator_command* ]] && [[ -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]] && [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]] + if [[ "$BASH_COMMAND" != warp_run_generator_command* ]] && [[ -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]] && [[ -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]] then # Read PIDs from the started generators tmp file that are not present in # the completed generators tmp file into a bash array. # # The logic used to be the following: # - # pids=($(command -p comm -23 $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE)) + # pids=($(command -p comm -23 $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE)) # # However, that requires that the files are sorted, which we do not enforce (the OS can assign PIDs # in any order). While we could sort the files and then compare them, the files are expected to be @@ -307,7 +307,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then completed_pids=() while IFS= read -r pid; do completed_pids+=("$pid") - done < $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + done < $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE pids=() while IFS= read -r pid; do @@ -321,7 +321,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then if (( found == 0 )); then pids+=("$pid") fi - done < $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + done < $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE # If the array is not empty, kill the ongoing pids. if [[ ! -z $pids ]]; then @@ -336,7 +336,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Set terminal window and tab title to the same title value. Note that for values longer than 25 # characters, we truncate the title and prepend "..". # Usage warp_title "title" - # Users can disable the auto title if they chose to by setting WARP_DISABLE_AUTO_TITLE. + # Users can disable the auto title if they chose to by setting ZAPLEX_DISABLE_AUTO_TITLE. warp_title () { DISABLE_AUTO_TITLE="1" @@ -356,8 +356,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Runs before executing the command warp_set_title_idle_on_precmd () { - # If the user wants to set the title themselves, they can set the WARP_DISABLE_AUTO_TITLE flag. - if [ ! -z "$WARP_DISABLE_AUTO_TITLE" ]; then + # If the user wants to set the title themselves, they can set the ZAPLEX_DISABLE_AUTO_TITLE flag. + if [ ! -z "$ZAPLEX_DISABLE_AUTO_TITLE" ]; then return fi @@ -370,7 +370,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then new_home='~' bash_term_tab_title="${PWD/#$HOME/$new_home}" - if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then + if [[ $ZAPLEX_IS_LOCAL_SHELL_SESSION == "1" ]]; then warp_title "$bash_term_tab_title" else bash_term_tab_title_remote="${HOSTNAME%%.*}:$bash_term_tab_title" @@ -380,8 +380,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Runs before executing the command warp_set_title_active_on_preexec () { - # If the user wants to set the title themselves, they can set the WARP_DISABLE_AUTO_TITLE flag. - if [ ! -z "$WARP_DISABLE_AUTO_TITLE" ]; then + # If the user wants to set the title themselves, they can set the ZAPLEX_DISABLE_AUTO_TITLE flag. + if [ ! -z "$ZAPLEX_DISABLE_AUTO_TITLE" ]; then return fi @@ -434,34 +434,34 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # executed within this block instead of the actual last # command that was run. local exit_code=$? - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "CommandFinished" warp_send_hook_kv_pair "exit_code" "$exit_code" - warp_send_hook_kv_pair "next_block_id" "precmd-$WARP_SESSION_ID-$((block_id++))" + warp_send_hook_kv_pair "next_block_id" "precmd-$ZAPLEX_SESSION_ID-$((block_id++))" warp_send_hook_via_kv_pairs_end else - warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$((block_id++))\"}}" + warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$ZAPLEX_SESSION_ID-$((block_id++))\"}}" fi warp_maybe_send_reset_grid_osc if [[ $PS1 == "" ]]; then # Use the saved PS1, if we've already unset it (due to active Warp prompt). - WARP_PS1="$SAVED_PS1" + ZAPLEX_PS1="$SAVED_PS1" else # If we haven't unset it yet, then we can use the current PS1 value. - WARP_PS1="$PS1" + ZAPLEX_PS1="$PS1" fi # If this is being called for a generator command, short circuit and send an unpopulated # precmd payload (except for pwd), since we don't re-render the prompt after generator commands # are run. - if [ ! -z $_WARP_GENERATOR_COMMAND ]; then + if [ ! -z $_ZAPLEX_GENERATOR_COMMAND ]; then # Restore the user's precmd_functions, since they were un-registered prior to executing # the generator. precmd_functions=(${_USER_PRECMD_FUNCTIONS[@]}) - unset _WARP_GENERATOR_COMMAND + unset _ZAPLEX_GENERATOR_COMMAND warp_send_json_message "{\"hook\": \"Precmd\", \"value\": { \"pwd\": \"\", \"ps1\": \"\", @@ -470,30 +470,30 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then \"virtual_env\": \"\", \"conda_env\": \"\", \"node_version\": \"\", - \"session_id\": $WARP_SESSION_ID, + \"session_id\": $ZAPLEX_SESSION_ID, \"is_after_in_band_command\": true }}" return 0 fi # If the files for tracking generator PIDs exist, clear them. - if [[ -n $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE && -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then - echo "" > $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + if [[ -n $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE && -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then + echo "" > $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE fi - if [[ -n $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE && -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - echo "" > $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + if [[ -n $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE && -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + echo "" > $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE fi - if [[ -z $WARP_INPUT_REPORTING_SUPPORTED ]]; then - WARP_INPUT_REPORTING_SUPPORTED=$(warp_input_reporting_supported) + if [[ -z $ZAPLEX_INPUT_REPORTING_SUPPORTED ]]; then + ZAPLEX_INPUT_REPORTING_SUPPORTED=$(warp_input_reporting_supported) fi # If we haven't already, cache information about supported features. - if [[ -z $WARP_PS1_EXPANSION_SUPPORTED ]]; then - WARP_PS1_EXPANSION_SUPPORTED=$(warp_ps1_expanding_supported) + if [[ -z $ZAPLEX_PS1_EXPANSION_SUPPORTED ]]; then + ZAPLEX_PS1_EXPANSION_SUPPORTED=$(warp_ps1_expanding_supported) fi - if [[ $WARP_PS1_EXPANSION_SUPPORTED == "1" ]]; then + if [[ $ZAPLEX_PS1_EXPANSION_SUPPORTED == "1" ]]; then # When evaluating the PS1, we want to ensure that it's aware of the last exit code. # Since we captured it already and executed multiple other commands, the actual # last exit code has changed. So before the evaluation, we want to trick the shell @@ -502,17 +502,17 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then return $1 } exit_code_hack $exit_code - deref_ps1=${WARP_PS1@P} + deref_ps1=${ZAPLEX_PS1@P} else # Tricking the shell into rendering the prompt # Note that in more modern versions of bash we could use ${PS1@P} to achieve the same, # but MacOS comes by default with a much older version of bash, and we want to be compatible. - deref_ps1=$(echo -e "\n" | PS1="$WARP_PS1" BASH_SILENCE_DEPRECATION_WARNING=1 "$BASH" --norc -i 2>&1 | command -p head -2 | command -p tail -1) + deref_ps1=$(echo -e "\n" | PS1="$ZAPLEX_PS1" BASH_SILENCE_DEPRECATION_WARNING=1 "$BASH" --norc -i 2>&1 | command -p head -2 | command -p tail -1) fi # Escaped PS1 variable local escaped_ps1 - if [ "$WARP_IN_MSYS2" = false ]; then + if [ "$ZAPLEX_IN_MSYS2" = false ]; then escaped_ps1=$(warp_escape_ps1 "$(echo "$deref_ps1")") fi @@ -527,7 +527,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Reset the report-input binding in case the user's bashrc modified it. # This is arbitrarily bound to ESC-i in all supported shells ("i" for input). - if [[ $WARP_INPUT_REPORTING_SUPPORTED == "1" ]]; then + if [[ $ZAPLEX_INPUT_REPORTING_SUPPORTED == "1" ]]; then bind -r '"\ei"' bind -x '"\ei":"warp_report_input"' fi @@ -544,7 +544,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then bind -x '"\ew":"warp_change_prompt_modes_to_warp_prompt"' local escaped_pwd - if [ "$WARP_IN_MSYS2" = false ]; then + if [ "$ZAPLEX_IN_MSYS2" = false ]; then if [ -n "$WSL_DISTRO_NAME" ]; then # In WSL, avoid symlinks b/c on Windows `std::fs` is unable to resolve symlink inside WSL containers. escaped_pwd=$(warp_escape_json "$(pwd -P)") @@ -565,17 +565,17 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # blocks created during the bootstrap process don't have visible # prompts, and we don't want to invoke `git` before we've sourced the # user's rcfiles and have a fully-populated PATH. - if [[ -n "$WARP_BOOTSTRAPPED" ]]; then - if [[ -n "$VIRTUAL_ENV" ]] && [ "$WARP_IN_MSYS2" = false ]; then + if [[ -n "$ZAPLEX_BOOTSTRAPPED" ]]; then + if [[ -n "$VIRTUAL_ENV" ]] && [ "$ZAPLEX_IN_MSYS2" = false ]; then escaped_virtual_env=$(warp_escape_json "$VIRTUAL_ENV") fi - if [[ -n "$CONDA_DEFAULT_ENV" ]] && [ "$WARP_IN_MSYS2" = false ]; then + if [[ -n "$CONDA_DEFAULT_ENV" ]] && [ "$ZAPLEX_IN_MSYS2" = false ]; then escaped_conda_env=$(warp_escape_json "$CONDA_DEFAULT_ENV") fi # Get Node.js version if node is available and we're in a Node.js project - if command -v node > /dev/null 2>&1 && [ "$WARP_IN_MSYS2" = false ]; then + if command -v node > /dev/null 2>&1 && [ "$ZAPLEX_IN_MSYS2" = false ]; then # Check for package.json in current directory and parent directories local current_dir="$PWD" local found_package_json=false @@ -620,7 +620,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # The git branch the user is on, or the git commit hash if they're not on a branch. git_head="${git_branch:-$(warp_git rev-parse --short HEAD 2> /dev/null)}" fi - if [ "$WARP_IN_MSYS2" = false ]; then + if [ "$ZAPLEX_IN_MSYS2" = false ]; then escaped_git_head=$(warp_escape_json "$git_head") escaped_git_branch=$(warp_escape_json "$git_branch") fi @@ -634,11 +634,11 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # as JS string literals of the form \uHEX, and will include # ctrl characters (like ESC) in the json, which will cause a JSON # parse error. - # Note WARP_SESSION_ID doesn't need to be escaped since it's a number + # Note ZAPLEX_SESSION_ID doesn't need to be escaped since it's a number # We also pass the shell's notion of `honor_ps1` to ensure it's synced correctly on the Warp-side for prompt handling. # This is passed as a "real boolean" via the JSON payload (string interpolated into JSON string below). local honor_ps1 - if [[ "$WARP_HONOR_PS1" == "1" ]]; then + if [[ "$ZAPLEX_HONOR_PS1" == "1" ]]; then honor_ps1="true" # The Warp prompt preview can be rendered using the active prompt in this case (which uses prompt markers). escaped_ps1="" @@ -647,7 +647,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then honor_ps1="false" fi # We send the escaped PS1, if we are in active Warp prompt mode, for prompt preview rendering (note the shell's PS1 is unset in this case). - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "Precmd" warp_send_hook_kv_pair "pwd" "$PWD" warp_send_hook_kv_pair_escaped "ps1" "$deref_ps1" @@ -658,7 +658,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then warp_send_hook_kv_pair "virtual_env" "$VIRTUAL_ENV" warp_send_hook_kv_pair "conda_env" "$CONDA_DEFAULT_ENV" warp_send_hook_kv_pair "node_version" "$node_version" - warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID" + warp_send_hook_kv_pair "session_id" "$ZAPLEX_SESSION_ID" warp_send_hook_via_kv_pairs_end else local escaped_json="{\"hook\": \"Precmd\", \"value\": { @@ -671,7 +671,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then \"virtual_env\": \"$escaped_virtual_env\", \"conda_env\": \"$escaped_conda_env\", \"node_version\": \"$escaped_node_version\", - \"session_id\": $WARP_SESSION_ID + \"session_id\": $ZAPLEX_SESSION_ID }}" warp_send_json_message "$escaped_json" fi @@ -758,7 +758,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Report the current input buffer contents to Warp. This only works correctly # if `warp_input_reporting_supported` returns "1". warp_report_input () { - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "InputBuffer" warp_send_hook_kv_pair "buffer" "$READLINE_LINE" warp_send_hook_via_kv_pairs_end @@ -779,7 +779,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/6/diffs for details. local prompt_prefix=$'\e]133;A\a' local prompt_suffix=$'\e]133;B\a' - if [[ "$WARP_HONOR_PS1" != "1" ]] && [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then + if [[ "$ZAPLEX_HONOR_PS1" != "1" ]] && [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then local suffix="$prompt_suffix$RESET_GRID_OSC" else local suffix="$prompt_suffix" @@ -797,7 +797,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # product behavior of Warp prompt switches only taking effect in new sessions. # Certain prompt plugins can reset the prompt to a non-empty value, after we've initially unset it. # Confirm that it is unset, if using built-in Warp prompt (update prompt vars is forced to run as the last precmd fn). - if [[ "$WARP_HONOR_PS1" != "1" ]]; then + if [[ "$ZAPLEX_HONOR_PS1" != "1" ]]; then if [[ "$PS1" != "" ]]; then # If the PS1 has its original value, then we save it in SAVED_PS1 so we can restore to this value, if we were to unset it for # the Warp prompt case, but the user wants to switch back to PS1 later. @@ -828,7 +828,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then fi # Unset the PS1, if we are using the Warp prompt. - if [[ "$WARP_HONOR_PS1" != "1" ]]; then + if [[ "$ZAPLEX_HONOR_PS1" != "1" ]]; then PS1="" # Otherwise, if we are using the PS1, we use the normal prompt markers. else @@ -863,30 +863,30 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then precmd_functions+=("warp_update_prompt_vars") } - # Changes the WARP_HONOR_PS1 variable to 1, to indicate we want to use the PS1. Restores + # Changes the ZAPLEX_HONOR_PS1 variable to 1, to indicate we want to use the PS1. Restores # the original PS1 value (which we unset for Warp prompt) and calls warp_update_prompt_vars # to refresh the prompt. Note that we use an "empty block" workaround to achieve instant # prompt switching in bash, since there is no built-in methods to repaint the prompt, unlike # Zsh/fish. function warp_change_prompt_modes_to_ps1() { PS1="$SAVED_PS1" - WARP_HONOR_PS1="1" + ZAPLEX_HONOR_PS1="1" warp_update_prompt_vars } - # Changes the WARP_HONOR_PS1 variable to 0, to indicate we want to use the Warp prompt. Calls + # Changes the ZAPLEX_HONOR_PS1 variable to 0, to indicate we want to use the Warp prompt. Calls # warp_update_prompt_vars to refresh the prompt (note the PS1 will be unset in this logic). # Note that we use an "empty block" workaround to achieve instant prompt switching in bash, # since there is no built-in methods to repaint the prompt, unlike Zsh/fish. function warp_change_prompt_modes_to_warp_prompt() { - WARP_HONOR_PS1="0" + ZAPLEX_HONOR_PS1="0" warp_update_prompt_vars } function clear() { - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "Clear" warp_send_hook_via_kv_pairs_end else @@ -896,7 +896,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then function warp_finish_update { local update_id="$1" - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "FinishUpdate" warp_send_hook_kv_pair "update_id" "$update_id" warp_send_hook_via_kv_pairs_end @@ -931,7 +931,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # recursive SSH sessions. - if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then + if [[ $ZAPLEX_IS_LOCAL_SHELL_SESSION == "1" ]]; then # This helper function determines whether the user's ssh arguments imply # creation of a non-interactive session or otherwise would conflict with # our SSH wrapper. Returns 0 for an interactive session; >0 otherwise. @@ -976,7 +976,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters # We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte # and use printf to convert back to plaintext - local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n') + local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM"; ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; ZAPLEX_HONOR_PS1='$ZAPLEX_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n') # Keep remote commands up-to-date with shell.rs & bash.sh. # Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be @@ -985,19 +985,19 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then # determine what shell is the login shell on the remote machine. We perform a preliminary check to see if # the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't # support. - command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \ + command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$ZAPLEX_SESSION_ID \ -t "${@:1}" \ " -export TERM_PROGRAM='WarpTerminal' +export TERM_PROGRAM='ZaplexTerminal' # Mark the remote side of a Warp-managed SSH session so the bootstrap # body can distinguish it from local shells. Used to gate the ExitShell # hook which tears down the remote-server-proxy subprocess. -export WARP_IS_SSH='1' -test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION' +export ZAPLEX_IS_SSH='1' +test -n '$ZAPLEX_CLIENT_VERSION' && export ZAPLEX_CLIENT_VERSION='$ZAPLEX_CLIENT_VERSION' # Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on). -test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION' +test -n '$ZAPLEX_CLI_AGENT_PROTOCOL_VERSION' && export ZAPLEX_CLI_AGENT_PROTOCOL_VERSION='$ZAPLEX_CLI_AGENT_PROTOCOL_VERSION' -hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'" +hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$ZAPLEX_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'" printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'" if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then @@ -1032,30 +1032,30 @@ case "'${SHELL##*/}'" in command -p stty raw HISTCONTROL=ignorespace HISTIGNORE=" *" - WARP_SESSION_ID="$(command -p date +%s)$RANDOM" - WARP_HONOR_PS1="'$WARP_HONOR_PS1'" + ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" + ZAPLEX_HONOR_PS1="'$ZAPLEX_HONOR_PS1'" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) _user=$(command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER) - _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'" - WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ - if [[ "'$OS'" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi + _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'" + ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ + if [[ "'$OS'" == Windows_NT ]]; then ZAPLEX_IN_MSYS2=true; else ZAPLEX_IN_MSYS2=false; fi printf '\''"'\e]9278;d;%s\x07'"'\'' \""'$_msg'"\"') unset _hostname _user _msg ;; - zsh) WARP_TMP_DIR="'$(command -p mktemp -d warptmp.XXXXXX)'" + zsh) ZAPLEX_TMP_DIR="'$(command -p mktemp -d warptmp.XXXXXX)'" local ZSH_ENV_SCRIPT='$zsh_env_script' if [[ "'$?'" == 0 ]]; then if command -pv xxd >/dev/null 2>&1; then - echo "'$ZSH_ENV_SCRIPT'" | command -p xxd -p -r > "'$WARP_TMP_DIR'"/.zshenv + echo "'$ZSH_ENV_SCRIPT'" | command -p xxd -p -r > "'$ZAPLEX_TMP_DIR'"/.zshenv else for i in {0..\$((\${#ZSH_ENV_SCRIPT} - 1))..2}; do builtin printf "'"\x${ZSH_ENV_SCRIPT:$i:2}"'" - done > "'$WARP_TMP_DIR'"/.zshenv + done > "'$ZAPLEX_TMP_DIR'"/.zshenv fi else echo \"Failed to bootstrap warp. Continuing with a non-bootstrapped shell.\" fi -TMPPREFIX="'$HOME/.zshtmp-'" WARP_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" ZDOTDIR="'$WARP_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE +TMPPREFIX="'$HOME/.zshtmp-'" ZAPLEX_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" ZDOTDIR="'$ZAPLEX_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE ;; esac " @@ -1066,10 +1066,10 @@ esac warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}" # If the SSH wrapper is not enabled for this session, don't use it. - if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then - local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="" - if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then - TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x" + if [ "$ZAPLEX_USE_SSH_WRAPPER" = "1" ]; then + local TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE="" + if [[ "$ZAPLEX_SHELL_DEBUG_MODE" == "1" ]]; then + TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE="-x" fi warp_ssh_helper "$@" else @@ -1105,7 +1105,7 @@ esac # Do other shell startup first so we can ensure Warp goes last. # # If this is a subshell, the user and system RC files have already been sourced. - if [[ -z $WARP_IS_SUBSHELL ]]; then + if [[ -z $ZAPLEX_IS_SUBSHELL ]]; then # Make sure we force the locale used for number formatting to "C", to avoid # issues in locales that use a comma as the decimal separator. rcfiles_start_time="$(LC_ALL="C"; echo $EPOCHREALTIME)" @@ -1140,10 +1140,10 @@ esac # it would have been if we hadn't set an initial value. # # - if [[ $HISTFILESIZE == $WARP_INITIAL_HISTFILESIZE ]]; then + if [[ $HISTFILESIZE == $ZAPLEX_INITIAL_HISTFILESIZE ]]; then unset HISTFILESIZE fi - unset WARP_INITIAL_HISTFILESIZE + unset ZAPLEX_INITIAL_HISTFILESIZE # Save the value of HISTCONTROL as it existed just after reading the user's # rcfiles. @@ -1216,11 +1216,11 @@ esac fi ## ----- Warp initialization ----- - # Append additional PATH entries if provided via WARP_PATH_APPEND. This is after the user's RC + # Append additional PATH entries if provided via ZAPLEX_PATH_APPEND. This is after the user's RC # files are sourced in case they reset PATH (/etc/profile on Debian does this, for example). - if [[ ! -z "$WARP_PATH_APPEND" ]]; then - export PATH="$PATH:$WARP_PATH_APPEND" - unset WARP_PATH_APPEND + if [[ ! -z "$ZAPLEX_PATH_APPEND" ]]; then + export PATH="$PATH:$ZAPLEX_PATH_APPEND" + unset ZAPLEX_PATH_APPEND fi # Read through shell options to determine if the user has enabled vi mode. @@ -1261,7 +1261,7 @@ esac precmd_functions+=(user_prompt_command) fi - WARP_BOOTSTRAPPED=1 + ZAPLEX_BOOTSTRAPPED=1 warp_update_prompt_vars @@ -1276,7 +1276,7 @@ esac local function_names="`compgen -A function`" local builtins="`compgen -b`" local keywords="`compgen -k`" - if [ "$WARP_IN_MSYS2" = false ]; then + if [ "$ZAPLEX_IN_MSYS2" = false ]; then # Note that for now we don't support dynamically changing HISTFILE within a session. local escaped_histfile="$(warp_escape_json "$HISTFILE")" local escaped_abbrs="" @@ -1301,7 +1301,7 @@ esac shell_plugins+=("starship") fi - if [ "$WARP_IN_MSYS2" = false ]; then + if [ "$ZAPLEX_IN_MSYS2" = false ]; then local escaped_shell_plugins=$(warp_escape_json "$shell_plugins") local escaped_path="$(warp_escape_json "$PATH")" local escaped_shell_options=$(warp_escape_json "$shell_options") @@ -1309,10 +1309,10 @@ esac local _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) local _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) - if [ "$WARP_IN_MSYS2" = true ]; then + if [ "$ZAPLEX_IN_MSYS2" = true ]; then warp_send_hook_via_kv_pairs_start "Bootstrapped" warp_send_hook_kv_pair "histfile" "$HISTFILE" - warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID" + warp_send_hook_kv_pair "session_id" "$ZAPLEX_SESSION_ID" warp_send_hook_kv_pair "shell" "bash" warp_send_hook_kv_pair "home_dir" "$HOME" warp_send_hook_kv_pair "user" "$_user" @@ -1338,7 +1338,7 @@ esac else local escaped_editor="$(warp_escape_json "$EDITOR")" local escaped_shell_path="$(warp_escape_json "$BASH")" - local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}" + local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}" warp_send_json_message "$escaped_json" fi } diff --git a/app/assets/bundled/bootstrap/bash_init_shell.sh b/app/assets/bundled/bootstrap/bash_init_shell.sh index 8df977f77a..65b0ac696d 100644 --- a/app/assets/bundled/bootstrap/bash_init_shell.sh +++ b/app/assets/bundled/bootstrap/bash_init_shell.sh @@ -3,13 +3,13 @@ command -p stty raw HISTCONTROL=ignorespace HISTIGNORE=" *" -WARP_SESSION_ID="$(command -p date +%s)$RANDOM" +ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) -if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi +if [[ "$OS" == Windows_NT ]]; then ZAPLEX_IN_MSYS2=true; else ZAPLEX_IN_MSYS2=false; fi # If we're in MSYS2, we want to send the hook via key-value pairs. -if [ "$WARP_IN_MSYS2" = true ]; then _msg="\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;bash\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); fi -WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ +if [ "$ZAPLEX_IN_MSYS2" = true ]; then _msg="\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$ZAPLEX_SESSION_ID\a\e]9278;k;B;shell;bash\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); fi +ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ # We send the InitShell hook via OSCs when on Windows and via DCSs otherwise. -if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then if [ "$WARP_IN_MSYS2" = true ]; then printf "$_msg"; else printf '\e]9278;d;%s\x07' "$_msg"; fi; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi +if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then if [ "$ZAPLEX_IN_MSYS2" = true ]; then printf "$_msg"; else printf '\e]9278;d;%s\x07' "$_msg"; fi; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi unset _hostname _user _msg diff --git a/app/assets/bundled/bootstrap/bash_init_subshell.sh b/app/assets/bundled/bootstrap/bash_init_subshell.sh index 481c9a0450..df3ea860a8 100644 --- a/app/assets/bundled/bootstrap/bash_init_subshell.sh +++ b/app/assets/bundled/bootstrap/bash_init_subshell.sh @@ -4,12 +4,12 @@ command -p stty raw unset PROMPT_COMMAND HISTCONTROL=ignorespace HISTIGNORE=" *" -WARP_IS_SUBSHELL=1 -WARP_SESSION_ID="$(command -p date +%s)$RANDOM" +ZAPLEX_IS_SUBSHELL=1 +ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) -_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n") -if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi -WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ -if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi +_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n") +if [[ "$OS" == Windows_NT ]]; then ZAPLEX_IN_MSYS2=true; else ZAPLEX_IN_MSYS2=false; fi +ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ +if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi unset _hostname _user _msg diff --git a/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt b/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt index 1b4ff95b22..51002177f3 100644 --- a/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt +++ b/app/assets/bundled/bootstrap/bash_zsh_subshell_bootstrap_block_output.txt @@ -1 +1 @@ -echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> % +echo -e '\n# Auto-Zaplexify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> % diff --git a/app/assets/bundled/bootstrap/fish.sh b/app/assets/bundled/bootstrap/fish.sh index 6179d9bdb8..588b981e8f 100644 --- a/app/assets/bundled/bootstrap/fish.sh +++ b/app/assets/bundled/bootstrap/fish.sh @@ -1,4 +1,4 @@ -# Note that WARP_SESSION_ID is expected to have been set when executing commands to +# Note that ZAPLEX_SESSION_ID is expected to have been set when executing commands to # emit the InitShell payload, which includes the session ID. begin # We wrap ourselves in a begin block because these are effectively injected @@ -34,21 +34,21 @@ set -g OSC_PARAM_SEPARATOR ';' set -g RESET_GRID_OSC (printf '\e]9279\a') -if test -n "$WARP_INITIAL_WORKING_DIR" - cd "$WARP_INITIAL_WORKING_DIR" >/dev/null 2>&1 - set -e WARP_INITIAL_WORKING_DIR +if test -n "$ZAPLEX_INITIAL_WORKING_DIR" + cd "$ZAPLEX_INITIAL_WORKING_DIR" >/dev/null 2>&1 + set -e ZAPLEX_INITIAL_WORKING_DIR end -# Append additional PATH entries if provided via WARP_PATH_APPEND. -if test -n "$WARP_PATH_APPEND" - set -gx --path PATH "$PATH:$WARP_PATH_APPEND" - set -e WARP_PATH_APPEND +# Append additional PATH entries if provided via ZAPLEX_PATH_APPEND. +if test -n "$ZAPLEX_PATH_APPEND" + set -gx --path PATH "$PATH:$ZAPLEX_PATH_APPEND" + set -e ZAPLEX_PATH_APPEND end function warp_send_json_message # Sends a message to the controlling terminal as a DSC control sequence. set -l escaped_json (warp_hex_encode_string "$argv") - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ] + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ] echo -n "$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR$escaped_json$OSC_END" else echo -n "$DCS_START$DCS_JSON_MARKER$escaped_json$DCS_END" @@ -56,8 +56,8 @@ function warp_send_json_message end function warp_maybe_send_reset_grid_osc - # Note that $WARP_USING_WINDOWS_CON_PTY is set in the init shell script. - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ] + # Note that $ZAPLEX_USING_WINDOWS_CON_PTY is set in the init shell script. + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ] printf $RESET_GRID_OSC end end @@ -74,7 +74,7 @@ end set -g _warp_generator_pids '' # Runs the given command in the background, records its PID in -# _WARP_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when +# _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when # the job is completed. # # Usage: @@ -100,7 +100,7 @@ function _warp_run_generator_command_internal # N.B. Fish shell variables cannot contain null characters, so the command output must be # immediately hex encoded before being stored in a variable. fish -c " - set -l warp_using_windows_con_pty $WARP_USING_WINDOWS_CON_PTY; + set -l warp_using_windows_con_pty $ZAPLEX_USING_WINDOWS_CON_PTY; set -l reset_grid_osc $RESET_GRID_OSC; function warp_maybe_send_reset_grid_osc if [ \"\$warp_using_windows_con_pty\" = true ] @@ -152,7 +152,7 @@ end function warp_run_generator_command # Setting this environment variable allows warp_precmd to detect if a generator # command or a user command has just completed. - set -g _WARP_GENERATOR_COMMAND 1 + set -g _ZAPLEX_GENERATOR_COMMAND 1 _warp_run_generator_command_internal $argv end @@ -199,7 +199,7 @@ function warp_update_prompt_vars end # If not honoring PS1, set both prompts to be empty - if test "$WARP_HONOR_PS1" = "0" + if test "$ZAPLEX_HONOR_PS1" = "0" function fish_prompt; echo -n ""; end function fish_right_prompt; echo -n ""; end # If honoring PS1, add prefix/suffix to both prompts @@ -207,7 +207,7 @@ function warp_update_prompt_vars function end_prompt echo -n (printf '\x1b') - if test "$WARP_HONOR_PS1" != "1" && [ "$WARP_USING_WINDOWS_CON_PTY" = true ] + if test "$ZAPLEX_HONOR_PS1" != "1" && [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ] echo -n "]133;B$RESET_GRID_OSC" else echo -n ']133;B' @@ -237,11 +237,11 @@ function warp_update_prompt_vars end end -# Changes the WARP_HONOR_PS1 variable to 1, to indicate we want to use the user's custom prompt. Restores +# Changes the ZAPLEX_HONOR_PS1 variable to 1, to indicate we want to use the user's custom prompt. Restores # the original fish prompt functions (which we set to empty for Warp prompt) by calling warp_update_prompt_vars # to refresh the prompt. We force a repaint of the prompt to ensure the change is reflected immediately. function warp_change_prompt_modes_to_ps1 - set -x WARP_HONOR_PS1 "1" + set -x ZAPLEX_HONOR_PS1 "1" # Restores fish_prompt and fish_right_prompt. warp_update_prompt_vars @@ -249,11 +249,11 @@ function warp_change_prompt_modes_to_ps1 commandline -f repaint end -# Changes the WARP_HONOR_PS1 variable to 0, to indicate we want to use the Warp prompt. Saves and clears +# Changes the ZAPLEX_HONOR_PS1 variable to 0, to indicate we want to use the Warp prompt. Saves and clears # the fish prompt functions (which we set to empty for Warp prompt) by calling warp_update_prompt_vars # to refresh the prompt. We force a repaint of the prompt to ensure the change is reflected immediately. function warp_change_prompt_modes_to_warp_prompt - set -x WARP_HONOR_PS1 "0" + set -x ZAPLEX_HONOR_PS1 "0" # Updates fish_prompt and fish_right_prompt to be empty. warp_update_prompt_vars @@ -278,13 +278,13 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror set exit_code 1 end - warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$block_id\"}}" + warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$ZAPLEX_SESSION_ID-$block_id\"}}" warp_maybe_send_reset_grid_osc set block_id (math $block_id + 1) - if ! test -z $_WARP_GENERATOR_COMMAND - set -e _WARP_GENERATOR_COMMAND + if ! test -z $_ZAPLEX_GENERATOR_COMMAND + set -e _ZAPLEX_GENERATOR_COMMAND set -l escaped_json "{\"hook\": \"Precmd\", \"value\": { \"pwd\": \"\", \"ps1\": \"\", @@ -293,7 +293,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror \"virtual_env\": \"\", \"conda_env\": \"\", \"node_version\": \"\", - \"session_id\": $WARP_SESSION_ID, + \"session_id\": $ZAPLEX_SESSION_ID, \"is_after_in_band_command\": true }}" warp_send_json_message $escaped_json @@ -339,7 +339,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror # blocks created during the bootstrap process don't have visible # prompts, and we don't want to invoke `git` before we've sourced the # user's rcfiles and have a fully-populated PATH. - if test -n "$WARP_BOOTSTRAPPED" + if test -n "$ZAPLEX_BOOTSTRAPPED" if test -n "$VIRTUAL_ENV" set escaped_virtual_env (warp_escape_json "$VIRTUAL_ENV") end @@ -411,7 +411,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror set escaped_prompt (warp_escape_prompt "$raw_prompt_for_preview") set -l escaped_json - if test "$WARP_HONOR_PS1" = "1" + if test "$ZAPLEX_HONOR_PS1" = "1" # Don't send lprompt or rprompt in this case - we'll use prompt markers for both directly! set escaped_json "{\"hook\": \"Precmd\", \"value\": { \"pwd\": \"$escaped_pwd\", @@ -422,7 +422,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror \"virtual_env\": \"$escaped_virtual_env\", \"conda_env\": \"$escaped_conda_env\", \"node_version\": \"$escaped_node_version\", - \"session_id\": $WARP_SESSION_ID + \"session_id\": $ZAPLEX_SESSION_ID }}" else # We send an lprompt to use for prompt preview purposes only (we still use prompt markers for active prompts). @@ -435,7 +435,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror \"virtual_env\": \"$escaped_virtual_env\", \"conda_env\": \"$escaped_conda_env\", \"node_version\": \"$escaped_node_version\", - \"session_id\": $WARP_SESSION_ID + \"session_id\": $ZAPLEX_SESSION_ID }}" end warp_send_json_message $escaped_json @@ -570,7 +570,7 @@ end # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # recursive SSH sessions. -if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1" +if test "$ZAPLEX_IS_LOCAL_SHELL_SESSION" = "1" function is_interactive_ssh_session # Parse through all ssh options, as defined in the ssh man pages. Send # stderr to /dev/null to silence argparse output when an option is invalid. @@ -601,7 +601,7 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1" # Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters # We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte # and use printf to convert back to plaintext - set -l zsh_env_script (printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); printf '"'"'\x1b\x50\x24\x64%s\x9c'"'"' $_msg; unset _hostname _user _msg' | command od -An -v -tx1 | command tr -d ' \n') + set -l zsh_env_script (printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM"; ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; ZAPLEX_HONOR_PS1='$ZAPLEX_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); printf '"'"'\x1b\x50\x24\x64%s\x9c'"'"' $_msg; unset _hostname _user _msg' | command od -An -v -tx1 | command tr -d ' \n') # Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be # escaped with "''" to avoid the local shell from expanding them before they're passed to the remote shell. @@ -609,14 +609,14 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1" # determine what shell is the login shell on the remote machine. We perform a preliminary check to see if # the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't # support. - command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \ + command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$ZAPLEX_SESSION_ID \ -t $argv \ " -export TERM_PROGRAM='WarpTerminal' -test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION' +export TERM_PROGRAM='ZaplexTerminal' +test -n '$ZAPLEX_CLIENT_VERSION' && export ZAPLEX_CLIENT_VERSION='$ZAPLEX_CLIENT_VERSION' # Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on). -test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION' -hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'" +test -n '$ZAPLEX_CLI_AGENT_PROTOCOL_VERSION' && export ZAPLEX_CLI_AGENT_PROTOCOL_VERSION='$ZAPLEX_CLI_AGENT_PROTOCOL_VERSION' +hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$ZAPLEX_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'" printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'" if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then @@ -651,31 +651,31 @@ bash) stty raw HISTCONTROL=ignorespace HISTIGNORE=" *" - WARP_SESSION_ID="$(command -p date +%s)$RANDOM" - WARP_HONOR_PS1="'$WARP_HONOR_PS1'" + ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" + ZAPLEX_HONOR_PS1="'$ZAPLEX_HONOR_PS1'" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n) _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) - _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'" - WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ - if [[ "'$OS'" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi + _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'" + ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ + if [[ "'$OS'" == Windows_NT ]]; then ZAPLEX_IN_MSYS2=true; else ZAPLEX_IN_MSYS2=false; fi printf '\''"'\eP$d%s\x9c'"'\'' \""'$_msg'"\"' unset _hostname _user _msg ) ;; -zsh) WARP_TMP_DIR="'$(mktemp -d warptmp.XXXXXX)'" +zsh) ZAPLEX_TMP_DIR="'$(mktemp -d warptmp.XXXXXX)'" local ZSH_ENV_SCRIPT='$zsh_env_script' if [[ "'$?'" == 0 ]]; then if command -v xxd >/dev/null 2>&1; then - echo "'$ZSH_ENV_SCRIPT'" | command xxd -p -r > "'$WARP_TMP_DIR'"/.zshenv + echo "'$ZSH_ENV_SCRIPT'" | command xxd -p -r > "'$ZAPLEX_TMP_DIR'"/.zshenv else for i in {0..\$((\${#ZSH_ENV_SCRIPT} - 1))..2}; do builtin printf "'"\x${ZSH_ENV_SCRIPT:$i:2}"'" - done > "'$WARP_TMP_DIR'"/.zshenv + done > "'$ZAPLEX_TMP_DIR'"/.zshenv fi else echo \"Failed to bootstrap warp. Continuing with a non-bootstrapped shell.\" fi -TMPPREFIX="'$HOME/.zshtmp-'" WARP_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" ZDOTDIR="'$WARP_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE +TMPPREFIX="'$HOME/.zshtmp-'" ZAPLEX_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" ZDOTDIR="'$ZAPLEX_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE ;; esac " @@ -685,11 +685,11 @@ esac if is_interactive_ssh_session $argv warp_send_json_message '{"hook": "PreInteractiveSSHSession", "value": {}}' - if [ "$WARP_USE_SSH_WRAPPER" = "1" ] - if test $WARP_SHELL_DEBUG_MODE - set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "-x" + if [ "$ZAPLEX_USE_SSH_WRAPPER" = "1" ] + if test $ZAPLEX_SHELL_DEBUG_MODE + set -g TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE "-x" else - set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "" + set -g TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE "" end warp_ssh_helper $argv else @@ -719,6 +719,6 @@ end warp_bootstrapped -set -g WARP_BOOTSTRAPPED 1 +set -g ZAPLEX_BOOTSTRAPPED 1 set -g fish_private_mode $saved_fish_private_mode end diff --git a/app/assets/bundled/bootstrap/fish_init_shell.sh b/app/assets/bundled/bootstrap/fish_init_shell.sh index 8d867c1363..1f6455b98c 100644 --- a/app/assets/bundled/bootstrap/fish_init_shell.sh +++ b/app/assets/bundled/bootstrap/fish_init_shell.sh @@ -1,9 +1,9 @@ -set -g WARP_SESSION_ID (random) +set -g ZAPLEX_SESSION_ID (random) set _hostname (command -v hostname >/dev/null 2>&1 && command hostname 2>/dev/null || uname -n) set _user (command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER) -set -g WARP_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false) -if test "$WARP_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"fish\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end -set WARP_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@ +set -g ZAPLEX_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false) +if test "$ZAPLEX_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$ZAPLEX_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"fish\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end +set ZAPLEX_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@ # We send the InitShell hook via OSCs when on Windows and via DCSs otherwise. -if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end +if test "$ZAPLEX_USING_WINDOWS_CON_PTY" = true; if test "$ZAPLEX_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end set -e _hostname _user _msg diff --git a/app/assets/bundled/bootstrap/fish_init_subshell.sh b/app/assets/bundled/bootstrap/fish_init_subshell.sh index 523d32a819..87504eb3f2 100644 --- a/app/assets/bundled/bootstrap/fish_init_subshell.sh +++ b/app/assets/bundled/bootstrap/fish_init_subshell.sh @@ -1,8 +1,8 @@ -set -g WARP_SESSION_ID (random) +set -g ZAPLEX_SESSION_ID (random) set _hostname (command -v hostname >/dev/null 2>&1 && command hostname 2>/dev/null || uname -n) set _user (command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER) -set -g WARP_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false) -if test "$WARP_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;B;is_subshell;true\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"user\": \"%s\", \"hostname\": \"%s\", \"shell\": \"fish\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end -set WARP_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@ -if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end +set -g ZAPLEX_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false) +if test "$ZAPLEX_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$ZAPLEX_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;B;is_subshell;true\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"user\": \"%s\", \"hostname\": \"%s\", \"shell\": \"fish\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end +set ZAPLEX_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@ +if test "$ZAPLEX_USING_WINDOWS_CON_PTY" = true; if test "$ZAPLEX_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end set -e _hostname _user _msg diff --git a/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt b/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt index e2421fd2a0..30b5b8b5e9 100644 --- a/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt +++ b/app/assets/bundled/bootstrap/fish_subshell_bootstrap_block_output.txt @@ -1 +1 @@ -echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> % +echo -e '\n# Auto-Zaplexify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> % diff --git a/app/assets/bundled/bootstrap/pwsh.ps1 b/app/assets/bundled/bootstrap/pwsh.ps1 index 7d02c08479..5ae8dd41ad 100644 --- a/app/assets/bundled/bootstrap/pwsh.ps1 +++ b/app/assets/bundled/bootstrap/pwsh.ps1 @@ -135,7 +135,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock { } function Warp-Bootstrapped { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'WARP_BOOTSTRAPPED', Justification = 'False positive as we are assigning to global')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'ZAPLEX_BOOTSTRAPPED', Justification = 'False positive as we are assigning to global')] param([decimal]$rcStartTime, [decimal]$rcEndTime) $envVarNames = (Get-ChildItem env: | Select-Object -ExpandProperty Name | ForEach-Object { 'env:' + $_ }) + ` @@ -222,7 +222,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock { } } Warp-Send-JsonMessage $bootstrappedMsg - $global:WARP_BOOTSTRAPPED = 1 + $global:ZAPLEX_BOOTSTRAPPED = 1 } function Warp-Preexec([string]$command) { @@ -363,14 +363,14 @@ $null = New-Module -Name Warp-Module -ScriptBlock { # Sets the prompt mode to custom prompt (PS1) # Is the equivalent of warp_change_prompt_modes_to_ps1 in other shells Set-PSReadLineKeyHandler -Chord 'Alt+p' -ScriptBlock { - $env:WARP_HONOR_PS1 = '1' + $env:ZAPLEX_HONOR_PS1 = '1' Warp-Redraw-Prompt } # Sets the prompt mode to warp prompt # Is the equivalent of warp_change_prompt_modes_to_warp_prompt in other shells Set-PSReadLineKeyHandler -Chord 'Alt+w' -ScriptBlock { - $env:WARP_HONOR_PS1 = '0' + $env:ZAPLEX_HONOR_PS1 = '0' Warp-Redraw-Prompt } @@ -482,7 +482,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock { # blocks created during the bootstrap process don't have visible # prompts, and we don't want to invoke 'git' before we've sourced the # user's rcfiles and have a fully-populated PATH. - if ($global:WARP_BOOTSTRAPPED -eq 1) { + if ($global:ZAPLEX_BOOTSTRAPPED -eq 1) { if (Test-Path env:VIRTUAL_ENV) { $virtualEnv = $env:VIRTUAL_ENV } @@ -553,7 +553,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock { } } - $honor_ps1 = "$env:WARP_HONOR_PS1" -eq '1' + $honor_ps1 = "$env:ZAPLEX_HONOR_PS1" -eq '1' $precmdMsg = @{ hook = 'Precmd' @@ -795,7 +795,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock { # Wrap prompt in Prompt Marker OSCs $startPromptMarker = "$e]133;A$oscEnd" $startRPromptMarker = "$e]133;P;k=r$oscEnd" - if ("$env:WARP_HONOR_PS1" -eq '0') { + if ("$env:ZAPLEX_HONOR_PS1" -eq '0') { $endPromptMarker = "$e]133;B$oscEnd$oscResetGrid" } else { $endPromptMarker = "$e]133;B$oscEnd" @@ -865,9 +865,9 @@ $null = New-Module -Name Warp-Module -ScriptBlock { return $decoratedPrompt } - if ((Test-Path env:WARP_INITIAL_WORKING_DIR) -and -not [String]::IsNullOrEmpty($env:WARP_INITIAL_WORKING_DIR)) { - Set-Location $env:WARP_INITIAL_WORKING_DIR 2> $null - Remove-Item -Path env:WARP_INITIAL_WORKING_DIR + if ((Test-Path env:ZAPLEX_INITIAL_WORKING_DIR) -and -not [String]::IsNullOrEmpty($env:ZAPLEX_INITIAL_WORKING_DIR)) { + Set-Location $env:ZAPLEX_INITIAL_WORKING_DIR 2> $null + Remove-Item -Path env:ZAPLEX_INITIAL_WORKING_DIR } # In some cases, the Clear-Host command will not interface properly with the blocklist. @@ -977,11 +977,11 @@ $null = New-Module -Name Warp-Module -ScriptBlock { } } - # Append additional PATH entries if provided via WARP_PATH_APPEND. + # Append additional PATH entries if provided via ZAPLEX_PATH_APPEND. # This happens after we source RC files in case they reset PATH. - if (-not [String]::IsNullOrEmpty($env:WARP_PATH_APPEND)) { - $env:PATH = '{0}{1}{2}' -f $env:PATH, [IO.Path]::PathSeparator, $env:WARP_PATH_APPEND - Remove-Item -Path env:WARP_PATH_APPEND + if (-not [String]::IsNullOrEmpty($env:ZAPLEX_PATH_APPEND)) { + $env:PATH = '{0}{1}{2}' -f $env:PATH, [IO.Path]::PathSeparator, $env:ZAPLEX_PATH_APPEND + Remove-Item -Path env:ZAPLEX_PATH_APPEND } # This is a workaround for oh-my-posh's "transient prompt" feature. When enabled, it causes the diff --git a/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 b/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 index 309670275d..98c8fe0be1 100644 --- a/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 +++ b/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 @@ -7,7 +7,7 @@ if ($PSEdition -eq 'Desktop' -or $IsWindows) { $EP = [Microsoft.PowerShell.ExecutionPolicy] # MachinePolicy and UserPolicy scopes cannot be overridden. If either is Restricted, there's nothing we can do. if ((Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Restricted -or (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Restricted) { - Write-Error 'ExecutionPolicy is Restricted. Unable to Warpify this PowerShell session.' + Write-Error 'ExecutionPolicy is Restricted. Unable to Zaplexify this PowerShell session.' } elseif ((Get-ExecutionPolicy) -eq $EP::Restricted -and (Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Undefined -and (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Undefined) { $global:_warp_PSProcessExecPolicy = $(Get-ExecutionPolicy -Scope Process) Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force diff --git a/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt b/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt index 7b56c6a898..86b23efe80 100644 --- a/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt +++ b/app/assets/bundled/bootstrap/subshell_bootstrap_block_command.txt @@ -1 +1 @@ -Success! This subshell spawned by % has been Warpified. \ No newline at end of file +Success! This subshell spawned by % has been Zaplexified. \ No newline at end of file diff --git a/app/assets/bundled/bootstrap/unknown_init_subshell.sh b/app/assets/bundled/bootstrap/unknown_init_subshell.sh index e831946414..60c2011816 100644 --- a/app/assets/bundled/bootstrap/unknown_init_subshell.sh +++ b/app/assets/bundled/bootstrap/unknown_init_subshell.sh @@ -5,4 +5,4 @@ # Thankfully, we don't need curly braces around the first expression, so we can put the fish check # first and it early exits. This runs correctly in sh, bash, zsh, and fish. # Replace `HOOK_NAME` with the appropriate hook name. -[ -z $WARP_BOOTSTRAPPED ] && printf "\\e]9278;f;{\"hook\": \"HOOK_NAME\", \"value\": { \"shell\": \"%s\", \"uname\": \"%s\" }}\\a" $([ $FISH_VERSION ] && echo "fish" || { echo $0 | command -p grep -q zsh && echo "zsh"; } || { echo $0 | command -p grep -q bash && echo "bash"; } || echo "unknown") $(uname) +[ -z $ZAPLEX_BOOTSTRAPPED ] && printf "\\e]9278;f;{\"hook\": \"HOOK_NAME\", \"value\": { \"shell\": \"%s\", \"uname\": \"%s\" }}\\a" $([ $FISH_VERSION ] && echo "fish" || { echo $0 | command -p grep -q zsh && echo "zsh"; } || { echo $0 | command -p grep -q bash && echo "bash"; } || echo "unknown") $(uname) diff --git a/app/assets/bundled/bootstrap/zsh.sh b/app/assets/bundled/bootstrap/zsh.sh index 540135afb8..d22a64f7b8 100644 --- a/app/assets/bundled/bootstrap/zsh.sh +++ b/app/assets/bundled/bootstrap/zsh.sh @@ -16,6 +16,6 @@ # Also, note that we put the 'eval' on the same line as the 'read' separated by a semi-colon # rather than on its own line after the HEREDOC. This seems to work around a bug in zsh # where the buffer was getting messed up after processing the heredoc about 1/50 of the time. - read -r -d '' WARP_BOOTSTRAP_VAR << 'EOM'; eval "$WARP_BOOTSTRAP_VAR"; unset WARP_BOOTSTRAP_VAR + read -r -d '' ZAPLEX_BOOTSTRAP_VAR << 'EOM'; eval "$ZAPLEX_BOOTSTRAP_VAR"; unset ZAPLEX_BOOTSTRAP_VAR #include bundled/bootstrap/zsh_body.sh EOM diff --git a/app/assets/bundled/bootstrap/zsh_body.sh b/app/assets/bundled/bootstrap/zsh_body.sh index 050c8746e1..ae439b6cfa 100644 --- a/app/assets/bundled/bootstrap/zsh_body.sh +++ b/app/assets/bundled/bootstrap/zsh_body.sh @@ -1,10 +1,10 @@ -# Note that WARP_SESSION_ID is expected to have been set when executing commands to +# Note that ZAPLEX_SESSION_ID is expected to have been set when executing commands to # emit the InitShell payload, which includes the session ID. # # Throughout, command -p is used to call external binaries. command -p resolves the # given command using the system default $PATH, which ensures the shells can locate # the corresponding binaries even if the user has a clobbered value of $PATH. -if [[ -z $WARP_BOOTSTRAPPED ]]; then +if [[ -z $ZAPLEX_BOOTSTRAPPED ]]; then # Return PS2 to its original value. We set this to an empty string in zsh.sh, # and want to reset it now that we've received the bootstrap script and started # to eval it. @@ -46,9 +46,9 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # Attempt to cd to the desired initial working directory, swallowing any # errors. If this fails, the user will end up in their home directory. - if [[ ! -z "$WARP_INITIAL_WORKING_DIR" ]]; then - cd "$WARP_INITIAL_WORKING_DIR" >/dev/null 2>&1 - unset WARP_INITIAL_WORKING_DIR + if [[ ! -z "$ZAPLEX_INITIAL_WORKING_DIR" ]]; then + cd "$ZAPLEX_INITIAL_WORKING_DIR" >/dev/null 2>&1 + unset ZAPLEX_INITIAL_WORKING_DIR fi # We configure history to ignore commands starting with space to avoid leaking @@ -59,19 +59,19 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # The temporary files used to track generator PIDs. We'll fill these in later, # if we execute any generator commands. - _WARP_GENERATOR_PIDS_STARTED_TMP_FILE="" - _WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE="" + _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE="" + _ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE="" # Flag to indicate whether the current command is a generator command. # We use an empty string as the sentinel value (rather than unsetting) for # compatibility with `setopt nounset`. - _WARP_GENERATOR_COMMAND="" + _ZAPLEX_GENERATOR_COMMAND="" # Make sure we delete generator PID files when the shell exits, if they exist. __warp_generator_pid_file_cleanup() { - if [[ -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then - command -p rm $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + if [[ -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then + command -p rm $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE fi - if [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - command -p rm $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + if [[ -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + command -p rm $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE fi } trap __warp_generator_pid_file_cleanup EXIT @@ -84,7 +84,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # one of the bytes in JSON is 9c (ST) or other (CAN, SUB, ESC). local msg=$(warp_hex_encode_string "$1") # We send the InitShell hook via OSCs when on WSL and via DCSs otherwise. - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf $OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR$msg$OSC_END else printf "%b%b%s%b" $DCS_START $DCS_JSON_MARKER $msg $DCS_END @@ -98,7 +98,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # waits on orphaned slave channels when the user ends their interactive # session. # - # Only relevant for remote SSH shells. WARP_IS_SSH is exported to "1" + # Only relevant for remote SSH shells. ZAPLEX_IS_SSH is exported to "1" # by `warp_ssh_helper` on the remote side of a Warp-managed SSH session # and is unset everywhere else (local shells, subshells, docker # sandboxes, etc.), so the hook only fires where a remote-server-proxy @@ -106,11 +106,11 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # # Installed after warp_send_json_message is defined so the handler is # callable the moment the hook is registered. - if [[ "$WARP_IS_SSH" == "1" ]]; then + if [[ "$ZAPLEX_IS_SSH" == "1" ]]; then __warp_emit_exit_shell() { - if [[ -n "$WARP_SESSION_ID" ]]; then + if [[ -n "$ZAPLEX_SESSION_ID" ]]; then warp_send_json_message \ - "{\"hook\": \"ExitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID}}" + "{\"hook\": \"ExitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID}}" fi } # zshexit_functions is zsh's idiomatic exit-hook mechanism. We prefer @@ -131,7 +131,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then fi warp_maybe_send_reset_grid_osc() { - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf $OSC_RESET_GRID fi } @@ -183,13 +183,13 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then } # Runs the given command in the background, records its PID in - # _WARP_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when + # _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE, and adds its PID from the file when # the job is completed. _warp_run_generator_command_internal() { _warp_execute_command "$@" & # $! contains the PID of the most recently backgrounded command. local pid=$! - echo $pid >> $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + echo $pid >> $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE wait $pid 2> /dev/null # If the exit code of the backgrounded _warp_execute_command process is non-zero, @@ -205,8 +205,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # # The completed generator PIDs file may not exist if this generator was (by # error) left running/not cancelled properly in warp_preexec. - if [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - echo $pid >> $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + if [[ -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + echo $pid >> $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE fi } @@ -222,14 +222,14 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then warp_run_generator_command() { # Setting this environment variable prevents warp_precmd from emitting the # 'Block started' hook to the Rust app. - _WARP_GENERATOR_COMMAND=1 + _ZAPLEX_GENERATOR_COMMAND=1 # Ensure the started and completed generator PID files exist. - if [[ -z $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE || ! -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then - _WARP_GENERATOR_PIDS_STARTED_TMP_FILE="$(command -p mktemp)" + if [[ -z $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE || ! -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then + _ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE="$(command -p mktemp)" fi - if [[ -z $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE || ! -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - _WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE="$(command -p mktemp)" + if [[ -z $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE || ! -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + _ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE="$(command -p mktemp)" fi # To minimize latency and prevent the user from being blocked from entering a command, @@ -258,21 +258,21 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # If this preexec is called for user command, kill ongoing generator command jobs and clean # up the bookkeeping temp files used to bookkeep. - if _is_warp_generator_command "$1" && [[ -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]] && [[ -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]] + if _is_warp_generator_command "$1" && [[ -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]] && [[ -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]] then # Read PIDs from the started generators tmp file that are not present in # the completed generators tmp file into a zsh array. # # The logic used to be the following: # - # pids=($(command -p comm -23 $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE)) + # pids=($(command -p comm -23 $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE)) # # However, that requires that the files are sorted, which we do not enforce (the OS can assign PIDs # in any order). While we could sort the files and then compare them, the files are expected to be # small, so we avoid the overhead of spawning multiple processes and instead do the comparison # manually. - completed_pids=(${(f)"$(<$_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE)"}) - spawned_pids=(${(f)"$(<$_WARP_GENERATOR_PIDS_STARTED_TMP_FILE)"}) + completed_pids=(${(f)"$(<$_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE)"}) + spawned_pids=(${(f)"$(<$_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE)"}) pids=(${spawned_pids:|completed_pids}) # If the array is not empty, kill the ongoing pids. @@ -307,18 +307,18 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # in this function below). local exit_code=$? - warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$((block_id++))\"}}" + warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$ZAPLEX_SESSION_ID-$((block_id++))\"}}" warp_maybe_send_reset_grid_osc # If this is being called for a generator command, short circuit and send an unpopulated # precmd payload (except for pwd), since we don't re-render the prompt after generator commands # are run. - if [ -n "$_WARP_GENERATOR_COMMAND" ]; then + if [ -n "$_ZAPLEX_GENERATOR_COMMAND" ]; then # Restore the user's precmd_functions, since they were un-registered prior to executing # the generator. precmd_functions=($_USER_PRECMD_FUNCTIONS) - _WARP_GENERATOR_COMMAND="" + _ZAPLEX_GENERATOR_COMMAND="" warp_send_json_message "{\"hook\": \"Precmd\", \"value\": { \"pwd\": \"\", \"ps1\": \"\", @@ -327,18 +327,18 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then \"virtual_env\": \"\", \"conda_env\": \"\", \"node_version\": \"\", - \"session_id\": $WARP_SESSION_ID, + \"session_id\": $ZAPLEX_SESSION_ID, \"is_after_in_band_command\": true }}" return 0 fi # If the files for tracking generator PIDs exist, clear them. - if [[ -n $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE && -f $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then - echo "" > $_WARP_GENERATOR_PIDS_STARTED_TMP_FILE + if [[ -n $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE && -f $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE ]]; then + echo "" > $_ZAPLEX_GENERATOR_PIDS_STARTED_TMP_FILE fi - if [[ -n $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE && -f $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then - echo "" > $_WARP_GENERATOR_PIDS_COMPLETED_TMP_FILE + if [[ -n $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE && -f $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE ]]; then + echo "" > $_ZAPLEX_GENERATOR_PIDS_COMPLETED_TMP_FILE fi # Reset the custom kill-buffer binding as the user's zshrc (which is sourced after zshrc_warp) @@ -366,14 +366,14 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then bindkey -r '\ew' bindkey '\ew' warp_change_prompt_modes_to_warp_prompt - # 将 bracketed-paste ZLE widget 重置为内置默认值(.bracketed-paste)。 - # bracketed-paste-magic 等插件(如 Oh My Zsh 中的同名插件)会覆盖此 widget, - # 对紧跟在 URL 之后的 ')' 等字符进行转义。由于 Warp 提交命令时使用 bracketed - # paste 序列,这类插件会在提交时错误地修改命令文本——例如将 - # `(echo "https://example.com")` 变为 `(echo "https://example.com"\)`, - # 导致 ZSH 进入续行提示而非直接执行命令。 - # 由于 Warp 通过自身输入层(而非 ZSH ZLE)管理所有编辑操作,重置此 widget - # 对用户不会产生任何可见影响。 + # Reset bracketed-paste ZLE widget to built-in default (.bracketed-paste). + # Plugins like bracketed-paste-magic (e.g. Oh My Zsh plugin with same name) override this widget, + # escaping characters like ')' that follow URLs. Since Warp submits commands using bracketed + # paste sequences, such plugins incorrectly modify command text on submission — for example changing + # `(echo "https://example.com")` to `(echo "https://example.com"\)`, + # causing ZSH to enter continuation prompt instead of executing the command directly. + # Since Warp manages all editing operations via its own input layer (not ZSH ZLE), resetting this widget + # has no visible impact on users. zle -A .bracketed-paste bracketed-paste 2>/dev/null || true local escaped_pwd @@ -395,7 +395,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # blocks created during the bootstrap process don't have visible # prompts, and we don't want to invoke `git` before we've sourced the # user's rcfiles and have a fully-populated PATH. - if [[ -n $WARP_BOOTSTRAPPED ]]; then + if [[ -n $ZAPLEX_BOOTSTRAPPED ]]; then if [[ -n ${VIRTUAL_ENV:-} ]]; then escaped_virtual_env=$(warp_escape_json $VIRTUAL_ENV) fi @@ -464,7 +464,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # We also pass the shell's notion of `honor_ps1` to ensure it's synced correctly on the Warp-side for prompt handling. # This is passed as a "real boolean" via the JSON payload (string interpolated into JSON string below). local honor_ps1 - if [[ "$WARP_HONOR_PS1" == "1" ]]; then + if [[ "$ZAPLEX_HONOR_PS1" == "1" ]]; then honor_ps1="true" else honor_ps1="false" @@ -481,7 +481,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then \"conda_env\": \"$escaped_conda_env\", \"node_version\": \"$escaped_node_version\", \"kube_config\": \"$escaped_kube_config\", - \"session_id\": $WARP_SESSION_ID + \"session_id\": $ZAPLEX_SESSION_ID }}" warp_send_json_message "$escaped_json" } @@ -543,7 +543,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # # If another shell script also has precmd/preexec hooks to set the title, # we won't be clobbering them because our hooks will run first. - # If the WARP_DISABLE_AUTO_TITLE variable is set, we won't set the title at all. + # If the ZAPLEX_DISABLE_AUTO_TITLE variable is set, we won't set the title at all. # This way, setting the terminal title in a echo command and escape # sequences will work (a single command to set the title normally will get # clobbered by a precmd hook)." @@ -566,10 +566,10 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # Runs before showing the prompt function warp_set_title_idle_on_precmd { # If the user wants to set the title using oh-my-zsh, they can - # set the WARP_DISABLE_AUTO_TITLE flag. - [[ "${WARP_DISABLE_AUTO_TITLE:-}" != true ]] || return + # set the ZAPLEX_DISABLE_AUTO_TITLE flag. + [[ "${ZAPLEX_DISABLE_AUTO_TITLE:-}" != true ]] || return - if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then + if [[ $ZAPLEX_IS_LOCAL_SHELL_SESSION == "1" ]]; then warp_title "$ZSH_THEME_TERM_TITLE_IDLE" else warp_title "$ZSH_THEME_TERM_TAB_TITLE_IDLE_REMOTE" @@ -580,8 +580,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # Runs before executing the command function warp_set_title_active_on_preexec { # If the user wants to set the title using oh-my-zsh, they can - # set the WARP_DISABLE_AUTO_TITLE flag. - [[ "${WARP_DISABLE_AUTO_TITLE:-}" != true ]] || return + # set the ZAPLEX_DISABLE_AUTO_TITLE flag. + [[ "${ZAPLEX_DISABLE_AUTO_TITLE:-}" != true ]] || return emulate -L zsh setopt extended_glob @@ -681,7 +681,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then local prompt_prefix=$'\e]133;A\a' local rprompt_prefix=$'\e]133;P;k=r\a' local prompt_suffix=$'\e]133;B\a' - if [[ "$WARP_HONOR_PS1" != "1" ]] && [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then + if [[ "$ZAPLEX_HONOR_PS1" != "1" ]] && [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then local suffix="$prompt_suffix$OSC_RESET_GRID" else local suffix="$prompt_suffix" @@ -698,7 +698,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # product behavior of Warp prompt switches only taking effect in new sessions. # Certain prompt plugins like p10k can reset the prompt to a non-empty value, after we've initially unset it. # Confirm that it is unset, if using built-in Warp prompt (update prompt vars is forced to run as the last precmd fn). - if [[ "$WARP_HONOR_PS1" != "1" ]]; then + if [[ "$ZAPLEX_HONOR_PS1" != "1" ]]; then # If the PROMPT has its original value (i.e. we haven't modified it yet), we save it to SAVED_PROMPT # so we can recover it, via bindkey, if we switch back from Warp prompt to PS1 (intra-session). if [[ "$PROMPT" != "%{$prompt_prefix"*"%}" ]]; then @@ -771,7 +771,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # command grid. # If we are using the Warp prompt, we pass a "hidden left prompt" to the prompt # preview grid (the hidden prompt grid) with cursor markers surrounding the entire prompt. - if [[ "$WARP_HONOR_PS1" != "1" ]]; then + if [[ "$ZAPLEX_HONOR_PS1" != "1" ]]; then if [[ "$PROMPT" != "%{$prompt_prefix$ORIGINAL_PROMPT$suffix%}" ]]; then # We purposefully surround this entire prompt with cursor markers to prevent # the shell from moving its internal state of the cursor position, for purposes @@ -804,12 +804,12 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then } # Switches to PS1 prompt by restoring the prompt/rprompt to their original values and flipping - # WARP_HONOR_PS1 to "1" (they had originally been unset for the Warp prompt). Resets the prompt, + # ZAPLEX_HONOR_PS1 to "1" (they had originally been unset for the Warp prompt). Resets the prompt, # forcing a re-print. function warp_change_prompt_modes_to_ps1() { PROMPT="$SAVED_PROMPT" RPROMPT="$SAVED_RPROMPT" - WARP_HONOR_PS1=1 + ZAPLEX_HONOR_PS1=1 warp_update_prompt_vars zle .reset-prompt @@ -819,11 +819,11 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # so we can reference this when we register it with a bindkey. zle -N warp_change_prompt_modes_to_ps1 - # Switches to Warp prompt by flipping WARP_HONOR_PS1 to "0", which will result + # Switches to Warp prompt by flipping ZAPLEX_HONOR_PS1 to "0", which will result # in unsetting the PROMPT variables to avoid a double prompt. Resets the prompt, forcing # a re-print. function warp_change_prompt_modes_to_warp_prompt() { - WARP_HONOR_PS1=0 + ZAPLEX_HONOR_PS1=0 warp_update_prompt_vars zle .reset-prompt @@ -835,7 +835,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping # recursive SSH sessions. - if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then + if [[ $ZAPLEX_IS_LOCAL_SHELL_SESSION == "1" ]]; then # This helper function determines whether the user's ssh arguments imply # creation of a non-interactive session or otherwise would conflict with # our SSH wrapper. Returns 0 for an interactive session; >0 otherwise. @@ -877,7 +877,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters # We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte # and use printf to convert back to plaintext - local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n') + local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM"; ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n') # Keep remote commands up-to-date with shell.rs & bash.sh. # Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be @@ -886,18 +886,18 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then # determine what shell is the login shell on the remote machine. We perform a preliminary check to see if # the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't # support. - command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \ + command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$ZAPLEX_SESSION_ID \ -t "${@:1}" \ " -export TERM_PROGRAM='WarpTerminal' +export TERM_PROGRAM='ZaplexTerminal' # Mark the remote side of a Warp-managed SSH session so the bootstrap # body can distinguish it from local shells. Used to gate the ExitShell # hook which tears down the remote-server-proxy subprocess. -export WARP_IS_SSH='1' -test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION' +export ZAPLEX_IS_SSH='1' +test -n '$ZAPLEX_CLIENT_VERSION' && export ZAPLEX_CLIENT_VERSION='$ZAPLEX_CLIENT_VERSION' # Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on). -test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION' -hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'" +test -n '$ZAPLEX_CLI_AGENT_PROTOCOL_VERSION' && export ZAPLEX_CLI_AGENT_PROTOCOL_VERSION='$ZAPLEX_CLI_AGENT_PROTOCOL_VERSION' +hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$ZAPLEX_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'" printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'" if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then @@ -932,32 +932,32 @@ case "'${SHELL##*/}'" in command -p stty raw HISTCONTROL=ignorespace HISTIGNORE=" *" - WARP_SESSION_ID="$(command -p date +%s)$RANDOM" - WARP_HONOR_PS1="'$WARP_HONOR_PS1'" + ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" + ZAPLEX_HONOR_PS1="'$ZAPLEX_HONOR_PS1'" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) - _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'" - WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ - if [[ "'$OS'" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi + _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'" + ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ + if [[ "'$OS'" == Windows_NT ]]; then ZAPLEX_IN_MSYS2=true; else ZAPLEX_IN_MSYS2=false; fi printf '\''"'\e]9278;d;%s\x07'"'\'' \""'$_msg'"\"' unset _hostname _user _msg ) ;; - zsh) WARP_TMP_DIR="'$(command -p mktemp -d warptmp.XXXXXX)'" + zsh) ZAPLEX_TMP_DIR="'$(command -p mktemp -d warptmp.XXXXXX)'" local ZSH_ENV_SCRIPT='$zsh_env_script' - local WARP_HONOR_PS1='$WARP_HONOR_PS1' + local ZAPLEX_HONOR_PS1='$ZAPLEX_HONOR_PS1' if [[ "'$?'" == 0 ]]; then if command -pv xxd >/dev/null 2>&1; then - echo "'$ZSH_ENV_SCRIPT'" | command -p xxd -p -r > "'$WARP_TMP_DIR'"/.zshenv + echo "'$ZSH_ENV_SCRIPT'" | command -p xxd -p -r > "'$ZAPLEX_TMP_DIR'"/.zshenv else for i in {0..\$((\${#ZSH_ENV_SCRIPT} - 1))..2}; do builtin printf "'"\x${ZSH_ENV_SCRIPT:$i:2}"'" - done > "'$WARP_TMP_DIR'"/.zshenv + done > "'$ZAPLEX_TMP_DIR'"/.zshenv fi else echo \"Failed to bootstrap warp. Continuing with a non-bootstrapped shell.\" fi - TMPPREFIX="'$HOME/.zshtmp-'" WARP_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" WARP_HONOR_PS1="'$WARP_HONOR_PS1'" ZDOTDIR="'$WARP_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE + TMPPREFIX="'$HOME/.zshtmp-'" ZAPLEX_SSH_RCFILES="'${ZDOTDIR:-$HOME}'" ZAPLEX_HONOR_PS1="'$ZAPLEX_HONOR_PS1'" ZDOTDIR="'$ZAPLEX_TMP_DIR'" exec -l zsh -g $TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE ;; esac " @@ -968,10 +968,10 @@ esac warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}" # If the SSH wrapper is not enabled for this session, don't use it. - if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then - local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="" - if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then - TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x" + if [ "$ZAPLEX_USE_SSH_WRAPPER" = "1" ]; then + local TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE="" + if [[ "$ZAPLEX_SHELL_DEBUG_MODE" == "1" ]]; then + TRACE_FLAG_IF_ZAPLEX_SHELL_DEBUG_MODE="-x" fi warp_ssh_helper "$@" else @@ -1024,7 +1024,7 @@ esac command -p rm -r "$ZDOTDIR" # Restore ZDOTDIR. Note that if it was originally unset, it'd be home instead of unset. - ZDOTDIR=$WARP_SSH_RCFILES + ZDOTDIR=$ZAPLEX_SSH_RCFILES fi fi @@ -1040,7 +1040,7 @@ esac # Do other shell startup first so we can ensure Warp goes last. # If this is a subshell, the user and system RC files have already been sourced. - if [[ -z $WARP_IS_SUBSHELL ]]; then + if [[ -z $ZAPLEX_IS_SUBSHELL ]]; then if [[ -e ${ZDOTDIR:-$HOME}/.zshenv ]]; then source ${ZDOTDIR:-$HOME}/.zshenv; fi @@ -1094,11 +1094,11 @@ esac # hook function array). zshaddhistory_functions+=(_warp_zshaddhistory) - # Append additional PATH entries if provided via WARP_PATH_APPEND. This is after the user's RC + # Append additional PATH entries if provided via ZAPLEX_PATH_APPEND. This is after the user's RC # files are sourced in case they reset PATH (/etc/profile on Debian does this, for example). - if [[ -n "${WARP_PATH_APPEND:-}" ]]; then - export PATH="$PATH:$WARP_PATH_APPEND" - unset WARP_PATH_APPEND + if [[ -n "${ZAPLEX_PATH_APPEND:-}" ]]; then + export PATH="$PATH:$ZAPLEX_PATH_APPEND" + unset ZAPLEX_PATH_APPEND fi local -a shell_plugins @@ -1182,7 +1182,7 @@ esac precmd_functions+=(warp_precmd warp_update_prompt_vars) preexec_functions+=(warp_preexec) - WARP_BOOTSTRAPPED=1 + ZAPLEX_BOOTSTRAPPED=1 # Unset the prompt environment variable: Warp doesn't render the user's default prompt. # We explicitly unset this for performance optimizations and so that the we can read the diff --git a/app/assets/bundled/bootstrap/zsh_init_shell.sh b/app/assets/bundled/bootstrap/zsh_init_shell.sh index 08533c637d..b81dd7fce7 100644 --- a/app/assets/bundled/bootstrap/zsh_init_shell.sh +++ b/app/assets/bundled/bootstrap/zsh_init_shell.sh @@ -3,11 +3,11 @@ # command -p resolves the given command with the system default PATH, ensuring the shell # can find them even if the user has a clobbered PATH value. unsetopt ZLE - WARP_SESSION_ID="$(command -p date +%s)$RANDOM" + ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) - _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n") - WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ + _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n") + ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ # We send the InitShell hook via OSCs when on Windows and via DCSs otherwise. - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi unset _hostname _user _msg diff --git a/app/assets/bundled/bootstrap/zsh_init_subshell.sh b/app/assets/bundled/bootstrap/zsh_init_subshell.sh index 939dd712b2..5c04d95a09 100644 --- a/app/assets/bundled/bootstrap/zsh_init_subshell.sh +++ b/app/assets/bundled/bootstrap/zsh_init_subshell.sh @@ -6,11 +6,11 @@ # can find them even if the user has a clobbered PATH value. setopt hist_ignore_space unsetopt ZLE - WARP_IS_SUBSHELL=1 - WARP_SESSION_ID="$(command -p date +%s)$RANDOM" + ZAPLEX_IS_SUBSHELL=1 + ZAPLEX_SESSION_ID="$(command -p date +%s)$RANDOM" _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n) _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER) - _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n") - WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ - if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi + _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $ZAPLEX_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n") + ZAPLEX_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@ + if [ "$ZAPLEX_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi unset _hostname _user _msg diff --git a/app/assets/bundled/ssh/bash_zsh/install_tmux_and_warpify_brew.sh b/app/assets/bundled/ssh/bash_zsh/install_tmux_and_zaplexify_brew.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/install_tmux_and_warpify_brew.sh rename to app/assets/bundled/ssh/bash_zsh/install_tmux_and_zaplexify_brew.sh diff --git a/app/assets/bundled/ssh/bash_zsh/install_tmux_and_warpify_linux.sh b/app/assets/bundled/ssh/bash_zsh/install_tmux_and_zaplexify_linux.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/install_tmux_and_warpify_linux.sh rename to app/assets/bundled/ssh/bash_zsh/install_tmux_and_zaplexify_linux.sh diff --git a/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_apt.sh b/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_apt.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_apt.sh rename to app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_apt.sh diff --git a/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_dnf.sh b/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_dnf.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_dnf.sh rename to app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_dnf.sh diff --git a/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_pacman.sh b/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_pacman.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_pacman.sh rename to app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_pacman.sh diff --git a/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_yum.sh b/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_yum.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_yum.sh rename to app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_yum.sh diff --git a/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_zypper.sh b/app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_zypper.sh similarity index 100% rename from app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_warpify_zypper.sh rename to app/assets/bundled/ssh/bash_zsh/root/install_tmux_and_zaplexify_zypper.sh diff --git a/app/assets/bundled/ssh/bash_zsh/warpify_ssh_session.sh b/app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session.sh similarity index 97% rename from app/assets/bundled/ssh/bash_zsh/warpify_ssh_session.sh rename to app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session.sh index 62bb722644..da9582f95e 100644 --- a/app/assets/bundled/ssh/bash_zsh/warpify_ssh_session.sh +++ b/app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session.sh @@ -8,7 +8,7 @@ _log() { } _err() { - _log RemoteWarpificationIsUnavailable "$1" + _log RemoteZaplexificationIsUnavailable "$1" } _system_details() { diff --git a/app/assets/bundled/ssh/bash_zsh/warpify_ssh_session_mac.sh b/app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session_mac.sh similarity index 96% rename from app/assets/bundled/ssh/bash_zsh/warpify_ssh_session_mac.sh rename to app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session_mac.sh index 7ee6a60990..17c5c9966f 100644 --- a/app/assets/bundled/ssh/bash_zsh/warpify_ssh_session_mac.sh +++ b/app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session_mac.sh @@ -8,7 +8,7 @@ _log() { } _err() { - _log RemoteWarpificationIsUnavailable "$1" + _log RemoteZaplexificationIsUnavailable "$1" } _sd() { diff --git a/app/assets/bundled/ssh/fish/install_tmux_and_warpify_brew.sh b/app/assets/bundled/ssh/fish/install_tmux_and_zaplexify_brew.sh similarity index 100% rename from app/assets/bundled/ssh/fish/install_tmux_and_warpify_brew.sh rename to app/assets/bundled/ssh/fish/install_tmux_and_zaplexify_brew.sh diff --git a/app/assets/bundled/ssh/fish/install_tmux_and_warpify_linux.sh b/app/assets/bundled/ssh/fish/install_tmux_and_zaplexify_linux.sh similarity index 100% rename from app/assets/bundled/ssh/fish/install_tmux_and_warpify_linux.sh rename to app/assets/bundled/ssh/fish/install_tmux_and_zaplexify_linux.sh diff --git a/app/assets/bundled/ssh/fish/warpify_ssh_session.sh b/app/assets/bundled/ssh/fish/zaplexify_ssh_session.sh similarity index 97% rename from app/assets/bundled/ssh/fish/warpify_ssh_session.sh rename to app/assets/bundled/ssh/fish/zaplexify_ssh_session.sh index 0d264d60cc..bc87854ff4 100644 --- a/app/assets/bundled/ssh/fish/warpify_ssh_session.sh +++ b/app/assets/bundled/ssh/fish/zaplexify_ssh_session.sh @@ -10,7 +10,7 @@ function _log end function _err - _log RemoteWarpificationIsUnavailable $argv[1] + _log RemoteZaplexificationIsUnavailable $argv[1] end function _system_details diff --git a/app/assets/bundled/ssh/fish/warpify_ssh_session_mac.sh b/app/assets/bundled/ssh/fish/zaplexify_ssh_session_mac.sh similarity index 96% rename from app/assets/bundled/ssh/fish/warpify_ssh_session_mac.sh rename to app/assets/bundled/ssh/fish/zaplexify_ssh_session_mac.sh index 368a49dd47..fb1ba8b872 100644 --- a/app/assets/bundled/ssh/fish/warpify_ssh_session_mac.sh +++ b/app/assets/bundled/ssh/fish/zaplexify_ssh_session_mac.sh @@ -8,7 +8,7 @@ function _l end function _e - _l RemoteWarpificationIsUnavailable $argv[1] + _l RemoteZaplexificationIsUnavailable $argv[1] end function _sd diff --git a/app/assets/resources/mac/warp_install_image.png b/app/assets/resources/mac/warp_install_image.png deleted file mode 100644 index f93dbc6be2..0000000000 Binary files a/app/assets/resources/mac/warp_install_image.png and /dev/null differ diff --git a/app/assets/resources/mac/zaplex_install_image.png b/app/assets/resources/mac/zaplex_install_image.png new file mode 100644 index 0000000000..48be50478f Binary files /dev/null and b/app/assets/resources/mac/zaplex_install_image.png differ diff --git a/app/build.rs b/app/build.rs index 5f71a5d6cf..e789c40364 100644 --- a/app/build.rs +++ b/app/build.rs @@ -1,5 +1,5 @@ // We can use `std::process:Command` here because this is invoked within a build script, -// _not_ within the Zap binary (where it could cause a terminal to temporarily flash on +// _not_ within the Zaplex binary (where it could cause a terminal to temporarily flash on // Windows). #![allow(clippy::disallowed_types)] @@ -119,9 +119,9 @@ fn main() -> Result<()> { if target_env == "msvc" && env::var("CARGO_FEATURE_WINDOWS_HIGH_PERFORMANCE_GPU_DEFAULT").is_ok() { - println!("cargo:rustc-link-arg-bin=zap-oss=/EXPORT:NvOptimusEnablement,DATA"); + println!("cargo:rustc-link-arg-bin=zaplex=/EXPORT:NvOptimusEnablement,DATA"); println!( - "cargo:rustc-link-arg-bin=zap-oss=/EXPORT:AmdPowerXpressRequestHighPerformance,DATA" + "cargo:rustc-link-arg-bin=zaplex=/EXPORT:AmdPowerXpressRequestHighPerformance,DATA" ); } @@ -168,7 +168,7 @@ fn generate_channel_config_if_needed(target_family: &str, target_os: &str) { let config_bin = "warp-channel-config"; // Check if the config binary is available on PATH. If not, we can't generate embedded - // configs. This is expected for external contributors building Zap OSS. + // configs. This is expected for external contributors building Zaplex OSS. if Command::new(config_bin) .arg("--help") .stdout(std::process::Stdio::null()) @@ -286,7 +286,7 @@ fn copy_async_assets() { } } -/// Copies the DLLs needed to run Zap on Windows. +/// Copies the DLLs needed to run Zaplex on Windows. /// /// They are organized as follows: /// - `conpty.dll` @@ -367,19 +367,19 @@ fn embed_resource_file(target_dir: &Path) { use std::io::Write; let version = env::var("GIT_RELEASE_TAG").unwrap_or("v0".to_owned()); - // Default value aligns with publisher and is set to "Zap", aligned globally with - // `script/windows/bundle.ps1` OSS branch (`$APP_NAME = 'Zap'`) + AUMID `dev.zap.Zap` + Cargo bundle + // Default value aligns with publisher and is set to "Zaplex", aligned globally with + // `script/windows/bundle.ps1` OSS branch (`$APP_NAME = 'Zaplex'`) + AUMID `dev.zaplex.Zaplex` + Cargo bundle // metadata. Windows Task Manager's process grouping name actually comes from PE resources - // `FileDescription` / `ProductName` (not the window title), so if we fall back to default "Zap" here, - // a dev binary built directly via `cargo build` will display as `Zap(N)` in Task Manager. - // Upstream official CI pipeline overrides this explicitly via `export WARP_APP_NAME=...` before calling, unaffected. - let app_name = env::var("WARP_APP_NAME").unwrap_or_else(|_| "Zap".to_owned()); + // `FileDescription` / `ProductName` (not the window title), so if we fall back to default "Zaplex" here, + // a dev binary built directly via `cargo build` will display as `Zaplex(N)` in Task Manager. + // Upstream official CI pipeline overrides this explicitly via `export ZAPLEX_APP_NAME=...` before calling, unaffected. + let app_name = env::var("ZAPLEX_APP_NAME").unwrap_or_else(|_| "Zaplex".to_owned()); let bin_name = env::var("CARGO_BIN_NAME").unwrap_or("oss".to_owned()); - // Override with `WARP_APP_PUBLISHER`; default aligns with installer / AUMID as "Zap". + // Override with `ZAPLEX_APP_PUBLISHER`; default aligns with installer / AUMID as "Zaplex". // Keep installer `MyAppPublisher`, Cargo bundle metadata `copyright`, and process AUMID - // `dev.zap.Zap` globally aligned across three places, avoiding Windows Shell missing the + // `dev.zaplex.Zaplex` globally aligned across three places, avoiding Windows Shell missing the // icon cache due to publisher / product name fingerprint mismatch. - let publisher = env::var("WARP_APP_PUBLISHER").unwrap_or_else(|_| "Zap".to_owned()); + let publisher = env::var("ZAPLEX_APP_PUBLISHER").unwrap_or_else(|_| "Zaplex".to_owned()); let (ver_major, ver_minor, ver_patch, ver_build) = parse_file_version_quad(&version); let icon_path = Path::new("channels") diff --git a/app/channels/oss/dev.zap.Zap.desktop b/app/channels/oss/dev.zap.Zap.desktop deleted file mode 100644 index 4e92d18a07..0000000000 --- a/app/channels/oss/dev.zap.Zap.desktop +++ /dev/null @@ -1,27 +0,0 @@ -[Desktop Entry] -# The version of the desktop entry spec this conforms to. -Version=1.0 - -Type=Application - -Name=Zap -GenericName=TerminalEmulator - -# `zap` 在 deb/rpm 包里是 /opt/zap/zap-oss 的 symlink, -# 在 arch 包里是 /usr/bin/zap wrapper script —— 三者都在 PATH 上能直接调用。 -# AppImage 打包时 bundle_appimage 会用 sed 把这一行替换为 `Exec=zap-oss %U`, -# 直接指向 AppImage 内部的二进制。 -Exec=zap %U -StartupWMClass=dev.zap.Zap - -Keywords=shell;prompt;command;commandline;cmd; - -Icon=dev.zap.Zap - -Categories=System;TerminalEmulator; - -# Don't run this application within a terminal. -Terminal=false - -# Register ourselves as the handler for zap:// URLs. -MimeType=x-scheme-handler/zap; diff --git a/app/channels/oss/dev.zaplex.Zaplex.desktop b/app/channels/oss/dev.zaplex.Zaplex.desktop new file mode 100644 index 0000000000..2ea1f34663 --- /dev/null +++ b/app/channels/oss/dev.zaplex.Zaplex.desktop @@ -0,0 +1,28 @@ +[Desktop Entry] +# The version of the desktop entry spec this conforms to. +Version=1.0 + +Type=Application + +Name=Zaplex +GenericName=TerminalEmulator + +# Exec must match the command the Linux packages actually install on PATH. The +# deb/rpm/arch packages currently name the OSS package `zap` (PACKAGE_NAME in +# script/linux/bundle_{deb,rpm}), so the installed command is `zap`. (When the +# Linux package slug is renamed to `zaplex`, update this to `zaplex` in lockstep.) +# bundle_appimage rewrites this line with sed to point at the in-AppImage binary. +Exec=zap %U +StartupWMClass=dev.zaplex.Zaplex + +Keywords=shell;prompt;command;commandline;cmd; + +Icon=dev.zaplex.Zaplex + +Categories=System;TerminalEmulator; + +# Don't run this application within a terminal. +Terminal=false + +# Register ourselves as the handler for zaplex:// URLs. +MimeType=x-scheme-handler/zaplex; diff --git a/app/channels/oss/icon/AppIcon.icon/Assets/logo.svg b/app/channels/oss/icon/AppIcon.icon.disabled/Assets/logo.svg similarity index 100% rename from app/channels/oss/icon/AppIcon.icon/Assets/logo.svg rename to app/channels/oss/icon/AppIcon.icon.disabled/Assets/logo.svg diff --git a/app/channels/oss/icon/AppIcon.icon/icon.json b/app/channels/oss/icon/AppIcon.icon.disabled/icon.json similarity index 100% rename from app/channels/oss/icon/AppIcon.icon/icon.json rename to app/channels/oss/icon/AppIcon.icon.disabled/icon.json diff --git a/app/channels/oss/icon/zaplex.icns b/app/channels/oss/icon/zaplex.icns new file mode 100644 index 0000000000..3275d71294 Binary files /dev/null and b/app/channels/oss/icon/zaplex.icns differ diff --git a/app/i18n.toml b/app/i18n.toml index ece10260c4..6f149268c7 100644 --- a/app/i18n.toml +++ b/app/i18n.toml @@ -1,4 +1,4 @@ -# i18n-embed 在 app crate 内查找 i18n.toml 决定 fallback / locale 目录 +# i18n-embed searches for i18n.toml in app crate to determine fallback / locale directory fallback_language = "en" [fluent] diff --git a/app/i18n/en/warp.ftl b/app/i18n/en/warp.ftl index ef4741c7ae..5e607480b3 100644 --- a/app/i18n/en/warp.ftl +++ b/app/i18n/en/warp.ftl @@ -1,4 +1,4 @@ -# Zap Desktop — English (source-of-truth locale) +# Zaplex Desktop — English (source-of-truth locale) # 本文件由多 agent 并行编辑,各自维护自己的 SECTION,key 以 surface 前缀隔离避免冲突。 # 加 key 时 ctrl-F 找到对应 SECTION 头追加;新 surface 在文件末尾加新 SECTION。 # @@ -9,7 +9,7 @@ # SECTION: common (Owner: foundation) # ============================================================================= -app-name = Zap +app-name = Zaplex app-tagline = The local-first agentic terminal for developers common-ok = OK @@ -123,9 +123,9 @@ agent-management-artifact-plan = Plan agent-management-artifact-screenshot = Screenshot agent-management-artifact-file = File agent-management-source-scheduled = Scheduled -agent-management-source-local-agent = Zap (local agent) -agent-management-source-cloud-agent = Zap agent -agent-management-source-oz-web = Oz +agent-management-source-local-agent = Zaplex (local agent) +agent-management-source-cloud-agent = Zaplex agent +agent-management-source-oz-web = Agent agent-management-source-github-action = GitHub Action agent-management-no-session-available = No session available agent-management-session-expired = Session expired @@ -142,13 +142,13 @@ agent-management-loading-cloud-runs = Loading agent runs # Files: app/src/workspace/view.rs # ============================================================================= -workspace-menu-update-warp-manually = Update Zap manually +workspace-menu-update-warp-manually = Update Zaplex manually workspace-menu-whats-new = What's new workspace-menu-settings = Settings workspace-menu-keyboard-shortcuts = Keyboard shortcuts workspace-menu-documentation = Documentation workspace-menu-feedback = Feedback -workspace-menu-view-warp-logs = View Zap logs +workspace-menu-view-warp-logs = View Zaplex logs workspace-menu-slack = Slack workspace-toast-failed-load-conversation = Failed to load conversation. workspace-toast-failed-load-conversation-for-forking = Failed to load conversation for forking. @@ -176,14 +176,14 @@ workspace-reopen-closed-session = Reopen closed session app-menu-new-window = New Window app-menu-save-new = Save New... app-menu-launch-configurations = Launch Configurations -app-menu-warp = Zap +app-menu-warp = Zaplex app-menu-preferences = Preferences app-menu-privacy-policy = Privacy Policy... app-menu-debug = Debug -app-menu-set-default-terminal = Set Zap as Default Terminal +app-menu-set-default-terminal = Set Zaplex as Default Terminal app-menu-file = File app-menu-edit = Edit -app-menu-use-warp-prompt = Use Zap's Prompt +app-menu-use-warp-prompt = Use Zaplex's Prompt app-menu-copy-on-select-terminal = Copy on Select within the Terminal app-menu-synchronize-inputs = Synchronize Inputs app-menu-view = View @@ -197,8 +197,8 @@ app-menu-blocks = Blocks app-menu-drive = Drive app-menu-show-in-band-command-blocks = Show In-band Command Blocks app-menu-hide-in-band-command-blocks = Hide In-band Command Blocks -app-menu-show-warpified-ssh-blocks = Show Warpified SSH Blocks -app-menu-hide-warpified-ssh-blocks = Hide Warpified SSH Blocks +app-menu-show-zaplexified-ssh-blocks = Show Zaplexified SSH Blocks +app-menu-hide-zaplexified-ssh-blocks = Hide Zaplexified SSH Blocks app-menu-show-initialization-block = Show Initialization Block app-menu-hide-initialization-block = Hide Initialization Block app-menu-window = Window @@ -213,12 +213,12 @@ app-menu-export-default-settings-csv = Export Default Settings as CSV to home di app-menu-create-anonymous-user = Create anonymous user app-menu-send-feedback = Send Feedback... app-menu-help = Help -app-menu-warp-documentation = Zap Documentation... +app-menu-warp-documentation = Zaplex Documentation... app-menu-github-issues = GitHub Issues... -app-menu-warp-slack-community = Zap Slack Community... -workspace-update-and-relaunch-warp = Update and relaunch Zap +app-menu-warp-slack-community = Zaplex Slack Community... +workspace-update-and-relaunch-warp = Update and relaunch Zaplex workspace-updating-to-version = Updating to ({ $version }) -workspace-update-warp-manually = Update Zap manually +workspace-update-warp-manually = Update Zaplex manually pane-get-started-title = Get started pane-new-tab-title = New tab @@ -230,10 +230,10 @@ pane-new-tab-title = New tab terminal-banner-completions-not-working-prefix = Seems like your completions are not working ( terminal-banner-more-info-lower = more info terminal-banner-more-info = More info -terminal-banner-completions-not-working-middle = ). Enabling tmux warpification in {" "} +terminal-banner-completions-not-working-middle = ). Enabling tmux zaplexification in {" "} terminal-banner-settings = settings terminal-banner-completions-not-working-suffix = may resolve this issue. -terminal-banner-shell-config-incompatible = Your shell configuration is incompatible with Zap...{" "} +terminal-banner-shell-config-incompatible = Your shell configuration is incompatible with Zaplex...{" "} terminal-banner-did-you-intend = Did you intend {" "} terminal-banner-move-cursor = to move the cursor? terminal-toast-powershell-subshells-not-supported = PowerShell subshells not supported @@ -244,15 +244,15 @@ terminal-free-credits = Free credits terminal-cloud-agent-run = Agent run terminal-agent-header-for-terminal = for terminal ssh-remote-choice-title = Choose your experience for this remote session: -ssh-remote-choice-install-extension = Install Zap's SSH extension -ssh-remote-choice-install-extension-desc = Install Zap's extension to enable agent features like file browsing, code review, and intelligent command completions in this session. +ssh-remote-choice-install-extension = Install Zaplex's SSH extension +ssh-remote-choice-install-extension-desc = Install Zaplex's extension to enable agent features like file browsing, code review, and intelligent command completions in this session. ssh-remote-choice-continue-without-installing = Continue without installing -ssh-remote-choice-continue-without-installing-desc = You'll still get a Warpified experience just without the agent features. -ssh-remote-choice-manage-warpify-settings = Manage Warpify settings +ssh-remote-choice-continue-without-installing-desc = You'll still get a Zaplexified experience just without the agent features. +ssh-remote-choice-manage-zaplexify-settings = Manage Zaplexify settings ai-document-show-version-history = Show version history ai-document-update-agent = Update Agent -ai-document-save-and-sync-tooltip = Save and auto-sync this plan to your Zap Drive -ai-document-show-in-warp-drive = Show in Zap Drive +ai-document-save-and-sync-tooltip = Save and auto-sync this plan to your Zaplex Drive +ai-document-show-in-warp-drive = Show in Zaplex Drive ai-document-save-as-markdown-file = Save as markdown file ai-document-attach-to-active-session = Attach to active session ai-document-copy-plan-id = Copy plan ID @@ -283,7 +283,7 @@ ai-rule-edit-rule = Edit rule ai-rule-delete-rule = Delete rule ai-aws-refresh-credentials = Refresh AWS Credentials ai-footer-enable-notifications = Enable notifications -ai-footer-enable-notifications-tooltip = Install the Warp plugin to enable rich agent notifications within Zap +ai-footer-enable-notifications-tooltip = Install the Warp plugin to enable rich agent notifications within Zaplex ai-footer-notifications-setup-instructions = Notifications setup instructions ai-footer-install-plugin-instructions-tooltip = View instructions to install the Warp plugin ai-footer-update-warp-plugin = Update Warp plugin @@ -334,13 +334,13 @@ editor-change-keybinding = Change keybinding autosuggestion-ignore-this-suggestion = Ignore this suggestion codex-use-latest-model = Use latest codex model zap-launch-visit-repo = Visit the repo -zap-launch-title = Zap is now open-source -zap-launch-description = You, our community, can participate in building Zap using an agent-first workflow. +zap-launch-title = Zaplex is now open-source +zap-launch-description = You, our community, can participate in building Zaplex using an agent-first workflow. zap-launch-contribute-title = Contribute -zap-launch-contribute-description = Zap's client code is now open source. Get started by using the /feedback skill to open an issue, and follow the contribution guidelines here. +zap-launch-contribute-description = Zaplex's client code is now open source. Get started by using the /feedback skill to open an issue, and follow the contribution guidelines here. zap-launch-contribute-link-text = here zap-launch-oad-title = Open Automated Development -zap-launch-oad-description = The Zap repo is managed by an agent-first local workflow powered by Oz. +zap-launch-oad-description = The Zaplex repo is managed by an agent-first local workflow. zap-launch-auto-model-title = Introducing 'auto (open-weights)' zap-launch-auto-model-description = We've added a new auto model that picks the best open weight model for a task, like Kimi or MiniMax. hoa-see-whats-new = See what's new @@ -367,12 +367,12 @@ drive-sharing-only-people-invited = Only people invited drive-sharing-anyone-with-link = Anyone with the link drive-sharing-only-invited-teammates = Local access only drive-sharing-teammates-with-link = Local access with link -terminal-warpify-subshell = Warpify subshell -terminal-warpify-subshell-tooltip = Enable Zap shell integration in this session +terminal-zaplexify-subshell = Zaplexify subshell +terminal-zaplexify-subshell-tooltip = Enable Zaplex shell integration in this session terminal-use-agent = Use agent -terminal-use-agent-tooltip = Ask the Zap agent to assist +terminal-use-agent-tooltip = Ask the Zaplex agent to assist terminal-give-control-back-to-agent = Give control back to agent -terminal-resume-agent-tooltip = Ask the Zap agent to resume +terminal-resume-agent-tooltip = Ask the Zaplex agent to resume terminal-voice-input-tooltip = Voice input terminal-attach-file-tooltip = Attach file terminal-slash-commands-tooltip = Slash commands @@ -381,8 +381,8 @@ terminal-profiles = Profiles terminal-manage-profiles = Manage profiles terminal-continue-locally = Continue locally terminal-fork-conversation-locally-tooltip = Fork this conversation locally -terminal-open-in-warp = Open in Zap -terminal-open-conversation-in-warp-tooltip = Open this conversation in the Zap desktop app +terminal-open-in-warp = Open in Zaplex +terminal-open-conversation-in-warp-tooltip = Open this conversation in the Zaplex desktop app terminal-stop-sharing = Stop sharing terminal-copy-session-sharing-link = Copy session sharing link terminal-shared-session-make-editor = Make editor @@ -397,26 +397,26 @@ terminal-input-a11y-label = Command Input. terminal-input-a11y-helper = Input your shell command, press enter to execute. Press cmd-up to navigate to output of previously executed commands. Press cmd-l to re-focus command input. terminal-input-ai-command-search-hint = Type '#' for AI command suggestions terminal-input-run-commands-hint = Run commands -terminal-input-agent-hint-deploy-react-vercel = Zap anything e.g. Deploy my React app to Vercel and set up environment variables -terminal-input-agent-hint-debug-python-ci = Zap anything e.g. Help me debug why my Python tests are failing in CI -terminal-input-agent-hint-setup-microservice = Zap anything e.g. Set up a new microservice with Docker and create the deployment pipeline -terminal-input-agent-hint-fix-node-memory-leak = Zap anything e.g. Find and fix the memory leak in my Node.js application -terminal-input-agent-hint-backup-postgres = Zap anything e.g. Create a backup script for my PostgreSQL database and schedule it -terminal-input-agent-hint-migrate-mysql-postgres = Zap anything e.g. Help me migrate my data from MySQL to PostgreSQL -terminal-input-agent-hint-monitor-aws = Zap anything e.g. Set up monitoring and alerts for my AWS infrastructure -terminal-input-agent-hint-build-fastapi = Zap anything e.g. Build a REST API for my mobile app using FastAPI -terminal-input-agent-hint-optimize-sql = Zap anything e.g. Help me optimize my SQL queries that are running slowly -terminal-input-agent-hint-github-actions = Zap anything e.g. Create a GitHub Actions workflow to automatically deploy on merge -terminal-input-agent-hint-redis-cache = Zap anything e.g. Set up Redis caching for my web application -terminal-input-agent-hint-kubernetes-pods = Zap anything e.g. Help me troubleshoot why my Kubernetes pods keep crashing -terminal-input-agent-hint-bigquery-pipeline = Zap anything e.g. Build a data pipeline to process CSV files and load them into BigQuery -terminal-input-agent-hint-ssl-https = Zap anything e.g. Set up SSL certificates and configure HTTPS for my domain -terminal-input-agent-hint-refactor-legacy-code = Zap anything e.g. Help me refactor this legacy code to use modern design patterns -terminal-input-agent-hint-unit-tests = Zap anything e.g. Create unit tests for my authentication service -terminal-input-agent-hint-elk-logs = Zap anything e.g. Set up log aggregation with ELK stack for my distributed system -terminal-input-agent-hint-oauth-express = Zap anything e.g. Help me implement OAuth2 authentication in my Express.js app -terminal-input-agent-hint-optimize-docker = Zap anything e.g. Optimize my Docker images to reduce build times and size -terminal-input-agent-hint-ab-testing = Zap anything e.g. Set up A/B testing infrastructure for my web application +terminal-input-agent-hint-deploy-react-vercel = Zaplex anything e.g. Deploy my React app to Vercel and set up environment variables +terminal-input-agent-hint-debug-python-ci = Zaplex anything e.g. Help me debug why my Python tests are failing in CI +terminal-input-agent-hint-setup-microservice = Zaplex anything e.g. Set up a new microservice with Docker and create the deployment pipeline +terminal-input-agent-hint-fix-node-memory-leak = Zaplex anything e.g. Find and fix the memory leak in my Node.js application +terminal-input-agent-hint-backup-postgres = Zaplex anything e.g. Create a backup script for my PostgreSQL database and schedule it +terminal-input-agent-hint-migrate-mysql-postgres = Zaplex anything e.g. Help me migrate my data from MySQL to PostgreSQL +terminal-input-agent-hint-monitor-aws = Zaplex anything e.g. Set up monitoring and alerts for my AWS infrastructure +terminal-input-agent-hint-build-fastapi = Zaplex anything e.g. Build a REST API for my mobile app using FastAPI +terminal-input-agent-hint-optimize-sql = Zaplex anything e.g. Help me optimize my SQL queries that are running slowly +terminal-input-agent-hint-github-actions = Zaplex anything e.g. Create a GitHub Actions workflow to automatically deploy on merge +terminal-input-agent-hint-redis-cache = Zaplex anything e.g. Set up Redis caching for my web application +terminal-input-agent-hint-kubernetes-pods = Zaplex anything e.g. Help me troubleshoot why my Kubernetes pods keep crashing +terminal-input-agent-hint-bigquery-pipeline = Zaplex anything e.g. Build a data pipeline to process CSV files and load them into BigQuery +terminal-input-agent-hint-ssl-https = Zaplex anything e.g. Set up SSL certificates and configure HTTPS for my domain +terminal-input-agent-hint-refactor-legacy-code = Zaplex anything e.g. Help me refactor this legacy code to use modern design patterns +terminal-input-agent-hint-unit-tests = Zaplex anything e.g. Create unit tests for my authentication service +terminal-input-agent-hint-elk-logs = Zaplex anything e.g. Set up log aggregation with ELK stack for my distributed system +terminal-input-agent-hint-oauth-express = Zaplex anything e.g. Help me implement OAuth2 authentication in my Express.js app +terminal-input-agent-hint-optimize-docker = Zaplex anything e.g. Optimize my Docker images to reduce build times and size +terminal-input-agent-hint-ab-testing = Zaplex anything e.g. Set up A/B testing infrastructure for my web application terminal-input-steer-agent-hint = Steer the running agent terminal-input-steer-agent-backspace-hint = Steer the running agent, or backspace to exit terminal-input-follow-up-hint = Ask a follow up @@ -452,7 +452,7 @@ terminal-message-open-plan = {" "}open plan terminal-starting-shell = Starting shell... terminal-input-no-skills-found = No skills found terminal-model-specs-title = Model Specs -terminal-model-specs-description = Zap's benchmarks for how well a model performs in our harness, the rate at which it consumes credits, and task speed. +terminal-model-specs-description = Zaplex's benchmarks for how well a model performs in our harness, the rate at which it consumes credits, and task speed. terminal-model-specs-reasoning-level-title = Reasoning level terminal-model-specs-reasoning-level-description = Increased reasoning levels consume more credits and have higher latency, but higher performance for complicated tasks. terminal-model-auto-mode-title = Auto mode @@ -518,7 +518,7 @@ env-vars-variables-label = Variables # Files: crates/onboarding/src/callout/view.rs # ============================================================================= -onboarding-callout-meet-input-title = Meet the Zap input +onboarding-callout-meet-input-title = Meet the Zaplex input onboarding-callout-meet-input-text-prefix = Your terminal input accepts both terminal commands and agent prompts and automatically detects which you're using. Use onboarding-callout-meet-input-text-suffix = to lock the input to Agent mode (natural language) or Terminal mode (commands). onboarding-callout-talk-agent-title = Talk to the agent @@ -533,9 +533,9 @@ onboarding-callout-meet-terminal-text-suffix = to start or send to the agent. onboarding-callout-nl-overrides-title = Natural language overrides onboarding-callout-nl-overrides-text-prefix = You can always override any auto-detection using onboarding-callout-nl-support-title = Natural language support -onboarding-callout-nl-support-text-prefix = Natural language input is off by default. If enabled, you can type requests in plain English and Zap will autodetect queries for the agent. You can always override them using +onboarding-callout-nl-support-text-prefix = Natural language input is off by default. If enabled, you can type requests in plain English and Zaplex will autodetect queries for the agent. You can always override them using onboarding-callout-enable-nl-detection = Enable Natural Language Detection -onboarding-callout-new-agent-title = Introducing Zap's new agent experience +onboarding-callout-new-agent-title = Introducing Zaplex's new agent experience onboarding-callout-new-agent-text = Agent conversations are now their own scoped view outside of your terminal. Simply hit ESC to return to the terminal at any point. onboarding-callout-updated-agent-input-title = Updated agent input onboarding-callout-updated-agent-input-project-text = Your agent input will detect natural language as well as commands by default. Use ! to lock the input in bash mode to write commands.\n\nSubmit the query below to have the agent initialize this project, or ⊗ to clear the input and start your own! @@ -550,9 +550,9 @@ onboarding-callout-back-terminal = Back to terminal # ============================================================================= language-widget-label = Language -language-widget-secondary = Restart Zap for the change to fully take effect. +language-widget-secondary = Restart Zaplex for the change to fully take effect. language-restart-required-title = Language changed -language-restart-required-body = Zap's UI language has been updated. Some text will switch immediately, but a full restart is required for the change to take effect everywhere. +language-restart-required-body = Zaplex's UI language has been updated. Some text will switch immediately, but a full restart is required for the change to take effect everywhere. # ============================================================================= # SECTION: settings (Owner: agent-settings) @@ -564,7 +564,7 @@ language-restart-required-body = Zap's UI language has been updated. Some text w # Sidebar / SettingsSection labels (Display impl) settings-section-about = About -# Zap: settings-section-account removed alongside the Account settings page. +# Zaplex: settings-section-account removed alongside the Account settings page. settings-section-mcp-servers = MCP Servers settings-section-billing-and-usage = Billing and usage settings-section-appearance = Appearance @@ -572,12 +572,12 @@ settings-section-features = Features settings-section-keybindings = Keyboard shortcuts settings-section-referrals = Referrals settings-section-shared-blocks = Shared blocks -settings-section-warp-drive = Zap Drive -settings-section-warpify = Warpify +settings-section-warp-drive = Zaplex Drive +settings-section-zaplexify = Zaplexify settings-section-network = Network settings-section-cloud-sync = Cloud Sync settings-section-ai = AI -settings-section-warp-agent = Zap Agent +settings-section-warp-agent = Zaplex Agent settings-section-agent-profiles = Profiles settings-section-agent-mcp-servers = MCP servers settings-section-agent-providers = Providers @@ -607,11 +607,11 @@ settings-debug-hide-inband-blocks = Hide in-band command blocks # 命名前缀:settings-about-* / settings-main-* # about_page.rs -settings-about-copyright = Copyright 2026 Zap +settings-about-copyright = Copyright 2026 Zaplex settings-about-automatic-updates-label = Automatic updates -settings-about-automatic-updates-description = When enabled, Zap checks for new versions in the background and downloads the installer to a local cache. The currently running Zap is not touched until you click "Install now" to launch the installer yourself. +settings-about-automatic-updates-description = When enabled, Zaplex checks for new versions in the background and downloads the installer to a local cache. The currently running Zaplex is not touched until you click "Install now" to launch the installer yourself. settings-about-update-checking = Checking for updates… -settings-about-update-up-to-date = Zap is up to date. +settings-about-update-up-to-date = Zaplex is up to date. settings-about-update-available = New version { $version } is available. settings-about-update-downloading = Downloading { $version }… { $progress } settings-about-update-downloading-init = Downloading { $version }… @@ -619,15 +619,15 @@ settings-about-update-ready = { $version } is downloaded and ready to install. settings-about-update-check-now = Check for updates settings-about-update-open-release = Download from GitHub settings-about-update-install-now = Install now -settings-about-update-install-hint-macos = The installer will open — drag Zap into your Applications folder to finish. +settings-about-update-install-hint-macos = The installer will open — drag Zaplex into your Applications folder to finish. settings-about-update-install-hint-windows = The setup wizard will launch — follow the prompts to finish the upgrade. -settings-about-update-install-hint-linux = The AppImage will be replaced in place and Zap will restart. +settings-about-update-install-hint-linux = The AppImage will be replaced in place and Zaplex will restart. settings-about-export-logs = Export logs… settings-about-export-logs-description = Bundles recent app logs (and MCP / update logs when present) plus a diagnostic summary into a zip you choose where to save, so you can share it for troubleshooting. settings-about-export-logs-success = Logs exported to { $path } settings-about-export-logs-failure = Failed to export logs: { $error } -# Zap: main_page.rs (Account / version / autoupdate) strings removed alongside +# Zaplex: main_page.rs (Account / version / autoupdate) strings removed alongside # the Account settings page. The About page now owns version / update CTAs. @@ -675,7 +675,7 @@ settings-mcp-install-modal-install = Install settings-mcp-install-modal-no-server = No MCP server selected # ---- list_page.rs ---- -settings-mcp-list-description = Add MCP servers to extend the Zap Agent's capabilities. MCP servers expose data sources or tools to agents through a standardized interface, essentially acting like plugins. Add a custom server, or use the presets to get started with popular servers. +settings-mcp-list-description = Add MCP servers to extend the Zaplex Agent's capabilities. MCP servers expose data sources or tools to agents through a standardized interface, essentially acting like plugins. Add a custom server, or use the presets to get started with popular servers. settings-mcp-list-learn-more = Learn more. settings-mcp-list-empty-state = Once you add a MCP server, it will be shown here. settings-mcp-list-no-search-results = No search results found @@ -688,9 +688,9 @@ settings-mcp-list-template-available-to-install = Available to install settings-mcp-list-file-based-detected = Detected from config file settings-mcp-list-toast-server-updated = MCP server updated settings-mcp-list-section-my-mcps = My MCPs -settings-mcp-list-section-shared-by-warp-and-team = Available from Zap and { $name } -settings-mcp-list-section-shared-by-warp-and-other-devices = Shared by Zap and from other devices -settings-mcp-list-section-shared-from-warp = Shared from Zap +settings-mcp-list-section-shared-by-warp-and-team = Available from Zaplex and { $name } +settings-mcp-list-section-shared-by-warp-and-other-devices = Shared by Zaplex and from other devices +settings-mcp-list-section-shared-from-warp = Shared from Zaplex settings-mcp-list-section-detected-from = Detected from { $provider } settings-mcp-list-chip-global = global settings-mcp-list-chip-shared-by-creator = Shared by: { $creator } @@ -729,7 +729,7 @@ settings-mcp-update-modal-no-updates = No updates available # 此锚点下放 settings_view/platform_page.rs 字符串 # 命名前缀:settings-platform-* settings-platform-section-title = Agent API Keys -settings-platform-description = Create and manage API keys to allow local agents to access your Zap account. +settings-platform-description = Create and manage API keys to allow local agents to access your Zaplex account. For more information, visit the settings-platform-documentation-link = Documentation. settings-platform-create-button = + Create API Key @@ -746,10 +746,10 @@ settings-platform-value-never = Never settings-platform-scope-personal = Personal settings-platform-scope-team = Team settings-platform-zero-state-title = No API Keys -settings-platform-zero-state-description = Create a key to manage external access to Zap -settings-platform-create-api-key-description-personal = This API key is tied to your user and can make requests against your Zap account. +settings-platform-zero-state-description = Create a key to manage external access to Zaplex +settings-platform-create-api-key-description-personal = This API key is tied to your user and can make requests against your Zaplex account. settings-platform-create-api-key-description-team = This API key is tied to your team and can make requests on behalf of your team. -settings-platform-create-api-key-name-placeholder = Zap API Key +settings-platform-create-api-key-name-placeholder = Zaplex API Key settings-platform-create-api-key-expiration-one-day = 1 day settings-platform-create-api-key-expiration-thirty-days = 30 days settings-platform-create-api-key-expiration-ninety-days = 90 days @@ -780,8 +780,8 @@ settings-keybindings-subheader = Configure keyboard shortcuts settings-keybindings-command-column = Command # --- ANCHOR-SUB-REFERRALS (agent-settings-referrals) --- -settings-referrals-page-title = Invite a friend to Zap -settings-referrals-anonymous-header = Referral program is unavailable in local Zap builds +settings-referrals-page-title = Invite a friend to Zaplex +settings-referrals-anonymous-header = Referral program is unavailable in local Zaplex builds settings-referrals-sign-up = Unavailable locally settings-referrals-link-label = Link settings-referrals-email-label = Email @@ -795,7 +795,7 @@ settings-referrals-email-success-toast = Successfully sent emails. settings-referrals-email-failure-toast = Failed to send emails. Please try again. settings-referrals-email-empty-error = Please enter an email. settings-referrals-email-invalid-error = Please ensure the following email is valid: { $email } -settings-referrals-reward-intro = Get exclusive Zap goodies when you refer someone* +settings-referrals-reward-intro = Get exclusive Zaplex goodies when you refer someone* settings-referrals-claimed-count-singular = Current referral settings-referrals-claimed-count-plural = Current referrals settings-referrals-terms-link = Certain restrictions apply. @@ -809,25 +809,25 @@ settings-referrals-reward-hoodie = Hoodie settings-referrals-reward-hydroflask = Premium Hydro Flask settings-referrals-reward-backpack = Backpack -# --- ANCHOR-SUB-WARPIFY (agent-settings-warpify) --- -settings-warpify-page-title = Warpify -settings-warpify-description-prefix = Configure whether Zap attempts to "Warpify" (add support for blocks, input modes, etc) certain shells. -settings-warpify-learn-more = Learn more -settings-warpify-section-subshells = Subshells -settings-warpify-section-subshells-subtitle = Subshells supported: bash, zsh, and fish. -settings-warpify-section-ssh = SSH -settings-warpify-section-ssh-subtitle = Warpify your interactive SSH sessions. -settings-warpify-added-commands = Added commands -settings-warpify-denylisted-commands = Denylisted commands -settings-warpify-denylisted-hosts = Denylisted hosts -settings-warpify-command-placeholder = command (supports regex) -settings-warpify-host-placeholder = host (supports regex) -settings-warpify-enable-ssh = Warpify SSH Sessions -settings-warpify-install-ssh-extension = Install SSH extension -settings-warpify-install-ssh-extension-description = Controls the installation behavior for Zap's SSH extension when a remote host doesn't have it installed. -settings-warpify-use-tmux = Use Tmux Warpification -settings-warpify-tmux-description = The tmux ssh wrapper works in many situations where the default one does not, but may require you to hit a button to warpify. Takes effect in new tabs. -settings-warpify-ssh-tmux-toggle-binding-label = SSH session detection for Warpification +# --- ANCHOR-SUB-ZAPLEXIFY (agent-settings-zaplexify) --- +settings-zaplexify-page-title = Zaplexify +settings-zaplexify-description-prefix = Configure whether Zaplex attempts to "Zaplexify" (add support for blocks, input modes, etc) certain shells. +settings-zaplexify-learn-more = Learn more +settings-zaplexify-section-subshells = Subshells +settings-zaplexify-section-subshells-subtitle = Subshells supported: bash, zsh, and fish. +settings-zaplexify-section-ssh = SSH +settings-zaplexify-section-ssh-subtitle = Zaplexify your interactive SSH sessions. +settings-zaplexify-added-commands = Added commands +settings-zaplexify-denylisted-commands = Denylisted commands +settings-zaplexify-denylisted-hosts = Denylisted hosts +settings-zaplexify-command-placeholder = command (supports regex) +settings-zaplexify-host-placeholder = host (supports regex) +settings-zaplexify-enable-ssh = Zaplexify SSH Sessions +settings-zaplexify-install-ssh-extension = Install SSH extension +settings-zaplexify-install-ssh-extension-description = Controls the installation behavior for Zaplex's SSH extension when a remote host doesn't have it installed. +settings-zaplexify-use-tmux = Use Tmux Zaplexification +settings-zaplexify-tmux-description = The tmux ssh wrapper works in many situations where the default one does not, but may require you to hit a button to zaplexify. Takes effect in new tabs. +settings-zaplexify-ssh-tmux-toggle-binding-label = SSH session detection for Zaplexification # --- ANCHOR-SUB-NETWORK (network-settings) --- # Global HTTP proxy settings page (see Issue #72). @@ -911,7 +911,7 @@ settings-cloud-sync-auto-sync-description = Automatically upload on config chang # --- ANCHOR-SUB-AI-PAGE (agent-settings-ai-page) --- # Section / sub-headers -settings-ai-warp-agent-header = Zap Agent +settings-ai-warp-agent-header = Zaplex Agent settings-ai-active-ai-section = Active AI settings-ai-input-section = Input settings-ai-mcp-servers-section = MCP Servers @@ -951,23 +951,23 @@ settings-ai-execute-commands = Execute commands settings-ai-interact-running-commands = Interact with running commands settings-ai-call-mcp-servers = Call MCP servers settings-ai-command-denylist = Command denylist -settings-ai-command-denylist-description = Regular expressions to match commands that the Zap Agent should always ask permission to execute. +settings-ai-command-denylist-description = Regular expressions to match commands that the Zaplex Agent should always ask permission to execute. settings-ai-command-allowlist = Command allowlist -settings-ai-command-allowlist-description = Regular expressions to match commands that can be automatically executed by the Zap Agent. +settings-ai-command-allowlist-description = Regular expressions to match commands that can be automatically executed by the Zaplex Agent. settings-ai-directory-allowlist = Directory allowlist settings-ai-directory-allowlist-description = Give the agent file access to certain directories. settings-ai-mcp-allowlist = MCP allowlist -settings-ai-mcp-allowlist-description = Allow the Zap Agent to call these MCP servers. +settings-ai-mcp-allowlist-description = Allow the Zaplex Agent to call these MCP servers. settings-ai-mcp-denylist = MCP denylist -settings-ai-mcp-denylist-description = The Zap Agent will always ask for permission before calling any MCP servers on this list. +settings-ai-mcp-denylist-description = The Zaplex Agent will always ask for permission before calling any MCP servers on this list. settings-ai-info-banner-managed-by-workspace = Some of your permissions are managed by your workspace. # Models / Profiles settings-ai-base-model = Base model -settings-ai-base-model-description = This model serves as the primary engine behind the Zap Agent. It powers most interactions and invokes other models for tasks like planning or code generation when necessary. Zap may automatically switch to alternate models based on model availability or for auxiliary tasks such as conversation summarization. +settings-ai-base-model-description = This model serves as the primary engine behind the Zaplex Agent. It powers most interactions and invokes other models for tasks like planning or code generation when necessary. Zaplex may automatically switch to alternate models based on model availability or for auxiliary tasks such as conversation summarization. settings-ai-show-model-picker-in-prompt = Show model picker in prompt settings-ai-codebase-context = Codebase Context -settings-ai-codebase-context-description = Allow the Zap Agent to generate an outline of your codebase that can be used for context. No code is ever stored on our servers. +settings-ai-codebase-context-description = Allow the Zaplex Agent to generate an outline of your codebase that can be used for context. No code is ever stored on our servers. settings-ai-add-profile = Add Profile settings-ai-agents-description = Set the boundaries for how your Agent operates. Choose what it can access, how much autonomy it has, and when it must ask for your approval. You can also fine-tune behavior around natural language input, codebase awareness, and more. settings-ai-profiles-description = Profiles let you define how your Agent operates — from the actions it can take and when it needs approval, to the models it uses for tasks like coding and planning. You can also scope them to individual projects. @@ -1004,7 +1004,7 @@ settings-ai-rules-label = Rules settings-ai-suggested-rules-label = Suggested Rules settings-ai-suggested-rules-description = Let AI suggest rules to save based on your interactions. settings-ai-manage-rules = Manage rules -settings-ai-rules-description = Rules help the Zap Agent follow your conventions, whether for codebases or specific workflows. +settings-ai-rules-description = Rules help the Zaplex Agent follow your conventions, whether for codebases or specific workflows. # Voice settings-ai-voice-input-label = Voice Input @@ -1042,12 +1042,12 @@ settings-ai-coding-agent-select-header = Select coding agent # Experimental / Agent settings-ai-cloud-agent-computer-use = Computer use in agents -settings-ai-cloud-agent-computer-use-description = Enable computer use in agent conversations started from the Zap app. +settings-ai-cloud-agent-computer-use-description = Enable computer use in agent conversations started from the Zaplex app. # AWS Bedrock settings-ai-aws-bedrock-toggle = Use AWS Bedrock credentials -settings-ai-aws-bedrock-description = Zap loads and sends local AWS CLI credentials for Bedrock-supported models. -settings-ai-aws-bedrock-description-managed = Zap loads and sends local AWS CLI credentials for Bedrock-supported models. This setting is managed by your organization. +settings-ai-aws-bedrock-description = Zaplex loads and sends local AWS CLI credentials for Bedrock-supported models. +settings-ai-aws-bedrock-description-managed = Zaplex loads and sends local AWS CLI credentials for Bedrock-supported models. This setting is managed by your organization. settings-ai-aws-login-command = Login Command settings-ai-aws-profile = AWS Profile settings-ai-aws-auto-login = Automatically run login command @@ -1078,9 +1078,9 @@ settings-features-show-changelog-after-update = Show changelog toast after updat settings-features-mouse-scroll-multiplier = Lines scrolled by mouse wheel interval settings-features-auto-open-code-review = Auto open code review panel settings-features-max-rows-per-block = Maximum rows in a block -settings-features-ssh-wrapper = Zap SSH Wrapper -settings-features-ssh-auto-discovery = Auto-discover SSH hosts -settings-features-receive-desktop-notifications = Receive desktop notifications from Zap +settings-features-ssh-wrapper = Zaplex SSH Wrapper +settings-features-ssh-auto-discovery = Suggest hosts from ~/.ssh/config when adding +settings-features-receive-desktop-notifications = Receive desktop notifications from Zaplex settings-features-show-in-app-agent-notifications = Show in-app agent notifications settings-features-confirm-close-shared-session = Confirm before closing read-only session settings-features-global-hotkey-label = Global hotkey: @@ -1133,7 +1133,7 @@ settings-features-working-dir-custom = Custom directory settings-features-undo-close-enable = Enable reopening of closed sessions settings-features-undo-close-grace-period = Grace period (seconds) settings-features-configure-global-hotkey = Configure Global Hotkey -settings-features-make-default-terminal = Make Zap the default terminal +settings-features-make-default-terminal = Make Zaplex the default terminal settings-features-pin-top = Pin to top settings-features-pin-bottom = Pin to bottom settings-features-pin-left = Pin to left @@ -1161,7 +1161,7 @@ settings-features-see-docs = See docs. settings-features-allowed-values-1-20 = Allowed Values: 1-20 settings-features-supports-floating-1-20 = Supports floating point values between 1 and 20. settings-features-auto-open-code-review-description = When this setting is on, the code review panel will open on the first accepted diff of a conversation -settings-features-default-terminal-current = Zap is the default terminal +settings-features-default-terminal-current = Zaplex is the default terminal settings-features-takes-effect-new-sessions = This change will take effect in new sessions settings-features-seconds = seconds settings-features-vim-system-clipboard = Set unnamed register as system clipboard @@ -1177,7 +1177,7 @@ settings-features-new-tab-placement = New tab placement settings-features-linux-selection-clipboard-tooltip = Whether the Linux primary clipboard should be supported. settings-features-changes-apply-new-windows = Changes will apply to new windows. settings-features-wayland-description = Enabling this setting disables global hotkey support. When disabled, text may be blurry if your Wayland compositor is using fraction scaling (ex: 125%). -settings-features-restart-warp-to-apply = Restart Zap for changes to take effect. +settings-features-restart-warp-to-apply = Restart Zaplex for changes to take effect. # --- ANCHOR-SUB-SETTINGS-PAGE-NAV (agent-settings-page-nav) --- # 此锚点下放 settings_view/{settings_page,nav,delete_environment_confirmation_dialog,directory_color_add_picker,pane_manager}.rs 字符串 @@ -1201,7 +1201,7 @@ settings-color-picker-add-directory-color = Add directory color # ---- settings_file_footer.rs ---- settings-footer-open-file = Open settings file settings-footer-alert-open-file = Open file -settings-footer-alert-fix-with-oz = Fix with Oz +settings-footer-alert-fix-with-oz = Fix with AI # --- ANCHOR-SUB-CODE (agent-settings-code) --- settings-code-auto-open-review-panel = Auto open code review panel @@ -1260,7 +1260,7 @@ settings-exec-profile-editor-workspace-override-tooltip = This option is enforce settings-exec-profile-editor-section-models = MODELS settings-exec-profile-editor-section-permissions = PERMISSIONS settings-exec-profile-editor-base-model = Base model -settings-exec-profile-editor-base-model-desc = This model serves as the primary engine behind the agent. It powers most interactions and invokes other models for tasks like planning or code generation when necessary. Zap may automatically switch to alternate models based on model availability or for auxiliary tasks such as conversation summarization. +settings-exec-profile-editor-base-model-desc = This model serves as the primary engine behind the agent. It powers most interactions and invokes other models for tasks like planning or code generation when necessary. Zaplex may automatically switch to alternate models based on model availability or for auxiliary tasks such as conversation summarization. settings-exec-profile-editor-full-terminal-use-model = Full terminal use model settings-exec-profile-editor-full-terminal-use-model-desc = The model used when the agent operates inside interactive terminal applications like database shells, debuggers, REPLs, or dev servers—reading live output and writing commands to the PTY. settings-exec-profile-editor-title-model = Title generation model @@ -1283,13 +1283,13 @@ settings-exec-profile-editor-call-web-tools-desc = The agent may use web search settings-exec-profile-editor-directory-allowlist = Directory allowlist settings-exec-profile-editor-directory-allowlist-desc = Give the agent file access to certain directories. settings-exec-profile-editor-command-allowlist = Command allowlist -settings-exec-profile-editor-command-allowlist-desc = Regular expressions to match commands that can be automatically executed by Oz. +settings-exec-profile-editor-command-allowlist-desc = Regular expressions to match commands that can be automatically executed by the agent. settings-exec-profile-editor-command-denylist = Command denylist -settings-exec-profile-editor-command-denylist-desc = Regular expressions to match commands that Oz should always ask permission to execute. +settings-exec-profile-editor-command-denylist-desc = Regular expressions to match commands that the agent should always ask permission to execute. settings-exec-profile-editor-mcp-allowlist = MCP allowlist -settings-exec-profile-editor-mcp-allowlist-desc = MCP servers that are allowed to be called by Oz. +settings-exec-profile-editor-mcp-allowlist-desc = MCP servers that are allowed to be called by the agent. settings-exec-profile-editor-mcp-denylist = MCP denylist -settings-exec-profile-editor-mcp-denylist-desc = MCP servers that are not allowed to be called by Oz. +settings-exec-profile-editor-mcp-denylist-desc = MCP servers that are not allowed to be called by the agent. # ---- agent_assisted_environment_modal ---- settings-env-modal-add-repo = Add repo @@ -1324,7 +1324,7 @@ settings-show-blocks-unshare-failed = Failed to unshare block. Please try again. settings-show-blocks-confirm-dialog-title = Unshare block settings-show-blocks-confirm-dialog-text = Are you sure you want to unshare this block? - It will no longer be accessible by link and will be permanently deleted from Zap servers. + It will no longer be accessible by link and will be permanently deleted from Zaplex servers. settings-show-blocks-confirm-cancel = Cancel settings-show-blocks-confirm-unshare = Unshare @@ -1356,7 +1356,7 @@ settings-appearance-theme-sync-os-description = Automatically switch between lig # Custom App Icon widget settings-appearance-custom-icon-label = Customize your app icon settings-appearance-custom-icon-bundle-warning = Changing the app icon requires the app to be bundled. -settings-appearance-custom-icon-restart-warning = You may need to restart Zap for MacOS to apply the preferred icon style. +settings-appearance-custom-icon-restart-warning = You may need to restart Zaplex for MacOS to apply the preferred icon style. # Window widgets settings-appearance-window-custom-size-label = Open new windows with custom size @@ -1374,10 +1374,10 @@ settings-appearance-tools-panel-consistent-label = Tools panel visibility is con # Input settings-appearance-input-type-label = Input type -settings-appearance-input-type-warp = Zap +settings-appearance-input-type-warp = Zaplex settings-appearance-input-type-shell = Shell (PS1) settings-appearance-input-position-label = Input position -settings-appearance-input-mode-pinned-bottom = Pin to the bottom (Zap mode) +settings-appearance-input-mode-pinned-bottom = Pin to the bottom (Zaplex mode) settings-appearance-input-mode-pinned-top = Pin to the top (Reverse mode) settings-appearance-input-mode-waterfall = Start at the top (Classic mode) @@ -1464,8 +1464,8 @@ settings-environments-page-description = Environments define where your ambient settings-environments-search-placeholder = Search environments... settings-environments-no-matches = No environments match your search. settings-environments-section-personal = Personal -settings-environments-section-team-default = Provided by Zap and this device -settings-environments-section-team-named = Shared by Zap and { $team } +settings-environments-section-team-default = Provided by Zaplex and this device +settings-environments-section-team-named = Shared by Zaplex and { $team } settings-environments-env-id-prefix = Env ID: { $id } settings-environments-detail-image = Image: { $image } settings-environments-detail-repos = Repos: { $repos } @@ -1614,7 +1614,7 @@ quit-warning-title-pane = Close pane? quit-warning-title-tab-singular = Close tab? quit-warning-title-tab-plural = Close tabs? quit-warning-title-window = Close window? -quit-warning-title-app = Quit Zap? +quit-warning-title-app = Quit Zaplex? quit-warning-title-editor-tab = Save changes? # ---- Buttons ---- @@ -1654,7 +1654,7 @@ quit-warning-unsaved-editor-tab = Do you want to save the changes you made to { quit-warning-unsaved-editor-tab-fallback-name = this file # --- ANCHOR-SUB-RULES-PAGE (agent-rules-page) --- -# Manage Rules 页面(Zap Drive 中的 AI Fact Collection)。 +# Manage Rules 页面(Zaplex Drive 中的 AI Fact Collection)。 rules-collection-name = Rules # --- ANCHOR-SUB-KEYBINDING-DESC (agent-keybinding-descriptions) --- @@ -1741,13 +1741,13 @@ keybinding-desc-workspace-toggle-vertical-tabs-menu = Toggle Vertical Tabs Panel keybinding-desc-workspace-left-panel-agent-conversations = Left Panel: Agent conversations keybinding-desc-workspace-left-panel-project-explorer = Left Panel: Project explorer keybinding-desc-workspace-left-panel-global-search = Left Panel: Global search -keybinding-desc-workspace-left-panel-warp-drive = Left Panel: Zap Drive +keybinding-desc-workspace-left-panel-warp-drive = Left Panel: Zaplex Drive keybinding-desc-workspace-left-panel-ssh-manager = Left Panel: SSH Manager keybinding-desc-workspace-left-panel-skill-manager = Left Panel: Skill Manager keybinding-desc-workspace-open-global-search = Open global search keybinding-desc-workspace-open-global-search-menu = Global Search -keybinding-desc-workspace-toggle-warp-drive = Toggle Zap Drive -keybinding-desc-workspace-toggle-warp-drive-menu = Zap Drive +keybinding-desc-workspace-toggle-warp-drive = Toggle Zaplex Drive +keybinding-desc-workspace-toggle-warp-drive-menu = Zaplex Drive keybinding-desc-workspace-toggle-conversation-list-view = Toggle Agent conversation list view keybinding-desc-workspace-toggle-conversation-list-view-menu = Agent conversation list view keybinding-desc-workspace-close-panel = Close focused panel @@ -1759,7 +1759,7 @@ keybinding-desc-workspace-toggle-navigation-palette = Toggle navigation palette keybinding-desc-workspace-toggle-navigation-palette-menu = Navigation Palette keybinding-desc-workspace-toggle-launch-config-palette = Launch configuration palette keybinding-desc-workspace-toggle-files-palette = Toggle Files Palette -keybinding-desc-workspace-search-drive = Search Zap Drive +keybinding-desc-workspace-search-drive = Search Zaplex Drive keybinding-desc-workspace-move-tab-left = Move tab left keybinding-desc-workspace-move-tab-up = move tab up keybinding-desc-workspace-move-tab-right = Move tab right @@ -1772,7 +1772,7 @@ keybinding-desc-workspace-toggle-block-snackbar = Toggle sticky command header # Window / tab close keybinding-desc-workspace-rename-active-tab = Rename the current tab -keybinding-desc-workspace-terminate-app = Quit Zap +keybinding-desc-workspace-terminate-app = Quit Zaplex keybinding-desc-workspace-close-window = Close Window keybinding-desc-workspace-close-active-tab = Close the current tab keybinding-desc-workspace-close-other-tabs = Close other tabs @@ -1790,12 +1790,12 @@ keybinding-desc-workspace-view-changelog = View latest changelog # Resource center / Drive export / CLI keybinding-desc-workspace-toggle-resource-center = Toggle resource center -keybinding-desc-workspace-export-all-warp-drive-objects = Export all Zap Drive objects -keybinding-desc-workspace-install-cli = Install Oz CLI command -keybinding-desc-workspace-uninstall-cli = Uninstall Oz CLI command +keybinding-desc-workspace-export-all-warp-drive-objects = Export all Zaplex Drive objects +keybinding-desc-workspace-install-cli = Install Zaplex CLI command +keybinding-desc-workspace-uninstall-cli = Uninstall Zaplex CLI command # AI assistant / agents -keybinding-desc-workspace-toggle-ai-assistant = Toggle Zap AI +keybinding-desc-workspace-toggle-ai-assistant = Toggle Zaplex AI # Env vars / prompts keybinding-desc-workspace-create-personal-env-vars = Create new personal environment variables @@ -1819,7 +1819,7 @@ keybinding-desc-workspace-toggle-notification-mailbox = Toggle notification mail # Settings pages keybinding-desc-workspace-show-settings = Open Settings keybinding-desc-workspace-show-settings-menu = Settings -# Zap: keybinding-desc-workspace-show-settings-account removed alongside the +# Zaplex: keybinding-desc-workspace-show-settings-account removed alongside the # Account settings page. keybinding-desc-workspace-show-settings-appearance = Open Settings: Appearance keybinding-desc-workspace-show-settings-appearance-menu = Appearance... @@ -1829,9 +1829,9 @@ keybinding-desc-workspace-show-settings-shared-blocks-menu = View Shared Blocks. keybinding-desc-workspace-show-settings-keyboard-shortcuts = Open Settings: Keyboard Shortcuts keybinding-desc-workspace-show-settings-keyboard-shortcuts-menu = Configure Keyboard Shortcuts... keybinding-desc-workspace-show-settings-about = Open Settings: About -keybinding-desc-workspace-show-settings-about-menu = About Zap -keybinding-desc-workspace-show-settings-warpify = Open Settings: Warpify -keybinding-desc-workspace-show-settings-warpify-menu = Configure Warpify... +keybinding-desc-workspace-show-settings-about-menu = About Zaplex +keybinding-desc-workspace-show-settings-zaplexify = Open Settings: Zaplexify +keybinding-desc-workspace-show-settings-zaplexify-menu = Configure Zaplexify... keybinding-desc-workspace-show-settings-ai = Open Settings: AI keybinding-desc-workspace-show-settings-code = Open Settings: Code keybinding-desc-workspace-show-settings-referrals = Open Settings: Referrals @@ -1843,8 +1843,8 @@ keybinding-desc-workspace-open-settings-file = Open settings file keybinding-desc-workspace-link-to-slack = Join our Slack community (opens external link) keybinding-desc-workspace-link-to-user-docs = View user docs (opens external link) keybinding-desc-workspace-send-feedback = Send feedback (opens external link) -keybinding-desc-workspace-send-feedback-oz = Send feedback with Oz -keybinding-desc-workspace-view-logs = View Zap logs +keybinding-desc-workspace-send-feedback-oz = Send feedback +keybinding-desc-workspace-view-logs = View Zaplex logs keybinding-desc-workspace-link-to-privacy-policy = View privacy policy (opens external link) # Input / terminal / project bindings (registered outside workspace/mod.rs) @@ -1863,10 +1863,10 @@ keybinding-desc-workspace-panic = Trigger a panic (for testing local panic loggi keybinding-desc-workspace-open-view-tree-debugger = Open view tree debugger keybinding-desc-workspace-view-first-time-user-experience = [Debug] View first-time user experience keybinding-desc-workspace-undismiss-aws-login-banner = [Debug] Un-dismiss AWS login banner -keybinding-desc-workspace-open-oz-launch-modal = [Debug] Open Oz Launch Modal -keybinding-desc-workspace-reset-oz-launch-modal-state = [Debug] Reset Oz Launch Modal State -keybinding-desc-workspace-open-zap-launch-modal = [Debug] Open Zap Launch Modal -keybinding-desc-workspace-reset-zap-launch-modal-state = [Debug] Reset Zap Launch Modal State +keybinding-desc-workspace-open-oz-launch-modal = [Debug] Open Agent Launch Modal +keybinding-desc-workspace-reset-oz-launch-modal-state = [Debug] Reset Agent Launch Modal State +keybinding-desc-workspace-open-zap-launch-modal = [Debug] Open Zaplex Launch Modal +keybinding-desc-workspace-reset-zap-launch-modal-state = [Debug] Reset Zaplex Launch Modal State keybinding-desc-workspace-install-opencode-warp-plugin = [Debug] Install OpenCode Warp plugin keybinding-desc-workspace-use-local-opencode-warp-plugin = [Debug] Use local OpenCode Warp plugin (testing only) keybinding-desc-workspace-open-session-config-modal = [Debug] Open Session Config Modal @@ -1875,7 +1875,7 @@ keybinding-desc-workspace-sample-process = Sample Process keybinding-desc-workspace-dump-heap-profile = Dump heap profile (can only be done once) # Terminal input bindings -keybinding-desc-input-show-network-log = Show Zap network log +keybinding-desc-input-show-network-log = Show Zaplex network log keybinding-desc-input-clear-screen = Clear screen keybinding-desc-input-toggle-classic-completions = (Experimental) Toggle classic completions mode keybinding-desc-input-command-search = Command Search @@ -1890,8 +1890,8 @@ keybinding-desc-input-clear-and-reset-ai-context-menu-query = Clear and reset AI # Terminal view bindings keybinding-desc-terminal-alternate-paste = Alternate terminal paste keybinding-desc-terminal-toggle-cli-agent-rich-input = Toggle CLI Agent Rich Input -keybinding-desc-terminal-warpify-subshell = Warpify subshell -keybinding-desc-terminal-warpify-ssh-session = Warpify ssh session +keybinding-desc-terminal-zaplexify-subshell = Zaplexify subshell +keybinding-desc-terminal-zaplexify-ssh-session = Zaplexify ssh session keybinding-desc-terminal-accept-prompt-suggestion = Accept Prompt Suggestion keybinding-desc-terminal-cancel-process-windows = Copy text or cancel active process keybinding-desc-terminal-cancel-process = Cancel active process @@ -2058,9 +2058,9 @@ keybinding-desc-welcome-terminal-session = Terminal session keybinding-desc-welcome-add-repository = Add repository # AI assistant panel binding desc -keybinding-desc-ai-assistant-close = Close Zap AI -keybinding-desc-ai-assistant-focus-terminal-input = Focus Terminal Input From Zap AI -keybinding-desc-ai-assistant-restart = Restart Zap AI +keybinding-desc-ai-assistant-close = Close Zaplex AI +keybinding-desc-ai-assistant-focus-terminal-input = Focus Terminal Input From Zaplex AI +keybinding-desc-ai-assistant-restart = Restart Zaplex AI # Code review binding desc keybinding-desc-code-review-save-all = Save all unsaved files in code review @@ -2129,9 +2129,9 @@ keybinding-desc-conversation-details-copy = Copy # Terminal extras binding desc keybinding-desc-terminal-show-history = Show History -keybinding-desc-terminal-ask-ai-selection = Ask Zap AI about Selection -keybinding-desc-terminal-ask-ai-last-block = Ask Zap AI about last block -keybinding-desc-terminal-ask-ai = Ask Zap AI +keybinding-desc-terminal-ask-ai-selection = Ask Zaplex AI about Selection +keybinding-desc-terminal-ask-ai-last-block = Ask Zaplex AI about last block +keybinding-desc-terminal-ask-ai = Ask Zaplex AI keybinding-desc-terminal-load-agent-conversation = Load agent mode conversation (from debug link in clipboard) keybinding-desc-terminal-toggle-session-recording = Toggle PTY Recording for Session @@ -2174,7 +2174,7 @@ rules-add-button = Add rules-init-project-button = Initialize Project # --- Agent view zero-state + message bar --- -agent-zero-state-title = New Oz agent conversation +agent-zero-state-title = New agent conversation agent-zero-state-description = Send a prompt below to start a new conversation agent-zero-state-description-with-location = Send a prompt below to start a new conversation in `{ $location }` agent-zero-state-recent-activity = RECENT ACTIVITY @@ -2269,8 +2269,8 @@ toggle-suffix-syntax-highlighting = syntax highlighting toggle-suffix-audible-bell = audible terminal bell toggle-suffix-autosuggestions = autosuggestions toggle-suffix-autosuggestion-keybinding-hint = autosuggestion keybinding hint -toggle-suffix-ssh-wrapper = Zap SSH wrapper -toggle-suffix-ssh-auto-discovery = auto-discover SSH hosts +toggle-suffix-ssh-wrapper = Zaplex SSH wrapper +toggle-suffix-ssh-auto-discovery = suggest hosts from ~/.ssh/config when adding toggle-suffix-link-tooltip = show tooltip on click on links toggle-suffix-quit-warning = quit warning modal toggle-suffix-alias-expansion = alias expansion @@ -2302,10 +2302,10 @@ agent-thinking-display-never-show = Set agent thinking display: never show # --- ANCHOR-SUB-EXTERNAL-EDITOR (settings-external-editor) --- settings-external-editor-choose-default = Choose an editor to open file links settings-external-editor-choose-code-panels = Choose an editor to open files from the code review panel, project explorer, and global search -settings-external-editor-choose-layout = Choose a layout to open files in Zap +settings-external-editor-choose-layout = Choose a layout to open files in Zaplex settings-external-editor-tabbed-header = Group files into single editor pane settings-external-editor-tabbed-desc = When this setting is on, any files opened in the same tab will be automatically grouped into a single editor pane. -settings-external-editor-prefer-markdown = Open Markdown files in Zap's Markdown Viewer by default +settings-external-editor-prefer-markdown = Open Markdown files in Zaplex's Markdown Viewer by default settings-external-editor-layout-split-pane = Split Pane settings-external-editor-layout-new-tab = New Tab settings-external-editor-default-app = Default App @@ -2321,7 +2321,7 @@ menu-block-copy-url = Copy URL menu-block-copy-path = Copy path menu-block-show-in-finder = Show in Finder menu-block-show-containing-folder = Show containing folder -menu-block-open-in-warp = Open in Zap +menu-block-open-in-warp = Open in Zaplex menu-block-open-in-editor = Open in editor menu-block-insert-into-input = Insert into input menu-block-copy-command = Copy command @@ -2333,7 +2333,7 @@ menu-block-scroll-to-top-of-blocks = Scroll to top of blocks menu-block-scroll-to-bottom-of-block = Scroll to bottom of block menu-block-scroll-to-bottom-of-blocks = Scroll to bottom of blocks menu-block-save-as-workflow = Save as workflow -menu-block-ask-warp-ai = Ask Zap AI +menu-block-ask-warp-ai = Ask Zaplex AI menu-block-copy-output = Copy output menu-block-copy-filtered-output = Copy filtered output menu-block-toggle-block-filter = Toggle block filter @@ -2358,7 +2358,7 @@ menu-input-paste = Paste menu-input-select-all = Select all menu-input-command-search = Command search menu-input-ai-command-search = AI command search -menu-input-ask-warp-ai = Ask Zap AI +menu-input-ask-warp-ai = Ask Zaplex AI menu-input-save-as-workflow = Save as workflow menu-input-hide-hint-text = Hide input hint text menu-input-show-hint-text = Show input hint text @@ -2428,16 +2428,16 @@ menu-attach-as-agent-context = Attach as agent context slash-cmd-agent-desc = Start a new conversation slash-cmd-add-mcp-desc = Add new MCP server slash-cmd-pr-comments-desc = Pull GitHub PR review comments -slash-cmd-create-environment-desc = Create an Oz environment (Docker image + repos) via guided setup +slash-cmd-create-environment-desc = Create an agent environment (Docker image + repos) via guided setup slash-cmd-create-environment-hint = slash-cmd-docker-sandbox-desc = Create a new docker sandbox terminal session -slash-cmd-create-new-project-desc = Have Oz walk you through creating a new coding project +slash-cmd-create-new-project-desc = Have the agent walk you through creating a new coding project slash-cmd-create-new-project-hint = -slash-cmd-open-skill-desc = Open a skill's markdown file in Zap's built-in editor +slash-cmd-open-skill-desc = Open a skill's markdown file in Zaplex's built-in editor slash-cmd-skills-desc = Invoke a skill slash-cmd-add-prompt-desc = Add new Agent prompt slash-cmd-add-rule-desc = Add a new global rule for the agent -slash-cmd-open-file-desc = Open a file in Zap's code editor +slash-cmd-open-file-desc = Open a file in Zaplex's code editor slash-cmd-open-file-hint = or "@" to search slash-cmd-rename-tab-desc = Rename the current tab slash-cmd-rename-tab-hint = @@ -2476,7 +2476,7 @@ slash-cmd-export-to-file-hint = # --- ANCHOR-SUB-PROMPT-TIPS --- # Prompt editor modal (app/src/prompt/editor_modal.rs) prompt-editor-title = Edit prompt -prompt-editor-warp-prompt-section = Zap terminal prompt +prompt-editor-warp-prompt-section = Zaplex terminal prompt prompt-editor-shell-prompt-section = Shell prompt (PS1) prompt-editor-restore-default = Restore default prompt-editor-same-line-prompt = Same line prompt @@ -2486,7 +2486,7 @@ prompt-editor-save-changes = Save changes # Welcome tips (app/src/tips/tip_view.rs) welcome-tips-command-palette-title = Command Palette -welcome-tips-command-palette-description = Easily discover everything you can do in Zap without your hands leaving the keyboard. +welcome-tips-command-palette-description = Easily discover everything you can do in Zaplex without your hands leaving the keyboard. welcome-tips-split-pane-title = Split Pane welcome-tips-split-pane-description = Split tabs into multiple panes to make your ideal layout. welcome-tips-history-search-title = History Search @@ -2494,7 +2494,7 @@ welcome-tips-history-search-description = Find, edit and re-run previously execu welcome-tips-ai-command-search-title = AI Command Search welcome-tips-ai-command-search-description = Generate shell commands with natural language. welcome-tips-theme-picker-title = Theme Picker -welcome-tips-theme-picker-description = Make Zap your own by choosing a built-in theme. Or create your own. +welcome-tips-theme-picker-description = Make Zaplex your own by choosing a built-in theme. Or create your own. welcome-tips-shortcut-label = Shortcut welcome-tips-skip = Skip Welcome Tips welcome-tips-complete-title = Complete! @@ -2555,7 +2555,7 @@ search-filter-display-actions = actions search-filter-display-sessions = sessions search-filter-display-conversations = conversations search-filter-display-launch-configurations = launch configurations -search-filter-display-drive = Zap Drive +search-filter-display-drive = Zaplex Drive search-filter-display-environment-variables = environment variables search-filter-display-prompt-history = prompt history search-filter-display-files = files @@ -2640,7 +2640,7 @@ drive-trash-title = Trash drive-trash-deletion-warning = Items in the trash will be deleted forever after 30 days. drive-team-space-zero-state = Team spaces are unavailable in local builds. Manage workflows and notebooks in Personal. drive-sign-up-storage-limit = Local storage limits are enforced on this device. -drive-local-storage-limit-description = Local storage limits are enforced on this device. Remove unused items to create space for new Zap Drive objects. +drive-local-storage-limit-description = Local storage limits are enforced on this device. Remove unused items to create space for new Zaplex Drive objects. drive-sign-up = Manage locally drive-copy-link = Copy link drive-collapse-all = Collapse all @@ -2704,7 +2704,7 @@ workflow-keep-editing = Keep editing workflow-discard-changes = Discard changes workflow-ai-assist-autofill = Autofill workflow-ai-assist-loading = Loading -workflow-ai-assist-tooltip = Generate a title, descriptions, or parameters with Zap AI +workflow-ai-assist-tooltip = Generate a title, descriptions, or parameters with Zaplex AI workflow-tooltip-restore-from-trash = Restore workflow from trash workflow-ai-assist-error-byop-required = Autofill requires a BYOP model. Configure a provider and model in Settings → AI. workflow-ai-assist-error-bad-command = Failed to generate metadata. Please try again with a different command. @@ -2746,11 +2746,13 @@ project-explorer-unavailable-disabled-description = The Project Explorer require project-explorer-unavailable-remote-description = The Project Explorer requires access to your local workspace, which isn’t supported in remote sessions. project-explorer-unavailable-wsl-description = The Project Explorer doesn't currently work in WSL. workspace-left-panel-global-search = Global search -workspace-left-panel-warp-drive = Zap Drive +workspace-left-panel-warp-drive = Zaplex Drive workspace-left-panel-agent-conversations = Agent conversations workspace-left-panel-ssh-manager = SSH Manager workspace-left-panel-server-file-browser = Server files workspace-left-panel-skill-manager = Skill Manager +workspace-left-panel-cockpit = Cockpit +workspace-left-panel-cockpit-empty = No Claude or Codex accounts found. skill-manager-search-placeholder = Search skills skill-manager-filter-all = All skill-manager-filter-provider = Source @@ -2768,6 +2770,10 @@ workspace-left-panel-ssh-manager-detail-key-path = Key path workspace-left-panel-ssh-manager-auth-password = Password workspace-left-panel-ssh-manager-auth-key = Private key workspace-left-panel-ssh-manager-auth-onekey = OneKey +workspace-left-panel-ssh-manager-detail-resilience = Session persistence +workspace-left-panel-ssh-manager-resilience-off = Standard +workspace-left-panel-ssh-manager-resilience-on = Persistent +workspace-left-panel-ssh-manager-detail-ring-ceiling = Scrollback buffer workspace-left-panel-ssh-manager-onekey-credential = Credential workspace-left-panel-ssh-manager-onekey-new = New credential workspace-left-panel-ssh-manager-onekey-label = Credential name @@ -2793,7 +2799,12 @@ workspace-left-panel-ssh-manager-menu-new-folder = New folder workspace-left-panel-ssh-manager-menu-new-server = New SSH server workspace-left-panel-ssh-manager-menu-edit = Edit workspace-left-panel-ssh-manager-menu-connect = Connect +workspace-left-panel-ssh-manager-menu-sessions = Running sessions workspace-left-panel-ssh-manager-menu-sftp = File Manager +workspace-left-panel-ssh-manager-sessions-loading = Loading sessions… +workspace-left-panel-ssh-manager-sessions-empty = No running sessions +workspace-left-panel-ssh-manager-sessions-not-persistent = Set "Session persistence" to Persistent to use this +workspace-left-panel-ssh-manager-sessions-needs-key = Running sessions need key-based authentication workspace-left-panel-ssh-manager-menu-clone = Clone workspace-left-panel-ssh-manager-menu-delete = Delete workspace-left-panel-ssh-manager-pane-hint = Editing fields and "Connect" will arrive in the next iteration. For now this pane shows the saved configuration; tweak it via the SQLite store or the upcoming editor. @@ -2804,7 +2815,7 @@ workspace-left-panel-ssh-manager-field-group = Group workspace-left-panel-ssh-manager-group-root = Root workspace-left-panel-ssh-manager-passphrase = Passphrase workspace-left-panel-ssh-manager-save = Save -workspace-left-panel-ssh-manager-status-saved = Saved. +workspace-left-panel-ssh-manager-status-saved = ✓ Saved workspace-left-panel-ssh-manager-error-name-required = Name cannot be empty. workspace-left-panel-ssh-manager-error-port-invalid = Port must be a number between 1 and 65535. workspace-left-panel-ssh-manager-error-host-required = Host cannot be empty. @@ -2831,6 +2842,9 @@ workspace-left-panel-ssh-manager-candidates-error = Could not read SSH config at workspace-left-panel-ssh-manager-candidates-add = Add to SSH Manager workspace-left-panel-ssh-manager-candidates-added = Added workspace-left-panel-ssh-manager-candidates-refresh = Refresh from ~/.ssh/config +workspace-left-panel-ssh-manager-add-heading = Add a host +workspace-left-panel-ssh-manager-add-blank = Create a blank server +workspace-left-panel-ssh-manager-add-cancel = Cancel terminal-su-root-password-confirm = Auto-fill Root Password terminal-su-root-password-confirm-subtitle = Click to confirm and inject the saved Root password terminal-su-root-password-cancel = Cancel @@ -2933,7 +2947,7 @@ vertical-tabs-no-tabs-open = No tabs open vertical-tabs-untitled-tab = Untitled tab vertical-tabs-view-options-tooltip = View options vertical-tabs-new-session = New session -vertical-tabs-terminal-kind-oz = Oz +vertical-tabs-terminal-kind-oz = Agent vertical-tabs-pane-kind-terminal = Terminal vertical-tabs-pane-kind-code = Code vertical-tabs-pane-kind-code-diff = Code Diff @@ -2987,34 +3001,34 @@ global-search-unsupported-session-description = Global search doesn't currently global-search-failed = Global search failed. # Wasm NUX dialog (app/src/wasm_nux_dialog.rs) -wasm-nux-open-desktop-title = Open in Zap Desktop? +wasm-nux-open-desktop-title = Open in Zaplex Desktop? wasm-nux-open-desktop-detail = Future links will automatically open on desktop. -wasm-nux-open-desktop-confirm = Open in Zap -wasm-nux-download-title = Download Zap Desktop? -wasm-nux-download-description = Zap is the intelligent terminal with AI and your dev team's knowledge built-in. +wasm-nux-open-desktop-confirm = Open in Zaplex +wasm-nux-download-title = Download Zaplex Desktop? +wasm-nux-download-description = Zaplex is the intelligent terminal with AI and your dev team's knowledge built-in. wasm-nux-learn-more = Learn more wasm-nux-download-confirm = Download -wasm-nux-object-kind-drive-objects = Zap Drive objects -wasm-nux-object-kind-warp-links = Zap links +wasm-nux-object-kind-drive-objects = Zaplex Drive objects +wasm-nux-object-kind-warp-links = Zaplex links wasm-nux-always-open-on-web-title = Always open { $object_kind } on the web? wasm-nux-always-open-on-web-detail = You can change this at any time in settings. wasm-nux-yes = Yes # Auth override warning (app/src/auth/auth_override_warning_body.rs) auth-override-warning-title = New login detected -auth-override-warning-confirm-title = Delete personal Zap Drive objects and preferences? -auth-override-warning-description = It looks like you logged into a Zap account through a web browser. If you continue, any personal Zap Drive objects and preferences from this anonymous session will be permanently deleted. +auth-override-warning-confirm-title = Delete personal Zaplex Drive objects and preferences? +auth-override-warning-description = It looks like you logged into a Zaplex account through a web browser. If you continue, any personal Zaplex Drive objects and preferences from this anonymous session will be permanently deleted. auth-override-warning-cannot-undo = This cannot be undone. auth-override-warning-export = Export your data auth-override-warning-export-description = to import later. auth-override-warning-cancel = Cancel auth-override-warning-continue = Continue -auth-override-warning-accessibility-help = Zap has detected a new login from a web browser. Press escape to cancel and continue using Zap without login. +auth-override-warning-accessibility-help = Zaplex has detected a new login from a web browser. Press escape to cancel and continue using Zaplex without login. # Auth SSO link/login failures/paste token/logout/offline/privacy auth-needs-sso-link-button = Link SSO auth-needs-sso-link-title = Your organization has enabled SSO for your account -auth-needs-sso-link-detail = Click the button below to link your Zap account to your SSO provider. +auth-needs-sso-link-detail = Click the button below to link your Zaplex account to your SSO provider. auth-login-failure-troubleshooting-prefix = Not the first time? See our auth-login-failure-troubleshooting-link = troubleshooting docs auth-login-failure-troubleshooting-suffix = . @@ -3028,20 +3042,20 @@ auth-paste-token-title = Paste your auth token below auth-paste-token-detail = Paste your auth token from the browser to get complete login. auth-paste-token-cancel = Cancel auth-paste-token-continue = Continue -auth-offline-first-use-description = You are currently offline. An internet connection is required to use Zap for the first time. +auth-offline-first-use-description = You are currently offline. An internet connection is required to use Zaplex for the first time. auth-offline-first-use-learn-more = Learn more -auth-offline-overlay-title = Using Zap Offline -auth-offline-overlay-paragraph-1 = Zap can be used offline for local terminal and agent workflows. +auth-offline-overlay-title = Using Zaplex Offline +auth-offline-overlay-paragraph-1 = Zaplex can be used offline for local terminal and agent workflows. auth-offline-overlay-paragraph-2 = Some setup flows may still need an internet connection when they depend on external providers. auth-offline-overlay-paragraph-3 = Logged-out usage keeps local workflows on this machine. auth-offline-overlay-dismiss = Dismiss auth-privacy-settings-title = Privacy Settings auth-privacy-settings-done = Done -auth-privacy-settings-help-improve = Help improve Zap -auth-privacy-settings-help-improve-description = High-level feature usage data helps Zap's product team prioritize the roadmap. +auth-privacy-settings-help-improve = Help improve Zaplex +auth-privacy-settings-help-improve-description = High-level feature usage data helps Zaplex's product team prioritize the roadmap. auth-privacy-settings-learn-more = Learn more auth-privacy-settings-send-crash-reports = Send crash reports -auth-privacy-settings-crash-reports-description = Crash reporting helps Zap's engineering team understand stability and improve performance. +auth-privacy-settings-crash-reports-description = Crash reporting helps Zaplex's engineering team understand stability and improve performance. auth-logout-confirm = Yes, log out auth-logout-show-running-processes = Show running processes auth-logout-cancel = Cancel @@ -3054,7 +3068,7 @@ auth-logout-shared-sessions-warning = You have { $count } remote { $count -> [one] session *[other] sessions }. -auth-logout-unsynced-drive-objects-warning = You have { $count } unsynced Zap Drive { $count -> +auth-logout-unsynced-drive-objects-warning = You have { $count } unsynced Zaplex Drive { $count -> [one] object *[other] objects }. Logging out will cause you to lose the { $count -> @@ -3071,40 +3085,40 @@ auth-logout-unsaved-files-warning = You have { $count } unsaved { $count -> # CLI agent plugin instructions cli-agent-plugin-run-on-remote = Be sure to run these commands on your remote machine. -cli-agent-plugin-codex-install-title = Enable Zap Notifications for Codex -cli-agent-plugin-codex-install-subtitle = Update Codex to the latest version, then enable in-focus notifications so Zap can display them while you work. +cli-agent-plugin-codex-install-title = Enable Zaplex Notifications for Codex +cli-agent-plugin-codex-install-subtitle = Update Codex to the latest version, then enable in-focus notifications so Zaplex can display them while you work. cli-agent-plugin-codex-update-step = Update Codex to the latest version. cli-agent-plugin-codex-notification-step = Set the notification condition to "always" in your Codex config. Open or create ~/.codex/config.toml and add: cli-agent-plugin-codex-restart-note = Restart Codex to apply the changes. -cli-agent-plugin-deepseek-install-title = Enable Zap Notifications for DeepSeek +cli-agent-plugin-deepseek-install-title = Enable Zaplex Notifications for DeepSeek cli-agent-plugin-deepseek-install-subtitle = Add the following to your DeepSeek config file (~/.deepseek/config.toml) to enable turn-completion notifications. cli-agent-plugin-deepseek-notification-step = Set the notification condition to "always" in ~/.deepseek/config.toml: cli-agent-plugin-deepseek-restart-note = Restart DeepSeek to apply the changes. -cli-agent-plugin-claude-install-title = Install Zap Plugin for Claude Code +cli-agent-plugin-claude-install-title = Install Zaplex Plugin for Claude Code cli-agent-plugin-claude-install-subtitle = Ensure that jq is installed on your machine. Then, run these commands. cli-agent-plugin-claude-add-marketplace-step = Add the Warp plugin marketplace repository cli-agent-plugin-install-warp-plugin-step = Install the Warp plugin cli-agent-plugin-claude-restart-note = Restart Claude Code to activate the plugin. cli-agent-plugin-claude-known-issues-note = There are some known issues with Claude Code's plugin system. If the plugin is not found after step 1, you can try manually adding an "extraKnownMarketplaces" entry to ~/.claude/settings.json. -cli-agent-plugin-claude-update-title = Update Zap Plugin for Claude Code +cli-agent-plugin-claude-update-title = Update Zaplex Plugin for Claude Code cli-agent-plugin-run-following-commands = Run the following commands. cli-agent-plugin-remove-existing-marketplace-step = Remove the existing marketplace (if present) cli-agent-plugin-readd-marketplace-step = Re-add the marketplace cli-agent-plugin-install-latest-version-step = Install the latest plugin version cli-agent-plugin-claude-restart-update-note = Restart Claude Code to activate the update. -cli-agent-plugin-gemini-install-title = Install Zap Plugin for Gemini CLI +cli-agent-plugin-gemini-install-title = Install Zaplex Plugin for Gemini CLI cli-agent-plugin-gemini-run-command-restart = Run the following command, then restart Gemini CLI. cli-agent-plugin-install-warp-extension-step = Install the Warp extension cli-agent-plugin-gemini-restart-note = Restart Gemini CLI to activate the plugin. -cli-agent-plugin-gemini-update-title = Update Zap Plugin for Gemini CLI +cli-agent-plugin-gemini-update-title = Update Zaplex Plugin for Gemini CLI cli-agent-plugin-update-warp-extension-step = Update the Warp extension cli-agent-plugin-gemini-restart-update-note = Restart Gemini CLI to activate the update. -cli-agent-plugin-opencode-install-title = Install Zap Plugin for OpenCode +cli-agent-plugin-opencode-install-title = Install Zaplex Plugin for OpenCode cli-agent-plugin-opencode-install-subtitle = Add the Warp plugin to your OpenCode configuration, then restart OpenCode. cli-agent-plugin-opencode-open-config-step = Open or create your opencode.json. This can be in your project root, or the global config path: cli-agent-plugin-opencode-add-plugin-step = Add "@warp-dot-dev/opencode-warp" to the "plugin" array in the top-level JSON object: cli-agent-plugin-opencode-restart-note = Restart OpenCode to activate the plugin. -cli-agent-plugin-opencode-update-title = Update Zap Plugin for OpenCode +cli-agent-plugin-opencode-update-title = Update Zaplex Plugin for OpenCode cli-agent-plugin-opencode-update-subtitle = Pin the plugin to the latest version in your opencode.json. OpenCode caches plugins per version spec, so changing the pin forces it to re-fetch on restart. cli-agent-plugin-opencode-replace-plugin-step = Replace the existing "@warp-dot-dev/opencode-warp" entry in the "plugin" array with the explicit version: cli-agent-plugin-opencode-restart-update-note = Restart OpenCode to load the updated plugin. @@ -3117,18 +3131,18 @@ terminal-bootstrapping-installing-progress = Installing... ({ $p }%) terminal-bootstrapping-installing = Installing... terminal-bootstrapping-updating = Updating... terminal-bootstrapping-initializing = Initializing... -terminal-bootstrapping-installing-warp-ssh-extension-progress = Installing Zap SSH Extension... ({ $p }%) -terminal-bootstrapping-installing-warp-ssh-extension = Installing Zap SSH Extension... -terminal-bootstrapping-updating-warp-ssh-extension = Updating Zap SSH Extension... +terminal-bootstrapping-installing-warp-ssh-extension-progress = Installing Zaplex SSH Extension... ({ $p }%) +terminal-bootstrapping-installing-warp-ssh-extension = Installing Zaplex SSH Extension... +terminal-bootstrapping-updating-warp-ssh-extension = Updating Zaplex SSH Extension... terminal-bootstrapping-starting-shell-name = Starting { $shell }... agent-tip-prefix = Tip: agent-tip-slash-menu = `/` to open the slash-command menu and access quick agent actions. agent-tip-toggle-input-mode = to toggle natural language detection and switch between agent and terminal input. agent-tip-plan = `/plan` to create a plan for the agent before executing. -agent-tip-command-palette = to open the Command Palette and access Zap actions and shortcuts. +agent-tip-command-palette = to open the Command Palette and access Zaplex actions and shortcuts. agent-tip-warp-drive = Store reusable workflows, notebooks, and prompts in your agent-tip-redirect-running-agent = Enter a new prompt to redirect the agent while it's running. -agent-tip-add-context = `@` to add context from files, blocks, or Zap Drive objects to your prompt. +agent-tip-add-context = `@` to add context from files, blocks, or Zaplex Drive objects to your prompt. agent-tip-attach-prior-output = to attach the prior command output as agent context. agent-tip-init-index = `/init` to index the repo so the agent can understand your codebase. agent-tip-agent-profiles = Add agent profiles to customize permissions and models per session. @@ -3146,21 +3160,21 @@ agent-tip-open-code-review = `/open-code-review` to open the code review panel a agent-tip-new-conversation = `/new` to start a new agent conversation with clean context. agent-tip-compact = `/compact` to summarize the current conversation and free up space in the context window. agent-tip-usage = `/usage` to show your current AI credits usage. -agent-tip-oz-headless = Use the `oz` command to run an Oz agent in headless mode, useful for remote machines. +agent-tip-oz-headless = Use the `zaplex` command to run an agent in headless mode, useful for remote machines. agent-tip-selected-text-context = Right-click selected text to attach it as agent context. agent-tip-project-rules = Use `AGENTS.md` or `CLAUDE.md` to apply project-scoped rules. agent-tip-url-context = Paste a URL to attach that webpage as context for the agent. -agent-tip-warpify-ssh = Warpify a remote SSH session to enable Oz inside that environment. +agent-tip-zaplexify-ssh = Zaplexify a remote SSH session to enable the agent inside that environment. agent-tip-switch-profiles = Switch agent profiles to quickly change models and agent permissions. agent-tip-init-rules = `/init` to generate a `WARP.md` file and define project rules for the agent. agent-tip-auto-approve = to auto-approve the agent's commands and diffs for the rest of the session. agent-tip-desktop-notifications = Enable desktop notifications to get an alert when an agent needs your attention. agent-tip-cancel-task = to cancel the current agent task. agent-tip-action-open-palette = Open palette -agent-tip-action-warp-drive = Zap Drive. +agent-tip-action-warp-drive = Zaplex Drive. agent-tip-action-show-diff-view = Show diff view agent-tip-voice-input = Hold to speak your prompt directly to the agent. -hoa-welcome-banner-title = Introducing universal agent support: level up any coding agent with Zap +hoa-welcome-banner-title = Introducing universal agent support: level up any coding agent with Zaplex hoa-feature-vertical-tabs-title = Vertical tabs hoa-feature-vertical-tabs-description = Rich tab titles and metadata like git branch, worktree, and PR. Fully customizable. hoa-feature-tab-configs-title = Tab configs @@ -3168,10 +3182,10 @@ hoa-feature-tab-configs-description = Tab-level schema to set your directory, st hoa-feature-agent-inbox-title = Agent inbox hoa-feature-agent-inbox-description = Notifications when any agent needs your attention, also accessible in a central inbox hoa-feature-native-code-review-title = Native code review -hoa-feature-native-code-review-description = Send inline comments from Zap's code review directly to Claude Code, Codex, or OpenCode +hoa-feature-native-code-review-description = Send inline comments from Zaplex's code review directly to Claude Code, Codex, or OpenCode resource-center-whats-new-section = What's New? resource-center-getting-started-section = Getting Started -resource-center-maximize-warp-section = Maximize Zap +resource-center-maximize-warp-section = Maximize Zaplex resource-center-advanced-setup-section = Advanced Setup resource-center-create-first-block-title = Create your first block resource-center-create-first-block-description = Run a command to see your command and output grouped. @@ -3180,16 +3194,16 @@ resource-center-navigate-blocks-description = Click to select a block and naviga resource-center-block-action-title = Take an action on block resource-center-block-action-description = Right click on a block to copy/paste, share, more. resource-center-command-palette-title = Open command palette -resource-center-command-palette-description = Access all of Zap via the keyboard. +resource-center-command-palette-description = Access all of Zaplex via the keyboard. resource-center-set-theme-title = Set your theme -resource-center-set-theme-description = Make Zap your own by choosing a theme. +resource-center-set-theme-description = Make Zaplex your own by choosing a theme. resource-center-custom-prompt-title = Use your custom prompt -resource-center-custom-prompt-description = Set up Zap to honor your PS1 setting +resource-center-custom-prompt-description = Set up Zaplex to honor your PS1 setting resource-center-view-documentation = View documentation -resource-center-integrate-ide-title = Integrate Zap with your IDE -resource-center-integrate-ide-description = Configure Zap to launch from your most used development tools -resource-center-how-warp-uses-warp-title = How Zap uses Zap -resource-center-how-warp-uses-warp-description = Learn how Zap's engineering team uses their favorite features +resource-center-integrate-ide-title = Integrate Zaplex with your IDE +resource-center-integrate-ide-description = Configure Zaplex to launch from your most used development tools +resource-center-how-warp-uses-warp-title = How Zaplex uses Zaplex +resource-center-how-warp-uses-warp-description = Learn how Zaplex's engineering team uses their favorite features resource-center-read-article = Read article resource-center-command-search-title = Command search resource-center-command-search-description = Find and run previously executed commands, workflows, and more. @@ -3231,20 +3245,20 @@ agent-message-bar-again-send-to-agent = again to send to agent # resource center, theme picker, terminal banners, AI footer/tool output # ============================================================================= -onboarding-intention-title = Welcome to Zap +onboarding-intention-title = Welcome to Zaplex onboarding-intention-subtitle = How do you want to work? onboarding-intention-agent-title = Build faster with AI agents onboarding-intention-agent-description = An agent-first experience with best in class terminal support. Get terminal and agent driven development AI features like: onboarding-intention-terminal-title = Just use the terminal onboarding-intention-terminal-badge = No AI features onboarding-intention-terminal-description = A modern terminal optimized for speed, context, and control without AI. -onboarding-ai-feature-warp-agents = Zap agents -onboarding-ai-feature-oz-cloud-agents-platform = Oz local agents platform +onboarding-ai-feature-warp-agents = Zaplex agents +onboarding-ai-feature-oz-cloud-agents-platform = Local agents platform onboarding-ai-feature-next-command-predictions = Next command predictions onboarding-ai-feature-prompt-suggestions = Prompt suggestions onboarding-ai-feature-remote-control-agents = Remote control with Claude Code, Codex, and other agents onboarding-ai-feature-agents-over-ssh = Agents over SSH -onboarding-agent-title = Customize your Zap Agent +onboarding-agent-title = Customize your Zaplex Agent onboarding-agent-subtitle = Select your in-app agent's defaults. onboarding-agent-default-model = Default model onboarding-agent-autonomy = Autonomy @@ -3256,9 +3270,9 @@ onboarding-agent-autonomy-partial-title = Partial onboarding-agent-autonomy-partial-subtitle = Can plan, read files, and execute low-risk commands. Asks before making any changes or executing sensitive commands. onboarding-agent-autonomy-none-title = None onboarding-agent-autonomy-none-subtitle = Takes no actions without your approval. -onboarding-agent-disable-warp-agent = Disable Zap Agent +onboarding-agent-disable-warp-agent = Disable Zaplex Agent onboarding-project-title = Open a project -onboarding-project-subtitle = Set up a project to optimize it for coding in Zap. +onboarding-project-subtitle = Set up a project to optimize it for coding in Zaplex. onboarding-project-open-local-folder = Open local folder onboarding-project-initialize-automatically = Initialize project automatically onboarding-project-initialize-description = Prepares the project environment, builds an index of your code, and generates project rules—giving the agent deeper understanding and better performance. @@ -3272,7 +3286,7 @@ onboarding-third-party-title = Customize third party agents onboarding-third-party-subtitle = Select defaults for using agents like Claude Code, Codex, and Gemini. onboarding-third-party-cli-toolbar = CLI agent toolbar onboarding-third-party-notifications = Notifications -onboarding-customize-title = Customize your Zap +onboarding-customize-title = Customize your Zaplex onboarding-customize-subtitle = Tailor your features and UI to your working style. onboarding-customize-tab-styling = Tab styling onboarding-customize-vertical = Vertical @@ -3280,35 +3294,35 @@ onboarding-customize-horizontal = Horizontal onboarding-customize-conversation-history = Conversation history onboarding-customize-file-explorer = File explorer onboarding-customize-global-file-search = Global file search -onboarding-customize-warp-drive = Zap Drive +onboarding-customize-warp-drive = Zaplex Drive onboarding-customize-tools-panel = Tools panel onboarding-customize-code-review = Code review -auth-opt-out-line-1 = Zap stores onboarding choices locally. +auth-opt-out-line-1 = Zaplex stores onboarding choices locally. auth-opt-out-line-2-prefix = You can adjust your{" "} auth-privacy-settings-prefix = You can adjust your{" "} auth-privacy-settings-ai-prefix = You can adjust your local AI preferences in{" "} auth-privacy-settings = Privacy Settings -auth-local-privacy-note = Zap stores onboarding choices locally on this device. +auth-local-privacy-note = Zaplex stores onboarding choices locally on this device. auth-terms-prefix = Continuing keeps this setup on your device.{" "} auth-terms-of-service = Local setup auth-log-in = Log in auth-paste-token-from-browser = Click here to paste your token from the browser -auth-login-slide-title-warp-drive = Get started with Zap Drive +auth-login-slide-title-warp-drive = Get started with Zaplex Drive auth-login-slide-title-ai = Get started with AI auth-login-slide-subtitle-warp-drive = Connect your account to save and share notebooks, workflows, and more across devices. auth-login-slide-subtitle-ai = Connect your account to enable AI-powered planning, coding, and automation. -auth-disable-warp-drive = Disable Zap Drive +auth-disable-warp-drive = Disable Zaplex Drive auth-disable-ai-features = Disable AI features -auth-enable-warp-drive = Enable Zap Drive +auth-enable-warp-drive = Enable Zaplex Drive auth-enable-ai-features = Enable AI features auth-browser-sign-in-one-line-title = Sign in on your browser to continue auth-open-page-manually-line-prefix = {" "}and open auth-open-page-manually-line-suffix = the page manually. -auth-disable-warp-drive-confirm-title = Are you sure you want to disable Zap Drive? +auth-disable-warp-drive-confirm-title = Are you sure you want to disable Zaplex Drive? auth-disable-ai-features-confirm-title = Are you sure you want to disable AI features? -auth-disable-warp-drive-confirm-body = Zap Drive lets you save workflows and knowledge across devices and share them with your team. By continuing, you won't have access to the following features: -auth-disable-ai-features-confirm-body = Zap is better with AI. By continuing, you won't have access to any of the following features: +auth-disable-warp-drive-confirm-body = Zaplex Drive lets you save workflows and knowledge across devices and share them with your team. By continuing, you won't have access to the following features: +auth-disable-ai-features-confirm-body = Zaplex is better with AI. By continuing, you won't have access to any of the following features: auth-feature-session-sharing = Session Sharing auth-sign-up = Continue locally auth-sign-in = Sign in @@ -3319,11 +3333,11 @@ auth-skip-login-confirm-title = Are you sure you want to skip login? auth-skip-login-confirm-line-1 = You can sign up later, but some features, such as AI, auth-skip-login-confirm-line-2-prefix = are only available to logged-in users.{" "} auth-yes-skip-login = Yes, skip login -auth-require-login-ai-collaboration = Local AI features do not require a Zap account. -auth-require-login-drive-limit = Zap Drive objects are stored locally in Zap. -auth-require-login-share = Sharing is unavailable in local Zap builds. -auth-welcome-title = Welcome to Zap! -auth-sign-up-for-warp = Continue in Zap +auth-require-login-ai-collaboration = Local AI features do not require a Zaplex account. +auth-require-login-drive-limit = Zaplex Drive objects are stored locally in Zaplex. +auth-require-login-share = Sharing is unavailable in local Zaplex builds. +auth-welcome-title = Welcome to Zaplex! +auth-sign-up-for-warp = Continue in Zaplex auth-browser-sign-in-title = Sign in on your browser\nto continue auth-open-page-manually-suffix = and open the page manually. @@ -3334,7 +3348,7 @@ voice-transcription-disabled-microphone = Voice transcription is disabled becaus voice-transcription = Voice transcription voice-transcription-hold-key = Voice transcription (hold `{ $key }` key) -get-started-welcome-title = Welcome to Zap +get-started-welcome-title = Welcome to Zaplex get-started-subtitle = The Agentic Development Environment theme-creator-theme-name = Theme name theme-creator-background-color = Background color @@ -3372,11 +3386,11 @@ notebook-file-loading = Loading { $name }... notebook-file-missing-source = Missing source file terminal-shared-session-reconnecting = Offline, trying to reconnect... -terminal-banner-p10k-supported = Powerlevel10k now supports Zap!{" "} +terminal-banner-p10k-supported = Powerlevel10k now supports Zaplex!{" "} terminal-banner-p10k-older-version-prefix = You seem to be running an older (unsupported) version, please follow{" "} terminal-banner-these-instructions = these instructions terminal-banner-update-latest-suffix = {" "}to update to the latest version. -terminal-banner-pure-unsupported = Pure is not yet supported in Zap. You might consider one of the supported prompts as an alternative.{" "} +terminal-banner-pure-unsupported = Pure is not yet supported in Zaplex. You might consider one of the supported prompts as an alternative.{" "} terminal-loading-session = Loading session... ai-footer-hide-rich-input = Hide Rich Input @@ -3443,7 +3457,7 @@ sharing-invite = Invite sharing-who-has-access = Who has access terminal-shared-session-cancel-request = Cancel request terminal-shared-session-continue-sharing = Continue sharing -settings-import-reset-to-warp-defaults = Reset to Zap defaults +settings-import-reset-to-warp-defaults = Reset to Zaplex defaults settings-import-type-theme = Theme settings-import-type-theme-with-comma = Theme, settings-import-type-option-as-meta = Option as Meta @@ -3542,19 +3556,19 @@ common-resource-not-found-or-access-denied = Resource not found or access denied workspace-close-session = Close session workspace-auto-reload = Auto-reload workspace-add-new-repo = {" "}+ Add new repo -workspace-notification-permission-denied-toast = Zap doesn't have permission to send desktop notifications. +workspace-notification-permission-denied-toast = Zaplex doesn't have permission to send desktop notifications. workspace-troubleshoot-notifications-link = Troubleshoot notifications -workspace-plan-synced-to-warp-drive-toast = Plan synced to your Zap Drive +workspace-plan-synced-to-warp-drive-toast = Plan synced to your Zaplex Drive workspace-remote-control-link-copied-toast = Remote control link copied. workspace-update-now = Update now -workspace-update-warp = Update Zap +workspace-update-warp = Update Zaplex workspace-app-out-of-date-needs-update = Your app is out of date and needs to update. workspace-restart-app-and-update-now = Restart app and update now workspace-sampling-process-toast = Sampling process for 3 seconds... workspace-version-deprecation-banner = Your app is out of date and some features may not work as expected. Please update immediately. -workspace-version-deprecation-without-permissions-banner = Some Zap features may not work as expected without updating immediately, but Zap is unable to perform the update. -workspace-new-version-unable-to-update-banner = A new version is available but Zap is unable to perform the update. -workspace-unable-to-launch-new-installed-version = Zap was unable to launch the new installed version. +workspace-version-deprecation-without-permissions-banner = Some Zaplex features may not work as expected without updating immediately, but Zaplex is unable to perform the update. +workspace-new-version-unable-to-update-banner = A new version is available but Zaplex is unable to perform the update. +workspace-unable-to-launch-new-installed-version = Zaplex was unable to launch the new installed version. tab-config-session-type = Session type terminal-copy-error = Copy error terminal-authenticate-with-github = Authenticate with GitHub @@ -3563,11 +3577,11 @@ terminal-regenerate-agents-file = Re-generate AGENTS.md file terminal-view-index-status = View index status terminal-shared-session-request-edit-access = Request edit access terminal-create-team = Create team -terminal-warpify-without-tmux = Warpify without TMUX -terminal-continue-without-warpification = Continue without Warpification +terminal-zaplexify-without-tmux = Zaplexify without TMUX +terminal-continue-without-zaplexification = Continue without Zaplexification terminal-always-install = Always install terminal-never-install = Never install -terminal-ssh-report-issue-prefix = We are actively working on improving the stability of SSH in Zap. Please consider{" "} +terminal-ssh-report-issue-prefix = We are actively working on improving the stability of SSH in Zaplex. Please consider{" "} terminal-ssh-report-issue-link = filing an issue terminal-ssh-report-issue-suffix = {" "}on GitHub so we can better identify the problem. terminal-ssh-why-need-tmux = Why do I need tmux? @@ -3579,7 +3593,7 @@ terminal-hide-secret = Hide secret terminal-copy-secret = Copy secret terminal-tag-agent-for-assistance = Tag agent for assistance terminal-save-as-workflow-secrets-tooltip = Blocks containing secrets cannot be saved. -terminal-agent-mode-setup-title = Optimize Zap for this codebase? +terminal-agent-mode-setup-title = Optimize Zaplex for this codebase? terminal-agent-mode-setup-description = Unlock smarter, more consistent responses by letting the Agent understand your codebase and generate rules for it. You can also do this at any point by running /init terminal-agent-mode-setup-optimize = Optimize terminal-no-active-conversation-to-export = No active conversation to export @@ -3589,14 +3603,14 @@ terminal-show-initialization-block = Show initialization block terminal-shell-process-exited = Shell process exited terminal-shell-process-could-not-start = Shell process could not start! terminal-shell-process-exited-prematurely = Shell process exited prematurely! -terminal-shell-premature-subtext = Something went wrong while starting { $shell_detail } and Warpifying it, causing the process to terminate. Warpify script output is displayed here, which may point at a cause. +terminal-shell-premature-subtext = Something went wrong while starting { $shell_detail } and Zaplexifying it, causing the process to terminate. Zaplexify script output is displayed here, which may point at a cause. terminal-file-issue = File issue notifications-banner-troubleshoot = Troubleshoot notifications-banner-dismissed-title = We won't show this banner again, but you can always go to Settings to enable notifications. notifications-banner-disabled-title = Notifications were turned off, but you can always go to Settings to enable notifications. notifications-banner-enable = Enable notifications-banner-permissions-accepted-title = Success! You are now ready to receive desktop notifications. -notifications-banner-permissions-denied-title = Zap was denied permissions to send you notifications. +notifications-banner-permissions-denied-title = Zaplex was denied permissions to send you notifications. notifications-banner-permissions-error-title = Something went wrong while requesting permissions. notifications-banner-allow-permissions-title = Don't forget to 'Allow' the permissions request to finish setting up notifications. notifications-banner-configure-notifications = Configure notifications @@ -3640,7 +3654,7 @@ notebook-sync-conflict-resolution-message = This notebook could not be saved bec notebook-sync-feature-not-available-message = This notebook could not be saved to the server because the feature is temporarily unavailable. The changes are saved locally. Please retry later. notebook-link-copied-toast = Link copied settings-share-with-team = Save locally -tooltip-secrets-not-sent-to-warp-server = *Secrets are not sent to Zap's server. +tooltip-secrets-not-sent-to-warp-server = *Secrets are not sent to Zaplex's server. editor-voice-limit-hit-toast = You have hit the limit for Voice requests. Your limit will be refreshed as a part of your next cycle. editor-voice-error-toast = An error occurred while processing your voice input. ai-copied-branch-name-toast = Copied branch name diff --git a/app/resources/tab_configs/new_tab_config_template.toml b/app/resources/tab_configs/new_tab_config_template.toml index 8197677d44..b5b623cd60 100644 --- a/app/resources/tab_configs/new_tab_config_template.toml +++ b/app/resources/tab_configs/new_tab_config_template.toml @@ -1,4 +1,4 @@ -# Zap Tab Config +# Zaplex Tab Config # Stored in ~/.warp/tab_configs/ — rename this file and edit anytime! # # Tip: Ask Oz to update this tab config for you using /skills -> /update-tab-config! diff --git a/app/src/ai/agent/api.rs b/app/src/ai/agent/api.rs index 680560882a..89cce1a2c5 100644 --- a/app/src/ai/agent/api.rs +++ b/app/src/ai/agent/api.rs @@ -89,7 +89,7 @@ pub struct RequestParams { pub cli_agent_model: LLMId, pub computer_use_model: LLMId, pub is_memory_enabled: bool, - /// Zap BYOP exclusive: snapshot of global Rules (`AIFact::Memory`) + /// Zaplex BYOP exclusive: snapshot of global Rules (`AIFact::Memory`) /// created by the user in Settings → Agents → Rules. Fetched once from `ObjectStoreModel` /// in `new()` and plumbed with the request to `chat_stream::build_chat_request` → `prompt_renderer`, /// where `partials/user_rules.j2` renders them into the system prompt. @@ -112,22 +112,22 @@ pub struct RequestParams { pub ask_user_question_enabled: bool, pub research_agent_enabled: bool, pub supported_tools_override: Option>, - /// Zap BYOP exclusive: local session id, used only for request-readiness diagnostic logs. + /// Zaplex BYOP exclusive: local session id, used only for request-readiness diagnostic logs. pub byop_conversation_id: Option, - /// Zap BYOP exclusive: non-persistent diagnostic correlation id within a single request. + /// Zaplex BYOP exclusive: non-persistent diagnostic correlation id within a single request. pub byop_readiness_attempt_id: Option, /// The conversation ID of the parent agent that spawned this child agent, if any. pub parent_agent_id: Option, /// The display name for this agent (e.g. "Agent 1"), assigned by the orchestrator. pub agent_name: Option, - /// Zap BYOP exclusive: the LRC (Long Running Command) block id associated when initiating this request. + /// Zaplex BYOP exclusive: the LRC (Long Running Command) block id associated when initiating this request. /// Populated in the first turn after tag-in and subsequent turns of CLI subagent under agent control, /// used to keep BYOP prompt/tools bound to the current PTY, preventing the model from spawning a new shell for the same TUI. pub lrc_command_id: Option, - /// Zap BYOP exclusive: current LRC snapshot. `UserQuery.running_command` only covers user input turns; + /// Zaplex BYOP exclusive: current LRC snapshot. `UserQuery.running_command` only covers user input turns; /// auto-resume / tool result subsequent turns need to carry the latest PTY content via this field. pub lrc_running_command: Option, - /// Zap BYOP local session compression sidecar snapshot (controller injects conversation.compaction_state.clone() here). + /// Zaplex BYOP local session compression sidecar snapshot (controller injects conversation.compaction_state.clone() here). /// `chat_stream::build_chat_request` uses this to: /// 1. Filter messages in [`crate::ai::byop_compaction::state::CompactionState::hidden_message_ids`] /// 2. Insert "summary user/assistant pairs" at hidden interval boundaries @@ -136,12 +136,12 @@ pub struct RequestParams { /// /// Default `None` = compatibility path (no compression). pub compaction_state: Option, - /// Zap BYOP repair sidecar snapshot. serializer uses read-only, does not deserialize persisted JSON during request construction. + /// Zaplex BYOP repair sidecar snapshot. serializer uses read-only, does not deserialize persisted JSON during request construction. pub byop_repair_state: crate::ai::byop_readiness::RepairStateStatus, - /// Zap BYOP exclusive: whether this turn needs to simulate upstream CreateTask flow to upgrade optimistic CLI subtask. + /// Zaplex BYOP exclusive: whether this turn needs to simulate upstream CreateTask flow to upgrade optimistic CLI subtask. /// Only required on the first turn after user tag-in; subsequent turns with existing CLI subagent reuse the task and cannot re-spawn. pub lrc_should_spawn_subagent: bool, - /// Zap BYOP exclusive: the task that this turn's response should be written to. Normal conversations use root task; + /// Zaplex BYOP exclusive: the task that this turn's response should be written to. Normal conversations use root task; /// CLI subagent subsequent turns use the corresponding subtask. pub byop_target_task_id: Option, } @@ -251,7 +251,7 @@ impl RequestParams { let is_memory_enabled = ai_settings.is_memory_enabled(app); let warp_drive_context_enabled = ai_settings.is_warp_drive_context_enabled(app); - // Zap BYOP fix for Issue #116: gate on `is_memory_enabled`, collection logic + // Zaplex BYOP fix for Issue #116: gate on `is_memory_enabled`, collection logic // extracted to `collect_user_rules` pure function that takes only `&ObjectStoreModel` parameter for testing, // does not depend on full AppContext singleton. let user_rules = if is_memory_enabled { diff --git a/app/src/ai/agent/api/convert_from.rs b/app/src/ai/agent/api/convert_from.rs index f1274cb165..9831f505eb 100644 --- a/app/src/ai/agent/api/convert_from.rs +++ b/app/src/ai/agent/api/convert_from.rs @@ -540,7 +540,7 @@ impl ConvertAPIToolCallToAIAgentAction for api::message::ToolCall { params: ConversionParams, ) -> Result { let Some(tool) = self.tool else { - // Zap BYOP: `make_tool_call_carrier_message` intentionally emits `tool: None` ToolCall + // Zaplex BYOP: `make_tool_call_carrier_message` intentionally emits `tool: None` ToolCall // when from_args parsing fails, serving as a carrier for the next build_chat_request round to restore // the original fn_name + args_str to the upstream model (server_message_data carries the original content); // the synthetic error ToolCallResult immediately following is the content to display to the user. diff --git a/app/src/ai/agent/conversation.rs b/app/src/ai/agent/conversation.rs index 0f70678fd0..71ac02b3f1 100644 --- a/app/src/ai/agent/conversation.rs +++ b/app/src/ai/agent/conversation.rs @@ -219,12 +219,12 @@ pub struct AIConversation { /// Legacy cloud event cursor retained only for deserializing older conversations. last_event_sequence: Option, - /// Zap BYOP local session compaction sidecar — decoupled from warp protobuf message, + /// Zaplex BYOP local session compaction sidecar — decoupled from warp protobuf message, /// indexed by message_id to attach metadata like "is_summary / tool_output_compacted_at / synthetic_continue". /// Empty by default = uncompacted state, fully non-invasive. /// See [`crate::ai::byop_compaction`] for details. pub(crate) compaction_state: crate::ai::byop_compaction::state::CompactionState, - /// Zap BYOP repair sidecar. Invalid sidecars must be preserved as-is to avoid silently + /// Zaplex BYOP repair sidecar. Invalid sidecars must be preserved as-is to avoid silently /// authorizing repair or erasing corrupted metadata during save. pub(crate) byop_repair_state: RepairStateStatus, } @@ -1157,7 +1157,7 @@ impl AIConversation { }); } - /// Updates the notebook_uid for a plan artifact when it's synced to Zap Drive. + /// Updates the notebook_uid for a plan artifact when it's synced to Zaplex Drive. pub fn update_plan_notebook_uid( &mut self, document_uid: AIDocumentId, @@ -2507,7 +2507,7 @@ impl AIConversation { }) .ok_or(UpdateConversationError::ExchangeNotFound)?; - // Zap optimization 1: Fast path for text/reasoning streams. + // Zaplex optimization 1: Fast path for text/reasoning streams. // When mask is `agent_output.text` or `agent_reasoning.reasoning`, // todos_op is not triggered (only UpdateTodos message does), // and current_todo_list / current_comment_state are @@ -2693,7 +2693,7 @@ impl AIConversation { new_task_id } - /// Zap BYOP-specific: When agent self-initiates LRC and receives snapshot, + /// Zaplex BYOP-specific: When agent self-initiates LRC and receives snapshot, /// directly land a Server-backed cli subagent task in conversation. /// /// Does not use `create_optimistic_cli_subagent_task` (which produces `TaskImpl::Optimistic` and @@ -3479,7 +3479,7 @@ impl AIConversation { } } -/// Zap optimization 1: Detect if AppendToMessageContent mask is purely text/reasoning append. +/// Zaplex optimization 1: Detect if AppendToMessageContent mask is purely text/reasoning append. /// These two mask paths: /// - `agent_output.text` — BYOP / cloud path text chunk /// - `agent_reasoning.reasoning` — BYOP / cloud path reasoning chunk @@ -3705,7 +3705,7 @@ pub enum AIAgentSerializedBlockFormat { /// Describes the format capabilities of a conversation. #[derive(Debug, Clone)] pub struct AIAgentConversationFormat { - /// Whether there is a Zap MAA task list available for this conversation. + /// Whether there is a Zaplex MAA task list available for this conversation. pub has_task_list: bool, /// The format of the TUI serialized block, if available. pub block_snapshot: Option, diff --git a/app/src/ai/agent/mod.rs b/app/src/ai/agent/mod.rs index e1895f703d..a33f0b4f23 100644 --- a/app/src/ai/agent/mod.rs +++ b/app/src/ai/agent/mod.rs @@ -682,9 +682,9 @@ impl Display for RenderableAIError { match self { Self::QuotaLimit => write!(f, "Quota limit reached."), Self::ServerOverloaded => { - write!(f, "Zap is currently overloaded. Please try again later.") + write!(f, "Zaplex is currently overloaded. Please try again later.") } - Self::InternalWarpError => write!(f, "Internal Zap error."), + Self::InternalWarpError => write!(f, "Internal Zaplex error."), Self::ContextWindowExceeded(message) => { write!(f, "Context window exceeded: {message}") } @@ -2390,7 +2390,7 @@ pub enum AIAgentInput { SummarizeConversation { prompt: Option, - /// Zap BYOP: this field marks whether this summarization was triggered automatically by token overflow. + /// Zaplex BYOP: this field marks whether this summarization was triggered automatically by token overflow. /// The `chat_stream::SummarizeConversation` branch uses it to decide the follow-up wording /// (the overflow path appends a "previous request exceeded ..." explanation). /// The local agent conversation path does not read this field. All existing call sites keep `overflow: false`. diff --git a/app/src/ai/agent/task.rs b/app/src/ai/agent/task.rs index 48dfea05a9..b4607408ef 100644 --- a/app/src/ai/agent/task.rs +++ b/app/src/ai/agent/task.rs @@ -194,7 +194,7 @@ impl Task { } } - /// Zap BYOP-exclusive: when an agent's self-hosted LRC receives a snapshot, create a Server-backed subagent task + /// Zaplex BYOP-exclusive: when an agent's self-hosted LRC receives a snapshot, create a Server-backed subagent task /// directly in the conversation. /// /// Cannot reuse `new_optimistic_cli_agent_subtask` because its resulting `TaskImpl::Optimistic` diff --git a/app/src/ai/agent_conversations_model.rs b/app/src/ai/agent_conversations_model.rs index a3e8ed4ec1..ef566ff32d 100644 --- a/app/src/ai/agent_conversations_model.rs +++ b/app/src/ai/agent_conversations_model.rs @@ -667,7 +667,7 @@ pub struct AgentConversationsModel { /// A map of conversation IDs to local conversations. conversations: HashMap, /// Set of view IDs actively consuming this model's data per window. - /// Zap: After localization, there is no polling; this is only used as a placeholder record for register_view_open/closed. + /// Zaplex: After localization, there is no polling; this is only used as a placeholder record for register_view_open/closed. active_data_consumers_per_window: HashMap>, /// Whether we have finished the initial task load has_finished_initial_load: bool, @@ -698,7 +698,7 @@ impl SingletonEntity for AgentConversationsModel {} impl AgentConversationsModel { pub fn new(ctx: &mut ModelContext) -> Self { - // Zap (localization, Phase 3b-1 / Wave 6-6): AgentConversationsModel originally polled/probed + // Zaplex (localization, Phase 3b-1 / Wave 6-6): AgentConversationsModel originally polled/probed // for remote ambient agent tasks and conversation metadata. In the localized scenario: // - No polling subsystem (physically removed in Wave 6-6) // - has_finished_initial_load is directly set to true, so UI queries return empty sets @@ -963,7 +963,7 @@ impl AgentConversationsModel { /// Retrieves locally cached task data by task ID. /// - /// Zap no longer fetches ambient agent tasks from the cloud. If a caller restores an old layout + /// Zaplex no longer fetches ambient agent tasks from the cloud. If a caller restores an old layout /// but the local model has no corresponding task, this returns `None`, which is handled by /// the existing panel fallback path. pub fn get_or_async_fetch_task_data( diff --git a/app/src/ai/agent_conversations_model_tests.rs b/app/src/ai/agent_conversations_model_tests.rs index 266e6f9ae3..fe9b9526e6 100644 --- a/app/src/ai/agent_conversations_model_tests.rs +++ b/app/src/ai/agent_conversations_model_tests.rs @@ -811,7 +811,7 @@ fn test_harness_filter_matches_only_selected_harness() { let task_claude = task_with_harness(5100, "user-a", Some(Some(Harness::Claude))); let task_gemini = task_with_harness(5101, "user-a", Some(Some(Harness::Gemini))); - // Snapshot present but no harness set → Some(Oz), matches Zap Agent. + // Snapshot present but no harness set → Some(Oz), matches Zaplex Agent. let task_oz_default = task_with_harness(5102, "user-a", Some(None)); // No snapshot at all → None, matches only `All`. let task_no_snapshot = task_with_harness(5103, "user-a", None); @@ -825,7 +825,7 @@ fn test_harness_filter_matches_only_selected_harness() { model.tasks.insert(t.task_id, t.clone()); } - // Local conversation: effectively Zap Agent. + // Local conversation: effectively Zaplex Agent. let conv_id = AIConversationId::new(); model.conversations.insert( conv_id, @@ -863,20 +863,20 @@ fn test_harness_filter_matches_only_selected_harness() { let gemini_items = items_for(HarnessFilter::Specific(Harness::Gemini)); assert_eq!(gemini_items, vec![format!("task:{}", task_gemini.task_id)]); - // Zap Agent / Oz → default-snapshot task and local conversation. + // Zaplex Agent / Oz → default-snapshot task and local conversation. // The stub task with no snapshot resolves to `harness() == None` and // is deliberately excluded from any specific-harness filter. let oz_items = items_for(HarnessFilter::Specific(Harness::Oz)); assert_eq!( oz_items.len(), 2, - "expected 2 Zap Agent matches, got {oz_items:?}" + "expected 2 Zaplex Agent matches, got {oz_items:?}" ); assert!(oz_items.contains(&format!("task:{}", task_oz_default.task_id))); assert!(oz_items.contains(&format!("conversation:{conv_id}"))); assert!( !oz_items.contains(&format!("task:{}", task_no_snapshot.task_id)), - "stub task with no snapshot should not match the Zap Agent filter" + "stub task with no snapshot should not match the Zaplex Agent filter" ); }); }); diff --git a/app/src/ai/agent_events/message_hydrator.rs b/app/src/ai/agent_events/message_hydrator.rs index 07ec69ca3b..3502efd43d 100644 --- a/app/src/ai/agent_events/message_hydrator.rs +++ b/app/src/ai/agent_events/message_hydrator.rs @@ -1,7 +1,7 @@ use crate::ai::agent::ReceivedMessageInput; use crate::ai::agent_events::AgentRunEvent; -/// Zap local builds no longer fetch message bodies from cloud mailbox or send delivery receipts. +/// Zaplex local builds no longer fetch message bodies from cloud mailbox or send delivery receipts. /// This type preserves side-effect-free compatible semantics for the local harness bridging call surface. #[derive(Clone)] pub(crate) struct MessageHydrator; diff --git a/app/src/ai/agent_events/mod.rs b/app/src/ai/agent_events/mod.rs index 4ae445627e..7b3373e2c6 100644 --- a/app/src/ai/agent_events/mod.rs +++ b/app/src/ai/agent_events/mod.rs @@ -17,7 +17,7 @@ pub(crate) use driver::{ }; pub(crate) use message_hydrator::MessageHydrator; -/// Local agent event stream entry point. Zap retains interface to support local driver injection, default implementation disables cloud RTC. +/// Local agent event stream entry point. Zaplex retains interface to support local driver injection, default implementation disables cloud RTC. #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] pub(crate) trait AgentEventStreamClient: 'static + Send + Sync { @@ -39,7 +39,7 @@ impl AgentEventStreamClient for DisabledAgentEventStreamClient { _since_sequence: i64, ) -> Result { Err(anyhow!( - "Agent event stream disabled in Zap - RTC endpoint is removed" + "Agent event stream disabled in Zaplex - RTC endpoint is removed" )) } } diff --git a/app/src/ai/agent_providers/active_ai/mod.rs b/app/src/ai/agent_providers/active_ai/mod.rs index 98713e04f2..44aacb3b12 100644 --- a/app/src/ai/agent_providers/active_ai/mod.rs +++ b/app/src/ai/agent_providers/active_ai/mod.rs @@ -12,7 +12,7 @@ //! 3. UI callback directly consumes returned response, completely equivalent to original `ServerApi` path //! //! No BYOP configuration (`active_ai_model` decode fails) → `dispatch::*` returns `None`, -//! caller silently no-ops (Zap has removed cloud, no longer fallback to ServerApi). +//! caller silently no-ops (Zaplex has removed cloud, no longer fallback to ServerApi). use minijinja::{context, Environment}; use serde::Serialize; diff --git a/app/src/ai/agent_providers/cache_stability_tests.rs b/app/src/ai/agent_providers/cache_stability_tests.rs index 826f4b0474..1f7478d97f 100644 --- a/app/src/ai/agent_providers/cache_stability_tests.rs +++ b/app/src/ai/agent_providers/cache_stability_tests.rs @@ -10,7 +10,7 @@ //! 2. Not depend on `HashMap` iteration order //! 3. Not depend on external state (timestamps, randomness, PID, etc.) //! -//! This test suite is Zap's "anti-regression safeguard"—any future changes to the prompt +//! This test suite is Zaplex's "anti-regression safeguard"—any future changes to the prompt //! construction path that break byte-level stability will cause assertions here to fail. use crate::ai::agent::{MCPContext, MCPServer}; diff --git a/app/src/ai/agent_providers/chat_stream.rs b/app/src/ai/agent_providers/chat_stream.rs index c56ce957f0..c293ba27f9 100644 --- a/app/src/ai/agent_providers/chat_stream.rs +++ b/app/src/ai/agent_providers/chat_stream.rs @@ -150,7 +150,7 @@ fn render_lrc_request_context(params: &RequestParams) -> Option { }) } -/// Zap: render an SSH session status block and append it to the end of the system prompt. +/// Zaplex: render an SSH session status block and append it to the end of the system prompt. /// /// Trigger condition: `SessionContext.is_legacy_ssh()` is true (the user manually typed /// `ssh xx@xx` in a local PTY to enter a remote host that has no warp shell hook installed). For such sessions: @@ -161,7 +161,7 @@ fn render_lrc_request_context(params: &RequestParams) -> Option { /// Without explicitly telling the model this, the LLM will infer from the local OS in the system prompt that "the target is on the remote, /// I have to ssh over first," and so output a doubly-nested command like `ssh xx@xx uname -a`. /// -/// Note: warpified SSH (`SessionType::WarpifiedRemote`) is not handled here — on that path +/// Note: zaplexified SSH (`SessionType::ZaplexifiedRemote`) is not handled here — on that path /// the remote shell hook has re-bootstrapped, host_info / shell are the true remote values, and the prompt is correct as-is. fn render_ssh_session_block( session_context: &crate::ai::blocklist::SessionContext, @@ -396,7 +396,7 @@ fn build_user_message_with_binaries( let mut error_replacements: Vec<(String, String)> = Vec::new(); for bin in binaries { if !caps.supports_mime(&bin.content_type) { - // Zap aligns with opencode `unsupportedParts` (packages/opencode/src/provider/transform.ts:305-341): + // Zaplex aligns with opencode `unsupportedParts` (packages/opencode/src/provider/transform.ts:305-341): // mimes the model does not support are not silently dropped; instead an ERROR text part is inserted, letting the LLM tell the user itself. // The wording strictly copies opencode's `ERROR: Cannot read {name} (this model does not support // {modality} input). Inform the user.`; modality is mapped from the mime prefix, and name prefers the filename. @@ -1191,7 +1191,7 @@ fn build_chat_request( plan_mode, ¶ms.user_rules, ); - // Zap: legacy SSH session profile patch. `render_system` goes through AIAgentContext, + // Zaplex: legacy SSH session profile patch. `render_system` goes through AIAgentContext, // so the OS/shell it gets is the local client; under legacy SSH the PTY is actually on the remote, // so append an SSH status block to correct the LLM's inference. if let Some(ssh_block) = render_ssh_session_block(¶ms.session_context) { @@ -1212,7 +1212,7 @@ fn build_chat_request( // reordered to the end, or duplication caused by LRC subagent copies). See that function's docs for details. let all_msgs: Vec<&api::Message> = collect_linearized_task_messages(¶ms.tasks); - // Zap BYOP local conversation compaction: apply conversation.compaction_state to the message sequence. + // Zaplex BYOP local conversation compaction: apply conversation.compaction_state to the message sequence. // 1. filter out the (user, assistant) pairs already covered by some compaction (`hidden_message_ids`) // 2. at the position of the hidden range, insert a synthesized pair of (user "compacted, summary follows" + assistant summary text) messages — // this step is emitted in place within the main loop via the `summary_inserts` index @@ -1306,7 +1306,7 @@ fn build_chat_request( )?; let mut buf = AssistantBuffer::new(force_echo_reasoning); - // Zap: the call_ids of subagent ToolCalls that were skipped in the history —— their + // Zaplex: the call_ids of subagent ToolCalls that were skipped in the history —— their // ToolCallResults must also be skipped, otherwise they become orphan tool_responses and Anthropic returns 400 outright: // `unexpected tool_use_id ... no corresponding tool_use block`. let mut skipped_subagent_call_ids: std::collections::HashSet = @@ -1335,7 +1335,7 @@ fn build_chat_request( match inner { api::message::Message::UserQuery(u) => { flush_assistant_buffer(&mut buf, &mut messages, &mut outbound_tool_groups); - // Zap: multimodal keep-alive across historical turns. warp's own path relies on the cloud server re-injecting InputContext; + // Zaplex: multimodal keep-alive across historical turns. warp's own path relies on the cloud server re-injecting InputContext; // the BYOP direct connection has no such layer, so when `make_user_query_message` persists, it stuffs all binaries // (image / pdf / audio) into `UserQuery.context.images`, and here we restore them back into // UserBinary via `build_user_message_with_binaries`, so later turns the model can still see the previously @@ -1407,7 +1407,7 @@ fn build_chat_request( buf.text = Some(a.text.clone()); } api::message::Message::ToolCall(tc) => { - // Zap BYOP: **the virtual subagent tool_call is not sent to the upstream model**. + // Zaplex BYOP: **the virtual subagent tool_call is not sent to the upstream model**. // In the LRC tag-in scenario, we synthesize a `Tool::Subagent { metadata: Cli }` at the head of the chat_stream // and write it into root.task.messages, solely to trigger the conversation creating a cli subtask + spawning the floating window; // it is not a tool call the model actually produced, and the model would be confused if it saw it (a spurious tool call + nothing to respond to). @@ -1448,7 +1448,7 @@ fn build_chat_request( } api::message::Message::ToolCallResult(tcr) => { flush_assistant_buffer(&mut buf, &mut messages, &mut outbound_tool_groups); - // Zap: the corresponding ToolCall was skipped (subagent virtual call) → skip the result too, + // Zaplex: the corresponding ToolCall was skipped (subagent virtual call) → skip the result too, // otherwise an orphan tool_response is left behind, causing an upstream 400. if skipped_subagent_call_ids.contains(&tcr.tool_call_id) { continue; @@ -1494,7 +1494,7 @@ fn build_chat_request( // Environment-type context (env / git / skills / ...) is rendered into the system prompt by prompt_renderer, // and does not overlap with this path. // - // Zap: in the LRC tag-in scenario, `running_command: Some(...)` contains the full PTY context + // Zaplex: in the LRC tag-in scenario, `running_command: Some(...)` contains the full PTY context // (alt-screen grid_contents + command + is_alt_screen_active flag), rendered into an // `` XML block via `render_running_command_context`. // The model decides based on this whether to call write_to_long_running_shell_command. @@ -1575,11 +1575,11 @@ fn build_chat_request( skill, user_query, .. } => { let mut composed = format!( - "请按下面的技能 \"{}\" 指引执行任务:\n\n{}\n\n---\n", + "Please execute the task following the skill \"{}\" guidance below:\n\n{}\n\n---\n", skill.name, skill.content, ); if let Some(uq) = user_query { - composed.push_str(&format!("用户进一步指令: {}", uq.query)); + composed.push_str(&format!("Further user instruction: {}", uq.query)); } messages.push(ChatMessage::user(composed)); } @@ -1607,7 +1607,7 @@ fn build_chat_request( prompt, overflow: _, } => { - // Zap BYOP local conversation compaction entry point — 1:1 aligned with opencode `compaction.ts processCompaction`. + // Zaplex BYOP local conversation compaction entry point — 1:1 aligned with opencode `compaction.ts processCompaction`. // // The earlier messages loop already cut the sequence to the head (dropping the tail) based on `summarize_head_end`; // here we append the final user message: `build_prompt(previous_summary, plugin_context)`, @@ -1720,7 +1720,7 @@ const REPAIR_PLACEHOLDER_NOTE: &str = "tool result was unavailable in repaired conversation history"; fn is_placeholder_tool_response_content(content: &str) -> bool { - if content == "(tool 执行结果未保留)" { + if content == "(tool result not retained)" { return true; } @@ -2273,7 +2273,7 @@ fn repair_tool_call_pairs_for_accepted_history_gaps( if !orphan_call_ids.is_empty() { log::warn!( - "[byop-diag] accepted_history_repair: 丢弃 {} 个孤儿 ToolResponse: \ + "[byop-diag] accepted_history_repair: discarded {} orphan ToolResponse(s): \ orphan_call_ids={:?}", orphan_call_ids.len(), orphan_call_ids @@ -2281,8 +2281,8 @@ fn repair_tool_call_pairs_for_accepted_history_gaps( } if !placeholders_inserted.is_empty() { log::info!( - "[byop-diag] accepted_history_repair: 给 {} 个 ToolCall 补 repair placeholder \ - ToolResponse: missing_call_ids={:?}", + "[byop-diag] accepted_history_repair: inserted repair placeholder ToolResponse for {} ToolCall(s): \ + missing_call_ids={:?}", placeholders_inserted.len(), placeholders_inserted ); @@ -2294,7 +2294,7 @@ fn repair_tool_call_pairs_for_accepted_history_gaps( // outbound_tool_groups construction logic introduced a difference). At this point we cannot continue emitting // an illegal request with a missing ToolResponse, so we must block. log::error!( - "[byop-diag] accepted_history_repair: readiness 未授权的缺失 ToolResponse: \ + "[byop-diag] accepted_history_repair: readiness has unauthorized missing ToolResponse: \ missing_call_ids={:?}", missing_without_repair ); @@ -2625,7 +2625,7 @@ fn is_plan_mode_turn(input: &[AIAgentInput]) -> bool { /// a tool not in the tool list cannot be called (the provider protocol layer rejects unknown functions outright). /// /// **Write-class tools NOT blocked**: `create_documents` / `edit_documents`. They only touch -/// Zap Drive local document storage (AIDocumentModel), do not touch the file system, and do not run commands; semantically +/// Zaplex Drive local document storage (AIDocumentModel), do not touch the file system, and do not run commands; semantically /// they are exactly the output-archiving action of Plan Mode —— the model deposits the final plan as a Drive document, /// which the user can later view / edit / drag into their own PLAN folder in the Drive UI for reuse. /// @@ -2679,7 +2679,7 @@ pub fn available_tool_names(params: &RequestParams) -> Vec { } fn build_tools_array(params: &RequestParams) -> Vec { - // Zap A2: in the LRC tag-in scenario, drop `run_shell_command` to force the model to pick a PTY-operation tool. + // Zaplex A2: in the LRC tag-in scenario, drop `run_shell_command` to force the model to pick a PTY-operation tool. // // Under alt-screen long-running commands (nvim/htop) + the user tagged-in state, **the mistake the model is most prone to** // is calling `run_shell_command` to run `taskkill nvim` / `Stop-Process nvim` (spawning a new process), @@ -2696,7 +2696,7 @@ fn build_tools_array(params: &RequestParams) -> Vec { let is_lrc = params.lrc_command_id.is_some(); let web_enabled = params.web_search_enabled; let plan_mode = is_plan_mode_turn(¶ms.input); - // Zap BYOP: the `suggest_prompt` chip UI has been restored via the view layer subscribing to + // Zaplex BYOP: the `suggest_prompt` chip UI has been restored via the view layer subscribing to // PromptSuggestionExecutorEvent (see `terminal/view.rs:: // handle_suggest_prompt_executor_event`), so it can be exposed to the model. // `suggest_new_conversation` is still filtered: the UX has no ready-made popup component, and the executor was changed to @@ -2719,7 +2719,7 @@ fn build_tools_array(params: &RequestParams) -> Vec { { return false; } - // suggest_new_conversation: no UI implementation; in Zap the executor was changed to + // suggest_new_conversation: no UI implementation; in Zaplex the executor was changed to // fast-fail Cancelled. Filter it out here to avoid a model call producing a meaningless // tool_call→cancelled round trip (pure token waste). if t.name == "suggest_new_conversation" { @@ -2862,7 +2862,7 @@ pub(super) fn build_client( }, ); - // Zap BYOP: the SSE stream must not carry gzip. `Accept-Encoding: gzip` makes nginx-style + // Zaplex BYOP: the SSE stream must not carry gzip. `Accept-Encoding: gzip` makes nginx-style // proxies compress the response, and the server must flush a complete deflate frame before the client can decode // the plaintext, breaking streaming semantics into ~K-byte bursts that feel like "a stutter every few hundred ms". zed/opencode // use native fetch / std HTTP and do not actively negotiate gzip on SSE, so the same proxy is fine. @@ -2870,7 +2870,7 @@ pub(super) fn build_client( // We explicitly construct `WebConfig` here even though the genai default is already `gzip=false` (fork modification). // // User-Agent dynamically binds the current application name (taken from `ChannelState::app_id().application_name()`, - // registered by the entry bin: `bin/oss.rs` → "Zap"; other channels carry their own names). + // registered by the entry bin: `bin/oss.rs` → "Zaplex"; other channels carry their own names). // This way the upstream service can identify which fork build the request comes from, and it automatically follows any later rename. let mut headers = reqwest::header::HeaderMap::new(); if let Ok(value) = build_user_agent_header() { @@ -2889,7 +2889,7 @@ pub(super) fn build_client( &proxy_cfg.password, &proxy_cfg.no_proxy, ) { - log::warn!("[byop] proxy URL '{}' 无效,跳过代理配置: {err}", proxy_cfg.url); + log::warn!("[byop] proxy URL '{}' is invalid, skipping proxy config: {err}", proxy_cfg.url); } } Client::builder() @@ -2899,11 +2899,11 @@ pub(super) fn build_client( } /// Construct the `User-Agent` header for BYOP outbound requests, with a value like: -/// - `Zap/` —— for release builds where `GIT_RELEASE_TAG` is injected -/// - `Zap` —— for Dev / local builds with no version +/// - `Zaplex/` —— for release builds where `GIT_RELEASE_TAG` is injected +/// - `Zaplex` —— for Dev / local builds with no version /// /// The application name is always taken from `ChannelState::app_id().application_name()`, ensuring consistency with the `AppId` -/// registered by the entry bin (`bin/oss.rs` registers "Zap"). +/// registered by the entry bin (`bin/oss.rs` registers "Zaplex"). fn build_user_agent_header( ) -> Result { let app_name = warp_core::channel::ChannelState::app_id() @@ -2968,7 +2968,7 @@ fn dashscope_needs_enable_thinking( /// `opencode*` send it. All other providers (including all OpenAI-compatible /// relays / local services / most Chinese clouds) never send it. /// -/// Zap has no `providerID` dimension, only the user-provided `base_url`. +/// Zaplex has no `providerID` dimension, only the user-provided `base_url`. /// So we infer it from `base_url`: /// - `api.openai.com` → "openai" /// - `*.openai.azure.com` → "azure" @@ -3024,7 +3024,7 @@ fn build_chat_options( // `openai` / `azure` / `openrouter` / `venice` / `opencode`. All other providers // (including all OpenAI-compatible relays / Chinese clouds / local services) never send it. // - // opencode decides via `providerID` (the string the user picks in config); Zap has no + // opencode decides via `providerID` (the string the user picks in config); Zaplex has no // `providerID`, so it can only infer from `base_url` → see `opencode_compatible_cache_provider`. // This is base_url's only semantic use; do not extend it to decide behavior beyond caching. // @@ -3056,7 +3056,7 @@ fn build_chat_options( // (`lib/rust-genai/src/adapter/adapters/anthropic/adapter_impl.rs:121-135` // does not read whether effort is `None`). // - **Off + DeepSeek**: the server's `thinking_mode` is on by default (deepseek-v4-flash etc.), - // and an explicit `extra_body.thinking.type=disabled` is needed to turn it off. Zap's local fork + // and an explicit `extra_body.thinking.type=disabled` is needed to turn it off. Zaplex's local fork // of genai already supports top-level merging of `ChatOptions::extra_body`. // - **Off + OpenAI / OpenAiResp**: take the `reasoning_effort: "none"` path // (GPT-5 / codex accept the `none` tier; o-series is filtered by the capability table). @@ -3261,7 +3261,7 @@ pub async fn generate_byop_output( // // The emit timing must be after CreateTask (the task has been upgraded to Server state), // and before the model response begins (UI order: user shown → thinking/answer). - // Zap: multimodal keep-alive across historical turns. Besides the query text, package all of this turn's + // Zaplex: multimodal keep-alive across historical turns. Besides the query text, package all of this turn's // multimodal binaries (image / pdf / audio / ...) from UserQuery.context into `UserQuery.context.images` // for persistence (the proto field is named images, but semantically it is a generic BinaryFile —— `bytes data + mime_type`, // equivalent to opencode FilePart), so that when build_chat_request rebuilds messages on the next turn it can restore the binary @@ -3517,7 +3517,7 @@ pub async fn generate_byop_output( // takes the `Task::new_subtask` path, automatically binding SubagentParams. yield Ok(create_subtask_event(&subtask_id, &task_id)); - // c) Zap A1: also copy this turn's UserQuery into the subtask, to initialize the subtask's + // c) Zaplex A1: also copy this turn's UserQuery into the subtask, to initialize the subtask's // exchange.output.messages. Otherwise when CLISubagentView renders, the subtask's exchanges // output is empty, and the floating window forever shows only a 49.6-height empty dialog box with no content. // The upstream cloud has a full ClientAction sequence filling exchange.output on the cli subagent task, @@ -3629,7 +3629,7 @@ pub async fn generate_byop_output( let byte_len = body.len(); let start = col.saturating_sub(200).min(byte_len); let end = (col + 200).min(byte_len); - let context = body.get(start..end).unwrap_or("(slice failed: 非 char 边界)"); + let context = body.get(start..end).unwrap_or("(slice failed: not a char boundary)"); log::error!( "[byop] error column={col} diag_body_len={byte_len} context[{start}..{end}]={context:?}" ); @@ -3944,7 +3944,7 @@ pub async fn generate_byop_output( if chunk_count == 0 && reasoning_count == 0 && total_tools == 0 { log::warn!( "[byop] stream returned 0 content / 0 reasoning / 0 tool_calls — \ - 上游可能返回空响应(model_id 错? max_tokens 缺? proxy 异常?)" + upstream may have returned an empty response (invalid model_id? missing max_tokens? proxy error?)" ); } @@ -4009,7 +4009,7 @@ pub async fn generate_byop_output( ); } - // Zap BYOP todowrite interception: not mapped to the protobuf executor; synthesize + // Zaplex BYOP todowrite interception: not mapped to the protobuf executor; synthesize // `Message::UpdateTodos` to write conversation.todo_lists directly, triggering the chip + popup // UI (aligned with the server-side ClientAction::AddMessagesToTask::UpdateTodos path). // Then append a carrier ToolCall + ToolCallResult to unblock the model. @@ -4099,7 +4099,7 @@ pub async fn generate_byop_output( continue; } - // Zap BYOP web tool interception: webfetch / websearch are not mapped to a protobuf + // Zaplex BYOP web tool interception: webfetch / websearch are not mapped to a protobuf // executor variant; run local HTTP directly here, synthesizing a (carrier ToolCall, // ToolCallResult) pair of messages, bypassing parse_incoming_tool_call. // @@ -4396,10 +4396,6 @@ fn sanitize_title(raw: &str) -> Option { "title:", "subject:", "thread:", - "标题:", - "标题:", - "主题:", - "主题:", ]; loop { let lower = s.to_lowercase(); @@ -4700,7 +4696,7 @@ fn make_user_query_message( query: String, binaries: &[user_context::UserBinary], ) -> api::Message { - // Zap: write the multimodal binaries (image / pdf / audio etc.) into `UserQuery.context.images` + // Zaplex: write the multimodal binaries (image / pdf / audio etc.) into `UserQuery.context.images` // (an InputContext field; the proto Image is actually a generic container `bytes data + string mime_type`, // named images for historical reasons). UserBinary.data is a base64 string while proto.data is raw bytes, // so decode once here; skip entries that fail to decode, without blocking the model stream (a decode failure already means this entry @@ -5139,7 +5135,7 @@ mod assistant_buffer_tests { buf.reasoning = Some("planning".to_string()); let mut msgs = Vec::new(); buf.flush_into(&mut msgs); - assert_eq!(msgs.len(), 2, "text + tool_calls flush 成两条"); + assert_eq!(msgs.len(), 2, "text + tool_calls should flush into two messages"); for m in &msgs { assert!( reasoning_part(m).is_none(), @@ -5342,11 +5338,11 @@ mod build_chat_options_off_tests { let o = opts(AgentProviderApiType::Anthropic, "claude-sonnet-4-6", R::Off); assert!( o.reasoning_effort.is_none(), - "Anthropic+Off 必须不传 reasoning_effort,避免 4.6 系强插 adaptive thinking" + "Anthropic+Off must not send reasoning_effort to avoid 4.6 forcing adaptive thinking" ); assert!( o.extra_body.is_none(), - "Anthropic+Off 也不应注入 extra_body" + "Anthropic+Off should also not inject extra_body" ); } @@ -5402,13 +5398,13 @@ mod build_chat_options_off_tests { let o = opts(AgentProviderApiType::DeepSeek, "deepseek-v4-flash", R::Off); assert!( o.reasoning_effort.is_none(), - "DeepSeek+Off 不能走 reasoning_effort=none" + "DeepSeek+Off must not use reasoning_effort=none" ); let body = o.extra_body.as_ref().expect("extra_body must be set"); assert_eq!( body.pointer("/thinking/type"), Some(&serde_json::Value::String("disabled".to_string())), - "DeepSeek+Off 必须发 thinking.type=disabled" + "DeepSeek+Off must send thinking.type=disabled" ); } @@ -5426,7 +5422,7 @@ mod build_chat_options_off_tests { let o = opts(AgentProviderApiType::OpenAi, "gpt-5", R::Off); assert!( matches!(o.reasoning_effort, Some(GE::None)), - "OpenAI+GPT-5+Off 应发 reasoning_effort=none" + "OpenAI+GPT-5+Off should send reasoning_effort=none" ); } @@ -5467,7 +5463,7 @@ mod cache_boundary_stability_tests { fn build_three_turn_conversation() -> Vec { vec![ ChatMessage::system( - "You are a helpful coding assistant for Zap BYOP.\n\ + "You are a helpful coding assistant for Zaplex BYOP.\n\ Guidelines: be concise, prefer code over prose.", ), ChatMessage::user("What is rust borrow checker?"), @@ -5501,7 +5497,7 @@ mod cache_boundary_stability_tests { assert_eq!( cache_signature(&a), cache_signature(&b), - "同输入 × 多次调用 cache 标记必须一致" + "same input × multiple invocations: cache marks must be consistent" ); } @@ -5515,7 +5511,7 @@ mod cache_boundary_stability_tests { .iter() .filter(|m| extract_cache_control(m).is_some()) .collect(); - assert!(!tagged.is_empty(), "必须至少打一个 breakpoint"); + assert!(!tagged.is_empty(), "must set at least one cache breakpoint"); for m in &tagged { let cc = extract_cache_control(m).unwrap(); let expected = if matches!(m.role, ChatRole::System) { @@ -5523,7 +5519,7 @@ mod cache_boundary_stability_tests { } else { CacheControl::Ephemeral5m }; - assert_eq!(cc, expected, "role={:?} 的 TTL 不匹配预期", m.role); + assert_eq!(cc, expected, "role={:?} cache TTL does not match expected value", m.role); } } @@ -5540,13 +5536,13 @@ mod cache_boundary_stability_tests { .map(|(i, _)| i) .collect(); // Verify that system (idx=0) and the last 2 non-system (idx=4, idx=5) are all marked. - assert!(tagged_indices.contains(&0), "首 system 未被标记"); - assert!(tagged_indices.contains(&4), "倒数第 2 条未被标记"); - assert!(tagged_indices.contains(&5), "末条未被标记"); + assert!(tagged_indices.contains(&0), "first system message was not marked"); + assert!(tagged_indices.contains(&4), "second-to-last message was not marked"); + assert!(tagged_indices.contains(&5), "last message was not marked"); assert_eq!( tagged_indices.len(), 3, - "总计 3 个 breakpoint(1 system + 2 tail)" + "total 3 breakpoints (1 system + 2 tail)" ); } @@ -5573,13 +5569,13 @@ mod cache_boundary_stability_tests { // The first system's cache_control is consistent across turns → means the upstream hash is unchanged → subsequent hits. assert_eq!( sys_t1_cc, sys_t2_cc, - "首 system breakpoint 的 TTL/位置跨轮应一致" + "first system breakpoint TTL/position should be consistent across turns" ); // turn 1's user position is marked (the tail); turn 2 no longer marks it. assert!(extract_cache_control(&t1[1]).is_some()); assert!( extract_cache_control(&t2[1]).is_none(), - "turn 2 的旧 user 不再是 tail" + "turn 2's old user message is no longer the tail" ); } @@ -5631,11 +5627,11 @@ mod cache_boundary_stability_tests { assert_eq!( opts.prompt_cache_key.as_deref(), Some("conv-1"), - "{url}: 白名单 provider 应下发 prompt_cache_key=conversation_id" + "{url}: whitelisted provider should send prompt_cache_key=conversation_id" ); assert!( opts.cache_control.is_none(), - "{url}: cache_control 永远不发(opencode 不使用 prompt_cache_retention)" + "{url}: cache_control should never be sent (opencode does not use prompt_cache_retention)" ); } } @@ -5673,11 +5669,11 @@ mod cache_boundary_stability_tests { ); assert!( opts.cache_control.is_none(), - "{url}: 非白名单不应下发 cache_control" + "{url}: non-whitelisted provider should not send cache_control" ); assert!( opts.prompt_cache_key.is_none(), - "{url}: 非白名单不应下发 prompt_cache_key" + "{url}: non-whitelisted provider should not send prompt_cache_key" ); } } @@ -5724,9 +5720,9 @@ mod cache_boundary_stability_tests { ); assert!( opts.prompt_cache_key.is_none(), - "空 conversation_id 应跳过 prompt_cache_key" + "empty conversation_id should skip prompt_cache_key" ); - assert!(opts.cache_control.is_none(), "cache_control 永远不发"); + assert!(opts.cache_control.is_none(), "cache_control should never be sent"); } /// **The Anthropic path's build_chat_options does not send cache_control** @@ -5744,11 +5740,11 @@ mod cache_boundary_stability_tests { ); assert!( opts.cache_control.is_none(), - "Anthropic 的 ChatOptions 不能带 cache_control(走 per-message)" + "Anthropic ChatOptions must not include cache_control (uses per-message instead)" ); assert!( opts.prompt_cache_key.is_none(), - "Anthropic 不走 prompt_cache_key" + "Anthropic does not use prompt_cache_key" ); } @@ -5771,7 +5767,7 @@ mod cache_boundary_stability_tests { ); assert!( opts.cache_control.is_none(), - "{api:?} 不应下发 cache_control" + "{api:?} should not send cache_control" ); } } @@ -6649,7 +6645,7 @@ mod serializer_readiness_tests { payload["note"], "tool result was unavailable in repaired conversation history" ); - assert!(!response.content.contains("(tool 执行结果未保留)")); + assert!(!response.content.contains("(tool result not retained)")); assert!( params.tasks[0].messages.iter().all(|message| !matches!( message.message, @@ -6882,7 +6878,7 @@ mod accepted_history_repair_tests { ]; repair_messages(&mut msgs); - assert_eq!(msgs.len(), 3, "两条相邻 Tool 合并为一条"); + assert_eq!(msgs.len(), 3, "two adjacent Tool messages should merge into one"); assert_eq!(msgs[0].role, ChatRole::User); assert_eq!(msgs[1].role, ChatRole::Assistant); assert_eq!(msgs[2].role, ChatRole::Tool); @@ -6892,7 +6888,7 @@ mod accepted_history_repair_tests { ("a".to_owned(), "resp_a".to_owned()), ("b".to_owned(), "resp_b".to_owned()), ], - "bundled response 顺序必须与 Assistant.tool_calls 一致" + "bundled response order must match Assistant.tool_calls" ); } @@ -6903,7 +6899,7 @@ mod accepted_history_repair_tests { let mut msgs = vec![ChatMessage::user("q"), assistant_with_calls(&["a", "b"])]; repair_messages(&mut msgs); - assert_eq!(msgs.len(), 3, "Assistant 后必须补一条 Tool message"); + assert_eq!(msgs.len(), 3, "Tool message must be added after Assistant"); assert_eq!(msgs[2].role, ChatRole::Tool); let responses = responses_of(&msgs[2]); assert_eq!( @@ -6945,12 +6941,12 @@ mod accepted_history_repair_tests { ]; repair_messages(&mut msgs); - assert_eq!(msgs.len(), 3, "两条相邻 Tool 合并,孤儿 z 丢弃"); + assert_eq!(msgs.len(), 3, "two adjacent Tool messages merge, orphan z is dropped"); let responses = responses_of(&msgs[2]); assert_eq!( responses, vec![("a".to_owned(), "real_a".to_owned())], - "只保留 Assistant 认识的 call_id" + "only keep call_ids known to Assistant" ); } @@ -7016,7 +7012,7 @@ mod accepted_history_repair_tests { assert_eq!( responses_of(&msgs[2]), vec![("a".to_owned(), "real_a_v2".to_owned())], - "同 call_id 多条真实 response,后到者胜出" + "multiple real responses for the same call_id: later one wins" ); } @@ -7029,7 +7025,7 @@ mod accepted_history_repair_tests { ChatMessage::user("q1"), assistant_with_calls(&["a"]), tool_response("a", "real_a"), - tool_response("a", "(tool 执行结果未保留)"), + tool_response("a", "(tool result not retained)"), ]; repair_messages(&mut msgs); @@ -7037,7 +7033,7 @@ mod accepted_history_repair_tests { assert_eq!( responses_of(&msgs[2]), vec![("a".to_owned(), "real_a".to_owned())], - "placeholder 不能覆盖真实值" + "placeholder must not overwrite real value" ); } @@ -7047,7 +7043,7 @@ mod accepted_history_repair_tests { let mut msgs = vec![ ChatMessage::user("q1"), assistant_with_calls(&["a"]), - tool_response("a", "(tool 执行结果未保留)"), + tool_response("a", "(tool result not retained)"), ChatMessage::user("interrupt"), tool_response("a", "real_a"), ]; @@ -7142,7 +7138,7 @@ mod accepted_history_repair_tests { assert_eq!( responses_of(&msgs[5]), vec![("dup".to_owned(), "real_second".to_owned())], - "重复 call_id 的真实结果必须留在自己的 Assistant group 后" + "duplicate call_id real result must remain after its own Assistant group" ); } @@ -7173,7 +7169,7 @@ mod accepted_history_repair_tests { assert_eq!( responses_of(&msgs[2]), vec![("a".to_owned(), real_unavailable.to_owned())], - "真实 unavailable JSON 不能被 repair placeholder 覆盖" + "real unavailable JSON must not be overwritten by repair placeholder" ); } } @@ -7281,23 +7277,23 @@ mod issue_94_task_linearization_tests { assert_eq!( count_user_queries(&root_first_refs), 2, - "朴素拼接会让同一条 user query 出现两次 —— 这正是 Issue #94 的 bug" + "naive concatenation causes the same user query to appear twice — this is Issue #94 bug" ); // (2) Order drifts with input task order —— when the subtask is first, the historical user (m1) is thrown to the end. assert_ne!( root_first, subtask_first, - "朴素拼接结果依赖 task 顺序,非确定性" + "naive concatenation result depends on task order, non-deterministic" ); assert_eq!( subtask_first.last().map(String::as_str), Some("m3"), - "subtask 排前时 root 的消息整体后移" + "when subtask is first, root messages shift to the end" ); assert!( subtask_first.iter().position(|id| id == "s1").unwrap() < subtask_first.iter().position(|id| id == "m1").unwrap(), - "subtask 的 UserQuery 副本(s1)排到了 root 原件(m1)之前" + "subtask UserQuery copy (s1) comes before root original (m1)" ); } @@ -7317,14 +7313,14 @@ mod issue_94_task_linearization_tests { assert_eq!( message_ids(&a), message_ids(&b), - "结果必须与 params.tasks 的输入顺序无关" + "result must be independent of params.tasks input order" ); // UserQuery deduplication: the LRC-copied subtask duplicate (s1) is dropped. assert_eq!( count_user_queries(&a), 1, - "重复的 UserQuery 必须被去重为一条" + "duplicate UserQuery must be deduplicated into one" ); // DFS linear order: root's messages come first, descending into the subtask on a Subagent ToolCall. @@ -7364,7 +7360,7 @@ mod issue_94_task_linearization_tests { assert_eq!( count_user_queries(&out), 2, - "request_id 不同的两轮 user 消息都要保留" + "both user messages with different request_ids must be preserved" ); assert_eq!(message_ids(&out), vec!["m1", "m2", "m3"]); } diff --git a/app/src/ai/agent_providers/llm_id.rs b/app/src/ai/agent_providers/llm_id.rs index ff9faa367b..31a422de54 100644 --- a/app/src/ai/agent_providers/llm_id.rs +++ b/app/src/ai/agent_providers/llm_id.rs @@ -1,7 +1,7 @@ //! BYOP (Bring Your Own Provider) `LLMId` prefix encoding/decoding. //! //! Custom Agent provider models are distinguished in the `LLMId` string by the `byop:` prefix, -//! allowing the controller to determine at request egress whether to use the Zap backend +//! allowing the controller to determine at request egress whether to use the Zaplex backend //! or the user's own OpenAI-compatible endpoint. //! //! Encoding format: `byop::` diff --git a/app/src/ai/agent_providers/prompt_renderer.rs b/app/src/ai/agent_providers/prompt_renderer.rs index d164bfbb91..368557c1bc 100644 --- a/app/src/ai/agent_providers/prompt_renderer.rs +++ b/app/src/ai/agent_providers/prompt_renderer.rs @@ -209,7 +209,7 @@ struct ProjectRuleCtx { content: String, } -/// Zap BYOP fix for Issue #116: a flat view of the global Rules (created by the user under +/// Zaplex BYOP fix for Issue #116: a flat view of the global Rules (created by the user under /// Settings → Agents → Rules), fed to `partials/user_rules.j2` to be rendered into the system prompt. #[derive(Debug, Serialize)] struct UserRuleCtx { @@ -230,7 +230,7 @@ struct PromptContext { git: Option, skills: Vec, project_rules: Vec, - /// Zap BYOP fix for Issue #116: injected by the caller (`render_system`) from + /// Zaplex BYOP fix for Issue #116: injected by the caller (`render_system`) from /// `RequestParams.user_rules` and rendered via `partials/user_rules.j2`. user_rules: Vec, current_time: String, @@ -284,7 +284,7 @@ fn collect_prompt_context(model_id: &str, ctx: &[AIAgentContext]) -> PromptConte } AIAgentContext::CurrentTime { current_time } => { // P0-1: consistent with the default value, keep only calendar-day granularity. - // Upstream Zap may pass a second-precision timestamp, so we normalize it down to "current date" here. + // Upstream Zaplex may pass a second-precision timestamp, so we normalize it down to "current date" here. out.current_time = current_time.format("%Y-%m-%d").to_string(); } // Code indexing is not implemented, so Codebase context does not go into the system prompt. @@ -442,7 +442,7 @@ fn fallback_init_project_command(arguments: &str) -> String { /// Renders the fallback system prompt (used only when template loading/rendering fails; should not be triggered on the normal path). fn fallback_system(model_id: &str) -> String { format!( - "You are the AI coding agent inside Zap, an AI Development Environment (ADE). \ + "You are the AI coding agent inside Zaplex, an AI Development Environment (ADE). \ Model: {model_id}. \ Use the registered tools (run_shell_command / read_files / apply_file_diffs / grep / file_glob / ...) \ to take actions on the user's behalf. Be concise." @@ -551,7 +551,7 @@ mod tests { #[test] fn render_produces_non_empty_for_all_families() { - // Any model id should render a non-empty string (containing Zap's self-identification). + // Any model id should render a non-empty string (containing Zaplex's self-identification). for id in [ "claude-sonnet-4-5", "gpt-4o", @@ -570,8 +570,8 @@ mod tests { &[], ); assert!( - out.contains("Zap"), - "id={id} should mention Zap, got: {out}" + out.contains("Zaplex"), + "id={id} should mention Zaplex, got: {out}" ); } } @@ -626,7 +626,7 @@ mod tests { name: "find-skills".into(), description: "Help discover and install new agent skills.".into(), scope: SkillScope::Bundled, - provider: SkillProvider::Zap, + provider: SkillProvider::Zaplex, icon_override: Some(Icon::WarpLogoLight), }; let ctx = vec![AIAgentContext::Skills { diff --git a/app/src/ai/agent_providers/reasoning.rs b/app/src/ai/agent_providers/reasoning.rs index 3669fdcd21..148f355968 100644 --- a/app/src/ai/agent_providers/reasoning.rs +++ b/app/src/ai/agent_providers/reasoning.rs @@ -76,7 +76,7 @@ pub fn model_reasoning_variants( vec![] } // DeepSeek thinking-mode models (deepseek-reasoner / v4 / thinking / r1). - // Zap's local fork (`lib/rust-genai`) relaxes the injection condition in + // Zaplex's local fork (`lib/rust-genai`) relaxes the injection condition in // adapter_shared.rs so the top-level `reasoning_effort` field is sent per the // DeepSeek thinking_mode documentation. // diff --git a/app/src/ai/agent_providers/tools/ask.rs b/app/src/ai/agent_providers/tools/ask.rs index 38af2fd703..9ffbe87347 100644 --- a/app/src/ai/agent_providers/tools/ask.rs +++ b/app/src/ai/agent_providers/tools/ask.rs @@ -46,34 +46,34 @@ fn parameters() -> Value { "properties": { "questions": { "type": "array", - "description": "要向用户提的问题列表(通常 1 个就够,确实有多维需澄清才发多个)。", + "description": "Questions to ask the user (usually 1 is enough; provide multiple only if truly multi-dimensional clarification is needed).", "items": { "type": "object", "properties": { "question": { "type": "string", - "description": "问题文本(中文,简短具体)。" + "description": "Question text: short and specific." }, "options": { "type": "array", "items": {"type": "string"}, "minItems": 2, "maxItems": 4, - "description": "可选项标签列表,2-4 个,具体描述每条选项后果。" + "description": "Option labels list, 2-4 items; each should clearly describe the consequence of that option." }, "recommended_index": { "type": "integer", - "description": "0-based 推荐选项的下标。", + "description": "0-based index of the recommended option.", "default": 0 }, "multi_select": { "type": "boolean", - "description": "是否允许用户多选。", + "description": "Whether the user is allowed to select multiple options.", "default": false }, "supports_other": { "type": "boolean", - "description": "是否允许用户输入 \"其他\" 自由文本。", + "description": "Whether the user is allowed to enter free-form 'Other' text.", "default": false } }, diff --git a/app/src/ai/agent_providers/tools/documents.rs b/app/src/ai/agent_providers/tools/documents.rs index 50071d9b0d..3d0de433e4 100644 --- a/app/src/ai/agent_providers/tools/documents.rs +++ b/app/src/ai/agent_providers/tools/documents.rs @@ -1,4 +1,4 @@ -//! Zap Drive local document system: read / edit / create toolkit. +//! Zaplex Drive local document system: read / edit / create toolkit. //! //! Unlike `read_files` / `apply_file_diffs`: these operations target **documents managed by AIDocumentModel** //! (local documents internal to Drive, referenced by `document_id`), not files in the filesystem. @@ -124,7 +124,7 @@ fn read_result_to_json(result: &api::message::tool_call_result::Result) -> Optio pub static READ_DOCUMENTS: OpenAiTool = OpenAiTool { name: "read_documents", - description: "Read local Zap Drive documents (referenced by document_id, not filesystem files).\ + description: "Read local Zaplex Drive documents (referenced by document_id, not filesystem files).\ Returns JSON: { documents: [{document_id, content, line_range?}] }.\ Use when the user mentions a specific document_id or document in Drive.", parameters: read_parameters, @@ -213,7 +213,7 @@ fn edit_result_to_json(result: &api::message::tool_call_result::Result) -> Optio pub static EDIT_DOCUMENTS: OpenAiTool = OpenAiTool { name: "edit_documents", - description: "Perform string search-and-replace on existing documents in Zap Drive.\ + description: "Perform string search-and-replace on existing documents in Zaplex Drive.\ Similar to apply_file_diffs::edit, but targets Drive documents (referenced by document_id).\ search must match document's current content **exactly** (including whitespace and newlines), or the operation fails.", parameters: edit_parameters, @@ -299,7 +299,7 @@ fn create_result_to_json(result: &api::message::tool_call_result::Result) -> Opt pub static CREATE_DOCUMENTS: OpenAiTool = OpenAiTool { name: "create_documents", - description: "Create one or more new documents in Zap Drive (each with title + complete content).\ + description: "Create one or more new documents in Zaplex Drive (each with title + complete content).\ Ideal for consolidating analysis results, notes, todos, etc. into reusable Drive documents.", parameters: create_parameters, from_args: create_from_args, diff --git a/app/src/ai/agent_providers/tools/skill.rs b/app/src/ai/agent_providers/tools/skill.rs index 884a891317..f3548d4bdf 100644 --- a/app/src/ai/agent_providers/tools/skill.rs +++ b/app/src/ai/agent_providers/tools/skill.rs @@ -1,8 +1,8 @@ -//! `read_skill`: read Zap's Skill markdown template. +//! `read_skill`: read Zaplex's Skill markdown template. //! //! Skills are user/project-defined, reusable workflows (`SKILL.md` file + optional metadata). //! After the model reads a skill, it can advance tasks following the steps the user expects. -//! Zap maintains a `SkillManager` that indexes all available skills; they can be referenced +//! Zaplex maintains a `SkillManager` that indexes all available skills; they can be referenced //! either by name (frontmatter `name` field), by absolute path, or by bundled ID. //! //! ## Input Contract diff --git a/app/src/ai/agent_providers/tools/todowrite.rs b/app/src/ai/agent_providers/tools/todowrite.rs index 040ef6ec57..f91ea59908 100644 --- a/app/src/ai/agent_providers/tools/todowrite.rs +++ b/app/src/ai/agent_providers/tools/todowrite.rs @@ -18,7 +18,7 @@ //! `update_todo_list_from_todo_op` moves items matching the second message from pending to completed //! (`mark_todos_complete` looks up id in pending), resulting in final `AIAgentTodoList` state: //! `completed_items = [completed]`, `pending_items = [pending + in_progress]`. -//! Zap UI's `in_progress_item()` calls `pending_items.first()`, so the in_progress todo +//! Zaplex UI's `in_progress_item()` calls `pending_items.first()`, so the in_progress todo //! should be the first item in the `todos` array with `status != completed/cancelled`. //! //! Then synthesize a pair `Message::ToolCall`(carrier, tool=None) + `Message::ToolCallResult` @@ -47,7 +47,7 @@ pub struct TodoArg { /// parsing defaults unrecognized values to `pending`. #[serde(default)] pub status: String, - /// opencode protocol includes priority; Zap data model does not differentiate. + /// opencode protocol includes priority; Zaplex data model does not differentiate. /// Accepted but unused; retained to allow the model to send parameters per opencode convention without errors. #[serde(default, rename = "priority")] pub _priority: Option, diff --git a/app/src/ai/agent_providers/tools/web_runtime.rs b/app/src/ai/agent_providers/tools/web_runtime.rs index d6f874ce33..aec47cb9b9 100644 --- a/app/src/ai/agent_providers/tools/web_runtime.rs +++ b/app/src/ai/agent_providers/tools/web_runtime.rs @@ -8,7 +8,7 @@ //! ## Alignment with opencode //! //! - `webfetch` mirrors `packages/opencode/src/tool/webfetch.ts`: -//! * User-Agent defaults to Chrome; 403 + `cf-mitigated: challenge` → retry once with `Zap` UA +//! * User-Agent defaults to Chrome; 403 + `cf-mitigated: challenge` → retry once with `Zaplex` UA //! * `Accept` header negotiated by format parameter q priority //! * Content-Length pre-check + actual byte double-check, 5 MB limit //! * Timeout defaults to 30s, max 120s @@ -43,7 +43,7 @@ pub const SEARCH_TIMEOUT_SECS: u64 = 25; pub const CHROME_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"; -pub const FALLBACK_UA: &str = "Zap"; +pub const FALLBACK_UA: &str = "Zaplex"; // --------------------------------------------------------------------------- // webfetch diff --git a/app/src/ai/agent_providers/tools/webfetch_tests.rs b/app/src/ai/agent_providers/tools/webfetch_tests.rs index d9e6de1ac1..5f38cb2bf3 100644 --- a/app/src/ai/agent_providers/tools/webfetch_tests.rs +++ b/app/src/ai/agent_providers/tools/webfetch_tests.rs @@ -400,11 +400,11 @@ async fn ssrf_safe_client_builds_with_redirect_policy() { // --------------------------------------------------------------------------- // Real endpoint smoke tests are skipped by default to avoid CI/offline dev relying on external network. -// Set WARP_RUN_WEB_INTEGRATION=1 when manual verification is needed. +// Set ZAPLEX_RUN_WEB_INTEGRATION=1 when manual verification is needed. // --------------------------------------------------------------------------- fn skip_real() -> bool { - std::env::var("WARP_RUN_WEB_INTEGRATION").is_err() + std::env::var("ZAPLEX_RUN_WEB_INTEGRATION").is_err() } #[tokio::test] diff --git a/app/src/ai/agent_providers/tools/websearch_tests.rs b/app/src/ai/agent_providers/tools/websearch_tests.rs index 80be4b6ea3..2ea280ed99 100644 --- a/app/src/ai/agent_providers/tools/websearch_tests.rs +++ b/app/src/ai/agent_providers/tools/websearch_tests.rs @@ -205,11 +205,11 @@ async fn handles_multiple_data_lines() { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- -// Real endpoint smoke test (enabled by default; set WARP_SKIP_WEB_INTEGRATION=1 when CI network is restricted) +// Real endpoint smoke test (enabled by default; set ZAPLEX_SKIP_WEB_INTEGRATION=1 when CI network is restricted) // --------------------------------------------------------------------------- fn skip_real() -> bool { - std::env::var("WARP_SKIP_WEB_INTEGRATION").is_ok() + std::env::var("ZAPLEX_SKIP_WEB_INTEGRATION").is_ok() } #[tokio::test] diff --git a/app/src/ai/agent_providers/user_context_tests.rs b/app/src/ai/agent_providers/user_context_tests.rs index b723b52b27..08dc7d811f 100644 --- a/app/src/ai/agent_providers/user_context_tests.rs +++ b/app/src/ai/agent_providers/user_context_tests.rs @@ -44,7 +44,7 @@ fn only_environment_context_returns_none() { ]; assert!( render_user_attachments(&ctx).is_none(), - "环境型 context 不应进 user message" + "environment-type context should not enter user message" ); } @@ -57,7 +57,7 @@ fn single_block_renders_required_fields() { 1, false, )))]; - let out = render_user_attachments(&ctx).expect("应当渲染"); + let out = render_user_attachments(&ctx).expect("should render"); assert!(out.starts_with("")); assert!(out.ends_with("")); assert!(out.contains("command_id=\"b-1\"")); @@ -171,7 +171,7 @@ fn file_binary_empty_omits_size_attr() { assert!(out.contains("binary=\"true\"")); assert!( !out.contains("size="), - "空 BinaryContent 不应输出 size 属性" + "empty BinaryContent should not output size attribute" ); // The default mime for .exe is application/vnd.microsoft.portable-executable or octet-stream, // so do not assert the specific value, only verify that the mime_type attribute exists @@ -190,7 +190,7 @@ fn image_renders_placeholder_only() { assert!(out.contains("")); assert!( !out.contains("BASE64DATA"), - "首版不应内联 base64,避免上下文被填爆" + "first version should not inline base64 to avoid filling the context" ); } @@ -208,7 +208,7 @@ fn referenced_notebook_renders_full_payload() { }, ); - let out = render_referenced_attachments(&attachments).expect("应当渲染"); + let out = render_referenced_attachments(&attachments).expect("should render"); assert!(out.contains("")); assert!(out.contains("reference=\"@base\"")); assert!(out.contains("uid=\"Client-1\"")); @@ -230,7 +230,7 @@ fn referenced_document_content_renders_full_payload() { }, ); - let out = render_referenced_attachments(&attachments).expect("应当渲染"); + let out = render_referenced_attachments(&attachments).expect("should render"); assert!(out.contains("( async { Ok(()) } } -/// Refresh Zap Drive before executing an operation. +/// Refresh Zaplex Drive before executing an operation. pub fn refresh_warp_drive( ctx: &AppContext, ) -> impl Future> + Send + 'static { ObjectStoreModel::as_ref(ctx) .initial_load_complete() - .with_timeout(WARP_DRIVE_SYNC_TIMEOUT) - .map_err(|_| anyhow::anyhow!("Timed out waiting for Zap Drive to sync")) + .with_timeout(ZAPLEX_DRIVE_SYNC_TIMEOUT) + .map_err(|_| anyhow::anyhow!("Timed out waiting for Zaplex Drive to sync")) } /// Format an object owner for display in the CLI. diff --git a/app/src/ai/agent_sdk/driver.rs b/app/src/ai/agent_sdk/driver.rs index 2d0ecaf6a8..c3a8cc6d3c 100644 --- a/app/src/ai/agent_sdk/driver.rs +++ b/app/src/ai/agent_sdk/driver.rs @@ -74,23 +74,23 @@ use terminal::TerminalDriverEvent; const MCP_SERVER_STARTUP_TIMEOUT: Duration = Duration::from_secs(60); const HARNESS_SAVE_INTERVAL: Duration = Duration::from_secs(30); -pub(crate) const WARP_DRIVE_SYNC_TIMEOUT: Duration = Duration::from_secs(60); +pub(crate) const ZAPLEX_DRIVE_SYNC_TIMEOUT: Duration = Duration::from_secs(60); const SETUP_FAILED_IDLE_TIMEOUT: Duration = Duration::from_secs(120); /// Maximum time to wait for an automatic error resume before propagating the error. /// If no follow-up status arrives within this window, the driver terminates with the /// original error so the CLI does not hang indefinitely. const AUTO_RESUME_TIMEOUT: Duration = Duration::from_secs(120); -/// Signals to Claude child-harness hooks that Zap already owns the background +/// Signals to Claude child-harness hooks that Zaplex already owns the background /// message-listener lifecycle, so the plugin should reuse the shared state /// files instead of spawning and cleaning up its own listener. /// /// When this variable is absent, the Claude plugin falls back to its legacy -/// self-managed listener path so older Zap builds and standalone plugin +/// self-managed listener path so older Zaplex builds and standalone plugin /// invocations keep working. pub(crate) const OZ_MESSAGE_LISTENER_MANAGED_EXTERNALLY_ENV: &str = "OZ_MESSAGE_LISTENER_MANAGED_EXTERNALLY"; /// Optional root directory for the per-session Claude message-listener state -/// that Zap and the Claude hook scripts share. +/// that Zaplex and the Claude hook scripts share. pub(crate) const OZ_MESSAGE_LISTENER_STATE_ROOT_ENV: &str = "OZ_MESSAGE_LISTENER_STATE_ROOT"; // Keep exporting the legacy `OZ_PARENT_*` names to child hooks until the // external Claude plugin has fully migrated to the canonical @@ -187,7 +187,7 @@ pub struct AgentDriverOptions { pub selected_harness: Harness, } -/// `AgentDriver` is a model for driving an ambient Zap agent to completion. +/// `AgentDriver` is a model for driving an ambient Zaplex agent to completion. /// /// Its primary responsibility is to configure a headless terminal pane and execute an AI query within it. pub struct AgentDriver { @@ -277,13 +277,13 @@ pub enum AgentDriverError { MCPMissingVariables, #[error("Agent profile \"{0}\" not found")] ProfileError(String), - #[error("Local user state is unavailable. Restart Zap and try again.")] + #[error("Local user state is unavailable. Restart Zaplex and try again.")] NotLoggedIn, #[error("Saved prompt not found for id {0}")] AIWorkflowNotFound(String), #[error("Terminal bootstrap failed")] BootstrapFailed, - #[error("Error syncing Zap Drive")] + #[error("Error syncing Zaplex Drive")] WarpDriveSyncFailed, #[error("Requested environment not found: {0}")] EnvironmentNotFound(String), @@ -351,7 +351,7 @@ impl AgentDriver { ) ); - // Zap initializes the local user on startup; reaching here means the local auth + // Zaplex initializes the local user on startup; reaching here means the local auth // singleton was not correctly initialized. if !AuthStateProvider::as_ref(ctx).get().is_logged_in() { return Err(AgentDriverError::NotLoggedIn); @@ -1009,7 +1009,7 @@ impl AgentDriver { } /// Sets up the third-party harness by subscribing to CLI session events and - /// installing the Zap plugin and platform plugin, if applicable. + /// installing the Zaplex plugin and platform plugin, if applicable. /// /// Returns a oneshot receiver that fires when the harness should exit /// (either immediately on completion or after the idle-on-complete timeout). @@ -1377,7 +1377,7 @@ impl AgentDriver { } }); - // openWarp does not sync plan to Zap Drive; the original "plan_artifact_created" + // openWarp does not sync plan to Zaplex Drive; the original "plan_artifact_created" // CLI output depended on cloud notebook_link, so we no longer subscribe to // AIDocumentModel's SaveStatusUpdated event. diff --git a/app/src/ai/agent_sdk/driver/harness/claude_code.rs b/app/src/ai/agent_sdk/driver/harness/claude_code.rs index d827c4b2e3..d09bbfb60a 100644 --- a/app/src/ai/agent_sdk/driver/harness/claude_code.rs +++ b/app/src/ai/agent_sdk/driver/harness/claude_code.rs @@ -352,7 +352,7 @@ impl HarnessRunner for ClaudeHarnessRunner { let claude_version = self.resolve_claude_version(foreground).await; let _ = (foreground, conversation_id, block_id, claude_version); - log::debug!("Skipping Claude transcript and block snapshot export in Zap"); + log::debug!("Skipping Claude transcript and block snapshot export in Zaplex"); Ok(()) } diff --git a/app/src/ai/agent_sdk/driver/harness/claude_code/parent_bridge.rs b/app/src/ai/agent_sdk/driver/harness/claude_code/parent_bridge.rs index d8c9db0034..24f744fbd7 100644 --- a/app/src/ai/agent_sdk/driver/harness/claude_code/parent_bridge.rs +++ b/app/src/ai/agent_sdk/driver/harness/claude_code/parent_bridge.rs @@ -6,7 +6,7 @@ //! - `staged/` holds newly observed message IDs from the event stream. //! - `surfaced/` holds the fully hydrated records currently exposed to Claude. //! - `pending-hook-output.json` plus `pending-hook-output.ack` coordinates the -//! handoff between Zap's driver and the Claude hook process. +//! handoff between Zaplex's driver and the Claude hook process. use std::fmt::Write as _; use std::fs; use std::io::Write; diff --git a/app/src/ai/agent_sdk/driver/harness/gemini.rs b/app/src/ai/agent_sdk/driver/harness/gemini.rs index a0fa9af8c2..b2d37699d9 100644 --- a/app/src/ai/agent_sdk/driver/harness/gemini.rs +++ b/app/src/ai/agent_sdk/driver/harness/gemini.rs @@ -186,7 +186,7 @@ impl HarnessRunner for GeminiHarnessRunner { }; let _ = (foreground, conversation_id, block_id); - log::debug!("Skipping Gemini block snapshot export in Zap"); + log::debug!("Skipping Gemini block snapshot export in Zaplex"); Ok(()) } } diff --git a/app/src/ai/agent_sdk/mod.rs b/app/src/ai/agent_sdk/mod.rs index 6f5ee79c43..9811b5b0ef 100644 --- a/app/src/ai/agent_sdk/mod.rs +++ b/app/src/ai/agent_sdk/mod.rs @@ -73,7 +73,7 @@ fn maybe_warn_team_api_key(ctx: &AppContext) { ); } -/// Run a Zap CLI command. +/// Run a Zaplex CLI command. pub fn run( ctx: &mut AppContext, command: CliCommand, @@ -178,7 +178,7 @@ fn run_agent( } AgentCommand::Profile(sub) => profiles::run(ctx, global_options, sub), AgentCommand::List(_) => Err(anyhow::anyhow!( - "Agent skill listing is disabled in Zap" + "Agent skill listing is disabled in Zaplex" )), } } @@ -308,7 +308,7 @@ impl AgentDriverRunner { // Ensure we've synced team state before starting the driver. Self::refresh_team_metadata(&foreground).await?; - // Wait for Zap Drive to sync before building the task config, since + // Wait for Zaplex Drive to sync before building the task config, since // prompt resolution (SavedPrompt -> workflow lookup) depends on it. if foreground .spawn(|_, ctx| common::refresh_warp_drive(ctx)) @@ -500,7 +500,7 @@ impl AgentDriverRunner { /// Creates local driver task state for a new agent run. /// - /// A local Zap run does not create a remote ambient-agent task record. + /// A local Zaplex run does not create a remote ambient-agent task record. /// The driver leaves `task_id` as `None`, so downstream code naturally skips the remote-task branch. async fn initialize_new_task( driver_options: &mut AgentDriverOptions, @@ -579,18 +579,18 @@ fn launch_command( let auth_state = AuthStateProvider::handle(ctx).as_ref(ctx).get(); if !auth_state.is_logged_in() { return Err(anyhow::anyhow!( - "No local user is available. Restart Zap and try again." + "No local user is available. Restart Zaplex and try again." )); } dispatch_command(ctx, command, global_options) } -/// Check if we're running within Zap (for example, if this is an invocation of the Zap CLI -/// within a Zap terminal session). +/// Check if we're running within Zaplex (for example, if this is an invocation of the Zaplex CLI +/// within a Zaplex terminal session). pub fn is_running_in_warp() -> bool { std::env::var("TERM_PROGRAM") - .map(|v| v == "WarpTerminal") + .map(|v| v == "ZaplexTerminal") .unwrap_or(false) } @@ -606,7 +606,7 @@ fn report_fatal_error(err: anyhow::Error, ctx: &mut AppContext) { if let Ok(path) = log_file_path() { let _ = write!( message, - "\n\nFor more information, check Zap logs at {}", + "\n\nFor more information, check Zaplex logs at {}", path.display() ); } diff --git a/app/src/ai/agent_sdk/provider.rs b/app/src/ai/agent_sdk/provider.rs index 23616327c2..5d765caf44 100644 --- a/app/src/ai/agent_sdk/provider.rs +++ b/app/src/ai/agent_sdk/provider.rs @@ -57,7 +57,7 @@ impl ProviderCommandRunner { return Err(anyhow::anyhow!("User is not on a team")); } - println!("Provider OAuth setup for {slug} is disabled in Zap."); + println!("Provider OAuth setup for {slug} is disabled in Zaplex."); ctx.terminate_app(TerminationMode::ForceTerminate, None); diff --git a/app/src/ai/agent_tips.rs b/app/src/ai/agent_tips.rs index cd5f366a9c..49731286f5 100644 --- a/app/src/ai/agent_tips.rs +++ b/app/src/ai/agent_tips.rs @@ -68,7 +68,7 @@ pub trait AITip: Clone { /// Kinds of agent tips for organizing and filtering. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AgentTipKind { - ZapDrive, + ZaplexDrive, General, Mcp, SlashCommands, @@ -116,8 +116,8 @@ static DEFAULT_TIPS: LazyLock> = LazyLock::new(|| { description: crate::t!("agent-tip-warp-drive"), link: Some("".to_string()), binding_name: None, - action: Some(WorkspaceAction::ZapDrive), - kind: AgentTipKind::ZapDrive, + action: Some(WorkspaceAction::ZaplexDrive), + kind: AgentTipKind::ZaplexDrive, }, AgentTip { description: crate::t!("agent-tip-redirect-running-agent"), @@ -202,7 +202,7 @@ static DEFAULT_TIPS: LazyLock> = LazyLock::new(|| { link: None, binding_name: None, action: None, - kind: AgentTipKind::ZapDrive, + kind: AgentTipKind::ZaplexDrive, }, AgentTip { description: crate::t!("agent-tip-add-rule"), @@ -275,7 +275,7 @@ static DEFAULT_TIPS: LazyLock> = LazyLock::new(|| { kind: AgentTipKind::Context, }, AgentTip { - description: crate::t!("agent-tip-warpify-ssh"), + description: crate::t!("agent-tip-zaplexify-ssh"), link: Some("".to_string()), binding_name: None, action: None, @@ -394,7 +394,7 @@ impl WorkspaceAction { pub fn display_text(&self) -> Option { match self { WorkspaceAction::OpenPalette { .. } => Some(crate::t!("agent-tip-action-open-palette")), - WorkspaceAction::ZapDrive => Some(crate::t!("agent-tip-action-warp-drive")), + WorkspaceAction::ZaplexDrive => Some(crate::t!("agent-tip-action-warp-drive")), WorkspaceAction::ToggleRightPanel => Some(crate::t!("agent-tip-action-show-diff-view")), _ => None, } diff --git a/app/src/ai/ai_document_view.rs b/app/src/ai/ai_document_view.rs index 7f78e0eacc..8b4f1e45e9 100644 --- a/app/src/ai/ai_document_view.rs +++ b/app/src/ai/ai_document_view.rs @@ -364,7 +364,7 @@ impl AIDocumentView { pane_config.refresh_pane_header_overflow_menu_items(ctx) }); - // Create sync button mouse state (for Zap Drive syncing) + // Create sync button mouse state (for Zaplex Drive syncing) let sync_button_mouse_state = MouseStateHandle::default(); // Create Update Agent button @@ -1163,7 +1163,7 @@ impl BackingView for AIDocumentView { ) -> Vec> { let mut menu_items = vec![]; - // openWarp localization: cloud menu items "Copy Link" / "Show in Zap Drive" + // openWarp localization: cloud menu items "Copy Link" / "Show in Zaplex Drive" // were only shown after successful cloud sync. Local path is unreachable, remove directly. #[cfg(feature = "local_fs")] diff --git a/app/src/ai/ambient_agents/mod.rs b/app/src/ai/ambient_agents/mod.rs index ae6c44cf4a..1e5cbc68a6 100644 --- a/app/src/ai/ambient_agents/mod.rs +++ b/app/src/ai/ambient_agents/mod.rs @@ -18,9 +18,9 @@ pub use task::{ pub const OUT_OF_CREDITS_TASK_FAILURE_MESSAGE: &str = "Agent usage limit reached. Please try again later."; pub const SERVER_OVERLOADED_TASK_FAILURE_MESSAGE: &str = - "Zap is temporarily overloaded. Please try again shortly."; + "Zaplex is temporarily overloaded. Please try again shortly."; -/// JSON payload for starting an agent run. In Zap this is only used by local UI/CLI +/// JSON payload for starting an agent run. In Zaplex this is only used by local UI/CLI /// plumbing; no remote run endpoint is contacted. #[derive(Debug, Clone, Serialize)] pub struct SpawnAgentRequest { @@ -73,7 +73,7 @@ impl FromStr for AmbientAgentTaskId { } impl AmbientAgentTaskId { - /// Zap (localization, Phase 3b-4): Generate a UUID v4 locally as task_id to avoid the local + /// Zaplex (localization, Phase 3b-4): Generate a UUID v4 locally as task_id to avoid the local /// harness depending on a remote pre-create-task endpoint when spawning child tasks. pub fn new_local() -> Self { let uuid = Uuid::new_v4(); diff --git a/app/src/ai/ambient_agents/spawn.rs b/app/src/ai/ambient_agents/spawn.rs index 55b00e47a3..75a7106eb7 100644 --- a/app/src/ai/ambient_agents/spawn.rs +++ b/app/src/ai/ambient_agents/spawn.rs @@ -69,6 +69,6 @@ pub fn spawn_task( _timeout: Option, ) -> impl Stream> { async_stream::stream! { - yield Err(anyhow::anyhow!("Agent spawning is disabled in Zap")); + yield Err(anyhow::anyhow!("Agent spawning is disabled in Zaplex")); } } diff --git a/app/src/ai/ambient_agents/task.rs b/app/src/ai/ambient_agents/task.rs index 126d512dff..b0f3601569 100644 --- a/app/src/ai/ambient_agents/task.rs +++ b/app/src/ai/ambient_agents/task.rs @@ -52,7 +52,7 @@ pub struct AgentConfigSnapshot { #[serde(default, skip_serializing_if = "Option::is_none")] pub computer_use_enabled: Option, /// Execution harness for the agent run. - /// If None, we use Zap's default ("oz"). + /// If None, we use Zaplex's default ("oz"). #[serde(default, skip_serializing_if = "Option::is_none")] pub harness: Option, /// Authentication secrets for third-party harnesses. @@ -179,7 +179,7 @@ impl AgentSource { AgentSource::Slack => "Slack", AgentSource::Cli => "CLI", AgentSource::ScheduledAgent => "Scheduled", - AgentSource::Interactive => "Zap (local agent)", + AgentSource::Interactive => "Zaplex (local agent)", AgentSource::WebApp => "Oz", AgentSource::GitHubAction => "GitHub Action", } diff --git a/app/src/ai/api_error.rs b/app/src/ai/api_error.rs index d5dd4a7cd7..c1e13b64f1 100644 --- a/app/src/ai/api_error.rs +++ b/app/src/ai/api_error.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use warp_core::errors::{AnyhowErrorExt, ErrorExt}; use warp_core::register_error; -const WARP_ERROR_CODE_HEADER: &str = "X-Zap-Error-Code"; -const WARP_ERROR_CODE_OUT_OF_CREDITS: &str = "OUT_OF_CREDITS"; +const ZAPLEX_ERROR_CODE_HEADER: &str = "X-Zap-Error-Code"; +const ZAPLEX_ERROR_CODE_OUT_OF_CREDITS: &str = "OUT_OF_CREDITS"; #[derive(thiserror::Error, Debug, Serialize, Deserialize)] #[error("{error}")] @@ -28,7 +28,7 @@ pub enum AIApiError { #[error("Request failed due to lack of AI quota.")] QuotaLimit, - #[error("Zap is currently overloaded. Please try again later.")] + #[error("Zaplex is currently overloaded. Please try again later.")] ServerOverloaded, #[error("Internal error occurred at transport layer.")] @@ -107,9 +107,9 @@ impl AIApiError { fn error_for_429(headers: &::http::HeaderMap) -> Self { if headers - .get(WARP_ERROR_CODE_HEADER) + .get(ZAPLEX_ERROR_CODE_HEADER) .and_then(|v| v.to_str().ok()) - == Some(WARP_ERROR_CODE_OUT_OF_CREDITS) + == Some(ZAPLEX_ERROR_CODE_OUT_OF_CREDITS) { AIApiError::QuotaLimit } else { diff --git a/app/src/ai/artifacts/buttons.rs b/app/src/ai/artifacts/buttons.rs index 42202df8c7..fd000304a8 100644 --- a/app/src/ai/artifacts/buttons.rs +++ b/app/src/ai/artifacts/buttons.rs @@ -177,7 +177,7 @@ fn collect_buttons( description: _, size_bytes: _, } => { - // Zap no longer has cloud artifact storage; file and screenshot artifacts + // Zaplex no longer has cloud artifact storage; file and screenshot artifacts // cannot be fetched. Keep deserialization for legacy history support, // but do not render buttons that can only fail. } diff --git a/app/src/ai/artifacts/mod.rs b/app/src/ai/artifacts/mod.rs index 90f75cc53f..f3cfb47601 100644 --- a/app/src/ai/artifacts/mod.rs +++ b/app/src/ai/artifacts/mod.rs @@ -15,7 +15,7 @@ pub enum Artifact { #[serde(rename = "PLAN")] Plan { document_uid: String, - /// None until the plan is synced to Zap Drive. + /// None until the plan is synced to Zaplex Drive. notebook_uid: Option, title: Option, }, diff --git a/app/src/ai/blocklist/action_model.rs b/app/src/ai/blocklist/action_model.rs index 33ea8deecc..0fc77cb9f6 100644 --- a/app/src/ai/blocklist/action_model.rs +++ b/app/src/ai/blocklist/action_model.rs @@ -892,7 +892,7 @@ impl BlocklistAIActionModel { /// (internally takes `is_user_initiated=true` path, bypassing `NeedsConfirmation` checks), /// instead of the default `try_to_execute_available_actions`(`is_user_initiated=false`). /// - /// Purpose: Zap BYOP path LRC tag-in scenario — user actively SetInputModeAgent to + /// Purpose: Zaplex BYOP path LRC tag-in scenario — user actively SetInputModeAgent to /// give control to agent, but cannot see RequestedCommand's Accept button in alt-screen fullscreen, /// controller detects LRC state then uses this method to bypass manual confirmation deadlock. pub(super) fn queue_actions_with_options( @@ -988,7 +988,7 @@ impl BlocklistAIActionModel { } } if auto_accept && !auto_accept_ids.is_empty() { - // Zap: LRC tag-in auto-authorization path. Bypass the default + // Zaplex: LRC tag-in auto-authorization path. Bypass the default // try_to_execute_available_actions(is_user_initiated=false), // directly call execute_action for each action just pushed (equivalent to user Accept). log::info!( diff --git a/app/src/ai/blocklist/action_model/execute/create_documents.rs b/app/src/ai/blocklist/action_model/execute/create_documents.rs index 4ed632a440..3f3fd16122 100644 --- a/app/src/ai/blocklist/action_model/execute/create_documents.rs +++ b/app/src/ai/blocklist/action_model/execute/create_documents.rs @@ -137,7 +137,7 @@ impl CreateDocumentsExecutor { model.clear_streaming_documents_for_action(&conversation_id, action_id, ctx); }); - // openWarp no longer pushes plan to Zap Drive; plan content written to local SQLite + // openWarp no longer pushes plan to Zaplex Drive; plan content written to local SQLite // ai_document_panes table via AIDocumentModel::enqueue_save. Users can view and // edit directly in right-side pane. diff --git a/app/src/ai/blocklist/action_model/execute/read_files.rs b/app/src/ai/blocklist/action_model/execute/read_files.rs index fa504d5b8b..bb8699b402 100644 --- a/app/src/ai/blocklist/action_model/execute/read_files.rs +++ b/app/src/ai/blocklist/action_model/execute/read_files.rs @@ -115,7 +115,7 @@ impl ReadFilesExecutor { // Check if this is a remote session with a connected host. let session_type = self.active_session.as_ref(ctx).session_type(ctx); let remote_client = match &session_type { - Some(SessionType::WarpifiedRemote { + Some(SessionType::ZaplexifiedRemote { host_id: Some(host_id), }) => remote_server::manager::RemoteServerManager::as_ref(ctx) .client_for_host(host_id) @@ -126,7 +126,7 @@ impl ReadFilesExecutor { // Remote session without a usable remote server client. File reading // requires either local access or a connected remote server, neither // of which is available. - if matches!(session_type, Some(SessionType::WarpifiedRemote { .. })) + if matches!(session_type, Some(SessionType::ZaplexifiedRemote { .. })) && remote_client.is_none() { return ActionExecution::Sync(AIAgentActionResultType::ReadFiles( diff --git a/app/src/ai/blocklist/action_model/execute/request_file_edits.rs b/app/src/ai/blocklist/action_model/execute/request_file_edits.rs index 9798496c97..0edd45834e 100644 --- a/app/src/ai/blocklist/action_model/execute/request_file_edits.rs +++ b/app/src/ai/blocklist/action_model/execute/request_file_edits.rs @@ -394,7 +394,7 @@ impl RequestFileEditsExecutor { // Set the session type on the diff view so save/delete/create routes // through the correct FileModel backend. let diff_session_type = match self.active_session.as_ref(ctx).session_type(ctx) { - Some(SessionType::WarpifiedRemote { + Some(SessionType::ZaplexifiedRemote { host_id: Some(host_id), }) => DiffSessionType::Remote(host_id.clone()), _ => DiffSessionType::Local, diff --git a/app/src/ai/blocklist/action_model/execute/shell_command.rs b/app/src/ai/blocklist/action_model/execute/shell_command.rs index ddd52832cf..66ac3f3bfc 100644 --- a/app/src/ai/blocklist/action_model/execute/shell_command.rs +++ b/app/src/ai/blocklist/action_model/execute/shell_command.rs @@ -317,7 +317,7 @@ impl ShellCommandExecutor { RequestCommandOutputResult::CancelledBeforeExecution, )); } - // Zap: synchronous wait-style commands (wait_until_completion=true) unconditionally + // Zaplex: synchronous wait-style commands (wait_until_completion=true) unconditionally // disable the pager. // // The model's self-reported `uses_pager` is unreliable -- small models such as @@ -879,7 +879,7 @@ fn effective_read_shell_command_delay( /// Determines whether `command` starts an interactive session that **never exits on its own**. /// Matching rules: -/// - For a command wrapped by the Zap generator wrapper, recursively evaluate the inner command. +/// - For a command wrapped by the Zaplex generator wrapper, recursively evaluate the inner command. /// - Bare `ssh ...` (via `parse_interactive_ssh_command`, which correctly excludes non-interactive /// forms such as `-T` / `-W`). /// - ssh with a path or `.exe` suffix (rewritten to bare `ssh` and then evaluated). @@ -905,7 +905,7 @@ fn command_starts_non_terminating_session(command: &str) -> bool { }) } -/// Unwraps Zap's own generator wrapper to extract the actual command to run from inside it. +/// Unwraps Zaplex's own generator wrapper to extract the actual command to run from inside it. /// /// The wrapper protocol looks like: ` '' [extra flags...]` /// where: diff --git a/app/src/ai/blocklist/action_model/execute/suggest_new_conversation.rs b/app/src/ai/blocklist/action_model/execute/suggest_new_conversation.rs index 26fe91dc9b..90dd608122 100644 --- a/app/src/ai/blocklist/action_model/execute/suggest_new_conversation.rs +++ b/app/src/ai/blocklist/action_model/execute/suggest_new_conversation.rs @@ -52,7 +52,7 @@ impl SuggestNewConversationExecutor { let message_id = message_id.clone(); let receiver = self.suggest_new_conversation_result_rx.clone().1; - // Zap: No "Start new conversation?" confirmation popup view component; + // Zaplex: No "Start new conversation?" confirmation popup view component; // send fast-fail Reject to prevent BYOP loop hanging forever on receiver. // Model sees Rejected and continues main flow unaffected. // Synchronously send Reject once to channel; receiver.recv() gets it immediately. diff --git a/app/src/ai/blocklist/action_model/execute/suggest_prompt.rs b/app/src/ai/blocklist/action_model/execute/suggest_prompt.rs index e17db37c14..e1c3738ec8 100644 --- a/app/src/ai/blocklist/action_model/execute/suggest_prompt.rs +++ b/app/src/ai/blocklist/action_model/execute/suggest_prompt.rs @@ -52,7 +52,7 @@ impl PromptSuggestionExecutor { return ActionExecution::InvalidAction; }; - // Zap: emit unconditionally (upstream gated this behind the PromptSuggestionsViaMAA cargo + // Zaplex: emit unconditionally (upstream gated this behind the PromptSuggestionsViaMAA cargo // feature, which OSS leaves off by default → the chip never shows → the oneshot hangs // forever). In the BYOP scenario, when the model actively calls suggest_prompt, the view // layer must subscribe to this event to show the chip to the user; once accepted, it calls diff --git a/app/src/ai/blocklist/action_model/execute_tests.rs b/app/src/ai/blocklist/action_model/execute_tests.rs index 4a00620484..3cc8080427 100644 --- a/app/src/ai/blocklist/action_model/execute_tests.rs +++ b/app/src/ai/blocklist/action_model/execute_tests.rs @@ -39,7 +39,7 @@ mod binary_detection { let path = write_file( &dir, "bundle", - b"#!/usr/bin/env bash\n#\n# Builds a Zap binary and bundles it up for distribution.\n\nset -e\n", + b"#!/usr/bin/env bash\n#\n# Builds a Zaplex binary and bundles it up for distribution.\n\nset -e\n", ); assert!(!block_on(should_read_as_binary(&path))); } diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs index 10e8d50999..6a077a16de 100644 --- a/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs +++ b/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs @@ -1,6 +1,6 @@ pub(super) mod chips; pub mod editor; -// Zap Wave 7-3:`environment_selector` was removed with the hosted-mode footer. +// Zaplex Wave 7-3:`environment_selector` was removed with the hosted-mode footer. mod reasoning_depth_selector; pub mod toolbar_item; @@ -56,7 +56,7 @@ use crate::{ workspaces::user_workspaces::UserWorkspaces, }; use toolbar_item::AgentToolbarItemKind; -// Zap Wave 7-3:`warp_cli::agent::Harness` import was removed with the hosted-mode footer. +// Zaplex Wave 7-3:`warp_cli::agent::Harness` import was removed with the hosted-mode footer. use std::sync::Arc; @@ -107,7 +107,7 @@ use warpui::{ #[cfg(not(target_family = "wasm"))] use warpui::r#async::Timer; -// Zap Wave 7-3:`EnvironmentSelector` / `EnvironmentSelectorEvent` re-export was removed +// Zaplex Wave 7-3:`EnvironmentSelector` / `EnvironmentSelectorEvent` re-export was removed // with the hosted-mode footer. pub(crate) use self::reasoning_depth_selector::{ ReasoningDepthSelector, ReasoningDepthSelectorEvent, @@ -124,7 +124,7 @@ use crate::view_components::ToastLink; #[cfg(not(target_family = "wasm"))] use crate::workspace::WorkspaceAction; -// Zap Wave 7-3: removed the hosted-mode footer gap constant with the old footer. +// Zaplex Wave 7-3: removed the hosted-mode footer gap constant with the old footer. /// Voice input state for the CLI agent footer. Unlike the editor-based voice /// flow (which goes through Input → EditorView), this state is self-contained @@ -186,7 +186,7 @@ pub struct AgentInputFooter { context_window_button: ViewHandle, model_selector: ViewHandle, ftu_callout_close_button: ViewHandle, - // Zap Wave 7-3:`environment_selector` field was removed with the hosted-mode footer. + // Zaplex Wave 7-3:`environment_selector` field was removed with the hosted-mode footer. reasoning_depth_selector: ViewHandle, prompt_alert: ViewHandle, ambient_agent_view_model: ModelHandle, @@ -574,7 +574,7 @@ impl AgentInputFooter { me.handle_profile_model_selector_event(event, ctx); }); - // Zap Wave 7-3: `EnvironmentSelector` initialization + subscription + ambient_agent + // Zaplex Wave 7-3: `EnvironmentSelector` initialization + subscription + ambient_agent // status rerender subscription was removed with the hosted-mode footer. let reasoning_depth_selector = ctx.add_typed_action_view(|ctx| { @@ -694,7 +694,7 @@ impl AgentInputFooter { plugin_chip_ready: false, context_window_button, model_selector: profile_model_selector_full, - // Zap Wave 7-3: `environment_selector` field init was removed with hosted-mode UI. + // Zaplex Wave 7-3: `environment_selector` field init was removed with hosted-mode UI. // Subsystem physically deleted. reasoning_depth_selector, prompt_alert, @@ -743,7 +743,7 @@ impl AgentInputFooter { .is_some_and(|s| s.as_ref(app).is_menu_open()) } - // Zap Wave 7-3: hosted-mode footer rendering was removed. + // Zaplex Wave 7-3: hosted-mode footer rendering was removed. fn all_display_chips(&self) -> impl Iterator> { self.left_display_chips @@ -1014,7 +1014,7 @@ impl AgentInputFooter { // executor so it runs inside the container and targets the // container's shell / package layout. A common use case will be // running a 3p harness (e.g. Claude Code) inside a sandbox and - // needing the Zap plugin to integrate with it. + // needing the Zaplex plugin to integrate with it. Some(ShellLaunchData::DockerSandbox { .. }) => return false, }; @@ -1335,7 +1335,7 @@ impl AgentInputFooter { } pub fn has_open_chip_menu(&self, app: &AppContext) -> bool { - // Zap Wave 7-3: `environment_selector` is_menu_open() check was removed with hosted-mode UI. + // Zaplex Wave 7-3: `environment_selector` is_menu_open() check was removed with hosted-mode UI. // Subsystem physically deleted. self.all_display_chips() .any(|chip| chip.as_ref(app).display_chip_kind().has_open_menu()) @@ -1476,7 +1476,7 @@ impl AgentInputFooter { match &self.cli_voice_input_state { CLIVoiceInputState::Stopped => { - // Zap(Phase 3c A1): deleted `AIRequestUsageModel::can_request_voice` + // Zaplex(Phase 3c A1): deleted `AIRequestUsageModel::can_request_voice` // quota gate. After localization, voice input is unrestricted by cloud quotas; all can be sent. let session_result = voice_input::VoiceInput::handle(ctx) @@ -1828,7 +1828,7 @@ impl View for AgentInputFooter { } fn render(&self, app: &warpui::AppContext) -> Box { - // Zap Wave 7-3: hosted-mode footer rendering was removed. + // Zaplex Wave 7-3: hosted-mode footer rendering was removed. // When a CLI agent session is active, render the CLI agent toolbar instead. if self.is_cli_agent_session_active(app) { return self.render_cli_mode_footer(app); @@ -1845,7 +1845,7 @@ impl View for AgentInputFooter { .with_run_spacing(4.) .with_spacing(4.); - // Zap Wave 7-3: removed environment-selector ambient-agent chip injection. + // Zaplex Wave 7-3: removed environment-selector ambient-agent chip injection. let terminal_model = self.terminal_model.lock(); let shared_status = terminal_model.shared_session_status(); @@ -2257,7 +2257,7 @@ pub enum AgentInputFooterEvent { ShowContextMenu { position: Vector2F, }, - // Zap Wave 7-3: `OpenEnvironmentManagementPane` event was removed with hosted-mode UI. + // Zaplex Wave 7-3: `OpenEnvironmentManagementPane` event was removed with hosted-mode UI. // Physically deleted. PluginInstalled(CLIAgent), #[cfg(not(target_family = "wasm"))] @@ -2357,7 +2357,7 @@ impl ActionButtonTheme for ActiveMicButtonTheme { } } -/// Green-accented theme for the "Install Zap plugin" chip. +/// Green-accented theme for the "Install Zaplex plugin" chip. struct InstallPluginButtonTheme; impl ActionButtonTheme for InstallPluginButtonTheme { @@ -2397,7 +2397,7 @@ async fn write_install_log(agent: CLIAgent, err: &PluginInstallError) -> Option< let log_path = env::temp_dir().join("warp-plugin-install.log"); let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"); let contents = format!( - "Zap plugin installation — {agent:?}\n\ + "Zaplex plugin installation — {agent:?}\n\ {now}\n\ \n\ {log}", diff --git a/app/src/ai/blocklist/agent_view/agent_message_bar.rs b/app/src/ai/blocklist/agent_view/agent_message_bar.rs index 13fe91c650..a01749c089 100644 --- a/app/src/ai/blocklist/agent_view/agent_message_bar.rs +++ b/app/src/ai/blocklist/agent_view/agent_message_bar.rs @@ -349,7 +349,7 @@ impl View for AgentMessageBar { return Empty::new().finish(); }; - // Zap (Phase 3c A1): removed the ambient credits banner UI. + // Zaplex (Phase 3c A1): removed the ambient credits banner UI. // After localization, `ambient_only_credits_remaining` is always None, so the original branch only ever took the None path. let right_element: Option> = None; diff --git a/app/src/ai/blocklist/agent_view/zero_state_block.rs b/app/src/ai/blocklist/agent_view/zero_state_block.rs index 5808f50d13..2aa5a922db 100644 --- a/app/src/ai/blocklist/agent_view/zero_state_block.rs +++ b/app/src/ai/blocklist/agent_view/zero_state_block.rs @@ -298,7 +298,7 @@ fn format_session_location(session: &Session, working_directory: Option<&str>) - let hostname = session.hostname(); match session_type { SessionType::Local => Some(display_path), - SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")), + SessionType::ZaplexifiedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")), } } @@ -422,7 +422,7 @@ pub enum AgentViewZeroStateAction { conversation_id: AIConversationId, }, /// Click "×" button on the right side of title: permanently hide zero-state keystroke hints (including message bar row). - /// User can re-enable in "Settings → Zap Agent → AI Input". + /// User can re-enable in "Settings → Zaplex Agent → AI Input". HideZeroStateHints, } @@ -463,7 +463,7 @@ fn current_working_directory_for_zero_state(terminal_model: &TerminalModel) -> O .is_some_and(|pending_session_info| { matches!( pending_session_info.session_type, - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::ZaplexifiedRemote ) }); (!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell) diff --git a/app/src/ai/blocklist/block.rs b/app/src/ai/blocklist/block.rs index 1ae450d41c..f86af04233 100644 --- a/app/src/ai/blocklist/block.rs +++ b/app/src/ai/blocklist/block.rs @@ -1018,7 +1018,7 @@ impl AIBlock { ActiveSessionEvent::Bootstrapped => {} }); - // Zap: Previously subscribed to GetRelevantFilesController Success event to refresh UI with `ctx.notify` after RAG + // Zaplex: Previously subscribed to GetRelevantFilesController Success event to refresh UI with `ctx.notify` after RAG // completion. That controller was decommissioned with outline push, subscription point deleted as well. let manage_rules_button = ctx.add_typed_action_view(|_| { @@ -1026,7 +1026,7 @@ impl AIBlock { .on_click(|ctx| ctx.dispatch_typed_action(AIBlockAction::OpenAIFactCollection)) }); - // Zap(Phase 3c A1): Removed subscription to `AIRequestUsageModelEvent::RequestBonusRefunded`. + // Zaplex(Phase 3c A1): Removed subscription to `AIRequestUsageModelEvent::RequestBonusRefunded`. // After localization this event will never emit (no `provide_negative_feedback_response` // RPC call), subscription itself has become permanent no-op dead code. `request_refunded_count` field retained // but always initial value None. @@ -5112,7 +5112,7 @@ pub enum AIBlockEvent { }, ToggleCodeDiffVisibility, - /// Open a Zap Text instance with the requested code diff. + /// Open a Zaplex Text instance with the requested code diff. OpenCodeWithDiff { view: ViewHandle, }, @@ -5326,7 +5326,7 @@ pub enum AIBlockAction { DisableRuleSuggestions, /// Copy the debug ID to clipboard CopyDebugId(String), - /// Open Zap feedback documentation + /// Open Zaplex feedback documentation OpenFeedbackDocs, /// Toggle the usage summary footer expansion state ToggleIsUsageFooterExpanded, diff --git a/app/src/ai/blocklist/block/cli.rs b/app/src/ai/blocklist/block/cli.rs index 588862d80e..d80788d6d0 100644 --- a/app/src/ai/blocklist/block/cli.rs +++ b/app/src/ai/blocklist/block/cli.rs @@ -434,21 +434,21 @@ impl CLISubagentView { c.get_task(&task_id) .and_then(|t| t.last_exchange().map(|e| e.id)) .or_else(|| { - // Zap BYOP fallback:agent 自起 LRC 时 - // `cli_controller::FinishedAction` 通过 - // `create_silent_cli_subagent_task_for_conversation` 真实创建 - // subtask 但暂未给它 append exchange(没新 query 触发 - // `update_for_new_request_input`),用 root task 的 last - // exchange 占位。后续用户 follow-up query 路由到此 task → - // `AppendedExchange` → 上面的订阅(line 365-394)会自动 - // replace model 到真实 exchange,所以占位只在窗口创建瞬间 - // 短暂存在,UX 上不可见。 + // Zaplex BYOP fallback: when agent initiates LRC, + // `cli_controller::FinishedAction` goes through + // `create_silent_cli_subagent_task_for_conversation` to create + // a real subtask but hasn't yet appended exchange (no new query triggered + // `update_for_new_request_input`), uses the root task's last + // exchange as placeholder. Subsequently, user follow-up query routed to this task → + // `AppendedExchange` → the subscription above (line 365-394) automatically + // replaces the model to the real exchange, so the placeholder only exists + // momentarily when the window is created, and is not visible in UX. let fallback = c.root_task_exchanges().last().map(|e| e.id); if fallback.is_some() { log::warn!( - "[byop] CLISubagentView::new task={task_id:?} 暂无 \ - exchange,fallback 到 root_task last_exchange;\ - 等待 AppendedExchange 触发 replace。" + "[byop] CLISubagentView::new task={task_id:?} no exchange yet, \ + fallback to root_task last_exchange; \ + waiting for AppendedExchange to trigger replacement." ); } fallback diff --git a/app/src/ai/blocklist/block/cli_controller.rs b/app/src/ai/blocklist/block/cli_controller.rs index 9c4e3332ed..910ef6f056 100644 --- a/app/src/ai/blocklist/block/cli_controller.rs +++ b/app/src/ai/blocklist/block/cli_controller.rs @@ -180,7 +180,7 @@ impl CLISubagentController { .and_then(|result| snapshot_block_id_for_action_result(&result.result)) .cloned(); - // Zap BYOP fallback: after the agent self-starts LRC, the upstream server path + // Zaplex BYOP fallback: after the agent self-starts LRC, the upstream server path // emits `BlocklistAIHistoryEvent::CreatedSubtask` which triggers // `handle_history_model_event` to upgrade the block to monitored state. BYOP has no // this server event source, so without compensation, active_block stays frozen at @@ -305,7 +305,7 @@ impl CLISubagentController { ctx.emit(CLISubagentEvent::UpdatedLastSnapshot); } - // Zap BYOP: silent_create_for_byop doesn't emit CreatedSubtask, so we manually + // Zaplex BYOP: silent_create_for_byop doesn't emit CreatedSubtask, so we manually // trigger SpawnedSubagent here to let terminal_view create the CLISubagentView overlay. // active_subagents_by_block.task_id is synced to ensure the BlockCompleted hook // cleans up correctly when LRC ends. diff --git a/app/src/ai/blocklist/block/status_bar.rs b/app/src/ai/blocklist/block/status_bar.rs index e4b6978688..382a97a15c 100644 --- a/app/src/ai/blocklist/block/status_bar.rs +++ b/app/src/ai/blocklist/block/status_bar.rs @@ -51,7 +51,7 @@ use crate::{ model::block::LONG_RUNNING_COMMAND_DURATION_MS, model_events::{ModelEvent, ModelEventDispatcher}, view::ambient_agent::{AmbientAgentViewModel, AmbientAgentViewModelEvent}, - warpify::render::LEFT_STRIPE_WIDTH, + zaplexify::render::LEFT_STRIPE_WIDTH, TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING, TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING, }, diff --git a/app/src/ai/blocklist/block/view_impl.rs b/app/src/ai/blocklist/block/view_impl.rs index 7955235fbf..7143598354 100644 --- a/app/src/ai/blocklist/block/view_impl.rs +++ b/app/src/ai/blocklist/block/view_impl.rs @@ -671,8 +671,8 @@ pub fn render_citation( ) } AIAgentCitation::WarpDocumentation { .. } => { - let icon = Icon::Zap.to_warpui_icon(theme.foreground()).finish(); - let name = String::from("Zap Docs"); + let icon = Icon::Zaplex.to_warpui_icon(theme.foreground()).finish(); + let name = String::from("Zaplex Docs"); (Some(icon), name) } AIAgentCitation::WebPage { url } => { diff --git a/app/src/ai/blocklist/block/view_impl/common.rs b/app/src/ai/blocklist/block/view_impl/common.rs index f2c6d5e0fa..e415a90e5a 100644 --- a/app/src/ai/blocklist/block/view_impl/common.rs +++ b/app/src/ai/blocklist/block/view_impl/common.rs @@ -127,7 +127,7 @@ pub const WAITING_FOR_USER_INPUT_MESSAGE: &str = "Agent waiting for instructions const IMAGE_SOURCE_LINK_LINE_INDEX: usize = 1; const ERROR_APOLOGY_TEXT: &str = "I'm sorry, I couldn't complete that request."; -const INTERNAL_WARP_ERROR: &str = "Internal Zap error."; +const INTERNAL_ZAPLEX_ERROR: &str = "Internal Zaplex error."; pub const LOAD_OUTPUT_MESSAGE: &str = "Warping..."; pub const LOAD_OUTPUT_MESSAGE_FOR_ADJUSTING: &str = "Adjusting tasks..."; @@ -545,10 +545,10 @@ pub fn render_warping_indicator_base( is_passive_code_diff, secondary_element, } = props; - // Unicode code point for the Zap glyph that is embedded in the version of Roboto we bundle + // Unicode code point for the Zaplex glyph that is embedded in the version of Roboto we bundle // into the app. This code point MUST be rendered using Roboto (the default ui font) or else the // glyph may not be rendered. - const WARP_GLYPH: &str = "\u{E500}"; + const ZAPLEX_GLYPH: &str = "\u{E500}"; let appearance = Appearance::as_ref(app); @@ -580,13 +580,13 @@ pub fn render_warping_indicator_base( let mut text_col = Flex::column(); if let Some(sub_element) = secondary_element { - // Our warping indicator text prepends the Zap glyph (and a space) to the label. + // Our warping indicator text prepends the Zaplex glyph (and a space) to the label. // If we render the tip directly underneath, it will align to the glyph instead of // the start of the actual warping text. let sub_element = if should_indent_tip_for_warp_glyph { let font_size = appearance.monospace_font_size() - 3.; let glyph_indent = Text::new_inline( - format!("{WARP_GLYPH} "), + format!("{ZAPLEX_GLYPH} "), appearance.ui_font_family(), font_size, ) @@ -1724,7 +1724,7 @@ struct VisualMarkdownBlockOptions { alignment: VisualMarkdownAlignment, lightbox_trigger: Option, /// When `Some(non_empty)`, the rendered image is wrapped in the standard - /// Zap tooltip primitive so hovering surfaces the CommonMark image title. + /// Zaplex tooltip primitive so hovering surfaces the CommonMark image title. /// Mermaid diagrams pass `None` here because CommonMark titles do not /// apply to them. tooltip: Option, @@ -2157,7 +2157,7 @@ fn render_visual_markdown_block( VisualMarkdownAlignment::Center => Align::new(content).finish(), }; - // Wrap the rendered image in the standard Zap tooltip when the source + // Wrap the rendered image in the standard Zaplex tooltip when the source // carried a CommonMark `title`. Branching on `Some(non_empty)` here means // untitled images remain un-wrapped, matching `specs/GH849/product.md` // invariant 6 (no tooltip for empty or absent titles). The tooltip's @@ -2963,15 +2963,15 @@ pub fn render_failed_output(props: FailedOutputProps, app: &AppContext) -> Box { - // Zap(Phase 3c A1): remove logic in QuotaLimit that depends on `AIRequestUsageModel` + // Zaplex(Phase 3c A1): remove logic in QuotaLimit that depends on `AIRequestUsageModel` // for refreshing rendered time. After localization, cloud quota no longer applies; keep only generic error text. - format!("{ERROR_APOLOGY_TEXT}\n\n{INTERNAL_WARP_ERROR}") + format!("{ERROR_APOLOGY_TEXT}\n\n{INTERNAL_ZAPLEX_ERROR}") } RenderableAIError::ServerOverloaded => { - "Zap is currently overloaded. Please try again later.".to_string() + "Zaplex is currently overloaded. Please try again later.".to_string() } RenderableAIError::InternalWarpError => { - format!("{ERROR_APOLOGY_TEXT}\n\n{INTERNAL_WARP_ERROR}") + format!("{ERROR_APOLOGY_TEXT}\n\n{INTERNAL_ZAPLEX_ERROR}") } RenderableAIError::Other { error_message, @@ -3356,7 +3356,7 @@ pub(crate) fn render_debug_footer( ); debug_row.add_child(copy_button_with_tooltip); - // Zap: no longer use `Expanded` — in alt-screen / long-command take-over scenarios, parent container + // Zaplex: no longer use `Expanded` — in alt-screen / long-command take-over scenarios, parent container // has infinite constraint along main axis (BYOP error block render path), and `Flex + Expanded` // directly panics with `flex contains flexible children but has an infinite constraint`. // debug_row's width is controlled by its internal Shrinkable; no need to actively fill parent. diff --git a/app/src/ai/blocklist/block/view_impl/header.rs b/app/src/ai/blocklist/block/view_impl/header.rs index 7aefb6b4c5..6585056680 100644 --- a/app/src/ai/blocklist/block/view_impl/header.rs +++ b/app/src/ai/blocklist/block/view_impl/header.rs @@ -22,7 +22,7 @@ use crate::ai::blocklist::{ }; use crate::appearance::Appearance; use crate::terminal::block_list_element::render_hoverable_block_button; -use crate::terminal::view::{TerminalAction, WARP_PROMPT_HEIGHT_LINES}; +use crate::terminal::view::{TerminalAction, ZAPLEX_PROMPT_HEIGHT_LINES}; use crate::ui_components::blended_colors; use crate::ui_components::icons::Icon; use crate::view_components::action_button::ActionButton; @@ -258,5 +258,5 @@ pub(super) fn render_overflow_menu_button( /// /// This matches the font size used for the warp prompt in completed command blocks. fn prompt_font_size(appearance: &Appearance) -> f32 { - appearance.monospace_font_size() * WARP_PROMPT_HEIGHT_LINES + appearance.monospace_font_size() * ZAPLEX_PROMPT_HEIGHT_LINES } diff --git a/app/src/ai/blocklist/code_block.rs b/app/src/ai/blocklist/code_block.rs index f39b565b8b..71834a2758 100644 --- a/app/src/ai/blocklist/code_block.rs +++ b/app/src/ai/blocklist/code_block.rs @@ -256,7 +256,7 @@ fn render_linked_code_block_internal( let open_button = render_button( appearance, Icon::LinkExternal, - "Open in Zap", + "Open in Zaplex", mouse_handles.open_button, code_clone.clone(), on_open, diff --git a/app/src/ai/blocklist/context_model.rs b/app/src/ai/blocklist/context_model.rs index a2b8f411f1..100ce553d6 100644 --- a/app/src/ai/blocklist/context_model.rs +++ b/app/src/ai/blocklist/context_model.rs @@ -672,7 +672,7 @@ impl BlocklistAIContextModel { /// If false, excludes these user-specific contexts but includes everything else. pub fn pending_context(&self, app: &AppContext, is_user_query: bool) -> Vec { let pwd = self.current_pwd(); - // Zap: used to check RepoOutlines to determine if the repo at pwd was indexed, + // Zaplex: used to check RepoOutlines to determine if the repo at pwd was indexed, // allowing optional “Use codebase semantic search” as context. Now outlines are offline; // always false. let is_pwd_indexed = false; @@ -772,7 +772,7 @@ impl BlocklistAIContextModel { } } - // Zap P0/P1: sync-read PendingFile and push as AIAgentContext::File to context. + // Zaplex P0/P1: sync-read PendingFile and push as AIAgentContext::File to context. // - text-like (UTF-8 parse success) → StringContent → rendered as XML block via // user_context.rs::render_file (BYOP) / api::input_context::File (warp-own). // - binary (PDF, audio, etc.) → BinaryContent → upgrade path via BYOP user_context Binary diff --git a/app/src/ai/blocklist/controller.rs b/app/src/ai/blocklist/controller.rs index f4dace455a..4b73c22195 100644 --- a/app/src/ai/blocklist/controller.rs +++ b/app/src/ai/blocklist/controller.rs @@ -89,7 +89,7 @@ pub struct SessionContext { session_type: Option, shell: Option, current_working_directory: Option, - /// Zap: connection info for a legacy SSH session (the user manually typed `ssh xxx@yyy` in a local PTY, + /// Zaplex: connection info for a legacy SSH session (the user manually typed `ssh xxx@yyy` in a local PTY, /// with no warp shell hook installed on the remote). `session_type` is still `Local`, /// but the PTY actually runs on the remote, so we must tell the LLM in the prompt, otherwise the model assumes the local OS. ssh_connection_info: Option, @@ -129,11 +129,11 @@ impl SessionContext { &self.current_working_directory } - /// Returns the remote host ID if this is a `WarpifiedRemote` session with + /// Returns the remote host ID if this is a `ZaplexifiedRemote` session with /// a connected `RemoteServerClient`. pub fn host_id(&self) -> Option<&warp_core::HostId> { match &self.session_type { - Some(SessionType::WarpifiedRemote { host_id }) => host_id.as_ref(), + Some(SessionType::ZaplexifiedRemote { host_id }) => host_id.as_ref(), Some(SessionType::Local) | None => None, } } @@ -141,15 +141,15 @@ impl SessionContext { /// Returns `true` if this is a remote session (regardless of whether /// the remote server client is connected). pub fn is_remote(&self) -> bool { - matches!(self.session_type, Some(SessionType::WarpifiedRemote { .. })) + matches!(self.session_type, Some(SessionType::ZaplexifiedRemote { .. })) } - /// Zap: legacy SSH connection info (host/port); only meaningful when `is_legacy_ssh()` is true. + /// Zaplex: legacy SSH connection info (host/port); only meaningful when `is_legacy_ssh()` is true. pub fn ssh_connection_info(&self) -> Option<&InteractiveSshCommand> { self.ssh_connection_info.as_ref() } - /// Zap: whether this session is a legacy SSH session (the user manually typed ssh, with no warp hook on the remote). + /// Zaplex: whether this session is a legacy SSH session (the user manually typed ssh, with no warp hook on the remote). /// For such sessions `session_type` is still `Local`, but the PTY actually runs on the remote, so /// the `host_info`/`shell` profile reflects the local client rather than the remote shell. pub fn is_legacy_ssh(&self) -> bool { @@ -359,7 +359,7 @@ pub struct BlocklistAIController { shared_session_state: shared_session::SharedSessionState, /// Ambient agent task ID attached to this controller. This is a property of the controller, and not an individual - /// conversation, because the ambient agent task driver owns the entire Zap window working on a task, and any + /// conversation, because the ambient agent task driver owns the entire Zaplex window working on a task, and any /// sessions within it. In the future, one task may span several sessions with background processes. ambient_agent_task_id: Option, @@ -2994,7 +2994,7 @@ impl BlocklistAIController { conversation_data.id, request_params.model.clone(), is_queued_prompt, - "Zap couldn't save the BYOP conversation state needed to send this \ + "Zaplex couldn't save the BYOP conversation state needed to send this \ request. Check that conversation persistence is enabled and that there \ is enough disk space, then try again." .to_owned(), @@ -3350,7 +3350,7 @@ impl BlocklistAIController { ); } } - // Zap BYOP local session compression: fetch summarization flag before stream finishes + // Zaplex BYOP local session compression: fetch summarization flag before stream finishes let summarize_overflow = response_stream.as_ref(ctx).summarization_overflow(); self.handle_response_stream_finished( @@ -3384,7 +3384,7 @@ impl BlocklistAIController { } Err(e) => { if matches!(e.as_ref(), AIApiError::QuotaLimit) { - // Zap(Phase 3c A1): remove + // Zaplex(Phase 3c A1): remove // `AIRequestUsageModel::enable_buy_credits_banner` call. // After localization, BYOP scenarios do not have "buy additional credits" business logic. } @@ -3445,7 +3445,7 @@ impl BlocklistAIController { let mut was_passive_request = false; let mut is_any_exchange_unfinished = false; let mut actions_to_queue = vec![]; - // Zap BYOP: collect newly-added message ids this round, used later to detect + // Zaplex BYOP: collect newly-added message ids this round, used later to detect // synthetic invalid_arguments error markers in the EMPTY branch. **Only look at // newly added** to avoid repeatedly hitting markers in history and causing auto-resume // loops (markers persist once persisted). @@ -3512,7 +3512,7 @@ impl BlocklistAIController { .join(", "), conversation_id, ); - // Zap: LRC tag-in auto-authorizes agent tool execution on the first round. + // Zaplex: LRC tag-in auto-authorizes agent tool execution on the first round. // // Trigger condition: when initiating this request, active_block is in // InteractionMode::User { did_user_tag_in_agent: true }. Cannot fall back to the current @@ -3535,7 +3535,7 @@ impl BlocklistAIController { ); }); } else { - // Zap BYOP: when from_args parsing fails, chat_stream falls back to emitting + // Zaplex BYOP: when from_args parsing fails, chat_stream falls back to emitting // a carrier ToolCall(tool=None) + synthetic error ToolCallResult(result=None, // server_message_data is invalid_arguments JSON). Both walk NoClientRepresentation, // not entering actions_to_queue, exchange ends silently → the model never receives @@ -3548,7 +3548,7 @@ impl BlocklistAIController { // bad args, which would cause a deadloop. // Only search newly-added messages for synthetic error markers to avoid repeating // hits on historically-persisted markers, which would cause deadloops. - // Zap BYOP: synthetic ToolCallResults that don't enter the AIAgentAction queue + // Zaplex BYOP: synthetic ToolCallResults that don't enter the AIAgentAction queue // need auto-resume, otherwise the exchange ends silently and the model gets stuck waiting. // 1. invalid_arguments — fallback for from_args parsing failure (original). // 2. _byop_intercepted — results from locally-intercepted tools like todowrite / webfetch / websearch. @@ -3645,7 +3645,7 @@ impl BlocklistAIController { stream_id, conversation_id, }); - // Zap(Phase 3c A1): remove + // Zaplex(Phase 3c A1): remove // `AIRequestUsageModel::refresh_request_usage_async` and // `maybe_refresh_ai_overages` calls. Both are essentially server-side metering sync RPCs, // which have no effect after localization. @@ -3671,7 +3671,7 @@ impl BlocklistAIController { }); } - // Zap(Phase 3c A1): remove `maybe_refresh_ai_overages` function. + // Zaplex(Phase 3c A1): remove `maybe_refresh_ai_overages` function. // The original implementation was an optimization path for “fetch the latest overage status // from the server when local limit is exhausted”. After BYOP localization, there is neither a // limit nor overage; both the function body and its only call site must be removed together. @@ -3685,7 +3685,7 @@ impl BlocklistAIController { summarize_overflow: Option, ctx: &mut ModelContext, ) { - // Zap BYOP local session compression: aggregate before token_usage moves into the closure below, + // Zaplex BYOP local session compression: aggregate before token_usage moves into the closure below, // used for auto overflow checks (in the Done branch below). let aggregate_token_count: usize = finished_event .token_usage @@ -3712,7 +3712,7 @@ impl BlocklistAIController { let history_model = BlocklistAIHistoryModel::handle(ctx); match finished_event.reason { Some(warp_multi_agent_api::response_event::stream_finished::Reason::Done(_)) | None => { - // Zap BYOP local session compression - write back summary + // Zaplex BYOP local session compression - write back summary if let Some(overflow) = summarize_overflow { let compaction_cfg = crate::ai::byop_compaction::CompactionConfig::from_settings(ctx); history_model.update(ctx, |history_model, _ctx| { @@ -3734,7 +3734,7 @@ impl BlocklistAIController { ); }); - // Zap BYOP local session compression - auto overflow trigger (aligned with opencode `processor.ts:395-403`) + // Zaplex BYOP local session compression - auto overflow trigger (aligned with opencode `processor.ts:395-403`) // Only check when this stream is not itself a summary, to prevent recursion. if summarize_overflow.is_none() { let aggregate_count = aggregate_token_count; @@ -4022,9 +4022,9 @@ fn get_running_command(terminal_model: &TerminalModel) -> Option }) } -/// Zap BYOP specific: extract RunningCommand in LRC tag-in / agent-monitored scenarios. +/// Zaplex BYOP specific: extract RunningCommand in LRC tag-in / agent-monitored scenarios. /// -/// Upstream `get_running_command` returns None when `is_agent_monitoring()` because in Zap's own +/// Upstream `get_running_command` returns None when `is_agent_monitoring()` because in Zaplex's own /// LRC path, after spawning cli subagent, the server persists this state, so subsequent client /// rounds don't need to resend running_command. However, BYOP directly connects to the model with /// no server-side persistence, so **every round must re-provide the current PTY grid contents** diff --git a/app/src/ai/blocklist/controller/response_stream.rs b/app/src/ai/blocklist/controller/response_stream.rs index 6f7b92656a..3a1b0cf7f5 100644 --- a/app/src/ai/blocklist/controller/response_stream.rs +++ b/app/src/ai/blocklist/controller/response_stream.rs @@ -103,7 +103,7 @@ fn byop_dispatch_info( // Title generation: only trigger in first round (avoid repeating title per round). // Parse active title_model: may be base_model itself or another BYOP model independently selected by user. - // If either model is not BYOP-encoded (e.g., fallback to non-BYOP default), skip it — Zap main path is all BYOP; + // If either model is not BYOP-encoded (e.g., fallback to non-BYOP default), skip it — Zaplex main path is all BYOP; // when actually falling back to base, base itself is BYOP. let llm_prefs = crate::ai::llms::LLMPreferences::as_ref(ctx); let title_gen = if needs_create_task { @@ -333,7 +333,7 @@ impl ResponseStream { self.params.lrc_should_spawn_subagent } - /// Zap BYOP local session compression: returns whether this stream is running SummarizeConversation + /// Zaplex BYOP local session compression: returns whether this stream is running SummarizeConversation /// and the overflow flag. Controller calls commit_summarization in the Done branch of /// handle_response_stream_finished to persist the summary to conversation.compaction_state. pub fn summarization_overflow(&self) -> Option { @@ -650,10 +650,10 @@ impl Entity for ResponseStream { async fn byop_required_response_stream( cancellation_rx: oneshot::Receiver<()>, ) -> Result { - log::debug!("No BYOP provider selected for Zap agent request"); + log::debug!("No BYOP provider selected for Zaplex agent request"); let error_stream = futures::stream::once(async { Err(Arc::new(AIApiError::Other(anyhow!( - "Zap requires a configured BYOP provider in Settings" + "Zaplex requires a configured BYOP provider in Settings" )))) }) .take_until(cancellation_rx); diff --git a/app/src/ai/blocklist/controller/slash_command.rs b/app/src/ai/blocklist/controller/slash_command.rs index a625415971..6464d63298 100644 --- a/app/src/ai/blocklist/controller/slash_command.rs +++ b/app/src/ai/blocklist/controller/slash_command.rs @@ -33,7 +33,7 @@ pub enum SlashCommandRequest { }, Summarize { prompt: Option, - /// Zap BYOP local conversation compaction: whether this summary was triggered automatically by token overflow. + /// Zaplex BYOP local conversation compaction: whether this summary was triggered automatically by token overflow. /// The chat_stream::SummarizeConversation branch uses this to decide the follow-up wording /// (the overflow path appends a "previous request exceeded ..." explanation). /// false for manual /compact and /compact-and triggers; true for the auto-trigger path. diff --git a/app/src/ai/blocklist/history_model.rs b/app/src/ai/blocklist/history_model.rs index 171b465450..510f5f4d20 100644 --- a/app/src/ai/blocklist/history_model.rs +++ b/app/src/ai/blocklist/history_model.rs @@ -757,7 +757,7 @@ impl BlocklistAIHistoryModel { Ok(conversation.create_optimistic_cli_subagent_task(&block_id, terminal_view_id, ctx)) } - /// Zap BYOP-specific: see `Conversation::create_optimistic_cli_subagent_task_silent` docs. + /// Zaplex BYOP-specific: see `Conversation::create_optimistic_cli_subagent_task_silent` docs. /// Creates real subagent task but doesn't emit `CreatedSubtask`, avoiding triggering `CLISubagentView` /// panic when task has no exchange. pub fn create_silent_cli_subagent_task_for_conversation( diff --git a/app/src/ai/blocklist/inline_action/code_diff_view.rs b/app/src/ai/blocklist/inline_action/code_diff_view.rs index 6f2bb1e211..40555ba76e 100644 --- a/app/src/ai/blocklist/inline_action/code_diff_view.rs +++ b/app/src/ai/blocklist/inline_action/code_diff_view.rs @@ -1640,7 +1640,7 @@ impl CodeDiffView { // Renders the 'open config' button only when every MCP config file in this diff // belongs to the same provider. Mixed-provider diffs (e.g. editing both a Claude - // config and a Zap config at once) show no badge to avoid misleading attribution. + // config and a Zaplex config at once) show no badge to avoid misleading attribution. let mcp_configs: Vec<_> = file_paths .iter() .filter_map(|path| { diff --git a/app/src/ai/blocklist/inline_action/requested_action.rs b/app/src/ai/blocklist/inline_action/requested_action.rs index e50afd60b6..d52b6f9d31 100644 --- a/app/src/ai/blocklist/inline_action/requested_action.rs +++ b/app/src/ai/blocklist/inline_action/requested_action.rs @@ -1,6 +1,6 @@ //! This module contains rendering functions for various requested inline actions that have not yet //! been transformed into a [`View`] component. This currently encompasses UI for file retrieval, -//! environmental variable collection, and SSH Warpification, to name a few. +//! environmental variable collection, and SSH Zaplexification, to name a few. //! //! There's quite a bit of duplication between function-based inline actions and view-based inline //! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig. diff --git a/app/src/ai/blocklist/inline_action/requested_command.rs b/app/src/ai/blocklist/inline_action/requested_command.rs index a924dfcf8b..48cd471b05 100644 --- a/app/src/ai/blocklist/inline_action/requested_command.rs +++ b/app/src/ai/blocklist/inline_action/requested_command.rs @@ -242,7 +242,7 @@ pub struct RequestedCommandView { header_mouse_state: MouseStateHandle, is_editing: bool, - // A requested command can either be copied directly off of one citation (such as a Zap Drive + // A requested command can either be copied directly off of one citation (such as a Zaplex Drive // object), derived from one or more citations, or be unrelated to any citations. // A given citation should only appear once per block. copied_from_citation: Option, diff --git a/app/src/ai/blocklist/inline_action/requested_command_attribution.rs b/app/src/ai/blocklist/inline_action/requested_command_attribution.rs index ac21e0f3ce..c2f18a7549 100644 --- a/app/src/ai/blocklist/inline_action/requested_command_attribution.rs +++ b/app/src/ai/blocklist/inline_action/requested_command_attribution.rs @@ -1,5 +1,5 @@ //! Module to attribute AI-generated requested commands -//! to known documents (e.g. Zap Drive objects). +//! to known documents (e.g. Zaplex Drive objects). use warpui::AppContext; use warpui::SingletonEntity; @@ -32,7 +32,7 @@ pub(crate) fn is_command_copied_from_document( } /// Returns true iff the `command` is directly copied from the -/// Zap Drive object identified by `object_uid`. +/// Zaplex Drive object identified by `object_uid`. fn is_command_copied_from_warp_drive_object( command: &str, object_uid: &str, diff --git a/app/src/ai/blocklist/passive_suggestions/legacy.rs b/app/src/ai/blocklist/passive_suggestions/legacy.rs index cab7466938..677794c9e5 100644 --- a/app/src/ai/blocklist/passive_suggestions/legacy.rs +++ b/app/src/ai/blocklist/passive_suggestions/legacy.rs @@ -256,7 +256,7 @@ impl PassiveSuggestionsModel { }; // BYOP path: replace the ServerApi call with a BYOP one-shot completion. - // Zap has stripped out the Zap Inc cloud; with no BYOP config this is a silent no-op. + // Zaplex has stripped out the Zaplex Inc cloud; with no BYOP config this is a silent no-op. let Some(rendered) = build_prompt_suggestions_byop_request( &block_completed, execution_context, @@ -390,7 +390,7 @@ impl PassiveSuggestionsModel { .active_session .as_ref(ctx) .session_type(ctx) - .map(|session_type| matches!(session_type, SessionType::WarpifiedRemote { .. })) + .map(|session_type| matches!(session_type, SessionType::ZaplexifiedRemote { .. })) .unwrap_or(true); if !can_read_file || should_skip_for_remote { let reason = if !can_read_file { diff --git a/app/src/ai/blocklist/passive_suggestions/maa.rs b/app/src/ai/blocklist/passive_suggestions/maa.rs index 5a867e06dd..b813991cfc 100644 --- a/app/src/ai/blocklist/passive_suggestions/maa.rs +++ b/app/src/ai/blocklist/passive_suggestions/maa.rs @@ -167,7 +167,7 @@ impl PassiveSuggestionsModel { }; log::debug!( - "[passive-suggestions] skipped MAA request because the multi-agent endpoint is disabled in Zap" + "[passive-suggestions] skipped MAA request because the multi-agent endpoint is disabled in Zaplex" ); let (cancellation_tx, cancellation_rx) = futures::channel::oneshot::channel(); @@ -388,7 +388,7 @@ impl PassiveSuggestionsModel { ) { self.abort_pending_requests(ctx); - // Zap: temporarily disable cloud requests for passive suggestions triggered by AgentResponseCompleted, + // Zaplex: temporarily disable cloud requests for passive suggestions triggered by AgentResponseCompleted, // to avoid sending a failed HTTP request to ${server_root_url}/ai/multi-agent after each Agent response // (no cloud auth + ShowSuggestions proto action cannot be synthesized by BYOP). // TODO(byop-suggestions): if later needing to replicate "Suggested Workflow / Rule" chip in BYOP, diff --git a/app/src/ai/blocklist/permissions.rs b/app/src/ai/blocklist/permissions.rs index f0592ea147..59fd266546 100644 --- a/app/src/ai/blocklist/permissions.rs +++ b/app/src/ai/blocklist/permissions.rs @@ -640,7 +640,7 @@ impl BlocklistAIPermissions { self.can_read_files(Some(conversation_id), paths, terminal_view_id, ctx) } - /// Returns whether or not Zap can auto-read the given files (e.g. for codebase indexing). + /// Returns whether or not Zaplex can auto-read the given files (e.g. for codebase indexing). pub fn can_read_files( &self, conversation_id: Option<&AIConversationId>, diff --git a/app/src/ai/blocklist/prompt/prompt_alert.rs b/app/src/ai/blocklist/prompt/prompt_alert.rs index 90c914484a..ad20175c72 100644 --- a/app/src/ai/blocklist/prompt/prompt_alert.rs +++ b/app/src/ai/blocklist/prompt/prompt_alert.rs @@ -90,14 +90,14 @@ impl PromptAlertView { return PromptAlertState::NoAlert; } - // Zap: BYOP / local providers handle connection state themselves, including localhost + // Zaplex: BYOP / local providers handle connection state themselves, including localhost // providers like Ollama. The global offline state only blocks built-in cloud usage. if !NetworkStatus::as_ref(app).is_online() { return PromptAlertState::NoConnection; } let request_usage_model = AIRequestUsageModel::as_ref(app); - // Zap (Phase 3c A1): after localization, `has_requests_remaining` is always true, so of the + // Zaplex (Phase 3c A1): after localization, `has_requests_remaining` is always true, so of the // original if/else split only the SoftGate branch is reachable; we just take the true branch. let auth_state = AuthStateProvider::as_ref(app).get(); diff --git a/app/src/ai/blocklist/suggested_agent_mode_workflow_modal.rs b/app/src/ai/blocklist/suggested_agent_mode_workflow_modal.rs index 1aaa034456..870602740b 100644 --- a/app/src/ai/blocklist/suggested_agent_mode_workflow_modal.rs +++ b/app/src/ai/blocklist/suggested_agent_mode_workflow_modal.rs @@ -233,7 +233,7 @@ impl View for SuggestedAgentModeWorkflowModal { if let Some(modal) = &self.modal { ChildView::new(modal).finish() } else { - // Zap: this view is always attached to the workspace tree; modal=None by default is normal + // Zaplex: this view is always attached to the workspace tree; modal=None by default is normal // (cloud ShowSuggestions is disabled, chip won't appear, modal won't be populated). // Don't log a warning to avoid polluting the log every frame. Empty::new().finish() diff --git a/app/src/ai/blocklist/telemetry_banner.rs b/app/src/ai/blocklist/telemetry_banner.rs index 1a4cddd9a1..3ed95b0b10 100644 --- a/app/src/ai/blocklist/telemetry_banner.rs +++ b/app/src/ai/blocklist/telemetry_banner.rs @@ -1,6 +1,6 @@ use warpui::AppContext; -/// Zap has no telemetry sender and must not collect user-generated AI data. +/// Zaplex has no telemetry sender and must not collect user-generated AI data. pub fn should_collect_ai_ugc_telemetry(_app: &AppContext, _is_telemetry_enabled: bool) -> bool { false } diff --git a/app/src/ai/byop_readiness/mod.rs b/app/src/ai/byop_readiness/mod.rs index b1b068d307..c36445cd58 100644 --- a/app/src/ai/byop_readiness/mod.rs +++ b/app/src/ai/byop_readiness/mod.rs @@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet}; pub const REPAIR_STATE_VERSION: u32 = 1; pub const BLOCKED_BYOP_REQUEST_MESSAGE: &str = - "Can't continue this conversation: an earlier tool result is missing or corrupted in this conversation's history, so Zap can't safely send the request to your provider. Start a new conversation or fork from an earlier point to continue."; + "Can't continue this conversation: an earlier tool result is missing or corrupted in this conversation's history, so Zaplex can't safely send the request to your provider. Start a new conversation or fork from an earlier point to continue."; pub const PENDING_BYOP_TOOL_RESULTS_MESSAGE: &str = "Waiting for a running tool to finish before sending your next request."; diff --git a/app/src/ai/execution_profiles/mod.rs b/app/src/ai/execution_profiles/mod.rs index b1cacd3d1e..5f115cdd05 100644 --- a/app/src/ai/execution_profiles/mod.rs +++ b/app/src/ai/execution_profiles/mod.rs @@ -209,7 +209,7 @@ pub struct AIExecutionProfile { pub context_window_limit: Option, - /// Whether plans created by the agent should be automatically synced to Zap Drive + /// Whether plans created by the agent should be automatically synced to Zaplex Drive pub autosync_plans_to_warp_drive: bool, /// Whether the agent may use web search when helpful for completing tasks diff --git a/app/src/ai/execution_profiles/profiles.rs b/app/src/ai/execution_profiles/profiles.rs index 6881c2e443..3040826df6 100644 --- a/app/src/ai/execution_profiles/profiles.rs +++ b/app/src/ai/execution_profiles/profiles.rs @@ -63,7 +63,7 @@ impl AIExecutionProfileInfo { &self.id } - /// The Zap Drive sync ID of this profile, if it has been synced. + /// The Zaplex Drive sync ID of this profile, if it has been synced. #[cfg_attr(target_family = "wasm", allow(dead_code))] pub fn sync_id(&self) -> Option { self.sync_id diff --git a/app/src/ai/facts/view/mod.rs b/app/src/ai/facts/view/mod.rs index 2929a7effb..da6766b342 100644 --- a/app/src/ai/facts/view/mod.rs +++ b/app/src/ai/facts/view/mod.rs @@ -1,4 +1,4 @@ -// Zap (Localization, Phase 2d-1): This file originally handled "offline banner / sync state predicates"; +// Zaplex (Localization, Phase 2d-1): This file originally handled "offline banner / sync state predicates"; // after the cloud backend (SyncQueue / NetworkStatus online gating) was completely shut down, // all this code lost meaning and was removed entirely with imports cleaned up. The Pane container view // itself is retained to manage switching between Rules / RuleEditor pages. @@ -278,7 +278,7 @@ impl BackingView for AIFactView { } } -// Zap (Localization, Phase 2d-1): Original `is_online` / `is_delete_allowed` / `is_edit_allowed` / +// Zaplex (Localization, Phase 2d-1): Original `is_online` / `is_delete_allowed` / `is_edit_allowed` / // `is_syncing` predicates depended on cloud SyncQueue and network online status. After localization, // rules are always editable, always deletable, never in "syncing" state; predicates are removed entirely, // call sites also cleaned up accordingly. diff --git a/app/src/ai/facts/view/rule.rs b/app/src/ai/facts/view/rule.rs index 98f9524515..e22d421c02 100644 --- a/app/src/ai/facts/view/rule.rs +++ b/app/src/ai/facts/view/rule.rs @@ -146,7 +146,7 @@ pub struct RuleView { impl RuleView { pub fn new(ctx: &mut ViewContext) -> Self { - // Zap(localization, Phase 2d-1): Original UpdateManager subscription was for receiving cloud create/update ack + // Zaplex(localization, Phase 2d-1): Original UpdateManager subscription was for receiving cloud create/update ack // events and network-status-driven panel redraws. After localization, ObjectStoreEvent covers local write UI refresh needs // (2c-2/2c-3 emitted in update_object/create_object), so UpdateManager and NetworkStatus subscriptions are dead code; removed together. let object_store_model = ObjectStoreModel::handle(ctx); @@ -603,7 +603,7 @@ impl RuleView { .finish() } - // Zap(localization, Phase 2d-1): Original `render_sync_status_icon` depended on `SyncQueue::is_dequeueing()` + // Zaplex(localization, Phase 2d-1): Original `render_sync_status_icon` depended on `SyncQueue::is_dequeueing()` // and `is_syncing` predicates. After localization, "syncing" state never occurs; entire feature removed. fn render_project_based_row( @@ -731,7 +731,7 @@ impl RuleView { .finish() }); - // Zap(localization, Phase 2d-1): Original `is_edit_allowed` depended on two dimensions: network online + server_id. + // Zaplex(localization, Phase 2d-1): Original `is_edit_allowed` depended on two dimensions: network online + server_id. // After localization, rules are always editable; click action is unconditionally attached. hoverable .with_cursor(Cursor::PointingHand) diff --git a/app/src/ai/facts/view/rule_editor.rs b/app/src/ai/facts/view/rule_editor.rs index 62ce9ff693..61a61bfda8 100644 --- a/app/src/ai/facts/view/rule_editor.rs +++ b/app/src/ai/facts/view/rule_editor.rs @@ -73,7 +73,7 @@ pub struct RuleEditorView { impl RuleEditorView { pub fn new(ctx: &mut ViewContext) -> Self { - // Zap (localization, Phase 2d-1): original NetworkStatus subscription was for redrawing on online/offline state change + // Zaplex (localization, Phase 2d-1): original NetworkStatus subscription was for redrawing on online/offline state change // (coupled with `is_delete_allowed` predicate); after localization the predicate is always true, subscription is dead code, removed. let appearance = Appearance::as_ref(ctx); let font_family = appearance.ui_font_family(); @@ -377,7 +377,7 @@ impl View for RuleEditorView { .with_child(self.render_header(appearance)) .with_child(self.render_form(appearance)); - // Zap (localization, Phase 2d-1): original `is_delete_allowed` depended on network online + server_id; + // Zaplex (localization, Phase 2d-1): original `is_delete_allowed` depended on network online + server_id; // after localization, deletion is allowed whenever editing an existing rule, predicate is removed. if self.ai_fact.is_some() { col.add_child(ChildView::new(&self.delete_button).finish()); diff --git a/app/src/ai/harness_display.rs b/app/src/ai/harness_display.rs index ec062f6fa4..b99270fa93 100644 --- a/app/src/ai/harness_display.rs +++ b/app/src/ai/harness_display.rs @@ -15,7 +15,7 @@ use crate::ui_components::icons::Icon; /// User-visible display name for a [`Harness`]. pub fn display_name(harness: Harness) -> &'static str { match harness { - Harness::Oz => "Zap Agent", + Harness::Oz => "Zaplex Agent", Harness::Claude => "Claude Code", Harness::OpenCode => "OpenCode", Harness::Gemini => "Gemini CLI", @@ -26,7 +26,7 @@ pub fn display_name(harness: Harness) -> &'static str { /// Leading icon for a [`Harness`]. pub fn icon_for(harness: Harness) -> Icon { match harness { - Harness::Oz => Icon::Zap, + Harness::Oz => Icon::Zaplex, Harness::Claude => Icon::ClaudeLogo, Harness::OpenCode => Icon::OpenCodeLogo, Harness::Gemini => Icon::GeminiLogo, diff --git a/app/src/ai/llms.rs b/app/src/ai/llms.rs index 4e0ea660cb..5785b4f5a3 100644 --- a/app/src/ai/llms.rs +++ b/app/src/ai/llms.rs @@ -303,7 +303,7 @@ impl LLMInfo { /// The set of LLMs available for a feature. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AvailableLLMs { - /// The Zap "default" LLM. + /// The Zaplex "default" LLM. default_id: LLMId, choices: Vec, diff --git a/app/src/ai/loading/shimmering_warp_loading_text.rs b/app/src/ai/loading/shimmering_warp_loading_text.rs index 3369176423..7748074c22 100644 --- a/app/src/ai/loading/shimmering_warp_loading_text.rs +++ b/app/src/ai/loading/shimmering_warp_loading_text.rs @@ -1,4 +1,4 @@ -//! Shimmering Zap loading text - renders Zap logo with shimmering text for loading states. +//! Shimmering Zaplex loading text - renders Zaplex logo with shimmering text for loading states. use warp_core::ui::appearance::Appearance; use warpui::elements::shimmering_text::{ @@ -7,10 +7,10 @@ use warpui::elements::shimmering_text::{ use warpui::elements::Element; use warpui::{AppContext, SingletonEntity}; -/// Zap icon glyph character -const WARP_GLYPH: &str = "\u{E500}"; +/// Zaplex icon glyph character +const ZAPLEX_GLYPH: &str = "\u{E500}"; -/// Creates a shimmering text element with the Zap glyph. +/// Creates a shimmering text element with the Zaplex glyph. pub fn shimmering_warp_loading_text( text: impl Into, font_size: f32, @@ -29,7 +29,7 @@ pub fn shimmering_warp_loading_text( // Create a single shimmering element with glyph and text ShimmeringTextElement::new( - format!("{} {}", WARP_GLYPH, text.into()), + format!("{} {}", ZAPLEX_GLYPH, text.into()), appearance.ui_font_family(), font_size, base_color, diff --git a/app/src/ai/mcp/file_based_manager.rs b/app/src/ai/mcp/file_based_manager.rs index 06fd526b0e..80012f9f2f 100644 --- a/app/src/ai/mcp/file_based_manager.rs +++ b/app/src/ai/mcp/file_based_manager.rs @@ -232,7 +232,7 @@ impl FileBasedMCPManager { /// config location. /// /// "Global" means the installation was detected outside of a user repository: - /// - For `MCPProvider::Zap`: the logical root for `~/.warp*/.mcp.json`. + /// - For `MCPProvider::Zaplex`: the logical root for `~/.warp*/.mcp.json`. /// - For any other provider: the user's home directory (e.g. `~/.claude.json`). /// /// Project-scoped installations (those detected inside a repo) are not considered @@ -248,7 +248,7 @@ impl FileBasedMCPManager { return false; } match provider { - MCPProvider::Zap => Self::is_global_warp_root(root_path), + MCPProvider::Zaplex => Self::is_global_warp_root(root_path), MCPProvider::Claude | MCPProvider::Codex | MCPProvider::Agents => { home_dir.as_ref().is_some_and(|home| root_path == home) } @@ -258,14 +258,14 @@ impl FileBasedMCPManager { } /// Returns `true` if the server identified by `hash` is referenced from the global - /// Zap config (`~/.warp/.mcp.json`). Global Zap servers always auto-spawn. + /// Zaplex config (`~/.warp/.mcp.json`). Global Zaplex servers always auto-spawn. fn is_global_warp_server(&self, hash: u64) -> bool { self.file_based_servers_by_root .iter() .any(|(root_path, provider_map)| { Self::is_global_warp_root(root_path) && provider_map - .get(&MCPProvider::Zap) + .get(&MCPProvider::Zaplex) .is_some_and(|hashes| hashes.contains(&hash)) }) } @@ -285,8 +285,8 @@ impl FileBasedMCPManager { let mcp_enabled = AISettings::as_ref(ctx).is_file_based_mcp_enabled(ctx); // Partition servers into three buckets based on scope: - // - Global Zap: always auto-spawn. - // - Global non-Zap: auto-spawn iff the toggle is on. + // - Global Zaplex: always auto-spawn. + // - Global non-Zaplex: auto-spawn iff the toggle is on. // - Project-scoped (any provider): never auto-spawn; require explicit opt-in // via the "Detected from {provider}" section of the MCP settings. let mut to_spawn = Vec::new(); @@ -310,7 +310,7 @@ impl FileBasedMCPManager { fn handle_file_based_mcp_enabled_change(&mut self, ctx: &mut ModelContext) { // Only global third-party servers are affected by the toggle: - // - Global Zap servers always spawn regardless of the toggle. + // - Global Zaplex servers always spawn regardless of the toggle. // - Project-scoped servers (any provider) are never auto-spawned and their // running state is managed per-card via the MCP settings UI; toggling the // setting must not spawn or despawn them. @@ -331,7 +331,7 @@ impl FileBasedMCPManager { .collect_vec(), }); } else { - // Toggle on: spawn global third-party servers (global Zap servers are + // Toggle on: spawn global third-party servers (global Zaplex servers are // already running; project-scoped servers are unaffected). ctx.emit(FileBasedMCPManagerEvent::SpawnServers { installations: global_third_party_servers, @@ -386,7 +386,7 @@ impl FileBasedMCPManager { /// when its config does not specify `working_directory`. /// /// The spawn root is the directory the config was discovered in, with one - /// exception: global Zap installs are discovered in `~/.warp*/`, which + /// exception: global Zaplex installs are discovered in `~/.warp*/`, which /// isn't a useful cwd for spawned processes, so they are remapped to the /// home directory instead. /// - Project-scoped installations: the repo root. @@ -406,9 +406,9 @@ impl FileBasedMCPManager { .sorted() .next()?; - // Global Zap installs live under `~/.warp*/`, which is internal Zap + // Global Zaplex installs live under `~/.warp*/`, which is internal Zaplex // state rather than a meaningful working directory. Map them to the - // home dir so all global installs (Zap and third-party) share a + // home dir so all global installs (Zaplex and third-party) share a // consistent cwd. if self.is_global_warp_server(hash) { return dirs::home_dir().or(Some(discovery_root)); diff --git a/app/src/ai/mcp/file_based_manager_tests.rs b/app/src/ai/mcp/file_based_manager_tests.rs index 856817cbb8..a6907c4ef6 100644 --- a/app/src/ai/mcp/file_based_manager_tests.rs +++ b/app/src/ai/mcp/file_based_manager_tests.rs @@ -269,12 +269,12 @@ fn test_global_warp_server_from_managed_home_root_always_spawns() { let manager = setup_app(&mut app); let events = subscribe_events(&mut app, &manager); - // Toggle is off by default; the watcher-produced Zap root should still - // be classified as the global Zap config and auto-spawn. + // Toggle is off by default; the watcher-produced Zaplex root should still + // be classified as the global Zaplex config and auto-spawn. manager.update(&mut app, |m, ctx| { m.apply_parsed_servers( warp_mcp_config_path.root_path.clone(), - MCPProvider::Zap, + MCPProvider::Zaplex, parsed, ctx, ); @@ -284,24 +284,24 @@ fn test_global_warp_server_from_managed_home_root_always_spawns() { assert_eq!( e.spawned_uuids.len(), 1, - "Managed Zap MCP config should auto-spawn regardless of toggle" + "Managed Zaplex MCP config should auto-spawn regardless of toggle" ); }); - // Flipping the toggle must not despawn the global Zap server. + // Flipping the toggle must not despawn the global Zaplex server. set_file_based_mcp_enabled(&mut app, true); set_file_based_mcp_enabled(&mut app, false); events.update(&mut app, |e, _| { assert!( e.despawned_uuids.is_empty(), - "Managed Zap MCP config should never be despawned by toggle changes, got: {:?}", + "Managed Zaplex MCP config should never be despawned by toggle changes, got: {:?}", e.despawned_uuids ); }); }); } -/// A globally-scoped non-Zap installation only auto-spawns when the toggle is on. +/// A globally-scoped non-Zaplex installation only auto-spawns when the toggle is on. #[test] fn test_global_non_warp_server_respects_toggle() { let _flag_guard = FeatureFlag::FileBasedMcp.override_enabled(true); @@ -323,7 +323,7 @@ fn test_global_non_warp_server_respects_toggle() { events.update(&mut app, |e, _| { assert!( e.spawned_uuids.is_empty(), - "Global non-Zap server must not auto-spawn while toggle is off, got: {:?}", + "Global non-Zaplex server must not auto-spawn while toggle is off, got: {:?}", e.spawned_uuids ); }); @@ -334,29 +334,29 @@ fn test_global_non_warp_server_respects_toggle() { servers[0].uuid() }); - // Toggle on: the global non-Zap server should be spawned. + // Toggle on: the global non-Zaplex server should be spawned. set_file_based_mcp_enabled(&mut app, true); events.update(&mut app, |e, _| { assert_eq!( e.spawned_uuids, vec![installation_uuid], - "Global non-Zap server should spawn when toggle flips on" + "Global non-Zaplex server should spawn when toggle flips on" ); }); - // Toggle off: the global non-Zap server should be despawned. + // Toggle off: the global non-Zaplex server should be despawned. set_file_based_mcp_enabled(&mut app, false); events.update(&mut app, |e, _| { assert_eq!( e.despawned_uuids, vec![installation_uuid], - "Global non-Zap server should despawn when toggle flips off" + "Global non-Zaplex server should despawn when toggle flips off" ); }); }); } -/// Project-scoped installations (both Zap and third-party) never auto-spawn on +/// Project-scoped installations (both Zaplex and third-party) never auto-spawn on /// detection, and the toggle must not spawn or despawn them either. #[test] fn test_project_scoped_servers_never_auto_spawn() { @@ -372,7 +372,7 @@ fn test_project_scoped_servers_never_auto_spawn() { manager.update(&mut app, |m, ctx| { m.apply_parsed_servers(repo_path.clone(), MCPProvider::Claude, claude_parsed, ctx); - m.apply_parsed_servers(repo_path.clone(), MCPProvider::Zap, warp_parsed, ctx); + m.apply_parsed_servers(repo_path.clone(), MCPProvider::Zaplex, warp_parsed, ctx); }); // Neither detection should emit a spawn event. @@ -413,7 +413,7 @@ fn test_project_scoped_servers_never_auto_spawn() { } /// An installation referenced from both a global location and a project location -/// is considered global (and thus gated only by the toggle for non-Zap providers). +/// is considered global (and thus gated only by the toggle for non-Zaplex providers). #[test] fn test_server_referenced_from_both_global_and_project_is_global() { let _flag_guard = FeatureFlag::FileBasedMcp.override_enabled(true); diff --git a/app/src/ai/mcp/file_mcp_watcher.rs b/app/src/ai/mcp/file_mcp_watcher.rs index e131dc4547..45421e6623 100644 --- a/app/src/ai/mcp/file_mcp_watcher.rs +++ b/app/src/ai/mcp/file_mcp_watcher.rs @@ -167,14 +167,14 @@ impl FileMCPWatcher { Self::spawn_config_parse( mcp_config_path.config_path, mcp_config_path.root_path, - MCPProvider::Zap, + MCPProvider::Zaplex, ctx, ); } if let Some(home_dir) = dirs::home_dir() { for provider in MCPProvider::iter() { - if provider == MCPProvider::Zap { + if provider == MCPProvider::Zaplex { continue; } match home_subdir_to_watch(provider) { @@ -331,7 +331,7 @@ impl FileMCPWatcher { }; for provider in MCPProvider::iter() { - if provider == MCPProvider::Zap { + if provider == MCPProvider::Zaplex { continue; } match home_subdir_to_watch(provider) { @@ -424,7 +424,7 @@ impl FileMCPWatcher { || update.moved.keys().any(|target| target.path == config_path); self.handle_single_config_update( mcp_config_path.root_path, - MCPProvider::Zap, + MCPProvider::Zaplex, config_path, was_deleted, was_added, @@ -652,7 +652,7 @@ async fn parse_mcp_config_file( return vec![]; } }, - MCPProvider::Claude | MCPProvider::Zap | MCPProvider::Agents => file_contents, + MCPProvider::Claude | MCPProvider::Zaplex | MCPProvider::Agents => file_contents, }; let resolved_contents = match substitute_env_vars(&json) { diff --git a/app/src/ai/mcp/gallery.rs b/app/src/ai/mcp/gallery.rs index 05e39cea76..0b7a88c4a7 100644 --- a/app/src/ai/mcp/gallery.rs +++ b/app/src/ai/mcp/gallery.rs @@ -96,7 +96,7 @@ pub struct MCPGalleryManager { impl MCPGalleryManager { pub fn new(_ctx: &mut ModelContext) -> Self { - // Zap (localization, Phase 2d-2): originally subscribed to `UpdateManager`'s `MCPGalleryUpdated` event + // Zaplex (localization, Phase 2d-2): originally subscribed to `UpdateManager`'s `MCPGalleryUpdated` event // to distribute gallery items after cloud fetch. Post-localization, cloud object fetch/fan-in removed; // this Phase keeps gallery empty and unsubscribes —— // gallery locally always empty, rendered as empty canvas by `MCPServersListPageView`; local MCP uses `file_based_manager` diff --git a/app/src/ai/mcp/mod.rs b/app/src/ai/mcp/mod.rs index 99276eec96..e70c7e17d0 100644 --- a/app/src/ai/mcp/mod.rs +++ b/app/src/ai/mcp/mod.rs @@ -48,7 +48,7 @@ cfg_if::cfg_if! { pub(crate) fn home_config_file_path(provider: MCPProvider) -> Option { match provider { - MCPProvider::Zap => warp_core::paths::warp_home_mcp_config_file_path(), + MCPProvider::Zaplex => warp_core::paths::warp_home_mcp_config_file_path(), _ => dirs::home_dir().map(|home_dir| home_dir.join(provider.home_config_path())), } } @@ -64,7 +64,7 @@ cfg_if::cfg_if! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] pub enum MCPProvider { - Zap, + Zaplex, Claude, Codex, Agents, @@ -73,7 +73,7 @@ pub enum MCPProvider { impl MCPProvider { pub fn display_name(&self) -> &str { match self { - MCPProvider::Zap => "Zap", + MCPProvider::Zaplex => "Zaplex", MCPProvider::Claude => "Claude", MCPProvider::Codex => "Codex", MCPProvider::Agents => "Other Agents", @@ -82,17 +82,17 @@ impl MCPProvider { pub fn icon(&self) -> Icon { match self { - MCPProvider::Zap => Icon::Zap, + MCPProvider::Zaplex => Icon::Zaplex, MCPProvider::Claude => Icon::ClaudeLogo, MCPProvider::Codex => Icon::OpenAILogo, - MCPProvider::Agents => Icon::Zap, + MCPProvider::Agents => Icon::Zaplex, } } /// Returns the path of the provider's config file relative to the home directory. pub fn home_config_path(&self) -> &'static Path { match self { - MCPProvider::Zap => Path::new(".warp/.mcp.json"), + MCPProvider::Zaplex => Path::new(".warp/.mcp.json"), MCPProvider::Claude => Path::new(".claude.json"), MCPProvider::Codex => Path::new(".codex/config.toml"), MCPProvider::Agents => Path::new(".agents/.mcp.json"), @@ -102,7 +102,7 @@ impl MCPProvider { /// Returns the path of the provider's config file relative to a project root. pub fn project_config_path(&self) -> &'static Path { match self { - MCPProvider::Zap => Path::new(".warp/.mcp.json"), + MCPProvider::Zaplex => Path::new(".warp/.mcp.json"), MCPProvider::Claude => Path::new(".mcp.json"), MCPProvider::Codex => Path::new(".codex/config.toml"), MCPProvider::Agents => Path::new(".agents/.mcp.json"), @@ -151,7 +151,7 @@ mod tests { { assert_eq!( mcp_provider_from_file_path(&warp_home_mcp_config_file_path), - Some(MCPProvider::Zap) + Some(MCPProvider::Zaplex) ); } } diff --git a/app/src/ai/mcp/parsing.rs b/app/src/ai/mcp/parsing.rs index d106751569..47bc1a7508 100644 --- a/app/src/ai/mcp/parsing.rs +++ b/app/src/ai/mcp/parsing.rs @@ -73,7 +73,7 @@ enum CodexServerEntry { #[serde(default)] env_vars: Vec, /// Working directory for the server process. - /// Mapped to `working_directory` in Zap JSON. + /// Mapped to `working_directory` in Zaplex JSON. cwd: Option, }, /// A remote server reached over streamable HTTP. diff --git a/app/src/ai/mcp/templatable_manager/native.rs b/app/src/ai/mcp/templatable_manager/native.rs index 56ff0fbe43..d76bd713ce 100644 --- a/app/src/ai/mcp/templatable_manager/native.rs +++ b/app/src/ai/mcp/templatable_manager/native.rs @@ -159,7 +159,7 @@ fn error_to_user_message(error: &rmcp::RmcpError) -> String { } } -/// An MCP server integration that Zap ships with bundled skills for. +/// An MCP server integration that Zaplex ships with bundled skills for. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum McpIntegration { Figma, @@ -773,7 +773,7 @@ impl TemplatableMCPServerManager { // (repo root for project-scoped configs, ~/.warp/ or ~ for globals). This // matches user expectations for repo-relative commands in `.mcp.json`. // Cloud-templated installations (lookup returns None) are unaffected and - // continue to inherit Zap's process cwd. + // continue to inherit Zaplex's process cwd. if cli_server.cwd_parameter.is_none() { if let Some(spawn_root) = FileBasedMCPManager::as_ref(ctx).spawn_root_for_installation(installation_uuid) @@ -1779,7 +1779,7 @@ async fn spawn_server( if err.kind() == std::io::ErrorKind::NotFound { let cwd_display = cwd_for_log .as_deref() - .unwrap_or(""); + .unwrap_or(""); logger.log(format!( "[error] MCP: Failed to spawn '{server_name}': command '{command_for_log}' \ not found (cwd: {cwd_display}). If your MCP server depends on a specific \ diff --git a/app/src/ai/mcp/templatable_manager/oauth.rs b/app/src/ai/mcp/templatable_manager/oauth.rs index 8f4f94c018..5d1739b356 100644 --- a/app/src/ai/mcp/templatable_manager/oauth.rs +++ b/app/src/ai/mcp/templatable_manager/oauth.rs @@ -50,7 +50,7 @@ pub type PersistedCredentialsMap = HashMap; pub type FileBasedPersistedCredentialsMap = HashMap; /// A credential store that wraps [`InMemoryCredentialStore`] and persists token -/// updates to Zap's secure storage via a channel. +/// updates to Zaplex's secure storage via a channel. /// /// When rmcp auto-refreshes an expired access token at runtime, the rotated /// tokens are only saved to the in-memory store by default. This wrapper @@ -126,7 +126,7 @@ impl CredentialStore for PersistingCredentialStore { } /// Installs a [`PersistingCredentialStore`] on the given auth manager so that -/// runtime token auto-refreshes are written back to Zap's secure storage. +/// runtime token auto-refreshes are written back to Zaplex's secure storage. /// /// A background tokio task is spawned to receive credential updates and persist /// them via the [`ModelSpawner`]. The task terminates when the auth manager (and @@ -291,19 +291,19 @@ pub async fn make_authenticated_client( log::warn!( "File-based MCP server {uuid} requires OAuth authentication; \ skipping in headless mode. To use this server, authenticate it \ - in the Zap desktop app first." + in the Zaplex desktop app first." ); } return Err(AuthError::AuthorizationFailed( "MCP server requires OAuth authentication. Please authenticate this server in the \ - Zap desktop app first, then try again." + Zaplex desktop app first, then try again." .to_string(), )); } // Start the authorization process with our custom redirect URI oauth_state - .start_authorization(&[], &redirect_uri, Some("Zap")) + .start_authorization(&[], &redirect_uri, Some("Zaplex")) .await?; let OAuthState::Session(AuthorizationSession { @@ -321,7 +321,7 @@ pub async fn make_authenticated_client( // For apps for which we have static client IDs (e.g. GitHub), we manually override scopes. let mut scopes: &[&str] = &[]; - let config = match auth_manager.register_client("Zap", &redirect_uri).await { + let config = match auth_manager.register_client("Zaplex", &redirect_uri).await { Ok(config) => config, Err(err @ AuthError::RegistrationFailed(_)) => { // If we failed dynamic registration, check to see if this is an auth diff --git a/app/src/ai/mcp/templatable_manager/wasm.rs b/app/src/ai/mcp/templatable_manager/wasm.rs index 67fb223909..afa3d59743 100644 --- a/app/src/ai/mcp/templatable_manager/wasm.rs +++ b/app/src/ai/mcp/templatable_manager/wasm.rs @@ -43,7 +43,7 @@ impl TemplatableMCPServerManager { None } - /// Updates a TemplatableMCPServer in Zap Drive. + /// Updates a TemplatableMCPServer in Zaplex Drive. /// /// This is a no-op in WASM, as MCP servers are not supported in WASM. pub fn update_templatable_mcp_server( @@ -54,7 +54,7 @@ impl TemplatableMCPServerManager { log::warn!("Templatable MCP server update not supported in WASM"); } - /// Gets all TemplatableMCPServers currently in Zap Drive. + /// Gets all TemplatableMCPServers currently in Zaplex Drive. /// /// This is a no-op in WASM, as MCP servers are not supported in WASM. pub fn get_all_templatable_mcp_servers(&self) -> Vec<&TemplatableMCPServer> { @@ -71,7 +71,7 @@ impl TemplatableMCPServerManager { None } - /// Creates a new TemplatableMCPServer in Zap Drive. + /// Creates a new TemplatableMCPServer in Zaplex Drive. /// /// This is a no-op in WASM, as MCP servers are not supported in WASM. pub fn create_templatable_mcp_server( @@ -84,7 +84,7 @@ impl TemplatableMCPServerManager { log::warn!("Creating a TemplatableMCPServer is not supported in WASM"); } - /// Deletes a TemplatableMCPServer from Zap Drive. + /// Deletes a TemplatableMCPServer from Zaplex Drive. /// /// This is a no-op in WASM, as MCP servers are not supported in WASM. pub fn delete_templatable_mcp_server(&mut self, _uuid: Uuid, _ctx: &mut ModelContext) { diff --git a/app/src/ai/mod.rs b/app/src/ai/mod.rs index b92a6c6ea1..90739c05eb 100644 --- a/app/src/ai/mod.rs +++ b/app/src/ai/mod.rs @@ -1,7 +1,7 @@ //! This module should houses all horizontal/cross-cutting AI functionality throughout -//! Zap (including Agent Mode). +//! Zaplex (including Agent Mode). //! -//! The side panel Zap AI implementation lives in `super::ai_assistant`. +//! The side panel Zaplex AI implementation lives in `super::ai_assistant`. pub(crate) mod agent; pub(crate) mod agent_conversations_model; pub(crate) mod agent_events; @@ -38,12 +38,12 @@ pub use request_usage_model::*; use warpui::AppContext; #[cfg(not(target_family = "wasm"))] pub mod agent_sdk; -// Zap Wave 7-3: `ambient_agent_settings` was physically removed along with the ambient-agent UI subsystem. -// Zap Wave 7-2: the CLI / form / environment-preparation paths for Cloud environments have been removed; +// Zaplex Wave 7-3: `ambient_agent_settings` was physically removed along with the ambient-agent UI subsystem. +// Zaplex Wave 7-2: the CLI / form / environment-preparation paths for Cloud environments have been removed; // the local object data types are still kept here for ObjectStoreModel deserialization and existing view filtering. pub mod execution_profiles; pub mod facts; -// Zap Wave 6-8: `generate_block_title` was removed along with the +// Zaplex Wave 6-8: `generate_block_title` was removed along with the // `BlockClient::generate_shared_block_title` stub -- its only consumer was the BlockClient trait signature, with no other local code path. pub(crate) mod loading; pub mod mcp; diff --git a/app/src/ai/predict/mod.rs b/app/src/ai/predict/mod.rs index 9857ab56f6..d3cb47bc60 100644 --- a/app/src/ai/predict/mod.rs +++ b/app/src/ai/predict/mod.rs @@ -1,11 +1,11 @@ -//! This module contains all code relevant to Agent Predict within Zap. +//! This module contains all code relevant to Agent Predict within Zaplex. //! -//! Agent Predict attempts to predict the next action the user will take in Zap. +//! Agent Predict attempts to predict the next action the user will take in Zaplex. pub(crate) mod generate_ai_input_suggestions; pub(crate) mod generate_am_query_suggestions; pub mod next_command_model; -// Zap(Wave 3-2): `predict_am_queries` API module physically deleted — original `ServerApi::predict_am_queries` +// Zaplex(Wave 3-2): `predict_am_queries` API module physically deleted — original `ServerApi::predict_am_queries` // and all 0 external consumers already removed; FeatureFlag::PredictAMQueries / `predict_am_queries_future_handle` // in terminal/input.rs kept only as control switch/handle placeholder, no longer needs this module. pub mod prompt_suggestions; diff --git a/app/src/ai/predict/next_command_model.rs b/app/src/ai/predict/next_command_model.rs index ff6177186b..d38ca7d0f6 100644 --- a/app/src/ai/predict/next_command_model.rs +++ b/app/src/ai/predict/next_command_model.rs @@ -61,7 +61,7 @@ async fn byop_generate_input_suggestions( user_rules: Vec<(Option, String)>, ) -> Result { let Some(cfg) = byop_cfg else { - // Zap is cloud-removed; without BYOP config, no longer fallback to ServerApi —— + // Zaplex is cloud-removed; without BYOP config, no longer fallback to ServerApi —— // return empty response, UI naturally won't show suggestions or spam error logs. return Ok(GenerateAIInputSuggestionsResponseV2::default()); }; diff --git a/app/src/ai/request_usage_model.rs b/app/src/ai/request_usage_model.rs index fd812c3ba9..2c77714822 100644 --- a/app/src/ai/request_usage_model.rs +++ b/app/src/ai/request_usage_model.rs @@ -1,7 +1,7 @@ -//! Zap (Phase 3c subtask A1): Localized as a permanent "unlimited" stub. +//! Zaplex (Phase 3c subtask A1): Localized as a permanent "unlimited" stub. //! //! Historical responsibility: warp.dev server-side RPC-driven "monthly AI request quota" model. -//! Zap uses BYOP (Bring Your Own Provider), where users pay directly to LLM providers +//! Zaplex uses BYOP (Bring Your Own Provider), where users pay directly to LLM providers //! and should never be constrained by cloud concepts like "remaining request count / upgrade CTA / buy extra credits". //! //! Write constraints: @@ -32,7 +32,7 @@ pub enum BonusGrantType { /// Threshold of ambient-only credits at which we surface upgrade/CTA UI. /// -/// Zap: In the local scenario, this will never be reached (because `ambient_only_credits_remaining` is always `None`), +/// Zaplex: In the local scenario, this will never be reached (because `ambient_only_credits_remaining` is always `None`), /// but the constant definition is retained for compatibility with external imports. pub const AMBIENT_AGENT_TRIAL_CREDIT_THRESHOLD: i32 = 20; @@ -71,7 +71,7 @@ pub enum RequestLimitRefreshDuration { } /// Historical: Server-issued snapshot of "monthly request quota". -/// Zap: Retained only as a type shell (`AISettings::update_quota_info` / `ai_assistant/requests.rs` +/// Zaplex: Retained only as a type shell (`AISettings::update_quota_info` / `ai_assistant/requests.rs` /// and other out-of-domain files still construct this structure). `AIRequestUsageModel` no longer holds / caches / updates it. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct RequestLimitInfo { @@ -96,7 +96,7 @@ fn default_voice_requests_limit() -> usize { } impl Default for RequestLimitInfo { - /// Zap: No cloud-side quota; default value is treated as "unlimited". + /// Zaplex: No cloud-side quota; default value is treated as "unlimited". fn default() -> Self { Self { limit: usize::MAX, @@ -125,21 +125,21 @@ impl RequestLimitInfo { } /// Historical: Aggregate structure returned by server's `getRequestLimitInfo`. -/// Zap: Retained only as a type shell (`ai_assistant/requests.rs` still constructs this type). +/// Zaplex: Retained only as a type shell (`ai_assistant/requests.rs` still constructs this type). /// `AIRequestUsageModel` no longer consumes it. pub struct RequestUsageInfo { pub request_limit_info: RequestLimitInfo, pub bonus_grants: Vec, } -/// Zap: Model no longer holds any state. +/// Zaplex: Model no longer holds any state. pub struct AIRequestUsageModel; impl Entity for AIRequestUsageModel { type Event = AIRequestUsageModelEvent; } -/// Zap: Enum definition is retained to be compatible with subscription callback `match` patterns; +/// Zaplex: Enum definition is retained to be compatible with subscription callback `match` patterns; /// after localization, `AIRequestUsageModel` no longer emits any variants → all subscription callbacks become silent no-ops. pub enum AIRequestUsageModelEvent { RequestUsageUpdated, @@ -164,42 +164,42 @@ impl AIRequestUsageModel { None } - /// Zap: No cloud backend, no-op. + /// Zaplex: No cloud backend, no-op. pub fn refresh_request_usage_async(&mut self, _ctx: &mut ModelContext) {} - /// Zap (localized): Always returns true; BYOP local runs are not constrained by cloud limits. + /// Zaplex (localized): Always returns true; BYOP local runs are not constrained by cloud limits. pub fn has_requests_remaining(&self) -> bool { true } - /// Zap (localized): Always returns true. + /// Zaplex (localized): Always returns true. /// AI availability depends only on whether the user has configured an API key (managed independently by `ApiKeyManager`), /// not on cloud-side metering components like `request_limit_info`. pub fn has_any_ai_remaining(&self, _ctx: &AppContext) -> bool { true } - /// Zap (localized): No cloud-side metering; always returns 0. + /// Zaplex (localized): No cloud-side metering; always returns 0. pub fn requests_used(&self) -> usize { 0 } - /// Zap (localized): No cloud-side metering; always returns 0.0. + /// Zaplex (localized): No cloud-side metering; always returns 0.0. pub fn request_percentage_used(&self) -> f32 { 0.0 } - /// Zap (localized): No cloud-side limit; always returns `usize::MAX`. + /// Zaplex (localized): No cloud-side limit; always returns `usize::MAX`. pub fn request_limit(&self) -> usize { usize::MAX } - /// Zap (localized): Far-future placeholder time. + /// Zaplex (localized): Far-future placeholder time. pub fn next_refresh_time(&self) -> DateTime { Utc::now() + chrono::Duration::days(365) } - /// Zap (localized): Always unlimited. + /// Zaplex (localized): Always unlimited. pub fn is_unlimited(&self) -> bool { true } @@ -208,27 +208,27 @@ impl AIRequestUsageModel { "monthly".to_string() } - /// Zap (localized): Local users have no bonus grants. + /// Zaplex (localized): Local users have no bonus grants. pub fn bonus_grants(&self) -> &[BonusGrant] { &[] } - /// Zap (localized): Local users have no concept of ambient-only credits. + /// Zaplex (localized): Local users have no concept of ambient-only credits. pub fn ambient_only_credits_remaining(&self) -> Option { None } - /// Zap (localized): Local users have no concept of workspace bonus credits. + /// Zaplex (localized): Local users have no concept of workspace bonus credits. pub fn total_workspace_bonus_credits_remaining(&self, _uid: WorkspaceUid) -> i32 { 0 } - /// Zap (localized): Local users have no concept of workspace bonus credits. + /// Zaplex (localized): Local users have no concept of workspace bonus credits. pub fn total_current_workspace_bonus_credits_remaining(&self, _ctx: &AppContext) -> i32 { 0 } - /// Zap (localized): Purchasing extra credits does not apply. + /// Zaplex (localized): Purchasing extra credits does not apply. pub fn compute_buy_addon_credits_banner_display_state( &self, _ctx: &AppContext, @@ -236,13 +236,13 @@ impl AIRequestUsageModel { BuyCreditsBannerDisplayState::Hidden } - /// Zap (localized): No-op. + /// Zaplex (localized): No-op. pub fn dismiss_buy_credits_banner(&mut self, _ctx: &mut ModelContext) {} - /// Zap (localized): No-op. + /// Zaplex (localized): No-op. pub fn enable_buy_credits_banner(&mut self, _ctx: &mut ModelContext) {} - /// Zap (localized): Voice input is not constrained by cloud-side quota; always returns true. + /// Zaplex (localized): Voice input is not constrained by cloud-side quota; always returns true. pub fn can_request_voice(&self) -> bool { true } diff --git a/app/src/ai/skills/file_watchers/skill_watcher.rs b/app/src/ai/skills/file_watchers/skill_watcher.rs index 33865e4b7a..0d2ddc4b92 100644 --- a/app/src/ai/skills/file_watchers/skill_watcher.rs +++ b/app/src/ai/skills/file_watchers/skill_watcher.rs @@ -141,7 +141,7 @@ impl SkillWatcher { Self::spawn_read_skills_from_directories(warp_managed_skill_dirs(), ctx); let skills_parent_paths: HashSet = SKILL_PROVIDER_DEFINITIONS .iter() - .filter(|provider| provider.provider != SkillProvider::Zap) + .filter(|provider| provider.provider != SkillProvider::Zaplex) .filter_map(|provider| { home_skills_path(provider.provider) .and_then(|skills_path| skills_path.parent().map(Path::to_path_buf)) @@ -701,7 +701,7 @@ impl SkillWatcher { let provider_root_paths: HashSet = SKILL_PROVIDER_DEFINITIONS .iter() - .filter(|provider| provider.provider != SkillProvider::Zap) + .filter(|provider| provider.provider != SkillProvider::Zaplex) .filter_map(|provider| { let component = provider.skills_path.components().next(); component.map(|component| component.as_os_str().to_string_lossy().to_string()) diff --git a/app/src/ai/skills/file_watchers/utils.rs b/app/src/ai/skills/file_watchers/utils.rs index 3de4c11dd8..cf8d03e64e 100644 --- a/app/src/ai/skills/file_watchers/utils.rs +++ b/app/src/ai/skills/file_watchers/utils.rs @@ -135,7 +135,7 @@ pub fn is_home_skill_directory(path: &Path) -> bool { /// E.g. ~/.agents/skills pub fn is_home_provider_path(path: &Path) -> bool { SKILL_PROVIDER_DEFINITIONS.iter().any(|provider| { - if provider.provider == SkillProvider::Zap { + if provider.provider == SkillProvider::Zaplex { return warp_managed_skill_dirs().iter().any(|dir| path == dir); } home_skills_path(provider.provider) diff --git a/app/src/ai/skills/file_watchers/utils_tests.rs b/app/src/ai/skills/file_watchers/utils_tests.rs index 3a5b9b640d..a26987f03e 100644 --- a/app/src/ai/skills/file_watchers/utils_tests.rs +++ b/app/src/ai/skills/file_watchers/utils_tests.rs @@ -351,7 +351,7 @@ fn extract_skill_parent_directory_returns_home_dir_for_warp_home_skill() { return; }; let Some(warp_home_skills_dir) = warp_core::paths::warp_home_skills_dir() else { - eprintln!("Skipping test: Zap home skills directory not available"); + eprintln!("Skipping test: Zaplex home skills directory not available"); return; }; diff --git a/app/src/ai/skills/listed_skill.rs b/app/src/ai/skills/listed_skill.rs index 9d816bc37d..157bb8c7e9 100644 --- a/app/src/ai/skills/listed_skill.rs +++ b/app/src/ai/skills/listed_skill.rs @@ -9,7 +9,7 @@ pub struct SkillDescriptor { pub description: String, /// The scope of the skill. pub scope: SkillScope, - /// The provider/origin of the skill (Claude, Codex, or Zap). + /// The provider/origin of the skill (Claude, Codex, or Zaplex). /// None if the skill path didn't match a known provider directory. pub provider: SkillProvider, /// Override icon for this skill. When set, rendering code should use this @@ -26,7 +26,7 @@ impl SkillDescriptor { pub fn new_bundled(id: String, skill: ParsedSkill, icon: Icon) -> Self { Self { - provider: SkillProvider::Zap, + provider: SkillProvider::Zaplex, scope: SkillScope::Bundled, reference: SkillReference::BundledSkillId(id), name: skill.name, diff --git a/app/src/ai/skills/resolve_skill_spec.rs b/app/src/ai/skills/resolve_skill_spec.rs index c547b7300e..5963fb9a3d 100644 --- a/app/src/ai/skills/resolve_skill_spec.rs +++ b/app/src/ai/skills/resolve_skill_spec.rs @@ -63,7 +63,7 @@ fn resolve_from_skill_dirs_by_directory_scan( fn home_skill_dirs_for_resolution() -> Vec { let mut skill_dirs = Vec::new(); for provider in SKILL_PROVIDER_DEFINITIONS.iter() { - if provider.provider == SkillProvider::Zap { + if provider.provider == SkillProvider::Zaplex { for dir in warp_managed_skill_dirs() { push_unique_path(&mut skill_dirs, dir); } diff --git a/app/src/ai/skills/resolve_skill_spec_tests.rs b/app/src/ai/skills/resolve_skill_spec_tests.rs index 95f8131cab..eaf370c970 100644 --- a/app/src/ai/skills/resolve_skill_spec_tests.rs +++ b/app/src/ai/skills/resolve_skill_spec_tests.rs @@ -28,7 +28,7 @@ fn resolve_from_skill_dirs_by_directory_scan_resolves_home_skill_dir() -> Result &skill_path, "my-skill", "desc", - "# Global Zap skill\n\nUse this one.", + "# Global Zaplex skill\n\nUse this one.", )?; let spec = SkillSpec::without_repo("my-skill".to_string()); @@ -36,7 +36,7 @@ fn resolve_from_skill_dirs_by_directory_scan_resolves_home_skill_dir() -> Result .context("Expected to resolve skill from explicit home skill dir")?; assert_eq!(resolved.skill_path, skill_path); - assert!(resolved.instructions.contains("Global Zap skill")); + assert!(resolved.instructions.contains("Global Zaplex skill")); Ok(()) } @@ -63,7 +63,7 @@ fn resolve_from_root_path_by_directory_scan_respects_directory_precedence() -> R &warp_skill, "my-skill", "desc", - "# Zap version\n\nDo not pick this when .agents exists.", + "# Zaplex version\n\nDo not pick this when .agents exists.", )?; write_skill_file( &claude_skill, @@ -83,7 +83,7 @@ fn resolve_from_root_path_by_directory_scan_respects_directory_precedence() -> R assert_eq!(resolved.skill_path, agents_skill); assert!(resolved.instructions.contains("Agents version")); - assert!(!resolved.instructions.contains("Zap version")); + assert!(!resolved.instructions.contains("Zaplex version")); assert!(!resolved.instructions.contains("Claude version")); assert!(!resolved.instructions.contains("Codex version")); assert!(!resolved.instructions.contains("name:")); @@ -201,7 +201,7 @@ fn resolve_simple_name_uses_directory_precedence() -> Result<()> { &warp_skill, "my-skill", "desc", - "# Zap version\n\nThis should lose to .agents but beat .claude.", + "# Zaplex version\n\nThis should lose to .agents but beat .claude.", )?; let claude_skill = root.join(".claude/skills/my-skill/SKILL.md"); @@ -218,7 +218,7 @@ fn resolve_simple_name_uses_directory_precedence() -> Result<()> { .context("Expected to resolve skill by name")?; assert_eq!(resolved.skill_path, agents_skill); assert!(resolved.instructions.contains("Agents version")); - assert!(!resolved.instructions.contains("Zap version")); + assert!(!resolved.instructions.contains("Zaplex version")); assert!(!resolved.instructions.contains("Claude version")); Ok(()) diff --git a/app/src/ai/skills/skill_manager.rs b/app/src/ai/skills/skill_manager.rs index 70350672b7..9a4bb58492 100644 --- a/app/src/ai/skills/skill_manager.rs +++ b/app/src/ai/skills/skill_manager.rs @@ -97,7 +97,7 @@ pub struct SkillManager { /// Reverse lookup: skill name → set of paths with that name. /// This allows efficient lookup by skill name without scanning all paths. skills_by_name: HashMap>, - /// Skills bundled into Zap, each with activation condition and icon. + /// Skills bundled into Zaplex, each with activation condition and icon. bundled_skills: HashMap, #[allow(dead_code)] skill_watcher: ModelHandle, // Can't remove this or it'll get cleaned up after new() @@ -348,7 +348,7 @@ impl SkillManager { /// doesn't know the SKILL.md absolute path, so name → ParsedSkill resolution is required. /// /// When multiple skills have the same name, pick the first in [`provider_rank`] order - /// (`Agents > Zap > Claude > …`), aligning with priority in `unique_skills`/`list_skill_inventory`. + /// (`Agents > Zaplex > Claude > …`), aligning with priority in `unique_skills`/`list_skill_inventory`. /// Bundled skills don't enter the `skills_by_name` index; they're handled separately as fallback here. pub fn find_skill_by_name(&self, name: &str) -> Option<&ParsedSkill> { // Prefer filesystem skills: when multiple have the same name, select the best by provider_rank. @@ -509,7 +509,7 @@ impl SkillManager { } } - /// Load skill definitions bundled with Zap. + /// Load skill definitions bundled with Zaplex. async fn load_bundled_skills() -> HashMap { let Some(resources_dir) = warp_core::paths::bundled_resources_dir() else { return HashMap::new(); @@ -615,7 +615,7 @@ async fn read_bundled_skills(skills_dir: &Path) -> HashMap /// Builds the context map for bundled skill variable substitution. /// /// Supported variables: -/// - `{{warp_server_url}}` - Empty in Zap; retained for bundled skill compatibility. +/// - `{{warp_server_url}}` - Empty in Zaplex; retained for bundled skill compatibility. /// - `{{warp_cli_binary_name}}` - The CLI binary name (e.g., `warp` or `warp-cli`) /// - `{{warp_url_scheme}}` - The URL scheme (e.g., `warp`, `warpdev`, `warppreview`) /// - `{{settings_schema_path}}` - Path to the bundled JSON settings schema @@ -653,7 +653,7 @@ fn build_bundled_skill_context() -> HashMap { /// Returns the icon for a bundled skill, given its directory-based ID. /// Skills with a known brand (e.g. `pr-comments` → GitHub) get a -/// branded icon; everything else falls back to the Zap logo. +/// branded icon; everything else falls back to the Zaplex logo. fn icon_for_bundled_skill(skill_id: &str) -> Icon { match skill_id { "pr-comments" => Icon::Github, diff --git a/app/src/ai/skills/skill_manager_tests.rs b/app/src/ai/skills/skill_manager_tests.rs index bd3bee991a..8e11e97cbd 100644 --- a/app/src/ai/skills/skill_manager_tests.rs +++ b/app/src/ai/skills/skill_manager_tests.rs @@ -465,7 +465,7 @@ fn make_skill(name: &str, provider_dir: &str) -> ParsedSkill { provider: get_provider_for_path(&PathBuf::from(format!( "/repo/{provider_dir}/skills/{name}/SKILL.md" ))) - .unwrap_or(SkillProvider::Zap), + .unwrap_or(SkillProvider::Zaplex), scope: SkillScope::Project, } } diff --git a/app/src/ai/skills/skill_utils.rs b/app/src/ai/skills/skill_utils.rs index 7554b06d17..f0fd36bf5c 100644 --- a/app/src/ai/skills/skill_utils.rs +++ b/app/src/ai/skills/skill_utils.rs @@ -24,7 +24,7 @@ use crate::warp_managed_paths_watcher::warp_managed_skill_dirs; /// Priority rules (when multiple skills have the same name): /// /// 1. **Lower provider rank wins**: follows [`SKILL_PROVIDER_DEFINITIONS`] order (index 0 = highest priority), -/// e.g., `Agents > Zap > Claude > …`. +/// e.g., `Agents > Zaplex > Claude > …`. /// 2. **On equal rank, shorter reference path wins**: used as stable tiebreaker. /// /// This implementation covers three scenarios: @@ -148,7 +148,7 @@ pub fn icon_override_for_skill_name(name: &str) -> Option { pub fn skill_path_from_file_path(file_path: &Path) -> Option { for definition in SKILL_PROVIDER_DEFINITIONS.iter() { - let home_skill_dirs = if definition.provider == SkillProvider::Zap { + let home_skill_dirs = if definition.provider == SkillProvider::Zaplex { warp_managed_skill_dirs() } else { home_skills_path(definition.provider).into_iter().collect() diff --git a/app/src/ai/skills/skill_utils_tests.rs b/app/src/ai/skills/skill_utils_tests.rs index ddcb375d92..4d3f4a1e2b 100644 --- a/app/src/ai/skills/skill_utils_tests.rs +++ b/app/src/ai/skills/skill_utils_tests.rs @@ -15,7 +15,7 @@ fn test_skill_path_from_file_path_skill_md() { #[test] fn test_skill_path_from_file_path_warp_home_skill() { let Some(warp_home_skills_dir) = warp_core::paths::warp_home_skills_dir() else { - eprintln!("Skipping test: Zap home skills directory not available"); + eprintln!("Skipping test: Zaplex home skills directory not available"); return; }; let warp_home_skill = warp_home_skills_dir diff --git a/app/src/ai/skills/telemetry.rs b/app/src/ai/skills/telemetry.rs index 4225284867..219683f1ca 100644 --- a/app/src/ai/skills/telemetry.rs +++ b/app/src/ai/skills/telemetry.rs @@ -27,7 +27,7 @@ pub enum SkillTelemetryEvent { name: Option, /// Specifies the scope of the skill. scope: Option, - /// Specifies the provider of the skill (Zap, Claude, Codex, etc.) + /// Specifies the provider of the skill (Zaplex, Claude, Codex, etc.) provider: Option, /// Whether the ReadSkill lookup failed (reference could not be resolved) error: bool, diff --git a/app/src/ai/voice/mod.rs b/app/src/ai/voice/mod.rs index dcb1527831..a9f7e558e3 100644 --- a/app/src/ai/voice/mod.rs +++ b/app/src/ai/voice/mod.rs @@ -1,7 +1,7 @@ -//! This module contains all code relevant to Voice within Zap. +//! This module contains all code relevant to Voice within Zaplex. //! -//! Voice is used for voice input within Zap. +//! Voice is used for voice input within Zaplex. -// Zap Wave 6-1: `pub(crate) mod transcribe` to be deleted physically together with `ServerApi::transcribe`. +// Zaplex Wave 6-1: `pub(crate) mod transcribe` to be deleted physically together with `ServerApi::transcribe`. // The atomic module `transcribe/api/{request,response}` is wire type only for the deleted cloud // `/ai/transcribe` endpoint. Local voice uses `voice/transcriber.rs::Transcriber` trait + `TranscribeError`. diff --git a/app/src/ai_assistant/mod.rs b/app/src/ai_assistant/mod.rs index 1302226ab0..dabebc4fa3 100644 --- a/app/src/ai_assistant/mod.rs +++ b/app/src/ai_assistant/mod.rs @@ -1,4 +1,4 @@ -//! AI Assistant has since been renamed to "Zap AI" in the product. +//! AI Assistant has since been renamed to "Zaplex AI" in the product. use std::{collections::HashSet, sync::Arc}; use crate::{server::telemetry::OpenedWarpAISource, terminal::model::terminal_model::BlockIndex}; @@ -20,8 +20,8 @@ mod test_util; /// This is also roughly the limit at which the editor starts degrading. pub const PROMPT_CHARACTER_LIMIT: usize = 1000; -pub const AI_ASSISTANT_FEATURE_NAME: &str = "Zap AI"; -pub const ASK_AI_ASSISTANT_TEXT: &str = "Ask Zap AI"; +pub const AI_ASSISTANT_FEATURE_NAME: &str = "Zaplex AI"; +pub const ASK_AI_ASSISTANT_TEXT: &str = "Ask Zaplex AI"; pub const AI_ASSISTANT_SVG_PATH: &str = "bundled/svg/ai-assistant.svg"; diff --git a/app/src/ai_assistant/panel.rs b/app/src/ai_assistant/panel.rs index f1f72943e7..b22fa24dbe 100644 --- a/app/src/ai_assistant/panel.rs +++ b/app/src/ai_assistant/panel.rs @@ -31,7 +31,7 @@ use crate::input_suggestions::{Event as InputSuggestionsEvent, InputSuggestions} use crate::send_telemetry_from_ctx; use crate::server::telemetry::{TelemetryEvent, WarpAIActionType}; -use crate::terminal::resizable_data::{ModalType, ResizableData, DEFAULT_WARP_AI_WIDTH}; +use crate::terminal::resizable_data::{ModalType, ResizableData, DEFAULT_ZAPLEX_AI_WIDTH}; use crate::ui_components::blended_colors; use crate::workspaces::user_workspaces::UserWorkspaces; @@ -70,7 +70,7 @@ const EDITOR_MARGIN: f32 = 16.; const LOGO_SIZE: f32 = 20.; -const ZERO_STATE_HELP_TEXT: &str = "Shift + ctrl + space a block or text selection to ask Zap AI."; +const ZERO_STATE_HELP_TEXT: &str = "Shift + ctrl + space a block or text selection to ask Zaplex AI."; const SCRIPT_ZERO_STATE_PROMPT: &str = "Write a script to connect to an AWS EC2 instance."; const GIT_ZERO_STATE_PROMPT: &str = "How do I undo the most recent commits in git?"; const FILES_ZERO_STATE_PROMPT: &str = "How do I find all files containing specific text?"; @@ -228,7 +228,7 @@ impl AIAssistantPanelView { Some(handle) => handle, None => { log::error!("Couldn't retrieve warp ai resizable state handle."); - resizable_state_handle(DEFAULT_WARP_AI_WIDTH) + resizable_state_handle(DEFAULT_ZAPLEX_AI_WIDTH) } }; @@ -651,14 +651,14 @@ impl AIAssistantPanelView { let time_now = Local::now(); result.push_str(&format!( - "## Zap AI Transcript ({})\n\n", + "## Zaplex AI Transcript ({})\n\n", time_now.format("%x %l:%M %p") )); for part in transcript { result.push_str(&format!("Prompt: {}\n\n", part.raw_user_prompt().trim())); result.push_str(&format!( - "Zap AI: {}\n\n", + "Zaplex AI: {}\n\n", part.raw_assistant_answer().trim() )); } diff --git a/app/src/ai_assistant/requests.rs b/app/src/ai_assistant/requests.rs index e1d14f34db..40c3243142 100644 --- a/app/src/ai_assistant/requests.rs +++ b/app/src/ai_assistant/requests.rs @@ -21,7 +21,7 @@ use anyhow::Result; /// Not wiring through Settings for now since this data is only needed by the panel view. pub const REQUEST_LIMIT_INFO_CACHE_KEY: &str = "AIAssistantRequestLimitInfo"; -/// Tracks the current request status for making Zap AI requests against server. +/// Tracks the current request status for making Zaplex AI requests against server. pub enum RequestStatus { /// There isn't a request in flight right now. NotInFlight, @@ -103,7 +103,7 @@ impl Requests { request_limit_info, }; - // Zap: No Zap Inc cloud backend, initial request_limit_info no longer pulled from server; + // Zaplex: No Zaplex Inc cloud backend, initial request_limit_info no longer pulled from server; // use the cached / default values above. let _ = cached_request_limit_info; requests @@ -132,7 +132,7 @@ impl Requests { } } - /// Starts a Zap AI request against the server with the given request prompt. + /// Starts a Zaplex AI request against the server with the given request prompt. pub fn issue_request(&mut self, request: String, ctx: &mut ModelContext) { let raw_request = request.trim(); let transcript_part_index = self.current_transcript.len(); @@ -142,7 +142,7 @@ impl Requests { TranscriptPartSubType::Question, raw_request, ); - let response = "Zap AI Assistant cloud requests are disabled in Zap. Use Agent Mode with a configured BYOP model instead.".to_owned(); + let response = "Zaplex AI Assistant cloud requests are disabled in Zaplex. Use Agent Mode with a configured BYOP model instead.".to_owned(); let response_in_markdown = markdown_segments_from_text( transcript_part_index, TranscriptPartSubType::Answer, diff --git a/app/src/ai_assistant/transcript.rs b/app/src/ai_assistant/transcript.rs index 134a7c443d..3d23692386 100644 --- a/app/src/ai_assistant/transcript.rs +++ b/app/src/ai_assistant/transcript.rs @@ -63,7 +63,7 @@ const WHAT_TO_DO_NEXT_PROMPT: &str = "What should I do next?"; const IN_FLIGHT_REQUEST_TEXT: &str = "Generating answer..."; const ACCURACY_NOTICE_TEXT: &str = "AI responses can be inaccurate."; const MISSING_CONTEXT_NOTICE_TEXT: &str = - "Zap AI might forget earlier answers as conversations get long."; + "Zaplex AI might forget earlier answers as conversations get long."; lazy_static::lazy_static! { static ref SCROLL_BUFFER_OFFSET_PX: Pixels = (10.).into_pixels(); diff --git a/app/src/app_menus.rs b/app/src/app_menus.rs index 0408238a90..409b4daf80 100644 --- a/app/src/app_menus.rs +++ b/app/src/app_menus.rs @@ -44,7 +44,8 @@ pub fn menu_bar(ctx: &mut AppContext) -> MenuBar { make_new_tab_menu(ctx), make_new_blocks_menu(ctx), make_new_ai_menu(ctx), - make_new_drive_menu(ctx), + // Zaplex Drive (inherited Warp Drive) is out of scope; its menu is de-listed + // but `make_new_drive_menu` is preserved as a template. make_new_window_menu(), make_new_help_menu(), ]) @@ -316,8 +317,6 @@ fn make_new_edit_menu(ctx: &AppContext) -> Menu { fn make_new_view_menu(ctx: &AppContext) -> Menu { let mut items = vec![ - updateable_custom_item_without_checkmark(CustomAction::ToggleWarpDrive, ctx), - MenuItem::Separator, updateable_custom_item_without_checkmark(CustomAction::CommandPalette, ctx), updateable_custom_item_without_checkmark(CustomAction::NavigationPalette, ctx), updateable_custom_item_without_checkmark(CustomAction::LaunchConfigPalette, ctx), @@ -522,6 +521,11 @@ fn make_new_blocks_menu(ctx: &AppContext) -> Menu { Menu::new(&crate::t!("app-menu-blocks"), items) } +// Preserved as a template: Zaplex Drive (inherited Warp Drive) is out of scope and +// its menu is de-listed (not built into the menu bar), but the builder is kept so a +// future sidebar/menu can reuse it. See +// docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md +#[allow(dead_code)] fn make_new_drive_menu(ctx: &AppContext) -> Menu { let mut items = vec![ updateable_custom_item_without_checkmark(CustomAction::NewPersonalWorkflow, ctx), @@ -540,7 +544,7 @@ fn make_new_drive_menu(ctx: &AppContext) -> Menu { updateable_custom_item_without_checkmark(CustomAction::OpenMCPServerCollection, ctx), ]); - // Zap Phase 2a: removed `Share pane contents` menu item (cloud + // Zaplex Phase 2a: removed `Share pane contents` menu item (cloud // sharing UI gone) — keeping the entry would render a clickable menu // item that does nothing. @@ -588,7 +592,7 @@ fn block_menu_debug_items() -> Vec { ))); items.push(MenuItem::Custom(CustomMenuItem::new( - &crate::t!("app-menu-show-warpified-ssh-blocks"), + &crate::t!("app-menu-show-zaplexified-ssh-blocks"), move |ctx| { let handle = BlockVisibilitySettings::handle(ctx); handle.update(ctx, |block_visibility_settings, ctx| { @@ -605,9 +609,9 @@ fn block_menu_debug_items() -> Vec { let name = if BlockVisibilitySettings::handle(ctx).read(ctx, |settings, _ctx| { *settings.should_show_ssh_block.value() }) { - crate::t!("app-menu-hide-warpified-ssh-blocks") + crate::t!("app-menu-hide-zaplexified-ssh-blocks") } else { - crate::t!("app-menu-show-warpified-ssh-blocks") + crate::t!("app-menu-show-zaplexified-ssh-blocks") }; MenuItemPropertyChanges { @@ -799,8 +803,8 @@ fn debug_menu_items() -> Vec { None, ))); - // Zap Wave 3-1:"Create anonymous user" debug menu item 随 - // `workspace:debug_create_anonymous_user` global action + AuthClient 一同物理删。 + // Zaplex Wave 3-1: "Create anonymous user" debug menu item + // deleted together with `workspace:debug_create_anonymous_user` global action + AuthClient. } if FeatureFlag::RuntimeFeatureFlags.is_enabled() { @@ -1024,7 +1028,7 @@ fn open_new_agent_tab_or_window(ctx: &mut AppContext) { } } -/// Dispatch event to open a new Zap window +/// Dispatch event to open a new Zaplex window fn open_new_window(ctx: &mut AppContext) { ctx.dispatch_global_action("root_view:open_new", &()); ctx.dispatch_global_action("workspace:save_app", &()); diff --git a/app/src/app_services/linux/mod.rs b/app/src/app_services/linux/mod.rs index 7f0f6b666f..43a38f8f48 100644 --- a/app/src/app_services/linux/mod.rs +++ b/app/src/app_services/linux/mod.rs @@ -64,7 +64,7 @@ pub fn pass_startup_args_to_existing_instance( #[derive(Debug, thiserror::Error)] #[cfg(feature = "release_bundle")] pub enum StartupArgsForwardingError { - /// There's no instance of Zap already running. + /// There's no instance of Zaplex already running. #[error("no existing instance found to forward args to")] NoExistingInstance, /// This instance was launched after an auto-update and should not forward @@ -81,7 +81,7 @@ impl From for StartupArgsForwardingError { fn from(value: zbus::fdo::Error) -> Self { // While ServiceUnknown usually means that D-Bus doesn't know how to // _launch_ something to handle your message, in our case, we're not - // registering a service, so this really means that Zap is not already + // registering a service, so this really means that Zaplex is not already // running. if matches!(value, zbus::fdo::Error::ServiceUnknown(_)) { StartupArgsForwardingError::NoExistingInstance @@ -149,17 +149,17 @@ impl ApplicationService { } } -// A D-Bus client for connecting to an already-running instance of Zap and +// A D-Bus client for connecting to an already-running instance of Zaplex and // invoking org.freedesktop.Application IPC methods. // // `default_service` / `default_path` are overridden at the call site (`pass_startup_args_to_existing_instance`) // via `.destination(well_known_name())` / `.path(application_service_path())` on the builder, // so the constants here will not actually be used; but to avoid misleading future callers who might -// use `Proxy::new` directly with defaults, these still point to the OSS default `dev.zap.Zap`. +// use `Proxy::new` directly with defaults, these still point to the OSS default `dev.zaplex.Zaplex`. #[proxy( interface = "org.freedesktop.Application", - default_service = "dev.zap.Zap", - default_path = "/dev/zap/Zap", + default_service = "dev.zaplex.Zaplex", + default_path = "/dev/zap/Zaplex", gen_blocking = false )] trait ExistingApplication { diff --git a/app/src/app_services/mod.rs b/app/src/app_services/mod.rs index 5d657c4f53..79044792fd 100644 --- a/app/src/app_services/mod.rs +++ b/app/src/app_services/mod.rs @@ -2,7 +2,7 @@ //! to the host system. //! //! For example, on macOS, this module sets up integrations with -//! Finder such that the user can open a new Zap tab or window +//! Finder such that the user can open a new Zaplex tab or window //! in a given directory. #[cfg(any(target_os = "linux", target_os = "freebsd"))] diff --git a/app/src/app_services/windows/mod.rs b/app/src/app_services/windows/mod.rs index f76e652edf..921d578182 100644 --- a/app/src/app_services/windows/mod.rs +++ b/app/src/app_services/windows/mod.rs @@ -18,7 +18,7 @@ mod single_instance_manager; pub enum StartupArgsForwardingError { #[error("should not forward arguments after an auto-update")] IgnoredAfterAutoUpdate, - #[error("there is no other instance of Zap")] + #[error("there is no other instance of Zaplex")] NoExistingInstance, #[error("failed to construct url")] CouldNotCreateUrl(#[from] url::ParseError), diff --git a/app/src/app_services/windows/registry.rs b/app/src/app_services/windows/registry.rs index ebf97e52f5..2fc962b41c 100644 --- a/app/src/app_services/windows/registry.rs +++ b/app/src/app_services/windows/registry.rs @@ -12,7 +12,7 @@ pub(super) fn register_uri_handler() { return; }; - // The Windows Registry entry for Zap (assuming the channel is WarpLocal): + // The Windows Registry entry for Zaplex (assuming the channel is WarpLocal): // warplocal // (Default) = "WarpLocal" // URL Protocol = "" @@ -36,7 +36,7 @@ pub(super) fn register_uri_handler() { }; // TODO(CORE-2861): Add the `DefaultIcon` Default value here with the file path to - // Zap's icon once we figure out distribution on Windows. + // Zaplex's icon once we figure out distribution on Windows. let command_key = match parent_key.create("shell\\open\\command") { Ok(command_key) => command_key, diff --git a/app/src/app_services/windows/service_impl.rs b/app/src/app_services/windows/service_impl.rs index 446e69d96b..3164095a70 100644 --- a/app/src/app_services/windows/service_impl.rs +++ b/app/src/app_services/windows/service_impl.rs @@ -8,7 +8,7 @@ use warpui::r#async::executor::Background; use super::single_instance_manager::uri_named_pipe_name; -/// IPC Service to respond to URIs sent to the active Zap instance. +/// IPC Service to respond to URIs sent to the active Zaplex instance. pub(super) struct UriService {} impl ipc::Service for UriService { @@ -39,7 +39,7 @@ impl ipc::ServiceImpl for UriServiceImpl { } } -/// Forwards the given URLs to the main running instance of Zap. +/// Forwards the given URLs to the main running instance of Zaplex. pub(super) async fn forward_uri_to_sole_running_instance( urls: Vec, ) -> Result<(), ipc::ClientError> { diff --git a/app/src/app_services/windows/single_instance_manager.rs b/app/src/app_services/windows/single_instance_manager.rs index ad2bc17d80..03e7d943fb 100644 --- a/app/src/app_services/windows/single_instance_manager.rs +++ b/app/src/app_services/windows/single_instance_manager.rs @@ -45,7 +45,7 @@ static SOLE_INSTANCE_MUTEX: LazyLock, Error>>> LazyLock::new(|| Mutex::new(try_create_mutex())); pub(super) fn uri_named_pipe_name() -> String { - format!("Zap{:?}_URI_CHANNEL", ChannelState::channel()) + format!("Zaplex{:?}_URI_CHANNEL", ChannelState::channel()) } fn try_create_mutex() -> Result, Error> { @@ -55,9 +55,9 @@ fn try_create_mutex() -> Result, Error> { // session namespace" // // NOTE: This lock name must stay in sync with `AppMutexName` in - // `script/windows/windows-installer.iss`, which the installer uses to detect whether Zap is + // `script/windows/windows-installer.iss`, which the installer uses to detect whether Zaplex is // running. - let name = format!("Local\\Zap{:?}_SingleInstance", ChannelState::channel()) + let name = format!("Local\\Zaplex{:?}_SingleInstance", ChannelState::channel()) .encode_utf16() .chain(std::iter::once(0)) .collect::>(); @@ -82,7 +82,7 @@ fn try_create_mutex() -> Result, Error> { }) } -/// A singleton model that is responsible for ensuring there is only one instance of Zap running. +/// A singleton model that is responsible for ensuring there is only one instance of Zaplex running. /// Uses a Windows named mutex (via `CreateMutexW`) which is a kernel object automatically cleaned /// up by the OS when all handles are closed, including on crash. pub(super) struct SingleInstanceManager { @@ -90,7 +90,7 @@ pub(super) struct SingleInstanceManager { } impl SingleInstanceManager { - /// Attempts to upgrade the current Zap instance to the "main" instance (i.e. the one that + /// Attempts to upgrade the current Zaplex instance to the "main" instance (i.e. the one that /// holds the named mutex). This function enforces that a URI server is created iff the mutex /// is held. pub(super) fn new(ctx: &mut ModelContext) -> Self { @@ -130,7 +130,7 @@ impl SingleInstanceManager { } } - /// Returns whether or not this process should be treated as the main instance of Zap. + /// Returns whether or not this process should be treated as the main instance of Zaplex. /// /// NOTE: If an unexpected error occurs, we return `true` since it's better to open a second /// instance than to fail to create a first instance. diff --git a/app/src/app_state.rs b/app/src/app_state.rs index f8723cd98a..f50235ae4e 100644 --- a/app/src/app_state.rs +++ b/app/src/app_state.rs @@ -13,7 +13,7 @@ use crate::ai::ambient_agents::AmbientAgentTaskId; use crate::ai::blocklist::InputConfig; use crate::ai::blocklist::SerializedBlockListItem; use crate::code::editor_management::CodeSource; -use crate::drive::ZapDriveObjectSettings; +use crate::drive::ZaplexDriveObjectSettings; use crate::root_view::quake_mode_window_id; use crate::server::ids::SyncId; use crate::settings_view::SettingsSection; @@ -127,7 +127,7 @@ pub enum LeafContents { AIDocument(AIDocumentPaneSnapshot), Code(CodePaneSnapShot), EnvVarCollection(EnvVarCollectionPaneSnapshot), - // Zap Wave 7-3: the `EnvironmentManagement` LeafContents variant was physically removed + // Zaplex Wave 7-3: the `EnvironmentManagement` LeafContents variant was physically removed // along with the Ambient Agent UI subsystem. Workflow(WorkflowPaneSnapshot), Settings(SettingsPaneSnapshot), @@ -167,7 +167,7 @@ impl LeafContents { /// restoration to fail and the whole tab to disappear on restart. pub(crate) fn is_persisted(&self) -> bool { match self { - // Zap Wave 7-3: the `EnvironmentManagement` arm was physically removed along with the variant. + // Zaplex Wave 7-3: the `EnvironmentManagement` arm was physically removed along with the variant. // SSH server editor: the data (host/user/...) is persisted in the ssh_servers table, // the pane itself is just a view, so closing and reopening makes no difference. LeafContents::SshServer { .. } => false, @@ -237,7 +237,7 @@ pub enum NotebookPaneSnapshot { /// server ID. notebook_id: Option, // Settings for the notebook pane when it's opened (such as a folder to focus upon opening) - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, }, LocalFileNotebook { /// The path to the local file that was open in this pane. This may be `None` if @@ -276,7 +276,7 @@ pub enum WorkflowPaneSnapshot { WorkflowObject { workflow_id: Option, // Settings for the workflow pane when it's opened (such as a folder to focus upon opening) - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, }, } @@ -289,7 +289,7 @@ pub enum EnvVarCollectionPaneSnapshot { }, } -// Zap Wave 7-3: `EnvironmentManagementPaneSnapshot` was physically removed along with the LeafContents variant. +// Zaplex Wave 7-3: `EnvironmentManagementPaneSnapshot` was physically removed along with the LeafContents variant. #[derive(Clone, Debug, PartialEq)] pub enum SettingsPaneSnapshot { @@ -316,11 +316,12 @@ pub enum CodeReviewPaneSnapshot { pub enum LeftPanelDisplayedTab { FileTree, GlobalSearch, - ZapDrive, + ZaplexDrive, ConversationListView, SshManager, ServerFileBrowser, SkillManager, + Cockpit, } impl From for LeftPanelDisplayedTab { @@ -328,11 +329,12 @@ impl From for LeftPanelDisplayedTab { match view { ToolPanelView::ProjectExplorer => LeftPanelDisplayedTab::FileTree, ToolPanelView::GlobalSearch { .. } => LeftPanelDisplayedTab::GlobalSearch, - ToolPanelView::ZapDrive => LeftPanelDisplayedTab::ZapDrive, + ToolPanelView::ZaplexDrive => LeftPanelDisplayedTab::ZaplexDrive, ToolPanelView::ConversationListView => LeftPanelDisplayedTab::ConversationListView, ToolPanelView::SshManager => LeftPanelDisplayedTab::SshManager, ToolPanelView::ServerFileBrowser => LeftPanelDisplayedTab::ServerFileBrowser, ToolPanelView::SkillManager => LeftPanelDisplayedTab::SkillManager, + ToolPanelView::Cockpit => LeftPanelDisplayedTab::Cockpit, } } } diff --git a/app/src/auth/mod.rs b/app/src/auth/mod.rs index 88397fd226..21013316f9 100644 --- a/app/src/auth/mod.rs +++ b/app/src/auth/mod.rs @@ -1,4 +1,4 @@ -//! Zap local-identity facade. +//! Zaplex local-identity facade. //! //! This module preserves the public type surface of `AuthState` / `AuthStateProvider` / //! `AuthManager` / `User` / `UserUid` / `Credentials` and their pub method signatures, while @@ -40,7 +40,7 @@ pub enum OwnerType { User, } -/// Zap local API key prefix. +/// Zaplex local API key prefix. /// /// Historically used to identify "a string starting with wk- is a managed API key". On the BYOP /// path there is no longer any managed-account API key concept. The constant is still consumed @@ -51,20 +51,20 @@ pub const API_KEY_PREFIX: &str = "wk-"; // ---------- Credentials / AuthToken / LoginToken ---------- // // Originally the runtime branches for several authentication methods: managed token / API key / -// session cookie. After Zap's localization, only the two variants actually used -- `ApiKey` and +// session cookie. After Zaplex's localization, only the two variants actually used -- `ApiKey` and // `Test` -- are kept. The managed-token and cookie variants have been physically deleted; under -// Zap all former external-account branches always take the `None` path / return early. +// Zaplex all former external-account branches always take the `None` path / return early. -/// Represents how the user authenticates with Zap. +/// Represents how the user authenticates with Zaplex. /// -/// Zap localization branches: +/// Zaplex localization branches: /// - `ApiKey`: on the BYOP path, the user's own LLM provider API key, actually managed by /// settings/keychain respectively; here we only keep the enum facade for reader methods like /// `AuthState::credentials()`. /// - `Test`: used in test / `skip_login` builds. #[derive(Clone, Debug)] pub enum Credentials { - /// BYOP / Zap Inc API key; owner_type is kept for legacy code to read (always `None`). + /// BYOP / Zaplex Inc API key; owner_type is kept for legacy code to read (always `None`). ApiKey { key: String, owner_type: Option, @@ -82,7 +82,7 @@ impl Credentials { } } - /// Returns the API key owner type (always `None` on the Zap path). + /// Returns the API key owner type (always `None` on the Zaplex path). pub fn api_key_owner_type(&self) -> Option { match self { Credentials::ApiKey { owner_type, .. } => *owner_type, @@ -107,7 +107,7 @@ impl Credentials { pub enum AuthToken { /// BYOP / platform-layer API key. ApiKey(String), - /// No token at all (session cookie / test / Zap local mode). + /// No token at all (session cookie / test / Zaplex local mode). NoAuth, } @@ -131,8 +131,8 @@ impl AuthToken { // ---------- User metadata ---------- -/// Anonymous-user type facade. After Zap's localization there is no anonymous-user concept; the -/// enum is kept so that the match arms scattered across telemetry / settings still compile. No Zap +/// Anonymous-user type facade. After Zaplex's localization there is no anonymous-user concept; the +/// enum is kept so that the match arms scattered across telemetry / settings still compile. No Zaplex /// code path ever constructs `Some(AnonymousUserType::...)`. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum AnonymousUserType { @@ -141,7 +141,7 @@ pub enum AnonymousUserType { WebClientAnonymousUser, } -/// Authentication principal-type facade. Under Zap this is always equivalent to `User`. +/// Authentication principal-type facade. Under Zaplex this is always equivalent to `User`. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum PrincipalType { #[default] @@ -149,7 +149,7 @@ pub enum PrincipalType { ServiceAccount, } -/// Personal object-limits facade (originally the anonymous-user Free Tier limits). Zap never +/// Personal object-limits facade (originally the anonymous-user Free Tier limits). Zaplex never /// constructs this value, but the struct is kept so consumers keep compiling. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub struct PersonalObjectLimits { @@ -194,7 +194,7 @@ impl User { self.metadata.display_name.clone() } - /// Test/default user placeholder. Zap uses this user on all paths. + /// Test/default user placeholder. Zaplex uses this user on all paths. pub fn test() -> Self { Self { local_id: UserUid::new(TEST_USER_UID), @@ -213,7 +213,7 @@ impl User { } } - /// Whether the user is anonymous. Zap always returns `false`. + /// Whether the user is anonymous. Zaplex always returns `false`. pub fn is_user_anonymous(&self) -> bool { false } @@ -282,17 +282,17 @@ impl AuthState { state } - /// Whether the user is logged in. Zap always returns `true`. + /// Whether the user is logged in. Zaplex always returns `true`. pub fn is_logged_in(&self) -> bool { true } - /// Whether anonymous or logged out. Zap always returns `false`. + /// Whether anonymous or logged out. Zaplex always returns `false`. pub fn is_anonymous_or_logged_out(&self) -> bool { false } - /// Returns the cached access token (ignoring validity). On the Zap path this only has a value + /// Returns the cached access token (ignoring validity). On the Zaplex path this only has a value /// when the user has a `Credentials::ApiKey` attached. pub fn get_access_token_ignoring_validity(&self) -> Option { self.credentials @@ -347,7 +347,7 @@ impl AuthState { Some(false) } - /// The Zap local user never hits the Free Tier limit. + /// The Zaplex local user never hits the Free Tier limit. pub fn is_anonymous_user_past_object_limit( &self, _object_type: crate::cloud_object::ObjectType, @@ -386,19 +386,19 @@ impl AuthState { self.user.read().as_ref().map(|user| user.local_id) } - /// Returns the nil UUID string. After Zap's localization, this ID no longer appears in any + /// Returns the nil UUID string. After Zaplex's localization, this ID no longer appears in any /// outgoing HTTP header; it only serves as a formal placeholder for the telemetry context / /// session header. pub fn anonymous_id(&self) -> String { Uuid::nil().to_string() } - /// Returns whether reauthentication is needed. Zap always returns `false`. + /// Returns whether reauthentication is needed. Zaplex always returns `false`. pub fn needs_reauth(&self) -> bool { false } - /// Returns whether the current user's anonymous renotification block has expired. Zap users are + /// Returns whether the current user's anonymous renotification block has expired. Zaplex users are /// not treated as anonymous, so this function returns `false` (the signup prompt never pops up). pub fn anonymous_user_renotification_block_expired( &self, @@ -474,7 +474,7 @@ impl AuthStateProvider { /// Constructs a "logged out" AuthState provider. /// - /// Zap no longer has a genuine logged-out state, so this function returns a "logged-in test + /// Zaplex no longer has a genuine logged-out state, so this function returns a "logged-in test /// user" provider equivalent to `new_for_test`, to keep legacy test code compiling. pub fn new_logged_out_for_test() -> Self { Self::new_for_test() @@ -499,11 +499,11 @@ pub type LoginGatedFeature = &'static str; /// The URL-construction callback for `AuthManager::open_url_maybe_with_anonymous_token`. /// /// In the original UI, this callback received the anonymous-user token and assembled an -/// "open browser, optionally carrying identity" URL. Under Zap there is no longer any anonymous +/// "open browser, optionally carrying identity" URL. Under Zaplex there is no longer any anonymous /// identity, so the callback is discarded. pub type AnonymousTokenUrlBuilder = Box) -> String>; -/// AuthView variant facade. Zap has physically deleted the AuthView UI; in the stub all dispatch +/// AuthView variant facade. Zaplex has physically deleted the AuthView UI; in the stub all dispatch /// points only produce a log, but the enum surface is kept so legacy `match` arms still compile. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AuthViewVariant { @@ -541,13 +541,13 @@ impl AuthView { self.variant = variant; } - /// Returns the current variant. Unused on the Zap path. + /// Returns the current variant. Unused on the Zaplex path. pub fn variant(&self) -> AuthViewVariant { self.variant } /// In the original native login UI, this skipped the "enter passcode" step and went straight to - /// the subsequent "open in browser" step. Zap: no-op. + /// the subsequent "open in browser" step. Zaplex: no-op. pub fn skip_to_browser_open_step(&mut self, _ctx: &mut ViewContext) {} } @@ -696,7 +696,7 @@ pub enum AuthManagerEvent { } /// User-authentication error facade. A few subscribers still match the variants, so the enum is -/// kept; Zap no longer triggers construction of any variant. +/// kept; Zaplex no longer triggers construction of any variant. #[derive(Debug, thiserror::Error)] pub enum UserAuthenticationError { #[error("Access token denied")] @@ -725,7 +725,7 @@ pub struct PersistedCurrentUserInformation { pub email: String, } -/// AuthManager facade. After Zap's localization all external-account / RPC entry points become +/// AuthManager facade. After Zaplex's localization all external-account / RPC entry points become /// no-ops, but `AuthManager` is still mounted in the App as a singleton model, so that /// `subscribe_to_model` / `handle(ctx).update(...)` calls need zero changes, while preserving the /// local identity / onboarded flag / logout-reset semantics. @@ -748,18 +748,18 @@ impl AuthManager { /// Refreshes the current user state. /// - /// Historically this performed a cloud token refresh; after Zap's localization the auth state + /// Historically this performed a cloud token refresh; after Zaplex's localization the auth state /// is already locally initialized at startup, so no external-account request is ever sent. pub fn refresh_user(&self, _ctx: &mut ModelContext) {} /// Actively logs out. /// - /// Zap no longer enters a "cloud logged-out" state; this only restores the local identity + /// Zaplex no longer enters a "cloud logged-out" state; this only restores the local identity /// snapshot to the default placeholder user, for reuse by call sites such as settings reset / /// session cleanup. pub(crate) fn log_out(&mut self, _ctx: &mut ModelContext) { self.auth_state.reset_local_defaults(); - log::debug!("AuthManager::log_out 已本地 reset: 已切换为本地占位用户态"); + log::debug!("AuthManager::log_out local reset: switched to local placeholder user state"); } /// Marks that reauthentication is needed. Localized: no-op. @@ -804,8 +804,8 @@ impl AuthManager { // ---------- URL-construction facade ---------- // // Before being physically deleted, the legacy UI (login_slide / paste_auth_token_modal / - // auth_view_modal) called these methods to populate the historical login prompt links; Zap no - // longer opens the Zap cloud login page. After the UI was physically deleted there are no + // auth_view_modal) called these methods to populate the historical login prompt links; Zaplex no + // longer opens the Zaplex cloud login page. After the UI was physically deleted there are no // callers, but the enum/trait may still be consumed reflectively, so the stubs are kept. pub fn sign_up_url(&self) -> String { @@ -848,7 +848,7 @@ impl SingletonEntity for AuthManager {} // ---------- module-wide init ---------- -/// init for the Zap local-identity facade (no-op). +/// init for the Zaplex local-identity facade (no-op). /// /// The submodules previously mounted in the original `init` -- `init` / `auth_view_body::init` / /// `auth_override_warning_body::init` / `login_slide::init` / `paste_auth_token_modal::init` -- diff --git a/app/src/autoupdate/channel_versions.rs b/app/src/autoupdate/channel_versions.rs index ba894a5815..ca7abde14f 100644 --- a/app/src/autoupdate/channel_versions.rs +++ b/app/src/autoupdate/channel_versions.rs @@ -5,14 +5,14 @@ use channel_versions::{ChannelChangelogs, ChannelVersion, ChannelVersions, Versi use crate::channel::ChannelState; -// Load channel versions only from local state. Zap no longer requests release-channel metadata from Zap or GCP. +// Load channel versions only from local state. Zaplex no longer requests release-channel metadata from Zaplex or GCP. pub async fn fetch_channel_versions( nonce: &str, client: &http_client::Client, include_changelogs: bool, is_daily: bool, ) -> Result { - if let Ok(path) = env::var("WARP_CHANNEL_VERSIONS_PATH") { + if let Ok(path) = env::var("ZAPLEX_CHANNEL_VERSIONS_PATH") { // Load channel versions from local filesystem. Used for testing both // autoupdate and changelog behavior. let path = shellexpand::tilde(&path); diff --git a/app/src/autoupdate/github.rs b/app/src/autoupdate/github.rs index c053f733d4..7253c9590d 100644 --- a/app/src/autoupdate/github.rs +++ b/app/src/autoupdate/github.rs @@ -1,4 +1,4 @@ -// openWarp(Channel::Oss) autoupdate uses GitHub Releases API, not Zap's official +// openWarp(Channel::Oss) autoupdate uses GitHub Releases API, not Zaplex's official // channel_versions / GCS. This module only handles "fetch latest release metadata" + "select asset by filename"; // actual download, save, and directory opening are handled by windows.rs / mac.rs. diff --git a/app/src/autoupdate/linux.rs b/app/src/autoupdate/linux.rs index 8a839ee2b2..c6adf90cc3 100644 --- a/app/src/autoupdate/linux.rs +++ b/app/src/autoupdate/linux.rs @@ -31,7 +31,7 @@ pub(super) async fn download_update_and_cleanup( .await } UpdateMethod::PackageManager(package_manager) => { - log::info!("Detected that Zap was installed using {package_manager:?}"); + log::info!("Detected that Zaplex was installed using {package_manager:?}"); Ok(DownloadReady::NeedsAuthorization) } } @@ -45,7 +45,7 @@ pub(super) fn apply_update() -> Result { UpdateMethod::Unknown => bail!("Cannot apply update for unknown update method!"), UpdateMethod::AppImage(_) => Ok(ReadyForRelaunch::Yes), UpdateMethod::PackageManager(package_manager) => bail!( - "Zap does not support package-manager autoupdate for {package_manager}; install the new release manually" + "Zaplex does not support package-manager autoupdate for {package_manager}; install the new release manually" ), } } @@ -185,7 +185,7 @@ mod appimage { .as_file_mut() .set_permissions(appimage_path.metadata()?.permissions())?; - // Move new AppImage over the one that launched the current Zap instance. + // Move new AppImage over the one that launched the current Zaplex instance. let new_appimage_path = new_appimage.into_temp_path(); let mv_status = command::r#async::Command::new("mv") .arg(new_appimage_path.as_os_str()) @@ -211,8 +211,8 @@ mod appimage { command.arg(warp_cli::finish_update_flag()); // When testing local channel version JSON, have the newly launched binary continue referencing the same file // to verify changelog display after auto-update. - if let Ok(path) = std::env::var("WARP_CHANNEL_VERSIONS_PATH") { - command.env("WARP_CHANNEL_VERSIONS_PATH", path); + if let Ok(path) = std::env::var("ZAPLEX_CHANNEL_VERSIONS_PATH") { + command.env("ZAPLEX_CHANNEL_VERSIONS_PATH", path); } log::info!("Relaunching warp for update..."); @@ -246,8 +246,8 @@ mod package_manager { command.arg(finish_update_flag); // When testing local channel version JSON, have the newly launched binary continue referencing the same file // to verify changelog display after auto-update. - if let Ok(path) = std::env::var("WARP_CHANNEL_VERSIONS_PATH") { - command.env("WARP_CHANNEL_VERSIONS_PATH", path); + if let Ok(path) = std::env::var("ZAPLEX_CHANNEL_VERSIONS_PATH") { + command.env("ZAPLEX_CHANNEL_VERSIONS_PATH", path); } log::info!("Relaunching warp for update..."); @@ -256,14 +256,14 @@ mod package_manager { } } -/// Returns which method should be used to update Zap. +/// Returns which method should be used to update Zaplex. #[derive(Debug)] pub(crate) enum UpdateMethod { - /// We don't know how to update Zap. + /// We don't know how to update Zaplex. Unknown, - /// Zap is running as an AppImage and should be updated in-place. + /// Zaplex is running as an AppImage and should be updated in-place. AppImage(PathBuf), - /// Zap can be updated using the given package manager. + /// Zaplex can be updated using the given package manager. PackageManager(PackageManager), } @@ -420,7 +420,7 @@ impl PackageManager { } /// Write the "command the user should run to upgrade" to logs. OSS users can find the exact command - /// in logs under ~/.local/share/dev.zap.Zap/; the UI still falls back to "go to GitHub to download", not distinguishing by package manager. + /// in logs under ~/.local/share/dev.zaplex.Zaplex/; the UI still falls back to "go to GitHub to download", not distinguishing by package manager. fn log_upgrade_hint(&self) { let hint = match self { Self::Apt { package_name } => { diff --git a/app/src/autoupdate/mac.rs b/app/src/autoupdate/mac.rs index 0d9e7b8623..bdf4d979e0 100644 --- a/app/src/autoupdate/mac.rs +++ b/app/src/autoupdate/mac.rs @@ -150,7 +150,7 @@ pub(super) fn relaunch() -> Result<()> { let bundle_path = PathBuf::from(get_bundle_path()?); - // Wait for the current process to exit before launching the new Zap version, preventing multiple icons + // Wait for the current process to exit before launching the new Zaplex version, preventing multiple icons // briefly appearing in the Dock. Use an intermediate shell process to poll the current PID and launch // the new version after it exits. // @@ -168,9 +168,9 @@ pub(super) fn relaunch() -> Result<()> { ); // When testing local channel version JSON, have the newly launched binary continue referencing the same file // to verify changelog display after auto-update. - if let Ok(path) = env::var("WARP_CHANNEL_VERSIONS_PATH") { + if let Ok(path) = env::var("ZAPLEX_CHANNEL_VERSIONS_PATH") { let quoted_path = shell_escape::escape(path.into()); - open_args.push_str(&format!(" --env WARP_CHANNEL_VERSIONS_PATH={quoted_path}")); + open_args.push_str(&format!(" --env ZAPLEX_CHANNEL_VERSIONS_PATH={quoted_path}")); } let relaunch_script = @@ -321,7 +321,7 @@ pub async fn cleanup_all_except(preserve_update_id: Option<&str>) { } } -/// Determines if the user needs authorization in order to update Zap. +/// Determines if the user needs authorization in order to update Zaplex. async fn needs_authorization(bundle_path: &Path) -> Result { // For the bundle path itself, check permissions without creating a test file so as to not // interfere with code signing. @@ -346,8 +346,8 @@ async fn needs_authorization(bundle_path: &Path) -> Result { } /// Determines if a directory is writable as part of an update. This means: -/// * Zap can create files in the directory -/// * Zap can modify the permissions of created files +/// * Zaplex can create files in the directory +/// * Zaplex can modify the permissions of created files async fn is_directory_writable(directory: &Path) -> Result { // Just because we have writability access does not mean we can set the correct owner/group. // Test if we can set the owner/group on a temporarily created file. If we can, then we can @@ -382,7 +382,7 @@ async fn is_directory_writable(directory: &Path) -> Result { } /// Verifies that the staged bundle path has a valid macOS code signature, and that its -/// team identifier matches Zap's team identifier. +/// team identifier matches Zaplex's team identifier. async fn verify_code_signature(component: &str, path: &Path) -> Result<()> { // Verify the signature of the staged update bundle with team identifier let codesign_verify_output = Command::new("/usr/bin/codesign") @@ -524,7 +524,7 @@ async fn apply_update(channel: Channel, version_info: &VersionInfo, update_id: & .await .is_ok() { - // If we performed this process already but didn't relaunch Zap, the old executable will + // If we performed this process already but didn't relaunch Zaplex, the old executable will // still be located in the user application data directory. In that case, leave it there. log::info!("Already autoupdated without relaunching; ignoring executable from old bundle"); } else { @@ -834,8 +834,8 @@ async fn mount_dmg(dmg_dir: &Path, update_id: &str) -> Result { hdiutil_cmd.args(["attach", "-mountpoint"]); hdiutil_cmd.arg(&volume); // Explanation of flags: - // -nobrowse: Do not show the Zap DMG in Finder or similar apps. - // -noautoopen: Do not open the Zap DMG in Finder. + // -nobrowse: Do not show the Zaplex DMG in Finder or similar apps. + // -noautoopen: Do not open the Zaplex DMG in Finder. // -readonly: For safety, we mount read-only since there's no need to modify the new app version. // -autofsck: Ensure that the DMG contents are verified. This is on by default for quarantined images, but macOS // doesn't necessarily recognize our download as such. @@ -895,7 +895,7 @@ fn dmg_name(channel: Channel) -> String { .is_ok_and(|output| output.stdout.starts_with(b"arm64")); // openWarp GitHub Release asset names are fixed as `Zap-arm64.dmg` / `Zap-intel.dmg` - // (naming convention from .github/workflows), which differs from `app_name_prefix("zap-oss")`. + // (naming convention from .github/workflows), which differs from `app_name_prefix("zaplex")`. // Here we hardcode only for OSS, which doesn't affect the universal naming for official channels. if matches!(channel, Channel::Oss) { return if is_arm64 { @@ -915,12 +915,12 @@ fn dmg_name(channel: Channel) -> String { fn app_name_prefix(channel: Channel) -> &'static str { match channel { - Channel::Stable => "Zap", + Channel::Stable => "Zaplex", Channel::Preview => "WarpPreview", Channel::Local => "warp", Channel::Integration => "integration", Channel::Dev => "WarpDev", - Channel::Oss => "zap-oss", + Channel::Oss => "zaplex", } } @@ -931,7 +931,7 @@ fn executable_name(channel: Channel) -> &'static str { Channel::Local => "warp", Channel::Integration => "integration", Channel::Dev => "dev", - Channel::Oss => "zap-oss", + Channel::Oss => "zaplex", } } diff --git a/app/src/autoupdate/mod.rs b/app/src/autoupdate/mod.rs index a834b1188c..33ae377f3c 100644 --- a/app/src/autoupdate/mod.rs +++ b/app/src/autoupdate/mod.rs @@ -121,7 +121,7 @@ pub enum AutoupdateStage { }, /// A relaunch was initiated to use the new version, but failed. UnableToLaunchNewVersion { new_version: VersionInfo }, - /// A new version was installed, but Zap hasn't restarted yet. + /// A new version was installed, but Zaplex hasn't restarted yet. /// /// This state is only used on macOS, where the update isn't fully applied until right before /// restarting. @@ -384,7 +384,7 @@ impl AutoupdateState { // `trim_start_matches('v')` to remove the `v` (e.g., `2026.05.10.preview`). Directly doing // string equality comparison would always be false, causing "already up to date" to be // misidentified as "new version found". Here we perform idempotent prefix normalization: - // - Official Zap: tag always has `v`, both sides still equal after trim, behavior unchanged. + // - Official Zaplex: tag always has `v`, both sides still equal after trim, behavior unchanged. // - openWarp: only equal after trim, correctly identifies same version. if version.version.trim_start_matches('v') == current_version.trim_start_matches('v') { log::info!("Already up to date with {}", version.version); @@ -846,7 +846,7 @@ pub fn accessibility_content( // Found autoupdate (RequestType::ManualCheck, Ok(UpdateReady::Yes { .. })) => Some(AccessibilityContent::new( "Update available.", - "Use the command palette to install and relaunch Zap", + "Use the command palette to install and relaunch Zaplex", WarpA11yRole::HelpRole, )), // Any non-successful autoupdate check @@ -884,7 +884,7 @@ async fn fetch_version( update_id: &str, http_client: Arc, ) -> Result { - // openWarp uses GitHub Releases (zerx-lab/warp), completely bypassing Zap official + // openWarp uses GitHub Releases (zerx-lab/warp), completely bypassing Zaplex official // channel_versions / GCS. Return early to avoid fetch_channel_versions failing downstream. if matches!(channel, Channel::Oss) { let release = github::fetch_latest_release(http_client.as_ref()).await?; @@ -978,7 +978,7 @@ pub fn apply_update( } } -/// Relaunch Zap to apply an update. +/// Relaunch Zaplex to apply an update. /// /// This will: /// 1. Perform any last update steps. diff --git a/app/src/autoupdate/windows.rs b/app/src/autoupdate/windows.rs index d7f9592704..c300285dfe 100644 --- a/app/src/autoupdate/windows.rs +++ b/app/src/autoupdate/windows.rs @@ -24,7 +24,7 @@ lazy_static! { static ref INSTALLER_PATH: Arc>> = Default::default(); } -/// Download the Inno Setup install wizard, the same one users run on the first Zap install, and +/// Download the Inno Setup install wizard, the same one users run on the first Zaplex install, and /// place it into the "data dir". pub(super) async fn download_update_and_cleanup( version_info: &VersionInfo, @@ -38,8 +38,8 @@ pub(super) async fn download_update_and_cleanup( let channel = ChannelState::channel(); let installer_file_name = installer_file_name()?; - // openWarp: fetch actual download URL from GitHub Release cache (asset name is ZapSetup.exe / - // ZapSetup-arm64.exe, see installer_file_name()). Other channels use official base url. + // openWarp: fetch actual download URL from GitHub Release cache (asset name is ZaplexSetup.exe / + // ZaplexSetup-arm64.exe, see installer_file_name()). Other channels use official base url. let url = if matches!(channel, Channel::Oss) { if let Some(release) = github::cached_release() { if let Some(found) = release.find_asset(&installer_file_name) { @@ -283,7 +283,7 @@ pub(super) fn relaunch() -> Result<()> { // the standard installation UI, can visually confirm the version to install, destination directory, // and cancel via normal UI. Still keep /SP- to skip "ready to install" confirmation dialog; // /NORESTART avoids requesting Windows restart; /update=1 tells Inno script to detect upgrade mode. - // /NOCLOSEAPPLICATIONS lets Inno wait for the current Zap process to exit naturally (mutex poll) + // /NOCLOSEAPPLICATIONS lets Inno wait for the current Zaplex process to exit naturally (mutex poll) // instead of forcibly killing it via RestartManager. let mut cmd = Command::new(&installer_path); if matches!(channel, Channel::Oss) { @@ -297,7 +297,7 @@ pub(super) fn relaunch() -> Result<()> { ]); } else { // Official channel: maintain original "silent + progress bar" behavior, auto-install and restart. - // The Inno Setup install wizard will run without user input. It will re-launch Zap after + // The Inno Setup install wizard will run without user input. It will re-launch Zaplex after // installing the update files. // https://jrsoftware.org/ishelp/index.php?topic=setupcmdline cmd.args([ @@ -313,8 +313,8 @@ pub(super) fn relaunch() -> Result<()> { "/NORESTART", &log_arg, "/update=1", - // Do not forcibly kill Zap via RestartManager. The installer will wait for - // Zap to exit naturally by polling the single-instance mutex instead. + // Do not forcibly kill Zaplex via RestartManager. The installer will wait for + // Zaplex to exit naturally by polling the single-instance mutex instead. "/NOCLOSEAPPLICATIONS", &format!("/DIR={}", install_dir.display()), ]); @@ -350,13 +350,13 @@ fn installer_file_name() -> Result { fn app_name_prefix(channel: Channel) -> &'static str { match channel { - Channel::Stable => "Zap", + Channel::Stable => "Zaplex", Channel::Preview => "WarpPreview", Channel::Local => "warp", Channel::Integration => "integration", Channel::Dev => "WarpDev", - // Align with script/windows/bundle.ps1 OSS branch INSTALLER_NAME=Zap+Setup, - // so GitHub Release asset name ZapSetup.exe can be correctly generated by installer_file_name(). - Channel::Oss => "Zap", + // Align with script/windows/bundle.ps1 OSS branch INSTALLER_NAME=Zaplex+Setup, + // so GitHub Release asset name ZaplexSetup.exe can be correctly generated by installer_file_name(). + Channel::Oss => "Zaplex", } } diff --git a/app/src/banner/view.rs b/app/src/banner/view.rs index 385f2eba9d..1aab25a249 100644 --- a/app/src/banner/view.rs +++ b/app/src/banner/view.rs @@ -33,7 +33,7 @@ pub enum DismissalType { Temporary, /// The banner should not be shown again to the user, whether in a new or existing session. - /// Dismissal state should also persist across app sessions (e.g. when Zap is restarted). + /// Dismissal state should also persist across app sessions (e.g. when Zaplex is restarted). Permanent, } diff --git a/app/src/bin/zap_oss.rs b/app/src/bin/zaplex.rs similarity index 83% rename from app/src/bin/zap_oss.rs rename to app/src/bin/zaplex.rs index 10b697500d..8fd0234891 100644 --- a/app/src/bin/zap_oss.rs +++ b/app/src/bin/zaplex.rs @@ -21,13 +21,13 @@ pub static NvOptimusEnablement: u32 = 1; #[used] pub static AmdPowerXpressRequestHighPerformance: u32 = 1; -// Zap OSS build entry point, simple wrapper around warp::run(). +// Zaplex OSS build entry point, simple wrapper around warp::run(). fn main() -> Result<()> { let mut state = ChannelState::new( Channel::Oss, ChannelConfig { - app_id: AppId::new("dev", "zap", "Zap"), - logfile_name: "zap.log".into(), + app_id: AppId::new("dev", "zaplex", "Zaplex"), + logfile_name: "zaplex.log".into(), autoupdate_config: None, mcp_static_config: None, }, @@ -36,7 +36,7 @@ fn main() -> Result<()> { state = state.with_additional_features(DEBUG_FLAGS); } // Always enable IME marked-text rendering: winit's IME path is supported on both macOS / Windows, - // but if not explicitly enabled here, Zap will discard preedit / input composition updates entirely, leaving only OS candidate window + // but if not explicitly enabled here, Zaplex will discard preedit / input composition updates entirely, leaving only OS candidate window // visible — on Windows, this constitutes significant breakage for Japanese / Chinese / Korean input. #[cfg(any(target_os = "macos", target_os = "windows"))] { @@ -58,11 +58,11 @@ embed_plist::embed_info_plist_bytes!(r#" CFBundleDevelopmentRegion English CFBundleDisplayName - Zap + Zaplex CFBundleExecutable - zap-oss + zaplex CFBundleIdentifier - dev.zap.Zap + dev.zaplex.Zaplex CFBundleInfoDictionaryVersion 6.0 CFBundleLocalizations @@ -70,7 +70,7 @@ embed_plist::embed_info_plist_bytes!(r#" en CFBundleName - Zap + Zaplex CFBundlePackageType APPL CFBundleShortVersionString @@ -82,9 +82,9 @@ embed_plist::embed_info_plist_bytes!(r#" UIDesignRequiresCompatibility CFBundleURLTypes - CFBundleURLNameCustom AppCFBundleURLSchemeszap + CFBundleURLNameCustom AppCFBundleURLSchemeszaplex NSHumanReadableCopyright - © 2026, Zap + © 2026, Zaplex "#.as_bytes()); diff --git a/app/src/cloud_object/mod.rs b/app/src/cloud_object/mod.rs index 21019833a5..5a24311ab8 100644 --- a/app/src/cloud_object/mod.rs +++ b/app/src/cloud_object/mod.rs @@ -1,10 +1,10 @@ -//! # Zap Localization Notes (Phase 2d-4b, 2026-05-11) +//! # Zaplex Localization Notes (Phase 2d-4b, 2026-05-11) //! -//! This module previously handled "cloud object" abstractions upstream in Zap, unifying descriptions +//! This module previously handled "cloud object" abstractions upstream in Zaplex, unifying descriptions //! of Notebook / Workflow / EnvVar / Fact / MCP / ExecutionProfile / AIDocument and other object types //! that need to sync across devices. //! -//! The cloud sync pipeline in Zap (RTC / UpdateManager / SyncQueue / ServerApiProvider) has been removed +//! The cloud sync pipeline in Zaplex (RTC / UpdateManager / SyncQueue / ServerApiProvider) has been removed //! (see `docs/zap-cloud-removal-plan.md`), and this module is now a pure local object abstraction: //! //! - `StoredObject` trait → actual semantics are "local domain object trait", carrying metadata / permissions / @@ -30,7 +30,7 @@ use crate::{ auth::UserUid, channel::ChannelState, drive::{ - items::WarpDriveItem, ObjectTypeAndId, ZapDriveObjectArgs, ZapDriveObjectSettings, + items::WarpDriveItem, ObjectTypeAndId, ZaplexDriveObjectArgs, ZaplexDriveObjectSettings, }, persistence::ModelEvent, server::ids::{ClientId, HashableId, HashedSqliteId, ObjectUid, ServerId, SyncId, ToServerId}, @@ -64,7 +64,7 @@ pub use server_types::*; /// Newtype wrapping a serialized model string. /// -/// Zap (Wave 4): originally defined in `crate::server::sync_queue`; after SyncQueue was entirely removed, +/// Zaplex (Wave 4): originally defined in `crate::server::sync_queue`; after SyncQueue was entirely removed, /// it was moved here. Multiple models' `serialized()` still returns it (used when writing to local sqlite). #[derive(Debug, Clone, PartialEq, Eq)] pub struct SerializedModel(String); @@ -193,8 +193,8 @@ pub trait StoredObject: Debug { true } - /// Creates a new Zap Drive item for this object. Returns None if this - /// object is not rendered in Zap Drive. + /// Creates a new Zaplex Drive item for this object. Returns None if this + /// object is not rendered in Zaplex Drive. fn to_warp_drive_item(&self, appearance: &Appearance) -> Option>; /// Returns the web link of this object. Will return none if we do not support web links @@ -480,7 +480,7 @@ pub trait StoredObjectModel: Debug + Clone + Send + Sync { } /// Creates a new zap drive item for this model type. Returns None - /// if this object does not render in Zap Drive. + /// if this object does not render in Zaplex Drive. fn to_warp_drive_item( &self, id: SyncId, @@ -488,10 +488,10 @@ pub trait StoredObjectModel: Debug + Clone + Send + Sync { object: &Self::StoredObjectType, ) -> Option>; - /// Returns the display name for this model (e.g. to show in the Zap Drive index) + /// Returns the display name for this model (e.g. to show in the Zaplex Drive index) fn display_name(&self) -> String; - /// Sets the display name to show in the Zap Drive Index. Setting the name + /// Sets the display name to show in the Zaplex Drive Index. Setting the name /// is not currently supported by all object types, hence the default empty /// implementation. fn set_display_name(&mut self, _name: &str) {} @@ -824,11 +824,11 @@ where } /// Extracts the server id and object type from a (caller validated) Drive link. -/// Intended use is deriving metadata from links such that Zap objects -/// can be opened natively in Zap with no web interaction. +/// Intended use is deriving metadata from links such that Zaplex objects +/// can be opened natively in Zaplex with no web interaction. pub fn extract_server_id_and_object_type_from_warp_drive_link( url: &Url, -) -> Option { +) -> Option { let server_id = url .path_segments() .and_then(|mut segments| segments.next_back()) @@ -851,13 +851,13 @@ pub fn extract_server_id_and_object_type_from_warp_drive_link( let invitee_email: Option = query_string.get("invitee_email").map(|s| s.to_string()); - Some(ZapDriveObjectArgs { + Some(ZaplexDriveObjectArgs { object_type, server_id: match server_id { Some(server_id) => server_id.try_into().ok()?, _ => return None, }, - settings: ZapDriveObjectSettings { + settings: ZaplexDriveObjectSettings { focused_folder_id, invitee_email, }, diff --git a/app/src/cloud_object/model/generic_string_model.rs b/app/src/cloud_object/model/generic_string_model.rs index cf4066d5a7..ac6e2a5554 100644 --- a/app/src/cloud_object/model/generic_string_model.rs +++ b/app/src/cloud_object/model/generic_string_model.rs @@ -79,7 +79,7 @@ pub trait StringModel: Clone + Debug + PartialEq + Send + Sync + 'static { fn set_display_name(&mut self, _name: &str) {} /// Creates a new zap drive item for this model type. Returns None - /// if this object does not render in Zap Drive. + /// if this object does not render in Zaplex Drive. fn to_warp_drive_item( &self, _id: SyncId, diff --git a/app/src/cloud_object/model/model_test.rs b/app/src/cloud_object/model/model_test.rs index ddcd155191..a349546e77 100644 --- a/app/src/cloud_object/model/model_test.rs +++ b/app/src/cloud_object/model/model_test.rs @@ -169,7 +169,7 @@ fn folder_from_object_store_model(model: &ObjectStoreModel, id: SyncId) -> &Fold /// Mock receiving an RTC update. These tests update objects by mocking RTC messages so that they /// don't need to mock the server API for updates. The unit tests for [`UpdateManager`] ensure that /// updates from both RTC and client actions emit the same events. -// Zap (localization, Phase 2d-4a-1): the RTC entry point `received_message_from_server` was +// Zaplex (localization, Phase 2d-4a-1): the RTC entry point `received_message_from_server` was // physically deleted along with `Listener`. The following 4 folder sorting-timestamp tests // (test_update_folder_timestamp_from_*) that depended on the `receive_rtc_update` / `move_object` // helpers were deleted along with those helpers; on the local-write path, metadata updates are diff --git a/app/src/cloud_object/model/persistence.rs b/app/src/cloud_object/model/persistence.rs index 6dcdd7dca6..1ba45610f0 100644 --- a/app/src/cloud_object/model/persistence.rs +++ b/app/src/cloud_object/model/persistence.rs @@ -100,7 +100,7 @@ enum FolderOpenState { Reversed, } -/// Persistence model for [StoredObject] information. In Zap, it corresponds to the local object store in SQLite. +/// Persistence model for [StoredObject] information. In Zaplex, it corresponds to the local object store in SQLite. /// Logic beyond basic update/query should be placed in [ObjectStoreViewModel] and covered in model_test.rs. pub struct ObjectStoreModel { objects_by_id: HashMap>, @@ -121,7 +121,7 @@ impl ObjectStoreModel { .map(|object| (object.uid().to_owned(), object)) .collect::>>(); let initial_load_complete = Condition::new(); - // Zap has no initial cloud object fetch; it becomes readable once SQLite restore completes. + // Zaplex has no initial cloud object fetch; it becomes readable once SQLite restore completes. initial_load_complete.set(); Self { @@ -132,7 +132,7 @@ impl ObjectStoreModel { } } - /// Wait for local object store to become readable. In Zap, this condition is satisfied immediately after SQLite restore. + /// Wait for local object store to become readable. In Zaplex, this condition is satisfied immediately after SQLite restore. pub fn initial_load_complete(&self) -> impl Future { self.initial_load_complete.wait() } @@ -728,7 +728,7 @@ impl ObjectStoreModel { .count() } - /// Number of local objects that have entered an error state and will appear in the Zap Drive index. + /// Number of local objects that have entered an error state and will appear in the Zaplex Drive index. pub fn num_visible_errored_objects(&self) -> usize { self.objects_by_id .values() diff --git a/app/src/cloud_object/model/view.rs b/app/src/cloud_object/model/view.rs index a2064fbc02..ee3a3ccfdf 100644 --- a/app/src/cloud_object/model/view.rs +++ b/app/src/cloud_object/model/view.rs @@ -165,7 +165,7 @@ impl ObjectStoreViewModel { .map(|object| object.space(app)) } - /// Get the current user's access level on a Zap Drive object. + /// Get the current user's access level on a Zaplex Drive object. /// /// This is based on the client's current view of the object permissions, which may be stale. The /// server is the source of truth for all permission data, and it may reject a request that the @@ -215,7 +215,7 @@ impl ObjectStoreViewModel { } } - /// Get the current user's editability state for a Zap Drive object. + /// Get the current user's editability state for a Zaplex Drive object. pub fn object_editability( &self, object_uid: &ObjectUid, diff --git a/app/src/cloud_object/update_manager.rs b/app/src/cloud_object/update_manager.rs index bab4974e95..836ada6554 100644 --- a/app/src/cloud_object/update_manager.rs +++ b/app/src/cloud_object/update_manager.rs @@ -208,14 +208,14 @@ impl UpdateManager { object_type_and_id: &ObjectTypeAndId, ctx: &mut ModelContext, ) { - // Zap(Wave 4): resync originally meant “re-enqueue to SyncQueue to push local changes to the server”. + // Zaplex(Wave 4): resync originally meant “re-enqueue to SyncQueue to push local changes to the server”. // After localization, it is now a one-way SQLite write; callers only need lightweight validation. let _ = (object_type_and_id, ctx); } /// Out-of-band (from the regular poll) refresh of updated objects. pub fn refresh_updated_objects(&mut self, ctx: &mut ModelContext) { - // Zap localization: no cloud object polling source yet. + // Zaplex localization: no cloud object polling source yet. // This method is preserved for backward compatibility with old call sites; does not trigger network I/O. let _ = ctx; } @@ -246,7 +246,7 @@ impl UpdateManager { } } - /// Zap localization: no longer fetches individual cloud objects; signature preserved for backward compatibility. + /// Zaplex localization: no longer fetches individual cloud objects; signature preserved for backward compatibility. /// /// Returns A `Receiver<()>` that completes when the fetch operation is done. /// This receiver can be used to wait for the fetch operation to complete before proceeding. @@ -259,7 +259,7 @@ impl UpdateManager { let _ = fetch_single_object_option; let _ = ctx; let (fetch_cloud_object_tx, fetch_cloud_object_rx) = oneshot::channel::<()>(); - log::debug!("Zap skipping single cloud object fetch: {server_id:?}"); + log::debug!("Zaplex skipping single cloud object fetch: {server_id:?}"); let _ = fetch_cloud_object_tx.send(()); fetch_cloud_object_rx } @@ -424,7 +424,7 @@ impl UpdateManager { _current_metadata_last_updated_ts: Option, ctx: &mut ModelContext, ) { - // Zap: cloud-side move RPC has been removed; this is now collapsed to local direct write and clear + // Zaplex: cloud-side move RPC has been removed; this is now collapsed to local direct write and clear // the has_pending_metadata_change flag. let _ = (object_type, owner, destination_folder); ObjectStoreModel::handle(ctx).update(ctx, |object_store_model, ctx| { @@ -450,7 +450,7 @@ impl UpdateManager { /// Attempts to move the object identified by `object_id` /// to the root of the drive identified by `destination_owner`. - /// Zap(Wave 6-7): The remote leg originally called `transfer_*_owner` stub series, always returning `Ok(true)`, + /// Zaplex(Wave 6-7): The remote leg originally called `transfer_*_owner` stub series, always returning `Ok(true)`, /// then after a round-trip cleared has_pending_permissions_change and emitted a Success toast. /// This is now collapsed to local direct write. `move_object_to_drive_failed` / `revert_workflow_on_failed_move` are retired. #[allow(clippy::too_many_arguments)] @@ -1023,7 +1023,7 @@ impl UpdateManager { /// Create a new local stored object using the given model. /// - /// Zap(localization): Same as `update_object` — the original implementation enqueued to `SyncQueue` and waited + /// Zaplex(localization): Same as `update_object` — the original implementation enqueued to `SyncQueue` and waited /// for server creation ack. After localization, only in-memory object creation + SQLite write is retained. /// The object persists under its client_id identity and is never promoted to server_id. /// Parameters `entrypoint` / `initiated_by` are kept for interface stability. @@ -1050,7 +1050,7 @@ impl UpdateManager { + 'static, M: StoredObjectModel> + 'static, { - // Zap: the cloud-enqueue leg has been removed; these two parameters are retained only for + // Zaplex: the cloud-enqueue leg has been removed; these two parameters are retained only for // `create_object_queue_item` construction and interface stability to avoid breaking 30+ call sites. let _ = entrypoint; let _ = initiated_by; @@ -1083,7 +1083,7 @@ impl UpdateManager { /// Update a local stored object using a new model. /// - /// Zap(localization): No cloud = no server ack. Original implementation: update in-memory → mark `InFlight` → + /// Zaplex(localization): No cloud = no server ack. Original implementation: update in-memory → mark `InFlight` → /// write SQLite → enqueue to `SyncQueue` (wait for server response before decrementing `InFlight`). /// After localization, the two cloud-side legs are removed; only retained: update in-memory + write SQLite. /// The object's sync_status remains at initial `NoLocalChanges` (local write = "complete" semantics). @@ -1107,7 +1107,7 @@ impl UpdateManager { + 'static, M: StoredObjectModel> + 'static, { - let _ = revision_ts; // Zap: no server-side revision coordination; ignored. + let _ = revision_ts; // Zaplex: no server-side revision coordination; ignored. // Update in-memory model. ObjectStoreModel::handle(ctx).update(ctx, |object_store_model, ctx| { @@ -1148,7 +1148,7 @@ impl UpdateManager { // Update sqlite. self.save_to_db([ModelEvent::InsertObjectAction { object_action }]); - // Zap(Wave 4): The original implementation enqueued to SyncQueue at the end to report RecordObjectAction. + // Zaplex(Wave 4): The original implementation enqueued to SyncQueue at the end to report RecordObjectAction. // After SyncQueue removal, a local SQLite record is “complete”. let _ = (id_and_type, action_type, data, action_timestamp); } @@ -1209,7 +1209,7 @@ impl UpdateManager { }); } - /// Zap: Cloud-side notebook edit lease has been removed. This is now collapsed to local edit permission grant; + /// Zaplex: Cloud-side notebook edit lease has been removed. This is now collapsed to local edit permission grant; /// method signature is kept for `notebooks/notebook.rs` call sites. pub fn grab_notebook_edit_access( &mut self, @@ -1225,7 +1225,7 @@ impl UpdateManager { self.set_notebook_current_editor(¬ebook_id, Some(TEST_USER_UID.to_string()), ctx); } - /// Zap: Cloud-side notebook edit lease has been removed; this is now collapsed to local direct clearing of edit permission. + /// Zaplex: Cloud-side notebook edit lease has been removed; this is now collapsed to local direct clearing of edit permission. pub fn give_up_notebook_edit_access( &mut self, notebook_id: SyncId, @@ -1284,7 +1284,7 @@ impl UpdateManager { } pub fn trash_object(&mut self, id: ObjectTypeAndId, ctx: &mut ModelContext) { - // Zap(decentralized branch): Local objects (without server_id) follow pure local trash — + // Zaplex(decentralized branch): Local objects (without server_id) follow pure local trash — // mark trashed_ts + write SQLite. **Do NOT emit ObjectOperationComplete**, // because multiple consumers (notebooks/env_vars/cloud_object/view) .expect server_id; // Drive UI has already received notification via ObjectStoreEvent::ObjectTrashed @@ -1292,7 +1292,7 @@ impl UpdateManager { let Some(server_id) = id.server_id() else { let hashed_id = id.uid(); self.mark_object_trashed_and_return_timestamps(&hashed_id, ctx); - // Zap: Local objects never have a server ack to clear has_pending_metadata_change. + // Zaplex: Local objects never have a server ack to clear has_pending_metadata_change. // Must be manually cleared before writing to SQLite; otherwise, the // `if !has_pending_metadata_change` branch in upsert_stored_object will skip writing the trashed_ts field, // causing trashed_ts to be NULL when loaded from SQLite after restart, and the object reappears in PERSONAL. @@ -1355,11 +1355,11 @@ impl UpdateManager { } pub fn untrash_object(&mut self, id: ObjectTypeAndId, ctx: &mut ModelContext) { - // Zap: Local object untrash — clear trashed_ts + emit ObjectUntrashed + write SQLite. + // Zaplex: Local object untrash — clear trashed_ts + emit ObjectUntrashed + write SQLite. // Do NOT emit ObjectOperationComplete (same rationale as trash_object comment). let Some(server_id) = id.server_id() else { let hashed_id = id.uid(); - // Zap: Local object untrash — clear trashed_ts and also clear + // Zaplex: Local object untrash — clear trashed_ts and also clear // has_pending_metadata_change (local branch has no server ack); // otherwise upsert_stored_object skips writing trashed_ts and SQLite retains the old value. ObjectStoreModel::handle(ctx).update(ctx, |object_store_model, ctx| { @@ -1398,7 +1398,7 @@ impl UpdateManager { return; } - // Zap: Cloud-side untrash RPC has been removed; this is collapsed to local direct write and clearing the pending_untrash flag. + // Zaplex: Cloud-side untrash RPC has been removed; this is collapsed to local direct write and clearing the pending_untrash flag. ObjectStoreModel::handle(ctx).update(ctx, |object_store_model, ctx| { if let Some(object) = object_store_model.get_mut_by_uid(&hashed_id) { object.metadata_mut().trashed_ts = None; @@ -1466,7 +1466,7 @@ impl UpdateManager { return; } - // Zap: Cloud-side delete RPC has been removed; this is collapsed to local direct deletion. + // Zaplex: Cloud-side delete RPC has been removed; this is collapsed to local direct deletion. let num_deleted_objects = self.on_object_delete_success(vec![SyncId::ServerId(server_id)], ctx); ctx.emit(UpdateManagerEvent::ObjectOperationComplete { @@ -1482,7 +1482,7 @@ impl UpdateManager { } pub fn empty_trash(&mut self, space: Space, ctx: &mut ModelContext) { - // Zap: Empty Trash follows a pure local path. The original implementation called the upstream + // Zaplex: Empty Trash follows a pure local path. The original implementation called the upstream // cloud empty_trash interface; without auth/server, it would fail with `Failed to get access token` // after 3 retries, leaving Trash UI unresponsive. Local branch: directly iterate ObjectStoreModel // to find objects matching owner + is_trashed, collect SyncIds, then reuse `on_object_delete_success` @@ -1618,6 +1618,6 @@ impl Entity for UpdateManager { impl SingletonEntity for UpdateManager {} // Phase 2c-2: Delete `update_manager_test.rs` (7500+ lines of cloud-side sync behavior tests): -// After Zap localization of `update_object`, all cloud-side assertions become invalid; this file +// After Zaplex localization of `update_object`, all cloud-side assertions become invalid; this file // originally belonged to Phase 2d-4a's full-file deletion scope. Deleting early avoids accumulating 12 `#[ignore]` markers. // Remaining consumers in `server/cloud_objects/` (listener / update_manager itself) go offline in 2d-4a. diff --git a/app/src/cockpit/mod.rs b/app/src/cockpit/mod.rs new file mode 100644 index 0000000000..d375b80104 --- /dev/null +++ b/app/src/cockpit/mod.rs @@ -0,0 +1,13 @@ +//! Cockpit app integration: the `CockpitModel` singleton + file-watch/reconcile +//! wiring over the pure `zaplex_cockpit` data spine, plus scalar settings. +//! +//! Increment 1: data only (no UI). The account cards / heat bars / cost UI that +//! subscribe to `CockpitEvent::Updated` land in Increment 2 (`app/src/cockpit/…`). + +pub mod model; +pub mod panel; +pub mod settings; + +pub use model::CockpitModel; +pub use panel::CockpitPanel; +pub use settings::CockpitSettings; diff --git a/app/src/cockpit/model.rs b/app/src/cockpit/model.rs new file mode 100644 index 0000000000..0fb2e1db1d --- /dev/null +++ b/app/src/cockpit/model.rs @@ -0,0 +1,142 @@ +//! `CockpitModel` — the singleton that holds the latest [`CockpitSnapshot`] and keeps +//! it fresh, emitting [`CockpitEvent::Updated`] on change. +//! +//! Refresh is driven by two sources (mirrors `file_mcp_watcher` + the daemon GC +//! timer): +//! - [`HomeDirectoryWatcher`] (top-level home changes) → catches account add/remove +//! (`~/.claude.json`, `~/.claude`, `~/.codex`). +//! - a periodic **reconcile tick** → catches usage growth (transcripts append deep in +//! `projects/**` / `sessions/**`, which the non-recursive home watcher never sees) +//! and window/reset rollover. +//! +//! The (blocking) disk scan runs on the background executor; results are applied back +//! on the model's thread via the spawner round-trip. + +use std::path::PathBuf; +use std::time::Duration; + +use chrono::Utc; +use warpui::{Entity, ModelContext, SingletonEntity}; +use watcher::HomeDirectoryWatcher; +use zaplex_cockpit::{build_snapshot, CockpitSnapshot, PricingTable, DEFAULT_BUDGET_5H}; + +use crate::cockpit::settings::CockpitSettings; + +/// How often to re-scan transcripts even when no top-level home change fired. +const RECONCILE_INTERVAL: Duration = Duration::from_secs(45); + +/// Emitted whenever the snapshot changes. +pub enum CockpitEvent { + Updated, +} + +pub struct CockpitModel { + snapshot: CockpitSnapshot, + pricing: PricingTable, +} + +/// Inputs captured on the model thread, moved into the off-thread build. +struct RefreshInputs { + home: PathBuf, + codex_home: PathBuf, + claude_config_dir_env: Option, + budget_5h: u64, + pricing: PricingTable, +} + +impl CockpitModel { + pub fn new(ctx: &mut ModelContext) -> Self { + // Account add/remove (top-level home entries). + ctx.subscribe_to_model(&HomeDirectoryWatcher::handle(ctx), |me, _event, ctx| { + me.spawn_refresh(ctx); + }); + + let model = Self { + snapshot: CockpitSnapshot { + accounts: Vec::new(), + generated_at: Utc::now(), + }, + pricing: PricingTable::default(), + }; + model.spawn_refresh(ctx); + model.start_reconcile_timer(ctx); + model + } + + /// The latest snapshot (empty until the first background scan completes). + pub fn snapshot(&self) -> &CockpitSnapshot { + &self.snapshot + } + + /// Gather the inputs for a build, or `None` if the cockpit is disabled or the home + /// directory is unavailable (in which case no refresh runs). + fn refresh_inputs(&self, ctx: &mut ModelContext) -> Option { + if !*CockpitSettings::as_ref(ctx).enabled { + return None; + } + let home = dirs::home_dir()?; + let budget_override = *CockpitSettings::as_ref(ctx).budget_5h as u64; + let budget_5h = if budget_override > 0 { + budget_override + } else { + DEFAULT_BUDGET_5H + }; + Some(RefreshInputs { + codex_home: home.join(".codex"), + claude_config_dir_env: std::env::var("CLAUDE_CONFIG_DIR").ok(), + home, + budget_5h, + pricing: self.pricing.clone(), + }) + } + + /// Kick off a background disk scan; applies the result on the model thread. + fn spawn_refresh(&self, ctx: &mut ModelContext) { + let Some(inputs) = self.refresh_inputs(ctx) else { + return; + }; + let spawner = ctx.spawner(); + ctx.background_executor() + .spawn(async move { + let snapshot = build_snapshot( + &inputs.home, + &inputs.codex_home, + inputs.claude_config_dir_env.as_deref(), + Utc::now(), + inputs.budget_5h, + &inputs.pricing, + ); + let _ = spawner + .spawn(move |me, ctx| me.apply(snapshot, ctx)) + .await; + }) + .detach(); + } + + fn apply(&mut self, snapshot: CockpitSnapshot, ctx: &mut ModelContext) { + self.snapshot = snapshot; + ctx.emit(CockpitEvent::Updated); + } + + /// Periodic reconcile: re-scan on a fixed interval for the model's lifetime. + fn start_reconcile_timer(&self, ctx: &mut ModelContext) { + let spawner = ctx.spawner(); + ctx.background_executor() + .spawn(async move { + loop { + async_io::Timer::after(RECONCILE_INTERVAL).await; + let outcome = spawner.spawn(|me, ctx| me.spawn_refresh(ctx)).await; + if outcome.is_err() { + break; // model dropped + } + } + }) + .detach(); + } +} + +impl Entity for CockpitModel { + type Event = CockpitEvent; +} + +impl SingletonEntity for CockpitModel {} diff --git a/app/src/cockpit/panel.rs b/app/src/cockpit/panel.rs new file mode 100644 index 0000000000..3acaf3eb62 --- /dev/null +++ b/app/src/cockpit/panel.rs @@ -0,0 +1,264 @@ +//! `CockpitPanel` — the cockpit **sidebar** (left toolbelt tab): a compact, glanceable +//! list of account cards over the `zaplex_cockpit` data spine. Read-only in C2; the +//! live-session quick-list + quick-launch land in later increments (see the cockpit +//! native-integration design doc). The roomy full dashboard is the main-area pane (C2b). + +use pathfinder_color::ColorU; +use warp_core::ui::appearance::Appearance; +use warp_core::ui::theme::color::internal_colors; +use warpui::elements::{ + ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container, CornerRadius, + CrossAxisAlignment, Element, Fill as ElementFill, Flex, MainAxisAlignment, MainAxisSize, + ParentElement, Radius, Rect, ScrollbarWidth, Shrinkable, Text, +}; +use warpui::{AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext}; +use zaplex_cockpit::{ + format_cost, format_reset, format_tokens, heat_fill, heat_pct_label, AccountUsage, HeatLevel, +}; + +use crate::cockpit::model::{CockpitEvent, CockpitModel}; + +const CARD_PADDING: f32 = 8.0; +const CARD_SPACING: f32 = 4.0; +const HEAT_BAR_WIDTH: f32 = 90.0; +const HEAT_BAR_HEIGHT: f32 = 6.0; + +/// Maps a heat band to its display colour (reference palette lives in +/// `zaplex_cockpit::HeatLevel::hex`; kept in sync here as `ColorU`). +fn heat_coloru(level: HeatLevel) -> ColorU { + match level { + HeatLevel::Ok => ColorU::from_u32(0x22C55EFF), + HeatLevel::Elevated => ColorU::from_u32(0xEAB308FF), + HeatLevel::High => ColorU::from_u32(0xFB923CFF), + HeatLevel::Critical => ColorU::from_u32(0xF97316FF), + HeatLevel::Over => ColorU::from_u32(0xEF4444FF), + } +} + +pub struct CockpitPanel { + scroll_state: ClippedScrollStateHandle, +} + +impl CockpitPanel { + pub fn new(ctx: &mut ViewContext) -> Self { + // Re-render on theme change and whenever the snapshot updates. + ctx.subscribe_to_model(&Appearance::handle(ctx), |_, _, _, ctx| ctx.notify()); + ctx.subscribe_to_model(&CockpitModel::handle(ctx), |_, _, event, ctx| { + let CockpitEvent::Updated = event; + ctx.notify(); + }); + Self { + scroll_state: ClippedScrollStateHandle::default(), + } + } + + fn text(s: String, family: warpui::fonts::FamilyId, size: f32, color: ColorU) -> Box { + Text::new_inline(s, family, size).with_color(color).finish() + } + + /// A labelled heat bar: `5h [▓▓▓░░] 62%`, coloured by band. + fn heat_bar(&self, label: &str, fraction: f64, appearance: &Appearance) -> Box { + let theme = appearance.theme(); + let family = appearance.ui_font_family(); + let size = appearance.ui_font_body(); + let muted = theme.sub_text_color(theme.background()).into_solid(); + let level = HeatLevel::from_fraction(fraction); + let fill_w = (heat_fill(fraction) as f32) * HEAT_BAR_WIDTH; + + let fill = ConstrainedBox::new( + Rect::new() + .with_background_color(heat_coloru(level)) + .finish(), + ) + .with_width(fill_w) + .with_height(HEAT_BAR_HEIGHT) + .finish(); + + let track = ConstrainedBox::new( + Container::new(fill) + .with_background(internal_colors::fg_overlay_1(theme)) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(3.0))) + .finish(), + ) + .with_width(HEAT_BAR_WIDTH) + .with_height(HEAT_BAR_HEIGHT) + .finish(); + + Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_spacing(6.0) + .with_child(Self::text(label.to_string(), family, size, muted)) + .with_child(track) + .with_child(Self::text( + heat_pct_label(fraction), + family, + size, + heat_coloru(level), + )) + .with_main_axis_size(MainAxisSize::Min) + .finish() + } + + fn render_card(&self, acct: &AccountUsage, appearance: &Appearance) -> Box { + let theme = appearance.theme(); + let family = appearance.ui_font_family(); + let body = appearance.ui_font_body(); + let sub = appearance.ui_font_subheading(); + let main = theme.main_text_color(theme.background()).into_solid(); + let muted = theme.sub_text_color(theme.background()).into_solid(); + let accent = theme.accent().into_solid(); + let now = chrono::Utc::now(); + + // Header: label (bold-ish subheading) + optional plan badge. + let mut header = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_main_axis_size(MainAxisSize::Max) + .with_spacing(6.0) + .with_child( + Shrinkable::new(1.0, Self::text(acct.account.label.clone(), family, sub, main)) + .finish(), + ); + if let Some(plan) = &acct.account.plan_tier { + header = header.with_child( + Container::new(Self::text(plan.clone(), family, body, accent)) + .with_padding_left(6.0) + .with_padding_right(6.0) + .with_padding_top(1.0) + .with_padding_bottom(1.0) + .with_background(internal_colors::fg_overlay_1(theme)) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) + .finish(), + ); + } + + let cost_line = format!( + "today {} · {}", + format_cost(acct.today.cost_usd), + format_tokens(acct.today.total) + ); + + let reset_5h = format_reset(acct.reset5h, now); + let reset_wk = format_reset(acct.reset_week, now); + let reset_line = match (reset_5h.is_empty(), reset_wk.is_empty()) { + (true, true) => None, + (false, true) => Some(format!("5h ↻ {reset_5h}")), + (true, false) => Some(format!("wk ↻ {reset_wk}")), + (false, false) => Some(format!("5h ↻ {reset_5h} · wk ↻ {reset_wk}")), + }; + + let mut col = Flex::column() + .with_cross_axis_alignment(CrossAxisAlignment::Stretch) + .with_main_axis_size(MainAxisSize::Min) + .with_spacing(CARD_SPACING) + .with_child(header.finish()) + .with_child(self.heat_bar("5h", acct.heat, appearance)) + .with_child(Self::text(cost_line, family, body, muted)); + if let Some(reset_line) = reset_line { + col = col.with_child(Self::text(reset_line, family, body, muted)); + } + + Container::new(col.finish()) + .with_uniform_padding(CARD_PADDING) + .with_margin_bottom(CARD_SPACING) + .with_background(internal_colors::fg_overlay_1(theme)) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(6.0))) + .finish() + } + + fn render_header(&self, snapshot_len: usize, cost5h: f64, cost_wk: f64, appearance: &Appearance) -> Box { + let theme = appearance.theme(); + let family = appearance.ui_font_family(); + let sub = appearance.ui_font_subheading(); + let body = appearance.ui_font_body(); + let main = theme.main_text_color(theme.background()).into_solid(); + let muted = theme.sub_text_color(theme.background()).into_solid(); + + Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_main_axis_size(MainAxisSize::Max) + .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) + .with_child(Self::text( + format!("{} account{}", snapshot_len, if snapshot_len == 1 { "" } else { "s" }), + family, + sub, + main, + )) + .with_child(Self::text( + format!("{} 5h · {} wk", format_cost(cost5h), format_cost(cost_wk)), + family, + body, + muted, + )) + .finish() + } +} + +impl View for CockpitPanel { + fn ui_name() -> &'static str { + "CockpitPanel" + } + + fn render(&self, app: &AppContext) -> Box { + let appearance = Appearance::as_ref(app); + let theme = appearance.theme(); + let family = appearance.ui_font_family(); + let body = appearance.ui_font_body(); + let muted = theme.sub_text_color(theme.background()).into_solid(); + + let snapshot = CockpitModel::as_ref(app).snapshot().clone(); + + let body_el: Box = if snapshot.accounts.is_empty() { + Container::new(Self::text( + crate::t!("workspace-left-panel-cockpit-empty"), + family, + body, + muted, + )) + .with_uniform_padding(CARD_PADDING) + .finish() + } else { + let cost5h: f64 = snapshot.accounts.iter().map(|a| a.block5h.cost_usd).sum(); + let cost_wk: f64 = snapshot.accounts.iter().map(|a| a.week.cost_usd).sum(); + + let mut cards = Flex::column() + .with_cross_axis_alignment(CrossAxisAlignment::Stretch) + .with_main_axis_size(MainAxisSize::Min) + .with_child( + Container::new(self.render_header( + snapshot.accounts.len(), + cost5h, + cost_wk, + appearance, + )) + .with_margin_bottom(CARD_SPACING * 2.0) + .finish(), + ); + for acct in &snapshot.accounts { + cards = cards.with_child(self.render_card(acct, appearance)); + } + + ClippedScrollable::vertical( + self.scroll_state.clone(), + cards.finish(), + ScrollbarWidth::Auto, + theme.disabled_text_color(theme.background()).into(), + theme.main_text_color(theme.background()).into(), + ElementFill::None, + ) + .with_overlayed_scrollbar() + .finish() + }; + + Container::new(body_el) + .with_uniform_padding(CARD_PADDING) + .finish() + } +} + +impl Entity for CockpitPanel { + type Event = (); +} + +impl TypedActionView for CockpitPanel { + type Action = (); +} diff --git a/app/src/cockpit/settings.rs b/app/src/cockpit/settings.rs new file mode 100644 index 0000000000..3146a41fca --- /dev/null +++ b/app/src/cockpit/settings.rs @@ -0,0 +1,41 @@ +//! Scalar policy settings for the cockpit. Accounts are *discovered*, not +//! configured, so no list settings live here (per the Increment 1 design §7). + +use settings::{ + macros::define_settings_group, RespectUserSyncSetting, SupportedPlatforms, SyncToCloud, +}; + +define_settings_group!(CockpitSettings, + settings: [ + enabled: CockpitEnabled { + type: bool, + default: true, + supported_platforms: SupportedPlatforms::ALL, + sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), + private: false, + storage_key: "CockpitEnabled", + toml_path: "cockpit.enabled", + description: "Whether the cockpit account/usage/cost data layer is active.", + }, + budget_5h: CockpitBudget5h { + type: u32, + default: 0, + supported_platforms: SupportedPlatforms::ALL, + sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), + private: false, + storage_key: "CockpitBudget5h", + toml_path: "cockpit.budget_5h", + description: "Per-5h-block token budget used for heat (0 = built-in estimate).", + }, + budget_week: CockpitBudgetWeek { + type: u32, + default: 0, + supported_platforms: SupportedPlatforms::ALL, + sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), + private: false, + storage_key: "CockpitBudgetWeek", + toml_path: "cockpit.budget_week", + description: "Per-week token budget (0 = built-in estimate). Reserved for later use.", + }, + ] +); diff --git a/app/src/code/editor/view/actions.rs b/app/src/code/editor/view/actions.rs index 5c617f75d1..be79a0ac77 100644 --- a/app/src/code/editor/view/actions.rs +++ b/app/src/code/editor/view/actions.rs @@ -1190,7 +1190,7 @@ impl RichTextAction for CodeEditorViewAction { modifiers.shift ); - // The first mouse down to bring focus to a Zap window will not have a corresponding mouse up. + // The first mouse down to bring focus to a Zaplex window will not have a corresponding mouse up. // We ignore it, and they can click again. if is_first_mouse { return None; diff --git a/app/src/code/file_tree/view.rs b/app/src/code/file_tree/view.rs index 60a3ca3245..138e555991 100644 --- a/app/src/code/file_tree/view.rs +++ b/app/src/code/file_tree/view.rs @@ -2907,7 +2907,7 @@ impl View for FileTreeView { if let CodingPanelEnablementState::RemoteSession { has_remote_server } = self.enablement { // When the session has a remote server connection (Auto SSH - // Warpification / mode 1), show a loading state — the server + // Zaplexification / mode 1), show a loading state — the server // may push repo metadata momentarily. For other SSH modes // (tmux, subshell) no data will arrive, so show the disabled // error instead. diff --git a/app/src/code_review/comment_list_view.rs b/app/src/code_review/comment_list_view.rs index 73f574ee5c..fe13e317f6 100644 --- a/app/src/code_review/comment_list_view.rs +++ b/app/src/code_review/comment_list_view.rs @@ -946,7 +946,7 @@ impl CommentListView { let enable_send = match &self.review_destination { ReviewDestination::None => false, ReviewDestination::Cli(_) => has_sendable_comments, - ReviewDestination::Zap => ai_available && has_sendable_comments, + ReviewDestination::Zaplex => ai_available && has_sendable_comments, }; let tooltip_text = Self::send_button_tooltip_text( diff --git a/app/src/code_review/comments/comment.rs b/app/src/code_review/comments/comment.rs index 2e83d7262e..63a82c8cb3 100644 --- a/app/src/code_review/comments/comment.rs +++ b/app/src/code_review/comments/comment.rs @@ -10,7 +10,7 @@ use warp_multi_agent_api::{self as api}; #[derive(Clone, Debug, Default, PartialEq, Eq)] pub enum CommentOrigin { - /// Comments originally created in the Zap UI. + /// Comments originally created in the Zaplex UI. #[default] Native, /// Comments imported from a GitHub pull request. diff --git a/app/src/code_review/telemetry_event.rs b/app/src/code_review/telemetry_event.rs index 48ada70353..6a16e47f25 100644 --- a/app/src/code_review/telemetry_event.rs +++ b/app/src/code_review/telemetry_event.rs @@ -72,7 +72,7 @@ pub enum CodeReviewContextDestination { /// Written directly to the terminal PTY for an active CLI agent. #[serde(rename = "pty")] Pty, - /// Inserted into the Zap AI input buffer as plain text. + /// Inserted into the Zaplex AI input buffer as plain text. #[serde(rename = "agent_input")] AgentInput, /// Registered as an AI attachment and referenced from the input. @@ -81,7 +81,7 @@ pub enum CodeReviewContextDestination { /// Inserted into the active command buffer while a command is running. #[serde(rename = "active_command_buffer")] ActiveCommandBuffer, - /// Submitted as an inline code review request through the Zap AI path. + /// Submitted as an inline code review request through the Zaplex AI path. #[serde(rename = "agent_review")] AgentReview, /// Inserted into CLI agent rich input. diff --git a/app/src/coding_panel_enablement_state.rs b/app/src/coding_panel_enablement_state.rs index a3f4ee39ec..498a011019 100644 --- a/app/src/coding_panel_enablement_state.rs +++ b/app/src/coding_panel_enablement_state.rs @@ -9,7 +9,7 @@ pub(crate) enum CodingPanelEnablementState { /// The active session is on a remote host. /// /// `has_remote_server` is `true` when the session is registered with - /// `RemoteServerManager` (i.e. Auto SSH Warpification / mode 1). When + /// `RemoteServerManager` (i.e. Auto SSH Zaplexification / mode 1). When /// `true`, remote repo metadata may arrive and the file tree should show /// a loading state. When `false` (tmux or subshell SSH), no data will /// arrive and the file tree should show a disabled message. diff --git a/app/src/completer/mod.rs b/app/src/completer/mod.rs index 67a5337fd6..77c4230ceb 100644 --- a/app/src/completer/mod.rs +++ b/app/src/completer/mod.rs @@ -48,7 +48,7 @@ pub struct SessionContext { cached_directory_entries: dashmap::DashMap>>, - /// Snapshot of all Zap workflow aliases. + /// Snapshot of all Zaplex workflow aliases. workflow_aliases: HashMap, } @@ -85,7 +85,7 @@ impl SessionContext { .filter_map(|res| res.and_then(EngineDirEntry::try_from).ok()) .collect::>() } - SessionType::WarpifiedRemote { .. } => { + SessionType::ZaplexifiedRemote { .. } => { let env_vars = self .session .path() @@ -198,7 +198,7 @@ impl PathCompletionContext for SessionContext { // connected yet, the command executor may be an InBand fallback that // sends escape sequences to a raw remote shell. Return empty without // caching so we retry after the remote server handshake finishes. - if let SessionType::WarpifiedRemote { host_id: None } = self.session.session_type() { + if let SessionType::ZaplexifiedRemote { host_id: None } = self.session.session_type() { if FeatureFlag::SshRemoteServer.is_enabled() && !self.session.is_legacy_ssh_session() { @@ -230,7 +230,7 @@ impl GeneratorContext for SessionContext { ) -> Result { let mut env_vars = session_env_vars.unwrap_or_default(); // We need to run the command with the PATH var set explicitly even if we have session env vars - // because if the user opened Zap through a parent process that didn't have the PATH var set + // because if the user opened Zaplex through a parent process that didn't have the PATH var set // (i.e. outside of a shell, for example opening the app via Finder), // the subshell won't inherit the PATH var, but we need the PATH var // to reference executables we might run as part of generators. diff --git a/app/src/context_chips/builtins.rs b/app/src/context_chips/builtins.rs index 029e0e658d..4480fac067 100644 --- a/app/src/context_chips/builtins.rs +++ b/app/src/context_chips/builtins.rs @@ -1,4 +1,4 @@ -//! Context chips built into Zap +//! Context chips built into Zaplex use chrono::Local; use warp_util::path::user_friendly_path; @@ -94,7 +94,7 @@ pub fn ssh_session(ctx: &GeneratorContext) -> Option { if session.is_legacy_ssh_session() || matches!( session.session_type(), - crate::terminal::model::session::SessionType::WarpifiedRemote { .. } + crate::terminal::model::session::SessionType::ZaplexifiedRemote { .. } ) { let user = session.user(); diff --git a/app/src/context_chips/builtins_tests.rs b/app/src/context_chips/builtins_tests.rs index 83b4a9c387..b83e314fc7 100644 --- a/app/src/context_chips/builtins_tests.rs +++ b/app/src/context_chips/builtins_tests.rs @@ -50,7 +50,7 @@ fn test_remote_sessions() { let local_session = Session::test(); let remote_session = Session::new( SessionInfo::new_for_test() - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::ZaplexifiedRemote) .with_hostname("remote-host".to_string()) .with_user("remote-user".to_string()), Arc::new(TestCommandExecutor {}), diff --git a/app/src/context_chips/current_prompt.rs b/app/src/context_chips/current_prompt.rs index 9847ea60b2..84c5ee04b8 100644 --- a/app/src/context_chips/current_prompt.rs +++ b/app/src/context_chips/current_prompt.rs @@ -171,7 +171,7 @@ pub struct CurrentPrompt { renderable_chips: HashSet, same_line_prompt_enabled: bool, - /// The separator to use as a trailing character at the end of Zap prompt, if any. + /// The separator to use as a trailing character at the end of Zaplex prompt, if any. separator: WarpPromptSeparator, latest_context: Option, @@ -330,12 +330,12 @@ impl CurrentPrompt { .collect() } - /// Whether same line prompt is enabled for the Zap prompt. + /// Whether same line prompt is enabled for the Zaplex prompt. pub fn same_line_prompt_enabled(&self) -> bool { self.same_line_prompt_enabled } - /// The separator for the current Zap prompt. + /// The separator for the current Zaplex prompt. pub fn separator(&self) -> WarpPromptSeparator { self.separator } diff --git a/app/src/context_chips/prompt.rs b/app/src/context_chips/prompt.rs index 9a8bb76740..c841bf6320 100644 --- a/app/src/context_chips/prompt.rs +++ b/app/src/context_chips/prompt.rs @@ -97,7 +97,7 @@ pub struct PromptConfiguration { #[schemars(description = "Whether the prompt is displayed on the same line as the input.")] same_line_prompt_enabled: bool, - /// The separator to use as a trailing character at the end of Zap prompt, if any. + /// The separator to use as a trailing character at the end of Zaplex prompt, if any. #[schemars(description = "Trailing separator character for the prompt.")] separator: WarpPromptSeparator, } @@ -196,7 +196,7 @@ impl Prompt { }) } - /// Reset to the default Zap prompt. + /// Reset to the default Zaplex prompt. pub fn reset( &mut self, ctx: &mut C, @@ -246,12 +246,12 @@ impl Prompt { } } - /// Whether same line prompt is enabled for Zap prompt. + /// Whether same line prompt is enabled for Zaplex prompt. pub fn same_line_prompt_enabled(&self) -> bool { self.config.same_line_prompt_enabled } - /// The separator to be used for the Zap prompt. + /// The separator to be used for the Zaplex prompt. pub fn separator(&self) -> WarpPromptSeparator { self.config.separator } @@ -317,7 +317,7 @@ impl Entity for Prompt { impl SingletonEntity for Prompt {} impl PromptConfiguration { - /// The default Zap prompt, synthesized from legacy prompt settings. + /// The default Zaplex prompt, synthesized from legacy prompt settings. /// The order of chips is important and would affect a lot of users if rearranged. pub fn default_prompt() -> Self { Self::default_prompt_with_pr_chip_suppressed(false) diff --git a/app/src/context_chips/prompt_snapshot.rs b/app/src/context_chips/prompt_snapshot.rs index 37d1e6cd57..ede774e9ea 100644 --- a/app/src/context_chips/prompt_snapshot.rs +++ b/app/src/context_chips/prompt_snapshot.rs @@ -15,7 +15,7 @@ pub struct PromptSnapshot { chips: Vec, same_line_prompt_enabled: bool, - /// The separator to use as a trailing character at the end of Zap prompt, if any. + /// The separator to use as a trailing character at the end of Zaplex prompt, if any. separator: WarpPromptSeparator, } diff --git a/app/src/context_chips/prompt_type.rs b/app/src/context_chips/prompt_type.rs index 924e865d50..000af0b625 100644 --- a/app/src/context_chips/prompt_type.rs +++ b/app/src/context_chips/prompt_type.rs @@ -162,7 +162,7 @@ impl PromptType { .collect() } - /// Whether same line prompt is enabled for the Zap Prompt. + /// Whether same line prompt is enabled for the Zaplex Prompt. pub fn same_line_prompt_enabled(&self, ctx: &AppContext) -> bool { match self { Self::Dynamic { prompt } => prompt.as_ref(ctx).same_line_prompt_enabled(), @@ -170,7 +170,7 @@ impl PromptType { } } - /// The separator for the Zap prompt. + /// The separator for the Zaplex prompt. pub fn separator(&self, ctx: &AppContext) -> WarpPromptSeparator { match self { Self::Dynamic { prompt } => prompt.as_ref(ctx).separator(), diff --git a/app/src/crash_reporting/linux.rs b/app/src/crash_reporting/linux.rs index 7aaf696ca9..4ac6b379a4 100644 --- a/app/src/crash_reporting/linux.rs +++ b/app/src/crash_reporting/linux.rs @@ -1,6 +1,6 @@ use crate::crash_reporting::VirtualEnvironment; -/// Returns what virtualized environment Zap is running in, if any. +/// Returns what virtualized environment Zaplex is running in, if any. pub fn get_virtualized_environment() -> Option { if let Ok(output) = command::blocking::Command::new("systemd-detect-virt").output() { if !output.status.success() { diff --git a/app/src/crash_reporting/local_minidump.rs b/app/src/crash_reporting/local_minidump.rs index e23fb0ca5a..6029b23352 100644 --- a/app/src/crash_reporting/local_minidump.rs +++ b/app/src/crash_reporting/local_minidump.rs @@ -97,7 +97,7 @@ pub struct MinidumpGuard { pub fn run_server(socket_path: &Path) -> anyhow::Result<()> { // For troubleshooting, attempt to log from the minidump server. There's not much we can really // do if crash reporting fails, so creating the log file itself is best-effort. - let log_dir = warp_core::paths::state_dir().join(warp_core::paths::WARP_LOGS_DIR); + let log_dir = warp_core::paths::state_dir().join(warp_core::paths::ZAPLEX_LOGS_DIR); let _ = std::fs::create_dir_all(&log_dir); let log_path = log_dir.join("warp-minidump.log"); let log_target = File::create(log_path) @@ -118,7 +118,7 @@ pub fn run_server(socket_path: &Path) -> anyhow::Result<()> { impl minidumper::ServerHandler for Handler { fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> { let dump_dir = warp_core::paths::state_dir() - .join(warp_core::paths::WARP_LOGS_DIR) + .join(warp_core::paths::ZAPLEX_LOGS_DIR) .join("crash-dumps"); std::fs::create_dir_all(&dump_dir)?; @@ -249,7 +249,7 @@ impl MinidumpGuard { }) .context("Failed to attach crash signal handler")?; - // Ensure that the crash server process can ptrace Zap. + // Ensure that the crash server process can ptrace Zaplex. #[cfg(target_os = "linux")] crash_handler.set_ptracer(Some(child.id())); diff --git a/app/src/debug_dump.rs b/app/src/debug_dump.rs index 308f8b7355..d201507167 100644 --- a/app/src/debug_dump.rs +++ b/app/src/debug_dump.rs @@ -1,7 +1,7 @@ //! This module contains the code path for the [`warp_cli::Command::DumpDebugInfo`] subcommand. //! //! This is intended to never be used by a vast majority of users. This is only intended for users -//! who are unable to run Zap and want to provide us, the dev team, with useful debugging +//! who are unable to run Zaplex and want to provide us, the dev team, with useful debugging //! information. #[cfg(not(windows))] use command::blocking::Command; @@ -9,7 +9,7 @@ use warp_core::channel::ChannelState; use warpui::windowing; pub(crate) fn run() -> anyhow::Result<()> { - println!("Zap version: {:?}", ChannelState::app_version()); + println!("Zaplex version: {:?}", ChannelState::app_version()); #[cfg(not(windows))] { diff --git a/app/src/default_terminal/mac.rs b/app/src/default_terminal/mac.rs index 35d8cd85a2..718838e9a8 100644 --- a/app/src/default_terminal/mac.rs +++ b/app/src/default_terminal/mac.rs @@ -64,7 +64,7 @@ pub fn is_warp_default_terminal() -> bool { } pub fn set_warp_as_default_terminal() -> Result<(), String> { - log::debug!("Setting Zap as default terminal"); + log::debug!("Setting Zaplex as default terminal"); let bundle_id = get_warp_bundle_id().ok_or("No bundle ID".to_string())?; @@ -94,7 +94,7 @@ fn set_default_terminal(bundle_id: &str) -> Result<(), String> { } } -/// Gets Zap's bundle identifier. This may be `None` if not running as a bundle, i.e. through +/// Gets Zaplex's bundle identifier. This may be `None` if not running as a bundle, i.e. through /// `cargo run` without `cargo bundle`. fn get_warp_bundle_id() -> Option { unsafe { diff --git a/app/src/default_terminal/mod.rs b/app/src/default_terminal/mod.rs index a1fde10a64..f12dc22601 100644 --- a/app/src/default_terminal/mod.rs +++ b/app/src/default_terminal/mod.rs @@ -20,7 +20,7 @@ mod non_mac { false } - /// Sets Zap as the default terminal + /// Sets Zaplex as the default terminal pub fn set_warp_as_default_terminal() -> Result<(), String> { Err("Not implemented".to_string()) } @@ -31,7 +31,7 @@ mod non_mac { use non_mac::*; pub struct DefaultTerminal { - /// Whether the OS will treat Zap as the default app for scripts/executables. + /// Whether the OS will treat Zaplex as the default app for scripts/executables. is_warp_default: bool, } @@ -53,9 +53,9 @@ impl DefaultTerminal { Self { is_warp_default } } - /// This is an OS-level setting. Unlike most other settings, where Zap is the source-of-truth - /// for the value of the setting, it can be changed outside of Zap. We monitor if it gets - /// changed externally by checking when Zap is focused. + /// This is an OS-level setting. Unlike most other settings, where Zaplex is the source-of-truth + /// for the value of the setting, it can be changed outside of Zaplex. We monitor if it gets + /// changed externally by checking when Zaplex is focused. fn handle_window_manager_event(&mut self, event: &StateEvent, ctx: &mut ModelContext) { match event { StateEvent::ValueChanged { current, previous } => { @@ -90,11 +90,11 @@ impl DefaultTerminal { self.is_warp_default } - /// This is a one-way operation. Once we set the default terminal to Zap, we can't really + /// This is a one-way operation. Once we set the default terminal to Zaplex, we can't really /// "unset" it unless we pick a new default terminal. Picking a new default is complicated. pub fn make_warp_default(&mut self, ctx: &mut ModelContext) { if let Err(e) = set_warp_as_default_terminal() { - log::error!("Error setting Zap as default terminal: {e:#}"); + log::error!("Error setting Zaplex as default terminal: {e:#}"); } else { self.set_is_warp_default(true, ctx); } diff --git a/app/src/drive/export.rs b/app/src/drive/export.rs index 4fff7d04c3..7772b7adbb 100644 --- a/app/src/drive/export.rs +++ b/app/src/drive/export.rs @@ -36,7 +36,7 @@ use crate::{ use super::ObjectTypeAndId; -/// Singleton model for exporting from Zap Drive. +/// Singleton model for exporting from Zaplex Drive. pub struct ExportManager { exports: HashMap, } @@ -59,7 +59,7 @@ pub enum ExportEvent { Completed { id: ExportId, path: PathBuf }, } -/// A single Zap Drive export. +/// A single Zaplex Drive export. struct Export { /// The ID of the window that started this export, for showing toasts. window_id: WindowId, diff --git a/app/src/drive/index.rs b/app/src/drive/index.rs index e52e4e0acb..71811e2272 100644 --- a/app/src/drive/index.rs +++ b/app/src/drive/index.rs @@ -149,7 +149,7 @@ const OFFLINE_BANNER_PADDING_VERTICAL: f32 = 4.; pub const DRIVE_INDEX_VIEW_POSITION_ID: &str = "drive_index_view_id"; -// Sets the speed of the autoscroll that occurs when you drag an item near the Zap Drive border. +// Sets the speed of the autoscroll that occurs when you drag an item near the Zaplex Drive border. pub const AUTOSCROLL_SPEED_MULTIPLIER: f32 = 10.; // Sets the distance from a border at which scroll events start to occur. pub const AUTOSCROLL_DETECTION_DISTANCE: f32 = 30.0; @@ -286,10 +286,10 @@ pub enum DriveIndexAction { CloseTrashIndex, FocusPreviousItem, FocusNextItem, - /// Hitting one of the l/r arrow keys on a Zap Drive item. + /// Hitting one of the l/r arrow keys on a Zaplex Drive item. LeftArrowKey, RightArrowKey, - /// Hitting enter key on a Zap Drive item. + /// Hitting enter key on a Zaplex Drive item. EnterKey, /// Hitting escape key from trash index returns to main drive index. EscapeKey, @@ -420,10 +420,10 @@ struct SpaceMenuState { offset: Vector2F, } -/// The main view for the Zap Drive sidebar. +/// The main view for the Zaplex Drive sidebar. /// `DriveIndex` is different from `DrivePanel` in that it is responsible for -/// all the logic within Zap Drive, whereas `DrivePanel` is responsible for -/// how Zap Drive interacts with the workspace and the rest of the app. +/// all the logic within Zaplex Drive, whereas `DrivePanel` is responsible for +/// how Zaplex Drive interacts with the workspace and the rest of the app. #[derive(Clone)] pub struct DriveIndex { window_id: WindowId, @@ -431,7 +431,7 @@ pub struct DriveIndex { /// default, should get the menu fields on open, example: + button to add notebook) menu: ViewHandle>, - /// Variant of the index, determines whether base Zap Drive or trash is viewed. + /// Variant of the index, determines whether base Zaplex Drive or trash is viewed. index_variant: DriveIndexVariant, /// If None, the context menu is closed. Otherwise, this contains the ID of the object it's open on. menu_object_id_if_open: Option, @@ -456,7 +456,7 @@ pub struct DriveIndex { /// A hashmap of location (space/folder) to a list of hashed IDs of objects inside /// the space/folder, used for rendering our objects sorted_orders_by_location: HashMap>, - /// A sorted list of all the items (spaces + objects) in Zap Drive + /// A sorted list of all the items (spaces + objects) in Zaplex Drive /// Unlike sorted_orders_by_location, this is not used for rendering /// This is used for object focusing and WD keyboard navigation ordered_items: Vec, @@ -466,7 +466,7 @@ pub struct DriveIndex { /// from links before everything has been set up. has_initialized_sections: Condition, - /// The number of objects in Zap Drive that have errored. + /// The number of objects in Zaplex Drive that have errored. /// This value is cached so that we can determine whether to render the "retry all" /// objects button in the case of syncing failures. num_errored_objects: usize, @@ -941,7 +941,7 @@ impl DriveIndex { app: &AppContext, ) -> bool { if let Some(object) = ObjectStoreModel::as_ref(app).get_by_uid(&object_type_and_id.uid()) { - // Zap (decentralized branch): local objects (no server_id, no-op upstream in SyncQueue) + // Zaplex (decentralized branch): local objects (no server_id, no-op upstream in SyncQueue) // previously could never access trash/move menus. Here we treat "no server_id" as a local object, // allowing it to perform local-side operations (trash uses sqlite, no server coordination needed). if !object_type_and_id.has_server_id() { @@ -2308,7 +2308,7 @@ impl DriveIndex { let access_level = ObjectStoreViewModel::as_ref(app).access_level(&row_object_id.uid(), app); - // Zap Phase 2a: sharing dialog removed; pass `false` for the + // Zaplex Phase 2a: sharing dialog removed; pass `false` for the // legacy `share_dialog_open` slot in `WarpDriveRow::new_from_cloud_object`. let share_dialog_open = false; let menu_open = self.menu_object_id_if_open == Some(warp_drive_item_id); @@ -2340,7 +2340,7 @@ impl DriveIndex { share_dialog_open, is_selected, is_focused, - false, /* Zap (Wave 4): SyncQueue completely removed, is_dequeueing always false */ + false, /* Zaplex (Wave 4): SyncQueue completely removed, is_dequeueing always false */ tools_panel_menu_direction(app), appearance, )?; @@ -2563,7 +2563,7 @@ impl DriveIndex { } }; - // This icon should render the same as other ZapDrive icons but with no click or hover states. + // This icon should render the same as other ZaplexDrive icons but with no click or hover states. Container::new( ConstrainedBox::new(icon.to_warpui_icon(icon_color).finish()) .with_width(SECTION_HEADER_FONT_SIZE) @@ -2746,7 +2746,7 @@ impl DriveIndex { if self.focused_index.is_some() { let DriveIndexSection::Space(space) = *section; self.set_focused_item(WarpDriveItemId::Space(space), true, ctx); - // Need to re-render focused index in Zap Drive after a space has been toggled + // Need to re-render focused index in Zaplex Drive after a space has been toggled if let Some(focused_index) = self.focused_index { self.update_focused_params(focused_index, ObjectStoreModel::as_ref(ctx)); } @@ -3375,7 +3375,7 @@ impl DriveIndex { } fn retry_all_failed(&mut self, ctx: &mut ViewContext) { - // Zap (Wave 4): After SyncQueue is completely removed, the original “retry” semantics + // Zaplex (Wave 4): After SyncQueue is completely removed, the original “retry” semantics // (re-submit to server) no longer apply; after localization, objects never enter errored state, this path is dead code. let _ = ctx; } @@ -3757,7 +3757,7 @@ impl DriveIndex { let object = ObjectStoreModel::as_ref(app).get_by_uid(&object_type_and_id.uid()); if let ObjectTypeAndId::Folder(folder_id) = object_type_and_id { - // Zap: local folders (ClientId, no-op upstream in SyncQueue) never get a server_id. + // Zaplex: local folders (ClientId, no-op upstream in SyncQueue) never get a server_id. // The original `SyncId::ServerId(_) && is_online` double gate would mean local folders // never get the "create child/Rename" context menu. Here we treat "local folder" as always ready. let is_local_folder = matches!(folder_id, SyncId::ClientId(_)); @@ -3879,7 +3879,7 @@ impl DriveIndex { ); if let Some(object) = object { - // Zap (Wave 6-7): “Leave shared object” menu retired along with `leave_object` pub fn. + // Zaplex (Wave 6-7): “Leave shared object” menu retired along with `leave_object` pub fn. let _ = object; } } @@ -4066,7 +4066,7 @@ impl DriveIndex { .into_item(), ); } - // Zap Phase 2a: drive-share menu item removed (sharing dialog gone). + // Zaplex Phase 2a: drive-share menu item removed (sharing dialog gone). if !warpui::platform::is_mobile_device() && !ContextFlag::HideOpenOnDesktopButton.is_enabled() && *UserAppInstallDetectionSettings::as_ref(app) @@ -4117,7 +4117,7 @@ impl DriveIndex { } if FeatureFlag::SharedWithMe.is_enabled() && object.can_leave(app) { - // Zap (Wave 6-7): “Leave shared object” menu retired along with `leave_object` pub fn. + // Zaplex (Wave 6-7): “Leave shared object” menu retired along with `leave_object` pub fn. } } } @@ -4138,7 +4138,7 @@ impl DriveIndex { menu_items } - /// Builder for a menu item to open a Zap Drive object in a pane. The icon and label depend + /// Builder for a menu item to open a Zaplex Drive object in a pane. The icon and label depend /// on whether the object is editable or not. /// /// If `prefer_open` is `true`, the item defaults to view/open mode rather than edit mode. diff --git a/app/src/drive/index_test.rs b/app/src/drive/index_test.rs index 4be1d77cb9..c0bff82d58 100644 --- a/app/src/drive/index_test.rs +++ b/app/src/drive/index_test.rs @@ -166,7 +166,7 @@ fn test_retry_menu_item_logic() { let object_type_and_id: ObjectTypeAndId = ObjectTypeAndId::from_id_and_type(sync_id, ObjectType::Workflow); - // Zap (Wave 4): SyncQueue entirely removed; assertions that previously validated SyncQueue changes + // Zaplex (Wave 4): SyncQueue entirely removed; assertions that previously validated SyncQueue changes // are now meaningless. Skip validation and leave the call flow itself to verify it doesn't panic. index.update(&mut app, |index, ctx| { @@ -180,9 +180,9 @@ fn test_retry_menu_item_logic() { } }); - // Zap (Wave 4): Previously verified that the SyncQueue head is CreateWorkflow; no longer applicable after SyncQueue removal. + // Zaplex (Wave 4): Previously verified that the SyncQueue head is CreateWorkflow; no longer applicable after SyncQueue removal. - // Zap (Wave 4): Previously verified SyncQueue length + UpdateWorkflow tag; no longer applicable after SyncQueue removal. + // Zaplex (Wave 4): Previously verified SyncQueue length + UpdateWorkflow tag; no longer applicable after SyncQueue removal. }) } diff --git a/app/src/drive/items/item.rs b/app/src/drive/items/item.rs index 93c11364bd..435064e9b1 100644 --- a/app/src/drive/items/item.rs +++ b/app/src/drive/items/item.rs @@ -37,7 +37,7 @@ use crate::{ DRIVE_INDEX_VIEW_POSITION_ID, FOLDER_DEPTH_INDENT, INDEX_CONTENT_MARGIN_LEFT, ITEM_FONT_SIZE, ITEM_MARGIN_BOTTOM, ITEM_PADDING_HORIZONTAL, ITEM_PADDING_VERTICAL, }, - panel::WARP_DRIVE_POSITION_ID, + panel::ZAPLEX_DRIVE_POSITION_ID, }, menu::Menu, ui_components::{ @@ -715,10 +715,10 @@ impl<'a> WarpDriveRow<'a> { } } -/// Generate a callback for calculating the Drag bounds within Zap Drive +/// Generate a callback for calculating the Drag bounds within Zaplex Drive fn drag_bounds_callback() -> impl Fn(&PositionCache, Vector2F) -> Option { move |position_cache, window: Vector2F| { - let drive_index = position_cache.get_position(WARP_DRIVE_POSITION_ID)?; + let drive_index = position_cache.get_position(ZAPLEX_DRIVE_POSITION_ID)?; let top_left = drive_index.origin(); diff --git a/app/src/drive/items/mod.rs b/app/src/drive/items/mod.rs index c9586a3d37..5e0567e40b 100644 --- a/app/src/drive/items/mod.rs +++ b/app/src/drive/items/mod.rs @@ -71,7 +71,7 @@ impl WarpDriveItemId { } } } -/// This uniquely identifies an item in Zap Drive index +/// This uniquely identifies an item in Zaplex Drive index /// Includes spaces (which ObjectTypeAndId does not entail) #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum WarpDriveItemId { diff --git a/app/src/drive/mod.rs b/app/src/drive/mod.rs index df8ed629ab..e93e896685 100644 --- a/app/src/drive/mod.rs +++ b/app/src/drive/mod.rs @@ -87,18 +87,18 @@ impl fmt::Display for DriveObjectType { } #[derive(Debug, Clone, Eq, PartialEq, Default)] -pub struct ZapDriveObjectSettings { - /// The folder that should be focused in the Zap Drive when the object is opened. +pub struct ZaplexDriveObjectSettings { + /// The folder that should be focused in the Zaplex Drive when the object is opened. pub focused_folder_id: Option, /// The email of the user to invite to the object, if the object is being opened via the request access flow. pub invitee_email: Option, } #[derive(Debug, Clone, Eq, PartialEq)] -pub struct ZapDriveObjectArgs { +pub struct ZaplexDriveObjectArgs { pub object_type: ObjectType, pub server_id: ServerId, - pub settings: ZapDriveObjectSettings, + pub settings: ZaplexDriveObjectSettings, } /// Enum to use to pass down type and id between actions to avoid multiplying actions whenever we @@ -249,7 +249,7 @@ pub fn write_has_auto_opened_welcome_folder_to_user_defaults(app: &mut AppContex .write_value(settings::HAS_AUTO_OPENED_WELCOME_FOLDER, true.to_string()); } -/// Enum used for sorting elements in the Zap Drive Index (and potentially other places). +/// Enum used for sorting elements in the Zaplex Drive Index (and potentially other places). /// In the future it can be used to add other options (like, by name or by author), and exposed to /// users in the index. #[derive( @@ -266,7 +266,7 @@ pub fn write_has_auto_opened_welcome_folder_to_user_defaults(app: &mut AppContex settings_value::SettingsValue, )] #[schemars( - description = "Sort order for Zap Drive items.", + description = "Sort order for Zaplex Drive items.", rename_all = "snake_case" )] pub enum DriveSortOrder { diff --git a/app/src/drive/panel.rs b/app/src/drive/panel.rs index 4a19d984b7..b37a048fa9 100644 --- a/app/src/drive/panel.rs +++ b/app/src/drive/panel.rs @@ -35,12 +35,12 @@ use super::{ pub const MIN_SIDEBAR_WIDTH: f32 = 250.; pub const MAX_SIDEBAR_WIDTH_RATIO: f32 = 0.75; -pub const WARP_DRIVE_POSITION_ID: &str = "warp_drive"; +pub const ZAPLEX_DRIVE_POSITION_ID: &str = "warp_drive"; -/// The sidebar that houses Zap Drive. +/// The sidebar that houses Zaplex Drive. /// `DrivePanel` is different from `DriveIndex` in that it is responsible for -/// how Zap Drive interacts with the workspace and the rest of the app, whereas -/// `DriveIndex` is the main zap drive view and responsible for the internals of Zap Drive. +/// how Zaplex Drive interacts with the workspace and the rest of the app, whereas +/// `DriveIndex` is the main zap drive view and responsible for the internals of Zaplex Drive. pub struct DrivePanel { index_view: ViewHandle, mouse_state_handles: MouseStateHandles, @@ -596,14 +596,14 @@ impl DrivePanel { }) } - /// This functions scrolls the relevant Zap Drive item into view. + /// This functions scrolls the relevant Zaplex Drive item into view. pub fn scroll_item_into_view(&mut self, item_id: WarpDriveItemId, ctx: &mut ViewContext) { self.index_view.update(ctx, |index, ctx| { index.scroll_item_into_view(item_id, ctx); }) } - /// This functions sets the index of a focused Zap Drive item. + /// This functions sets the index of a focused Zaplex Drive item. pub fn set_focused_index(&mut self, focused_index: Option, ctx: &mut ViewContext) { self.index_view.update(ctx, |index, ctx| { index.set_focused_index(focused_index, true, ctx); @@ -669,7 +669,7 @@ impl View for DrivePanel { Align::new( SavePosition::new( ChildView::new(&self.index_view).finish(), - WARP_DRIVE_POSITION_ID, + ZAPLEX_DRIVE_POSITION_ID, ) .finish(), ) diff --git a/app/src/drive/settings.rs b/app/src/drive/settings.rs index f316b31379..a43660fd0c 100644 --- a/app/src/drive/settings.rs +++ b/app/src/drive/settings.rs @@ -14,7 +14,7 @@ define_settings_group!(WarpDriveSettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "warp_drive.sorting_choice", - description: "The sort order for items in Zap Drive.", + description: "The sort order for items in Zaplex Drive.", }, sharing_onboarding_block_shown: WarpDriveSharingOnboardingBlockShown { type: bool, @@ -23,20 +23,24 @@ define_settings_group!(WarpDriveSettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: true, }, - // Controls whether Zap Drive appears in the tools panel, command palette, and command search. + // Controls whether Zaplex Drive appears in the tools panel, command palette, and command search. + // Zaplex Drive (inherited Warp Drive) is out of scope, so it is disabled by default: this is the + // master switch that also gates the Drive keybindings (flags::ENABLE_ZAPLEX_DRIVE) and the + // command-palette / command-search entries. The code is preserved as a template — see + // docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md enable_warp_drive: EnableWarpDrive { type: bool, - default: true, + default: false, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "warp_drive.enabled", - description: "Whether Zap Drive is enabled.", + description: "Whether Zaplex Drive is enabled.", }, ]); impl WarpDriveSettings { - /// Returns whether Zap Drive should be considered enabled. + /// Returns whether Zaplex Drive should be considered enabled. /// Returns `false` when the user is anonymous or fully logged out, /// regardless of the user setting. pub fn is_warp_drive_enabled(app: &warpui::AppContext) -> bool { diff --git a/app/src/drive/sharing/mod.rs b/app/src/drive/sharing/mod.rs index 9022e329c9..fd806595d0 100644 --- a/app/src/drive/sharing/mod.rs +++ b/app/src/drive/sharing/mod.rs @@ -19,7 +19,7 @@ use crate::{ workspaces::{user_profiles::UserProfiles, user_workspaces::UserWorkspaces}, }; -// Zap Phase 2a: `dialog/` (cloud sharing modal UI) deleted along with +// Zaplex Phase 2a: `dialog/` (cloud sharing modal UI) deleted along with // all consumer triggers. `style.rs` is retained because the Subject / // UserKind avatar helpers in this module still depend on it. mod style; @@ -161,11 +161,11 @@ impl PartialEq for UserKind { } } -/// Identifier for an object that's shareable via the Zap Drive ACL model. Not all sharing in Zap +/// Identifier for an object that's shareable via the Zaplex Drive ACL model. Not all sharing in Zaplex /// is _currently_ tied into this model (e.g. block sharing). #[derive(Debug, Clone)] pub enum ShareableObject { - /// A shareable Zap Drive object. + /// A shareable Zaplex Drive object. WarpDriveObject(ServerId), } diff --git a/app/src/drive/workflows/modal.rs b/app/src/drive/workflows/modal.rs index c22ac3e7c6..98be691e1f 100644 --- a/app/src/drive/workflows/modal.rs +++ b/app/src/drive/workflows/modal.rs @@ -423,7 +423,7 @@ impl WorkflowModal { #[allow(dead_code)] fn populate(&mut self, workflow: Workflow, ctx: &mut ViewContext) { // Sanitize the arguments generated for the workflow by removing any illegal characters. - // Necessary since Zap AI command search sometimes provides arguments in an invalid argument format. + // Necessary since Zaplex AI command search sometimes provides arguments in an invalid argument format. let mut sanitized_content = workflow.content().to_string(); let sanitized_arguments = workflow .arguments() diff --git a/app/src/editor/view/marked_text_tests.rs b/app/src/editor/view/marked_text_tests.rs index 9e1f867e4b..34b45fa090 100644 --- a/app/src/editor/view/marked_text_tests.rs +++ b/app/src/editor/view/marked_text_tests.rs @@ -15,7 +15,7 @@ fn test_set_marked_text() { app.add_window(WindowStyle::NotStealFocus, |ctx| { let mut editor = EditorView::new_with_base_text("", Default::default(), ctx); - // Simulate typing in "nihao" into the IME and then selecting "你好" as the candidate. + // Simulate typing in "nihao" into the IME and then selecting "nihao" (Chinese) as the candidate. editor.set_marked_text("nihao", &(5..5), ctx); assert_eq!(editor.selected_text(ctx), "nihao"); editor.ime_commit("你好", ctx); @@ -24,7 +24,7 @@ fn test_set_marked_text() { editor.user_insert(", I am Teddy ", ctx); assert_eq!(editor.buffer_text(ctx), "你好, I am Teddy ".to_owned()); - // Simulate typing in "xiong" into the IME and selecting "熊" as the candidate. + // Simulate typing in "xiong" into the IME and selecting "xiong" (bear) as the candidate. editor.set_marked_text("xiong", &(5..5), ctx); assert_eq!(editor.selected_text(ctx), "xiong"); editor.ime_commit("熊", ctx); diff --git a/app/src/editor/view/mod.rs b/app/src/editor/view/mod.rs index b1abcc5725..9f973b6455 100644 --- a/app/src/editor/view/mod.rs +++ b/app/src/editor/view/mod.rs @@ -415,7 +415,7 @@ pub fn init(ctx: &mut AppContext) { // This might seem like a no-op since `ctrl-right` changes desktops on Mac by default. // However, many Mac users coming from fish shell have asked for this binding. // They've already disabled the desktop change shortcut, and are expecting that this - // binding also works in Zap. We should not break their workflow. + // binding also works in Zaplex. We should not break their workflow. FixedBinding::new( "ctrl-right", EditorAction::MoveForwardOneWord, @@ -8548,7 +8548,7 @@ impl TypedActionView for EditorView { UnhandledModifierKey(keystroke) => { if self.can_select(ctx) { // This event helps us to keep track of what key bindings users - // try to use in the editor but are currently not available in Zap. + // try to use in the editor but are currently not available in Zaplex. ctx.emit(Event::UnhandledModifierKeyOnEditor(keystroke.clone())) } } diff --git a/app/src/editor/view/voice.rs b/app/src/editor/view/voice.rs index bab2d2ef24..4a49249c12 100644 --- a/app/src/editor/view/voice.rs +++ b/app/src/editor/view/voice.rs @@ -236,14 +236,14 @@ impl EditorView { // If the keypress is not valid in the current state, we ignore it. match &self.voice_input_state { // For example, the user could press Fn in a different app, then switch focus - // to Zap and let it go - we should NOT activate voice input in this case. + // to Zaplex and let it go - we should NOT activate voice input in this case. VoiceInputState::Stopped => { if matches!(state, warpui::event::KeyState::Released) { return false; } } // Note that in reality, this case is unreachable because we stop voice input - // if the user is not focused on Zap (since we lose the ability to listen to modifier + // if the user is not focused on Zaplex (since we lose the ability to listen to modifier // key events). Thus, the user cannot enter a state where we're listening for voice input // but the key is not held already. VoiceInputState::Listening => { diff --git a/app/src/env_vars/active_env_var_collection_data.rs b/app/src/env_vars/active_env_var_collection_data.rs index d24b961381..c2816fad81 100644 --- a/app/src/env_vars/active_env_var_collection_data.rs +++ b/app/src/env_vars/active_env_var_collection_data.rs @@ -43,7 +43,7 @@ pub struct ActiveEnvVarCollectionData { impl ActiveEnvVarCollectionData { pub fn new(ctx: &mut ModelContext) -> Self { - // Zap: the original `UpdateManager` subscription received cloud-sync completion events (Create/Update/Trash + // Zaplex: the original `UpdateManager` subscription received cloud-sync completion events (Create/Update/Trash // /Untrash::Success); with no cloud it never fires, and `trash_object`/`untrash_object` are now local-only // and no longer emit `ObjectOperationComplete`. Phase 2c-1 removes the subscription + handler. // The `ObjectStoreModel` subscription is kept (local object changes still need a breadcrumbs refresh). diff --git a/app/src/env_vars/manager.rs b/app/src/env_vars/manager.rs index db0f90ac31..46d57d6746 100644 --- a/app/src/env_vars/manager.rs +++ b/app/src/env_vars/manager.rs @@ -26,8 +26,8 @@ pub enum EnvVarCollectionSource { /// Manages EnvVarCollection panes. impl EnvVarCollectionManager { pub fn new(_ctx: &mut ModelContext) -> Self { - // Zap: Like WorkflowManager — client_id→server_id conversion events are only triggered after successful cloud creation; - // Zap has no cloud = never triggered. Phase 2c-1 removes UpdateManager subscriptions and dead handler functions. + // Zaplex: Like WorkflowManager — client_id→server_id conversion events are only triggered after successful cloud creation; + // Zaplex has no cloud = never triggered. Phase 2c-1 removes UpdateManager subscriptions and dead handler functions. EnvVarCollectionManager { panes_by_hashed_id: HashMap::new(), } diff --git a/app/src/env_vars/mod.rs b/app/src/env_vars/mod.rs index fd00fb5e0e..26a427461b 100644 --- a/app/src/env_vars/mod.rs +++ b/app/src/env_vars/mod.rs @@ -229,7 +229,7 @@ pub fn serialize_variables_for_shell<'s, I: IntoIterator String { match shell_type { - // Zap doesn't support newlines in fish so we can't use env syntax + // Zaplex doesn't support newlines in fish so we can't use env syntax ShellType::Fish => { serialize_variables_internal(pairs, "set -x ", " ", ";", " ", shell_type.into()) } diff --git a/app/src/env_vars/view/env_var_collection.rs b/app/src/env_vars/view/env_var_collection.rs index a5a958c1c6..69f3f39233 100644 --- a/app/src/env_vars/view/env_var_collection.rs +++ b/app/src/env_vars/view/env_var_collection.rs @@ -75,7 +75,7 @@ const SECTION_SPACING: f32 = 16.; // Variable rows pub(super) const ROW_SPACING: f32 = 8.; -pub const EDUCATION_TEXT: &str = "Add secret or command. Zap never stores external secrets"; +pub const EDUCATION_TEXT: &str = "Add secret or command. Zaplex never stores external secrets"; const VARIABLE_FONT_SIZE: f32 = 13.; const DESCRIPTION_EDITOR_CUTOFF: f32 = 30.; const DESCRIPTION_BOTTOM_MARGIN: f32 = 12.; diff --git a/app/src/experiments/mod.rs b/app/src/experiments/mod.rs index 1d3cd4e54f..f16b26d8a5 100644 --- a/app/src/experiments/mod.rs +++ b/app/src/experiments/mod.rs @@ -1,4 +1,4 @@ -//! A framework for running A/B tests within Zap. +//! A framework for running A/B tests within Zaplex. //! //! Before starting, please read the usage guide on Notion. The guide explains //! some important constraints that are required for proper use of the framework diff --git a/app/src/external_secrets/mod.rs b/app/src/external_secrets/mod.rs index 5e30b2e5e2..a799a0ada4 100644 --- a/app/src/external_secrets/mod.rs +++ b/app/src/external_secrets/mod.rs @@ -19,12 +19,12 @@ use crate::{terminal::shell::ShellType, ui_components::icons::Icon}; lazy_static! { // Used as a delimeter to separate metadata (such as names and references) // in cases the cli tool doesn't display secrets in a common format (i.e. json) - static ref WARP_SECRET_DELIMITER: &'static str = "/warp-secret-delimeter/"; + static ref ZAPLEX_SECRET_DELIMITER: &'static str = "/warp-secret-delimeter/"; static ref LASTPASS_LIST_SECRETS_COMMAND: Vec = { vec![ "lpass".to_owned(), "ls".to_owned(), - format!("--format=%an{}%ai", *WARP_SECRET_DELIMITER), + format!("--format=%an{}%ai", *ZAPLEX_SECRET_DELIMITER), ] }; } @@ -320,7 +320,7 @@ fn parse_lastpass_secrets(output: &str) -> anyhow::Result> { let parsed_output: Vec = output .lines() .filter_map(|line| { - let parts = line.split(*WARP_SECRET_DELIMITER).collect_vec(); + let parts = line.split(*ZAPLEX_SECRET_DELIMITER).collect_vec(); if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() { Some(ExternalSecret::LastPass(LastPassSecret { name: parts[0].to_owned(), diff --git a/app/src/i18n.rs b/app/src/i18n.rs index 1aa5d74734..c32dd3defc 100644 --- a/app/src/i18n.rs +++ b/app/src/i18n.rs @@ -1,4 +1,4 @@ -//! Fluent-based localization layer for Zap Desktop. +//! Fluent-based localization layer for Zaplex Desktop. //! //! Loading chain: //! 1. `init()` is called once at startup (idempotent), loading `app/i18n/{locale}/*.ftl` via `RustEmbed` diff --git a/app/src/integration_testing/assertions.rs b/app/src/integration_testing/assertions.rs index a85e42b047..9938bfcd7b 100644 --- a/app/src/integration_testing/assertions.rs +++ b/app/src/integration_testing/assertions.rs @@ -41,7 +41,7 @@ pub fn go_online() -> TestStep { } pub fn join_a_workspace() -> TestStep { - TestStep::new("Join a Zap Drive workspace") + TestStep::new("Join a Zaplex Drive workspace") .with_action(move |app, _, _| { UserWorkspaces::handle(app).update(app, |user_workspaces, ctx| { let workspace_uid = "workspace_uid123456789".to_string().into(); @@ -141,6 +141,6 @@ pub fn assert_binding_display_string( ) } -// Zap (localization, Phase 2d-4a-1): original `assert_websocket_has_started` / +// Zaplex (localization, Phase 2d-4a-1): original `assert_websocket_has_started` / // `assert_websocket_has_not_started` assertions depended on physically removed `Listener` singleton; // no callers, removed together. diff --git a/app/src/integration_testing/notebook/assertion.rs b/app/src/integration_testing/notebook/assertion.rs index 7a2c37841b..acba17b0b0 100644 --- a/app/src/integration_testing/notebook/assertion.rs +++ b/app/src/integration_testing/notebook/assertion.rs @@ -160,7 +160,7 @@ pub fn assert_open_in_warp_banner_open(tab_index: usize, pane_index: usize) -> A terminal.read(app, |view, _ctx| { async_assert!( view.is_open_in_warp_banner_open(), - "Expected the 'Open in Zap' banner to be open" + "Expected the 'Open in Zaplex' banner to be open" ) }) }) diff --git a/app/src/integration_testing/notebook/step.rs b/app/src/integration_testing/notebook/step.rs index 6f6e44aa5f..d57ebcbced 100644 --- a/app/src/integration_testing/notebook/step.rs +++ b/app/src/integration_testing/notebook/step.rs @@ -12,7 +12,7 @@ use crate::{ model::persistence::ObjectStoreModel, update_manager::UpdateManager, Space, StoredObjectEventEntrypoint, }, - drive::ZapDriveObjectSettings, + drive::ZaplexDriveObjectSettings, integration_testing::view_getters::{notebook_view, workspace_view}, notebooks::manager::NotebookSource, server::ids::{ClientId, SyncId}, @@ -83,7 +83,7 @@ pub fn open_notebook(window_key: impl Into, notebook_key: impl Into) -> String { startup_command: None, notes: None, last_connected_at: None, + session_resilience: warp_ssh_manager::SessionResilience::default(), }; let node = SshRepository::create_server(c, parent.as_deref(), &name, &info) .unwrap_or_else(|e| panic!("create server failed: {e:?}")); diff --git a/app/src/integration_testing/subshell/step.rs b/app/src/integration_testing/subshell/step.rs index d2d01e7444..ee65520db7 100644 --- a/app/src/integration_testing/subshell/step.rs +++ b/app/src/integration_testing/subshell/step.rs @@ -78,7 +78,7 @@ pub fn enter_local_subshell_command(shell: &str) -> TestStep { } pub fn assert_subshell_banner_is_showing() -> TestStep { - TestStep::new("Assert the Warpify banner is visible") + TestStep::new("Assert the Zaplexify banner is visible") .add_assertion(move |app, window_id| { let terminal_view = single_terminal_view(app, window_id); terminal_view.read(app, |view, _ctx| { @@ -88,7 +88,7 @@ pub fn assert_subshell_banner_is_showing() -> TestStep { .block_list_mut() .active_block() .block_banner(), - Some(WithinBlockBanner::WarpifyBanner(..)) + Some(WithinBlockBanner::ZaplexifyBanner(..)) )) }) }) @@ -118,10 +118,10 @@ pub fn assert_subshell_is_bootstrapped(tab_index: usize, pane_index: usize) -> T }; match rich_content_type { - Some(RichContentType::WarpifySuccessBlock) => {} + Some(RichContentType::ZaplexifySuccessBlock) => {} _ => { return AssertionOutcome::failure( - "Warpify success block wasn't added to the blocklist".to_owned(), + "Zaplexify success block wasn't added to the blocklist".to_owned(), ); } } diff --git a/app/src/integration_testing/warp_drive/assertion.rs b/app/src/integration_testing/warp_drive/assertion.rs index 60d21690ac..18dc4b23eb 100644 --- a/app/src/integration_testing/warp_drive/assertion.rs +++ b/app/src/integration_testing/warp_drive/assertion.rs @@ -35,7 +35,7 @@ pub fn assert_warp_drive_is_open() -> AssertionCallback { workspace.read(app, |workspace, _| { async_assert!( workspace.is_warp_drive_open(), - "Expected Zap Drive to be open, but it was closed" + "Expected Zaplex Drive to be open, but it was closed" ) }) }) @@ -48,7 +48,7 @@ pub fn assert_warp_drive_is_closed() -> AssertionCallback { workspace.read(app, |workspace, _| { async_assert!( !workspace.is_warp_drive_open(), - "Expected Zap Drive to be closed, but it was open" + "Expected Zaplex Drive to be closed, but it was open" ) }) }) diff --git a/app/src/integration_testing/workflow/step.rs b/app/src/integration_testing/workflow/step.rs index f6353d82e2..d55562c2bb 100644 --- a/app/src/integration_testing/workflow/step.rs +++ b/app/src/integration_testing/workflow/step.rs @@ -7,7 +7,7 @@ use crate::{ model::persistence::ObjectStoreModel, update_manager::UpdateManager, Space, StoredObjectEventEntrypoint, }, - drive::ZapDriveObjectSettings, + drive::ZaplexDriveObjectSettings, integration_testing::view_getters::workspace_view, server::ids::{ClientId, SyncId}, workflows::{manager::WorkflowOpenSource, workflow::Workflow, WorkflowViewMode}, @@ -70,7 +70,7 @@ pub fn open_workflow(window_key: impl Into, workflow_key: impl Into for PaneTemplateType { | LeafContents::GetStarted | LeafContents::Welcome { .. } | LeafContents::AIDocument(_) - // Zap Wave 7-3: `EnvironmentManagement` arm physically removed along with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `EnvironmentManagement` arm physically removed along with ambient-agent UI subsystem. | LeafContents::SshServer { .. } | LeafContents::Sftp { .. } | LeafContents::AmbientAgent(_) => { diff --git a/app/src/launch_configs/launch_config_tests.rs b/app/src/launch_configs/launch_config_tests.rs index 1acb3ba02f..1797682772 100644 --- a/app/src/launch_configs/launch_config_tests.rs +++ b/app/src/launch_configs/launch_config_tests.rs @@ -5,7 +5,7 @@ use crate::{ AppState, BranchSnapshot, LeafContents, LeafSnapshot, NotebookPaneSnapshot, PaneFlex, PaneNodeSnapshot, SplitDirection, TabSnapshot, TerminalPaneSnapshot, WindowSnapshot, }, - drive::ZapDriveObjectSettings, + drive::ZaplexDriveObjectSettings, tab::SelectedTabColor, }; @@ -81,7 +81,7 @@ fn test_config_from_snapshot_flattens_single_pane() { custom_vertical_tabs_title: None, contents: LeafContents::Notebook(NotebookPaneSnapshot::NotebookObject { notebook_id: None, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }), }), ), @@ -151,7 +151,7 @@ fn test_config_from_snapshot_filters_panes() { custom_vertical_tabs_title: None, contents: LeafContents::Notebook(NotebookPaneSnapshot::NotebookObject { notebook_id: None, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }), }), ), @@ -215,7 +215,7 @@ fn test_config_from_snapshot_filters_tabs() { custom_vertical_tabs_title: None, contents: LeafContents::Notebook(NotebookPaneSnapshot::NotebookObject { notebook_id: None, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }), }), )], @@ -286,7 +286,7 @@ fn test_config_with_active_tab_index_and_filtered_tabs() { contents: LeafContents::Notebook( NotebookPaneSnapshot::NotebookObject { notebook_id: None, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }, ), }), @@ -379,7 +379,7 @@ fn test_config_with_active_tab_being_filtered() { contents: LeafContents::Notebook( NotebookPaneSnapshot::NotebookObject { notebook_id: None, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }, ), }), diff --git a/app/src/launch_configs/save_modal.rs b/app/src/launch_configs/save_modal.rs index acad713871..a3d45756fd 100644 --- a/app/src/launch_configs/save_modal.rs +++ b/app/src/launch_configs/save_modal.rs @@ -687,7 +687,7 @@ impl TypedActionView for LaunchConfigSaveModal { // TODO(vorporeal): We should figure out a better way to handle the // interactions with the filesystem here, whether it's compiling out // the save modal more completely or doing something else. Perhaps - // this will become moot when we put launch configs in Zap Drive. + // this will become moot when we put launch configs in Zaplex Drive. let action = match action { ActionRequest::Action(action) => action.clone(), ActionRequest::Enter => LaunchConfigSaveAction::from_state(&self.save_state), diff --git a/app/src/lib.rs b/app/src/lib.rs index 8b90bebb2e..2a35ed277f 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -1,6 +1,6 @@ // Suppress warnings about rustdoc style. #![allow(clippy::doc_lazy_continuation)] -// Orphaned code remaining from upstream Zap pruning; temporarily retained with unified dead_code suppression. +// Orphaned code remaining from upstream Zaplex pruning; temporarily retained with unified dead_code suppression. #![allow(dead_code)] mod ai; @@ -16,6 +16,7 @@ mod banner; mod changelog_model; mod chip_configurator; mod cloud_object; +mod cockpit; mod code; mod code_review; mod coding_entrypoints; @@ -60,7 +61,6 @@ mod plugin; mod prefix; #[cfg(target_os = "macos")] mod preview_config_migration; -mod pricing; mod profiling; mod projects; mod prompt; @@ -150,7 +150,7 @@ use code::editor_management::CodeManager; use code::opened_files::OpenedFilesModel; use code_review::GlobalCodeReviewModel; use quit_warning::UnsavedStateSummary; -// Zap (localization, Phase 4): `ServerVoiceTranscriber` previously used for default VoiceTranscriber injection; now using `VoiceTranscriber::disabled()`, same-name import retained for now. +// Zaplex (localization, Phase 4): `ServerVoiceTranscriber` previously used for default VoiceTranscriber injection; now using `VoiceTranscriber::disabled()`, same-name import retained for now. #[cfg(feature = "local_fs")] use settings::import::model::ImportedConfigModel; @@ -310,18 +310,18 @@ pub struct Assets; pub static ASSETS: Assets = Assets; -/// Launch mode for how to start up Zap. +/// Launch mode for how to start up Zaplex. #[allow(clippy::large_enum_variant)] pub enum LaunchMode { /// Run the regular GUI application. App { args: warp_cli::AppArgs, - /// API key for server authentication, if provided via `--api-key` or `WARP_API_KEY`. + /// API key for server authentication, if provided via `--api-key` or `ZAPLEX_API_KEY`. /// Only used on dogfood channels. api_key: Option, }, - /// Run the Zap command-line SDK. + /// Run the Zaplex command-line SDK. CommandLine { command: warp_cli::CliCommand, global_options: GlobalOptions, @@ -410,7 +410,7 @@ impl LaunchMode { } } - /// Returns `true` if Zap should run headlessly, without a visible UI. + /// Returns `true` if Zaplex should run headlessly, without a visible UI. fn is_headless(&self) -> bool { match self { LaunchMode::CommandLine { command, .. } => match command { @@ -660,7 +660,7 @@ pub fn run() -> Result<()> { // instead of launching the GUI app. let is_cli_binary = cfg!(feature = "standalone") || warp_cli::binary_name().is_some_and(|name| name.starts_with("oz")) - || std::env::var_os("WARP_CLI_MODE").is_some(); + || std::env::var_os("ZAPLEX_CLI_MODE").is_some(); if is_cli_binary { warp_cli::Args::clap_command().print_help()?; return Ok(()); @@ -675,7 +675,7 @@ pub fn run() -> Result<()> { /// Runs an integration test using the provided test driver. pub fn run_integration_test(driver: TestDriver) -> Result<()> { - let is_integration_test = std::env::var("WARP_INTEGRATION").is_ok(); + let is_integration_test = std::env::var("ZAPLEX_INTEGRATION").is_ok(); let launch = LaunchMode::Test { driver: Box::new(Some(driver)), is_integration_test, @@ -789,15 +789,15 @@ fn run_internal(mut launch_mode: LaunchMode) -> Result<()> { launch_mode.args().as_ref(), ) { // If we were able to contact an existing application instance, quit - - // we only want to run a single instance of Zap at a time. + // we only want to run a single instance of Zaplex at a time. Ok(_) => std::process::exit(0), - // If Zap isn't already running, we're good to go. + // If Zaplex isn't already running, we're good to go. Err(app_services::linux::StartupArgsForwardingError::NoExistingInstance) => {} // If we just finished an auto-update, we should continue running. Err(app_services::linux::StartupArgsForwardingError::IgnoredAfterAutoUpdate) => {} // If we were unable to perform the forwarding for an unknown reason, // it's better to run a second instance than potentially end up in a - // state where Zap refuses to run even a first instance. + // state where Zaplex refuses to run even a first instance. Err(err) => { let err = anyhow::Error::from(err).context("Failed to forward startup args"); log::error!("{err:#}"); @@ -812,15 +812,15 @@ fn run_internal(mut launch_mode: LaunchMode) -> Result<()> { launch_mode.args().as_ref(), ) { // If we were able to contact an existing application instance, quit - - // we only want to run a single instance of Zap at a time. + // we only want to run a single instance of Zaplex at a time. Ok(_) => std::process::exit(0), - // If Zap isn't already running, we're good to go. + // If Zaplex isn't already running, we're good to go. Err(app_services::windows::StartupArgsForwardingError::NoExistingInstance) => {} // If we just finished an auto-update, we should continue running. Err(app_services::windows::StartupArgsForwardingError::IgnoredAfterAutoUpdate) => {} // If we were unable to perform the forwarding for an unknown reason, // it's better to run a second instance than potentially end up in a - // state where Zap refuses to run even a first instance. + // state where Zaplex refuses to run even a first instance. Err(err) => { let err = anyhow::Error::from(err).context("Failed to forward startup args"); log::error!("{err:#}"); @@ -829,7 +829,7 @@ fn run_internal(mut launch_mode: LaunchMode) -> Result<()> { } } - // Sets up a Job Object that we associate with the Zap process to handle + // Sets up a Job Object that we associate with the Zaplex process to handle // shared fate with its child processes. This should be called before we // start spawning any child processes. #[cfg(windows)] @@ -902,7 +902,7 @@ fn run_internal(mut launch_mode: LaunchMode) -> Result<()> { let force_x11 = ForceX11::read_from_preferences(prefs_for_public_settings) .unwrap_or(ForceX11::default_value()); - // Force use of wayland if the user has passed the `WARP_ENABLE_WAYLAND` env var. + // Force use of wayland if the user has passed the `ZAPLEX_ENABLE_WAYLAND` env var. let allow_wayland = linux::is_wayland_env_var_set() || !force_x11; app_builder.force_x11(!allow_wayland); } @@ -1062,10 +1062,10 @@ fn initialize_app( let update_http_client = Arc::new(http_client::Client::new()); - // Zap: retain AuthStateProvider singleton only for legacy call sites to read local placeholder user state. + // Zaplex: retain AuthStateProvider singleton only for legacy call sites to read local placeholder user state. ctx.add_singleton_model(|_ctx| AuthStateProvider::new(auth_state.clone())); - // Zap Wave 3-1: AuthManager has been localized to stub, no longer injecting server_api / auth_client. + // Zaplex Wave 3-1: AuthManager has been localized to stub, no longer injecting server_api / auth_client. ctx.add_singleton_model(AuthManager::new); ctx.add_singleton_model(|_ctx| GPUState::new()); @@ -1251,7 +1251,6 @@ fn initialize_app( ctx.add_singleton_model(|_| SettingsPaneManager::new()); ctx.add_singleton_model(|_| AIFactManager::new()); ctx.add_singleton_model(|_| ExecutionProfileEditorManager::default()); - ctx.add_singleton_model(|_| pricing::PricingInfoModel::new()); ctx.add_singleton_model(|_| { ManagedSecretManager::new( Arc::new(local_managed_secrets::DisabledManagedSecretsClient), @@ -1282,7 +1281,7 @@ fn initialize_app( ctx.add_singleton_model(|_ctx| SyncedInputState::new()); ctx.add_singleton_model(remote_server::manager::RemoteServerManager::new); - // Zap Wave 6-1: `remote_server::wire_auth_token_rotation(ctx)` call removed together with + // Zaplex Wave 6-1: `remote_server::wire_auth_token_rotation(ctx)` call removed together with // server API token rotation event + `wire_auth_token_rotation` function body. log::info!( @@ -1298,7 +1297,7 @@ fn initialize_app( apply_scroll_multiplier(event, ctx); }); - // Rewrite recognized Zap web URLs (sessions, Drive, settings, home) into local + // Rewrite recognized Zaplex web URLs (sessions, Drive, settings, home) into local // intent URLs when possible so they open directly in the desktop app. ctx.set_before_open_url(|url_str, _ctx| { if let Ok(url) = Url::parse(url_str) { @@ -1321,7 +1320,7 @@ fn initialize_app( let user_is_logged_in = auth_state.is_logged_in(); if user_is_logged_in { - // Zap local auth facade has already loaded identity snapshot at `AuthState::initialize`. + // Zaplex local auth facade has already loaded identity snapshot at `AuthState::initialize`. // Startup phase no longer triggers an additional cloud token refresh / auth refresh. // Set the first frame callback to record the app's startup time. @@ -1331,7 +1330,7 @@ fn initialize_app( ctx.on_first_frame_drawn(move |ctx| { let timing_data = IntervalTimer::handle(ctx).update(ctx, |timer, _| { timer.mark_interval_end("FIRST_FRAME_DRAWN"); - // Local tuning exit: when WARP_STARTUP_TRACE=1, dump complete startup sequence table to stderr. + // Local tuning exit: when ZAPLEX_STARTUP_TRACE=1, dump complete startup sequence table to stderr. // Does not affect telemetry logic, for developer use only. timer.print_trace_to_stderr_if_enabled(); timer.compute_stats() @@ -1371,7 +1370,7 @@ fn initialize_app( // This is sent immediately in case they quit the app on the signup screen. send_telemetry_sync_from_app_ctx!(TelemetryEvent::LoggedOutStartup, ctx); // Unauthenticated users also need to see startup sequence (BYOP scenarios are majority), after first frame - // dump WARP_STARTUP_TRACE table once. No telemetry, does not affect logic. + // dump ZAPLEX_STARTUP_TRACE table once. No telemetry, does not affect logic. ctx.on_first_frame_drawn(move |ctx| { IntervalTimer::handle(ctx).update(ctx, |timer, _| { timer.mark_interval_end("FIRST_FRAME_DRAWN"); @@ -1478,11 +1477,11 @@ fn initialize_app( ai::blocklist::block::status_bar::init(ctx); drive::index::init(ctx); ai_assistant::panel::init(ctx); - // Zap Wave 7-2: `settings_view::update_environment_form::init` removed together with cloud ambient agent + // Zaplex Wave 7-2: `settings_view::update_environment_form::init` removed together with cloud ambient agent // main subsystem. env_vars::env_var_collection_block::init(ctx); terminal::ssh::install_tmux::init(ctx); - terminal::ssh::warpify::init(ctx); + terminal::ssh::zaplexify::init(ctx); terminal::ssh::error::init(ctx); context_chips::display_menu::init(ctx); context_chips::node_version_popup::init(ctx); @@ -1537,7 +1536,7 @@ fn initialize_app( #[cfg(feature = "voice_input")] ctx.add_singleton_model(voice_input::VoiceInput::new); ctx.add_singleton_model(|_| { - // Zap (localization, Phase 4): originally default injected `ServerVoiceTranscriber` using cloud Wispr STT. + // Zaplex (localization, Phase 4): originally default injected `ServerVoiceTranscriber` using cloud Wispr STT. // In localization scenario, cloud speech transcription is unavailable; changed to `disabled()` to make upper-level `transcriber()` return None, // voice input UI becomes collection-only without transcription (connect local STT later). VoiceTranscriber::disabled() @@ -1562,7 +1561,7 @@ fn initialize_app( ) }); - // Zap (Wave 4): after SyncQueue is completely deleted, no more `unsynced_actions` / + // Zaplex (Wave 4): after SyncQueue is completely deleted, no more `unsynced_actions` / // `objects_with_pending_changes` tracking; local writes are "complete". let _ = (&object_store_model, &object_actions); // Retain `ObjectTypeAndId` import for other modules in the same crate to access via `crate::` path. @@ -1610,11 +1609,11 @@ fn initialize_app( ctx.add_singleton_model(|_| AudibleBell::new()); - // Zap: UpdateManager only handles local cloud object memory/SQLite synchronization, no longer injects cloud client. + // Zaplex: UpdateManager only handles local cloud object memory/SQLite synchronization, no longer injects cloud client. ctx.add_singleton_model(|ctx| UpdateManager::new(persistence_writer.sender(), ctx)); let toml_file_path = settings::user_preferences_toml_file_path(); - // Zap (localization, Phase 5): `PreferencesSyncer` has been physically deleted. Original syncer handled local + // Zaplex (localization, Phase 5): `PreferencesSyncer` has been physically deleted. Original syncer handled local // settings.toml and cloud preferences bidirectional sync, in localization scenario only retain local toml loading. let _ = toml_file_path; let _ = startup_toml_parse_error_for_syncer; @@ -1631,6 +1630,9 @@ fn initialize_app( ctx.add_singleton_model(FileMCPWatcher::new); ctx.add_singleton_model(FileBasedMCPManager::new); + // Cockpit data spine: registered after HomeDirectoryWatcher (which it subscribes to). + ctx.add_singleton_model(cockpit::CockpitModel::new); + // TemplatableMCPServerManager must be registered after UpdateManager and MCPServerManager so it can migrate legacy MCPs on start up // It should also be registered after FileBasedMCPManager so it can receive file-based server updates. ctx.add_singleton_model(|ctx| { @@ -1669,7 +1671,7 @@ fn initialize_app( ctx.add_singleton_model(NotebookKeybindings::new); ctx.add_singleton_model(TerminalKeybindings::new); ctx.add_singleton_model(|_| ActiveSession::default()); - // Zap (localization, Phase 2d-4a-1): original `Listener` singleton handled cloud cloud_objects RTC WebSocket, + // Zaplex (localization, Phase 2d-4a-1): original `Listener` singleton handled cloud cloud_objects RTC WebSocket, // after 2b-1, `start_listener` is already no-op, this entire file and singleton injection are physically deleted. #[cfg(all(not(target_family = "wasm"), feature = "local_tty"))] @@ -1817,7 +1819,7 @@ fn app_callbacks(is_integration_test: bool) -> warpui::platform::AppCallbacks { }); // We want to tear down the terminal server before relaunching for - // autoupdate, to ensure we're not running any extra Zap processes + // autoupdate, to ensure we're not running any extra Zaplex processes // when we bring up the new process. Additionally, this must occur // after terminating the persistence writer, so we don't keep track // of the fact that the shell sessions terminated. @@ -2277,7 +2279,7 @@ pub fn enabled_features() -> HashSet { #[cfg(all(debug_assertions, not(windows)))] flags.insert(FeatureFlag::ServerFileBrowser); - // Issue #72: HTTP proxy settings page. Does not use channel check, all channels including zap-oss + // Issue #72: HTTP proxy settings page. Does not use channel check, all channels including zaplex // enabled by default, as basic capability for enterprise VPN / corporate proxy scenarios. flags.insert(FeatureFlag::HttpProxySettings); @@ -2338,7 +2340,7 @@ pub fn enabled_features() -> HashSet { FeatureFlag::ShellSelector, #[cfg(feature = "block_toolbelt_save_as_workflow")] FeatureFlag::BlockToolbeltSaveAsWorkflow, - // Zap Wave 7-2: `CloudEnvironments` FeatureFlag removed together with cloud ambient agent main subsystem + // Zaplex Wave 7-2: `CloudEnvironments` FeatureFlag removed together with cloud ambient agent main subsystem // — `warp environment` subcommand + `--environment` parameter sunset together. #[cfg(all(feature = "simulate_github_unauthed", debug_assertions))] FeatureFlag::SimulateGithubUnauthed, @@ -2508,7 +2510,7 @@ pub fn enabled_features() -> HashSet { FeatureFlag::ServerFileBrowser, #[cfg(feature = "allow_ignoring_input_suggestions")] FeatureFlag::AllowIgnoringInputSuggestions, - // Zap (localization): cloud entry point for ambient agent / agent management view has been physically shut down. + // Zaplex (localization): cloud entry point for ambient agent / agent management view has been physically shut down. // BYOP agent local execution does not depend on these entry points. #[cfg(feature = "code_launch_modal")] FeatureFlag::CodeLaunchModal, @@ -2601,7 +2603,7 @@ pub fn enabled_features() -> HashSet { #[cfg(feature = "bundled_skills")] FeatureFlag::BundledSkills, #[cfg(feature = "open_warp_launch_modal")] - FeatureFlag::ZapLaunchModal, + FeatureFlag::ZaplexLaunchModal, #[cfg(feature = "new_tab_styling")] FeatureFlag::NewTabStyling, #[cfg(feature = "skill_arguments")] @@ -2623,7 +2625,7 @@ pub fn enabled_features() -> HashSet { #[cfg(feature = "directory_tab_colors")] FeatureFlag::DirectoryTabColors, #[cfg(feature = "open_warp_new_settings_modes")] - FeatureFlag::ZapNewSettingsModes, + FeatureFlag::ZaplexNewSettingsModes, #[cfg(feature = "hoa_code_review")] FeatureFlag::HoaCodeReview, #[cfg(feature = "vertical_tabs")] @@ -2642,8 +2644,8 @@ pub fn enabled_features() -> HashSet { FeatureFlag::CLIAgentRichInput, #[cfg(feature = "transfer_control_tool")] FeatureFlag::TransferControlTool, - #[cfg(feature = "warpify_footer")] - FeatureFlag::WarpifyFooter, + #[cfg(feature = "zaplexify_footer")] + FeatureFlag::ZaplexifyFooter, #[cfg(feature = "solo_user_byok")] FeatureFlag::SoloUserByok, #[cfg(feature = "hoa_onboarding_flow")] diff --git a/app/src/local_managed_secrets.rs b/app/src/local_managed_secrets.rs index d63e0634c0..64dc125e2d 100644 --- a/app/src/local_managed_secrets.rs +++ b/app/src/local_managed_secrets.rs @@ -1,7 +1,7 @@ -//! Zap local managed-secrets client. +//! Zaplex local managed-secrets client. //! -//! Upstream Zap originally maintained team/user-managed secrets here by calling cloud endpoints via server_api. -//! Zap retains the `warp_managed_secrets` crate for local feature reuse, but all cloud-managed secret +//! Upstream Zaplex originally maintained team/user-managed secrets here by calling cloud endpoints via server_api. +//! Zaplex retains the `warp_managed_secrets` crate for local feature reuse, but all cloud-managed secret //! operations are unreachable: queries return empty set, write operations and OIDC token issuance return disabled error. use std::collections::HashMap; @@ -33,11 +33,11 @@ impl ManagedSecretsClient for DisabledManagedSecretsClient { _encrypted_value: String, _description: Option, ) -> Result { - Err(anyhow!("Cloud managed secrets disabled in Zap")) + Err(anyhow!("Cloud managed secrets disabled in Zaplex")) } async fn delete_managed_secret(&self, _owner: SecretOwner, _name: String) -> Result<()> { - Err(anyhow!("Cloud managed secrets disabled in Zap")) + Err(anyhow!("Cloud managed secrets disabled in Zaplex")) } async fn update_managed_secret( @@ -47,7 +47,7 @@ impl ManagedSecretsClient for DisabledManagedSecretsClient { _encrypted_value: Option, _description: Option, ) -> Result { - Err(anyhow!("Cloud managed secrets disabled in Zap")) + Err(anyhow!("Cloud managed secrets disabled in Zaplex")) } async fn list_secrets(&self) -> Result> { @@ -65,6 +65,6 @@ impl ManagedSecretsClient for DisabledManagedSecretsClient { &self, _options: warp_managed_secrets::client::IdentityTokenOptions, ) -> Result { - Err(anyhow!("Task identity token issuance disabled in Zap")) + Err(anyhow!("Task identity token issuance disabled in Zaplex")) } } diff --git a/app/src/notebooks/editor/embedded_item.rs b/app/src/notebooks/editor/embedded_item.rs index b7f422eb7b..9194b50e7f 100644 --- a/app/src/notebooks/editor/embedded_item.rs +++ b/app/src/notebooks/editor/embedded_item.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, ops::Range, sync::Arc}; use itertools::Itertools; -use markdown_parser::html_parser::WARP_EMBED_ATTRIBUTE_NAME; +use markdown_parser::html_parser::ZAPLEX_EMBED_ATTRIBUTE_NAME; use pathfinder_color::ColorU; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_yaml::Mapping; @@ -310,7 +310,7 @@ impl EmbeddedItem for EmbeddedWorkflow { // If the workflow is no longer accessible or is trashed, set the content to // an empty string. But we should still keep the HTML element formatting and - // attributes so we could re-parse the ID and metadata when pasted into Zap. + // attributes so we could re-parse the ID and metadata when pasted into Zaplex. let workflow_content = workflow .and_then(|workflow| { if !workflow.is_trashed(object_store_model) { @@ -326,7 +326,7 @@ impl EmbeddedItem for EmbeddedWorkflow { html: EmbeddedItemHTMLRepresentation { element_name: "pre", content: workflow_content, - attributes: HashMap::from([(WARP_EMBED_ATTRIBUTE_NAME, self.hashed_id())]), + attributes: HashMap::from([(ZAPLEX_EMBED_ATTRIBUTE_NAME, self.hashed_id())]), }, } } diff --git a/app/src/notebooks/editor/mod.rs b/app/src/notebooks/editor/mod.rs index 533900008a..4e71ec66bb 100644 --- a/app/src/notebooks/editor/mod.rs +++ b/app/src/notebooks/editor/mod.rs @@ -330,7 +330,7 @@ impl<'a> From<&'a BufferBlockStyle> for BlockType { /// Wrapper around the shared [`Workflow`] type with additional context for workflows contained /// within a notebook. /// -/// This may be a command block that's part of the notebook text, or an embedded Zap Drive workflow. +/// This may be a command block that's part of the notebook text, or an embedded Zaplex Drive workflow. #[derive(Debug, Clone, PartialEq)] pub struct NotebookWorkflow { /// Definition of the workflow itself. diff --git a/app/src/notebooks/editor/view.rs b/app/src/notebooks/editor/view.rs index b34b8148c9..73d0030d9a 100644 --- a/app/src/notebooks/editor/view.rs +++ b/app/src/notebooks/editor/view.rs @@ -890,7 +890,7 @@ pub enum EditorViewAction { OpenEmbeddedObjectSearch, RemoveEmbeddingAt(CharOffset), MiddleClickPaste, - /// Open a file. If open_in_warp is true, open in Zap's code editor; otherwise use external editor. + /// Open a file. If open_in_warp is true, open in Zaplex's code editor; otherwise use external editor. OpenFile { path: PathBuf, line_and_column_num: Option, @@ -3341,7 +3341,7 @@ impl RichTextAction for EditorViewAction { ); let multiselect = modifiers.alt && FeatureFlag::RichTextMultiselect.is_enabled(); - // The first mouse down to bring focus to a Zap window will not have a corresponding mouse up. + // The first mouse down to bring focus to a Zaplex window will not have a corresponding mouse up. // We ignore it, and they can click again. if is_first_mouse { return None; diff --git a/app/src/notebooks/file/mod.rs b/app/src/notebooks/file/mod.rs index 41bf85ef9c..51243456f3 100644 --- a/app/src/notebooks/file/mod.rs +++ b/app/src/notebooks/file/mod.rs @@ -80,7 +80,7 @@ pub enum MarkdownDisplayMode { Raw, } -/// View for a read-only notebook backed by a file, rather than Zap Drive. +/// View for a read-only notebook backed by a file, rather than Zaplex Drive. pub struct FileNotebookView { /// The location of the open file. This is cached for displaying the title and breadcrumbs. location: Option, diff --git a/app/src/notebooks/link.rs b/app/src/notebooks/link.rs index c3cd6e2b0e..ef73b29136 100644 --- a/app/src/notebooks/link.rs +++ b/app/src/notebooks/link.rs @@ -21,7 +21,7 @@ use crate::util::file::external_editor::EditorSettings; #[cfg(feature = "local_fs")] use crate::util::openable_file_type::{is_supported_image_file, resolve_file_target, FileTarget}; use crate::{ - drive::ZapDriveObjectArgs, + drive::ZaplexDriveObjectArgs, terminal::model::session::Session, uri::parse_url_paths::{get_item_data_from_warp_link, WarpWebLink}, workspace::ActiveSession, @@ -43,7 +43,7 @@ pub enum LinkTarget { /// The base session when the link was resolved. It's stored here in case it changes /// between resolving and opening the link. session: Arc, - /// Whether or not this file is a Markdown file viewable in Zap. + /// Whether or not this file is a Markdown file viewable in Zaplex. is_markdown: bool, }, LocalDirectory { @@ -261,13 +261,13 @@ impl NotebookLinks { /// Open a resolved link: /// * URLs are opened in the web browser or system-default application. - /// * Markdown files are opened in Zap (if the `FileNotebooks` feature flag is enabled). + /// * Markdown files are opened in Zaplex (if the `FileNotebooks` feature flag is enabled). /// * Other files are opened in the configured editor or system-default application. pub fn open(&self, link: LinkTarget, ctx: &mut ModelContext) { match link { LinkTarget::Url(url) => { if let Some(WarpWebLink::DriveObject(args)) = get_item_data_from_warp_link(&url) { - return ctx.emit(LinkEvent::ZapDriveLink { + return ctx.emit(LinkEvent::ZaplexDriveLink { open_warp_drive_args: *args, }); } @@ -303,7 +303,7 @@ impl NotebookLinks { is_markdown: true, .. } => { - // The default action for Markdown file links is to open them in Zap. As a + // The default action for Markdown file links is to open them in Zaplex. As a // secondary action, open them in an external app. open_file(path.clone(), *line_and_column, ctx) } @@ -410,8 +410,8 @@ pub enum LinkEvent { path: PathBuf, session: Arc, }, - ZapDriveLink { - open_warp_drive_args: ZapDriveObjectArgs, + ZaplexDriveLink { + open_warp_drive_args: ZaplexDriveObjectArgs, }, /// This event tells the parent pane group to open a new terminal session in the given /// directory. @@ -420,7 +420,7 @@ pub enum LinkEvent { /// resolution has changed. RefreshLinks, #[cfg(feature = "local_fs")] - /// Emitted when a file should be opened in Zap (code editor or markdown viewer). + /// Emitted when a file should be opened in Zaplex (code editor or markdown viewer). OpenFileWithTarget { path: PathBuf, target: FileTarget, diff --git a/app/src/notebooks/manager.rs b/app/src/notebooks/manager.rs index 2fe2f375f7..dde1be494b 100644 --- a/app/src/notebooks/manager.rs +++ b/app/src/notebooks/manager.rs @@ -13,7 +13,7 @@ use crate::{ model::persistence::{ObjectStoreEvent, ObjectStoreModel}, Owner, }, - drive::ZapDriveObjectSettings, + drive::ZaplexDriveObjectSettings, pane_group::{NotebookPane, PaneContent}, safe_debug, safe_warn, server::ids::SyncId, @@ -32,7 +32,7 @@ mod tests; /// [pane group](crate::pane_group::PaneGroup) views, as they contain all open notebook panes. /// /// The overall flow is: -/// 1. A `Workspace` is asked to open a notebook (from the Zap Drive index, universal search, etc.). +/// 1. A `Workspace` is asked to open a notebook (from the Zaplex Drive index, universal search, etc.). /// 2. It checks the `NotebookManager` to see if the notebook is already open. /// 3. If it is, the existing notebook pane is focused (this may be in another window). /// 4. If not, the `Workspace` uses the `NotebookManager` to create a new notebook pane and @@ -179,7 +179,7 @@ impl NotebookManager { pub fn create_pane( &mut self, source: &NotebookSource, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, window_id: WindowId, ctx: &mut ModelContext, ) -> NotebookPane { diff --git a/app/src/notebooks/manager_tests.rs b/app/src/notebooks/manager_tests.rs index 16cc45ae95..92dc684709 100644 --- a/app/src/notebooks/manager_tests.rs +++ b/app/src/notebooks/manager_tests.rs @@ -94,7 +94,7 @@ fn initialize_app(app: &mut App) -> TestState { let (sender, receiver) = mpsc::sync_channel(10); app.add_singleton_model(|ctx| UpdateManager::new(Some(sender), ctx)); - // Zap (Wave 4): SyncQueue was removed entirely, so the original `sync_queue.start_dequeueing(ctx)` no longer applies. + // Zaplex (Wave 4): SyncQueue was removed entirely, so the original `sync_queue.start_dequeueing(ctx)` no longer applies. app.add_singleton_model(ObjectStoreViewModel::mock); let manager = app.add_singleton_model(NotebookManager::mock); diff --git a/app/src/notebooks/notebook.rs b/app/src/notebooks/notebook.rs index ddd3c5c788..53ffff9c2e 100644 --- a/app/src/notebooks/notebook.rs +++ b/app/src/notebooks/notebook.rs @@ -53,7 +53,7 @@ use crate::{ drive::{ drive_helpers::has_feature_gated_anonymous_user_reached_notebook_limit, export::ExportManager, items::WarpDriveItemId, ObjectTypeAndId, - ZapDriveObjectSettings, + ZaplexDriveObjectSettings, }, editor::{ EditOrigin, EditorView, Event as EditorEvent, InteractionState, @@ -224,7 +224,7 @@ enum NotebookSyncError { FeatureNotAvailable, } -/// A view that allows viewing/execution and editing of a Zap notebook. +/// A view that allows viewing/execution and editing of a Zaplex notebook. /// We don't currently persist any data. pub struct NotebookView { /// This is a stateful component that shows information about the notebook like its location @@ -1515,7 +1515,7 @@ impl NotebookView { pub fn wait_for_initial_load_then_load( &mut self, notebook_id: SyncId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, window_id: WindowId, ctx: &mut ViewContext, ) { @@ -1562,7 +1562,7 @@ impl NotebookView { fn fetch_and_load_notebook( &mut self, notebook_id: ServerId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, window_id: WindowId, ctx: &mut ViewContext, ) { @@ -1606,7 +1606,7 @@ impl NotebookView { pub fn load( &mut self, notebook: NotebookObject, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) -> SpawnedFutureHandle { self.set_title(¬ebook.model().title, ctx); @@ -1679,7 +1679,7 @@ impl NotebookView { } }); self.update_breadcrumbs(ctx); - // Zap Phase 2a: invitee-driven sharing dialog removed. + // Zaplex Phase 2a: invitee-driven sharing dialog removed. if let Some(focused_folder_id) = settings.focused_folder_id.map(SyncId::ServerId) { self.view_in_warp_drive( WarpDriveItemId::Object(ObjectTypeAndId::Folder(focused_folder_id)), @@ -1855,7 +1855,7 @@ impl NotebookView { if let Some(notebook) = ObjectStoreModel::as_ref(ctx).get_notebook(&id) { self.load( notebook.clone(), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, ); } diff --git a/app/src/notebooks/notebook/details_bar.rs b/app/src/notebooks/notebook/details_bar.rs index 71f86af11e..2a7d10e1e5 100644 --- a/app/src/notebooks/notebook/details_bar.rs +++ b/app/src/notebooks/notebook/details_bar.rs @@ -30,7 +30,7 @@ use crate::{ use super::{super::active_notebook_data::ActiveNotebookData, NotebookAction, EDIT_BUTTON_MARGIN}; /// Component to show details about a notebook: -/// * Interactive breadcrumbs for its location within Zap Drive +/// * Interactive breadcrumbs for its location within Zaplex Drive /// * The current editor of the notebook /// * Grab-the-baton UI controls pub struct DetailsBar { diff --git a/app/src/notebooks/notebook_tests.rs b/app/src/notebooks/notebook_tests.rs index b463adfe63..4961737a42 100644 --- a/app/src/notebooks/notebook_tests.rs +++ b/app/src/notebooks/notebook_tests.rs @@ -20,7 +20,7 @@ use crate::{ update_manager::UpdateManager, Owner, StoredObjectMetadata, StoredObjectPermissions, }, - drive::ZapDriveObjectSettings, + drive::ZaplexDriveObjectSettings, editor::{DisplayPoint, EditorAction, SelectAction}, network::NetworkStatus, notebooks::{ @@ -124,7 +124,7 @@ fn open_notebook( notebook: NotebookObject, ) -> BoxFuture<'static, ()> { let load_future = handle.update(app, |view, ctx| { - view.load(notebook, &ZapDriveObjectSettings::default(), ctx) + view.load(notebook, &ZaplexDriveObjectSettings::default(), ctx) }); app.update(|ctx| ctx.await_spawned_future(load_future.future_id())) } @@ -442,7 +442,7 @@ fn test_not_eager_baton_grab_different_editor() { } /// Test to make sure we do not eagerly enter edit mode when another editor took the baton -/// while Zap was closed. +/// while Zaplex was closed. #[test] fn test_baton_grab_editor_changed_offline() { App::test((), |mut app| async move { @@ -553,7 +553,7 @@ fn test_close_unmodified() { initialize_app(&mut app); initial_load(&mut app, vec![]).await; - // Zap(Wave 4): SyncQueue completely removed; original stop_dequeueing + queue length assertions no longer apply. + // Zaplex(Wave 4): SyncQueue completely removed; original stop_dequeueing + queue length assertions no longer apply. let cloud_notebook = mock_stored_notebook("Test", "Some text"); let notebook_id = cloud_notebook.id; @@ -575,7 +575,7 @@ fn test_close_unmodified() { .expect("Notebook should exist"); assert!(!object.metadata().has_pending_content_changes()); - // Zap(Wave 4): SyncQueue completely removed; original `sync_queue.is_empty()` assertion no longer applies. + // Zaplex(Wave 4): SyncQueue completely removed; original `sync_queue.is_empty()` assertion no longer applies. }) }); } diff --git a/app/src/notifications/item.rs b/app/src/notifications/item.rs index 6418c209f5..f532985d6e 100644 --- a/app/src/notifications/item.rs +++ b/app/src/notifications/item.rs @@ -42,7 +42,7 @@ impl NotificationFilter { } } -/// Source agent of a notification. `Oz` is Zap's native local BYOP agent; `CLI(...)` is a +/// Source agent of a notification. `Oz` is Zaplex's native local BYOP agent; `CLI(...)` is a /// third-party CLI agent (Claude Code / Codex / DeepSeek, etc.). #[derive(Debug, Clone, Copy)] #[allow(clippy::upper_case_acronyms)] diff --git a/app/src/palette.rs b/app/src/palette.rs index 743f68c0cc..977f48318d 100644 --- a/app/src/palette.rs +++ b/app/src/palette.rs @@ -5,7 +5,7 @@ pub enum PaletteMode { Command, Navigation, LaunchConfig, - ZapDrive, + ZaplexDrive, Files, Conversations, } diff --git a/app/src/pane_group/mod.rs b/app/src/pane_group/mod.rs index aa00a29133..ba68631e0f 100644 --- a/app/src/pane_group/mod.rs +++ b/app/src/pane_group/mod.rs @@ -104,7 +104,7 @@ use crate::banner::{Banner, BannerEvent, BannerState, BannerTextContent, Dismiss use crate::channel::{Channel, ChannelState}; use crate::code::view::CodeView; use crate::drive::items::WarpDriveItemId; -use crate::drive::{ObjectTypeAndId, ZapDriveObjectArgs}; +use crate::drive::{ObjectTypeAndId, ZaplexDriveObjectArgs}; use crate::features::FeatureFlag; use crate::launch_configs::launch_config::{self, PaneMode, PaneTemplateType}; use crate::persistence::ModelEvent; @@ -122,7 +122,7 @@ use crate::terminal::local_tty; use crate::terminal::model::session::Session; use crate::terminal::session_settings::NewSessionSource; use crate::terminal::session_settings::SessionSettings; -// Zap: removed ShareSessionModal import (cloud shared session modal) +// Zaplex: removed ShareSessionModal import (cloud shared session modal) use crate::terminal::shared_session::IsSharedSessionCreator; use crate::terminal::view::ssh_file_upload::FileUploadId; use crate::terminal::view::{ @@ -157,7 +157,7 @@ pub use pane::ai_fact_pane::AIFactPane; pub use pane::code_diff_pane::CodeDiffPane; pub use pane::code_pane::CodePane; pub use pane::env_var_collection_pane::EnvVarCollectionPane; -// Zap Wave 7-3: `EnvironmentManagementPane` physically removed with ambient-agent UI subsystem. +// Zaplex Wave 7-3: `EnvironmentManagementPane` physically removed with ambient-agent UI subsystem. pub use pane::execution_profile_editor_pane::ExecutionProfileEditorPane; pub use pane::file_pane::FilePane; pub use pane::image_pane::ImagePane; @@ -204,7 +204,7 @@ fn get_minimum_pane_size(app: &AppContext) -> f32 { /// 2. Otherwise look up by command name in the already-discovered /// [`AvailableShells`]. Its shell discovery supplements the process `PATH` /// with well-known install locations (e.g. `/opt/homebrew/bin` on macOS, -/// MSYS2/WSL on Windows) that a raw `PATH` lookup would miss when Zap is +/// MSYS2/WSL on Windows) that a raw `PATH` lookup would miss when Zaplex is /// launched outside an interactive shell. /// 3. As a final fallback, perform a plain `PATH` lookup via /// [`AvailableShell::try_from`] in case the user put something exotic in @@ -221,7 +221,7 @@ fn resolve_tab_config_shell(name: &str, ctx: &AppContext) -> Option, }, - ZapDriveLink { - open_warp_drive_args: ZapDriveObjectArgs, + ZaplexDriveLink { + open_warp_drive_args: ZaplexDriveObjectArgs, }, #[cfg(feature = "local_fs")] OpenCodeInWarp { @@ -562,7 +562,7 @@ pub enum Event { }, /// Clears the hovered tab index so it no longer appears as highlighted drop target ClearHoveredTabIndex, - ZapDriveObjectInPane(ObjectUid), + ZaplexDriveObjectInPane(ObjectUid), OpenSuggestedAgentModeWorkflowModal { workflow_and_id: SuggestedAgentModeWorkflowAndId, }, @@ -620,7 +620,7 @@ pub enum Event { initial_content: Option, }, OpenAddRulePane, - // Zap Wave 7-3: `OpenEnvironmentManagementPane` event physically removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `OpenEnvironmentManagementPane` event physically removed with ambient-agent UI subsystem. OpenFilesPalette { source: PaletteSource, }, @@ -634,7 +634,7 @@ pub enum Event { target: FileTarget, line_col: Option, }, - /// Zap: emitted when Ctrl/Cmd+clicking a file path in remote SSH session output in the terminal, + /// Zaplex: emitted when Ctrl/Cmd+clicking a file path in remote SSH session output in the terminal, /// workspace then uses buffer-sync protocol to open the remote file in the editor. #[cfg(all(feature = "local_tty", feature = "local_fs"))] OpenRemoteFileFromTerminal { @@ -719,6 +719,10 @@ pub struct NewTerminalOptions { pub is_shared_session_creator: IsSharedSessionCreator, /// The AI conversation to restore when the terminal is created. pub conversation_restoration: Option, + /// If set, back this terminal with a daemon-hosted (native persistent) + /// session instead of a local PTY. Set only by the resilient-SSH path + /// (`open_ssh_terminal`); `None` for ordinary terminals. + pub daemon_request: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -799,18 +803,18 @@ pub struct PaneGroup { /// Mapping from pane IDs to their contents. pane_contents: HashMap>, - // Zap: removed terminal_with_open_share_block_modal / share_block_modal fields (cloud share block) + // Zaplex: removed terminal_with_open_share_block_modal / share_block_modal fields (cloud share block) dragged_border: Option, user_default_shell_changed_banner: ViewHandle>, - // Zap: removed terminal_with_open_share_session_modal / share_session_modal fields (cloud shared session) + // Zaplex: removed terminal_with_open_share_session_modal / share_session_modal fields (cloud shared session) /// Model that tracks the currently active file. active_file_model: ModelHandle, /// If there is an open summarization cancel dialog, the terminal pane ID where summarization is active. terminal_with_open_summarization_dialog: Option, /// Pane with an open environment setup mode selector modal (rendered at tab level). - // Zap Wave 7-3: `pane_with_open_environment_setup_mode_selector` / + // Zaplex Wave 7-3: `pane_with_open_environment_setup_mode_selector` / // `pane_with_open_agent_assisted_environment_modal` physically removed with ambient-agent UI subsystem. /// If the left panel is open for this pane group @@ -1242,6 +1246,7 @@ impl PaneGroup { model_event_sender.clone(), chosen_shell, None, + None, // daemon_request: ordinary terminal ctx, ), }; @@ -1534,6 +1539,7 @@ impl PaneGroup { model_event_sender.clone(), chosen_shell, terminal_snapshot.input_config, + None, // daemon_request: restored snapshot is a local session ctx, ); @@ -1851,7 +1857,7 @@ impl PaneGroup { }; Ok((PaneData::new(pane_id), focus)) } - } // Zap Wave 7-3: `EnvironmentManagement` LeafContents arm physically removed with ambient-agent UI subsystem. + } // Zaplex Wave 7-3: `EnvironmentManagement` LeafContents arm physically removed with ambient-agent UI subsystem. }; if let (Ok((pane_data, _)), Some(title)) = (&result, custom_vertical_tabs_title.as_deref()) @@ -2290,7 +2296,7 @@ impl PaneGroup { } /// Send prompt change bindkey events to all terminal sessions in this pane group. This - /// is used for intra-session prompt switching between Zap prompt and PS1. + /// is used for intra-session prompt switching between Zaplex prompt and PS1. #[cfg_attr(not(feature = "local_tty"), allow(unused_variables))] pub fn send_prompt_change_bindkey_to_all_sessions( &self, @@ -2357,17 +2363,17 @@ impl PaneGroup { _terminal_pane_id: TerminalPaneId, ctx: &mut ViewContext, ) { - // Zap: share_session_modal removed, no-op + // Zaplex: share_session_modal removed, no-op ctx.notify(); } /// Closes the share session modal if it is open. Does nothing otherwise. Does not change /// which element is focused. fn close_share_session_modal(&mut self, _ctx: &mut ViewContext) { - // Zap: share_session_modal removed, no-op + // Zaplex: share_session_modal removed, no-op } - // Zap: removed handle_share_session_modal_event (cloud shared session modal) + // Zaplex: removed handle_share_session_modal_event (cloud shared session modal) fn new_internal( tips_completed: ModelHandle, @@ -2418,7 +2424,7 @@ impl PaneGroup { me.handle_focus_state_event(event, ctx); }); - // Zap: removed share_block_modal registration (cloud share block) + // Zaplex: removed share_block_modal registration (cloud share block) ctx.subscribe_to_model(&PaneSettings::handle(ctx), |_, _, _, ctx| { ctx.notify(); @@ -2428,11 +2434,11 @@ impl PaneGroup { Banner::::new_permanently_dismissible( BannerTextContent::formatted_text(vec![ FormattedTextFragment::plain_text( - "Zap doesn't currently support your default shell, falling back to zsh. ", + "Zaplex doesn't currently support your default shell, falling back to zsh. ", ), FormattedTextFragment::hyperlink( crate::t!("common-learn-more"), - WARP_SHELL_COMPATIBILITY_DOCS, + ZAPLEX_SHELL_COMPATIBILITY_DOCS, ), ]), ) @@ -2462,7 +2468,7 @@ impl PaneGroup { }, ); - // Zap: removed share_session_modal registration (cloud shared session modal) + // Zaplex: removed share_session_modal registration (cloud shared session modal) ctx.subscribe_to_model(&UndoCloseStack::handle(ctx), |me, _, event, ctx| { let UndoCloseStackEvent::DiscardPane(pane_id) = event; @@ -2483,7 +2489,7 @@ impl PaneGroup { user_default_shell_changed_banner, active_file_model, terminal_with_open_summarization_dialog: None, - // Zap Wave 7-3: pane-level modal tracking fields from ambient-agent UI subsystem physically removed with UI. + // Zaplex Wave 7-3: pane-level modal tracking fields from ambient-agent UI subsystem physically removed with UI. right_panel_open: false, left_panel_open: false, is_right_panel_maximized: false, @@ -2876,6 +2882,7 @@ impl PaneGroup { model_event_sender.clone(), options.shell, None, + options.daemon_request, ctx, ); let uuid = Uuid::new_v4(); @@ -3234,7 +3241,7 @@ impl PaneGroup { let _ = ambient_agent_task_id; - // Insert the conversation ended tombstone (includes Open in Zap button on WASM). + // Insert the conversation ended tombstone (includes Open in Zaplex button on WASM). if terminal_manager.is_some() { terminal_view.update(ctx, |view, ctx| { view.insert_conversation_ended_tombstone(ctx); @@ -3268,7 +3275,7 @@ impl PaneGroup { } } - // Zap: removed handle_share_block_modal_event (cloud share block) + // Zaplex: removed handle_share_block_modal_event (cloud share block) /// Used to add a new pane but not splitting panes. pub fn add_terminal_pane( @@ -3876,8 +3883,8 @@ impl PaneGroup { self.hide_closed_pane(pane_id, ctx); } - // Zap: removed share_block_modal cleanup (cloud share block) - // Zap Wave 7-3: pane-level modal tracking in the ambient-agent UI + // Zaplex: removed share_block_modal cleanup (cloud share block) + // Zaplex Wave 7-3: pane-level modal tracking in the ambient-agent UI // subsystem; the field cleanup was physically removed with the UI. self.focus_next_terminal_pane_and_activate_session( @@ -3900,8 +3907,8 @@ impl PaneGroup { self.clean_up_pane(pane_id, ctx); - // Zap: removed share_block_modal cleanup (cloud share block) - // Zap Wave 7-3: pane-level modal tracking in the ambient-agent UI + // Zaplex: removed share_block_modal cleanup (cloud share block) + // Zaplex Wave 7-3: pane-level modal tracking in the ambient-agent UI // subsystem; the field cleanup was physically removed with the UI. self.focus_next_terminal_pane_and_activate_session( @@ -4814,11 +4821,33 @@ impl PaneGroup { model_event_sender: Option>, chosen_shell: Option, initial_input_config: Option, + daemon_request: Option, ctx: &mut ViewContext, ) -> ( ViewHandle, ModelHandle>, ) { + // Daemon-hosted (native persistent) session: bytes come from the remote + // daemon over the protocol, not a local PTY. Additive — `local_tty` + // remains the default for every ordinary terminal (`daemon_request` is + // `None`). + if let Some(request) = daemon_request { + let terminal_manager: ModelHandle> = + crate::terminal::daemon_tty::TerminalManager::create_model( + resources, + initial_size, + model_event_sender, + ctx.window_id(), + initial_input_config, + request.connection_session_id, + request.open_params, + request.adopt_pty_session_id, + ctx, + ); + let terminal_view = terminal_manager.as_ref(ctx).view(); + return (terminal_view, terminal_manager); + } + cfg_if::cfg_if! { if #[cfg(feature = "remote_tty")] { let terminal_manager: ModelHandle> = crate::terminal::remote_tty::TerminalManager::create_model( @@ -4908,7 +4937,7 @@ impl PaneGroup { }); let terminal_view = terminal_manager.as_ref(ctx).view(); - // Insert the conversation ended tombstone (includes Open in Zap button on WASM) + // Insert the conversation ended tombstone (includes Open in Zaplex button on WASM) terminal_view.update(ctx, |view, ctx| { view.insert_conversation_ended_tombstone(ctx); }); @@ -5079,6 +5108,7 @@ impl PaneGroup { self.model_event_sender.clone(), None, // chosen_shell None, // initial_input_config + None, // daemon_request: conversation restoration is a local session ctx, ); @@ -5204,6 +5234,7 @@ impl PaneGroup { self.model_event_sender.clone(), chosen_shell, None, + None, // daemon_request: ordinary local terminal pane ctx, ); @@ -6079,7 +6110,7 @@ impl PaneGroup { ); self.close_share_session_modal(ctx); - // Zap: removed terminal_with_open_share_block_modal reset (field no longer exists) + // Zaplex: removed terminal_with_open_share_block_modal reset (field no longer exists) ctx.notify(); } @@ -6222,7 +6253,7 @@ impl View for PaneGroup { let mut stack = Stack::new().with_child(column.finish()); - // Zap: removed share_block_modal / share_session_modal / role-change modal render branches (fields no longer exist) + // Zaplex: removed share_block_modal / share_session_modal / role-change modal render branches (fields no longer exist) // Render the summarization cancel dialog at tab level when open. if let Some(terminal_pane_id) = self.terminal_with_open_summarization_dialog { @@ -6235,7 +6266,7 @@ impl View for PaneGroup { } } - // Zap Wave 7-3: the tab-level overlay rendering of the environment setup + // Zaplex Wave 7-3: the tab-level overlay rendering of the environment setup // mode selector / agent-assisted environment modal was physically // removed along with the ambient-agent UI subsystem. diff --git a/app/src/pane_group/pane/local_harness_launch.rs b/app/src/pane_group/pane/local_harness_launch.rs index a517f3b808..3ca291bbfd 100644 --- a/app/src/pane_group/pane/local_harness_launch.rs +++ b/app/src/pane_group/pane/local_harness_launch.rs @@ -119,7 +119,7 @@ pub(super) async fn prepare_local_harness_child_launch( Harness::Gemini => unreachable!("normalize_local_child_harness filters out Gemini"), }; - // Zap (localization, Phase 3b-4): local harness child task startup no longer goes through the cloud + // Zaplex (localization, Phase 3b-4): local harness child task startup no longer goes through the cloud // `create_agent_task` mutation; instead, task_id is generated locally as UUID v4. // The `local_child_task_config(harness)` parameter is no longer used. let _ = local_child_task_config(harness); diff --git a/app/src/pane_group/pane/mod.rs b/app/src/pane_group/pane/mod.rs index 91ce8c55f5..712ea10d48 100644 --- a/app/src/pane_group/pane/mod.rs +++ b/app/src/pane_group/pane/mod.rs @@ -14,7 +14,7 @@ pub(super) mod code_diff_pane; pub(super) mod code_diff_pane_model; pub(super) mod code_pane; pub(super) mod env_var_collection_pane; -// Zap Wave 7-3: `environment_management_pane` physically removed with ambient-agent UI subsystem. +// Zaplex Wave 7-3: `environment_management_pane` physically removed with ambient-agent UI subsystem. pub(super) mod execution_profile_editor_pane; pub(super) mod file_pane; pub(super) mod get_started_pane; @@ -144,7 +144,7 @@ pub(crate) enum IPaneType { Code, CodeDiff, EnvVarCollection, - // Zap Wave 7-3: `EnvironmentManagement` IPaneType physically removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `EnvironmentManagement` IPaneType physically removed with ambient-agent UI subsystem. Workflow, Settings, AIFact, @@ -170,7 +170,7 @@ impl Display for IPaneType { IPaneType::Code => write!(f, "Code"), IPaneType::CodeDiff => write!(f, "Code Diff"), IPaneType::EnvVarCollection => write!(f, "Environment Variable Collection"), - // Zap Wave 7-3: `EnvironmentManagement` Display arm physically removed with variant. + // Zaplex Wave 7-3: `EnvironmentManagement` Display arm physically removed with variant. IPaneType::Workflow => write!(f, "Workflow"), IPaneType::Settings => write!(f, "Settings"), IPaneType::AIFact => write!(f, "AI Fact"), @@ -229,7 +229,7 @@ impl PaneId { Self::new_from_ctx(IPaneType::EnvVarCollection, ctx) } - // Zap Wave 7-3: `from_environment_management_pane_ctx` physically removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `from_environment_management_pane_ctx` physically removed with ambient-agent UI subsystem. /// Creates a [`PaneId`] from a [`ViewContext>`] pub fn from_workflow_pane_ctx(ctx: &ViewContext>) -> Self { @@ -328,7 +328,7 @@ impl PaneId { Self::new(IPaneType::EnvVarCollection, env_var_collection_view) } - // Zap Wave 7-3: `from_environment_management_pane_view` physically removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `from_environment_management_pane_view` physically removed with ambient-agent UI subsystem. /// Creates a [`PaneId`] from a [`PaneView`] entity ID. pub fn from_workflow_pane_view( @@ -444,12 +444,12 @@ impl PaneId { } pub fn is_environment_management_pane(&self) -> bool { - // Zap Wave 7-3: ambient-agent UI subsystem physically removed; + // Zaplex Wave 7-3: ambient-agent UI subsystem physically removed; // no pane is an environment management pane. Kept for progressive cleanup, returns false. false } - /// Returns true if this pane contains a Zap Drive object (notebook, workflow, etc.). + /// Returns true if this pane contains a Zaplex Drive object (notebook, workflow, etc.). pub fn is_warp_drive_object_pane(&self) -> bool { matches!( self.0.pane_type, @@ -484,7 +484,7 @@ impl PaneId { IPaneType::EnvVarCollection => { ChildView::>::with_id(self.0.pane_view_id).finish() } - // Zap Wave 7-3: `EnvironmentManagement` render arm physically removed with variant. + // Zaplex Wave 7-3: `EnvironmentManagement` render arm physically removed with variant. IPaneType::Workflow => { ChildView::>::with_id(self.0.pane_view_id).finish() } @@ -841,7 +841,7 @@ impl PaneConfiguration { ctx.emit(PaneConfigurationEvent::HeaderContentChanged); } - // Zap Phase 2a: `set_shareable_object` / `toggle_sharing_dialog` removed + // Zaplex Phase 2a: `set_shareable_object` / `toggle_sharing_dialog` removed // along with the pane-header sharing UI. /// Notifies that the header content has changed and the pane header should re-render. diff --git a/app/src/pane_group/pane/notebook_pane.rs b/app/src/pane_group/pane/notebook_pane.rs index 3fe7cd6757..de02f44eb5 100644 --- a/app/src/pane_group/pane/notebook_pane.rs +++ b/app/src/pane_group/pane/notebook_pane.rs @@ -7,7 +7,7 @@ use warpui::{AppContext, ModelHandle, SingletonEntity, ViewContext, ViewHandle}; use crate::{ app_state::{LeafContents, NotebookPaneSnapshot}, cloud_object::Space, - drive::{items::WarpDriveItemId, ObjectTypeAndId, ZapDriveObjectSettings}, + drive::{items::WarpDriveItemId, ObjectTypeAndId, ZaplexDriveObjectSettings}, notebooks::{ link::{LinkEvent, NotebookLinks}, manager::{NotebookManager, NotebookSource}, @@ -47,7 +47,7 @@ impl NotebookPane { /// Restore a notebook pane given its cloud notebook ID. pub fn restore( notebook_id: Option, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) -> anyhow::Result { let window_id = ctx.window_id(); @@ -81,7 +81,7 @@ impl PaneContent for NotebookPane { let notebook_id = self.notebook_view(app).as_ref(app).notebook_id(app); LeafContents::Notebook(NotebookPaneSnapshot::NotebookObject { notebook_id, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }) } @@ -183,9 +183,9 @@ pub(super) fn subscribe_to_link_model( session: session.clone(), }) } - LinkEvent::ZapDriveLink { + LinkEvent::ZaplexDriveLink { open_warp_drive_args, - } => ctx.emit(crate::pane_group::Event::ZapDriveLink { + } => ctx.emit(crate::pane_group::Event::ZaplexDriveLink { open_warp_drive_args: open_warp_drive_args.clone(), }), LinkEvent::StartLocalSession { path } => { @@ -205,7 +205,7 @@ pub(super) fn subscribe_to_link_model( target, line_col, } => { - // Emit event to workspace to handle opening in Zap + // Emit event to workspace to handle opening in Zaplex ctx.emit(crate::pane_group::Event::OpenFileWithTarget { path: path.clone(), target: target.clone(), diff --git a/app/src/pane_group/pane/terminal_pane.rs b/app/src/pane_group/pane/terminal_pane.rs index 2d57c13368..dd860489ba 100644 --- a/app/src/pane_group/pane/terminal_pane.rs +++ b/app/src/pane_group/pane/terminal_pane.rs @@ -719,8 +719,8 @@ fn handle_terminal_view_event( group.focus_pane(terminal_pane_id.into(), true, ctx); ctx.emit(pane_group::Event::FocusPaneGroup); } - Event::ZapDriveObjectInPane(uid) => { - ctx.emit(pane_group::Event::ZapDriveObjectInPane(uid.clone())); + Event::ZaplexDriveObjectInPane(uid) => { + ctx.emit(pane_group::Event::ZaplexDriveObjectInPane(uid.clone())); } Event::OpenSuggestedAgentModeWorkflowModal { workflow_and_id } => { ctx.emit(pane_group::Event::OpenSuggestedAgentModeWorkflowModal { @@ -739,7 +739,7 @@ fn handle_terminal_view_event( group.terminal_with_open_summarization_dialog = is_open.then_some(terminal_pane_id); ctx.notify(); } - // Zap Wave 7-3: `Event::EnvironmentSetupModeSelectorToggled` handler removed with + // Zaplex Wave 7-3: `Event::EnvironmentSetupModeSelectorToggled` handler removed with // ambient-agent UI subsystem physical deletion. #[cfg(feature = "local_fs")] Event::OpenFileWithTarget { @@ -753,7 +753,7 @@ fn handle_terminal_view_event( line_col: *line_col, }); } - // Zap: transparently forward "open remote file" event from terminal to pane_group → workspace. + // Zaplex: transparently forward "open remote file" event from terminal to pane_group → workspace. #[cfg(all(feature = "local_tty", feature = "local_fs"))] Event::OpenRemoteFileFromTerminal { remote_path, @@ -848,7 +848,7 @@ fn handle_terminal_view_event( initial_content: initial_content.clone(), }); } - // Zap Wave 7-3: `OpenEnvironmentManagementPane` event forwarding removed with ambient-agent UI + // Zaplex Wave 7-3: `OpenEnvironmentManagementPane` event forwarding removed with ambient-agent UI // subsystem physical deletion. #[cfg(feature = "local_fs")] Event::FileRenamed { old_path, new_path } => { diff --git a/app/src/pane_group/pane/view/header/mod.rs b/app/src/pane_group/pane/view/header/mod.rs index d67a85da43..0588b74dc0 100644 --- a/app/src/pane_group/pane/view/header/mod.rs +++ b/app/src/pane_group/pane/view/header/mod.rs @@ -475,7 +475,7 @@ impl PaneHeader

{ .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_main_axis_size(MainAxisSize::Min); - // Zap Phase 2a: sharing controls removed from the header. + // Zaplex Phase 2a: sharing controls removed from the header. let _ = should_show_on_header; let optional_controls = @@ -710,7 +710,7 @@ impl View for PaneHeader

{ has_overflow_items, header_left_inset, render_sharing_controls_fn: Box::new(|_app, _icon_color, _button_size| { - // Zap Phase 2a: sharing controls removed. + // Zaplex Phase 2a: sharing controls removed. None }), }; diff --git a/app/src/pane_group/pane/view/mod.rs b/app/src/pane_group/pane/view/mod.rs index b9609aa759..457d7e0f2f 100644 --- a/app/src/pane_group/pane/view/mod.rs +++ b/app/src/pane_group/pane/view/mod.rs @@ -32,7 +32,7 @@ pub use header_content::{ }; pub fn init(_app: &mut AppContext) { - // Zap Phase 2a: pane:share_pane_contents keybinding removed (sharing UI gone). + // Zaplex Phase 2a: pane:share_pane_contents keybinding removed (sharing UI gone). } pub enum PaneViewEvent { diff --git a/app/src/pane_group/pane/workflow_pane.rs b/app/src/pane_group/pane/workflow_pane.rs index 5e61e34bbd..35346c7c89 100644 --- a/app/src/pane_group/pane/workflow_pane.rs +++ b/app/src/pane_group/pane/workflow_pane.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ app_state::{LeafContents, WorkflowPaneSnapshot}, - drive::{items::WarpDriveItemId, ZapDriveObjectSettings}, + drive::{items::WarpDriveItemId, ZaplexDriveObjectSettings}, server::ids::SyncId, workflows::{ manager::{WorkflowManager, WorkflowOpenSource}, @@ -39,7 +39,7 @@ impl WorkflowPane { pub fn restore( workflow_id: Option, - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) -> anyhow::Result { let window_id = ctx.window_id(); @@ -132,7 +132,7 @@ impl PaneContent for WorkflowPane { let workflow_id = self.get_view(app).as_ref(app).workflow_id(); LeafContents::Workflow(WorkflowPaneSnapshot::WorkflowObject { workflow_id: Some(workflow_id), - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }) } diff --git a/app/src/persistence/sqlite.rs b/app/src/persistence/sqlite.rs index ffc0241769..eb4eef25b4 100644 --- a/app/src/persistence/sqlite.rs +++ b/app/src/persistence/sqlite.rs @@ -78,7 +78,7 @@ use crate::cloud_object::{ }; use crate::code::editor_management::CodeSource; use crate::drive::folders::{FolderId, FolderObject, FolderObjectModel}; -use crate::drive::ZapDriveObjectSettings; +use crate::drive::ZaplexDriveObjectSettings; use crate::env_vars::{EnvVarCollectionObject, EnvVarCollectionObjectModel}; use crate::features::FeatureFlag; use crate::notebooks::{NotebookId, NotebookObject}; @@ -133,10 +133,10 @@ const COMMANDS_COUNT_LIMIT: i64 = 10000; use crate::persistence::cloud_objects::{upsert_stored_object, StoredObjectId}; -const WARP_SQLITE_FILE_NAME: &str = "warp.sqlite"; +const ZAPLEX_SQLITE_FILE_NAME: &str = "warp.sqlite"; const ZAP_APP_GROUP_SQLITE_MIGRATION_MARKER: &str = ".zap-app-group-sqlite-migrated"; #[cfg(target_os = "macos")] -const WARP_APP_GROUP_ID: &str = "2BBY89MBSN.dev.warp"; +const ZAPLEX_APP_GROUP_ID: &str = "2BBY89MBSN.dev.warp"; /// Callback used when deleting a local stored object. Parameter is the ID of the object to be deleted. /// The passed-in conn has already started a transaction. @@ -395,7 +395,7 @@ unsafe fn init_logging() { // According to the docs, this error means that the database file was moved (or deleted), // so SQLite can't safely modify it and the rollback journal: // https://www.sqlite.org/rescode.html#readonly_dbmoved - // This is mostly outside of Zap's control (e.g. the user or some system program is + // This is mostly outside of Zaplex's control (e.g. the user or some system program is // moving around files in the user data directory), so downgrade to a warning. (_, sqlite3::SQLITE_READONLY_DBMOVED) => log::Level::Warn, _ => log::Level::Error, @@ -459,7 +459,7 @@ pub(super) fn init_db() -> Result { if warp_core::channel::ChannelState::channel() == warp_core::channel::Channel::Oss { if let Some(legacy_dir) = zap_legacy_app_group_sqlite_dir() { if let Err(err) = migrate_zap_app_group_sqlite_if_needed(&db_path, &legacy_dir) - .context("Failed to migrate Zap SQLite database out of legacy App Group") + .context("Failed to migrate Zaplex SQLite database out of legacy App Group") { report_error!(err); log::warn!("Skipping legacy App Group SQLite migration and continuing startup"); @@ -468,7 +468,7 @@ pub(super) fn init_db() -> Result { } // Migrate old SQLite files into the secure application container. - let old_db_path = warp_core::paths::state_dir().join(WARP_SQLITE_FILE_NAME); + let old_db_path = warp_core::paths::state_dir().join(ZAPLEX_SQLITE_FILE_NAME); if old_db_path != db_path && old_db_path.exists() && !db_path.exists() { match std::fs::rename(&old_db_path, &db_path) { Ok(_) => { @@ -516,7 +516,7 @@ fn zap_legacy_app_group_sqlite_dir() -> Option { dirs::home_dir().map(|home_dir| { home_dir .join("Library/Group Containers") - .join(WARP_APP_GROUP_ID) + .join(ZAPLEX_APP_GROUP_ID) .join("Library/Application Support") .join(warp_core::channel::ChannelState::app_id().to_string()) }) @@ -533,7 +533,7 @@ fn migrate_zap_app_group_sqlite_if_needed(target_db: &Path, legacy_dir: &Path) - return Ok(()); } - let legacy_db = legacy_dir.join(WARP_SQLITE_FILE_NAME); + let legacy_db = legacy_dir.join(ZAPLEX_SQLITE_FILE_NAME); if !legacy_db.exists() { write_zap_app_group_sqlite_migration_marker(&marker)?; return Ok(()); @@ -556,8 +556,8 @@ fn migrate_zap_app_group_sqlite_if_needed(target_db: &Path, legacy_dir: &Path) - write_zap_app_group_sqlite_migration_marker(&marker)?; safe_info!( - safe: ("Migrated Zap SQLite database out of legacy App Group"), - full: ("Migrated Zap SQLite database from `{}` to `{}`", legacy_db.display(), target_db.display()) + safe: ("Migrated Zaplex SQLite database out of legacy App Group"), + full: ("Migrated Zaplex SQLite database from `{}` to `{}`", legacy_db.display(), target_db.display()) ); Ok(()) @@ -652,7 +652,7 @@ fn setup_database(database_path: &Path) -> Result { pub fn database_file_path() -> PathBuf { warp_core::paths::secure_state_dir() .unwrap_or_else(warp_core::paths::state_dir) - .join(WARP_SQLITE_FILE_NAME) + .join(ZAPLEX_SQLITE_FILE_NAME) } pub(super) fn remove(sender: SyncSender) { @@ -1266,7 +1266,7 @@ fn save_pane_state( LeafContents::GetStarted => GET_STARTED_PANE_KIND, LeafContents::Welcome { .. } => WELCOME_PANE_KIND, LeafContents::AIDocument(_) => AI_DOCUMENT_PANE_KIND, - // Zap Wave 7-3: `EnvironmentManagement` arm physically removed along with variant. + // Zaplex Wave 7-3: `EnvironmentManagement` arm physically removed along with variant. LeafContents::SshServer { .. } => { // These pane types are filtered out before this function is // called; see `LeafContents::is_persisted` and the skip in @@ -1435,7 +1435,7 @@ fn save_pane_state( .values(workflow) .execute(conn)?; } - // Zap Wave 7-3: `EnvironmentManagement` LeafContents arm physically removed along with variant. + // Zaplex Wave 7-3: `EnvironmentManagement` LeafContents arm physically removed along with variant. LeafContents::Settings(settings_pane_snapshot) => { let current_page = match settings_pane_snapshot { SettingsPaneSnapshot::Local { current_page, .. } => current_page, @@ -2496,7 +2496,7 @@ fn read_node(conn: &mut SqliteConnection, node: model::PaneNode) -> Result NotebookPaneSnapshot::LocalFileNotebook { path: Some(path) }, None => NotebookPaneSnapshot::NotebookObject { notebook_id, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }, }) } @@ -2514,7 +2514,7 @@ fn read_node(conn: &mut SqliteConnection, node: model::PaneNode) -> Result { diff --git a/app/src/plugin/host/native/js_api/mod.rs b/app/src/plugin/host/native/js_api/mod.rs index 82aca1acd9..f6b382a8ed 100644 --- a/app/src/plugin/host/native/js_api/mod.rs +++ b/app/src/plugin/host/native/js_api/mod.rs @@ -10,7 +10,7 @@ cfg_if::cfg_if! { } } -/// Returns a JS object representing the Zap Plugin API exposed to external JavaScript plugins. +/// Returns a JS object representing the Zaplex Plugin API exposed to external JavaScript plugins. /// /// Currently, the API contains a single "completions" namespace for registering command /// signatures. @@ -43,7 +43,7 @@ pub fn console(ctx: Ctx<'_>) -> rquickjs::Result> { Ok(console) } -/// Returns a JS object representing the Completions namespace for the Zap Plugin API. +/// Returns a JS object representing the Completions namespace for the Zaplex Plugin API. /// /// API methods: /// diff --git a/app/src/plugin/host/native/mod.rs b/app/src/plugin/host/native/mod.rs index efde35d655..22badb2733 100644 --- a/app/src/plugin/host/native/mod.rs +++ b/app/src/plugin/host/native/mod.rs @@ -115,7 +115,7 @@ fn plugin_paths() -> Vec { .collect() } -/// Returns `true` if the directory at the given path is directory containing JS source for a Zap plugin. +/// Returns `true` if the directory at the given path is directory containing JS source for a Zaplex plugin. fn is_plugin_dir(path: &Path) -> bool { if !path.is_dir() { return false; diff --git a/app/src/plugin/host/native/plugin_ref.rs b/app/src/plugin/host/native/plugin_ref.rs index b2640902a5..fcb862243a 100644 --- a/app/src/plugin/host/native/plugin_ref.rs +++ b/app/src/plugin/host/native/plugin_ref.rs @@ -11,7 +11,7 @@ pub(super) enum PluginLoadError { MissingBuiltin(BuiltInPluginType), } -/// Represents "Built-in" plugins. Each variant corresponds to a plugin bundled with Zap by +/// Represents "Built-in" plugins. Each variant corresponds to a plugin bundled with Zaplex by /// default (e.g. Completions/Command Signatures) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(super) enum BuiltInPluginType { @@ -41,7 +41,7 @@ pub(super) enum PluginRef { /// Refers to plugin source on disk. Path(PathBuf), - /// Refers to a "built-in" plugin bundled with the Zap binary. + /// Refers to a "built-in" plugin bundled with the Zaplex binary. BuiltIn(BuiltInPluginType), } diff --git a/app/src/plugin/mod.rs b/app/src/plugin/mod.rs index 00065bcb9e..a80020fe76 100644 --- a/app/src/plugin/mod.rs +++ b/app/src/plugin/mod.rs @@ -14,4 +14,4 @@ pub const PLUGIN_HOST_FLAG: &str = "--plugin_host"; /// The name of the environment variable used to pass connection address for the app server to the /// plugin host process. -const PLUGIN_HOST_ADDRESS_ENV_VAR: &str = "WARP_PLUGIN_HOST_ADDRESS"; +const PLUGIN_HOST_ADDRESS_ENV_VAR: &str = "ZAPLEX_PLUGIN_HOST_ADDRESS"; diff --git a/app/src/plugin/service/call_js_function.rs b/app/src/plugin/service/call_js_function.rs index ef627aab62..9b55548666 100644 --- a/app/src/plugin/service/call_js_function.rs +++ b/app/src/plugin/service/call_js_function.rs @@ -1,4 +1,4 @@ -//! IPC service for calling JS functions registered by Zap plugins. +//! IPC service for calling JS functions registered by Zaplex plugins. //! //! This service is hosted by the plugin host process and used by the main app process to call //! plugin functions defined in JS. diff --git a/app/src/preview_config_migration.rs b/app/src/preview_config_migration.rs index def15b7d82..d0d81be0d5 100644 --- a/app/src/preview_config_migration.rs +++ b/app/src/preview_config_migration.rs @@ -12,7 +12,7 @@ use std::path::Path; use warp_core::channel::{Channel, ChannelState}; -use warp_core::paths::{data_dir, WARP_CONFIG_DIR}; +use warp_core::paths::{data_dir, ZAPLEX_CONFIG_DIR}; /// Files that should not be symlinked during the Preview config directory /// migration. These are intentionally kept separate between Stable and @@ -37,7 +37,7 @@ pub(crate) fn migrate_preview_config_dir_if_needed() { return; }; - let old_dir = home.join(WARP_CONFIG_DIR); + let old_dir = home.join(ZAPLEX_CONFIG_DIR); // `data_dir()` is already channel-aware; for Preview it resolves to // `~/.warp-preview`. let new_dir = data_dir(); diff --git a/app/src/pricing/mod.rs b/app/src/pricing/mod.rs deleted file mode 100644 index 23b65c345e..0000000000 --- a/app/src/pricing/mod.rs +++ /dev/null @@ -1,96 +0,0 @@ -use warpui::{Entity, ModelContext, SingletonEntity}; - -#[derive(Debug, Clone)] -pub struct AddonCreditsOption { - pub credits: i32, - pub price_usd_cents: i32, -} - -impl AddonCreditsOption { - pub fn rate(&self) -> f32 { - self.price_usd_cents as f32 / self.credits as f32 - } -} - -#[derive(Debug, Clone)] -pub struct PricingInfo { - pub plans: Vec, - pub addon_credits_options: Vec, -} - -#[derive(Debug, Clone)] -pub struct PlanPricing { - pub plan: StripeSubscriptionPlan, - pub monthly_plan_price_per_month_usd_cents: i32, - pub yearly_plan_price_per_month_usd_cents: i32, - pub request_limit: Option, - pub max_team_size: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum StripeSubscriptionPlan { - Business, - Lightspeed, - Pro, - Team, - Turbo, - Build, - BuildBusiness, - BuildMax, - Other(String), -} - -/// Global model for server-side pricing information. -/// -/// In Zap it is a local no-op stub: the OSS channel has no cloud service pushing pricing data, -/// so during the process lifetime `pricing_info` typically remains `None`, and all getters return `None`. -/// The model is temporarily retained for a few usage quota and billing compatibility call sites; -/// after cloud cleanup is complete, it can be removed entirely. -#[derive(Debug)] -pub struct PricingInfoModel { - pricing_info: Option, -} - -impl PricingInfoModel { - pub fn new() -> Self { - Self { pricing_info: None } - } - - /// Updates the model with the latest pricing information from the server. - pub fn update_pricing_info(&mut self, pricing_info: PricingInfo, ctx: &mut ModelContext) { - self.pricing_info = Some(pricing_info); - ctx.emit(PricingInfoModelEvent::PricingInfoUpdated); - } - - /// Returns the pricing for a specific plan. - pub fn plan_pricing(&self, plan: &StripeSubscriptionPlan) -> Option<&PlanPricing> { - self.pricing_info - .as_ref()? - .plans - .iter() - .find(|p| &p.plan == plan) - } - - pub fn addon_credits_options(&self) -> Option<&[AddonCreditsOption]> { - self.pricing_info - .as_ref() - .map(|info| info.addon_credits_options.as_slice()) - } -} - -impl Default for PricingInfoModel { - fn default() -> Self { - Self::new() - } -} - -#[derive(Debug, Clone)] -pub enum PricingInfoModelEvent { - PricingInfoUpdated, -} - -impl Entity for PricingInfoModel { - type Event = PricingInfoModelEvent; -} - -impl SingletonEntity for PricingInfoModel {} diff --git a/app/src/profiling.rs b/app/src/profiling.rs index 738e79cb3f..62ccf168d9 100644 --- a/app/src/profiling.rs +++ b/app/src/profiling.rs @@ -66,12 +66,12 @@ pub fn dump_dhat_heap_profile() { /// This spawns `go tool pprof`, fetches the heap profile from the local HTTP server, and symbolizes it. #[cfg(feature = "heap_usage_tracking")] pub async fn dump_jemalloc_heap_profile(memory_breakdown: serde_json::Value) { - // Zap only writes the profile data and memory_breakdown to local logs. + // Zaplex only writes the profile data and memory_breakdown to local logs. let result = dump_jemalloc_heap_profile_inner().await; match result { Ok(profile_data) => { log::warn!( - "Zap: Detected abnormal memory usage (heap profile size {} bytes, memory breakdown: {})", + "Zaplex: Detected abnormal memory usage (heap profile size {} bytes, memory breakdown: {})", profile_data.len(), memory_breakdown ); diff --git a/app/src/prompt/editor_modal.rs b/app/src/prompt/editor_modal.rs index b3fb691773..8337ba44bf 100644 --- a/app/src/prompt/editor_modal.rs +++ b/app/src/prompt/editor_modal.rs @@ -106,10 +106,10 @@ pub struct EditorModal { /// used for saving changes. same_line_prompt_enabled: bool, - /// Dropdown to select the separator for the Zap prompt, in the case of - /// same line prompt. This separator is added at the end of the Zap prompt. + /// Dropdown to select the separator for the Zaplex prompt, in the case of + /// same line prompt. This separator is added at the end of the Zaplex prompt. warp_prompt_separator_dropdown: ViewHandle>, - /// The separator currently selected for the Zap prompt. + /// The separator currently selected for the Zaplex prompt. warp_prompt_separator: WarpPromptSeparator, /// True if there was any change while the modal was open. @@ -121,7 +121,7 @@ pub struct EditorModal { #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum PromptType { PS1, - Zap, + Zaplex, WarpDefault, } @@ -131,7 +131,7 @@ impl PromptType { if matches!(*session_settings.saved_prompt, PromptSelection::Default) { PromptType::WarpDefault } else { - PromptType::Zap + PromptType::Zaplex } } @@ -168,7 +168,7 @@ impl EditorModal { let warp_prompt_separator = match SessionSettings::as_ref(ctx).saved_prompt.value() { PromptSelection::CustomChipSelection(config) => config.separator(), - // If the "default Zap prompt" i.e. no context chips, is selected, then default to no Zap prompt separator. + // If the "default Zaplex prompt" i.e. no context chips, is selected, then default to no Zaplex prompt separator. _ => WarpPromptSeparator::None, }; let warp_prompt_separator_label = warp_prompt_separator.dropdown_item_label().to_owned(); @@ -285,9 +285,9 @@ impl EditorModal { ctx.notify(); } - /// Updates the state of the Zap prompt separator dropdown to be enabled/disabled based on the current state of the modal. + /// Updates the state of the Zaplex prompt separator dropdown to be enabled/disabled based on the current state of the modal. fn update_warp_separator_dropdown_state(&mut self, ctx: &mut ViewContext) { - // If we are using the Zap prompt and SLP is enabled, then we enable the dropdown. Otherwise, disable it. + // If we are using the Zaplex prompt and SLP is enabled, then we enable the dropdown. Otherwise, disable it. if self.prompt_type != PromptType::PS1 && self.same_line_prompt_enabled { self.warp_prompt_separator_dropdown .update(ctx, |dropdown, ctx| { @@ -305,7 +305,7 @@ impl EditorModal { if self.is_dirty { match self.prompt_type { PromptType::PS1 => { - // TODO: we need to stop the Zap prompt generators from running at this point + // TODO: we need to stop the Zaplex prompt generators from running at this point SessionSettings::handle(ctx).update(ctx, |settings, ctx| { report_if_error!(settings.honor_ps1.set_value(true, ctx)); }); @@ -315,7 +315,7 @@ impl EditorModal { report_if_error!(prompt.reset(ctx)); }); } - PromptType::Zap => { + PromptType::Zaplex => { let new_setup = self .chip_configurator .used_chips @@ -349,7 +349,7 @@ impl EditorModal { let prompt_info = match self.prompt_type { PromptType::PS1 => PromptChoice::PS1, PromptType::WarpDefault => PromptChoice::Default, - PromptType::Zap => PromptChoice::Custom { + PromptType::Zaplex => PromptChoice::Custom { builtin_chips: self .chip_configurator .used_chips @@ -396,21 +396,21 @@ impl TypedActionView for EditorModal { let mutated = self.chip_configurator.handle_action(chip_action, ctx); if mutated { self.is_dirty = true; - self.prompt_type = PromptType::Zap; + self.prompt_type = PromptType::Zaplex; } ctx.notify(); } Self::Action::UsePS1 => { self.is_dirty = true; self.prompt_type = PromptType::PS1; - // Disable the Zap separator dropdown (only applies to Zap prompt). + // Disable the Zaplex separator dropdown (only applies to Zaplex prompt). self.update_warp_separator_dropdown_state(ctx); ctx.notify(); } Self::Action::UseWarpPrompt => { self.is_dirty = true; self.prompt_type = PromptType::warp_prompt_from_settings(ctx); - // Enable the Zap separator dropdown, if SLP is on. + // Enable the Zaplex separator dropdown, if SLP is on. self.update_warp_separator_dropdown_state(ctx); ctx.notify(); } @@ -421,7 +421,7 @@ impl TypedActionView for EditorModal { let default_prompt = PromptConfiguration::default_prompt(); self.same_line_prompt_enabled = default_prompt.same_line_prompt_enabled(); self.warp_prompt_separator = default_prompt.separator(); - // Disable the Zap separator dropdown, since SLP is off for the default Zap prompt. + // Disable the Zaplex separator dropdown, since SLP is off for the default Zaplex prompt. self.update_warp_separator_dropdown_state(ctx); let restored_chips = default_prompt.chip_kinds(); self.update_used_chips(restored_chips, ctx); @@ -431,9 +431,9 @@ impl TypedActionView for EditorModal { self.is_dirty = true; self.same_line_prompt_enabled = !self.same_line_prompt_enabled; - // In case we had previously picked default Zap prompt, but now the user toggled + // In case we had previously picked default Zaplex prompt, but now the user toggled // same line prompt - it's no longer the default prompt. - self.prompt_type = PromptType::Zap; + self.prompt_type = PromptType::Zaplex; self.update_warp_separator_dropdown_state(ctx); ctx.notify(); @@ -587,7 +587,7 @@ impl EditorModal { } } - // TODO: consider supporting SLP with the new Zap prompt. + // TODO: consider supporting SLP with the new Zaplex prompt. #[allow(dead_code)] fn render_same_line_prompt_section(&self, appearance: &Appearance) -> Box { let label = appearance @@ -684,7 +684,7 @@ impl EditorModal { self.render_prompt_section( appearance, - matches!(self.prompt_type, PromptType::Zap | PromptType::WarpDefault), + matches!(self.prompt_type, PromptType::Zaplex | PromptType::WarpDefault), header_row, None, body, @@ -787,9 +787,9 @@ impl EditorModal { // We disable the save button in a couple of cases: // - there are no changes - // - the Zap prompt is used but there are no chips selected + // - the Zaplex prompt is used but there are no chips selected let save_disabled = !self.is_dirty - || (matches!(self.prompt_type, PromptType::Zap) + || (matches!(self.prompt_type, PromptType::Zaplex) && self.chip_configurator.used_chips.is_empty()); let save_button = self.render_primary_button( crate::t!("prompt-editor-save-changes"), diff --git a/app/src/quit_warning/mod.rs b/app/src/quit_warning/mod.rs index 0349b6fb5d..f1dc871a45 100644 --- a/app/src/quit_warning/mod.rs +++ b/app/src/quit_warning/mod.rs @@ -438,7 +438,7 @@ impl<'a> QuitWarningDialog<'a> { QuitScope::Tabs(tabs) if tabs.len() == 1 => "Close tab?".to_string(), QuitScope::Tabs(_) => "Close tabs?".to_string(), QuitScope::Window(_) => "Close window?".to_string(), - QuitScope::App => "Quit Zap?".to_string(), + QuitScope::App => "Quit Zaplex?".to_string(), QuitScope::EditorTab { .. } => crate::t!("quit-warning-save-changes-title"), }; diff --git a/app/src/remote_server/auth_context.rs b/app/src/remote_server/auth_context.rs index a978213fb3..4d6ebaf1be 100644 --- a/app/src/remote_server/auth_context.rs +++ b/app/src/remote_server/auth_context.rs @@ -7,8 +7,8 @@ use crate::auth::AuthState; /// Construct auth context for use by remote-server module. /// -/// Zap Wave 3-1: `AuthClient` trait already physically deleted. Bearer token source changed to directly read -/// `AuthState::get_access_token_ignoring_validity()`(on Zap path only returns `Some` when user has BYOP API key mounted, +/// Zaplex Wave 3-1: `AuthClient` trait already physically deleted. Bearer token source changed to directly read +/// `AuthState::get_access_token_ignoring_validity()`(on Zaplex path only returns `Some` when user has BYOP API key mounted, /// otherwise always `None`). pub fn server_api_auth_context(auth_state: Arc) -> RemoteServerAuthContext { let token_auth_state = auth_state.clone(); @@ -24,7 +24,7 @@ pub fn server_api_auth_context(auth_state: Arc) -> RemoteServerAuthCo } fn remote_server_identity_key(auth_state: &AuthState) -> String { - // Zap no longer distinguishes anonymous / authenticated identity, uniformly uses `user_id()` (local test UID). + // Zaplex no longer distinguishes anonymous / authenticated identity, uniformly uses `user_id()` (local test UID). auth_state .user_id() .map(|uid| uid.as_string()) diff --git a/app/src/remote_server/headless_connect.rs b/app/src/remote_server/headless_connect.rs new file mode 100644 index 0000000000..46da971f8a --- /dev/null +++ b/app/src/remote_server/headless_connect.rs @@ -0,0 +1,305 @@ +//! Headless SSH ControlMaster setup for daemon-hosted sessions (Stage 2, Option B). +//! +//! A resilient SSH host (`session_resilience.is_enabled()`) opens directly as a +//! daemon-hosted session — there is no interactive `ssh` PTY whose zaplexify +//! bootstrap would establish the ControlMaster. So we establish it ourselves +//! (`ssh -f -N -o ControlMaster=auto -o ControlPath= …`) and hand the +//! socket to [`SshTransport`](super::ssh_transport::SshTransport) + +//! `RemoteServerManager::connect_session`. +//! +//! v1 supports **key/agent auth** only (clean headless, `BatchMode=yes`). +//! Password-auth hosts fall back to the normal (non-daemon) SSH path — see the +//! caller in `app/src/workspace/view.rs`. See +//! `docs/superpowers/specs/2026-06-27-stage2-increment3c-daemon-trigger-design.md`. + +use std::path::{Path, PathBuf}; +use std::process::Stdio; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::{anyhow, Result}; +use remote_server::auth::RemoteServerAuthContext; +use remote_server::proto::SessionInfo; +use remote_server::transport::{Connection, RemoteTransport}; +use warp_core::SessionId; +use warp_ssh_manager::{AuthType, SshServerInfo}; +use warpui::r#async::executor::Background; + +use super::ssh_transport::SshTransport; + +/// Daemon sessions are allocated `SessionId`s in the **top half** of the u64 +/// space so they cannot collide with shell-bootstrap-minted ids (which are +/// PID/timestamp-derived and stay well below `2^63`). The manager keys all +/// sessions — interactive and daemon — by `SessionId`, so uniqueness matters. +const DAEMON_SESSION_ID_BASE: u64 = 1 << 63; +static NEXT_DAEMON_SESSION_ID: AtomicU64 = AtomicU64::new(1); + +/// Allocates a fresh, collision-safe `SessionId` for a daemon-hosted session. +pub fn alloc_daemon_session_id() -> SessionId { + let n = NEXT_DAEMON_SESSION_ID.fetch_add(1, Ordering::Relaxed); + SessionId::from(DAEMON_SESSION_ID_BASE | n) +} + +/// Whether this (already auth-resolved) host can be connected headlessly. +/// +/// v1: key auth only — it runs non-interactively under `BatchMode=yes` (with an +/// ssh-agent or an unencrypted key). Password auth needs an interactive prompt +/// we don't have here, so those hosts use the normal SSH path instead. +pub fn is_headless_capable(server: &SshServerInfo) -> bool { + matches!(server.auth_type, AuthType::Key) +} + +/// FNV-1a, used only to derive a short, stable, run-to-run-consistent socket +/// name per host (so multiple tabs to the same host share one master). +fn stable_hash(s: &str) -> u64 { + let mut h: u64 = 0xcbf2_9ce4_8422_2325; + for b in s.as_bytes() { + h ^= *b as u64; + h = h.wrapping_mul(0x0000_0100_0000_01b3); + } + h +} + +/// Local path for the ControlMaster socket. Uses a real (`$HOME`-expanded) path +/// so both `ssh -o ControlPath=` and our existence check agree (ssh would expand +/// `~` itself, but `Path::exists` would not). +pub fn control_socket_path(server: &SshServerInfo) -> PathBuf { + let home = std::env::var_os("HOME") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("/tmp")); + let key = format!("{}@{}:{}", server.username, server.host, server.port); + home.join(".ssh") + .join(format!("zaplex-daemon-{:016x}", stable_hash(&key))) +} + +/// Whether a live ControlMaster is serving `socket_path` — `ssh -O check` +/// returns success only when the master process is actually alive (a stale +/// socket file fails the check). Runs entirely over the local Unix socket, so +/// it returns quickly; bounded by a short timeout regardless. +async fn control_master_alive(socket_path: &Path) -> bool { + let mut cmd = command::r#async::Command::new("ssh"); + cmd.arg("-O") + .arg("check") + .arg("-o") + .arg(format!("ControlPath={}", socket_path.display())) + .arg("placeholder@placeholder") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true); + matches!( + tokio::time::timeout(Duration::from_secs(5), cmd.output()).await, + Ok(Ok(output)) if output.status.success() + ) +} + +/// Ensures a ControlMaster is up at `socket_path` (idempotent via +/// `ControlMaster=auto` + `ControlPersist`). Spawns `ssh -f -N …`, which +/// authenticates and then backgrounds itself; the master socket exists by the +/// time the foreground process exits. Key/agent auth only (`BatchMode=yes`). +pub async fn ensure_control_master(server: &SshServerInfo, socket_path: &Path) -> Result<()> { + if socket_path.exists() { + // A socket file is present, but the master may have died on an SSH drop, + // leaving a stale socket. Verify it's actually serving: reuse a live + // master, otherwise remove the stale socket and spawn a fresh one. This + // is what lets a daemon session's transport be re-established after a + // connection loss (the session itself kept running daemon-side). + if control_master_alive(socket_path).await { + return Ok(()); + } + log::info!( + "ControlMaster socket {} is stale; re-establishing", + socket_path.display() + ); + let _ = std::fs::remove_file(socket_path); + } + + let mut args: Vec = Vec::new(); + if server.port != 22 { + args.push("-p".into()); + args.push(server.port.to_string()); + } + if let Some(key) = server.key_path.as_deref().filter(|p| !p.is_empty()) { + args.push("-i".into()); + args.push(key.to_string()); + } + args.extend([ + "-f".into(), // background after authentication + "-N".into(), // no remote command — pure multiplexing master + "-o".into(), + "ControlMaster=auto".into(), + "-o".into(), + // Idle timeout, NOT `yes`: `yes` keeps the backgrounded master alive + // forever (it even survives app exit, since `-f` detaches it), and daemon + // sessions no longer stop it on tab close (it's a shared per-host master). + // A timeout lets it self-retire after the last client goes idle, while + // still being reused for reconnects / new tabs within the window. The + // remote daemon session is independent of the master and survives either way. + "ControlPersist=600".into(), + "-o".into(), + format!("ControlPath={}", socket_path.display()), + "-o".into(), + "BatchMode=yes".into(), + "-o".into(), + "ConnectTimeout=10".into(), + "-o".into(), + "StrictHostKeyChecking=accept-new".into(), + ]); + let target = if server.username.is_empty() { + server.host.clone() + } else { + format!("{}@{}", server.username, server.host) + }; + args.push(target); + + let output = tokio::time::timeout( + Duration::from_secs(20), + command::r#async::Command::new("ssh") + .args(&args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true) + .output(), + ) + .await + .map_err(|_| anyhow!("ControlMaster setup timed out"))? + .map_err(|e| anyhow!("failed to spawn ssh: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(anyhow!("ControlMaster setup failed: {}", stderr.trim())); + } + + // `-f` returns once the master is backgrounded; the socket should exist now. + // Poll briefly to absorb any small filesystem-visibility lag. + for _ in 0..20 { + if socket_path.exists() { + return Ok(()); + } + tokio::time::sleep(Duration::from_millis(50)).await; + } + Err(anyhow!( + "ControlMaster socket did not appear at {}", + socket_path.display() + )) +} + +/// Brings up the headless ControlMaster for `server` at `socket_path` and ensures +/// the remote-server binary is installed (auto-install if missing). Shared by the +/// daemon-terminal connect (`Workspace::spawn_daemon_session_connect`) and the +/// adopt-sidebar's connect-to-list. On success the caller builds a fresh +/// [`SshTransport`] over `socket_path` and calls `connect_session`. +pub async fn prepare_daemon_transport( + server: SshServerInfo, + socket_path: PathBuf, + auth_context: Arc, +) -> std::result::Result<(), String> { + let host = server.host.clone(); + log::info!("daemon connect [{host}]: establishing ControlMaster"); + ensure_control_master(&server, &socket_path) + .await + .map_err(|e| format!("ControlMaster setup failed: {e:#}"))?; + let transport = SshTransport::new(socket_path, auth_context); + log::info!("daemon connect [{host}]: checking remote-server binary"); + match transport.check_binary().await { + Ok(true) => log::info!("daemon connect [{host}]: binary present"), + Ok(false) => { + log::info!("daemon connect [{host}]: binary missing — installing"); + transport + .install_binary() + .await + .map_err(|e| format!("remote-server install failed: {e}"))?; + log::info!("daemon connect [{host}]: install complete"); + } + Err(e) => return Err(format!("remote-server binary check failed: {e}")), + } + Ok(()) +} + +/// Connects to `server`'s daemon (a transient connection) and returns the +/// sessions it currently owns — including ones that survived an app restart or +/// transport drop, which is the whole point of the adopt-sidebar. Self-contained: +/// brings up the ControlMaster + binary, connects, runs the initialize handshake, +/// calls `list_sessions`, then tears the transient connection down again (the +/// daemon and its sessions persist independently of this connection). +/// +/// Request/response works without draining the client event channel — responses +/// are routed to per-request oneshots; the event channel is unbounded so the +/// reader never blocks on our ignoring it. +pub async fn list_daemon_sessions( + server: SshServerInfo, + socket_path: PathBuf, + auth_context: Arc, + executor: Arc, +) -> std::result::Result, String> { + prepare_daemon_transport(server, socket_path.clone(), auth_context.clone()).await?; + let transport = SshTransport::new(socket_path, auth_context.clone()); + let Connection { client, child, .. } = transport + .connect(executor) + .await + .map_err(|e| format!("daemon connect failed: {e:#}"))?; + // Keep the proxy/ssh child alive for the duration of the requests; it is torn + // down when this returns. The daemon itself keeps running. + let _child = child; + let auth_token = auth_context.get_auth_token().await; + client + .initialize(auth_token.as_deref()) + .await + .map_err(|e| format!("daemon handshake failed: {e:#}"))?; + let list = client + .list_sessions() + .await + .map_err(|e| format!("list_sessions failed: {e:#}"))?; + Ok(list.sessions) +} + +#[cfg(test)] +mod tests { + use super::*; + use warp_ssh_manager::{AuthType, SshServerInfo}; + + fn server(auth: AuthType) -> SshServerInfo { + let mut s = SshServerInfo::new_default("node-1".to_string()); + s.host = "example.com".to_string(); + s.username = "me".to_string(); + s.port = 22; + s.auth_type = auth; + s + } + + #[test] + fn headless_capable_only_for_key_auth() { + assert!(is_headless_capable(&server(AuthType::Key))); + assert!(!is_headless_capable(&server(AuthType::Password))); + // OneKey is resolved to Key/Password upstream (resolve_server_auth); the + // bare OneKey marker is not headless-capable on its own. + assert!(!is_headless_capable(&server(AuthType::OneKey))); + } + + #[test] + fn control_socket_path_is_stable_and_per_host() { + let a1 = control_socket_path(&server(AuthType::Key)); + let a2 = control_socket_path(&server(AuthType::Key)); + assert_eq!(a1, a2, "same host → same socket path (run-to-run stable)"); + + let mut other = server(AuthType::Key); + other.host = "other.example.com".to_string(); + assert_ne!( + a1, + control_socket_path(&other), + "different host → different socket" + ); + assert!(a1.to_string_lossy().contains(".ssh/zaplex-daemon-")); + } + + #[test] + fn daemon_session_ids_are_unique_and_in_top_half() { + let a = alloc_daemon_session_id(); + let b = alloc_daemon_session_id(); + assert_ne!(a, b, "each allocation is unique"); + assert!(a.as_u64() >= DAEMON_SESSION_ID_BASE, "top-half id (no collision with shell ids)"); + assert!(b.as_u64() >= DAEMON_SESSION_ID_BASE); + } +} diff --git a/app/src/remote_server/mod.rs b/app/src/remote_server/mod.rs index 3c2535555c..41b5354494 100644 --- a/app/src/remote_server/mod.rs +++ b/app/src/remote_server/mod.rs @@ -4,6 +4,8 @@ pub use remote_server::*; #[cfg(not(target_family = "wasm"))] pub mod auth_context; +#[cfg(unix)] +pub mod headless_connect; #[cfg(not(target_family = "wasm"))] pub mod server_buffer_tracker; #[cfg(not(target_family = "wasm"))] @@ -85,7 +87,7 @@ pub(super) fn run_daemon_app( Ok(()) } -// Zap Wave 6-1: `wire_auth_token_rotation` function physically removed — originally subscribed to server API +// Zaplex Wave 6-1: `wire_auth_token_rotation` function physically removed — originally subscribed to server API // token rotation events and forwarded to `RemoteServerManager::rotate_auth_token`. After auth subsystem removal in Wave 3-1, // this event has 0 emit points. Wave 6-1 synchronously removes event + this subscription function + call site in `lib.rs`. // `RemoteServerManager::rotate_auth_token` function body kept for now. diff --git a/app/src/remote_server/server_model.rs b/app/src/remote_server/server_model.rs index 6c168150bf..a8b5df8b49 100644 --- a/app/src/remote_server/server_model.rs +++ b/app/src/remote_server/server_model.rs @@ -28,8 +28,9 @@ use super::proto::{ use zaplex_remote_session::types::supported_features; #[cfg(unix)] use super::proto::{ - CloseSession, OpenSession, ResizeSession, SessionExited, SessionInput, SessionOpened, - SessionOutput, + AttachSession, CloseSession, DetachSession, OpenSession, ResizeSession, SessionAttached, + SessionExited, SessionInfo, SessionInput, SessionList, SessionOpened, SessionOutput, + SessionSize, }; #[cfg(unix)] use std::os::fd::AsRawFd; @@ -56,6 +57,21 @@ use crate::code::global_buffer_model::{GlobalBufferModel, GlobalBufferModelEvent /// How long the daemon waits with no connections before exiting. pub const GRACE_PERIOD: std::time::Duration = std::time::Duration::from_secs(10 * 60); +/// Reap a session that has had no attached connection for this long. Detached +/// sessions otherwise live indefinitely so a client can reconnect (laptop +/// closed, etc.); this bounds memory against truly abandoned ones (Stage 4). +#[cfg(unix)] +const MAX_DETACHED_SESSION_AGE: std::time::Duration = + std::time::Duration::from_secs(24 * 60 * 60); +/// Soft cap on total output-ring bytes across all of this host's sessions. When +/// exceeded, the oldest *detached* sessions are reaped until back under it +/// (live, attached sessions are never reaped). +#[cfg(unix)] +const HOST_RING_CAP_BYTES: usize = 256 * 1024 * 1024; +/// How often the detached-session GC sweep runs. +#[cfg(unix)] +const GC_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5 * 60); + /// Unique identifier for a connected proxy session in daemon mode. pub type ConnectionId = uuid::Uuid; use super::protocol::RequestId; @@ -176,7 +192,7 @@ pub struct ServerModel { /// Per-connection outbound channels, keyed by `ConnectionId`. /// /// The daemon can serve multiple proxy connections simultaneously — one - /// per SSH session / Zap tab connecting to this host. Each entry maps + /// per SSH session / Zaplex tab connecting to this host. Each entry maps /// a connection's `Uuid` to the channel the connection task drains to /// write `ServerMessage`s back to its proxy. connection_senders: HashMap>, @@ -522,6 +538,9 @@ impl ServerModel { // register_connection will cancel the timer the moment the first proxy // arrives. model.start_grace_timer(ctx); + // Periodic memory governor for detached sessions (Stage 4). + #[cfg(unix)] + model.start_gc_timer(ctx); model } @@ -564,12 +583,33 @@ impl ServerModel { let remaining = self.connection_senders.len(); log::info!("Daemon: connection {conn_id} deregistered — {remaining} active remaining"); if remaining == 0 { - log::info!("Daemon: grace timer started ({GRACE_PERIOD:?})"); - self.start_grace_timer(ctx); + // Persistent sessions keep the daemon alive across client + // disconnects (the whole point of the native session layer): only + // arm the shutdown grace timer when nothing is still running. + // (A detached-idle GC for long-abandoned sessions is Stage 4.) + if self.has_live_sessions() { + log::info!("Daemon: no connections, but live session(s) remain — staying up"); + } else { + log::info!("Daemon: grace timer started ({GRACE_PERIOD:?})"); + self.start_grace_timer(ctx); + } } ctx.notify(); } + /// Whether any daemon-hosted session is still alive. Keeps the daemon up + /// across client disconnects so the session survives until reattach. + fn has_live_sessions(&self) -> bool { + #[cfg(unix)] + { + !self.sessions.is_empty() + } + #[cfg(not(unix))] + { + false + } + } + /// Starts (or restarts) a timer that shuts the daemon down after /// [`GRACE_PERIOD`] with no connected proxies. If a timer is already /// running its abort handle is cancelled before the new one is stored. @@ -662,7 +702,7 @@ impl ServerModel { Some(client_message::Message::ResolveConflict(msg)) => { self.handle_resolve_conflict(msg, &request_id, conn_id, ctx) } - // Zap: Remote terminal file link directory listing (for path form validation). + // Zaplex: Remote terminal file link directory listing (for path form validation). #[cfg(feature = "local_fs")] Some(client_message::Message::ListDirectory(msg)) => self.handle_list_directory(msg), #[cfg(feature = "local_fs")] @@ -695,15 +735,35 @@ impl ServerModel { self.handle_close_session(msg); return; } - // Attach/detach/list are not implemented yet (Stages 3-4) — reject - // honestly on all platforms. + // Re-attach a reconnecting client to a still-running session and + // replay the output it missed (Stage 3). + #[cfg(unix)] + Some(client_message::Message::AttachSession(msg)) => { + self.handle_attach_session(conn_id, msg) + } + #[cfg(unix)] + Some(client_message::Message::DetachSession(msg)) => { + self.handle_detach_session(conn_id, msg); + return; + } + // Multi-session listing for the sidebar / adopt-by-id (Stage 4). + #[cfg(unix)] + Some(client_message::Message::ListSessions(_)) => self.handle_list_sessions(), + #[cfg(not(unix))] + Some(client_message::Message::ListSessions(_)) => { + HandlerOutcome::Sync(server_message::Message::Error(ErrorResponse { + code: ErrorCode::InvalidRequest.into(), + message: "zaplex session host requires a unix daemon".to_string(), + })) + } + // Non-unix daemons have no session host (PTY ownership is unix-only). + #[cfg(not(unix))] Some( client_message::Message::AttachSession(_) - | client_message::Message::DetachSession(_) - | client_message::Message::ListSessions(_), + | client_message::Message::DetachSession(_), ) => HandlerOutcome::Sync(server_message::Message::Error(ErrorResponse { code: ErrorCode::InvalidRequest.into(), - message: "session attach/detach/list is not implemented yet".to_string(), + message: "zaplex session host requires a unix daemon".to_string(), })), // Non-unix daemons have no session host (PTY ownership is unix-only). #[cfg(not(unix))] @@ -1563,7 +1623,7 @@ impl ServerModel { } } - /// Zap: Handle `ListDirectory` — sync listing of direct children in a directory. + /// Zaplex: Handle `ListDirectory` — sync listing of direct children in a directory. /// /// For precise validation by remote terminal file link detection: client caches real directory /// entries under a cwd, link detector uses this to extract correct filenames from `ls -l` lines. @@ -1863,6 +1923,16 @@ fn file_context_result_to_proto(result: ReadFileContextResult) -> ReadFileContex } } +/// Current Unix time in epoch milliseconds (`0` if the clock is before the +/// epoch). Used to stamp session attach times for `ListSessions` / the GC. +#[cfg(unix)] +fn now_epoch_millis() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis() as u64) + .unwrap_or(0) +} + /// Daemon-side session-host handlers (Stage 1). Unix-only: the daemon owns the /// PTYs. The per-session state and async tasks live in `super::session_host`. #[cfg(unix)] @@ -1886,6 +1956,14 @@ impl ServerModel { .or_else(|| std::env::var("SHELL").ok()) .unwrap_or_else(|| "/bin/bash".to_string()); let cwd = msg.cwd.filter(|c| !c.is_empty()); + // Per-host scrollback ceiling (bytes) for this session's OutputRing; + // 0/absent → daemon default. Clamp to the host cap so a client can't + // request an unbounded buffer. + let ring_ceiling = msg + .ring_ceiling_bytes + .filter(|&b| b > 0) + .map(|b| (b as usize).min(HOST_RING_CAP_BYTES)) + .unwrap_or(super::session_host::RING_CEILING_BYTES); let (leader_fd, child) = match crate::terminal::local_tty::spawn_session_pty( cwd.as_deref().map(std::path::Path::new), @@ -1921,13 +1999,14 @@ impl ServerModel { super::session_host::Session { leader: async_leader.clone(), child, - ring: zaplex_remote_session::server::output_ring::OutputRing::new( - super::session_host::RING_CEILING_BYTES, - ), + ring: zaplex_remote_session::server::output_ring::OutputRing::new(ring_ceiling), rows, cols, attached: conn_id, input_tx, + cwd, + shell: shell.clone(), + last_attached_ms: now_epoch_millis(), }, ); @@ -1942,6 +2021,37 @@ impl ServerModel { exec.spawn(super::session_host::run_session_writer(async_leader, input_rx)) .detach(); + // Bootstrap the daemon-spawned shell with the Zaplexify shell integration + // (blocks, prompt marks, completions) by writing the init script as the + // session's first input — the ordered writer delivers it ahead of any + // user input. The script emits the InitShell DCS hook and is idempotent + // (ZAPLEX_BOOTSTRAPPED guard), so a later re-attach won't double-run it. + // The terminal *identity* (TERM_PROGRAM=ZaplexTerminal etc.) is set as a + // spawn env var in `spawn_session_pty`, not by this script. Together the + // identity env and the integration script are what make a daemon session + // a real Zaplex terminal rather than a bare VT. + match crate::terminal::shell::ShellType::from_name(&shell) { + Some(shell_type) => { + let mut bootstrap = + crate::terminal::bootstrap::init_shell_script_for_shell(shell_type, &crate::ASSETS) + .into_bytes(); + bootstrap.extend_from_slice(shell_type.execute_command_bytes()); + if let Some(session) = self.sessions.get(&session_id) { + if let Err(e) = session.input_tx.try_send(bootstrap) { + log::warn!("Daemon: failed to enqueue bootstrap for {session_id}: {e}"); + } else { + log::info!("Daemon: bootstrapped session {session_id} ({})", shell_type.name()); + } + } + } + None => { + log::info!( + "Daemon: shell {shell:?} is not Zaplexify-capable; session {session_id} \ + runs as a plain shell (no blocks)" + ); + } + } + log::info!("Daemon: opened session {session_id} ({rows}x{cols}, shell={shell})"); HandlerOutcome::Sync(server_message::Message::SessionOpened(SessionOpened { session_id, @@ -1957,6 +2067,223 @@ impl ServerModel { } } + /// Re-attaches a (possibly reconnected) connection to a still-running + /// session and replays the output it missed. Re-points the session's live + /// stream at `conn_id`, so subsequent `SessionOutput` pushes go to the + /// reconnected client. This is the heart of "survives the drop": the session + /// kept running and buffering into its ring while the client was gone. + fn handle_attach_session( + &mut self, + conn_id: ConnectionId, + msg: AttachSession, + ) -> HandlerOutcome { + let Some(session) = self.sessions.get_mut(&msg.session_id) else { + return HandlerOutcome::Sync(server_message::Message::Error(ErrorResponse { + code: ErrorCode::InvalidRequest.into(), + message: format!("no such session: {}", msg.session_id), + })); + }; + let (base_seq, replay) = session.ring.replay_from(msg.last_seq); + // Route live output to the reconnected connection from now on. + session.attached = conn_id; + session.last_attached_ms = now_epoch_millis(); + let size = SessionSize { + rows: session.rows as u32, + cols: session.cols as u32, + pixel_width: 0, + pixel_height: 0, + }; + log::info!( + "Daemon: attached conn {conn_id} to session {} (replay {} bytes from seq {base_seq})", + msg.session_id, + replay.len() + ); + HandlerOutcome::Sync(server_message::Message::SessionAttached(SessionAttached { + session_id: msg.session_id, + size: Some(size), + base_seq, + replay, + })) + } + + /// Detaches a connection from a session without ending it: the session keeps + /// running and its output accumulates in the ring (live pushes to the now + /// non-attached connection become harmless no-ops) until a later attach. + fn handle_detach_session(&mut self, conn_id: ConnectionId, msg: DetachSession) { + if let Some(session) = self.sessions.get_mut(&msg.session_id) { + // Only the connection that currently owns the attachment may clear it. + // A late DetachSession from a previously-attached tab must not steal + // the attachment from a newer tab that has since adopted this session + // (which would silently cut off the new tab's live output). + if session.attached == conn_id { + session.attached = uuid::Uuid::nil(); + log::info!("Daemon: detached session {} (still running)", msg.session_id); + } else { + log::debug!( + "Daemon: ignoring stale detach for session {} from conn {conn_id} \ + (currently attached to {})", + msg.session_id, + session.attached + ); + } + } + } + + /// Lists all live daemon-hosted sessions (Stage 4: multi-session UI / adopt). + /// Registry membership means alive — exited sessions are removed (and + /// `SessionExited`-announced) by the reader-EOF/close paths. + fn handle_list_sessions(&self) -> HandlerOutcome { + let sessions = self + .sessions + .iter() + .map(|(id, session)| { + let title = session + .cwd + .as_deref() + .filter(|c| !c.is_empty()) + .and_then(|cwd| { + std::path::Path::new(cwd) + .file_name() + .map(|b| b.to_string_lossy().into_owned()) + }) + .unwrap_or_else(|| { + std::path::Path::new(&session.shell) + .file_name() + .map(|b| b.to_string_lossy().into_owned()) + .unwrap_or_else(|| session.shell.clone()) + }); + SessionInfo { + session_id: id.clone(), + title, + cwd: session.cwd.clone().unwrap_or_default(), + alive: true, + last_attached_epoch_millis: session.last_attached_ms, + } + }) + .collect(); + HandlerOutcome::Sync(server_message::Message::SessionList(SessionList { sessions })) + } + + /// Memory governor (Stage 4): reaps detached sessions that are either idle + /// past `max_detached_age_ms`, or — if total ring bytes exceed + /// `host_ring_cap_bytes` — the oldest detached ones until back under the cap. + /// Never touches a session with a live attached connection. Returns the + /// number reaped. Wall-clock is passed in (`now_ms`) so it is unit-testable. + fn gc_sessions( + &mut self, + now_ms: u64, + max_detached_age_ms: u64, + host_ring_cap_bytes: usize, + ) -> usize { + let mut reaped = 0; + + // Phase 1: detached and idle longer than the max age. + let aged: Vec = { + let senders = &self.connection_senders; + self.sessions + .iter() + .filter(|(_, s)| { + let detached = + s.attached == uuid::Uuid::nil() || !senders.contains_key(&s.attached); + detached && now_ms.saturating_sub(s.last_attached_ms) >= max_detached_age_ms + }) + .map(|(id, _)| id.clone()) + .collect() + }; + for id in aged { + if let Some(mut session) = self.sessions.remove(&id) { + let _ = session.child.kill(); + let _ = session.child.wait(); + reaped += 1; + } + } + + // Phase 2: enforce the host-wide ring-bytes cap by reaping oldest + // detached sessions until back under it. + let total: usize = self.sessions.values().map(|s| s.ring.len()).sum(); + if total > host_ring_cap_bytes { + let mut over = total - host_ring_cap_bytes; + let mut candidates: Vec<(u64, String)> = { + let senders = &self.connection_senders; + self.sessions + .iter() + .filter(|(_, s)| { + s.attached == uuid::Uuid::nil() || !senders.contains_key(&s.attached) + }) + .map(|(id, s)| (s.last_attached_ms, id.clone())) + .collect() + }; + candidates.sort_by_key(|(age, _)| *age); // oldest (smallest ms) first + for (_, id) in candidates { + if over == 0 { + break; + } + if let Some(mut session) = self.sessions.remove(&id) { + over = over.saturating_sub(session.ring.len()); + let _ = session.child.kill(); + let _ = session.child.wait(); + reaped += 1; + } + } + } + + if reaped > 0 { + log::info!("Daemon GC: reaped {reaped} detached session(s)"); + } + reaped + } + + /// After a GC sweep, arm the shutdown grace timer if the daemon is now fully + /// idle: no connected proxies *and* no live sessions. This closes the leak + /// where `deregister_connection` left the daemon up because sessions still + /// existed, and those sessions were later reaped by the GC (age / RAM cap) — + /// leaving a daemon with nothing to do and no timer to retire it. The + /// `grace_timer_cancel.is_none()` guard avoids restarting an already-running + /// timer (which would reset the countdown each tick). + fn maybe_arm_grace_after_gc(&mut self, ctx: &mut ModelContext) { + if self.connection_senders.is_empty() + && !self.has_live_sessions() + && self.grace_timer_cancel.is_none() + { + log::info!( + "Daemon: idle after GC (no connections, no sessions) — grace timer started ({GRACE_PERIOD:?})" + ); + self.start_grace_timer(ctx); + } + } + + /// Starts the periodic detached-session GC sweep on the background executor, + /// re-entering the model each tick. Runs for the daemon's lifetime. + fn start_gc_timer(&self, ctx: &mut ModelContext) { + let spawner = ctx.spawner(); + ctx.background_executor() + .spawn(async move { + loop { + async_io::Timer::after(GC_INTERVAL).await; + let now = now_epoch_millis(); + let outcome = spawner + .spawn(move |me, ctx| { + me.gc_sessions( + now, + MAX_DETACHED_SESSION_AGE.as_millis() as u64, + HOST_RING_CAP_BYTES, + ); + // GC may have reaped the last session. If no proxies + // are connected either, the daemon is now fully idle — + // arm the grace timer so it exits. deregister_connection + // deliberately skipped the timer while sessions existed, + // so without this the daemon would linger forever. + me.maybe_arm_grace_after_gc(ctx); + }) + .await; + if outcome.is_err() { + break; // model gone — daemon shutting down + } + } + }) + .detach(); + } + /// Applies a window resize to the session's PTY (TIOCSWINSZ). fn handle_resize_session(&mut self, msg: ResizeSession) { let Some(session) = self.sessions.get_mut(&msg.session_id) else { diff --git a/app/src/remote_server/server_model_tests.rs b/app/src/remote_server/server_model_tests.rs index 29d8dc0005..dbd04c3113 100644 --- a/app/src/remote_server/server_model_tests.rs +++ b/app/src/remote_server/server_model_tests.rs @@ -234,3 +234,601 @@ fn create_directory_creates_nested_directories() { )); assert!(nested.is_dir()); } + +// ---- Daemon session host: end-to-end glue (Stage 2) ----------------------- +// +// Drives the full server-side glue headlessly on a real warpui test App: an +// OpenSession message spawns a real PTY+shell, SessionInput reaches that PTY, +// the background reader task streams PTY bytes back as SessionOutput pushes via +// the model, and CloseSession reaps the shell and emits SessionExited. This is +// the path that was previously only compile-verified (no async-model harness). +// Unix-only: the daemon owns the PTY (PTY ownership is unix-only). + +#[cfg(unix)] +mod daemon_session { + use super::test_model; + use crate::remote_server::proto::{ + client_message, server_message, AttachSession, ClientMessage, CloseSession, DetachSession, + ListSessions, OpenSession, ServerMessage, SessionInput, SessionList, SessionSize, + }; + use futures::future::Either; + use std::time::Duration; + use warpui::App; + + /// Awaits `rx.recv()` but gives up after `dur` so a stuck test fails instead + /// of hanging the CI job. + async fn recv_deadline( + rx: &async_channel::Receiver, + dur: Duration, + ) -> Option { + let timer = async_io::Timer::after(dur); + match futures::future::select(std::pin::pin!(rx.recv()), std::pin::pin!(timer)).await { + Either::Left((Ok(msg), _)) => Some(msg), + _ => None, + } + } + + /// Drains messages until a `SessionOutput` whose accumulated bytes contain + /// `needle`, or the overall deadline elapses. + async fn wait_for_output( + rx: &async_channel::Receiver, + needle: &[u8], + total: Duration, + ) -> bool { + let collect = async { + let mut buf: Vec = Vec::new(); + loop { + match rx.recv().await { + Ok(msg) => { + if let Some(server_message::Message::SessionOutput(out)) = msg.message { + buf.extend_from_slice(&out.bytes); + if buf.windows(needle.len()).any(|w| w == needle) { + return true; + } + } + } + Err(_) => return false, + } + } + }; + let timer = async_io::Timer::after(total); + match futures::future::select(std::pin::pin!(collect), std::pin::pin!(timer)).await { + Either::Left((found, _)) => found, + Either::Right(_) => false, + } + } + + async fn wait_for_exit( + rx: &async_channel::Receiver, + session_id: &str, + total: Duration, + ) -> bool { + let collect = async { + loop { + match rx.recv().await { + Ok(msg) => { + if let Some(server_message::Message::SessionExited(e)) = msg.message { + if e.session_id == session_id { + return true; + } + } + } + Err(_) => return false, + } + } + }; + let timer = async_io::Timer::after(total); + match futures::future::select(std::pin::pin!(collect), std::pin::pin!(timer)).await { + Either::Left((found, _)) => found, + Either::Right(_) => false, + } + } + + fn open_session_msg() -> ClientMessage { + ClientMessage { + request_id: "open-1".to_string(), + message: Some(client_message::Message::OpenSession(OpenSession { + cwd: None, + ring_ceiling_bytes: None, + shell: Some("/bin/bash".to_string()), + env: std::collections::HashMap::new(), + size: Some(SessionSize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }), + })), + } + } + + #[test] + fn open_streams_output_then_close_exits() { + App::test((), |mut app| async move { + // Build the model via the struct-literal helper (no `new()`), so the + // test doesn't need FileModel/RepoMetadata singletons — but still + // gets a real ModelContext (executor + spawner) from the App. + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_id = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id, conn_tx, ctx)); + + // OpenSession -> spawns PTY+shell, replies SessionOpened. + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_session_msg(), ctx)); + + let session_id = { + let msg = recv_deadline(&conn_rx, Duration::from_secs(10)) + .await + .expect("expected a server message after OpenSession"); + match msg.message { + Some(server_message::Message::SessionOpened(o)) => o.session_id, + other => panic!("expected SessionOpened, got {other:?}"), + } + }; + assert!(!session_id.is_empty(), "daemon assigned a session id"); + + // SessionInput: the executed output (not the echoed input) carries + // the marker, proving the byte round-trip reached the real shell. + // `D4''EM0N` echoes verbatim but executes to `D4EM0N`. + model.update(&mut app, |m, ctx| { + m.handle_message( + conn_id, + ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::SessionInput(SessionInput { + session_id: session_id.clone(), + bytes: b"echo D4''EM0N\n".to_vec(), + })), + }, + ctx, + ) + }); + assert!( + wait_for_output(&conn_rx, b"D4EM0N", Duration::from_secs(15)).await, + "expected SessionOutput containing the executed marker" + ); + + // CloseSession -> reaps the shell, emits SessionExited. + model.update(&mut app, |m, ctx| { + m.handle_message( + conn_id, + ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::CloseSession(CloseSession { + session_id: session_id.clone(), + })), + }, + ctx, + ) + }); + assert!( + wait_for_exit(&conn_rx, &session_id, Duration::from_secs(10)).await, + "expected SessionExited after CloseSession" + ); + }); + } + + fn input_msg(session_id: &str, bytes: &[u8]) -> ClientMessage { + ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::SessionInput(SessionInput { + session_id: session_id.to_string(), + bytes: bytes.to_vec(), + })), + } + } + + fn attach_msg(session_id: &str, last_seq: u64) -> ClientMessage { + ClientMessage { + request_id: "attach-1".to_string(), + message: Some(client_message::Message::AttachSession(AttachSession { + session_id: session_id.to_string(), + last_seq, + })), + } + } + + fn close_msg(session_id: &str) -> ClientMessage { + ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::CloseSession(CloseSession { + session_id: session_id.to_string(), + })), + } + } + + fn detach_msg(session_id: &str) -> ClientMessage { + ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::DetachSession(DetachSession { + session_id: session_id.to_string(), + })), + } + } + + fn contains(haystack: &[u8], needle: &[u8]) -> bool { + haystack.windows(needle.len()).any(|w| w == needle) + } + + /// Stage 3 core: a session keeps running while the client is gone, buffers + /// its output in the ring, and replays it on re-attach — then live output + /// re-routes to the reconnected connection. + #[test] + fn detached_session_buffers_output_and_replays_on_reattach() { + App::test((), |mut app| async move { + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_id = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id, conn_tx, ctx)); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_session_msg(), ctx)); + + let session_id = match recv_deadline(&conn_rx, Duration::from_secs(10)).await { + Some(m) => match m.message { + Some(server_message::Message::SessionOpened(o)) => o.session_id, + other => panic!("expected SessionOpened, got {other:?}"), + }, + None => panic!("no SessionOpened before deadline"), + }; + + // Output produced while attached streams normally. + model.update(&mut app, |m, ctx| { + m.handle_message(conn_id, input_msg(&session_id, b"echo BEFOR3\n"), ctx) + }); + assert!( + wait_for_output(&conn_rx, b"BEFOR3", Duration::from_secs(15)).await, + "pre-drop output should stream to the attached connection" + ); + + // Simulate a client drop. The session must keep running (no grace + // shutdown while a session is alive). + model.update(&mut app, |m, ctx| m.deregister_connection(conn_id, ctx)); + + // Output produced WHILE detached can only land in the ring. + model.update(&mut app, |m, ctx| { + m.handle_message(conn_id, input_msg(&session_id, b"echo WH1LE_GONE\n"), ctx) + }); + + // Reconnect on a fresh connection and re-attach from seq 0; replay + // must contain both pre-drop and while-detached output. + let (conn_tx2, conn_rx2) = async_channel::unbounded::(); + let conn_id2 = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id2, conn_tx2, ctx)); + + let mut replay_ok = false; + for _ in 0..50 { + model.update(&mut app, |m, ctx| { + m.handle_message(conn_id2, attach_msg(&session_id, 0), ctx) + }); + if let Some(msg) = recv_deadline(&conn_rx2, Duration::from_secs(2)).await { + if let Some(server_message::Message::SessionAttached(a)) = msg.message { + if contains(&a.replay, b"BEFOR3") && contains(&a.replay, b"WH1LE_GONE") { + replay_ok = true; + break; + } + } + } + async_io::Timer::after(Duration::from_millis(100)).await; + } + assert!( + replay_ok, + "re-attach replay must include both pre-drop and while-detached output" + ); + + // Live output now re-routes to the re-attached connection. + model.update(&mut app, |m, ctx| { + m.handle_message(conn_id2, input_msg(&session_id, b"echo L1V3_NOW\n"), ctx) + }); + assert!( + wait_for_output(&conn_rx2, b"L1V3_NOW", Duration::from_secs(15)).await, + "live output should re-route to the re-attached connection" + ); + + model.update(&mut app, |m, ctx| m.handle_message(conn_id2, close_msg(&session_id), ctx)); + }); + } + + fn open_in(cwd: &str) -> ClientMessage { + ClientMessage { + request_id: "open".to_string(), + message: Some(client_message::Message::OpenSession(OpenSession { + cwd: Some(cwd.to_string()), + ring_ceiling_bytes: None, + shell: Some("/bin/bash".to_string()), + env: std::collections::HashMap::new(), + size: Some(SessionSize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }), + })), + } + } + + fn list_msg() -> ClientMessage { + ClientMessage { + request_id: "list".to_string(), + message: Some(client_message::Message::ListSessions(ListSessions {})), + } + } + + /// First `SessionOpened` on the channel (skips any interleaved output). + async fn recv_session_opened(rx: &async_channel::Receiver) -> Option { + for _ in 0..20 { + match recv_deadline(rx, Duration::from_secs(10)).await { + Some(m) => { + if let Some(server_message::Message::SessionOpened(o)) = m.message { + return Some(o.session_id); + } + } + None => return None, + } + } + None + } + + /// Next `SessionList` on the channel (skips interleaved output / exits). + async fn recv_session_list(rx: &async_channel::Receiver) -> Option { + for _ in 0..100 { + match recv_deadline(rx, Duration::from_secs(5)).await { + Some(m) => { + if let Some(server_message::Message::SessionList(list)) = m.message { + return Some(list); + } + } + None => return None, + } + } + None + } + + /// Stage 4: multiple sessions per daemon are listable, carry their cwd, and + /// the list shrinks when a session is closed. + #[test] + fn list_sessions_reports_open_sessions() { + App::test((), |mut app| async move { + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_id = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id, conn_tx, ctx)); + + // Real, existing working directories — the daemon chdirs the PTY in. + let dir_a = tempfile::tempdir().unwrap(); + let dir_b = tempfile::tempdir().unwrap(); + let path_a = dir_a.path().to_string_lossy().to_string(); + let path_b = dir_b.path().to_string_lossy().to_string(); + + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_in(&path_a), ctx)); + let id_a = recv_session_opened(&conn_rx).await.expect("session A opened"); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_in(&path_b), ctx)); + let id_b = recv_session_opened(&conn_rx).await.expect("session B opened"); + assert_ne!(id_a, id_b); + + // ListSessions reports both, each with its cwd, all alive. + model.update(&mut app, |m, ctx| m.handle_message(conn_id, list_msg(), ctx)); + let list = recv_session_list(&conn_rx).await.expect("SessionList"); + assert_eq!(list.sessions.len(), 2, "two sessions listed"); + let by_id: std::collections::HashMap<&str, &str> = list + .sessions + .iter() + .map(|s| (s.session_id.as_str(), s.cwd.as_str())) + .collect(); + assert_eq!(by_id.get(id_a.as_str()), Some(&path_a.as_str())); + assert_eq!(by_id.get(id_b.as_str()), Some(&path_b.as_str())); + assert!(list.sessions.iter().all(|s| s.alive)); + + // Closing one shrinks the list to the survivor. + model.update(&mut app, |m, ctx| m.handle_message(conn_id, close_msg(&id_a), ctx)); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, list_msg(), ctx)); + let list2 = recv_session_list(&conn_rx).await.expect("SessionList after close"); + assert_eq!(list2.sessions.len(), 1); + assert_eq!(list2.sessions[0].session_id, id_b); + + model.update(&mut app, |m, ctx| m.handle_message(conn_id, close_msg(&id_b), ctx)); + }); + } + + /// Stage 4 memory governor: the GC reaps idle detached sessions (age) and, + /// when over the host ring cap, the oldest detached ones — never live ones. + #[test] + fn gc_reaps_idle_then_over_cap_detached_sessions() { + App::test((), |mut app| async move { + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_id = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id, conn_tx, ctx)); + + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().to_string_lossy().to_string(); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_in(&path), ctx)); + let id1 = recv_session_opened(&conn_rx).await.expect("session 1 opened"); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_in(&path), ctx)); + let id2 = recv_session_opened(&conn_rx).await.expect("session 2 opened"); + + // Drop the connection: both sessions detach but keep running (the + // grace guard keeps the daemon up). + model.update(&mut app, |m, ctx| m.deregister_connection(conn_id, ctx)); + model.update(&mut app, |m, _ctx| { + m.sessions.get_mut(&id1).unwrap().last_attached_ms = 0; + m.sessions.get_mut(&id2).unwrap().last_attached_ms = 1_000_000_000_000; + }); + + // Age GC (60s max, unlimited cap): reap ancient id1, keep recent id2. + let reaped = model.update(&mut app, |m, _ctx| { + m.gc_sessions(1_000_000_000_000, 60_000, usize::MAX) + }); + assert_eq!(reaped, 1, "ancient detached session reaped"); + model.update(&mut app, |m, _ctx| { + assert!(!m.sessions.contains_key(&id1), "id1 reaped"); + assert!(m.sessions.contains_key(&id2), "id2 kept"); + }); + + // Give id2 ring bytes, then a zero host cap reaps it (poll until its + // shell output has landed in the ring). + model.update(&mut app, |m, ctx| { + m.handle_message(uuid::Uuid::new_v4(), input_msg(&id2, b"echo GC\n"), ctx) + }); + let mut reaped2 = 0; + for _ in 0..50 { + reaped2 = + model.update(&mut app, |m, _ctx| m.gc_sessions(1_000_000_000_000, u64::MAX, 0)); + if reaped2 == 1 { + break; + } + async_io::Timer::after(Duration::from_millis(100)).await; + } + assert_eq!(reaped2, 1, "over-cap detached session reaped once it has ring bytes"); + model.update(&mut app, |m, _ctx| assert!(m.sessions.is_empty(), "all sessions reaped")); + }); + } + + /// Regression: a `DetachSession` from a connection that no longer owns the + /// attachment must be ignored. Otherwise a late detach from a closed/old tab + /// would steal the attachment from a newer tab that adopted the same session, + /// silently cutting off the new tab's live output. + #[test] + fn stale_detach_does_not_steal_a_newer_attachment() { + App::test((), |mut app| async move { + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_a = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_a, conn_tx, ctx)); + + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().to_string_lossy().to_string(); + model.update(&mut app, |m, ctx| m.handle_message(conn_a, open_in(&path), ctx)); + let id = recv_session_opened(&conn_rx).await.expect("session opened"); + + // A newer tab (conn_b) adopts the session — it now owns the attachment. + let conn_b = uuid::Uuid::new_v4(); + model.update(&mut app, |m, _| { + m.sessions.get_mut(&id).unwrap().attached = conn_b; + }); + + // A stale detach from the OLD connection must NOT clear it. + model.update(&mut app, |m, ctx| m.handle_message(conn_a, detach_msg(&id), ctx)); + model.update(&mut app, |m, _| { + assert_eq!( + m.sessions.get(&id).unwrap().attached, + conn_b, + "stale detach from a non-owner must be ignored" + ); + }); + + // The current owner's detach does clear it. + model.update(&mut app, |m, ctx| m.handle_message(conn_b, detach_msg(&id), ctx)); + model.update(&mut app, |m, _| { + assert_eq!( + m.sessions.get(&id).unwrap().attached, + uuid::Uuid::nil(), + "the owning connection's detach clears the attachment" + ); + }); + }); + } + + /// Regression: when the GC reaps the *last* session while no proxies are + /// connected, the daemon must arm its shutdown grace timer. Otherwise it + /// lingers forever — `deregister_connection` deliberately skips the timer + /// while a session still exists, and nothing re-evaluated idleness after the + /// session was later reaped. Covers `maybe_arm_grace_after_gc`. + #[test] + fn gc_reaping_last_session_arms_grace_timer() { + App::test((), |mut app| async move { + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_id = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id, conn_tx, ctx)); + + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().to_string_lossy().to_string(); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_in(&path), ctx)); + let _id = recv_session_opened(&conn_rx).await.expect("session opened"); + + // Client drops but the session keeps running: no grace timer yet. + model.update(&mut app, |m, ctx| m.deregister_connection(conn_id, ctx)); + model.update(&mut app, |m, _ctx| { + assert!( + m.grace_timer_cancel.is_none(), + "session still alive — daemon stays up without a grace timer" + ); + // Age the session so the next GC sweep reaps it. + for s in m.sessions.values_mut() { + s.last_attached_ms = 0; + } + }); + + // GC reaps the now-ancient last session, then re-evaluates idleness. + model.update(&mut app, |m, ctx| { + let reaped = m.gc_sessions(1_000_000_000_000, 60_000, usize::MAX); + assert_eq!(reaped, 1, "the last detached session is reaped"); + m.maybe_arm_grace_after_gc(ctx); + }); + + model.update(&mut app, |m, _ctx| { + assert!(m.sessions.is_empty(), "no sessions remain"); + assert!( + m.grace_timer_cancel.is_some(), + "GC emptied the daemon with no connections — grace timer must be armed" + ); + }); + }); + } + + /// A daemon session must be a real Zaplex terminal (blocks / prompt marks / + /// completions), not a bare VT. That takes two *independent* pieces, and this + /// test pins both so a regression in either fails loudly: + /// + /// 1. **Shell integration ran** — the daemon injects the Zaplexify init + /// script as the session's first input. On startup that script emits the + /// InitShell DCS hook (`ESC P $ d …`); it appears in the session output + /// only if the bootstrap injection actually happened. (The script does + /// *not* set TERM_PROGRAM — that is piece 2.) + /// 2. **Terminal identity** — the shell is spawned with + /// `TERM_PROGRAM=ZaplexTerminal` (a spawn env var in `spawn_session_pty`, + /// not from the script). Proven by `echo TP=$TERM_PROGRAM` printing the + /// executed value: the echoed input carries the literal `$TERM_PROGRAM`, + /// so `TP=ZaplexTerminal` appears only if the env var is really set. + #[test] + fn daemon_session_runs_zaplexify_bootstrap() { + App::test((), |mut app| async move { + let model = app.add_singleton_model(|_ctx| test_model()); + let (conn_tx, conn_rx) = async_channel::unbounded::(); + let conn_id = uuid::Uuid::new_v4(); + model.update(&mut app, |m, ctx| m.register_connection(conn_id, conn_tx, ctx)); + model.update(&mut app, |m, ctx| m.handle_message(conn_id, open_session_msg(), ctx)); + let session_id = recv_session_opened(&conn_rx).await.expect("session opened"); + + // (1) The integration script runs on open and emits the InitShell DCS + // hook (ESC P $ d …) before any input of ours — this is what no bare + // VT would produce. + assert!( + wait_for_output(&conn_rx, b"\x1bP$d", Duration::from_secs(20)).await, + "daemon shell should run the Zaplexify integration (InitShell DCS hook in output)" + ); + + // (2) The shell carries the Zaplex terminal identity env. + model.update(&mut app, |m, ctx| { + m.handle_message(conn_id, input_msg(&session_id, b"echo TP=$TERM_PROGRAM\n"), ctx) + }); + assert!( + wait_for_output(&conn_rx, b"TP=ZaplexTerminal", Duration::from_secs(20)).await, + "daemon shell should be spawned with TERM_PROGRAM=ZaplexTerminal" + ); + + // (3) The daemon owns persistence itself, so its login shell must NOT + // auto-launch the user's terminal multiplexer (byobu/tmux) — otherwise + // it joins the user's existing session group and cross-contaminates + // I/O. `BYOBU_DISABLE=1` must be set in the spawn env. + model.update(&mut app, |m, ctx| { + m.handle_message(conn_id, input_msg(&session_id, b"echo BD=$BYOBU_DISABLE\n"), ctx) + }); + assert!( + wait_for_output(&conn_rx, b"BD=1", Duration::from_secs(20)).await, + "daemon shell must set BYOBU_DISABLE=1 (no multiplexer auto-attach)" + ); + + model.update(&mut app, |m, ctx| m.handle_message(conn_id, close_msg(&session_id), ctx)); + }); + } +} diff --git a/app/src/remote_server/session_host.rs b/app/src/remote_server/session_host.rs index f368715ab3..53bb765d22 100644 --- a/app/src/remote_server/session_host.rs +++ b/app/src/remote_server/session_host.rs @@ -44,6 +44,13 @@ pub(super) struct Session { pub(super) attached: ConnectionId, /// Ordered keyboard/mouse input → the writer task → the PTY. pub(super) input_tx: async_channel::Sender>, + /// Working directory the session was opened in (for `ListSessions`). + pub(super) cwd: Option, + /// Login shell the session runs (for `ListSessions` titles). + pub(super) shell: String, + /// Unix epoch millis of the last attach (open counts as the first attach); + /// `0` means never. Drives `ListSessions` and the detached-idle GC. + pub(super) last_attached_ms: u64, } /// Per-session reader task: pumps PTY output into the model (which appends it to diff --git a/app/src/remote_server/ssh_transport.rs b/app/src/remote_server/ssh_transport.rs index 19a4ef3738..aea204cf6a 100644 --- a/app/src/remote_server/ssh_transport.rs +++ b/app/src/remote_server/ssh_transport.rs @@ -19,6 +19,10 @@ use remote_server::setup::{ }; use remote_server::ssh::ssh_args; use remote_server::transport::{Connection, RemoteTransport}; +// ControlMaster self-healing is unix-only (the `headless_connect` module is +// `#[cfg(unix)]`), so the related field/import are gated to match. +#[cfg(unix)] +use warp_ssh_manager::SshServerInfo; /// SSH transport: connects via a ControlMaster socket. /// @@ -30,6 +34,12 @@ use remote_server::transport::{Connection, RemoteTransport}; pub struct SshTransport { socket_path: PathBuf, auth_context: Arc, + /// When set (daemon sessions), `connect` first re-establishes the per-host + /// shared ControlMaster if its socket went stale/dead — so a persistent + /// session can reconnect after a network drop killed the master. `None` for + /// non-daemon callers, whose ControlMaster lifecycle is managed elsewhere. + #[cfg(unix)] + self_heal_server: Option, } impl fmt::Debug for SshTransport { @@ -45,9 +55,21 @@ impl SshTransport { Self { socket_path, auth_context, + #[cfg(unix)] + self_heal_server: None, } } + /// Opt into ControlMaster self-healing on `connect` (daemon sessions). The + /// `server` provides the SSH parameters needed to respawn a dead master. + /// The same transport instance is reused for reconnect attempts, so this is + /// what lets a persistent session re-attach after the master died. + #[cfg(unix)] + pub fn with_self_heal(mut self, server: SshServerInfo) -> Self { + self.self_heal_server = Some(server); + self + } + pub fn socket_path(&self) -> &PathBuf { &self.socket_path } @@ -156,7 +178,7 @@ fn should_skip_scp_fallback(error: &InstallError) -> bool { } // =========================================================================== -// Zap fork: dev-mode remote-server installation path +// Zaplex fork: dev-mode remote-server installation path // // Upstream/release builds have the remote install script download pre-built // remote-server binaries from GitHub releases. However, during local source @@ -317,12 +339,12 @@ async fn cross_compile_remote_server(backend: &DevBuildBackend) -> Result Result{:?})", + "dev remote-server cross-compilation timeout (>{:?})", remote_server::setup::DEV_CROSS_COMPILE_TIMEOUT ) })? - .map_err(|e| anyhow!("无法启动 cargo 构建: {e}"))?; + .map_err(|e| anyhow!("Failed to start cargo build: {e}"))?; if !status.success() { let code = status.code().unwrap_or(-1); return Err(anyhow!( - "cargo cross-compilation failed (exit {code}); see cargo output in the terminal running Zap" + "cargo cross-compilation failed (exit {code}); see cargo output in the terminal running Zaplex" )); } @@ -411,7 +433,7 @@ async fn dev_install_local_binary(socket_path: &Path) -> Result<()> { // Prerequisite check: missing any item returns error, caller falls back to download-install. if !musl_target_installed().await { return Err(anyhow!( - "未安装 rust target {};可执行 `rustup target add {}`", + "Rust target {} not installed; run `rustup target add {}`", remote_server::setup::DEV_MUSL_TARGET, remote_server::setup::DEV_MUSL_TARGET, )); @@ -421,9 +443,9 @@ async fn dev_install_local_binary(socket_path: &Path) -> Result<()> { // If neither available, error. let backend = select_dev_build_backend().ok_or_else(|| { anyhow!( - "未找到可用的 musl 交叉编译后端。建议安装 cargo-zigbuild + zig\ - (`cargo install cargo-zigbuild`,并用包管理器安装 `zig`),\ - 或安装完整的 musl C/C++ 交叉工具链({})", + "No available musl cross-compilation backend found. Recommended: install cargo-zigbuild + zig\ + (`cargo install cargo-zigbuild`, and install `zig` via package manager),\ + or install a complete musl C/C++ cross-toolchain ({})", DEV_MUSL_LINKER_CANDIDATES.join(" / ") ) })?; @@ -662,7 +684,7 @@ impl RemoteTransport for SshTransport { remote_server::setup::remote_server_binary() ); - // Zap fork: DEBUG source build (no release tag) uses dev mode, + // Zaplex fork: DEBUG source build (no release tag) uses dev mode, // cross-compiling local `warp` and uploading instead of downloading stale GitHub release. // On failure (cross-compile prerequisites missing, etc.), print warning and fall back // to download-install, preserving dev experience. Release builds skip this entire block. @@ -705,7 +727,18 @@ impl RemoteTransport for SshTransport { ) -> Pin> + Send>> { let socket_path = self.socket_path.clone(); let remote_proxy_command = self.remote_proxy_command(); + #[cfg(unix)] + let self_heal_server = self.self_heal_server.clone(); Box::pin(async move { + // For daemon sessions, re-establish the shared ControlMaster if its + // socket went stale/dead (e.g. the master died on a network drop). + // Without this, reconnect attempts reuse a dead master and all fail + // even though the remote daemon session is still alive and attachable. + #[cfg(unix)] + if let Some(server) = self_heal_server.as_ref() { + crate::remote_server::headless_connect::ensure_control_master(server, &socket_path) + .await?; + } let mut args = ssh_args(&socket_path); args.push(remote_proxy_command); diff --git a/app/src/remote_server/unix/proxy.rs b/app/src/remote_server/unix/proxy.rs index 873ff76309..ee9b7023d4 100644 --- a/app/src/remote_server/unix/proxy.rs +++ b/app/src/remote_server/unix/proxy.rs @@ -188,7 +188,7 @@ fn flock_wait(fd: std::os::unix::io::RawFd, operation: libc::c_int) -> anyhow::R /// stdin/stdout and the socket. /// /// The proxy is protocol-agnostic — it forwards raw bytes without parsing the -/// length-prefixed framing. The framing is handled at the endpoints (Zap +/// length-prefixed framing. The framing is handled at the endpoints (Zaplex /// client and daemon). /// /// **Important**: the stdout direction uses a manual read→write→flush loop diff --git a/app/src/resource_center/main_page.rs b/app/src/resource_center/main_page.rs index cd921d9c99..d726330c89 100644 --- a/app/src/resource_center/main_page.rs +++ b/app/src/resource_center/main_page.rs @@ -137,7 +137,7 @@ impl ResourceCenterMainView { } None => !is_tips_completed && !is_onboarded, }, - // Expand Maximize Zap section once user has completed welcome tips, + // Expand Maximize Zaplex section once user has completed welcome tips, // and keep open after users have completed/skipped all tips FeatureSection::MaximizeWarp => match ChannelState::app_version() { Some(version) => { @@ -394,7 +394,7 @@ impl ResourceCenterMainView { .with_text_and_icon_label( TextAndIcon::new( TextAndIconAlignment::IconFirst, - "Invite a friend to Zap", + "Invite a friend to Zaplex", Icon::new(SEND_SVG_PATH, appearance.theme().accent()), MainAxisSize::Max, MainAxisAlignment::Center, diff --git a/app/src/resource_center/mod.rs b/app/src/resource_center/mod.rs index 218a89a182..e781a1e6b3 100644 --- a/app/src/resource_center/mod.rs +++ b/app/src/resource_center/mod.rs @@ -90,9 +90,9 @@ pub enum TipAction { AiCommandSearch, SaveNewLaunchConfig, WarpAI, - // This toggles Zap Drive rather than opening it. This enum can't directly be + // This toggles Zaplex Drive rather than opening it. This enum can't directly be // renamed because we serialize it into the welcome tips. - ZapDrive, + ZaplexDrive, Changelog, // Note that this item has been deprecated from the UI and is not in any section. // We are leaving it in this enum to ensure that we don't re-use `Workflows` as a @@ -112,7 +112,7 @@ impl TipAction { TipAction::ThemePicker => "workspace:show_theme_chooser", TipAction::SaveNewLaunchConfig => "workspace:open_launch_config_save_modal", TipAction::WarpAI => "workspace:toggle_ai_assistant", - TipAction::ZapDrive => "workspace:toggle_left_panel", + TipAction::ZaplexDrive => "workspace:toggle_left_panel", // Slash commands are also registered as editable bindings, so callers can look them up here // the same way they do regular app actions. TipAction::Changelog => "/changelog", diff --git a/app/src/resource_center/utils.rs b/app/src/resource_center/utils.rs index a5f4ffa71c..13bf6746ec 100644 --- a/app/src/resource_center/utils.rs +++ b/app/src/resource_center/utils.rs @@ -121,7 +121,7 @@ pub fn get_additional_keybindings() -> Vec { ), CommandBinding::new( "workspace:hide_warp".into(), - "Hide Zap".into(), + "Hide Zaplex".into(), Some(Keystroke::parse("cmd-h").expect("Valid keystroke")), ), CommandBinding::new( @@ -131,7 +131,7 @@ pub fn get_additional_keybindings() -> Vec { ), CommandBinding::new( "workspace:quit_warp".into(), - "Quit Zap".into(), + "Quit Zaplex".into(), Some(Keystroke::parse("cmd-q").expect("Valid keystroke")), ), CommandBinding::new( diff --git a/app/src/resource_center/view.rs b/app/src/resource_center/view.rs index 1482d8e096..005c32e9a5 100644 --- a/app/src/resource_center/view.rs +++ b/app/src/resource_center/view.rs @@ -338,7 +338,7 @@ impl ResourceCenterView { if FeatureFlag::AvatarInTabBar.is_enabled() { String::new() } else { - "Zap Essentials".to_string() + "Zaplex Essentials".to_string() } } }; diff --git a/app/src/resource_limits.rs b/app/src/resource_limits.rs index f71b2f3b0f..be7fa13649 100644 --- a/app/src/resource_limits.rs +++ b/app/src/resource_limits.rs @@ -1,4 +1,4 @@ -/// Adjusts resource limits applied to the Zap process (e.g.: the limit on open +/// Adjusts resource limits applied to the Zaplex process (e.g.: the limit on open /// file descriptors) to ensure proper behavior. pub fn adjust_resource_limits() { #[cfg(target_os = "macos")] @@ -6,7 +6,7 @@ pub fn adjust_resource_limits() { /// Our desired limit on the maximum number of file descriptors we can open. /// /// MacOS sets this value to 256, by default, which can interfere with - /// normal usage of Zap for users who open lots of tabs. + /// normal usage of Zaplex for users who open lots of tabs. const TARGET_FD_LIMIT: u64 = 2560; use nix::sys::resource::{getrlimit, setrlimit, Resource::RLIMIT_NOFILE}; diff --git a/app/src/root_view.rs b/app/src/root_view.rs index 1528d6b0c4..8a70635c83 100644 --- a/app/src/root_view.rs +++ b/app/src/root_view.rs @@ -11,7 +11,7 @@ use crate::cloud_object::model::persistence::ObjectStoreModel; use crate::cloud_object::{GenericStringObjectFormat, JsonObjectType, ObjectType}; use crate::drive::export::ExportManager; use crate::drive::items::WarpDriveItemId; -use crate::drive::{ObjectTypeAndId, ZapDriveObjectArgs, ZapDriveObjectSettings}; +use crate::drive::{ObjectTypeAndId, ZaplexDriveObjectArgs, ZaplexDriveObjectSettings}; use crate::experiments::{BlockOnboarding, Experiment}; use crate::interval_timer::IntervalTimer; use crate::launch_configs::launch_config; @@ -102,10 +102,10 @@ use crate::auth::{WebHandoffEvent, WebHandoffView}; /// Return the product name of the current channel as the initial window title and for quake/transferred window titles. /// -/// Using `ChannelState::app_id().application_name()` allows OSS builds to show `Zap`, while -/// upstream channels (Stable/Preview/Dev) still show their own names (`Zap` / `WarpPreview` / `WarpDev`), +/// Using `ChannelState::app_id().application_name()` allows OSS builds to show `Zaplex`, while +/// upstream channels (Stable/Preview/Dev) still show their own names (`Zaplex` / `WarpPreview` / `WarpDev`), /// avoiding hardcoded strings scattered across forks (Windows Task Manager groups processes by window title, -/// hardcoding `"Zap"` would display Zap as `Zap(N)` in Task Manager). +/// hardcoding `"Zaplex"` would display Zaplex as `Zaplex(N)` in Task Manager). /// /// Note: After window creation, `Workspace::update_window_title()` overrides this value on each tab switch/rename, /// so this function only determines the initial title when the window first opens before any tab is attached. @@ -747,7 +747,7 @@ fn path_if_directory(path: &Path) -> Option<&Path> { /// Opens a new window with the workspace configured according to `source`. Returns the /// newly-opened window ID and a handle to the root view in that window. /// -/// This is the canonical way to open a new Zap window - all other entrypoints should delegate to +/// This is the canonical way to open a new Zaplex window - all other entrypoints should delegate to /// it if possible. pub(crate) fn open_new_with_workspace_source( source: NewWorkspaceSource, @@ -862,7 +862,7 @@ fn open_linear_issue_work_in_new_window(args: &LinearIssueWork, ctx: &mut AppCon }); } -fn open_warp_drive_object(arg: &ZapDriveObjectArgs, ctx: &mut AppContext) { +fn open_warp_drive_object(arg: &ZaplexDriveObjectArgs, ctx: &mut AppContext) { match arg.object_type { ObjectType::Notebook => open_new_workspace_with_notebook_open( SyncId::ServerId(arg.server_id), @@ -888,7 +888,7 @@ fn display_object_missing_error_in_window(window_id: WindowId, ctx: &mut AppCont fn open_new_workspace_with_notebook_open( notebook_id: SyncId, - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, ctx: &mut AppContext, ) { open_new_with_workspace_source( @@ -902,7 +902,7 @@ fn open_new_workspace_with_notebook_open( fn open_new_workspace_with_workflow_open( workflow_id: SyncId, - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, ctx: &mut AppContext, ) { open_new_with_workspace_source( @@ -1250,11 +1250,11 @@ fn toggle_quake_mode_window(global_resource_handles: &GlobalResourceHandles, ctx }; } -/// This action will show or hide all of Zap's windows except the quake window +/// This action will show or hide all of Zaplex's windows except the quake window /// -/// - If Zap is active and has any windows, hide those windows. -/// - If Zap is hidden, show all windows. -/// - If Zap is active but has 0 normal windows, create a new window with a new session. +/// - If Zaplex is active and has any windows, hide those windows. +/// - If Zaplex is hidden, show all windows. +/// - If Zaplex is active but has 0 normal windows, create a new window with a new session. fn show_or_hide_non_quake_mode_windows(_: &(), ctx: &mut AppContext) { let quake_window_id = get_quake_mode_state(ctx).map(|state| state.window_id); let non_quake_mode_window_ids = ctx @@ -1265,7 +1265,7 @@ fn show_or_hide_non_quake_mode_windows(_: &(), ctx: &mut AppContext) { open_new(&(), ctx); } let windowing_model = ctx.windows(); - // Now there is at least one window. If a Zap window is active, hide the app. + // Now there is at least one window. If a Zaplex window is active, hide the app. // Otherwise, show activate the app to show it in front. let active_window_id = windowing_model.active_window(); match active_window_id { @@ -1310,11 +1310,11 @@ pub enum NewWorkspaceSource { }, NotebookById { id: SyncId, - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, }, WorkflowById { id: SyncId, - settings: ZapDriveObjectSettings, + settings: ZaplexDriveObjectSettings, }, AgentSession { options: Box, @@ -1447,7 +1447,7 @@ impl RootView { me.handle_auth_manager_event(event, ctx); }); - // Zap(localization, Phase 5): `PreferencesSyncer` physically deleted. + // Zaplex(localization, Phase 5): `PreferencesSyncer` physically deleted. // Original `InitialLoadCompleted` event was used to call `apply_onboarding_settings` // after cloud preferences sync completes. In localized scenario, onboarding settings applied locally directly. @@ -1478,11 +1478,11 @@ impl RootView { if #[cfg(target_family = "wasm")] { AuthOnboardingState::WebImport(AuthOnboardingTarget::Workspace(workspace_args.into())) } else { - // When ZapNewSettingsModes is enabled, show onboarding before login for + // When ZaplexNewSettingsModes is enabled, show onboarding before login for // users who haven't completed it yet (tracked via a local UserPreferences key). - let has_completed_local_onboarding = FeatureFlag::ZapNewSettingsModes.is_enabled() + let has_completed_local_onboarding = FeatureFlag::ZaplexNewSettingsModes.is_enabled() && has_completed_local_onboarding(ctx); - let should_show_pre_login_onboarding = FeatureFlag::ZapNewSettingsModes.is_enabled() + let should_show_pre_login_onboarding = FeatureFlag::ZaplexNewSettingsModes.is_enabled() && FeatureFlag::AgentOnboarding.is_enabled() && !has_completed_local_onboarding; if FeatureFlag::ForceLogin.is_enabled() { @@ -1954,7 +1954,7 @@ impl RootView { pub fn open_warp_drive_object_in_existing_window( &mut self, - arg: &ZapDriveObjectArgs, + arg: &ZaplexDriveObjectArgs, ctx: &mut ViewContext, ) -> bool { if let AuthOnboardingState::Terminal(handle) = &self.auth_onboarding_state { @@ -2087,7 +2087,7 @@ impl RootView { } /// Insert a command that should create a subshell. If we support bootstrapping AKA - /// "warpifying" its [`ShellType`], set a flag to automatically bootstrap it when the command's + /// "zaplexifying" its [`ShellType`], set a flag to automatically bootstrap it when the command's /// block receives the [`AfterBlockStarted`] event. pub fn insert_subshell_command_and_bootstrap_if_supported( &mut self, @@ -2233,7 +2233,7 @@ impl RootView { } else if let AuthOnboardingState::NeedsSsoLink { .. } = &self.auth_onboarding_state { // We should be able to access their SSO state; if not, default to true, - // since we should err on the side of them _not_ being able to use Zap. + // since we should err on the side of them _not_ being able to use Zaplex. if auth_state.needs_sso_link() == Some(false) { self.auth_onboarding_state.complete_sso_link(ctx); } @@ -2269,8 +2269,8 @@ impl RootView { // application, which ought to be valid. self.web_handoff(ctx); } else { - // Zap removed log_out UI entry; native no longer forces logout. - log::warn!("User account disabled; ignoring (Zap removed log_out)"); + // Zaplex removed log_out UI entry; native no longer forces logout. + log::warn!("User account disabled; ignoring (Zaplex removed log_out)"); } } } @@ -2301,7 +2301,7 @@ impl RootView { ) { match event { AuthOverrideWarningModalEvent::Close => { - // Zap removed log_out entry; closing no longer triggers logout. + // Zaplex removed log_out entry; closing no longer triggers logout. } AuthOverrideWarningModalEvent::BulkExport => { self.export_all_warp_drive_objects(ctx); @@ -2422,7 +2422,7 @@ impl RootView { true } - /// Zap(localization, Phase 5): Original `handle_preferences_syncer_event` applied onboarding settings + /// Zaplex(localization, Phase 5): Original `handle_preferences_syncer_event` applied onboarding settings /// after cloud preferences sync initial load completes, physically deleted with syncer. /// Onboarding settings now applied directly when onboarding completes; no need to defer until cloud sync. /// If onboarding stored a pending tutorial (because login was required first), @@ -2436,7 +2436,7 @@ impl RootView { return; }; - if FeatureFlag::ZapNewSettingsModes.is_enabled() + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() && FeatureFlag::TabConfigs.is_enabled() { let intention = tutorial.intention(); diff --git a/app/src/search/action/data_source.rs b/app/src/search/action/data_source.rs index b7c76e80b8..72dd5eb51b 100644 --- a/app/src/search/action/data_source.rs +++ b/app/src/search/action/data_source.rs @@ -22,7 +22,7 @@ pub struct CommandBindingDataSource { impl CommandBindingDataSource { #[cfg(not(target_family = "wasm"))] pub fn new(binding_source: ModelHandle, ctx: &mut ModelContext) -> Self { - // Zap: command-palette actions always use character-level fuzzy matching (SkimMatcherV2). + // Zaplex: command-palette actions always use character-level fuzzy matching (SkimMatcherV2). // Tantivy's default tokenizer does not segment CJK, so it treats an entire Chinese // description as a single token and prefix-matches it, which means that searching for // "theme" fails to match a localized "open theme chooser" description. @@ -157,7 +157,7 @@ impl ActionSearcher for FuzzyActionSearcher { // both the search term and the description to ensure that we are matching the two // with the same casing. // - // Zap: also append binding.name (the action identifier, e.g. `workspace:show_theme_chooser`) + // Zaplex: also append binding.name (the action identifier, e.g. `workspace:show_theme_chooser`) // into the searchable string, replacing `:` and `_` with spaces, to make subsequence matching easier. // This way, typing "theme" can also match a localized "open theme chooser" description. let mut searchable = binding diff --git a/app/src/search/action/search_item.rs b/app/src/search/action/search_item.rs index 55be013f1c..f1edd00c25 100644 --- a/app/src/search/action/search_item.rs +++ b/app/src/search/action/search_item.rs @@ -211,7 +211,7 @@ impl SearchItemIcon for BindingGroup { | Self::Terminal | Self::Notifications => appearance.theme().foreground().into_solid(), Self::WarpAi if !FeatureFlag::AgentMode.is_enabled() => { - ColorU::from_u32(colors::WARP_AI) + ColorU::from_u32(colors::ZAPLEX_AI) } Self::WarpAi => appearance.theme().foreground().into_solid(), Self::Workflow => warp_drive_icon_color(appearance, DriveObjectType::Workflow), diff --git a/app/src/search/ai_context_menu/view.rs b/app/src/search/ai_context_menu/view.rs index aff509ed06..78ba218899 100644 --- a/app/src/search/ai_context_menu/view.rs +++ b/app/src/search/ai_context_menu/view.rs @@ -417,7 +417,7 @@ impl AIContextMenu { categories.push(AIContextMenuCategory::CurrentFolderFiles); } } - // Zap: Previously would push Code category based on the outline_codebase_symbols_for_at_context_menu setting. + // Zaplex: Previously would push Code category based on the outline_codebase_symbols_for_at_context_menu setting. // Now that outline is retired, the Code category no longer appears. return categories; } @@ -452,7 +452,7 @@ impl AIContextMenu { categories.push(AIContextMenuCategory::Commands); } categories.push(AIContextMenuCategory::Blocks); - // Zap: Code category retired in sync with outline sunset; no longer pushed. + // Zaplex: Code category retired in sync with outline sunset; no longer pushed. if show_warp_drive && FeatureFlag::DriveObjectsAsContext.is_enabled() { categories.push(AIContextMenuCategory::Workflows); categories.push(AIContextMenuCategory::Notebooks); @@ -474,7 +474,7 @@ impl AIContextMenu { categories } else if !is_shared_session_viewer { // Terminal mode: show Files category only. - // Zap: Previously would also push Code category based on outline_codebase_symbols_for_at_context_menu setting; + // Zaplex: Previously would also push Code category based on outline_codebase_symbols_for_at_context_menu setting; // now that outline is retired, the Code category no longer appears. if is_active_dir_in_git_repo { @@ -595,7 +595,7 @@ impl AIContextMenu { // Get initial categories for proper initialization let initial_categories = Self::get_categories_for_mode(true, false, false, false, ctx); // Default to AI mode, not a viewer, not ambient agent, not CLI agent input - // Zap: Previously, this would create a CodeSymbolCache (subscribing to RepoOutlines) to support + // Zaplex: Previously, this would create a CodeSymbolCache (subscribing to RepoOutlines) to support // code symbol search. This feature has been retired along with outline sunset, // and these subscription/creation code blocks have been removed accordingly. @@ -867,7 +867,7 @@ impl AIContextMenu { ); }); } - // Zap: Code category retired along with outline sunset. This category will not appear in categories, + // Zaplex: Code category retired along with outline sunset. This category will not appear in categories, // but the enum variant is retained to avoid widespread match errors; this branch will never be hit. #[cfg(not(target_family = "wasm"))] NavigationState::Category(AIContextMenuCategory::Workflows) => { @@ -1043,7 +1043,7 @@ impl AIContextMenu { mixer.add_sync_source(block_data_source, [QueryFilter::Blocks]); }); } - // Zap: Code category retired along with outline sunset; will not appear but kept as a no-op branch + // Zaplex: Code category retired along with outline sunset; will not appear but kept as a no-op branch // to avoid match errors. AIContextMenuCategory::Code => {} AIContextMenuCategory::Workflows => { @@ -1335,7 +1335,7 @@ impl AIContextMenu { .finish() } - // Zap: Previously, `render_code_symbols_indexing` was responsible for rendering + // Zaplex: Previously, `render_code_symbols_indexing` was responsible for rendering // “Code symbols indexing...” prompt when indexing code symbols. After outline retirement, // this render path is no longer called and the function is removed accordingly. @@ -1544,7 +1544,7 @@ impl AIContextMenu { fallback: Box, app: &AppContext, ) -> Box { - // Zap: Previously, this would check if code symbols were being indexed under the Code category, + // Zaplex: Previously, this would check if code symbols were being indexed under the Code category, // and if so, would display `render_code_symbols_indexing`. After outline retirement, this category no longer appears; completely removed. let _ = category; diff --git a/app/src/search/command_palette/render_util.rs b/app/src/search/command_palette/render_util.rs index 3431a427c5..7fdae3a5c6 100644 --- a/app/src/search/command_palette/render_util.rs +++ b/app/src/search/command_palette/render_util.rs @@ -49,5 +49,5 @@ fn render_search_item_icon_inner( } pub mod colors { - pub const WARP_AI: u32 = 0xF3B911FF; + pub const ZAPLEX_AI: u32 = 0xF3B911FF; } diff --git a/app/src/search/command_palette/view.rs b/app/src/search/command_palette/view.rs index b2ad6c6fd8..6474a8b385 100644 --- a/app/src/search/command_palette/view.rs +++ b/app/src/search/command_palette/view.rs @@ -101,7 +101,7 @@ pub enum Event { InvokeEnvironmentVariables { id: SyncId }, /// Open a notebook identified by `id`. OpenNotebook { id: SyncId }, - /// View the relevant object in the Zap Drive sidebar. + /// View the relevant object in the Zaplex Drive sidebar. ViewInWarpDrive { id: ObjectTypeAndId }, /// Open a file at the given path. OpenFile { @@ -402,7 +402,7 @@ impl View { | (PaletteMode::LaunchConfig, QueryFilter::LaunchConfigurations) | (PaletteMode::Files, QueryFilter::Files) | (PaletteMode::Conversations, QueryFilter::Conversations) - | (PaletteMode::ZapDrive, QueryFilter::Drive) + | (PaletteMode::ZaplexDrive, QueryFilter::Drive) ) } diff --git a/app/src/search/command_palette/warp_drive/data_source.rs b/app/src/search/command_palette/warp_drive/data_source.rs index a98f20b708..020ecf097c 100644 --- a/app/src/search/command_palette/warp_drive/data_source.rs +++ b/app/src/search/command_palette/warp_drive/data_source.rs @@ -21,7 +21,7 @@ use crate::workflows::WorkflowObject; use std::collections::HashMap; use warpui::{AppContext, Entity, ModelContext, SingletonEntity}; -/// Datasource that searches against all Zap Drive objects +/// Datasource that searches against all Zaplex Drive objects pub struct DataSource { searcher: Box, } @@ -549,10 +549,10 @@ mod full_text_searcher { use warpui::{AppContext, SingletonEntity}; /// Memory budget for the search index of zap drive. - /// Zap could potentially have a lot of objects, so we increase it from the default of 50MB to 100MB + /// Zaplex could potentially have a lot of objects, so we increase it from the default of 50MB to 100MB const MEMORY_BUDGET: usize = 100_000_000; // TODO: is 100MB really necessary? - // All Zap Drive objects are boosted due to multiple fields being a part of the same total score, + // All Zaplex Drive objects are boosted due to multiple fields being a part of the same total score, // putting them at an inherent disadvantage, as each field would only have a fractional weight. define_search_schema!( schema_name: NOTEBOOK_SEARCH_SCHEMA, diff --git a/app/src/search/command_search/searcher.rs b/app/src/search/command_search/searcher.rs index 1388aa6530..677629df48 100644 --- a/app/src/search/command_search/searcher.rs +++ b/app/src/search/command_search/searcher.rs @@ -60,10 +60,10 @@ pub enum CommandSearchItemAction { /// The user requested to run the AI query search item with this query text. RunAIQuery(String), - /// The user accepted the search item to open Zap AI. - ZapAI, + /// The user accepted the search item to open Zaplex AI. + ZaplexAI, - /// The user accepted the search item to translate the query to a command using Zap AI. + /// The user accepted the search item to translate the query to a command using Zaplex AI. TranslateUsingWarpAI, } diff --git a/app/src/search/command_search/view.rs b/app/src/search/command_search/view.rs index 8498b0eac3..fc077555f9 100644 --- a/app/src/search/command_search/view.rs +++ b/app/src/search/command_search/view.rs @@ -485,7 +485,7 @@ impl CommandSearchView { AcceptHistory(_) | AcceptWorkflow(_) | AcceptNotebook(_) - | ZapAI + | ZaplexAI | AcceptEnvVarCollection(_) | TranslateUsingWarpAI | AcceptAIQuery(_) => false, diff --git a/app/src/search/command_search/warp_ai.rs b/app/src/search/command_search/warp_ai.rs index a994f740d4..ea609bd143 100644 --- a/app/src/search/command_search/warp_ai.rs +++ b/app/src/search/command_search/warp_ai.rs @@ -21,8 +21,8 @@ use warpui::{ AppContext, Element, SingletonEntity, }; -const OPEN_WARP_AI_ITEM_BODY_TEXT: &str = "Ask Zap AI for command suggestions"; -const TRANSLATE_WITH_WARP_AI_ITEM_BODY_TEXT: &str = "Translate into shell command using Zap AI"; +const OPEN_ZAPLEX_AI_ITEM_BODY_TEXT: &str = "Ask Zaplex AI for command suggestions"; +const TRANSLATE_WITH_ZAPLEX_AI_ITEM_BODY_TEXT: &str = "Translate into shell command using Zaplex AI"; #[derive(Clone, Debug)] pub enum WarpAISearchItem { @@ -36,8 +36,8 @@ pub enum WarpAISearchItem { impl WarpAISearchItem { fn item_body_text(&self) -> &'static str { match self { - WarpAISearchItem::Translate => TRANSLATE_WITH_WARP_AI_ITEM_BODY_TEXT, - WarpAISearchItem::Open => OPEN_WARP_AI_ITEM_BODY_TEXT, + WarpAISearchItem::Translate => TRANSLATE_WITH_ZAPLEX_AI_ITEM_BODY_TEXT, + WarpAISearchItem::Open => OPEN_ZAPLEX_AI_ITEM_BODY_TEXT, } } } @@ -50,7 +50,7 @@ impl SearchItem for WarpAISearchItem { highlight_state: ItemHighlightState, appearance: &Appearance, ) -> Box { - // Since the Zap AI logo color is hardcoded, let's find the best + // Since the Zaplex AI logo color is hardcoded, let's find the best // contrasting color depending on the user's theme and the item's selected state. let command_search_background = appearance.theme().surface_1(); let item_background_color = match highlight_state.container_background_fill(appearance) { @@ -113,23 +113,23 @@ impl SearchItem for WarpAISearchItem { fn accept_result(&self) -> CommandSearchItemAction { match self { WarpAISearchItem::Translate => CommandSearchItemAction::TranslateUsingWarpAI, - WarpAISearchItem::Open => CommandSearchItemAction::ZapAI, + WarpAISearchItem::Open => CommandSearchItemAction::ZaplexAI, } } fn execute_result(&self) -> CommandSearchItemAction { match self { WarpAISearchItem::Translate => CommandSearchItemAction::TranslateUsingWarpAI, - WarpAISearchItem::Open => CommandSearchItemAction::ZapAI, + WarpAISearchItem::Open => CommandSearchItemAction::ZaplexAI, } } fn accessibility_label(&self) -> String { - format!("Zap AI: {}", self.item_body_text()) + format!("Zaplex AI: {}", self.item_body_text()) } } -/// Zap only retains synchronous entry points: open BYOP Agent or write natural language back to input box. +/// Zaplex only retains synchronous entry points: open BYOP Agent or write natural language back to input box. /// The cloud “natural-language-to-command” async source has been removed. pub struct WarpAIDataSource; diff --git a/app/src/search/command_search/workflows/stored_workflows_data_source.rs b/app/src/search/command_search/workflows/stored_workflows_data_source.rs index bf573b1f25..9c3356b0a5 100644 --- a/app/src/search/command_search/workflows/stored_workflows_data_source.rs +++ b/app/src/search/command_search/workflows/stored_workflows_data_source.rs @@ -30,7 +30,7 @@ pub(crate) struct StoredWorkflowsSnapshot { filter_to_command_workflows: bool, } -/// Creates an async data source for cloud workflows (i.e. those that exist in Zap Drive). +/// Creates an async data source for cloud workflows (i.e. those that exist in Zaplex Drive). pub fn stored_workflows_data_source( ) -> AsyncSnapshotDataSource { AsyncSnapshotDataSource::new( diff --git a/app/src/search/data_source.rs b/app/src/search/data_source.rs index 448ce5205d..d85486e906 100644 --- a/app/src/search/data_source.rs +++ b/app/src/search/data_source.rs @@ -180,7 +180,7 @@ pub enum QueryFilter { /// Filter results for launch configurations. LaunchConfigurations, - /// Filter for objects in Zap Drive + /// Filter for objects in Zaplex Drive Drive, /// Filter results for environment variables. diff --git a/app/src/search/palette_styles.rs b/app/src/search/palette_styles.rs index 518ff3b6e0..0ba953ccf7 100644 --- a/app/src/search/palette_styles.rs +++ b/app/src/search/palette_styles.rs @@ -17,7 +17,7 @@ pub const MULTILINE_RESULT_EXTRA_VERTICAL_PADDING: f32 = 2.; /// /// Figma reference: "Palette Menu Item" (node-id=6241:68275) is 28px tall with 4px vertical /// padding, leaving 20px for inner content. -/// https://www.figma.com/design/YjhPAtwuMsy6QnldxfL1DH/Open-files-in-Zap?node-id=6241-68275&m=dev +/// https://www.figma.com/design/YjhPAtwuMsy6QnldxfL1DH/Open-files-in-Zaplex?node-id=6241-68275&m=dev const COMMAND_PALETTE_BASE_ROW_HEIGHT: f32 = 28.; pub const PALETTE_HEIGHT: f32 = 464.; diff --git a/app/src/search/searcher_test.rs b/app/src/search/searcher_test.rs index 46ae816a4b..19c3e6f2c8 100644 --- a/app/src/search/searcher_test.rs +++ b/app/src/search/searcher_test.rs @@ -42,7 +42,7 @@ fn test_tokenizer_simple() { #[test] fn test_tokenizer_warp_special_chars() { // Test string includes warp-related terms with hyphen, underscore, forward slash, backslash, and colon - let test_string = "warp-cli/launch_command:run C:\\\\Program_Files\\\\Zap\\\\core-engine.dll check_status:/dev/warp_drive-0"; + let test_string = "warp-cli/launch_command:run C:\\\\Program_Files\\\\Zaplex\\\\core-engine.dll check_status:/dev/warp_drive-0"; let tokens = token_stream_helper(test_string); assert_eq!(tokens.len(), 25); @@ -56,7 +56,7 @@ fn test_tokenizer_warp_special_chars() { assert_token( &tokens[7], 7, - "C:\\\\Program_Files\\\\Zap\\\\core-engine", + "C:\\\\Program_Files\\\\Zaplex\\\\core-engine", 28, 64, ); diff --git a/app/src/search/welcome_palette/view.rs b/app/src/search/welcome_palette/view.rs index e596f01ac6..65cb7093ba 100644 --- a/app/src/search/welcome_palette/view.rs +++ b/app/src/search/welcome_palette/view.rs @@ -89,7 +89,7 @@ pub enum Event { OpenNotebook { id: SyncId, }, - /// View the relevant object in the Zap Drive sidebar. + /// View the relevant object in the Zaplex Drive sidebar. ViewInWarpDrive { id: ObjectTypeAndId, }, diff --git a/app/src/server/experiments/convert.rs b/app/src/server/experiments/convert.rs index 18b955fb7e..a792d74321 100644 --- a/app/src/server/experiments/convert.rs +++ b/app/src/server/experiments/convert.rs @@ -13,8 +13,8 @@ impl Display for ServerExperiment { Self::EnvVarsEarlyAccessExperiment => "ENV_VARS_EARLY_ACCESS_EXPERIMENT", Self::AgentModeAnalyticsExperiment => "AGENT_MODE_ANALYTICS_EXPERIMENT", Self::WindowsLaunchExperiment => "WINDOWS_LAUNCH_EXPERIMENT", - Self::TmuxSshWarpificationControl => "TMUX_SSH_WARPIFICATION_CONTROL", - Self::TmuxSshWarpificationExperiment => "TMUX_SSH_WARPIFICATION_EXPERIMENT", + Self::TmuxSshZaplexificationControl => "TMUX_SSH_ZAPLEXIFICATION_CONTROL", + Self::TmuxSshZaplexificationExperiment => "TMUX_SSH_ZAPLEXIFICATION_EXPERIMENT", Self::SuggestedCodeDiffsControl => "SUGGESTED_CODE_DIFFS_CONTROL", Self::SuggestedCodeDiffsExperiment => "SUGGESTED_CODE_DIFFS_EXPERIMENT", Self::PromptSuggestionsViaMaaControl => "PROMPT_SUGGESTIONS_VIA_MAA_CONTROL", @@ -38,8 +38,8 @@ impl ServerExperiment { "ENV_VARS_EARLY_ACCESS_EXPERIMENT" => Ok(Self::EnvVarsEarlyAccessExperiment), "AGENT_MODE_ANALYTICS_EXPERIMENT" => Ok(Self::AgentModeAnalyticsExperiment), "WINDOWS_LAUNCH_EXPERIMENT" => Ok(Self::WindowsLaunchExperiment), - "TMUX_SSH_WARPIFICATION_CONTROL" => Ok(Self::TmuxSshWarpificationControl), - "TMUX_SSH_WARPIFICATION_EXPERIMENT" => Ok(Self::TmuxSshWarpificationExperiment), + "TMUX_SSH_ZAPLEXIFICATION_CONTROL" => Ok(Self::TmuxSshZaplexificationControl), + "TMUX_SSH_ZAPLEXIFICATION_EXPERIMENT" => Ok(Self::TmuxSshZaplexificationExperiment), "SUGGESTED_CODE_DIFFS_CONTROL" => Ok(Self::SuggestedCodeDiffsControl), "SUGGESTED_CODE_DIFFS_EXPERIMENT" => Ok(Self::SuggestedCodeDiffsExperiment), "PROMPT_SUGGESTIONS_VIA_MAA_CONTROL" => Ok(Self::PromptSuggestionsViaMaaControl), diff --git a/app/src/server/experiments/mod.rs b/app/src/server/experiments/mod.rs index 3ee8030121..5729d2a10b 100644 --- a/app/src/server/experiments/mod.rs +++ b/app/src/server/experiments/mod.rs @@ -28,8 +28,8 @@ pub enum ServerExperiment { EnvVarsEarlyAccessExperiment, AgentModeAnalyticsExperiment, WindowsLaunchExperiment, - TmuxSshWarpificationControl, - TmuxSshWarpificationExperiment, + TmuxSshZaplexificationControl, + TmuxSshZaplexificationExperiment, SuggestedCodeDiffsControl, SuggestedCodeDiffsExperiment, PromptSuggestionsViaMaaControl, @@ -73,8 +73,8 @@ impl ServerExperiment { // TODO(alokedesai): Clean this up now that we no longer gate access to the Windows // build on an allowlist. } - Self::TmuxSshWarpificationControl => FeatureFlag::SSHTmuxWrapper.set_enabled(false), - Self::TmuxSshWarpificationExperiment => { + Self::TmuxSshZaplexificationControl => FeatureFlag::SSHTmuxWrapper.set_enabled(false), + Self::TmuxSshZaplexificationExperiment => { // Only enable the TMUX-based experience if not on windows. ConPTY doesn't support // DCS, which we need in order to use tmux control mode. if cfg!(not(windows)) { diff --git a/app/src/server/telemetry.rs b/app/src/server/telemetry.rs index 4f9990dbc6..72737686b7 100644 --- a/app/src/server/telemetry.rs +++ b/app/src/server/telemetry.rs @@ -1,4 +1,4 @@ -// Zap: The telemetry sending layer and context provider have been removed. +// Zaplex: The telemetry sending layer and context provider have been removed. // Only `TelemetryEvent` enum and its auxiliary types remain here, serving as type stubs // for many UI and model call sites. @@ -60,7 +60,7 @@ use crate::terminal::block_list_viewport::InputMode; use crate::terminal::cli_agent_sessions::CLIAgentInputEntrypoint; use crate::terminal::cli_agent_sessions::CLIAgentRichInputCloseReason; use crate::terminal::input::TelemetryInputSuggestionsMode; -use crate::terminal::model::ansi::WarpificationUnavailableReason; +use crate::terminal::model::ansi::ZaplexificationUnavailableReason; use crate::terminal::model::block::BlockId; use crate::terminal::model::session::SessionId; use crate::terminal::model::terminal_model::BlockSelectionCardinality; @@ -148,7 +148,7 @@ pub struct BlockLatencyInfo { pub execution_ms: u64, } -// Compatibility metadata for local Zap Drive object event shells. +// Compatibility metadata for local Zaplex Drive object event shells. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum TelemetryObjectType { Workflow, @@ -191,13 +191,13 @@ impl From for TelemetrySpace { } } -/// Common metadata retained for local Zap Drive event call sites that act on a specific object. +/// Common metadata retained for local Zaplex Drive event call sites that act on a specific object. /// Events that only apply to a single object type may use specific metadata like [`WorkflowTelemetryMetadata`], /// [`NotebookTelemetryMetadata`], or [`EnvVarTelemetryMetadata`] instead. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ObjectTelemetryMetadata { pub object_type: TelemetryObjectType, - /// Legacy server UID slot. Zap keeps it optional while object-event call sites are being + /// Legacy server UID slot. Zaplex keeps it optional while object-event call sites are being /// localized. pub object_uid: Option, /// The space through which the user has access to the object. @@ -357,7 +357,7 @@ impl From for MCPServerTelemetryError { } } -// Zap Phase 2a: `OpenedSharingDialogEvent` + `SharingDialogSource` and +// Zaplex Phase 2a: `OpenedSharingDialogEvent` + `SharingDialogSource` and // the corresponding `OpenedSharingDialog` `TelemetryEvent` variant removed // along with the sharing dialog UI. @@ -402,7 +402,7 @@ pub enum PaletteSource { PrefixChange, Keybinding, CtrlTab { shift_pressed_initially: bool }, - ZapDrive, + ZaplexDrive, QuitModal, LogOutModal, IntegrationTest, @@ -465,7 +465,7 @@ pub enum PluginChipTelemetryKind { #[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum NotificationAgentVariant { - /// Zap's built-in agent (Oz). + /// Zaplex's built-in agent (Oz). Oz, /// A CLI agent (e.g., Claude Code, Gemini CLI, etc.). CLIAgent(CLIAgentType), @@ -527,7 +527,7 @@ pub enum CommandCorrectionEvent { pub enum CommandSearchResultType { History, Workflow, - ZapAI, + ZaplexAI, TranslateUsingWarpAI, Notebook, EnvVarCollection, @@ -544,7 +544,7 @@ impl From<&CommandSearchItemAction> for CommandSearchResultType { AcceptWorkflow(_) => Self::Workflow, AcceptNotebook(_) => Self::Notebook, AcceptEnvVarCollection(_) => Self::EnvVarCollection, - ZapAI => Self::ZapAI, + ZaplexAI => Self::ZaplexAI, TranslateUsingWarpAI => Self::TranslateUsingWarpAI, AcceptAIQuery(_) | RunAIQuery(_) => Self::AIQuery, } @@ -667,7 +667,7 @@ pub enum KnowledgePaneEntrypoint { Settings, #[serde(rename = "warp_drive")] - ZapDrive, + ZaplexDrive, #[serde(rename = "ai_blocklist")] AIBlocklist, @@ -686,7 +686,7 @@ pub enum MCPServerCollectionPaneEntrypoint { Settings, #[serde(rename = "warp_drive")] - ZapDrive, + ZaplexDrive, #[serde(rename = "slash_command")] SlashCommand, @@ -1251,7 +1251,7 @@ pub enum TelemetryEvent { DatabaseReadError(String), DatabaseWriteError(String), AppStartup(AppStartupInfo), - /// The native app was opened while logged out. Since Zap requires login, + /// The native app was opened while logged out. Since Zaplex requires login, /// this usually means a new user. LoggedOutStartup, /// We attempted to bootstrap an SSH session via the SSH wrapper. The @@ -1481,11 +1481,11 @@ pub enum TelemetryEvent { InitialWorkingDirectoryConfigurationChanged { advanced_mode_enabled: bool, }, - /// Opened legacy Zap AI. + /// Opened legacy Zaplex AI. OpenedWarpAI { source: OpenedWarpAISource, }, - /// Issued legacy Zap AI request. + /// Issued legacy Zaplex AI request. WarpAIRequestIssued { result: WarpAIRequestResult, }, @@ -1554,7 +1554,7 @@ pub enum TelemetryEvent { ToggleSshTmuxWrapper { enabled: bool, }, - ToggleSshWarpification { + ToggleSshZaplexification { enabled: bool, }, /// User changed the SSH extension install mode. @@ -1568,26 +1568,26 @@ pub enum TelemetryEvent { }, /// An ssh interactive session was detected. SshInteractiveSessionDetected(SshInteractiveSessionDetected), - SshTmuxWarpifyBannerDisplayed, - /// A SSH Warpify Block was accepted - SshTmuxWarpifyBlockAccepted, - /// A SSH Warpify Block was dismissed - SshTmuxWarpifyBlockDismissed, - WarpifyFooterShown { + SshTmuxZaplexifyBannerDisplayed, + /// A SSH Zaplexify Block was accepted + SshTmuxZaplexifyBlockAccepted, + /// A SSH Zaplexify Block was dismissed + SshTmuxZaplexifyBlockDismissed, + ZaplexifyFooterShown { is_ssh: bool, }, AgentToolbarDismissed, - WarpifyFooterAcceptedWarpify { + ZaplexifyFooterAcceptedZaplexify { is_ssh: bool, }, - /// How long until the warpify process succeeded - SshTmuxWarpificationSuccess { + /// How long until the zaplexify process succeeded + SshTmuxZaplexificationSuccess { tmux_installation: Option, duration_ms: u64, }, /// An SSH Error block was displayed to the user. - SshTmuxWarpificationErrorBlock { - error: WarpificationUnavailableReason, + SshTmuxZaplexificationErrorBlock { + error: ZaplexificationUnavailableReason, tmux_installation: Option, }, /// A SSH Install Tmux Block was displayed. @@ -1617,7 +1617,7 @@ pub enum TelemetryEvent { source: WarpDriveSource, is_code_mode_v2: bool, }, - // Toggled the legacy Zap AI side panel. + // Toggled the legacy Zaplex AI side panel. ToggleWarpAI { opened: bool, }, @@ -1664,7 +1664,7 @@ pub enum TelemetryEvent { LogOut, SettingsImportInitiated, CopyObjectToClipboard(TelemetryObjectType), - OpenAndWarpifyDockerSubshell { + OpenAndZaplexifyDockerSubshell { /// Some variant if we support this shell type, and None otherwise. shell_type: Option, }, @@ -1816,7 +1816,7 @@ pub enum TelemetryEvent { /// language auto-detection false-positive. AgentModePotentialAutoDetectionFalsePositive(AgentModeAutoDetectionFalsePositivePayload), - /// This is a telemetry event used to help track performance of Agent Predict in Zap, + /// This is a telemetry event used to help track performance of Agent Predict in Zaplex, /// by keeping track of the context given and the predictions generated. AgentModePrediction { was_suggestion_accepted: bool, @@ -1826,7 +1826,7 @@ pub enum TelemetryEvent { does_actual_command_match_history_prediction: bool, history_prediction_likelihood: f64, total_history_count: usize, - // Zap leaves these optional; no telemetry sender consumes them. + // Zaplex leaves these optional; no telemetry sender consumes them. actual_next_command_run: Option, history_based_autosuggestion_state: Option, generate_ai_input_suggestions_request: Option, @@ -1840,7 +1840,7 @@ pub enum TelemetryEvent { block_id: Option, view: PromptSuggestionViewType, /// Legacy request token from the `/passive-suggestion` request that generated this - /// suggestion. Zap keeps it optional for local diagnostics only. + /// suggestion. Zaplex keeps it optional for local diagnostics only. server_request_token: Option, }, @@ -1853,7 +1853,7 @@ pub enum TelemetryEvent { code_exchange_id: Option, block_id: Option, request_duration_ms: u64, - /// Legacy request token from the `/passive-suggestion` request. Zap keeps it optional + /// Legacy request token from the `/passive-suggestion` request. Zaplex keeps it optional /// for local diagnostics only. server_request_token: Option, }, @@ -1876,7 +1876,7 @@ pub enum TelemetryEvent { id: String, block_id: String, static_prompt_suggestion_name: String, - // Zap leaves these optional; no telemetry sender consumes them. + // Zaplex leaves these optional; no telemetry sender consumes them. query: Option, block_command: Option, request_duration_ms: u64, @@ -2368,7 +2368,7 @@ pub enum TelemetryEvent { block_id: BlockId, user_took_over: bool, }, - /// Detected that Zap is running in an isolated sandbox. + /// Detected that Zaplex is running in an isolated sandbox. DetectedIsolationPlatform { platform: warp_isolation_platform::IsolationPlatformType, }, @@ -2983,8 +2983,8 @@ impl TelemetryEvent { Some(json!({ "remember": remember })) } TelemetryEvent::AgentToolbarDismissed => None, - TelemetryEvent::WarpifyFooterShown { is_ssh } - | TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh } => { + TelemetryEvent::ZaplexifyFooterShown { is_ssh } + | TelemetryEvent::ZaplexifyFooterAcceptedZaplexify { is_ssh } => { Some(json!({ "is_ssh": is_ssh })) } TelemetryEvent::ToggleSameLinePrompt { enabled } => Some(json!({ "enabled": enabled })), @@ -3039,7 +3039,7 @@ impl TelemetryEvent { TelemetryEvent::CopyObjectToClipboard(object_type) => { Some(json!({ "object_type": object_type })) } - TelemetryEvent::OpenAndWarpifyDockerSubshell { shell_type } => { + TelemetryEvent::OpenAndZaplexifyDockerSubshell { shell_type } => { Some(json!({ "shell_type": shell_type })) } TelemetryEvent::ToggleBlockFilterQuery { enabled, source } => { @@ -3064,7 +3064,7 @@ impl TelemetryEvent { Some(json!({"enabled": enabled})) } TelemetryEvent::ToggleSshTmuxWrapper { enabled } => Some(json!({"enabled": enabled})), - TelemetryEvent::ToggleSshWarpification { enabled } => Some(json!({"enabled": enabled})), + TelemetryEvent::ToggleSshZaplexification { enabled } => Some(json!({"enabled": enabled})), TelemetryEvent::SetSshExtensionInstallMode { mode } => Some(json!({"mode": mode})), TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { checked } => { Some(json!({"checked": checked})) @@ -3072,14 +3072,14 @@ impl TelemetryEvent { TelemetryEvent::SshInteractiveSessionDetected(ssh_interactive_session_detected) => { Some(json!({"ssh_interactive_session": ssh_interactive_session_detected})) } - TelemetryEvent::SshTmuxWarpificationSuccess { + TelemetryEvent::SshTmuxZaplexificationSuccess { duration_ms, tmux_installation, } => Some(json!({ "duration_ms": duration_ms, "tmux_installation": *tmux_installation, })), - TelemetryEvent::SshTmuxWarpificationErrorBlock { + TelemetryEvent::SshTmuxZaplexificationErrorBlock { error, tmux_installation, } => Some(json!({ @@ -3658,7 +3658,7 @@ impl TelemetryEvent { | TelemetryEvent::SetNewWindowsAtCustomSize | TelemetryEvent::DisableInputSync | TelemetryEvent::ShowSubshellBanner - | TelemetryEvent::SshTmuxWarpifyBannerDisplayed + | TelemetryEvent::SshTmuxZaplexifyBannerDisplayed | TelemetryEvent::AddDenylistedSubshellCommand | TelemetryEvent::RemoveDenylistedSubshellCommand | TelemetryEvent::AddAddedSubshellCommand @@ -3666,8 +3666,8 @@ impl TelemetryEvent { | TelemetryEvent::ReceivedSubshellRcFileDcs | TelemetryEvent::AddDenylistedSshTmuxWrapperHost | TelemetryEvent::RemoveDenylistedSshTmuxWrapperHost - | TelemetryEvent::SshTmuxWarpifyBlockAccepted - | TelemetryEvent::SshTmuxWarpifyBlockDismissed + | TelemetryEvent::SshTmuxZaplexifyBlockAccepted + | TelemetryEvent::SshTmuxZaplexifyBlockDismissed | TelemetryEvent::SshInstallTmuxBlockDisplayed | TelemetryEvent::SshInstallTmuxBlockAccepted | TelemetryEvent::SshInstallTmuxBlockDismissed @@ -4293,14 +4293,14 @@ impl TelemetryEvent { | TelemetryEvent::RemoveDenylistedSshTmuxWrapperHost | TelemetryEvent::ToggleSshTmuxWrapper { .. } | TelemetryEvent::SshInteractiveSessionDetected(_) - | TelemetryEvent::SshTmuxWarpifyBannerDisplayed - | TelemetryEvent::SshTmuxWarpifyBlockAccepted - | TelemetryEvent::SshTmuxWarpifyBlockDismissed - | TelemetryEvent::WarpifyFooterShown { .. } + | TelemetryEvent::SshTmuxZaplexifyBannerDisplayed + | TelemetryEvent::SshTmuxZaplexifyBlockAccepted + | TelemetryEvent::SshTmuxZaplexifyBlockDismissed + | TelemetryEvent::ZaplexifyFooterShown { .. } | TelemetryEvent::AgentToolbarDismissed - | TelemetryEvent::WarpifyFooterAcceptedWarpify { .. } - | TelemetryEvent::SshTmuxWarpificationSuccess { .. } - | TelemetryEvent::SshTmuxWarpificationErrorBlock { .. } + | TelemetryEvent::ZaplexifyFooterAcceptedZaplexify { .. } + | TelemetryEvent::SshTmuxZaplexificationSuccess { .. } + | TelemetryEvent::SshTmuxZaplexificationErrorBlock { .. } | TelemetryEvent::SshInstallTmuxBlockDisplayed | TelemetryEvent::SshInstallTmuxBlockAccepted | TelemetryEvent::SshInstallTmuxBlockDismissed @@ -4337,7 +4337,7 @@ impl TelemetryEvent { | TelemetryEvent::UnsupportedShell { .. } | TelemetryEvent::LogOut | TelemetryEvent::CopyObjectToClipboard(_) - | TelemetryEvent::OpenAndWarpifyDockerSubshell { .. } + | TelemetryEvent::OpenAndZaplexifyDockerSubshell { .. } | TelemetryEvent::UpdateBlockFilterQuery | TelemetryEvent::UpdateBlockFilterQueryContextLines { .. } | TelemetryEvent::ToggleBlockFilterQuery { .. } @@ -4403,7 +4403,7 @@ impl TelemetryEvent { | TelemetryEvent::MCPServerSpawned { .. } | TelemetryEvent::MCPToolCallAccepted { .. } | TelemetryEvent::ExecutedWarpDrivePrompt { .. } - | TelemetryEvent::ToggleSshWarpification { .. } + | TelemetryEvent::ToggleSshZaplexification { .. } | TelemetryEvent::SetSshExtensionInstallMode { .. } | TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { .. } | TelemetryEvent::SettingsImportInitiated diff --git a/app/src/server_time.rs b/app/src/server_time.rs index a6cf54e8f3..484358fac9 100644 --- a/app/src/server_time.rs +++ b/app/src/server_time.rs @@ -34,7 +34,7 @@ impl From> for ServerTimestamp { /// Locally estimated server time. /// -/// Zap no longer requests cloud `/current_time`; startup path initializes with local current time, +/// Zaplex no longer requests cloud `/current_time`; startup path initializes with local current time, /// callers can still get wall-clock time that advances with the monotonic clock via this type. #[derive(Debug, Clone)] pub struct ServerTime { diff --git a/app/src/settings/ai.rs b/app/src/settings/ai.rs index 319ac866ba..a8129fb6e0 100644 --- a/app/src/settings/ai.rs +++ b/app/src/settings/ai.rs @@ -1171,7 +1171,7 @@ impl Default for PerAgentSettings { impl settings_value::SettingsValue for PerAgentSettings {} define_settings_group!(AISettings, settings: [ - // Legacy setting. Zap's Zap agent is now always enabled; do not use this field to determine enablement status. + // Legacy setting. Zaplex's Zaplex agent is now always enabled; do not use this field to determine enablement status. is_any_ai_enabled: IsAnyAIEnabled { type: bool, default: true, @@ -1470,7 +1470,7 @@ define_settings_group!(AISettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "cloud_platform.third_party_api_keys.aws_bedrock_credentials_enabled", - description: "Whether Zap should use your local AWS credentials for Bedrock-enabled requests.", + description: "Whether Zaplex should use your local AWS credentials for Bedrock-enabled requests.", } // Whether to automatically run the AWS login command when Bedrock credentials are expired. // @@ -1533,7 +1533,7 @@ define_settings_group!(AISettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "agents.knowledge.warp_drive_context_enabled", - description: "Whether Zap Drive context is included in AI requests.", + description: "Whether Zaplex Drive context is included in AI requests.", } // Whether the agent mode setup banner has been shown for a given repo path. @@ -1590,7 +1590,7 @@ define_settings_group!(AISettings, settings: [ private: true, } - // Whether or not the user has enabled the ability to use Zap credits even when providing + // Whether or not the user has enabled the ability to use Zaplex credits even when providing // their own LLM provider API key. can_use_warp_credits_with_byok: CanUseWarpCreditsWithByok { type: bool, @@ -1599,7 +1599,7 @@ define_settings_group!(AISettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "cloud_platform.third_party_api_keys.can_use_warp_credits_with_byok", - description: "Whether Zap credits can be used even when providing your own API key.", + description: "Whether Zaplex credits can be used even when providing your own API key.", } should_render_use_agent_footer_for_user_commands: ShouldRenderUseAgentToolbarForUserCommands { @@ -1795,7 +1795,7 @@ define_settings_group!(AISettings, settings: [ description: "Whether agent notifications are shown.", } - // Zap T1-2: completed tool cards are hidden by default (aligned with opencode TUI showDetails behavior). + // Zaplex T1-2: completed tool cards are hidden by default (aligned with opencode TUI showDetails behavior). // true → by default hide cards in status.is_done() such as RequestCommandOutput / ReadFiles / // Grep / FileGlob / RequestFileEdits, keeping only in-progress + error, // so long sessions are not flooded with piled-up historical cards burying new content. The folded state can be toggled from the appearance settings panel. @@ -1845,7 +1845,7 @@ define_settings_group!(AISettings, settings: [ description: "User-configured custom Agent providers (OpenAI-compatible).", } - // Zap BYOP local conversation compaction — 1:1 aligned with opencode `Config.compaction.auto`. + // Zaplex BYOP local conversation compaction — 1:1 aligned with opencode `Config.compaction.auto`. // When true, summarization is triggered automatically on token-overflow; when false, only manual /compact /compact-and triggers it. byop_compaction_auto: ByopCompactionAuto { type: bool, @@ -1857,7 +1857,7 @@ define_settings_group!(AISettings, settings: [ description: "Enable BYOP automatic conversation compaction on context overflow.", } - // Zap BYOP local conversation compaction — 1:1 aligned with opencode `Config.compaction.prune`. + // Zaplex BYOP local conversation compaction — 1:1 aligned with opencode `Config.compaction.prune`. // When true, old tool output is cleared before each LLM request (replaced with placeholders). byop_compaction_prune: ByopCompactionPrune { type: bool, @@ -1869,7 +1869,7 @@ define_settings_group!(AISettings, settings: [ description: "Auto-prune older tool outputs to free BYOP context.", } - // Zap BYOP local conversation compaction — 1:1 aligned with opencode `Config.compaction.tail_turns` (default 2). + // Zaplex BYOP local conversation compaction — 1:1 aligned with opencode `Config.compaction.tail_turns` (default 2). // Keep the most recent N user turns as the tail; earlier ones go into the head for the summarization LLM. 0 disables compaction. byop_compaction_tail_turns: ByopCompactionTailTurns { type: u32, @@ -1881,7 +1881,7 @@ define_settings_group!(AISettings, settings: [ description: "Number of recent user turns to keep verbatim during compaction.", } - // Zap BYOP local conversation compaction — 1:1 aligned with `Config.compaction.preserve_recent_tokens`. + // Zaplex BYOP local conversation compaction — 1:1 aligned with `Config.compaction.preserve_recent_tokens`. // 0 = computed automatically by formula (min(MAX=8000, max(MIN=2000, usable * 0.25))); > 0 forces an override. byop_compaction_preserve_recent_tokens: ByopCompactionPreserveRecentTokens { type: u32, @@ -1893,7 +1893,7 @@ define_settings_group!(AISettings, settings: [ description: "Override the recent-tokens preservation budget (0 = auto).", } - // Zap BYOP local conversation compaction — 1:1 aligned with `Config.compaction.reserved`. + // Zaplex BYOP local conversation compaction — 1:1 aligned with `Config.compaction.reserved`. // For the overflow check, usable = input_limit - reserved. 0 = computed automatically as min(20_000, max_output). byop_compaction_reserved: ByopCompactionReserved { type: u32, @@ -1905,7 +1905,7 @@ define_settings_group!(AISettings, settings: [ description: "Reserved buffer tokens for compaction overflow check (0 = auto).", } - // Zap BYOP local conversation compaction — dedicated summarization model (optional). + // Zaplex BYOP local conversation compaction — dedicated summarization model (optional). // When set: summarization LLM calls use this provider+model instead of the current conversation model. // Leaving both fields empty = use the conversation's current model. byop_compaction_model_provider_id: ByopCompactionModelProviderId { @@ -1928,7 +1928,7 @@ define_settings_group!(AISettings, settings: [ description: "Optional dedicated model id for compaction LLM calls.", } - // Zap BYOP model + thinking-depth persistence (written immediately after a picker switch, carried over to new tabs/restarts). + // Zaplex BYOP model + thinking-depth persistence (written immediately after a picker switch, carried over to new tabs/restarts). // The model uses the LLMId string form; empty string = no last_used, falling back to the profile default. byop_last_used_model_id: ByopLastUsedModelId { type: String, @@ -1940,7 +1940,7 @@ define_settings_group!(AISettings, settings: [ description: "Last selected BYOP model id (picker hydrates new tabs/sessions from this).", } - // Zap BYOP per-(api_type, model) thinking-depth memory. + // Zaplex BYOP per-(api_type, model) thinking-depth memory. // key = `:`, value = ReasoningEffortSetting. Written on picker switch. byop_last_used_reasoning: ByopLastUsedReasoning { type: BYOPLastUsedReasoningMap, @@ -1996,7 +1996,7 @@ impl AISettings { } pub fn is_any_ai_enabled(&self, _app: &AppContext) -> bool { - // Zap no longer allows disabling the Zap agent via settings. A persisted + // Zaplex no longer allows disabling the Zaplex agent via settings. A persisted // `agents.warp_agent.is_any_ai_enabled = false` in old config files is ignored. true } diff --git a/app/src/settings/app_icon.rs b/app/src/settings/app_icon.rs index e4fd1b7d34..f8eccf0972 100644 --- a/app/src/settings/app_icon.rs +++ b/app/src/settings/app_icon.rs @@ -40,7 +40,7 @@ pub enum AppIcon { Classic3, #[schemars(description = "Comets")] Comets, - /// Cow icon, for Code on Zap launch. + /// Cow icon, for Code on Zaplex launch. #[schemars(description = "Cow")] Cow, #[schemars(description = "Glass Sky")] @@ -64,7 +64,7 @@ pub enum AppIcon { #[schemars(description = "Sticker")] Sticker, /// Previous default icon with solid blue background. - #[schemars(description = "Zap 1")] + #[schemars(description = "Zaplex 1")] WarpOne, } @@ -87,7 +87,7 @@ impl std::fmt::Display for AppIcon { AppIcon::Original => "Original", AppIcon::Starburst => "Starburst", AppIcon::Sticker => "Sticker", - AppIcon::WarpOne => "Zap 1", + AppIcon::WarpOne => "Zaplex 1", }; write!(f, "{value}") } diff --git a/app/src/settings/autoupdate.rs b/app/src/settings/autoupdate.rs index 4d120e1ced..c96e16ea36 100644 --- a/app/src/settings/autoupdate.rs +++ b/app/src/settings/autoupdate.rs @@ -9,6 +9,6 @@ define_settings_group!(AutoupdateSettings, settings: [ private: false, storage_key: "AutomaticUpdatesEnabled", toml_path: "updates.automatic_updates_enabled", - description: "Whether Zap automatically checks for and downloads updates in the background.", + description: "Whether Zaplex automatically checks for and downloads updates in the background.", }, ]); diff --git a/app/src/settings/code.rs b/app/src/settings/code.rs index ad451023fe..af1ff72a6f 100644 --- a/app/src/settings/code.rs +++ b/app/src/settings/code.rs @@ -10,7 +10,7 @@ define_settings_group!(CodeSettings, settings: [ sync_to_cloud: SyncToCloud::Never, private: false, toml_path: "code.editor.use_warp_as_default_editor", - description: "Whether Zap is used as the default code editor.", + description: "Whether Zaplex is used as the default code editor.", } // Whether or not the user has manually dismissed the code toolbelt new feature popup. diff --git a/app/src/settings/debug.rs b/app/src/settings/debug.rs index 511f94f73a..34da15eb01 100644 --- a/app/src/settings/debug.rs +++ b/app/src/settings/debug.rs @@ -2,7 +2,7 @@ use settings::{macros::define_settings_group, Setting, SupportedPlatforms, SyncT // Debug mode settings. // -// If "shell debug mode" is enabled, the `WARP_SHELL_DEBUG_MODE` environment variable is +// If "shell debug mode" is enabled, the `ZAPLEX_SHELL_DEBUG_MODE` environment variable is // set in subsequently spawned terminal sessions. // // If `are_in_band_generators_for_all_sessions_enabled` is `true`, then all new sessions employ diff --git a/app/src/settings/editor.rs b/app/src/settings/editor.rs index 98a61e72c0..c830365df9 100644 --- a/app/src/settings/editor.rs +++ b/app/src/settings/editor.rs @@ -98,8 +98,8 @@ impl TabBehavior { } } -/// This enum is used to enforce options in the dropdown for selecting a separator with the Zap prompt. -/// Note that these separators are added at the END of the Zap prompt (used in the case of same line prompt). +/// This enum is used to enforce options in the dropdown for selecting a separator with the Zaplex prompt. +/// Note that these separators are added at the END of the Zaplex prompt (used in the case of same line prompt). #[derive( Clone, Copy, diff --git a/app/src/settings/import/alacritty_parser.rs b/app/src/settings/import/alacritty_parser.rs index 6d94eeb00f..b6ddf7d338 100644 --- a/app/src/settings/import/alacritty_parser.rs +++ b/app/src/settings/import/alacritty_parser.rs @@ -305,7 +305,7 @@ impl AlacrittyTheme { } impl AlacrittyColors { - /// Returns terminal colors with Zap's default colors substituted in for any + /// Returns terminal colors with Zaplex's default colors substituted in for any /// missing terminal colors. fn into_ansi_with_default(self, default: AnsiColors) -> Result { Ok(AnsiColors { diff --git a/app/src/settings/import/config.rs b/app/src/settings/import/config.rs index cf828ff330..14c0c45401 100644 --- a/app/src/settings/import/config.rs +++ b/app/src/settings/import/config.rs @@ -43,7 +43,7 @@ pub enum ThemeError { #[derive(Clone, Error, Debug)] pub enum HotkeyError { - #[error("A hotkey window opens in a way Zap does not support")] + #[error("A hotkey window opens in a way Zaplex does not support")] UnsupportedWindowType, #[error("There are multiple hotkeys configured")] MultipleHotkeys, diff --git a/app/src/settings/import/iterm_parser.rs b/app/src/settings/import/iterm_parser.rs index 4bd2d9d682..323cf0484a 100644 --- a/app/src/settings/import/iterm_parser.rs +++ b/app/src/settings/import/iterm_parser.rs @@ -37,7 +37,7 @@ extern crate plist; const ITERM_DEFAULT_MONOSPACE_FONT_SIZE: &str = "12"; const ITERM_DEFAULT_MONOSPACE_FONT_FAMILY: &str = "Monaco"; -const WARP_DEFAULT_WORKING_DIRECTORY: ITermWorkingDirectoryStrategy = +const ZAPLEX_DEFAULT_WORKING_DIRECTORY: ITermWorkingDirectoryStrategy = ITermWorkingDirectoryStrategy::Simple(ITermWorkingDirectory::ReuseLast); const PIN_TOP: i64 = 2; @@ -47,7 +47,7 @@ const PIN_RIGHT: i64 = 7; bitflags! { /// Bit flags for modifier keys. Bit 17 = shift, bit 18 = ctrl, bit 19 = option, - /// bit 20 = cmd, bit 21 = numpad (which Zap does not store as a modifier). + /// bit 20 = cmd, bit 21 = numpad (which Zaplex does not store as a modifier). #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Flags: u32 { const CTRL = 1 << 18; @@ -299,7 +299,7 @@ impl TryFrom for Keystroke { alt: modifier_flags.contains(Flags::ALT), shift: modifier_flags.contains(Flags::SHIFT), cmd: modifier_flags.contains(Flags::CMD), - // Neither Zap nor iTerm supports Meta in global hotkeys. + // Neither Zaplex nor iTerm supports Meta in global hotkeys. meta: false, key, }) @@ -311,7 +311,7 @@ pub struct ITermGlobalHotkeyWindow { keystroke: ITermKeystroke, autohide: bool, /// Which screen the hotkey window should open on. -1 = any screen, - /// -2 = screen with cursor (not supported in Zap), and >= 0 is the index of the screen. + /// -2 = screen with cursor (not supported in Zaplex), and >= 0 is the index of the screen. screen: i64, /// How the quake window displays. 2 is pin to top, 5 is bottom, 6 is left, and 7 is right. screen_type: i64, @@ -677,8 +677,8 @@ impl ParseableConfig for ITermProfile { } fn parse(mut self, fonts: &[FontInfo]) -> Config { - // iTerm stores its fonts with internal names and supports styles as default terminal text, whereas Zap changes fonts based on display name. - // Only import a font if there is only one font whose iTerm name starts with the display name of a font Zap supports. + // iTerm stores its fonts with internal names and supports styles as default terminal text, whereas Zaplex changes fonts based on display name. + // Only import a font if there is only one font whose iTerm name starts with the display name of a font Zaplex supports. let translated_font_name = fonts .iter() .find(|font_info| { @@ -715,7 +715,7 @@ impl ParseableConfig for ITermProfile { }; let mouse_and_scroll_reporting = match (self.mouse_reporting, self.scroll_reporting) { - // Since this is the Zap default, return None. + // Since this is the Zaplex default, return None. (true, true) => None, (mouse_reporting, scroll_reporting) => Some(MouseAndScrollReporting { mouse_reporting, @@ -829,12 +829,12 @@ impl ParseableConfig for ITermProfile { } if self.working_directory == default_profile.working_directory - || self.working_directory == Some(WARP_DEFAULT_WORKING_DIRECTORY) + || self.working_directory == Some(ZAPLEX_DEFAULT_WORKING_DIRECTORY) { self.working_directory = None; } - // Zap's default is not to open windows with a custom size, + // Zaplex's default is not to open windows with a custom size, // so there is nothing to check against. if self.rows == default_profile.rows { self.rows = None; @@ -842,7 +842,7 @@ impl ParseableConfig for ITermProfile { if self.columns == default_profile.columns { self.columns = None; } - // iTerm's presets are the same as Zap's + // iTerm's presets are the same as Zaplex's if self.transparency == default_profile.transparency { self.transparency = None; } diff --git a/app/src/settings/import/view.rs b/app/src/settings/import/view.rs index ccb60a117f..16981d639b 100644 --- a/app/src/settings/import/view.rs +++ b/app/src/settings/import/view.rs @@ -1080,7 +1080,7 @@ impl TypedActionView for SettingsImportView { // Handle should_import within the model. - // set_preferences should not fail because it is writing directly to Zap's preferences. + // set_preferences should not fail because it is writing directly to Zaplex's preferences. self.set_preferences(ctx, terminal_type_and_profile); // write_theme can fail because we write themes in a separate directory. diff --git a/app/src/settings/init.rs b/app/src/settings/init.rs index 3622882e1b..ec13513034 100644 --- a/app/src/settings/init.rs +++ b/app/src/settings/init.rs @@ -19,7 +19,7 @@ use crate::{ session_settings::{SessionSettings, SessionSettingsChangedEvent}, settings::TerminalSettings, shared_session::settings::SharedSessionSettings, - warpify::settings::WarpifySettings, + zaplexify::settings::ZaplexifySettings, BlockListSettings, }, undo_close::UndoCloseSettings, @@ -73,7 +73,7 @@ pub fn register_all_settings(ctx: &mut AppContext) { GPUSettings::register(ctx); GeneralSettings::register(ctx); AISettings::register_and_subscribe_to_events(ctx); - // Zap Wave 7-3: `AmbientAgentSettings` physically removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `AmbientAgentSettings` physically removed with ambient-agent UI subsystem. ScrollSettings::register(ctx); SelectionSettings::register(ctx); InputModeSettings::register(ctx); @@ -89,10 +89,11 @@ pub fn register_all_settings(ctx: &mut AppContext) { LanguageSettings::register(ctx); AppEditorSettings::register(ctx); InputSettings::register(ctx); - WarpifySettings::register(ctx); + ZaplexifySettings::register(ctx); AltScreenReporting::register(ctx); UndoCloseSettings::register(ctx); SshSettings::register(ctx); + crate::cockpit::CockpitSettings::register(ctx); VimBannerSettings::register(ctx); SharedSessionSettings::register(ctx); WarpDriveSettings::register(ctx); diff --git a/app/src/settings/initializer.rs b/app/src/settings/initializer.rs index 27fdf5d9e9..99a7a8d379 100644 --- a/app/src/settings/initializer.rs +++ b/app/src/settings/initializer.rs @@ -8,9 +8,8 @@ use crate::{ auth::AuthState, report_if_error, settings::input::InputBoxType, - settings::{InputSettings, PrivacySettings, ThemeSettings}, + settings::{InputSettings, PrivacySettings}, terminal::session_settings::SessionSettings, - themes::theme::ThemeKind, }; pub struct SettingsInitializer; @@ -45,15 +44,6 @@ impl SettingsInitializer { settings.disable_default_regex_trigger(ctx); }); - if FeatureFlag::DefaultAdeberryTheme.is_enabled() { - log::debug!("Setting default theme to Adeberry for new user"); - ThemeSettings::handle(ctx).update(ctx, |settings, ctx| { - if *settings.theme_kind.value() == ThemeKind::Phenomenon { - report_if_error!(settings.theme_kind.set_value(ThemeKind::Adeberry, ctx)); - } - }); - } - if cfg!(windows) { log::debug!("Setting default font size to 16px (12pt) for a new Windows user"); FontSettings::handle(ctx).update(ctx, |settings, ctx| { @@ -67,7 +57,7 @@ impl SettingsInitializer { if !settings.input_box_type.is_value_explicitly_set() && *settings.input_box_type.value() == InputBoxType::Classic { - log::debug!("Setting default input type to Zap prompt for new user"); + log::debug!("Setting default input type to Zaplex prompt for new user"); report_if_error!(settings .input_box_type .set_value(InputBoxType::Universal, ctx)); diff --git a/app/src/settings/input.rs b/app/src/settings/input.rs index d005b6418c..2077034f5a 100644 --- a/app/src/settings/input.rs +++ b/app/src/settings/input.rs @@ -156,7 +156,7 @@ define_settings_group!(InputSettings, // Control whether shortcut hints are shown in Agent view zero-state: // 1) "ctrl+shift+enter start new conversation / /model switch model / esc return to terminal" three lines in zero_state_block; // 2) "? view help / / view commands / open conversation / enter code review" four items at Agent message bar bottom. - // After closing, user can re-enable in "Settings → Zap Agent → AI Input". + // After closing, user can re-enable in "Settings → Zaplex Agent → AI Input". show_agent_zero_state_hints: ShowAgentZeroStateHints { type: bool, default: true, @@ -213,7 +213,7 @@ impl InputSettings { }; // PS1 input is only valid when honor_ps1 is active. If the user has PS1 selected - // but the shell has not signalled PS1 support, fall back to Zap input. + // but the shell has not signalled PS1 support, fall back to Zaplex input. let is_ps1_enabled = *SessionSettings::as_ref(app).honor_ps1 && computed_input_type_value == InputBoxType::Classic; if is_ps1_enabled { diff --git a/app/src/settings/language.rs b/app/src/settings/language.rs index a0d760e8e4..009bcb7364 100644 --- a/app/src/settings/language.rs +++ b/app/src/settings/language.rs @@ -6,7 +6,7 @@ //! 3. Add a case for `Display` + `to_locale_str` //! //! Language switching takes full effect after restart (already-rendered UI text won't auto-reflow; views need rebuilding). -//! Settings page dropdown should include a hint: "takes full effect after restarting Zap". +//! Settings page dropdown should include a hint: "takes full effect after restarting Zaplex". use enum_iterator::Sequence; use serde::{Deserialize, Serialize}; @@ -27,7 +27,7 @@ use warp_core::settings::{macros::define_settings_group, SupportedPlatforms, Syn settings_value::SettingsValue, )] #[schemars( - description = "The language used in Zap's user interface.", + description = "The language used in Zaplex's user interface.", rename_all = "snake_case" )] pub enum Language { @@ -68,6 +68,6 @@ define_settings_group!(LanguageSettings, settings: [ private: false, storage_key: "Language", toml_path: "appearance.language", - description: "The language used in Zap's user interface. Falls back to English when the chosen language is not fully translated.", + description: "The language used in Zaplex's user interface. Falls back to English when the chosen language is not fully translated.", }, ]); diff --git a/app/src/settings/onboarding.rs b/app/src/settings/onboarding.rs index 350d0402d7..e230577d1f 100644 --- a/app/src/settings/onboarding.rs +++ b/app/src/settings/onboarding.rs @@ -30,7 +30,7 @@ pub fn apply_onboarding_settings(selected_settings: &SelectedSettings, app: &mut cli_agent_toolbar_enabled, show_agent_notifications, } => { - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { if let Some(ui) = ui_customization { apply_ui_customization_settings(ui, false, app); } @@ -55,7 +55,7 @@ fn apply_ui_customization_settings( app: &mut AppContext, ) { // Customize UI slide should only exist with this flag enabled. - if !FeatureFlag::ZapNewSettingsModes.is_enabled() { + if !FeatureFlag::ZaplexNewSettingsModes.is_enabled() { return; } TabSettings::handle(app).update(app, |settings, ctx| { diff --git a/app/src/settings/privacy.rs b/app/src/settings/privacy.rs index 038b021d18..5ab4cff3be 100644 --- a/app/src/settings/privacy.rs +++ b/app/src/settings/privacy.rs @@ -14,9 +14,9 @@ use crate::auth::AuthStateProvider; use crate::auth::SyncedUserSettings; use crate::cloud_object::model::persistence::ObjectStoreModel; use crate::report_error; -// Zap Wave 3-1: `AuthClient` trait + `MockAuthClient` physically deleted with server_api/auth.rs; +// Zaplex Wave 3-1: `AuthClient` trait + `MockAuthClient` physically deleted with server_api/auth.rs; // `SyncedUserSettings` moved to `crate::auth`. -// Zap Wave 3-1: `ServerApiProvider` no longer used in this file — +// Zaplex Wave 3-1: `ServerApiProvider` no longer used in this file — // all call sites of `auth_client = ServerApiProvider::as_ref(ctx).get_auth_client()` // physically deleted along with AuthClient trait. use crate::terminal::safe_mode_settings::SafeModeSettings; @@ -28,7 +28,7 @@ use settings::{ use serde::{Deserialize, Serialize}; -// Zap (localization, Phase 5): `PreferencesSyncer` physically deleted. +// Zaplex (localization, Phase 5): `PreferencesSyncer` physically deleted. use crate::workspaces::workspace::EnterpriseSecretRegex; pub trait RegexDisplayInfo { @@ -94,7 +94,7 @@ impl PartialEq for CustomSecretRegex { impl settings_value::SettingsValue for CustomSecretRegex {} // openWarp closed-source telemetry stripping: three privacy toggles default true → false. -// Original Zap defaulted on for commercial "opt-out" mode; Zap has physically cut telemetry, +// Original Zaplex defaulted on for commercial "opt-out" mode; Zaplex has physically cut telemetry, // crash reporting, and cloud conversation storage. Default-on toggles only showed ON to new users // but never actually sent data, creating cognitive split. Changed to default OFF. define_settings_group!(WarpDrivePrivacySettings, settings: [ @@ -142,9 +142,9 @@ maybe_define_setting!(HasInitializedDefaultSecretRegexes, group: PrivacySettings /// reporting and/or telemetry). pub struct PrivacySettings { auth_state: Arc, - // Zap Wave 3-1: `auth_client: Arc` field physically deleted with AuthClient trait. + // Zaplex Wave 3-1: `auth_client: Arc` field physically deleted with AuthClient trait. // Originally used to sync telemetry/crash reporting settings to server on change; - // Zap no longer syncs any server settings. + // Zaplex no longer syncs any server settings. pub is_telemetry_enabled: bool, pub is_crash_reporting_enabled: bool, pub has_initialized_default_secret_regexes: HasInitializedDefaultSecretRegexes, @@ -364,8 +364,8 @@ impl PrivacySettings { /// Fetch the user's privacy settings from the server if any or update the server settings. pub fn fetch_or_update_settings(&self, _ctx: &mut ModelContext) { - // Zap Wave 3-1: originally called `auth_client.get_user_settings().await`, - // physically deleted with AuthClient trait. After Zap localization, privacy + // Zaplex Wave 3-1: originally called `auth_client.get_user_settings().await`, + // physically deleted with AuthClient trait. After Zaplex localization, privacy // settings saved locally only; entry is no-op. } @@ -480,8 +480,8 @@ impl PrivacySettings { }); if self.auth_state.is_logged_in() { - // Zap Wave 3-1: originally called `auth_client.set_is_crash_reporting_enabled(new_value)`, - // physically deleted with AuthClient. Zap locally only updates local state. + // Zaplex Wave 3-1: originally called `auth_client.set_is_crash_reporting_enabled(new_value)`, + // physically deleted with AuthClient. Zaplex locally only updates local state. log::debug!( "set_is_crash_reporting_enabled remote sync localized, new_value={new_value}" ); @@ -514,7 +514,7 @@ impl PrivacySettings { }); if self.auth_state.is_logged_in() { - // Zap Wave 3-1: same as above. + // Zaplex Wave 3-1: same as above. log::debug!("set_is_telemetry_enabled remote sync localized, new_value={new_value}"); } ctx.emit(PrivacySettingsChangedEvent::UpdateIsTelemetryEnabled { @@ -651,7 +651,7 @@ impl PrivacySettings { match (cloud_telemetry_value, cloud_crash_reporting_value) { (Some(is_telemetry_enabled), Some(is_crash_reporting_enabled)) => { log::info!( - "Zap Drive privacy preferences are set, using those for telemetry={is_telemetry_enabled}, \ + "Zaplex Drive privacy preferences are set, using those for telemetry={is_telemetry_enabled}, \ crash_reporting={is_crash_reporting_enabled}" ); self.set_is_telemetry_enabled(is_telemetry_enabled, ctx); @@ -659,7 +659,7 @@ impl PrivacySettings { } _ => { log::info!( - "Zap Drive privacy preferences are not set, syncing local PrivacySettings values to \ + "Zaplex Drive privacy preferences are not set, syncing local PrivacySettings values to \ WarpDrivePrivacySettings and cloud. telemetry={}, crash_reporting={}", self.is_telemetry_enabled, self.is_crash_reporting_enabled @@ -672,7 +672,7 @@ impl PrivacySettings { .is_crash_reporting_enabled .set_value(self.is_crash_reporting_enabled, ctx)); }); - // Zap (localization, Phase 5): originally `PreferencesSyncer::maybe_sync_local_prefs_to_cloud` + // Zaplex (localization, Phase 5): originally `PreferencesSyncer::maybe_sync_local_prefs_to_cloud` // synced local privacy settings to cloud; physically deleted with syncer. Local settings only written to sqlite. } } diff --git a/app/src/settings/same_line_prompt_block.rs b/app/src/settings/same_line_prompt_block.rs index 81fd21d949..3a4b4f4f8e 100644 --- a/app/src/settings/same_line_prompt_block.rs +++ b/app/src/settings/same_line_prompt_block.rs @@ -29,7 +29,7 @@ pub enum SLPBlockState { } // This isn't a user-visible setting, but rather a record of a -// Zap action that should be persisted the same way we would a setting. +// Zaplex action that should be persisted the same way we would a setting. // // When a user has been shown the same line prompt onboarding block, // we want to remember that they have already been shown it. diff --git a/app/src/settings/ssh.rs b/app/src/settings/ssh.rs index f12453a238..45e246f022 100644 --- a/app/src/settings/ssh.rs +++ b/app/src/settings/ssh.rs @@ -11,7 +11,7 @@ define_settings_group!(SshSettings, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, storage_key: "EnableSSHWrapper", - toml_path: "warpify.ssh.enable_legacy_ssh_wrapper", + toml_path: "zaplexify.ssh.enable_legacy_ssh_wrapper", description: "Whether the legacy SSH wrapper is enabled for SSH sessions.", }, enable_ssh_auto_discovery: EnableSshAutoDiscovery { @@ -21,8 +21,8 @@ define_settings_group!(SshSettings, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, storage_key: "EnableSSHAutoDiscovery", - toml_path: "warpify.ssh.enable_ssh_auto_discovery", - description: "Whether to auto-discover SSH hosts from ~/.ssh/config.", + toml_path: "zaplexify.ssh.enable_ssh_auto_discovery", + description: "Whether to suggest SSH hosts from ~/.ssh/config when adding a server. Suggestions never enter the saved list automatically; the user imports them explicitly.", }, ] ); diff --git a/app/src/settings_view/about_page.rs b/app/src/settings_view/about_page.rs index 32bbeb30f6..36658fd2b0 100644 --- a/app/src/settings_view/about_page.rs +++ b/app/src/settings_view/about_page.rs @@ -140,7 +140,7 @@ impl SettingsWidget for AboutPageWidget { ) -> Box { let ui_builder = appearance.ui_builder(); - // Always use pure icon logo; brand name presented as standalone "Zap" text, no longer + // Always use pure icon logo; brand name presented as standalone "Zaplex" text, no longer // dependent on svg containing "warp" text. let image_path = "bundled/svg/warp-logo-light.svg"; @@ -189,7 +189,7 @@ impl SettingsWidget for AboutPageWidget { ) .with_child( ui_builder - .span("Zap") + .span("Zaplex") .build() .with_margin_top(12.) .finish(), diff --git a/app/src/settings_view/ai_page.rs b/app/src/settings_view/ai_page.rs index 400f4eedc0..7ad3d60c9d 100644 --- a/app/src/settings_view/ai_page.rs +++ b/app/src/settings_view/ai_page.rs @@ -672,7 +672,7 @@ impl AISettingsPageView { }); // The coding agent footer command editor is always enabled, // independent of the global AI toggle, because it controls - // third-party coding agents rather than Zap's own AI. + // third-party coding agents rather than Zaplex's own AI. Self::update_editor_interaction_state( cli_agent_footer_command_editor.as_ref(ctx).editor().clone(), true, @@ -5053,7 +5053,7 @@ impl AgentsWidget { let subtext = { let subtext_fragments = vec![ FormattedTextFragment::plain_text( - "You haven't added any MCP servers yet. Once you do, you'll be able to control how much autonomy the Zap Agent has when interacting with them. ", + "You haven't added any MCP servers yet. Once you do, you'll be able to control how much autonomy the Zaplex Agent has when interacting with them. ", ), FormattedTextFragment::hyperlink_action( crate::t!("settings-ai-add-server"), @@ -5536,7 +5536,7 @@ impl SettingsWidget for MCPServersWidget { let mcp_description = vec![ FormattedTextFragment::plain_text( - "Add MCP servers to extend the Zap Agent's capabilities. \ + "Add MCP servers to extend the Zaplex Agent's capabilities. \ MCP servers expose data sources or tools to agents through a standardized interface, essentially acting like plugins. ", ), FormattedTextFragment::hyperlink( @@ -5774,7 +5774,7 @@ impl SettingsWidget for AIFactWidget { column.add_child(self.render_rule_suggestions_toggle(view, ai_settings, app)); } - // Decentralized branch: no longer render "Zap Drive as agent context" toggle. + // Decentralized branch: no longer render "Zaplex Drive as agent context" toggle. let _ = self; let _ = view; column.with_child(button).finish() @@ -5808,7 +5808,7 @@ impl VoiceWidget { let voice_input_description_text_fragments = vec![ FormattedTextFragment::plain_text( - "Voice input allows you to control Zap by speaking directly to your terminal (powered by ", + "Voice input allows you to control Zaplex by speaking directly to your terminal (powered by ", ), FormattedTextFragment::hyperlink("Wispr Flow", WISPR_FLOW_URL), FormattedTextFragment::plain_text(")."), @@ -6009,7 +6009,7 @@ impl SettingsWidget for OtherAIWidget { // TODO: OpenConversationLayoutPreference should not depend on local_fs, but it lives under the external editor settings // which does require local_fs. It was a mistake to put it there, but now we keep it there for backward compatibility. #[cfg(feature = "local_fs")] - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { use crate::util::file::external_editor::settings::OpenConversationLayoutPreference; column.add_child(render_dropdown_item( @@ -6084,7 +6084,7 @@ impl SettingsWidget for CLIAgentWidget { // The Coding Agents section is always enabled, independent of the // global AI toggle, because these settings control third-party coding - // agents (Claude Code, Codex, Gemini CLI) rather than Zap's own AI. + // agents (Claude Code, Codex, Gemini CLI) rather than Zaplex's own AI. let cli_agent_footer_toggle = render_ai_setting_toggle::( crate::t!("settings-ai-show-coding-agent-toolbar"), AISettingsPageAction::ToggleCLIAgentToolbar, diff --git a/app/src/settings_view/appearance_page.rs b/app/src/settings_view/appearance_page.rs index 55cef1c380..89042555f8 100644 --- a/app/src/settings_view/appearance_page.rs +++ b/app/src/settings_view/appearance_page.rs @@ -256,9 +256,9 @@ pub fn init_actions_from_parent_view( context.to_owned(), )]); - // Add command palette entry for toggling between Zap and Classic input modes + // Add command palette entry for toggling between Zaplex and Classic input modes app.register_fixed_bindings(vec![FixedBinding::empty( - "Toggle Input Mode (Zap/Classic)".to_string(), + "Toggle Input Mode (Zaplex/Classic)".to_string(), builder(SettingsAction::AppearancePageToggle( AppearancePageAction::ToggleInputMode, )), @@ -282,7 +282,7 @@ pub fn init_actions_from_parent_view( ), ); - if !FeatureFlag::ZapNewSettingsModes.is_enabled() { + if !FeatureFlag::ZaplexNewSettingsModes.is_enabled() { toggle_binding_pairs.push( ToggleSettingActionPair::custom( SettingActionPairDescriptions::new( @@ -534,7 +534,7 @@ pub struct AppearanceSettingsPageView { header_toolbar_inline_editor: ViewHandle, /// The context chip renderers based on the most recently - /// selected Zap prompt configuration. + /// selected Zaplex prompt configuration. context_chips: Vec, /// The information we need to render the PS1 as a grid when we're @@ -1540,7 +1540,7 @@ impl AppearanceSettingsPageView { let tab_settings = TabSettings::as_ref(ctx); let mut tab_settings_widgets: Vec>> = vec![Box::new(TabIndicatorWidget::default())]; - if !FeatureFlag::ZapNewSettingsModes.is_enabled() { + if !FeatureFlag::ZaplexNewSettingsModes.is_enabled() { tab_settings_widgets.push(Box::new(CodeReviewButtonWidget::default())); } if FeatureFlag::FullScreenZenMode.is_enabled() @@ -1755,7 +1755,7 @@ impl AppearanceSettingsPageView { AppIcon::Original => "Original", AppIcon::Starburst => "Starburst", AppIcon::Sticker => "Sticker", - AppIcon::WarpOne => "Zap 1", + AppIcon::WarpOne => "Zaplex 1", } } @@ -3261,7 +3261,7 @@ impl SettingsWidget for LanguageWidget { type View = AppearanceSettingsPageView; fn search_terms(&self) -> &str { - "language locale 语言 中文 english 翻译 international i18n" + "language locale english translation international i18n" } fn render( diff --git a/app/src/settings_view/code_page.rs b/app/src/settings_view/code_page.rs index 87dfdda0d9..945baee0e6 100644 --- a/app/src/settings_view/code_page.rs +++ b/app/src/settings_view/code_page.rs @@ -1,4 +1,4 @@ -//! Code settings page: after Zap's full LSP stack + persisted workspace history sunset, +//! Code settings page: after Zaplex's full LSP stack + persisted workspace history sunset, //! this page only retains local switches related to "editor and code review". //! //! Historically this also hosted LSP management subpage + codebase indexing, both now sunset; @@ -61,7 +61,7 @@ impl CodeSettingsPageView { fn build_page( ctx: &mut ViewContext, ) -> (PageType, Option>) { - let (widgets, external_editor_view) = if FeatureFlag::ZapNewSettingsModes.is_enabled() + let (widgets, external_editor_view) = if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let editor_view = ctx.add_typed_action_view(ExternalEditorView::new); let widgets: Vec>> = vec![ @@ -90,7 +90,7 @@ impl CodeSettingsPageView { _ctx: &mut ViewContext, ) -> (PageType, Option>) { let widgets: Vec>> = - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { vec![ Box::new(AutoOpenCodeReviewPaneCodeWidget::default()), Box::new(CodeReviewPanelToggleWidget::default()), @@ -274,7 +274,7 @@ impl SettingsPageMeta for CodeSettingsPageView { } fn should_render(&self, _ctx: &AppContext) -> bool { - FeatureFlag::ZapNewSettingsModes.is_enabled() + FeatureFlag::ZaplexNewSettingsModes.is_enabled() } fn on_page_selected(&mut self, _: bool, _ctx: &mut ViewContext) {} diff --git a/app/src/settings_view/features/external_editor.rs b/app/src/settings_view/features/external_editor.rs index 18bb24bb06..3cffb22fe0 100644 --- a/app/src/settings_view/features/external_editor.rs +++ b/app/src/settings_view/features/external_editor.rs @@ -155,7 +155,7 @@ impl ExternalEditorView { let mut items = vec![default_app]; - items.push(DropdownItem::new("Zap", make_action(EditorChoice::Zap))); + items.push(DropdownItem::new("Zaplex", make_action(EditorChoice::Zaplex))); if FeatureFlag::AllowOpeningFileLinksUsingEditorEnv.is_enabled() { items.push(DropdownItem::new( "$EDITOR", @@ -177,7 +177,7 @@ impl ExternalEditorView { EditorChoice::ExternalEditor(editor) => { dropdown.set_selected_by_name(format!("{editor}"), ctx) } - EditorChoice::Zap => dropdown.set_selected_by_name("Zap", ctx), + EditorChoice::Zaplex => dropdown.set_selected_by_name("Zaplex", ctx), EditorChoice::EnvEditor => dropdown.set_selected_by_name("$EDITOR", ctx), EditorChoice::SystemDefault => dropdown.set_selected_by_name(default_option_text, ctx), }; diff --git a/app/src/settings_view/features_page.rs b/app/src/settings_view/features_page.rs index f06af62224..7ee4a63e28 100644 --- a/app/src/settings_view/features_page.rs +++ b/app/src/settings_view/features_page.rs @@ -627,7 +627,7 @@ pub fn init_actions_from_parent_view( builder(SettingsAction::FeaturesPageToggle( FeaturesPageAction::MakeWarpDefaultTerminal, )), - context.to_owned() & !id!(flags::WARP_IS_DEFAULT_TERMINAL), + context.to_owned() & !id!(flags::ZAPLEX_IS_DEFAULT_TERMINAL), )]); } } @@ -710,7 +710,7 @@ pub enum FeaturesPageAction { ToggleShowAutosuggestionIgnoreButton, ToggleAtContextMenuInTerminalMode, ToggleSlashCommandsInTerminalMode, - // Zap: `ToggleOutlineCodebaseSymbolsForAtContextMenu` deleted when outline / RAG went offline. + // Zaplex: `ToggleOutlineCodebaseSymbolsForAtContextMenu` deleted when outline / RAG went offline. ToggleAutoOpenCodeReviewPane, ToggleShowTerminalInputMessageLine, ToggleAgentInAppNotifications, @@ -1188,7 +1188,7 @@ impl FeaturesPageAction { .value(), ), }, - // Zap: ToggleOutlineCodebaseSymbolsForAtContextMenu went offline, + // Zaplex: ToggleOutlineCodebaseSymbolsForAtContextMenu went offline, // telemetry branch deleted together. Self::MakeWarpDefaultTerminal => TelemetryEvent::FeaturesPageAction { action: "MakeWarpDefaultTerminal".to_string(), @@ -1923,7 +1923,7 @@ impl TypedActionView for FeaturesPageView { .toggle_and_save_value(ctx)); }); } - // Zap: `ToggleOutlineCodebaseSymbolsForAtContextMenu` action deleted when outline went offline. + // Zaplex: `ToggleOutlineCodebaseSymbolsForAtContextMenu` action deleted when outline went offline. ToggleAutoOpenCodeReviewPane => { GeneralSettings::handle(ctx).update(ctx, |settings, ctx| { report_if_error!(settings @@ -1986,7 +1986,7 @@ impl FeaturesPageView { ctx.subscribe_to_model(&SelectionSettings::handle(ctx), |_, _, _, ctx| ctx.notify()); - // TODO(CORE-3029): Remove when we launch the new SSH Warpification. + // TODO(CORE-3029): Remove when we launch the new SSH Zaplexification. ctx.subscribe_to_model(&SshSettings::handle(ctx), |_, _, _, ctx| ctx.notify()); ctx.subscribe_to_model(&AltScreenReporting::handle(ctx), |_, _, _, ctx| { ctx.notify() @@ -2506,7 +2506,7 @@ impl FeaturesPageView { #[cfg(feature = "local_fs")] { - if !FeatureFlag::ZapNewSettingsModes.is_enabled() { + if !FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let external_editor_settings = crate::util::file::external_editor::EditorSettings::as_ref(ctx); if external_editor_settings @@ -2541,7 +2541,7 @@ impl FeaturesPageView { } if FeatureFlag::AutoOpenCodeReviewPane.is_enabled() - && !FeatureFlag::ZapNewSettingsModes.is_enabled() + && !FeatureFlag::ZaplexNewSettingsModes.is_enabled() { general_widgets.push(Box::new(AutoOpenCodeReviewPaneWidget::default())); } @@ -4382,7 +4382,7 @@ impl SettingsWidget for ConversationPersistenceWidget { type View = FeaturesPageView; fn search_terms(&self) -> &str { - "persist conversations agent history database save 历史 对话 保存" + "persist conversations agent history database save" } fn render( diff --git a/app/src/settings_view/mcp_servers/list_page.rs b/app/src/settings_view/mcp_servers/list_page.rs index f6b3269ed0..cd9face496 100644 --- a/app/src/settings_view/mcp_servers/list_page.rs +++ b/app/src/settings_view/mcp_servers/list_page.rs @@ -590,7 +590,7 @@ impl MCPServersListPageView { ctx.emit(MCPServersListPageViewEvent::Edit(*item_id)); } ServerCardEvent::Share(item_id) => { - log::debug!("Zap: MCP sharing is disabled for {item_id:?}"); + log::debug!("Zaplex: MCP sharing is disabled for {item_id:?}"); } ServerCardEvent::ViewLogs(item_id) => match item_id { ServerCardItemId::TemplatableMCP(_) => { @@ -1478,8 +1478,8 @@ impl MCPServersListPageView { } } - // If the path is the Zap data directory (e.g. ~/.warp or ~/.warp_dev), set the text to - // "global". The Zap provider stores its data directory as the root path rather than the + // If the path is the Zaplex data directory (e.g. ~/.warp or ~/.warp_dev), set the text to + // "global". The Zaplex provider stores its data directory as the root path rather than the // home directory, unlike other providers that store the home directory directly. if root_path == &crate::warp_managed_paths_watcher::warp_data_dir() { return Some(crate::t!("settings-mcp-list-chip-global")); diff --git a/app/src/settings_view/mcp_servers_page.rs b/app/src/settings_view/mcp_servers_page.rs index 7bf6d2c994..e2797a9fcd 100644 --- a/app/src/settings_view/mcp_servers_page.rs +++ b/app/src/settings_view/mcp_servers_page.rs @@ -39,7 +39,7 @@ use crate::{ /// See `specs/GH686/product.md`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum InstallOrigin { - /// Triggered by a user gesture inside Zap (gallery card click, + /// Triggered by a user gesture inside Zaplex (gallery card click, /// reinstall button, programmatic in-app flows, etc.). InApp, /// Triggered by a `warp://settings/mcp?autoinstall=...` deeplink; must be diff --git a/app/src/settings_view/mod.rs b/app/src/settings_view/mod.rs index cb5de5ae71..da8bb79265 100644 --- a/app/src/settings_view/mod.rs +++ b/app/src/settings_view/mod.rs @@ -46,7 +46,7 @@ use warp_core::{ settings::ToggleableSetting as _, ui::theme::color::internal_colors, }; use warp_editor::editor::NavigationKey; -use warpify_page::{WarpifyPageAction, WarpifyPageView}; +use zaplexify_page::{ZaplexifyPageAction, ZaplexifyPageView}; use warpui::Element; use warpui::{ elements::{ @@ -79,17 +79,17 @@ pub mod mcp_servers_page; mod nav; mod network_page; pub mod pane_manager; -// Zap Wave 3-1: `platform` / `platform_page` removed together with `OzCloudAPIKeys` settings entry + -// Zap Inc cloud API key management UI. -// Zap Wave 6-8: `referrals_page` / `show_blocks_view` removed with `ReferralsClient` / +// Zaplex Wave 3-1: `platform` / `platform_page` removed together with `OzCloudAPIKeys` settings entry + +// Zaplex Inc cloud API key management UI. +// Zaplex Wave 6-8: `referrals_page` / `show_blocks_view` removed with `ReferralsClient` / // `BlockClient` trait — both pages are stub Err / empty list, no local value. mod settings_file_footer; pub(crate) mod settings_page; -// Zap Wave 7-3: `telemetry` removed together with the only variant `EnvironmentsPageOpened` (ambient-agent UI). -// Zap Wave 7-2: `update_environment_form` removed with cloud ambient agent core — +// Zaplex Wave 7-3: `telemetry` removed together with the only variant `EnvironmentsPageOpened` (ambient-agent UI). +// Zaplex Wave 7-2: `update_environment_form` removed with cloud ambient agent core — // `terminal::view::ambient_agent::first_time_setup` and `cloud_environments` sunset together. mod warp_drive_page; -mod warpify_page; +mod zaplexify_page; #[cfg(not(target_family = "wasm"))] pub(crate) use ai_page::cli_agent_settings_widget_id; @@ -142,7 +142,7 @@ pub(super) fn editor_text_colors(appearance: &Appearance) -> TextColors { pub enum SettingsViewEvent { Pane(PaneEvent), StartResize, - // Zap decentralized branch: `CheckForUpdate` / `ZapDrive` variants removed with Account + // Zaplex decentralized branch: `CheckForUpdate` / `ZaplexDrive` variants removed with Account // main settings page sole emitter (`MainSettingsPageView`). ShowToast { message: String, @@ -164,15 +164,15 @@ pub enum SettingsSection { Appearance, Features, Keybindings, - ZapDrive, - Warpify, + ZaplexDrive, + Zaplexify, /// Internal backing-page identifier for AISettingsPageView. Multiple subpages /// (WarpAgent, AgentProfiles, Knowledge, ThirdPartyCLIAgents) share this single /// backing page, so this variant is needed as the key in `settings_pages`. /// External callers should navigate to a specific subpage (e.g. `WarpAgent`) instead. AI, // ── Agents umbrella subpages ── - // Decentralized branch: Settings default page changed to Zap Agent (local AI settings). + // Decentralized branch: Settings default page changed to Zaplex Agent (local AI settings). #[default] WarpAgent, AgentProfiles, @@ -191,8 +191,8 @@ pub enum SettingsSection { EditorAndCodeReview, /// Cloud sync settings page. CloudSync, - // Zap Wave 3-1: `OzCloudAPIKeys` enum variant removed with Zap Inc API key management UI. - // Zap Wave 7-3: `CloudEnvironments` removed with ambient-agent UI subsystem. + // Zaplex Wave 3-1: `OzCloudAPIKeys` enum variant removed with Zaplex Inc API key management UI. + // Zaplex Wave 7-3: `CloudEnvironments` removed with ambient-agent UI subsystem. } use crate::util::bindings::custom_tag_to_keystroke; @@ -206,8 +206,8 @@ impl Display for SettingsSection { SettingsSection::Appearance => crate::t!("settings-section-appearance"), SettingsSection::Features => crate::t!("settings-section-features"), SettingsSection::Keybindings => crate::t!("settings-section-keybindings"), - SettingsSection::ZapDrive => crate::t!("settings-section-warp-drive"), - SettingsSection::Warpify => crate::t!("settings-section-warpify"), + SettingsSection::ZaplexDrive => crate::t!("settings-section-warp-drive"), + SettingsSection::Zaplexify => crate::t!("settings-section-zaplexify"), SettingsSection::AI => crate::t!("settings-section-ai"), SettingsSection::WarpAgent => crate::t!("settings-section-warp-agent"), SettingsSection::AgentProfiles => crate::t!("settings-section-agent-profiles"), @@ -224,8 +224,8 @@ impl Display for SettingsSection { SettingsSection::CloudSync => crate::t!("settings-section-cloud-sync"), // Proxy settings page. i18n key `settings-section-network` is complete in en / zh-CN / ja. SettingsSection::Network => crate::t!("settings-section-network"), - // Zap Wave 3-1: `OzCloudAPIKeys` Display arm removed with variant. - // Zap Wave 7-3: `CloudEnvironments` Display arm removed with variant. + // Zaplex Wave 3-1: `OzCloudAPIKeys` Display arm removed with variant. + // Zaplex Wave 7-3: `CloudEnvironments` Display arm removed with variant. }; write!(f, "{s}") } @@ -260,8 +260,8 @@ impl SettingsSection { s if s.is_ai_subpage() => Self::AI, // EditorAndCodeReview is the only label still pointing at the Code page. Self::EditorAndCodeReview => Self::Code, - // Zap Wave 3-1: `OzCloudAPIKeys` removed with UI. - // Zap Wave 7-3: `CloudEnvironments` umbrella removed with ambient-agent UI. + // Zaplex Wave 3-1: `OzCloudAPIKeys` removed with UI. + // Zaplex Wave 7-3: `CloudEnvironments` umbrella removed with ambient-agent UI. other => *other, } } @@ -291,20 +291,20 @@ impl FromStr for SettingsSection { "Code" => Ok(Self::Code), "Features" => Ok(Self::Features), "Keyboard shortcuts" => Ok(Self::Keybindings), - "Warpify" => Ok(Self::Warpify), - "ZapDrive" | "Zap Drive" => Ok(Self::ZapDrive), + "Zaplexify" => Ok(Self::Zaplexify), + "ZaplexDrive" | "Zaplex Drive" => Ok(Self::ZaplexDrive), // This page was called "Oz" at one point, keep for backward compatibility. - "Oz" | "Zap Agent" => Ok(Self::WarpAgent), + "Oz" | "Zaplex Agent" => Ok(Self::WarpAgent), "Profiles" | "AgentProfiles" => Ok(Self::AgentProfiles), "MCP servers" | "AgentMCPServers" => Ok(Self::AgentMCPServers), "Providers" | "AgentProviders" => Ok(Self::AgentProviders), "Knowledge" => Ok(Self::Knowledge), "Third party CLI agents" | "ThirdPartyCLIAgents" => Ok(Self::ThirdPartyCLIAgents), "Editor and Code Review" | "EditorAndCodeReview" => Ok(Self::EditorAndCodeReview), - "Network" | "网络" => Ok(Self::Network), - "CloudSync" | "Cloud Sync" | "云同步" => Ok(Self::CloudSync), - // Zap Wave 3-1: `OzCloudAPIKeys` removed with UI. - // Zap Wave 7-3: `CloudEnvironments` FromStr arm removed with variant. + "Network" => Ok(Self::Network), + "CloudSync" | "Cloud Sync" => Ok(Self::CloudSync), + // Zaplex Wave 3-1: `OzCloudAPIKeys` removed with UI. + // Zaplex Wave 7-3: `CloudEnvironments` FromStr arm removed with variant. _ => Err(()), } } @@ -344,7 +344,7 @@ pub mod flags { pub const EXTRA_META_KEYS_LEFT_CONTEXT_FLAG: &str = "Extra_Meta_Keys_Left"; pub const SCROLL_REPORTING_CONTEXT_FLAG: &str = "Scroll_Reporting"; pub const FOCUS_REPORTING_CONTEXT_FLAG: &str = "Focus_Reporting"; - #[deprecated = "Use `SSH_TMUX_WRAPPER_CONTEXT_FLAG` for new ssh warpification logic"] + #[deprecated = "Use `SSH_TMUX_WRAPPER_CONTEXT_FLAG` for new ssh zaplexification logic"] pub const LEGACY_SSH_WRAPPER_CONTEXT_FLAG: &str = "SSH_Wrapper"; pub const SSH_TMUX_WRAPPER_CONTEXT_FLAG: &str = "SSH_Tmux_Wrapper"; pub const SSH_AUTO_DISCOVERY_CONTEXT_FLAG: &str = "SSH_Auto_Discovery"; @@ -401,7 +401,7 @@ pub mod flags { pub const IN_BAND_COMMAND_BLOCKS_FLAG: &str = "In_Band_Command_Blocks_Visible"; pub const RECORDING_MODE_FLAG: &str = "Recording_Mode_Enabled"; pub const IN_BAND_GENERATORS_FLAG: &str = "In_Band_Generators_Enabled"; - pub const WARP_SAME_LINE_PROMPT_FLAG: &str = "Warp_Same_Line_Prompt_Enabled"; + pub const ZAPLEX_SAME_LINE_PROMPT_FLAG: &str = "Warp_Same_Line_Prompt_Enabled"; pub const DEBUG_NETWORK_ONLINE_FLAG: &str = "Network_Status_Online"; pub const AI_INPUT_AUTODETECTION_FLAG: &str = "AI_Input_Autodetection"; pub const NLD_IN_TERMINAL_FLAG: &str = "NLD_In_Terminal"; @@ -417,11 +417,11 @@ pub mod flags { pub const IS_BLOCK_AI_SUMMARIES_ENABLED: &str = "IsBlockAISummariesEnabled"; pub const LIGATURE_RENDERING_CONTEXT_FLAG: &str = "Ligature_Rendering_Enabled"; pub const HAS_SETTINGS_TO_IMPORT_FLAG: &str = "HasSettingsToImport"; - /// The user's setting enabled UDI, but we may show a classic input (e.g. ssh/subshell warpification) + /// The user's setting enabled UDI, but we may show a classic input (e.g. ssh/subshell zaplexification) pub const UNIVERSAL_DEVELOPER_INPUT_ENABLED: &str = "UniversalDeveloperInputEnabled"; pub const AGENT_MODE_INPUT: &str = "InputAgentMode"; pub const TERMINAL_MODE_INPUT: &str = "InputTerminalMode"; - pub const WARP_IS_DEFAULT_TERMINAL: &str = "WarpIsDefaultTerminal"; + pub const ZAPLEX_IS_DEFAULT_TERMINAL: &str = "WarpIsDefaultTerminal"; pub const PASSIVE_CODE_DIFF_KEYBINDINGS_ENABLED: &str = "PassiveCodeDiffKeybindingsEnabled"; /// When set, ctrl-enter should accept a prompt suggestion rather than insert a newline. /// This flag is set by the terminal Input when there's a pending passive code diff. @@ -444,7 +444,7 @@ pub mod flags { pub const CLI_AGENT_RICH_INPUT_OPEN: &str = "CLIAgentRichInputOpen"; pub const CLI_AGENT_FOOTER_ENABLED: &str = "CLIAgentFooterEnabled"; pub const CLI_AGENT_RICH_INPUT_CHIP_ENABLED: &str = "CLIAgentRichInputChipEnabled"; - pub const ENABLE_WARP_DRIVE: &str = "EnableWarpDrive"; + pub const ENABLE_ZAPLEX_DRIVE: &str = "EnableWarpDrive"; // Tools panel settings pub const SHOW_CONVERSATION_HISTORY: &str = "ShowConversationHistory"; pub const SHOW_PROJECT_EXPLORER: &str = "ShowProjectExplorer"; @@ -458,7 +458,7 @@ pub fn init_actions_from_parent_view( ) { appearance_page::init_actions_from_parent_view(app, context, builder); features_page::init_actions_from_parent_view(app, context, builder); - warpify_page::init_actions_from_parent_view(app, context, builder); + zaplexify_page::init_actions_from_parent_view(app, context, builder); ai_page::init_actions_from_parent_view(app, context, builder); code_page::init_actions_from_parent_view(app, context, builder); @@ -769,9 +769,9 @@ pub enum SettingsAction { FeaturesPageToggle(FeaturesPageAction), AI(AISettingsPageAction), Code(CodeSettingsPageAction), - ZapDrive(warp_drive_page::WarpDriveSettingsPageAction), + ZaplexDrive(warp_drive_page::WarpDriveSettingsPageAction), CloudSync(cloud_sync_page::CloudSyncPageAction), - WarpifyPageToggle(WarpifyPageAction), + ZaplexifyPageToggle(ZaplexifyPageAction), Tab, Split(Direction), ToggleMaximizePane, @@ -914,15 +914,15 @@ macro_rules! update_page { SettingsPageViewHandle::Appearance(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Features(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Keybindings(handle) => $ctx.update_view(handle, $update), - SettingsPageViewHandle::Warpify(handle) => $ctx.update_view(handle, $update), - // Zap Wave 3-1: `OzCloudAPIKeys` arm removed with variant. - // Zap Wave 6-8: `SharedBlocks` / `Referrals` arm removed with variant. - // Zap Wave 7-3: `CloudEnvironments` arm removed with ambient-agent UI. + SettingsPageViewHandle::Zaplexify(handle) => $ctx.update_view(handle, $update), + // Zaplex Wave 3-1: `OzCloudAPIKeys` arm removed with variant. + // Zaplex Wave 6-8: `SharedBlocks` / `Referrals` arm removed with variant. + // Zaplex Wave 7-3: `CloudEnvironments` arm removed with ambient-agent UI. SettingsPageViewHandle::AI(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::About(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::Code(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::MCPServers(handle) => $ctx.update_view(handle, $update), - SettingsPageViewHandle::ZapDrive(handle) => $ctx.update_view(handle, $update), + SettingsPageViewHandle::ZaplexDrive(handle) => $ctx.update_view(handle, $update), // Issue #72: Global HTTP proxy settings page. SettingsPageViewHandle::Network(handle) => $ctx.update_view(handle, $update), SettingsPageViewHandle::CloudSync(handle) => $ctx.update_view(handle, $update), @@ -984,7 +984,7 @@ impl SettingsView { me.handle_features_page_event(event, ctx); }); - // Zap Wave 6-8: Shared blocks settings page removed with `ShowBlocksView` / `BlockClient`, + // Zaplex Wave 6-8: Shared blocks settings page removed with `ShowBlocksView` / `BlockClient`, // handle / event subscriptions removed together. // About page @@ -998,7 +998,7 @@ impl SettingsView { }); // Environments page - // Zap Wave 7-3: `environments_page_handle` removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `environments_page_handle` removed with ambient-agent UI subsystem. // Keybindings page let keybindings_handle = ctx.add_typed_action_view(KeybindingsView::new); @@ -1010,19 +1010,19 @@ impl SettingsView { me.handle_code_page_event(event, ctx); }); - let warpify_page_handle = ctx.add_typed_action_view(WarpifyPageView::new); - ctx.subscribe_to_view(&warpify_page_handle, |me, _, event, ctx| { - me.handle_warpify_page_event(event, ctx); + let zaplexify_page_handle = ctx.add_typed_action_view(ZaplexifyPageView::new); + ctx.subscribe_to_view(&zaplexify_page_handle, |me, _, event, ctx| { + me.handle_zaplexify_page_event(event, ctx); }); - // Zap Wave 6-8: Referrals settings page removed with `ReferralsPageView` / `ReferralsClient`, + // Zaplex Wave 6-8: Referrals settings page removed with `ReferralsPageView` / `ReferralsClient`, // handle / event subscriptions removed together. - // Zap Drive page + // Zaplex Drive page let warp_drive_page_handle = ctx.add_typed_action_view(warp_drive_page::WarpDriveSettingsPageView::new); - // Zap Wave 3-1: `platform_page_handle` removed together with `platform_page`. + // Zaplex Wave 3-1: `platform_page_handle` removed together with `platform_page`. // MCP Servers page let mcp_servers_page_handle = ctx.add_typed_action_view(MCPServersSettingsPageView::new); @@ -1071,8 +1071,8 @@ impl SettingsView { SettingsPage::new(appearance_page_handle), SettingsPage::new(features_page_handle), SettingsPage::new(keybindings_handle), - // Zap Wave 3-1: `platform_page_handle` removed with UI. - SettingsPage::new(warpify_page_handle), + // Zaplex Wave 3-1: `platform_page_handle` removed with UI. + SettingsPage::new(zaplexify_page_handle), SettingsPage::new(warp_drive_page_handle), ]; @@ -1099,7 +1099,7 @@ impl SettingsView { SettingsNavItem::Page(SettingsSection::Appearance), SettingsNavItem::Page(SettingsSection::Features), SettingsNavItem::Page(SettingsSection::Keybindings), - SettingsNavItem::Page(SettingsSection::Warpify), + SettingsNavItem::Page(SettingsSection::Zaplexify), SettingsNavItem::Page(SettingsSection::CloudSync), SettingsNavItem::Page(SettingsSection::About), ]; @@ -1476,7 +1476,7 @@ impl SettingsView { } } - // Zap Wave 7-3: `handle_environments_page_event` removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `handle_environments_page_event` removed with ambient-agent UI subsystem. fn handle_features_page_event( &mut self, @@ -1491,7 +1491,7 @@ impl SettingsView { } } - fn handle_warpify_page_event( + fn handle_zaplexify_page_event( &mut self, event: &SettingsPageEvent, ctx: &mut ViewContext, @@ -1505,7 +1505,7 @@ impl SettingsView { } } - // Zap Wave 3-1: `handle_platform_page_event` removed with `platform_page::PlatformPageViewEvent`. + // Zaplex Wave 3-1: `handle_platform_page_event` removed with `platform_page::PlatformPageViewEvent`. fn handle_mcp_servers_page_event( &mut self, @@ -1626,7 +1626,7 @@ impl SettingsView { self.clear_search_query(ctx); } self.current_settings_page = section; - // Zap Wave 7-3: `SettingsTelemetryEvent::EnvironmentsPageOpened` removed with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `SettingsTelemetryEvent::EnvironmentsPageOpened` removed with ambient-agent UI subsystem. let _ = previous_section; // When navigating to a subpage, update the backing page's active subpage mode @@ -1689,13 +1689,13 @@ impl SettingsView { SettingsPageViewHandle::Features(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::Appearance(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::About(v) => v.as_ref(app).should_render(app), - // Zap Wave 3-1: `OzCloudAPIKeys` arm removed with variant. - // Zap Wave 6-8: `SharedBlocks` / `Referrals` arm removed with variant. - SettingsPageViewHandle::Warpify(v) => v.as_ref(app).should_render(app), + // Zaplex Wave 3-1: `OzCloudAPIKeys` arm removed with variant. + // Zaplex Wave 6-8: `SharedBlocks` / `Referrals` arm removed with variant. + SettingsPageViewHandle::Zaplexify(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::AI(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::MCPServers(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::Code(v) => v.as_ref(app).should_render(app), - SettingsPageViewHandle::ZapDrive(v) => v.as_ref(app).should_render(app), + SettingsPageViewHandle::ZaplexDrive(v) => v.as_ref(app).should_render(app), // Issue #72: Global HTTP proxy settings page. SettingsPageViewHandle::Network(v) => v.as_ref(app).should_render(app), SettingsPageViewHandle::CloudSync(v) => v.as_ref(app).should_render(app), @@ -1885,7 +1885,7 @@ impl SettingsView { app: &AppContext, ) -> Option> { match page_handle { - // Zap Wave 3-1: `OzCloudAPIKeys` modal arm removed with UI. + // Zaplex Wave 3-1: `OzCloudAPIKeys` modal arm removed with UI. SettingsPageViewHandle::MCPServers(view) => { view.read(app, |view, _| view.get_modal_content(app)) } @@ -2188,7 +2188,7 @@ impl View for SettingsView { ); } - // Zap Wave 7-3:environment setup mode selector / agent-assisted environment + // Zaplex Wave 7-3:environment setup mode selector / agent-assisted environment // Modal overlay rendering removed with ambient-agent UI subsystem. SavePosition::new(stack.finish(), POSITION_ID).finish() @@ -2256,9 +2256,9 @@ impl TypedActionView for SettingsView { } } } - SettingsAction::ZapDrive(warp_drive_action) => { - if let Some(warp_drive_page) = self.settings_page(SettingsSection::ZapDrive) { - if let SettingsPageViewHandle::ZapDrive(view) = &warp_drive_page.view_handle { + SettingsAction::ZaplexDrive(warp_drive_action) => { + if let Some(warp_drive_page) = self.settings_page(SettingsSection::ZaplexDrive) { + if let SettingsPageViewHandle::ZaplexDrive(view) = &warp_drive_page.view_handle { view.update(ctx, |view, ctx| { view.handle_action(warp_drive_action, ctx); }) @@ -2274,11 +2274,11 @@ impl TypedActionView for SettingsView { } } } - SettingsAction::WarpifyPageToggle(warpify_action) => { - if let Some(warpify_page) = self.settings_page(SettingsSection::Warpify) { - if let SettingsPageViewHandle::Warpify(view) = &warpify_page.view_handle { + SettingsAction::ZaplexifyPageToggle(zaplexify_action) => { + if let Some(zaplexify_page) = self.settings_page(SettingsSection::Zaplexify) { + if let SettingsPageViewHandle::Zaplexify(view) = &zaplexify_page.view_handle { view.update(ctx, |view, ctx| { - view.handle_action(warpify_action, ctx); + view.handle_action(zaplexify_action, ctx); }) } } diff --git a/app/src/settings_view/network_page.rs b/app/src/settings_view/network_page.rs index 2304a27695..a9ed188ab6 100644 --- a/app/src/settings_view/network_page.rs +++ b/app/src/settings_view/network_page.rs @@ -583,7 +583,7 @@ impl SettingsWidget for NetworkPageWidget { type View = NetworkPageView; fn search_terms(&self) -> &str { - "network proxy http https 代理 网络 vpn 公司 corporate system custom off no_proxy 测试连接" + "network proxy http https corporate system custom off no_proxy test connection" } fn render( diff --git a/app/src/settings_view/settings_page.rs b/app/src/settings_view/settings_page.rs index 25e59e2841..e91ef07c19 100644 --- a/app/src/settings_view/settings_page.rs +++ b/app/src/settings_view/settings_page.rs @@ -16,7 +16,7 @@ use super::{ mcp_servers_page::MCPServersSettingsPageView, network_page::NetworkPageView, warp_drive_page::WarpDriveSettingsPageView, - warpify_page::WarpifyPageView, + zaplexify_page::ZaplexifyPageView, SettingsSection, }; use crate::{ @@ -100,15 +100,15 @@ pub enum SettingsPageViewHandle { Keybindings(ViewHandle), About(ViewHandle), Code(ViewHandle), - // Zap Wave 3-1: `OzCloudAPIKeys` variant removed together with `platform_page`. - // Cloud API key management UI fully represents Zap Inc cloud account; unrelated to BYOP. - // Zap Wave 6-8: `SharedBlocks` / `Referrals` variant removed together with `ShowBlocksView` / + // Zaplex Wave 3-1: `OzCloudAPIKeys` variant removed together with `platform_page`. + // Cloud API key management UI fully represents Zaplex Inc cloud account; unrelated to BYOP. + // Zaplex Wave 6-8: `SharedBlocks` / `Referrals` variant removed together with `ShowBlocksView` / // `ReferralsPageView` and the corresponding ServerApi client trait. - // Zap Wave 7-3: `CloudEnvironments` variant removed together with ambient-agent UI subsystem. - Warpify(ViewHandle), + // Zaplex Wave 7-3: `CloudEnvironments` variant removed together with ambient-agent UI subsystem. + Zaplexify(ViewHandle), AI(ViewHandle), MCPServers(ViewHandle), - ZapDrive(ViewHandle), + ZaplexDrive(ViewHandle), /// Global HTTP proxy settings page. Network(ViewHandle), /// Cloud sync settings page. @@ -124,13 +124,13 @@ impl SettingsPageViewHandle { Keybindings(view_handle) => ChildView::new(view_handle).finish(), About(view_handle) => ChildView::new(view_handle).finish(), Code(view_handle) => ChildView::new(view_handle).finish(), - // Zap Wave 3-1: `OzCloudAPIKeys` arm removed together with `platform_page`. - // Zap Wave 6-8: `SharedBlocks` / `Referrals` arm removed together with variant. - // Zap Wave 7-3: `CloudEnvironments` arm removed together with ambient-agent UI. - Warpify(view_handle) => ChildView::new(view_handle).finish(), + // Zaplex Wave 3-1: `OzCloudAPIKeys` arm removed together with `platform_page`. + // Zaplex Wave 6-8: `SharedBlocks` / `Referrals` arm removed together with variant. + // Zaplex Wave 7-3: `CloudEnvironments` arm removed together with ambient-agent UI. + Zaplexify(view_handle) => ChildView::new(view_handle).finish(), AI(view_handle) => ChildView::new(view_handle).finish(), MCPServers(view_handle) => ChildView::new(view_handle).finish(), - ZapDrive(view_handle) => ChildView::new(view_handle).finish(), + ZaplexDrive(view_handle) => ChildView::new(view_handle).finish(), Network(view_handle) => ChildView::new(view_handle).finish(), CloudSync(view_handle) => ChildView::new(view_handle).finish(), } @@ -193,7 +193,7 @@ impl SettingsPage { pub enum SettingsPageEvent { FocusModal, Pane(PaneEventWrapper), - // Zap Wave 7-3: `EnvironmentSetupModeSelectorToggled` / + // Zaplex Wave 7-3: `EnvironmentSetupModeSelectorToggled` / // `AgentAssistedEnvironmentModalToggled` removed together with ambient-agent UI subsystem. } diff --git a/app/src/settings_view/warp_drive_page.rs b/app/src/settings_view/warp_drive_page.rs index a50b99627d..bcb2d544f4 100644 --- a/app/src/settings_view/warp_drive_page.rs +++ b/app/src/settings_view/warp_drive_page.rs @@ -68,11 +68,11 @@ impl View for WarpDriveSettingsPageView { impl SettingsPageMeta for WarpDriveSettingsPageView { fn section() -> SettingsSection { - SettingsSection::ZapDrive + SettingsSection::ZaplexDrive } fn should_render(&self, _ctx: &AppContext) -> bool { - FeatureFlag::ZapNewSettingsModes.is_enabled() + FeatureFlag::ZaplexNewSettingsModes.is_enabled() } fn update_filter(&mut self, query: &str, ctx: &mut ViewContext) -> MatchData { @@ -90,7 +90,7 @@ impl SettingsPageMeta for WarpDriveSettingsPageView { impl From> for SettingsPageViewHandle { fn from(view_handle: ViewHandle) -> Self { - SettingsPageViewHandle::ZapDrive(view_handle) + SettingsPageViewHandle::ZaplexDrive(view_handle) } } @@ -116,7 +116,7 @@ impl SettingsWidget for WarpDriveToggleWidget { let settings = WarpDriveSettings::as_ref(app); render_body_item::( - "Zap Drive".into(), + "Zaplex Drive".into(), Some(AdditionalInfo { mouse_state: self.info_icon_mouse_state.clone(), on_click_action: Some(WarpDriveSettingsPageAction::OpenUrl( @@ -137,7 +137,7 @@ impl SettingsWidget for WarpDriveToggleWidget { ctx.dispatch_typed_action(WarpDriveSettingsPageAction::ToggleShowWarpDrive); }) .finish(), - Some("Zap Drive is a local workspace in your terminal where you can save Workflows, Notebooks, Prompts, and Environment Variables on this device.".into()), + Some("Zaplex Drive is a local workspace in your terminal where you can save Workflows, Notebooks, Prompts, and Environment Variables on this device.".into()), ) } } diff --git a/app/src/settings_view/warpify_page.rs b/app/src/settings_view/zaplexify_page.rs similarity index 71% rename from app/src/settings_view/warpify_page.rs rename to app/src/settings_view/zaplexify_page.rs index 680a04b9a3..f13b66e5a3 100644 --- a/app/src/settings_view/warpify_page.rs +++ b/app/src/settings_view/zaplexify_page.rs @@ -20,16 +20,16 @@ use warpui::{ ViewContext, ViewHandle, }; -use crate::terminal::warpify::settings::{ - EnableSshWarpification, SshExtensionInstallMode, SshExtensionInstallModeSetting, - UseSshTmuxWrapper, WarpifySettingsChangedEvent, +use crate::terminal::zaplexify::settings::{ + EnableSshZaplexification, SshExtensionInstallMode, SshExtensionInstallModeSetting, + UseSshTmuxWrapper, ZaplexifySettingsChangedEvent, }; use crate::ui_components::blended_colors; use crate::{ appearance::Appearance, report_if_error, send_telemetry_from_ctx, server::telemetry::TelemetryEvent, - terminal::warpify::settings::WarpifySettings, + terminal::zaplexify::settings::ZaplexifySettings, view_components::{SubmittableTextInput, SubmittableTextInputEvent}, }; @@ -53,14 +53,14 @@ pub fn init_actions_from_parent_view( context: &ContextPredicate, builder: fn(SettingsAction) -> T, ) { - // Add all of the toggle settings from the Warpify Page that you want to show up on the Command Palette here. + // Add all of the toggle settings from the Zaplexify Page that you want to show up on the Command Palette here. let mut toggle_binding_pairs = vec![]; if FeatureFlag::SSHTmuxWrapper.is_enabled() { toggle_binding_pairs.push(ToggleSettingActionPair::new( - &crate::t!("settings-warpify-ssh-tmux-toggle-binding-label"), - builder(SettingsAction::WarpifyPageToggle( - WarpifyPageAction::ToggleTmuxWarpification, + &crate::t!("settings-zaplexify-ssh-tmux-toggle-binding-label"), + builder(SettingsAction::ZaplexifyPageToggle( + ZaplexifyPageAction::ToggleTmuxZaplexification, )), context, flags::SSH_TMUX_WRAPPER_CONTEXT_FLAG, @@ -75,12 +75,12 @@ const ITEM_VERTICAL_SPACING: f32 = 24.; const BUILT_IN_TEXT_INPUT_MARGIN: f32 = 10.; const SPACE_AFTER_TEXT_INPUT: f32 = ITEM_VERTICAL_SPACING - BUILT_IN_TEXT_INPUT_MARGIN; -/// This page lets users configure when they get asked to warpify a session. Some shell commands +/// This page lets users configure when they get asked to zaplexify a session. Some shell commands /// are recognized by default. Users can add new shell commands, or prevent the default ones from /// asking. Users can also enable the SSH wrapper, and add hosts to a denylist. /// This page is essentially the View for the SubshellSettings model, as well as the SshSettings -/// related to warpification. -pub struct WarpifyPageView { +/// related to zaplexification. +pub struct ZaplexifyPageView { page: PageType, /// This needs to mirror the length of SubshellSettings::added_remove_button_states. remove_added_command_button_states: Vec, @@ -92,19 +92,19 @@ pub struct WarpifyPageView { remove_denylisted_ssh_button_states: Vec, add_denylisted_ssh_editor: ViewHandle, - ssh_extension_install_mode_dropdown: ViewHandle>, + ssh_extension_install_mode_dropdown: ViewHandle>, } -impl WarpifyPageView { +impl ZaplexifyPageView { pub fn new(ctx: &mut ViewContext) -> Self { - let warpify_settings_handle = WarpifySettings::handle(ctx); + let zaplexify_settings_handle = ZaplexifySettings::handle(ctx); - ctx.observe(&warpify_settings_handle, Self::update_button_states); - ctx.subscribe_to_model(&warpify_settings_handle, move |me, model, event, ctx| { + ctx.observe(&zaplexify_settings_handle, Self::update_button_states); + ctx.subscribe_to_model(&zaplexify_settings_handle, move |me, model, event, ctx| { me.update_button_states(model, ctx); if matches!( event, - WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } + ZaplexifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } ) { me.update_dropdown(ctx); } @@ -116,7 +116,7 @@ impl WarpifyPageView { let add_added_commands_editor = ctx.add_typed_action_view(|ctx| { let mut input = SubmittableTextInput::new(ctx).validate_on_edit(|regex| Regex::new(regex).is_ok()); - input.set_placeholder_text(crate::t!("settings-warpify-command-placeholder"), ctx); + input.set_placeholder_text(crate::t!("settings-zaplexify-command-placeholder"), ctx); input }); @@ -127,7 +127,7 @@ impl WarpifyPageView { let add_denylisted_commands_editor = ctx.add_typed_action_view(|ctx| { let mut input = SubmittableTextInput::new(ctx); - input.set_placeholder_text(crate::t!("settings-warpify-command-placeholder"), ctx); + input.set_placeholder_text(crate::t!("settings-zaplexify-command-placeholder"), ctx); input }); @@ -138,7 +138,7 @@ impl WarpifyPageView { let add_denylisted_ssh_editor = ctx.add_typed_action_view(|ctx| { let mut input = SubmittableTextInput::new(ctx); - input.set_placeholder_text(crate::t!("settings-warpify-host-placeholder"), ctx); + input.set_placeholder_text(crate::t!("settings-zaplexify-host-placeholder"), ctx); input }); @@ -161,7 +161,7 @@ impl WarpifyPageView { ssh_extension_install_mode_dropdown, }; - instance.update_button_states(warpify_settings_handle, ctx); + instance.update_button_states(zaplexify_settings_handle, ctx); instance } @@ -169,27 +169,27 @@ impl WarpifyPageView { let mut categories = vec![ Category::new("", vec![Box::new(TitleWidget::default())]), Category::new( - Box::leak(crate::t!("settings-warpify-section-subshells").into_boxed_str()), + Box::leak(crate::t!("settings-zaplexify-section-subshells").into_boxed_str()), vec![Box::new(SubshellsWidget::default())], ) .with_subtitle(Box::leak( - crate::t!("settings-warpify-section-subshells-subtitle").into_boxed_str(), + crate::t!("settings-zaplexify-section-subshells-subtitle").into_boxed_str(), )), ]; - let warpify_settings = WarpifySettings::as_ref(ctx); + let zaplexify_settings = ZaplexifySettings::as_ref(ctx); if FeatureFlag::SSHTmuxWrapper.is_enabled() - && warpify_settings - .enable_ssh_warpification + && zaplexify_settings + .enable_ssh_zaplexification .is_supported_on_current_platform() { categories.push( Category::new( - Box::leak(crate::t!("settings-warpify-section-ssh").into_boxed_str()), + Box::leak(crate::t!("settings-zaplexify-section-ssh").into_boxed_str()), vec![Box::new(SSHWidget::default())], ) .with_subtitle(Box::leak( - crate::t!("settings-warpify-section-ssh-subtitle").into_boxed_str(), + crate::t!("settings-zaplexify-section-ssh-subtitle").into_boxed_str(), )), ); } @@ -200,21 +200,21 @@ impl WarpifyPageView { /// its delete button in the View. fn update_button_states( &mut self, - warpify_settings_handle: ModelHandle, + zaplexify_settings_handle: ModelHandle, ctx: &mut ViewContext, ) { - let warpify_settings = warpify_settings_handle.as_ref(ctx); - self.remove_denylisted_command_button_states = warpify_settings + let zaplexify_settings = zaplexify_settings_handle.as_ref(ctx); + self.remove_denylisted_command_button_states = zaplexify_settings .subshell_command_denylist .iter() .map(|_| Default::default()) .collect(); - self.remove_added_command_button_states = warpify_settings + self.remove_added_command_button_states = zaplexify_settings .added_subshell_commands .iter() .map(|_| Default::default()) .collect(); - self.remove_denylisted_ssh_button_states = warpify_settings + self.remove_denylisted_ssh_button_states = zaplexify_settings .ssh_hosts_denylist .iter() .map(|_| Default::default()) @@ -223,16 +223,16 @@ impl WarpifyPageView { } /// Syncs the install-mode dropdown selection with the current - /// `WarpifySettings::ssh_extension_install_mode` value (e.g. after it + /// `ZaplexifySettings::ssh_extension_install_mode` value (e.g. after it /// was changed from the SSH remote server choice view). fn update_dropdown(&mut self, ctx: &mut ViewContext) { - let current_mode = *WarpifySettings::as_ref(ctx) + let current_mode = *ZaplexifySettings::as_ref(ctx) .ssh_extension_install_mode .value(); self.ssh_extension_install_mode_dropdown .update(ctx, |dropdown, ctx| { dropdown.set_selected_by_action( - WarpifyPageAction::SetSshExtensionInstallMode(current_mode), + ZaplexifyPageAction::SetSshExtensionInstallMode(current_mode), ctx, ); }); @@ -246,8 +246,8 @@ impl WarpifyPageView { ) { match event { SubmittableTextInputEvent::Submit(new_command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - warpify_settings.add_subshell_command(new_command, ctx); + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify_settings, ctx| { + zaplexify_settings.add_subshell_command(new_command, ctx); }); send_telemetry_from_ctx!(TelemetryEvent::AddAddedSubshellCommand, ctx); @@ -264,8 +264,8 @@ impl WarpifyPageView { ) { match event { SubmittableTextInputEvent::Submit(new_command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - warpify_settings.denylist_subshell_command(new_command, ctx); + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify_settings, ctx| { + zaplexify_settings.denylist_subshell_command(new_command, ctx); }); send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSubshellCommand, ctx); @@ -282,8 +282,8 @@ impl WarpifyPageView { ) { match event { SubmittableTextInputEvent::Submit(new_command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - warpify_settings.denylist_ssh_host(new_command, ctx); + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify_settings, ctx| { + zaplexify_settings.denylist_ssh_host(new_command, ctx); }); send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSshTmuxWrapperHost, ctx); @@ -294,27 +294,27 @@ impl WarpifyPageView { fn remove_denylisted_command(&self, index: usize, ctx: &mut ViewContext) { send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSubshellCommand, ctx); - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.remove_denylisted_subshell_command(index, ctx) + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify, ctx| { + zaplexify.remove_denylisted_subshell_command(index, ctx) }); } fn remove_added_command(&self, index: usize, ctx: &mut ViewContext) { send_telemetry_from_ctx!(TelemetryEvent::RemoveAddedSubshellCommand, ctx); - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.remove_added_subshell_command(index, ctx) + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify, ctx| { + zaplexify.remove_added_subshell_command(index, ctx) }); } fn remove_denylisted_ssh_host(&self, index: usize, ctx: &mut ViewContext) { send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSshTmuxWrapperHost, ctx); - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.remove_denylisted_ssh_host(index, ctx) + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify, ctx| { + zaplexify.remove_denylisted_ssh_host(index, ctx) }); } } -impl Entity for WarpifyPageView { +impl Entity for ZaplexifyPageView { type Event = SettingsPageEvent; } @@ -331,24 +331,24 @@ fn build_sub_sub_title(title: String, appearance: &Appearance) -> Container { const SSH_EXTENSION_DROPDOWN_WIDTH: f32 = 250.; -impl WarpifyPageView { +impl ZaplexifyPageView { fn create_ssh_extension_install_mode_dropdown( ctx: &mut ViewContext, - ) -> ViewHandle> { - let items: Vec> = SshExtensionInstallMode::iter() + ) -> ViewHandle> { + let items: Vec> = SshExtensionInstallMode::iter() .map(|mode| { DropdownItem::new( mode.display_name(), - WarpifyPageAction::SetSshExtensionInstallMode(mode), + ZaplexifyPageAction::SetSshExtensionInstallMode(mode), ) }) .collect(); - let current_mode = *WarpifySettings::as_ref(ctx) + let current_mode = *ZaplexifySettings::as_ref(ctx) .ssh_extension_install_mode .value(); - let enable_ssh_warpification = *WarpifySettings::as_ref(ctx) - .enable_ssh_warpification + let enable_ssh_zaplexification = *ZaplexifySettings::as_ref(ctx) + .enable_ssh_zaplexification .value(); ctx.add_typed_action_view(move |ctx| { @@ -357,10 +357,10 @@ impl WarpifyPageView { dropdown.set_menu_width(SSH_EXTENSION_DROPDOWN_WIDTH, ctx); dropdown.add_items(items, ctx); dropdown.set_selected_by_action( - WarpifyPageAction::SetSshExtensionInstallMode(current_mode), + ZaplexifyPageAction::SetSshExtensionInstallMode(current_mode), ctx, ); - if !enable_ssh_warpification { + if !enable_ssh_zaplexification { dropdown.set_disabled(ctx); } dropdown @@ -411,9 +411,9 @@ impl WarpifyPageView { } } -impl View for WarpifyPageView { +impl View for ZaplexifyPageView { fn ui_name() -> &'static str { - "WarpifyPageView" + "ZaplexifyPageView" } fn render(&self, app: &AppContext) -> Box { @@ -422,40 +422,40 @@ impl View for WarpifyPageView { } #[derive(Clone, Debug, PartialEq)] -pub enum WarpifyPageAction { +pub enum ZaplexifyPageAction { RemoveAddedCommand(usize), RemoveDenylistedCommand(usize), RemoveDenylistedSshHost(usize), - /// If disabled, auto-Warpification and the SSH Warpification prompt will be disabled. - ToggleTmuxWarpification, - ToggleSshWarpification, + /// If disabled, auto-Zaplexification and the SSH Zaplexification prompt will be disabled. + ToggleTmuxZaplexification, + ToggleSshZaplexification, /// Set the SSH extension installation mode (always ask / always install / always skip). SetSshExtensionInstallMode(SshExtensionInstallMode), OpenUrl(String), } -impl TypedActionView for WarpifyPageView { - type Action = WarpifyPageAction; +impl TypedActionView for ZaplexifyPageView { + type Action = ZaplexifyPageAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { - use WarpifyPageAction::*; + use ZaplexifyPageAction::*; match action { RemoveDenylistedCommand(index) => self.remove_denylisted_command(*index, ctx), RemoveAddedCommand(index) => self.remove_added_command(*index, ctx), - ToggleSshWarpification => { - WarpifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| { + ToggleSshZaplexification => { + ZaplexifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| { report_if_error!(ssh_settings - .enable_ssh_warpification + .enable_ssh_zaplexification .toggle_and_save_value(ctx)); send_telemetry_from_ctx!( - TelemetryEvent::ToggleSshWarpification { - enabled: *ssh_settings.enable_ssh_warpification.value(), + TelemetryEvent::ToggleSshZaplexification { + enabled: *ssh_settings.enable_ssh_zaplexification.value(), }, ctx ); }); - let enabled = *WarpifySettings::as_ref(ctx) - .enable_ssh_warpification + let enabled = *ZaplexifySettings::as_ref(ctx) + .enable_ssh_zaplexification .value(); self.ssh_extension_install_mode_dropdown .update(ctx, |dropdown, ctx| { @@ -466,8 +466,8 @@ impl TypedActionView for WarpifyPageView { } }); } - ToggleTmuxWarpification => { - WarpifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| { + ToggleTmuxZaplexification => { + ZaplexifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| { report_if_error!(ssh_settings.use_ssh_tmux_wrapper.toggle_and_save_value(ctx)); send_telemetry_from_ctx!( TelemetryEvent::ToggleSshTmuxWrapper { @@ -478,8 +478,8 @@ impl TypedActionView for WarpifyPageView { }); } SetSshExtensionInstallMode(mode) => { - WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| { - report_if_error!(warpify_settings + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify_settings, ctx| { + report_if_error!(zaplexify_settings .ssh_extension_install_mode .set_value(*mode, ctx)); send_telemetry_from_ctx!( @@ -490,7 +490,7 @@ impl TypedActionView for WarpifyPageView { ); }); } - WarpifyPageAction::RemoveDenylistedSshHost(index) => { + ZaplexifyPageAction::RemoveDenylistedSshHost(index) => { self.remove_denylisted_ssh_host(*index, ctx); } OpenUrl(url) => { @@ -500,9 +500,9 @@ impl TypedActionView for WarpifyPageView { } } -impl SettingsPageMeta for WarpifyPageView { +impl SettingsPageMeta for ZaplexifyPageView { fn section() -> SettingsSection { - SettingsSection::Warpify + SettingsSection::Zaplexify } fn should_render(&self, _ctx: &AppContext) -> bool { @@ -522,57 +522,51 @@ impl SettingsPageMeta for WarpifyPageView { } } -impl From> for SettingsPageViewHandle { - fn from(view_handle: ViewHandle) -> Self { - SettingsPageViewHandle::Warpify(view_handle) +impl From> for SettingsPageViewHandle { + fn from(view_handle: ViewHandle) -> Self { + SettingsPageViewHandle::Zaplexify(view_handle) } } #[derive(Default)] -struct TitleWidget { - learn_more_highlight_index: HighlightedHyperlink, -} +struct TitleWidget; impl TitleWidget { fn render_top_of_page(&self, appearance: &Appearance, _app: &AppContext) -> Box { - let warpify_description = vec![ - FormattedTextFragment::plain_text(crate::t!("settings-warpify-description-prefix")), - FormattedTextFragment::hyperlink( - crate::t!("settings-warpify-learn-more"), - "", - ), - ]; - - let warpify_description = FormattedTextElement::new( - FormattedText::new([FormattedTextLine::Line(warpify_description)]), + // No "Learn more" link yet: there is no Zaplexify docs page, and an empty + // href renders a broken/no-op hyperlink. The description stands alone as a + // complete sentence; restore the link once a real docs URL exists. + let zaplexify_description = + vec![FormattedTextFragment::plain_text(crate::t!( + "settings-zaplexify-description-prefix" + ))]; + + let zaplexify_description = FormattedTextElement::new( + FormattedText::new([FormattedTextLine::Line(zaplexify_description)]), appearance.ui_font_body(), appearance.ui_font_family(), appearance.ui_font_family(), blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()), - self.learn_more_highlight_index.clone(), + HighlightedHyperlink::default(), ) .with_heading_to_font_size_multipliers(appearance.heading_font_size_multipliers().clone()) - .with_hyperlink_font_color(appearance.theme().accent().into_solid()) - .register_default_click_handlers(|url, _, ctx| { - ctx.open_url(&url.url); - }) .finish(); Flex::column() .with_child(render_page_title( - &crate::t!("settings-warpify-page-title"), + &crate::t!("settings-zaplexify-page-title"), appearance, )) - .with_child(warpify_description) + .with_child(zaplexify_description) .finish() } } impl SettingsWidget for TitleWidget { - type View = WarpifyPageView; + type View = ZaplexifyPageView; fn search_terms(&self) -> &str { - "ssh subshell warpify session" + "ssh subshell zaplexify session" } fn render( @@ -593,20 +587,20 @@ struct SubshellsWidget {} impl SubshellsWidget { fn render_subshells_section( &self, - view: &WarpifyPageView, + view: &ZaplexifyPageView, appearance: &Appearance, app: &AppContext, ) -> Box { let mut column = Flex::column(); - let warpify_settings = WarpifySettings::as_ref(app); + let zaplexify_settings = ZaplexifySettings::as_ref(app); column.add_child( view.build_input_list( - crate::t!("settings-warpify-added-commands"), - &warpify_settings.added_subshell_commands, + crate::t!("settings-zaplexify-added-commands"), + &zaplexify_settings.added_subshell_commands, &view.remove_added_command_button_states, - WarpifyPageAction::RemoveAddedCommand, + ZaplexifyPageAction::RemoveAddedCommand, &view.add_added_commands_editor, appearance, ) @@ -615,10 +609,10 @@ impl SubshellsWidget { column.add_child( view.build_input_list( - crate::t!("settings-warpify-denylisted-commands"), - &warpify_settings.subshell_command_denylist, + crate::t!("settings-zaplexify-denylisted-commands"), + &zaplexify_settings.subshell_command_denylist, &view.remove_denylisted_command_button_states, - WarpifyPageAction::RemoveDenylistedCommand, + ZaplexifyPageAction::RemoveDenylistedCommand, &view.add_denylisted_commands_editor, appearance, ) @@ -631,10 +625,10 @@ impl SubshellsWidget { } impl SettingsWidget for SubshellsWidget { - type View = WarpifyPageView; + type View = ZaplexifyPageView; fn search_terms(&self) -> &str { - "warpify subshell" + "zaplexify subshell" } fn render( @@ -651,17 +645,17 @@ impl SettingsWidget for SubshellsWidget { #[derive(Default)] struct SSHWidget { - tmux_warpification_switch_state: SwitchStateHandle, - enable_ssh_warpification_switch_state: SwitchStateHandle, + tmux_zaplexification_switch_state: SwitchStateHandle, + enable_ssh_zaplexification_switch_state: SwitchStateHandle, additional_info_mouse_state: MouseStateHandle, local_only_icon_tooltip_states: RefCell>, } impl SettingsWidget for SSHWidget { - type View = WarpifyPageView; + type View = ZaplexifyPageView; fn search_terms(&self) -> &str { - "warpify ssh" + "zaplexify ssh" } fn render( @@ -676,34 +670,34 @@ impl SettingsWidget for SSHWidget { .theme() .sub_text_color(appearance.theme().surface_2()); - let enable_ssh_warpification = *WarpifySettings::as_ref(app) - .enable_ssh_warpification + let enable_ssh_zaplexification = *ZaplexifySettings::as_ref(app) + .enable_ssh_zaplexification .value(); let should_prompt_ssh_tmux_wrapper = - *WarpifySettings::as_ref(app).use_ssh_tmux_wrapper.value(); + *ZaplexifySettings::as_ref(app).use_ssh_tmux_wrapper.value(); add_setting( &mut column, - &WarpifySettings::as_ref(app).enable_ssh_warpification, + &ZaplexifySettings::as_ref(app).enable_ssh_zaplexification, move || { - render_body_item::( - crate::t!("settings-warpify-enable-ssh"), + render_body_item::( + crate::t!("settings-zaplexify-enable-ssh"), None, LocalOnlyIconState::for_setting( - EnableSshWarpification::storage_key(), - EnableSshWarpification::sync_to_cloud(), + EnableSshZaplexification::storage_key(), + EnableSshZaplexification::sync_to_cloud(), &mut self.local_only_icon_tooltip_states.borrow_mut(), app, ), ToggleState::Enabled, appearance, ui_builder - .switch(self.enable_ssh_warpification_switch_state.clone()) - .check(enable_ssh_warpification) + .switch(self.enable_ssh_zaplexification_switch_state.clone()) + .check(enable_ssh_zaplexification) .build() .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(WarpifyPageAction::ToggleSshWarpification); + ctx.dispatch_typed_action(ZaplexifyPageAction::ToggleSshZaplexification); }) .finish(), None, @@ -712,18 +706,18 @@ impl SettingsWidget for SSHWidget { ); if FeatureFlag::SshRemoteServer.is_enabled() { - let label_color_override = if !enable_ssh_warpification { + let label_color_override = if !enable_ssh_zaplexification { Some(appearance.theme().disabled_ui_text_color()) } else { None }; add_setting( &mut column, - &WarpifySettings::as_ref(app).ssh_extension_install_mode, + &ZaplexifySettings::as_ref(app).ssh_extension_install_mode, move || { - let install_ssh_label = crate::t!("settings-warpify-install-ssh-extension"); + let install_ssh_label = crate::t!("settings-zaplexify-install-ssh-extension"); let install_ssh_desc = - crate::t!("settings-warpify-install-ssh-extension-description"); + crate::t!("settings-zaplexify-install-ssh-extension-description"); Container::new(render_dropdown_item( appearance, &install_ssh_label, @@ -746,15 +740,15 @@ impl SettingsWidget for SSHWidget { add_setting( &mut column, - &WarpifySettings::as_ref(app).use_ssh_tmux_wrapper, + &ZaplexifySettings::as_ref(app).use_ssh_tmux_wrapper, move || { let mut column = Flex::column(); - column.add_child(render_body_item::( - crate::t!("settings-warpify-use-tmux"), + column.add_child(render_body_item::( + crate::t!("settings-zaplexify-use-tmux"), Some(AdditionalInfo { mouse_state: self.additional_info_mouse_state.clone(), - on_click_action: Some(WarpifyPageAction::OpenUrl( + on_click_action: Some(ZaplexifyPageAction::OpenUrl( "".into(), )), secondary_text: None, @@ -766,19 +760,19 @@ impl SettingsWidget for SSHWidget { &mut self.local_only_icon_tooltip_states.borrow_mut(), app, ), - enable_ssh_warpification.into(), + enable_ssh_zaplexification.into(), appearance, ui_builder - .switch(self.tmux_warpification_switch_state.clone()) + .switch(self.tmux_zaplexification_switch_state.clone()) .check(should_prompt_ssh_tmux_wrapper) - .with_disabled(!enable_ssh_warpification) + .with_disabled(!enable_ssh_zaplexification) .build() .on_click(move |ctx, _, _| { - if !enable_ssh_warpification { + if !enable_ssh_zaplexification { return; } - ctx.dispatch_typed_action(WarpifyPageAction::ToggleTmuxWarpification); + ctx.dispatch_typed_action(ZaplexifyPageAction::ToggleTmuxZaplexification); }) .finish(), None, @@ -786,7 +780,7 @@ impl SettingsWidget for SSHWidget { column.add_child( ui_builder - .paragraph(crate::t!("settings-warpify-tmux-description")) + .paragraph(crate::t!("settings-zaplexify-tmux-description")) .with_style(UiComponentStyles { font_color: Some(description_text_color.into_solid()), margin: Some( @@ -800,14 +794,14 @@ impl SettingsWidget for SSHWidget { .finish(), ); - if enable_ssh_warpification && should_prompt_ssh_tmux_wrapper { - let warpify_settings = WarpifySettings::as_ref(app); + if enable_ssh_zaplexification && should_prompt_ssh_tmux_wrapper { + let zaplexify_settings = ZaplexifySettings::as_ref(app); column.add_child( view.build_input_list( - crate::t!("settings-warpify-denylisted-hosts"), - &warpify_settings.ssh_hosts_denylist, + crate::t!("settings-zaplexify-denylisted-hosts"), + &zaplexify_settings.ssh_hosts_denylist, &view.remove_denylisted_ssh_button_states, - WarpifyPageAction::RemoveDenylistedSshHost, + ZaplexifyPageAction::RemoveDenylistedSshHost, &view.add_denylisted_ssh_editor, appearance, ) diff --git a/app/src/sftp_manager/browser.rs b/app/src/sftp_manager/browser.rs index 88f6de72de..1b21fadf92 100644 --- a/app/src/sftp_manager/browser.rs +++ b/app/src/sftp_manager/browser.rs @@ -226,7 +226,7 @@ pub struct SftpBrowserView { impl SftpBrowserView { /// Create a new SFTP browser view pub fn new(node_id: String, ctx: &mut ViewContext) -> Self { - let pane_configuration = ctx.add_model(|_ctx| PaneConfiguration::new("文件管理")); + let pane_configuration = ctx.add_model(|_ctx| PaneConfiguration::new("File Manager")); let rename_editor = make_editor("Enter new name", ctx); let new_folder_editor = make_editor("Folder name", ctx); let search_editor = make_editor("Search files...", ctx); @@ -497,8 +497,8 @@ impl SftpBrowserView { } Err(e) => { me.connection = - ConnectionState::Failed(format!("创建 SFTP 通道失败: {e}")); - me.show_error_toast(format!("创建 SFTP 通道失败: {e}"), ctx); + ConnectionState::Failed(format!("Failed to create SFTP channel: {e}")); + me.show_error_toast(format!("Failed to create SFTP channel: {e}"), ctx); } } } @@ -508,7 +508,7 @@ impl SftpBrowserView { } Err(_) => { // JoinError (aborted or panicked) - me.connection = ConnectionState::Failed("连接已取消".to_string()); + me.connection = ConnectionState::Failed("Connection cancelled".to_string()); } } ctx.notify(); @@ -516,13 +516,13 @@ impl SftpBrowserView { ); } Ok(None) => { - self.connection = ConnectionState::Failed("未找到服务器配置".to_string()); - self.show_error_toast("未找到服务器配置".to_string(), ctx); + self.connection = ConnectionState::Failed("Server configuration not found".to_string()); + self.show_error_toast("Server configuration not found".to_string(), ctx); ctx.notify(); } Err(e) => { - self.connection = ConnectionState::Failed(format!("读取服务器配置失败: {e}")); - self.show_error_toast(format!("读取服务器配置失败: {e}"), ctx); + self.connection = ConnectionState::Failed(format!("Failed to read server configuration: {e}")); + self.show_error_toast(format!("Failed to read server configuration: {e}"), ctx); ctx.notify(); } } @@ -560,7 +560,7 @@ impl SftpBrowserView { let sftp = match &self.sftp { Some(s) => s.clone(), None => { - self.show_error_toast("未连接到服务器".to_string(), ctx); + self.show_error_toast("Not connected to server".to_string(), ctx); ctx.notify(); return; } @@ -599,7 +599,7 @@ impl SftpBrowserView { me.sync_row_mouse_handles(); } Ok(Err(e)) => { - me.show_error_toast(format!("列出目录失败: {e}"), ctx); + me.show_error_toast(format!("Failed to list directory: {e}"), ctx); } Err(_) => {} } @@ -717,7 +717,7 @@ impl SftpBrowserView { let sftp = match &self.sftp { Some(s) => s.clone(), None => { - self.show_error_toast("未连接到服务器".to_string(), ctx); + self.show_error_toast("Not connected to server".to_string(), ctx); self.dialog = None; ctx.notify(); return; @@ -766,7 +766,7 @@ impl SftpBrowserView { me.refresh_dir(ctx); } Ok(Err(e)) => { - me.show_error_toast(format!("删除失败: {e}"), ctx); + me.show_error_toast(format!("Delete failed: {e}"), ctx); me.refresh_dir(ctx); } Err(_) => { @@ -1061,7 +1061,7 @@ impl SftpBrowserView { let remote_path = match build_upload_remote_path(&self.current_path, &file_name) { Some(p) => p, None => { - self.show_error_toast("文件名包含非法字符".to_string(), ctx); + self.show_error_toast("Filename contains invalid characters".to_string(), ctx); return; } }; @@ -1179,8 +1179,8 @@ impl SftpBrowserView { me.refresh_dir(ctx); } Ok(Err(e)) => { - log::error!("sftp: 上传失败: {e}"); - me.show_error_toast(format!("上传失败: {e}"), ctx); + log::error!("sftp: upload failed: {e}"); + me.show_error_toast(format!("Upload failed: {e}"), ctx); ctx.notify(); } Err(_) => { @@ -1193,10 +1193,10 @@ impl SftpBrowserView { } } else { if let Some(t) = self.transfers.iter_mut().find(|t| t.id == task_id) { - t.state = TransferState::Failed("未连接到服务器".to_string()); + t.state = TransferState::Failed("Not connected to server".to_string()); } - log::error!("sftp: 上传失败: 未连接到服务器"); - self.show_error_toast("上传失败: 未连接到服务器".to_string(), ctx); + log::error!("sftp: upload failed: not connected to server"); + self.show_error_toast("Upload failed: not connected to server".to_string(), ctx); ctx.notify(); } } @@ -1270,8 +1270,8 @@ impl SftpBrowserView { me.transfer_handles.remove(&task_id); if let Ok(Err(e)) = &result { - log::error!("sftp: 下载失败: {e}"); - me.show_error_toast(format!("下载失败: {e}"), ctx); + log::error!("sftp: download failed: {e}"); + me.show_error_toast(format!("Download failed: {e}"), ctx); } ctx.notify(); }, @@ -1280,10 +1280,10 @@ impl SftpBrowserView { } } else { if let Some(t) = self.transfers.iter_mut().find(|t| t.id == task_id) { - t.state = TransferState::Failed("未连接到服务器".to_string()); + t.state = TransferState::Failed("Not connected to server".to_string()); } - log::error!("sftp: 下载失败: 未连接到服务器"); - self.show_error_toast("下载失败: 未连接到服务器".to_string(), ctx); + log::error!("sftp: download failed: not connected to server"); + self.show_error_toast("Download failed: not connected to server".to_string(), ctx); ctx.notify(); } } @@ -1470,13 +1470,13 @@ impl TypedActionView for SftpBrowserView { let new_name = self.rename_editor.as_ref(ctx).buffer_text(ctx); let new_name = new_name.trim().to_string(); if new_name.is_empty() { - self.show_error_toast("名称不能为空".to_string(), ctx); + self.show_error_toast("Name cannot be empty".to_string(), ctx); return; } let new_path = match build_rename_path(original_path, &new_name) { Some(p) => p, None => { - self.show_error_toast("名称不合法:不能包含路径分隔符".to_string(), ctx); + self.show_error_toast("Invalid name: cannot contain path separators".to_string(), ctx); return; } }; @@ -1495,7 +1495,7 @@ impl TypedActionView for SftpBrowserView { me.refresh_dir(ctx); } Ok(Err(e)) => { - me.show_error_toast(format!("重命名失败: {e}"), ctx); + me.show_error_toast(format!("Rename failed: {e}"), ctx); } Err(_) => {} } @@ -1503,7 +1503,7 @@ impl TypedActionView for SftpBrowserView { }, ); } else { - self.show_error_toast("未连接到服务器".to_string(), ctx); + self.show_error_toast("Not connected to server".to_string(), ctx); self.dialog = None; } } @@ -1513,13 +1513,13 @@ impl TypedActionView for SftpBrowserView { let folder_name = self.new_folder_editor.as_ref(ctx).buffer_text(ctx); let folder_name = folder_name.trim().to_string(); if folder_name.is_empty() { - self.show_error_toast("文件夹名称不能为空".to_string(), ctx); + self.show_error_toast("Folder name cannot be empty".to_string(), ctx); return; } let folder_path = match build_new_folder_path(parent_path, &folder_name) { Some(p) => p, None => { - self.show_error_toast("名称不合法:不能包含路径分隔符".to_string(), ctx); + self.show_error_toast("Invalid name: cannot contain path separators".to_string(), ctx); return; } }; @@ -1537,7 +1537,7 @@ impl TypedActionView for SftpBrowserView { me.refresh_dir(ctx); } Ok(Err(e)) => { - me.show_error_toast(format!("创建文件夹失败: {e}"), ctx); + me.show_error_toast(format!("Create folder failed: {e}"), ctx); } Err(_) => {} } @@ -1545,7 +1545,7 @@ impl TypedActionView for SftpBrowserView { }, ); } else { - self.show_error_toast("未连接到服务器".to_string(), ctx); + self.show_error_toast("Not connected to server".to_string(), ctx); self.dialog = None; } } @@ -1638,7 +1638,7 @@ impl TypedActionView for SftpBrowserView { let target_path = match safe_join_name(target_dir, &file_name) { Some(p) => normalize_remote_path(&p), None => { - self.show_error_toast("目标路径不合法".to_string(), ctx); + self.show_error_toast("Invalid target path".to_string(), ctx); self.dialog = None; ctx.notify(); return; @@ -1659,7 +1659,7 @@ impl TypedActionView for SftpBrowserView { me.refresh_dir(ctx); } Ok(Err(e)) => { - me.show_error_toast(format!("移动失败: {e}"), ctx); + me.show_error_toast(format!("Move failed: {e}"), ctx); } Err(_) => {} } @@ -1667,7 +1667,7 @@ impl TypedActionView for SftpBrowserView { }, ); } else { - self.show_error_toast("未连接到服务器".to_string(), ctx); + self.show_error_toast("Not connected to server".to_string(), ctx); self.dialog = None; } } @@ -1870,7 +1870,7 @@ impl View for SftpBrowserView { // 10. Drag-and-drop visual feedback if self.is_drag_hovering { let drop_hint = Text::new_inline( - "拖放文件以上传".to_string(), + "Drag files to upload".to_string(), appearance.ui_font_family(), appearance.ui_font_size() + 2.0, ) diff --git a/app/src/sftp_manager/file_list.rs b/app/src/sftp_manager/file_list.rs index fe79dd7632..5ca216362d 100644 --- a/app/src/sftp_manager/file_list.rs +++ b/app/src/sftp_manager/file_list.rs @@ -151,7 +151,7 @@ pub fn render_header(appearance: &Appearance) -> Box { let name_el = Shrinkable::new( 1.0, Text::new_inline( - String::from("名称"), + String::from("Name"), appearance.ui_font_family(), appearance.ui_font_size(), ) @@ -162,7 +162,7 @@ pub fn render_header(appearance: &Appearance) -> Box { let size_el = ConstrainedBox::new( Text::new_inline( - String::from("大小"), + String::from("Size"), appearance.ui_font_family(), appearance.ui_font_size(), ) @@ -174,7 +174,7 @@ pub fn render_header(appearance: &Appearance) -> Box { let date_el = ConstrainedBox::new( Text::new_inline( - String::from("修改时间"), + String::from("Modified"), appearance.ui_font_family(), appearance.ui_font_size(), ) diff --git a/app/src/sftp_manager/sftp_ops.rs b/app/src/sftp_manager/sftp_ops.rs index e9a77745fb..df613202e8 100644 --- a/app/src/sftp_manager/sftp_ops.rs +++ b/app/src/sftp_manager/sftp_ops.rs @@ -37,11 +37,11 @@ pub enum SftpOpsError { impl std::fmt::Display for SftpOpsError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - SftpOpsError::Connection(msg) => write!(f, "连接错误: {msg}"), - SftpOpsError::Operation(msg) => write!(f, "操作错误: {msg}"), - SftpOpsError::LocalIo(msg) => write!(f, "本地 IO 错误: {msg}"), - SftpOpsError::NoCredentials(msg) => write!(f, "未找到凭据: {msg}"), - SftpOpsError::Cancelled => write!(f, "传输已取消"), + SftpOpsError::Connection(msg) => write!(f, "Connection error: {msg}"), + SftpOpsError::Operation(msg) => write!(f, "Operation error: {msg}"), + SftpOpsError::LocalIo(msg) => write!(f, "Local I/O error: {msg}"), + SftpOpsError::NoCredentials(msg) => write!(f, "Credentials not found: {msg}"), + SftpOpsError::Cancelled => write!(f, "Transfer cancelled"), } } } diff --git a/app/src/sftp_manager/types.rs b/app/src/sftp_manager/types.rs index 5c2e8badeb..8e3ea69841 100644 --- a/app/src/sftp_manager/types.rs +++ b/app/src/sftp_manager/types.rs @@ -483,7 +483,7 @@ mod tests { #[test] fn test_format_size_u64_max() { let result = format_size(u64::MAX); - assert!(result.contains("GB"), "u64::MAX 应以 GB 为单位: {result}"); + assert!(result.contains("GB"), "u64::MAX should be in GB units: {result}"); } /// Test format_size near MB boundary @@ -505,7 +505,7 @@ mod tests { ); task.transferred = 200; let pct = task.progress_percent(); - assert_eq!(pct, 100, "transferred > total_size 时进度限制为 100%"); + assert_eq!(pct, 100, "when transferred > total_size progress is capped to 100%"); } /// Test TransferTask progress_percent fractional truncation @@ -520,7 +520,7 @@ mod tests { ); task.transferred = 1; let pct = task.progress_percent(); - assert_eq!(pct, 14, "1/7 ≈ 14.28%,截断为 14"); + assert_eq!(pct, 14, "1/7 ≈ 14.28%, truncated to 14"); } /// Test TransferTask cancel idempotency diff --git a/app/src/ssh_manager/panel.rs b/app/src/ssh_manager/panel.rs index df68a4768d..3f3057a3bd 100644 --- a/app/src/ssh_manager/panel.rs +++ b/app/src/ssh_manager/panel.rs @@ -34,6 +34,8 @@ use warp_ssh_manager::{ SshServerInfo, }; +use remote_server::proto::SessionInfo; + use settings::Setting; use crate::editor::{ @@ -57,7 +59,7 @@ const PANEL_HORIZONTAL_PADDING: f32 = 8.0; const CONTEXT_MENU_WIDTH: f32 = 200.0; const CONTEXT_MENU_ITEM_PADDING_V: f32 = 7.0; const CONTEXT_MENU_ITEM_PADDING_H: f32 = 12.0; -const MAX_CONTEXT_MENU_ITEMS: usize = 5; +const MAX_CONTEXT_MENU_ITEMS: usize = 6; const SSH_PANEL_POSITION_ID: &str = "ssh_manager_panel_root"; #[derive(Clone, Debug)] @@ -71,6 +73,14 @@ pub enum SshManagerPanelAction { Connect, Edit, CloneServer(String), + /// Context menu on a server: toggle the inline list of its running daemon + /// sessions (fetched via connect-to-list on first expand). + ToggleSessions(String), + /// Click a listed daemon session: adopt it (attach + replay) in a new tab. + AdoptSession { + node_id: String, + pty_session_id: String, + }, /// Click a row; the handling depends on the node kind: /// - server: select + emit OpenSshTerminal (connect directly) /// - folder: select only @@ -96,6 +106,10 @@ pub enum SshManagerPanelAction { DoubleClick(String), /// Right-click a server → "File management": open the SFTP file browser pane. OpenSftp, + /// Toolbar "+": open/close the guided "Add a host" block (blank server + + /// on-demand `~/.ssh/config` suggestions). The saved list stays untouched + /// until the user explicitly creates or imports. + ToggleAddMode, /// "Candidates" section: copy one candidate from `~/.ssh/config` into the saved tree. ImportCandidate { alias: String, @@ -124,6 +138,13 @@ pub enum SshManagerPanelEvent { node_id: String, server: SshServerInfo, }, + /// The user clicked a listed (running) daemon session under a host, asking to + /// adopt it in a new tab (attach + replay). The list comes from the + /// multi-session sidebar (`headless_connect::list_daemon_sessions`). + AdoptDaemonSession { + server: SshServerInfo, + pty_session_id: String, + }, PersistenceError(String), } @@ -194,6 +215,29 @@ pub struct SshManagerPanel { /// Hover state for the section header's Refresh / Toggle buttons. candidates_refresh_btn: MouseStateHandle, candidates_toggle_btn: MouseStateHandle, + /// "Add a host" guided block is open (toggled by the toolbar "+"). + /// The `~/.ssh/config` suggestions are shown **only** while this is true, so + /// nothing unsolicited ever appears in the saved list (PRODUCT decision: + /// suggestions on-demand-when-adding, not always-on). + adding_mode: bool, + /// Hover state for the "Create a blank server" button in the add block. + add_blank_btn: MouseStateHandle, + /// Hover state for the "Cancel" button in the add block. + add_cancel_btn: MouseStateHandle, + + // --- Adopt-sidebar: per-host running daemon sessions (multi-session) --- + /// Running daemon sessions per server node, fetched on demand via + /// `headless_connect::list_daemon_sessions` (connect-to-list, so it also + /// surfaces sessions that survived a restart / drop — the main use case). + host_sessions: HashMap>, + /// Server node_ids whose session list is currently shown (expanded). + sessions_expanded: std::collections::HashSet, + /// Server node_ids with an in-flight session fetch. + sessions_loading: std::collections::HashSet, + /// Last fetch error per server node_id (shown inline under the host). + sessions_error: HashMap, + /// Hover/click state per session row (key = ":"). + session_row_states: HashMap, } impl SshManagerPanel { @@ -220,9 +264,18 @@ impl SshManagerPanel { candidate_add_states: HashMap::new(), candidates_refresh_btn: MouseStateHandle::default(), candidates_toggle_btn: MouseStateHandle::default(), + adding_mode: false, + add_blank_btn: MouseStateHandle::default(), + add_cancel_btn: MouseStateHandle::default(), + host_sessions: HashMap::new(), + sessions_expanded: std::collections::HashSet::new(), + sessions_loading: std::collections::HashSet::new(), + sessions_error: HashMap::new(), + session_row_states: HashMap::new(), }; - // First time the panel opens → read ssh_config once immediately (PRODUCT.md decision A). - me.candidates.update(ctx, |vm, ctx| vm.refresh(ctx)); + // `~/.ssh/config` is read on-demand only when the user opens the "Add a + // host" block (`on_toggle_add_mode`) — never unsolicited on mount, so the + // saved list never shows hosts the user didn't deliberately add. me.refresh_tree(ctx); ctx.subscribe_to_model( @@ -276,6 +329,23 @@ impl SshManagerPanel { self.row_drag_states.entry(n.id.clone()).or_default(); } + // Prune per-host adopt-session state for nodes that were deleted, so these + // maps don't grow unbounded across deletions (keyed by node_id; the + // row-state map is keyed by ":"). + self.host_sessions + .retain(|k, _| active_ids.contains(k.as_str())); + self.sessions_expanded + .retain(|k| active_ids.contains(k.as_str())); + self.sessions_loading + .retain(|k| active_ids.contains(k.as_str())); + self.sessions_error + .retain(|k, _| active_ids.contains(k.as_str())); + self.session_row_states.retain(|k, _| { + k.split(':') + .next() + .is_some_and(|node_id| active_ids.contains(node_id)) + }); + // Tree changed → recompute the "Added" set (PRODUCT.md decision E). "Imported" is determined by // `server.host == candidate.alias` — aligned with ImportCandidate's write // semantics (decision I: on import, `server.host = alias`). @@ -354,6 +424,8 @@ impl SshManagerPanel { /// On completion it emits `OpenServerEditor` (same as manual creation) + broadcasts /// `SshTreeChangedEvent::TreeChanged` so the `Added` badge flips immediately. fn on_import_candidate(&mut self, alias: String, ctx: &mut ViewContext) { + // Picking a suggestion is an explicit, deliberate add — close the add block. + self.adding_mode = false; let candidate = self .candidates .read(ctx, |vm, _| vm.find_candidate(&alias).cloned()); @@ -386,6 +458,8 @@ impl SshManagerPanel { startup_command: None, notes: Some(format!("Imported from {path_display}")), last_connected_at: None, + session_resilience: warp_ssh_manager::SessionResilience::default(), + ring_ceiling_mb: 0, }; let parent = self.parent_for_new_node(); @@ -419,7 +493,22 @@ impl SshManagerPanel { } } + /// Toolbar "+" — toggle the guided "Add a host" block. When opening, re-read + /// `~/.ssh/config` so the suggestions reflect the current file (the user may + /// have edited it since the panel mounted). Closing is a pure UI toggle; it + /// never touches the saved list. + fn on_toggle_add_mode(&mut self, ctx: &mut ViewContext) { + self.adding_mode = !self.adding_mode; + if self.adding_mode { + self.candidates.update(ctx, |vm, ctx| vm.refresh(ctx)); + self.sync_candidate_row_states(ctx); + } + ctx.notify(); + } + fn on_add_server(&mut self, ctx: &mut ViewContext) { + // Either path out of the add block (create blank / import) closes it. + self.adding_mode = false; let parent = self.parent_for_new_node(); let info_template = SshServerInfo::new_default(String::new()); let result = warp_ssh_manager::with_conn(|c| { @@ -557,6 +646,130 @@ impl SshManagerPanel { } } + /// Toggle the inline running-sessions list for a server node; the first + /// expand kicks off a connect-to-list fetch. + fn on_toggle_sessions(&mut self, id: String, ctx: &mut ViewContext) { + if self.sessions_expanded.remove(&id) { + ctx.notify(); + return; + } + self.sessions_expanded.insert(id.clone()); + self.fetch_sessions(id, ctx); + ctx.notify(); + } + + /// Fetches a server's running daemon sessions via connect-to-list and stores + /// them in `host_sessions` (or records `sessions_error`). + #[allow(unused_variables)] + fn fetch_sessions(&mut self, id: String, ctx: &mut ViewContext) { + let server = warp_ssh_manager::with_conn(|c| Ok(SshRepository::get_server(c, &id)?)) + .ok() + .flatten(); + let Some(server) = server else { + return; + }; + self.sessions_error.remove(&id); + + #[cfg(unix)] + { + use crate::auth::AuthStateProvider; + use crate::remote_server::auth_context::server_api_auth_context; + use crate::remote_server::headless_connect; + + if !server.session_resilience.is_enabled() { + self.sessions_error.insert( + id, + crate::t!("workspace-left-panel-ssh-manager-sessions-not-persistent"), + ); + return; + } + // Resolve OneKey → effective auth. The daemon listing runs headless + // (BatchMode), which only works with key auth, AND it must use the + // resolved username/key_path so it targets the SAME per-host + // ControlMaster the connect path uses (`control_socket_path` keys on + // user@host:port, and `open_ssh_terminal` connects with the resolved + // server). Using the unresolved record would key a different socket + // and/or fail auth for OneKey-key hosts. + let resolved = warp_ssh_manager::with_conn(|c| { + let auth = SshRepository::resolve_server_auth(c, &server)?; + let mut resolved = server.clone(); + resolved.username = auth.username; + resolved.key_path = auth.key_path; + resolved.auth_type = auth.auth_type; + Ok(resolved) + }); + let server = match resolved { + Ok(s) if s.auth_type == AuthType::Key => s, + _ => { + self.sessions_error.insert( + id, + crate::t!("workspace-left-panel-ssh-manager-sessions-needs-key"), + ); + return; + } + }; + self.sessions_loading.insert(id.clone()); + let auth_context = std::sync::Arc::new(server_api_auth_context( + AuthStateProvider::as_ref(ctx).get().clone(), + )); + let socket_path = headless_connect::control_socket_path(&server); + let executor = ctx.background_executor().clone(); + ctx.spawn( + headless_connect::list_daemon_sessions(server, socket_path, auth_context, executor), + move |me, result, ctx| { + me.sessions_loading.remove(&id); + match result { + Ok(sessions) => { + me.host_sessions.insert(id, sessions); + } + Err(e) => { + me.sessions_error.insert(id, e); + } + } + ctx.notify(); + }, + ); + } + #[cfg(not(unix))] + { + self.sessions_error.insert( + id, + "Daemon sessions are only supported on Unix hosts.".to_string(), + ); + } + } + + /// Adopt a listed daemon session in a new tab (attach + replay). + fn on_adopt_session( + &mut self, + node_id: String, + pty_session_id: String, + ctx: &mut ViewContext, + ) { + // Resolve OneKey → effective auth so the adopt connects with the same + // username/key_path the listing + connect paths use — otherwise an + // OneKey-key host would target a different ControlMaster / fail auth. + let server = warp_ssh_manager::with_conn(|c| { + let Some(server) = SshRepository::get_server(c, &node_id)? else { + return Ok(None); + }; + let auth = SshRepository::resolve_server_auth(c, &server)?; + let mut resolved = server; + resolved.username = auth.username; + resolved.key_path = auth.key_path; + resolved.auth_type = auth.auth_type; + Ok(Some(resolved)) + }) + .ok() + .flatten(); + if let Some(server) = server { + ctx.emit(SshManagerPanelEvent::AdoptDaemonSession { + server, + pty_session_id, + }); + } + } + fn on_edit(&mut self, ctx: &mut ViewContext) { let Some(id) = self.selected_id.clone() else { return; @@ -659,6 +872,9 @@ impl SshManagerPanel { // — the clear only applies to exit paths with no new selection (Enter/ESC/blur to empty space); a click // itself already provides a new selection context. self.selected_id = Some(id.clone()); + // Navigating the tree dismisses the guided add block — it's only relevant + // while the user is actively adding a host from the toolbar. + self.adding_mode = false; let kind = self.nodes.iter().find(|n| n.id == id).map(|n| n.kind); match kind { Some(NodeKind::Server) => { @@ -931,7 +1147,7 @@ impl SshManagerPanel { .with_child(make_btn( crate::ui_components::icons::Icon::Plus, self.add_server_btn.clone(), - SshManagerPanelAction::AddServer, + SshManagerPanelAction::ToggleAddMode, )) .with_main_axis_size(MainAxisSize::Min) .finish(); @@ -963,11 +1179,145 @@ impl SshManagerPanel { .finish() } + /// Guided "Add a host" block (toolbar "+"). Renders a prominent + /// "Create a blank server" action plus the on-demand `~/.ssh/config` + /// suggestions (`render_candidates`, which renders nothing when + /// auto-discovery is off or the config has no importable hosts). + /// + /// Shown only while `adding_mode` is true; the saved tree below stays + /// untouched until the user explicitly creates or imports. + fn render_add_block( + &self, + appearance: &warp_core::ui::appearance::Appearance, + app: &AppContext, + ) -> Box { + let theme = appearance.theme(); + let muted = theme.sub_text_color(theme.background()); + let main = theme.main_text_color(theme.background()); + let accent = theme.accent().into_solid(); + let icon_color = muted; + + // Header: "Add a host" + a Cancel button on the right. Accent-colored so the + // add-mode block reads as an active, distinct state. + let heading = Text::new_inline( + crate::t!("workspace-left-panel-ssh-manager-add-heading"), + appearance.ui_font_family(), + appearance.ui_font_subheading(), + ) + .with_color(accent.into()) + .finish(); + let cancel_label = Text::new_inline( + crate::t!("workspace-left-panel-ssh-manager-add-cancel"), + appearance.ui_font_family(), + appearance.ui_font_body(), + ) + .with_color(muted.into()) + .finish(); + let cancel_btn = Hoverable::new(self.add_cancel_btn.clone(), move |mouse| { + let mut c = Container::new(cancel_label) + .with_padding_top(ITEM_PADDING_VERTICAL) + .with_padding_bottom(ITEM_PADDING_VERTICAL) + .with_padding_left(ITEM_PADDING_HORIZONTAL) + .with_padding_right(ITEM_PADDING_HORIZONTAL) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))); + if mouse.is_hovered() { + c = c.with_background(internal_colors::fg_overlay_3(theme)); + } + c.finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(|ctx, _, _| { + ctx.dispatch_typed_action(SshManagerPanelAction::ToggleAddMode); + }) + .finish(); + let header_row = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_main_axis_size(MainAxisSize::Max) + .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) + .with_child( + Container::new(heading) + .with_padding_top(ITEM_PADDING_VERTICAL) + .with_padding_bottom(ITEM_PADDING_VERTICAL) + .with_padding_left(ITEM_PADDING_HORIZONTAL) + .finish(), + ) + .with_child(cancel_btn) + .finish(); + + // Primary action: create a blank server (the manual path) and open its editor. + let plus_icon = ConstrainedBox::new( + crate::ui_components::icons::Icon::Plus + .to_warpui_icon(icon_color) + .finish(), + ) + .with_width(ITEM_ICON_SIZE) + .with_height(ITEM_ICON_SIZE) + .finish(); + let blank_label = Text::new_inline( + crate::t!("workspace-left-panel-ssh-manager-add-blank"), + appearance.ui_font_family(), + appearance.ui_font_subheading(), + ) + .with_color(main.into()) + .finish(); + let blank_row = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_spacing(ITEM_ICON_TEXT_SPACING) + .with_child(plus_icon) + .with_child(blank_label) + .with_main_axis_size(MainAxisSize::Max) + .with_main_axis_alignment(MainAxisAlignment::Start) + .finish(); + let blank_btn = Hoverable::new(self.add_blank_btn.clone(), move |mouse| { + let mut c = Container::new(blank_row) + .with_padding_top(ITEM_PADDING_VERTICAL) + .with_padding_bottom(ITEM_PADDING_VERTICAL) + .with_padding_left(ITEM_PADDING_HORIZONTAL) + .with_padding_right(ITEM_PADDING_HORIZONTAL) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))); + if mouse.is_hovered() { + c = c.with_background(internal_colors::fg_overlay_3(theme)); + } + c.finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(|ctx, _, _| { + ctx.dispatch_typed_action(SshManagerPanelAction::AddServer); + }) + .finish(); + + let mut col = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch); + col.add_child(header_row); + col.add_child(blank_btn); + // Suggestions from ~/.ssh/config — renders nothing when auto-discovery is + // off or the config has no importable hosts. When present, give it a small + // top margin so the "from ~/.ssh/config" suggestions read as a distinct + // group from the blank-server CTA above (no dangling gap when empty). + if !self.candidates.as_ref(app).rows().is_empty() { + col.add_child( + Container::new(self.render_candidates(appearance, app)) + .with_margin_top(ITEM_PADDING_VERTICAL) + .finish(), + ); + } + // Set the whole "Add a host" block apart from the saved tree with an accent + // left-bar + subtle background, so it's unmistakable that you're in add mode. + Container::new(col.with_main_axis_size(MainAxisSize::Min).finish()) + .with_background(internal_colors::fg_overlay_1(theme)) + .with_border(Border::left(2.0).with_border_color(accent)) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) + .with_padding_top(ITEM_PADDING_VERTICAL) + .with_padding_bottom(ITEM_PADDING_VERTICAL) + .with_margin_bottom(ITEM_PADDING_VERTICAL) + .finish() + } + /// "Candidates" section — the list of importable hosts parsed from `~/.ssh/config`. /// - /// The section is shown **above** the saved tree; its layout style (row height, indent, font size) matches the tree, - /// with just an extra Refresh button + collapse chevron in the section header. Each candidate row ends with - /// a "+" or "Added" badge (PRODUCT.md decision E). + /// Rendered inside the guided "Add a host" block (`render_add_block`), shown only while the user is + /// actively adding. Its layout style (row height, indent, font size) matches the tree, with just an extra + /// Refresh button + collapse chevron in the section header. Each candidate row ends with a "+" or "Added" + /// badge (PRODUCT.md decision E). Returns Empty when auto-discovery is off or the config has no hosts. fn render_candidates( &self, appearance: &warp_core::ui::appearance::Appearance, @@ -1122,7 +1472,7 @@ impl SshManagerPanel { Hoverable::new(refresh_state, move |_| { Container::new(refresh_icon) .with_uniform_padding(2.0) - .with_corner_radius(CornerRadius::with_all(Radius::Pixels(3.0))) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) .finish() }) .with_cursor(Cursor::PointingHand) @@ -1134,34 +1484,38 @@ impl SshManagerPanel { refresh_icon }; - // The whole row: chevron + label (takes the middle space) + count + Refresh button. - // Use MainAxisSize::Max so the row fills the panel width, eliminating the gap on the right. - let row = Flex::row() + // chevron + label + count grouped at the left; the Refresh button pinned to + // the right edge via SpaceBetween (same pattern as render_toolbar) so the + // trailing action right-aligns instead of floating after the label. + let left_group = Flex::row() .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_spacing(ITEM_ICON_TEXT_SPACING) .with_child(chevron_el) .with_child(label) .with_child(count_label) - .with_child( - ConstrainedBox::new(Empty::new().finish()) - .with_width(8.0) - .finish(), - ) - .with_child(refresh_btn) + .with_main_axis_size(MainAxisSize::Min) + .finish(); + let row = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::Start) + .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) + .with_child(left_group) + .with_child(refresh_btn) .finish(); // Clicking the whole header = toggle (similar to a folder row's single-click). let toggle_state = self.candidates_toggle_btn.clone(); - Hoverable::new(toggle_state, move |_| { - Container::new(row) + Hoverable::new(toggle_state, move |mouse| { + let mut c = Container::new(row) .with_padding_top(ITEM_PADDING_VERTICAL) .with_padding_bottom(ITEM_PADDING_VERTICAL) .with_padding_left(ITEM_PADDING_HORIZONTAL) .with_padding_right(ITEM_PADDING_HORIZONTAL) - .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) - .finish() + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))); + if mouse.is_hovered() { + c = c.with_background(internal_colors::fg_overlay_3(theme)); + } + c.finish() }) .with_cursor(Cursor::PointingHand) .on_click(|ctx, _, _| { @@ -1320,7 +1674,7 @@ impl SshManagerPanel { Hoverable::new(add_state, move |_| { Container::new(plus_icon) .with_uniform_padding(2.0) - .with_corner_radius(CornerRadius::with_all(Radius::Pixels(3.0))) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) .finish() }) .with_cursor(Cursor::PointingHand) @@ -1332,8 +1686,10 @@ impl SshManagerPanel { .finish() }; - // Use MainAxisSize::Max so the candidate row fills the panel width, eliminating the gap on the right. - let row = Flex::row() + // indent + icon + label grouped at the left; the trailing "+"/"Added" + // pinned to the right edge via SpaceBetween, so it right-aligns instead of + // floating right after the label. + let left_group = Flex::row() .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_spacing(ITEM_ICON_TEXT_SPACING) .with_child( @@ -1343,13 +1699,14 @@ impl SshManagerPanel { ) .with_child(icon_el) .with_child(label_block) - .with_child( - ConstrainedBox::new(Empty::new().finish()) - .with_width(8.0) - .finish(), - ) - .with_child(trailing) + .with_main_axis_size(MainAxisSize::Min) + .finish(); + let row = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_main_axis_size(MainAxisSize::Max) + .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) + .with_child(left_group) + .with_child(trailing) .finish(); let row_state = self @@ -1372,10 +1729,126 @@ impl SshManagerPanel { .finish() } + /// Renders the inline running-daemon-session rows shown under an expanded + /// host: a loading / error / empty message, or one clickable row per session + /// (click → adopt). Indented one level past the host row. + fn render_session_rows( + &self, + node: &SshNode, + appearance: &warp_core::ui::appearance::Appearance, + ) -> Vec> { + let theme = appearance.theme(); + let muted: pathfinder_color::ColorU = theme.sub_text_color(theme.background()).into(); + let depth = self.depths.get(&node.id).copied().unwrap_or(0); + // Align the session title under the host *name*: the tree row places its + // label after the depth indent + chevron + icon (each ITEM_ICON_SIZE) with + // ITEM_ICON_TEXT_SPACING between, so a child session lines up on that grid. + let indent = + depth as f32 * FOLDER_DEPTH_INDENT + 2.0 * ITEM_ICON_SIZE + 2.0 * ITEM_ICON_TEXT_SPACING; + + let message = |text: String, color: pathfinder_color::ColorU| -> Box { + Container::new( + Text::new_inline(text, appearance.ui_font_family(), appearance.ui_font_body()) + .with_color(color) + .finish(), + ) + .with_padding_top(ITEM_PADDING_VERTICAL) + .with_padding_bottom(ITEM_PADDING_VERTICAL) + .with_padding_left(indent) + .with_padding_right(ITEM_PADDING_HORIZONTAL) + .with_margin_bottom(ITEM_MARGIN_BOTTOM) + .finish() + }; + + if self.sessions_loading.contains(&node.id) { + return vec![message( + crate::t!("workspace-left-panel-ssh-manager-sessions-loading"), + muted, + )]; + } + if let Some(err) = self.sessions_error.get(&node.id) { + // A failed session fetch is an error — render it in the theme's error + // color, matching the candidates error row (no glyph needed). + return vec![message(err.clone(), theme.ui_error_color())]; + } + let sessions = match self.host_sessions.get(&node.id) { + Some(sessions) if !sessions.is_empty() => sessions, + _ => { + return vec![message( + crate::t!("workspace-left-panel-ssh-manager-sessions-empty"), + muted, + )] + } + }; + + sessions + .iter() + .map(|session| { + let key = format!("{}:{}", node.id, session.session_id); + let state = self + .session_row_states + .get(&key) + .cloned() + .unwrap_or_default(); + let node_id = node.id.clone(); + let pty_session_id = session.session_id.clone(); + let title = if !session.title.is_empty() { + session.title.clone() + } else if !session.cwd.is_empty() { + session.cwd.clone() + } else { + pty_session_id.clone() + }; + let row = Flex::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_spacing(ITEM_ICON_TEXT_SPACING) + .with_child( + ConstrainedBox::new(Empty::new().finish()) + .with_width(indent) + .finish(), + ) + .with_child( + Text::new_inline( + title, + appearance.ui_font_family(), + appearance.ui_font_subheading(), + ) + .with_color(theme.main_text_color(theme.background()).into()) + .finish(), + ) + .with_main_axis_size(MainAxisSize::Max) + .finish(); + Hoverable::new(state, move |mouse| { + let mut c = Container::new(row) + .with_padding_top(ITEM_PADDING_VERTICAL) + .with_padding_bottom(ITEM_PADDING_VERTICAL) + .with_padding_left(ITEM_PADDING_HORIZONTAL) + .with_padding_right(ITEM_PADDING_HORIZONTAL) + .with_margin_bottom(ITEM_MARGIN_BOTTOM) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))); + if mouse.is_hovered() { + c = c.with_background(internal_colors::fg_overlay_3(theme)); + } + c.finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(move |ctx, _, _| { + ctx.dispatch_typed_action(SshManagerPanelAction::AdoptSession { + node_id: node_id.clone(), + pty_session_id: pty_session_id.clone(), + }); + }) + .finish() + }) + .collect() + } + fn render_tree(&self, appearance: &warp_core::ui::appearance::Appearance) -> Box { let mut col = Flex::column(); - if self.nodes.is_empty() { + // While the "Add a host" block is shown, suppress the "no servers yet" + // empty-state — showing both at once reads as a contradiction. + if self.nodes.is_empty() && !self.adding_mode { let theme = appearance.theme(); let muted = theme.sub_text_color(theme.background()); col.add_child( @@ -1400,6 +1873,14 @@ impl SshManagerPanel { continue; } col.add_child(self.render_row(node, appearance)); + // Adopt-sidebar: inline running daemon sessions under an expanded host. + if matches!(node.kind, NodeKind::Server) + && self.sessions_expanded.contains(&node.id) + { + for child in self.render_session_rows(node, appearance) { + col.add_child(child); + } + } } } let inner = col @@ -1528,13 +2009,15 @@ impl SshManagerPanel { let id_for_right_click = node.id.clone(); // While renaming, don't accept clicks/right-clicks (let EditorView handle them). + // Padding must match the normal (hoverable) branch exactly so the row does + // not shift when rename mode toggles — the normal branch adds no bottom + // margin, so this one must not either. if is_renaming { return Container::new(row) .with_padding_top(ITEM_PADDING_VERTICAL) .with_padding_bottom(ITEM_PADDING_VERTICAL) .with_padding_left(ITEM_PADDING_HORIZONTAL) .with_padding_right(ITEM_PADDING_HORIZONTAL) - .with_margin_bottom(ITEM_MARGIN_BOTTOM) .finish(); } @@ -1663,6 +2146,10 @@ impl SshManagerPanel { crate::t!("workspace-left-panel-ssh-manager-menu-connect"), SshManagerPanelAction::Connect, ), + ( + crate::t!("workspace-left-panel-ssh-manager-menu-sessions"), + SshManagerPanelAction::ToggleSessions(id.clone()), + ), ( crate::t!("workspace-left-panel-ssh-manager-menu-sftp"), SshManagerPanelAction::OpenSftp, @@ -1756,11 +2243,17 @@ impl TypedActionView for SshManagerPanel { let parent = self.parent_for_new_node(); self.on_add_folder_with_parent(parent, ctx) } + SshManagerPanelAction::ToggleAddMode => self.on_toggle_add_mode(ctx), SshManagerPanelAction::AddServer => self.on_add_server(ctx), SshManagerPanelAction::DeleteSelected => self.on_delete_selected(ctx), SshManagerPanelAction::Connect => self.on_connect(ctx), SshManagerPanelAction::Edit => self.on_edit(ctx), SshManagerPanelAction::CloneServer(id) => self.on_clone_server(id, ctx), + SshManagerPanelAction::ToggleSessions(id) => self.on_toggle_sessions(id.clone(), ctx), + SshManagerPanelAction::AdoptSession { + node_id, + pty_session_id, + } => self.on_adopt_session(node_id.clone(), pty_session_id.clone(), ctx), SshManagerPanelAction::Click(id) => self.on_click(id.clone(), ctx), SshManagerPanelAction::StartRename(id) => self.enter_rename(id.clone(), false, ctx), SshManagerPanelAction::CommitRename => self.commit_rename(ctx), @@ -1807,17 +2300,21 @@ impl View for SshManagerPanel { let appearance = warp_core::ui::appearance::Appearance::as_ref(app); let toolbar = Container::new(self.render_toolbar(appearance)) - .with_uniform_padding(8.0) + .with_uniform_padding(PANEL_HORIZONTAL_PADDING) .finish(); - // PRODUCT.md §2: the Candidates section sits **above** the saved tree, sharing the same panel - // horizontal padding. Before the view-model has refreshed, the section returns Empty and takes - // no height. When auto-discovery is off, the section is not rendered. - let auto_discover = *SshSettings::as_ref(app).enable_ssh_auto_discovery.value(); - let candidates_section = if auto_discover { - Container::new(self.render_candidates(appearance, app)) + // The saved tree shows **only** what the user deliberately added. The + // guided "Add a host" block — a blank-server action plus on-demand + // `~/.ssh/config` suggestions — is shown above the tree only while the + // user is actively adding (toolbar "+"), so nothing unsolicited ever + // appears in the list. + let candidates_section = if self.adding_mode { + Container::new(self.render_add_block(appearance, app)) .with_padding_left(PANEL_HORIZONTAL_PADDING - ITEM_PADDING_HORIZONTAL) .with_padding_right(PANEL_HORIZONTAL_PADDING - ITEM_PADDING_HORIZONTAL) + // Separate the "Add a host" block from the saved tree below, so + // "what I can add" reads as distinct from "what I have". + .with_padding_bottom(ITEM_ICON_TEXT_SPACING) .finish() } else { Empty::new().finish() diff --git a/app/src/ssh_manager/server_view.rs b/app/src/ssh_manager/server_view.rs index 89f12139a9..c45e6244ee 100644 --- a/app/src/ssh_manager/server_view.rs +++ b/app/src/ssh_manager/server_view.rs @@ -33,8 +33,8 @@ use warpui::{ use warp_ssh_manager::{ AuthType, ConnectionStatus, KeychainSecretStore, NodeKind, OneKeyCredentialKind, SecretKind, - SshNode, SshOneKeyCredential, SshRepository, SshSecretStore, SshSecretStoreError, - SshServerInfo, + SessionResilience, SshNode, SshOneKeyCredential, SshRepository, SshSecretStore, + SshSecretStoreError, SshServerInfo, }; use zeroize::Zeroizing; @@ -45,6 +45,10 @@ const SAVE_BUTTON_WIDTH: f32 = 96.0; const SAVE_BUTTON_HEIGHT: f32 = 28.0; const AUTH_TOGGLE_PADDING_H: f32 = 14.0; const AUTH_TOGGLE_PADDING_V: f32 = 6.0; +/// Per-host daemon scrollback-ceiling presets shown as pills: (MiB, label). +/// 0 = the daemon's built-in default ceiling. +const RING_CEILING_PRESETS: [(u32, &str); 4] = + [(0, "Default"), (64, "64 MB"), (256, "256 MB"), (1024, "1 GB")]; const ONEKEY_MANAGER_WIDTH: f32 = 680.0; const ONEKEY_MANAGER_HEIGHT: f32 = 500.0; const ONEKEY_MANAGER_LIST_WIDTH: f32 = 220.0; @@ -57,6 +61,11 @@ pub enum SshServerAction { SetAuthPassword, SetAuthKey, SetAuthOneKey, + /// Toggle session persistence (native remote-session layer). `true` selects + /// the persistent tier, `false` selects standard (non-persistent) SSH. + SetSessionResilience(bool), + /// Pick the per-host daemon scrollback ceiling, in MiB (0 = daemon default). + SetRingCeiling(u32), /// Open system file picker to select private key file and write path to key_path editor. PickKeyFile, /// Select group (None means root level, Some(index) means self.folders[index]). @@ -89,6 +98,29 @@ enum AuthSpecificField { OneKeyCredential, } +/// Immutable snapshot of every value the Save button submits, used for +/// dirty-tracking (see [`SshServerView::baseline_snapshot`]). +#[derive(Clone, PartialEq, Eq)] +struct ServerFormSnapshot { + name: String, + host: String, + port: String, + user: String, + password: String, + key_path: String, + onekey_label: String, + onekey_user: String, + onekey_key_path: String, + root_password: String, + startup_command: String, + notes: String, + auth_type: AuthType, + session_resilience: SessionResilience, + ring_ceiling_mb: u32, + group_id: Option, + onekey_credential_id: Option, +} + pub struct SshServerView { node_id: String, /// Node metadata (mainly uses name as header title). @@ -114,12 +146,23 @@ pub struct SshServerView { /// Currently selected authentication method. Save button submits this value to DB. auth_type: AuthType, + /// Currently selected session-persistence tier (native remote-session layer). + /// Save/Connect submit this value. + session_resilience: SessionResilience, + /// Per-host daemon scrollback ceiling in MiB (0 = daemon default). Only + /// meaningful when `session_resilience` is enabled. + ring_ceiling_mb: u32, + save_btn_state: MouseStateHandle, connect_btn_state: MouseStateHandle, test_btn_state: MouseStateHandle, auth_password_btn_state: MouseStateHandle, auth_key_btn_state: MouseStateHandle, auth_onekey_btn_state: MouseStateHandle, + resilience_off_btn_state: MouseStateHandle, + resilience_on_btn_state: MouseStateHandle, + /// Hover/click state per ring-ceiling preset pill (see RING_CEILING_PRESETS). + ring_ceiling_btn_states: Vec, key_path_picker_btn_state: MouseStateHandle, onekey_manager_btn_state: MouseStateHandle, onekey_manager_close_btn_state: MouseStateHandle, @@ -151,6 +194,11 @@ pub struct SshServerView { latency_ms: Option, is_testing: bool, scroll_state: ClippedScrollStateHandle, + /// Snapshot of all form values as of the last DB load / successful save. + /// The Save button is enabled only while the current form differs from this + /// baseline; after a save `reload` re-captures it, so the button disables + /// again — the visible "it worked" feedback the user was missing. + baseline_snapshot: Option, } impl SshServerView { @@ -216,12 +264,20 @@ impl SshServerView { startup_command_editor, notes_editor, auth_type: AuthType::Password, + session_resilience: SessionResilience::default(), + ring_ceiling_mb: 0, save_btn_state: MouseStateHandle::default(), connect_btn_state: MouseStateHandle::default(), test_btn_state: MouseStateHandle::default(), auth_password_btn_state: MouseStateHandle::default(), auth_key_btn_state: MouseStateHandle::default(), auth_onekey_btn_state: MouseStateHandle::default(), + resilience_off_btn_state: MouseStateHandle::default(), + resilience_on_btn_state: MouseStateHandle::default(), + ring_ceiling_btn_states: RING_CEILING_PRESETS + .iter() + .map(|_| MouseStateHandle::default()) + .collect(), key_path_picker_btn_state: MouseStateHandle::default(), onekey_manager_btn_state: MouseStateHandle::default(), onekey_manager_close_btn_state: MouseStateHandle::default(), @@ -247,6 +303,7 @@ impl SshServerView { latency_ms: None, is_testing: false, scroll_state: ClippedScrollStateHandle::default(), + baseline_snapshot: None, }; me.reload(ctx); @@ -272,17 +329,21 @@ impl SshServerView { EditorEvent::Edited(_) | EditorEvent::Enter => { if me.status.is_some() { me.status = None; - ctx.notify(); } + // Re-render so the Save button re-evaluates its dirty state on + // every edit (not only when a status banner is cleared). + ctx.notify(); } EditorEvent::Blurred => { // When losing focus, clear own selection too, to prevent // "old editor still highlighted/selected after clicking another editor". source.update(ctx, |e, ctx| e.clear_selections(ctx)); - if me.status.is_some() { - me.status = None; - ctx.notify(); - } + // Do NOT clear `status` on blur: clicking the Save button + // blurs the focused field, which would immediately wipe the + // "Saved." confirmation that on_save just set (the user saw + // no feedback at all). The status is cleared when the user + // actually edits a field (the Edited/Enter arm) — i.e. once + // the saved state has become stale. } EditorEvent::Focused | EditorEvent::ClearParentSelections => { me.clear_other_editors_selections(&source, ctx); @@ -374,6 +435,8 @@ impl SshServerView { if let Some(srv) = self.server.clone() { self.auth_type = srv.auth_type; + self.session_resilience = srv.session_resilience; + self.ring_ceiling_mb = srv.ring_ceiling_mb; self.selected_onekey_credential_id = srv.credential_id.clone(); let host = srv.host.clone(); let port_str = srv.port.to_string(); @@ -459,6 +522,9 @@ impl SshServerView { self.rebuild_group_dropdown(ctx); self.rebuild_onekey_credential_dropdown(ctx); self.sync_onekey_manager_row_states(); + // Re-baseline: the form now reflects the persisted state, so it is "clean" + // (Save stays disabled until the next edit). + self.baseline_snapshot = Some(self.current_form_snapshot(ctx)); ctx.notify(); } @@ -615,6 +681,39 @@ impl SshServerView { editor.as_ref(app).buffer_text(app) } + /// Capture the current form values into a snapshot for dirty-tracking. + fn current_form_snapshot(&self, app: &AppContext) -> ServerFormSnapshot { + ServerFormSnapshot { + name: self.current_text(&self.name_editor, app), + host: self.current_text(&self.host_editor, app), + port: self.current_text(&self.port_editor, app), + user: self.current_text(&self.user_editor, app), + password: self.current_text(&self.password_editor, app), + key_path: self.current_text(&self.key_path_editor, app), + onekey_label: self.current_text(&self.onekey_label_editor, app), + onekey_user: self.current_text(&self.onekey_user_editor, app), + onekey_key_path: self.current_text(&self.onekey_key_path_editor, app), + root_password: self.current_text(&self.root_password_editor, app), + startup_command: self.current_text(&self.startup_command_editor, app), + notes: self.current_text(&self.notes_editor, app), + auth_type: self.auth_type, + session_resilience: self.session_resilience, + ring_ceiling_mb: self.ring_ceiling_mb, + group_id: self.current_group_id.clone(), + onekey_credential_id: self.selected_onekey_credential_id.clone(), + } + } + + /// Whether the form differs from the last loaded/saved baseline. Without a + /// baseline (e.g. the DB load failed) we treat the form as dirty so Save + /// stays usable. + fn is_dirty(&self, app: &AppContext) -> bool { + match &self.baseline_snapshot { + Some(baseline) => self.current_form_snapshot(app) != *baseline, + None => true, + } + } + /// Get currently selected group ID. pub fn current_group_id(&self) -> &Option { &self.current_group_id @@ -693,6 +792,8 @@ impl SshServerView { Some(notes_text.trim().to_string()) }, last_connected_at: self.server.as_ref().and_then(|s| s.last_connected_at), + session_resilience: self.session_resilience, + ring_ceiling_mb: self.ring_ceiling_mb, }; // 2. Write to DB (rename + update_server + possible move_node) @@ -814,6 +915,11 @@ impl SshServerView { Some(notes_text.trim().to_string()) }, last_connected_at: self.server.as_ref().and_then(|s| s.last_connected_at), + // Use the live editor state (like host/auth/startup above), not the + // last-saved server — so toggling Persistent / scrollback and hitting + // Connect without Save first takes effect immediately. + session_resilience: self.session_resilience, + ring_ceiling_mb: self.ring_ceiling_mb, }; ctx.dispatch_typed_action(&crate::workspace::WorkspaceAction::OpenSshTerminal { node_id: self.node_id.clone(), @@ -855,6 +961,9 @@ impl SshServerView { startup_command: None, notes: None, last_connected_at: None, + // Ephemeral object for the connection test only; never persisted. + session_resilience: SessionResilience::default(), + ring_ceiling_mb: self.ring_ceiling_mb, }; let (server, password) = match resolve_test_server_and_password( @@ -967,6 +1076,33 @@ impl SshServerView { } } + fn on_set_session_resilience(&mut self, on: bool, ctx: &mut ViewContext) { + let next = if on { + // Preserve a higher tier (e.g. PersistPlusMosh) if already selected; + // the UI only distinguishes off vs. on for now (B3 mosh not built). + if self.session_resilience.is_enabled() { + self.session_resilience + } else { + SessionResilience::PersistOnly + } + } else { + SessionResilience::Off + }; + if self.session_resilience != next { + self.session_resilience = next; + self.status = None; + ctx.notify(); + } + } + + fn on_set_ring_ceiling(&mut self, mb: u32, ctx: &mut ViewContext) { + if self.ring_ceiling_mb != mb { + self.ring_ceiling_mb = mb; + self.status = None; + ctx.notify(); + } + } + fn on_save_managed_onekey_credential(&mut self, ctx: &mut ViewContext) { let label = self.current_text(&self.onekey_label_editor.clone(), ctx); let username = self.current_text(&self.onekey_user_editor.clone(), ctx); @@ -1367,6 +1503,159 @@ impl SshServerView { .finish() } + /// Session-persistence toggle (native remote-session layer). Two pills, + /// styled like the auth toggle: Standard (non-persistent) vs. Persistent. + fn render_resilience_toggle(&self, appearance: &Appearance) -> Box { + let theme = appearance.theme(); + + let make_pill = |label: String, + active: bool, + state: MouseStateHandle, + action: SshServerAction| + -> Box { + let main_color = if active { + theme.main_text_color(theme.accent()) + } else { + theme.sub_text_color(theme.background()) + }; + let bg = if active { + theme.accent() + } else { + theme.surface_2() + }; + let label_el = Text::new_inline( + label, + appearance.ui_font_family(), + appearance.ui_font_size(), + ) + .with_color(main_color.into()) + .finish(); + + Hoverable::new(state, move |_| { + Container::new(label_el) + .with_padding_left(AUTH_TOGGLE_PADDING_H) + .with_padding_right(AUTH_TOGGLE_PADDING_H) + .with_padding_top(AUTH_TOGGLE_PADDING_V) + .with_padding_bottom(AUTH_TOGGLE_PADDING_V) + .with_background(bg) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) + .finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(move |ctx, _, _| ctx.dispatch_typed_action(action)) + .finish() + }; + + let enabled = self.session_resilience.is_enabled(); + let mut row = Wrap::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_spacing(8.0) + .with_run_spacing(8.0) + .with_main_axis_size(MainAxisSize::Min); + row.add_child(make_pill( + crate::t!("workspace-left-panel-ssh-manager-resilience-off"), + !enabled, + self.resilience_off_btn_state.clone(), + SshServerAction::SetSessionResilience(false), + )); + row.add_child(make_pill( + crate::t!("workspace-left-panel-ssh-manager-resilience-on"), + enabled, + self.resilience_on_btn_state.clone(), + SshServerAction::SetSessionResilience(true), + )); + + Container::new( + Flex::column() + .with_cross_axis_alignment(CrossAxisAlignment::Stretch) + .with_child(self.render_label( + &crate::t!("workspace-left-panel-ssh-manager-detail-resilience"), + appearance, + )) + .with_child(row.finish()) + .finish(), + ) + .with_margin_bottom(FIELD_BLOCK_MARGIN_BOTTOM) + .finish() + } + + /// Per-host daemon scrollback-ceiling picker (preset pills). Only rendered + /// when session persistence is enabled — the ceiling sizes the daemon-side + /// replay/scrollback buffer (OutputRing). + fn render_ring_ceiling(&self, appearance: &Appearance) -> Box { + let theme = appearance.theme(); + + let make_pill = |label: String, + active: bool, + state: MouseStateHandle, + action: SshServerAction| + -> Box { + let main_color = if active { + theme.main_text_color(theme.accent()) + } else { + theme.sub_text_color(theme.background()) + }; + let bg = if active { + theme.accent() + } else { + theme.surface_2() + }; + let label_el = Text::new_inline( + label, + appearance.ui_font_family(), + appearance.ui_font_size(), + ) + .with_color(main_color.into()) + .finish(); + + Hoverable::new(state, move |_| { + Container::new(label_el) + .with_padding_left(AUTH_TOGGLE_PADDING_H) + .with_padding_right(AUTH_TOGGLE_PADDING_H) + .with_padding_top(AUTH_TOGGLE_PADDING_V) + .with_padding_bottom(AUTH_TOGGLE_PADDING_V) + .with_background(bg) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) + .finish() + }) + .with_cursor(Cursor::PointingHand) + .on_click(move |ctx, _, _| ctx.dispatch_typed_action(action)) + .finish() + }; + + let mut row = Wrap::row() + .with_cross_axis_alignment(CrossAxisAlignment::Center) + .with_spacing(8.0) + .with_run_spacing(8.0) + .with_main_axis_size(MainAxisSize::Min); + for (i, (mb, label)) in RING_CEILING_PRESETS.iter().enumerate() { + let state = self + .ring_ceiling_btn_states + .get(i) + .cloned() + .unwrap_or_default(); + row.add_child(make_pill( + (*label).to_string(), + self.ring_ceiling_mb == *mb, + state, + SshServerAction::SetRingCeiling(*mb), + )); + } + + Container::new( + Flex::column() + .with_cross_axis_alignment(CrossAxisAlignment::Stretch) + .with_child(self.render_label( + &crate::t!("workspace-left-panel-ssh-manager-detail-ring-ceiling"), + appearance, + )) + .with_child(row.finish()) + .finish(), + ) + .with_margin_bottom(FIELD_BLOCK_MARGIN_BOTTOM) + .finish() + } + fn auth_toggle_button_state(&self, auth_type: AuthType) -> MouseStateHandle { match auth_type { AuthType::Password => self.auth_password_btn_state.clone(), @@ -1375,8 +1664,8 @@ impl SshServerView { } } - fn render_save_button(&self, appearance: &Appearance) -> Box { - appearance + fn render_save_button(&self, appearance: &Appearance, enabled: bool) -> Box { + let mut builder = appearance .ui_builder() .button(ButtonVariant::Accent, self.save_btn_state.clone()) .with_style(UiComponentStyles { @@ -1392,7 +1681,19 @@ impl SshServerView { font_size: Some(13.0), ..Default::default() }) - .with_centered_text_label(crate::t!("workspace-left-panel-ssh-manager-save")) + // Muted, non-interactive look when there are no unsaved changes, so the + // button visibly communicates "nothing to save" rather than looking + // clickable but doing nothing. + .with_disabled_styles(UiComponentStyles { + background: Some(internal_colors::neutral_4(appearance.theme()).into()), + font_color: Some(internal_colors::neutral_5(appearance.theme())), + ..Default::default() + }) + .with_centered_text_label(crate::t!("workspace-left-panel-ssh-manager-save")); + if !enabled { + builder = builder.disabled(); + } + builder .build() .on_click(move |ctx, _, _| ctx.dispatch_typed_action(SshServerAction::Save)) .finish() @@ -1613,23 +1914,26 @@ impl SshServerView { .finish() }; - let row = Flex::row() + // Wrap::row (like the auth/resilience/ring pill groups) so the type pills + // wrap on a narrow form instead of overflowing. + let mut row = Wrap::row() .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_spacing(8.0) - .with_main_axis_size(MainAxisSize::Min) - .with_child(make_pill( - crate::t!("workspace-left-panel-ssh-manager-onekey-type-password"), - self.managed_onekey_kind == OneKeyCredentialKind::Password, - self.onekey_manager_password_btn_state.clone(), - SshServerAction::SetManagedOneKeyPassword, - )) - .with_child(make_pill( - crate::t!("workspace-left-panel-ssh-manager-onekey-type-key"), - self.managed_onekey_kind == OneKeyCredentialKind::Key, - self.onekey_manager_key_btn_state.clone(), - SshServerAction::SetManagedOneKeyKey, - )) - .finish(); + .with_run_spacing(8.0) + .with_main_axis_size(MainAxisSize::Min); + row.add_child(make_pill( + crate::t!("workspace-left-panel-ssh-manager-onekey-type-password"), + self.managed_onekey_kind == OneKeyCredentialKind::Password, + self.onekey_manager_password_btn_state.clone(), + SshServerAction::SetManagedOneKeyPassword, + )); + row.add_child(make_pill( + crate::t!("workspace-left-panel-ssh-manager-onekey-type-key"), + self.managed_onekey_kind == OneKeyCredentialKind::Key, + self.onekey_manager_key_btn_state.clone(), + SshServerAction::SetManagedOneKeyKey, + )); + let row = row.finish(); Container::new( Flex::column() @@ -1966,6 +2270,8 @@ impl TypedActionView for SshServerView { SshServerAction::SetAuthPassword => self.on_set_auth(AuthType::Password, ctx), SshServerAction::SetAuthKey => self.on_set_auth(AuthType::Key, ctx), SshServerAction::SetAuthOneKey => self.on_set_auth(AuthType::OneKey, ctx), + SshServerAction::SetSessionResilience(on) => self.on_set_session_resilience(*on, ctx), + SshServerAction::SetRingCeiling(mb) => self.on_set_ring_ceiling(*mb, ctx), SshServerAction::PickKeyFile => self.on_pick_key_file(ctx), SshServerAction::PickOneKeyKeyFile => self.on_pick_onekey_key_file(ctx), SshServerAction::OpenOneKeyManager => { @@ -2103,7 +2409,7 @@ impl View for SshServerView { .with_spacing(8.0) .with_child(self.render_test_button(appearance)) .with_child(self.render_connect_button(appearance)) - .with_child(self.render_save_button(appearance)) + .with_child(self.render_save_button(appearance, self.is_dirty(app))) .with_main_axis_size(MainAxisSize::Min) .finish(); let header = Flex::row() @@ -2180,6 +2486,14 @@ impl View for SshServerView { } } + // Session persistence — a core zaplex feature (survives transport drops), + // so it sits prominently right under the auth section, not buried at the + // bottom. The scrollback-ceiling picker only matters when it's enabled. + col.add_child(self.render_resilience_toggle(appearance)); + if self.session_resilience.is_enabled() { + col.add_child(self.render_ring_ceiling(appearance)); + } + // Startup command col.add_child(self.render_text_field( &crate::t!("workspace-left-panel-ssh-manager-startup-command"), @@ -2198,6 +2512,7 @@ impl View for SshServerView { &self.notes_editor, appearance, )); + // (Session persistence moved up, directly under the auth section.) let theme = appearance.theme(); let inner = ConstrainedBox::new( @@ -2337,7 +2652,7 @@ fn resolve_test_password( Ok(None) => None, Err(SshSecretStoreError::NoBackend) => None, Err(SshSecretStoreError::Keyring(msg)) => { - log::warn!("keychain 读取失败,fallback 失败: {msg}"); + log::warn!("keychain: read failed, fallback failed: {msg}"); None } } diff --git a/app/src/ssh_manager/server_view_tests.rs b/app/src/ssh_manager/server_view_tests.rs index 033b086e46..7134d5d168 100644 --- a/app/src/ssh_manager/server_view_tests.rs +++ b/app/src/ssh_manager/server_view_tests.rs @@ -181,6 +181,8 @@ fn onekey_test_connection_uses_shared_password_credential() { startup_command: None, notes: None, last_connected_at: None, + session_resilience: warp_ssh_manager::SessionResilience::default(), + ring_ceiling_mb: 0, }; let (server, pw) = resolve_test_server_and_password(server, &credentials, "", &store).unwrap(); @@ -211,6 +213,8 @@ fn onekey_test_connection_prefers_editor_password() { startup_command: None, notes: None, last_connected_at: None, + session_resilience: warp_ssh_manager::SessionResilience::default(), + ring_ceiling_mb: 0, }; let (_, pw) = @@ -239,6 +243,8 @@ fn onekey_key_credential_resolves_test_connection_to_key_auth() { startup_command: None, notes: None, last_connected_at: None, + session_resilience: warp_ssh_manager::SessionResilience::default(), + ring_ceiling_mb: 0, }; let (server, pw) = resolve_test_server_and_password(server, &credentials, "", &store).unwrap(); diff --git a/app/src/tab.rs b/app/src/tab.rs index b352c38257..f55202be62 100644 --- a/app/src/tab.rs +++ b/app/src/tab.rs @@ -61,8 +61,8 @@ pub fn uses_vertical_tabs(ctx: &AppContext) -> bool { FeatureFlag::VerticalTabs.is_enabled() && *TabSettings::as_ref(ctx).use_vertical_tabs } -const WARP_2_TAB_COLOR_OPACITY: Opacity = 25; -const WARP_2_HOVERED_TAB_COLOR_OPACITY: Opacity = 50; +const ZAPLEX_2_TAB_COLOR_OPACITY: Opacity = 25; +const ZAPLEX_2_HOVERED_TAB_COLOR_OPACITY: Opacity = 50; const TAB_CLOSE_BUTTON_OPACITY: Opacity = 60; const TAB_CLOSE_BUTTON_WIDTH: f32 = 20.0; const MAX_TOOLTIP_LENGTH: usize = 80; @@ -1184,9 +1184,9 @@ impl<'a> TabComponent<'a> { (bg, border) } else { let tab_opacity = if is_active || is_hovered { - WARP_2_HOVERED_TAB_COLOR_OPACITY + ZAPLEX_2_HOVERED_TAB_COLOR_OPACITY } else { - WARP_2_TAB_COLOR_OPACITY + ZAPLEX_2_TAB_COLOR_OPACITY }; let bg = if let Some(custom_background) = self.styles.background { diff --git a/app/src/tab_configs/session_config_tests.rs b/app/src/tab_configs/session_config_tests.rs index bc99185ca4..4063ccbf42 100644 --- a/app/src/tab_configs/session_config_tests.rs +++ b/app/src/tab_configs/session_config_tests.rs @@ -517,14 +517,14 @@ fn snapshot_2x2_grid() { #[test] fn snapshot_non_terminal_leaf_replaced_with_terminal() { use crate::app_state::NotebookPaneSnapshot; - use crate::drive::ZapDriveObjectSettings; + use crate::drive::ZaplexDriveObjectSettings; let notebook_leaf = PaneNodeSnapshot::Leaf(LeafSnapshot { is_focused: false, custom_vertical_tabs_title: None, contents: LeafContents::Notebook(NotebookPaneSnapshot::NotebookObject { notebook_id: None, - settings: ZapDriveObjectSettings::default(), + settings: ZaplexDriveObjectSettings::default(), }), }); let snapshot = PaneNodeSnapshot::Branch(BranchSnapshot { diff --git a/app/src/terminal/alias.rs b/app/src/terminal/alias.rs index ed872c91dd..7a7a4ddf6a 100644 --- a/app/src/terminal/alias.rs +++ b/app/src/terminal/alias.rs @@ -12,7 +12,7 @@ pub struct AliasedCommand { pub alias_value: String, } -/// Returns whether the alias can be expanded by Zap given its value. +/// Returns whether the alias can be expanded by Zaplex given its value. /// /// We don't expand on any alias that starts with itself, as it leads to /// cases where the alias is expanded twice: once as the user types in the diff --git a/app/src/terminal/available_shells.rs b/app/src/terminal/available_shells.rs index aff7893cc5..56d9c03994 100644 --- a/app/src/terminal/available_shells.rs +++ b/app/src/terminal/available_shells.rs @@ -924,7 +924,7 @@ impl AvailableShells { /// already discovered. Because that discovery supplements the process /// `PATH` with well-known install locations (such as `/opt/homebrew/bin` /// on macOS), this lookup can find shells that a plain `PATH` search via - /// [`AvailableShell::try_from`] would miss when Zap is launched outside + /// [`AvailableShell::try_from`] would miss when Zaplex is launched outside /// an interactive shell. /// /// Comparison is case-sensitive on Unix. On Windows, where file names are diff --git a/app/src/terminal/block_list_element.rs b/app/src/terminal/block_list_element.rs index 3cbbf60bca..94e80c9d30 100644 --- a/app/src/terminal/block_list_element.rs +++ b/app/src/terminal/block_list_element.rs @@ -85,12 +85,12 @@ use super::view::{ BlocklistAIRenderContext, InlineBannerId, RichContentMetadata, SeparatorId, SharedSessionBanners, TerminalEditor, TerminalViewRenderContext, BLOCK_BANNER_HEIGHT, }; -use super::warpify::render::{draw_flag_pole, render_subshell_flag}; +use super::zaplexify::render::{draw_flag_pole, render_subshell_flag}; use super::TerminalModel; use super::{heights_approx_eq, HEIGHT_FUDGE_FACTOR_LINES}; use crate::terminal::blockgrid_renderer::BlockGridParams; use crate::terminal::model::terminal_model::BlockIndex; -use crate::terminal::warpify::SubshellSource; +use crate::terminal::zaplexify::SubshellSource; use crate::terminal::model::escape_sequences::{ maybe_kitty_keyboard_escape_sequence, KeystrokeWithDetails, ToEscapeSequence, @@ -710,7 +710,7 @@ pub struct BlockListElement { use_ligature_rendering: bool, /// When true, suppresses cursor rendering for CLI agents when rich input is open. For agents that draw their own cursor (SHOW_CURSOR off), - /// the cursor cell is skipped. For agents that let Zap draw the cursor + /// the cursor cell is skipped. For agents that let Zaplex draw the cursor /// (SHOW_CURSOR on), the `draw_cursor` call and cursor contrast colouring /// are suppressed instead. hide_cursor_cell: bool, @@ -2546,7 +2546,7 @@ impl BlockListElement { } } - // If Zap prompt (non-PS1) is being used, the command is drawn below the prompt, + // If Zaplex prompt (non-PS1) is being used, the command is drawn below the prompt, // hence we account for the prompt's vertical offset. let prompt_vertical_offset_px = if !block.honor_ps1() { cell_size_height * (block.command_padding_top() + block.prompt_height()).as_f64() as f32 @@ -2703,9 +2703,9 @@ impl BlockListElement { if block.is_active_and_long_running() // Check if the "hide cursor" escape sequence is present. && block.is_mode_set(TermMode::SHOW_CURSOR) - // Don't draw the Zap cursor when rich input is hiding + // Don't draw the Zaplex cursor when rich input is hiding // the CLI agent's cursor cell — agents like OpenCode and Codex - // rely on Zap's cursor, so we suppress it here too. + // rely on Zaplex's cursor, so we suppress it here too. && !block_grid_params.grid_render_params.hide_cursor_cell { block.output_grid().draw_cursor( diff --git a/app/src/terminal/blockgrid_renderer.rs b/app/src/terminal/blockgrid_renderer.rs index e531d9090c..4efd196144 100644 --- a/app/src/terminal/blockgrid_renderer.rs +++ b/app/src/terminal/blockgrid_renderer.rs @@ -33,7 +33,7 @@ pub struct GridRenderParams { pub cell_size: Vector2F, pub use_ligature_rendering: bool, /// When true, suppresses cursor rendering for CLI agents when rich input is open. For agents that draw their own cursor (SHOW_CURSOR off), - /// the cursor cell is skipped. For agents that let Zap draw the cursor + /// the cursor cell is skipped. For agents that let Zaplex draw the cursor /// (SHOW_CURSOR on), the `draw_cursor` call and cursor contrast colouring /// are suppressed instead. pub hide_cursor_cell: bool, diff --git a/app/src/terminal/bootstrap.rs b/app/src/terminal/bootstrap.rs index 0ab37b0185..bf680bfacb 100644 --- a/app/src/terminal/bootstrap.rs +++ b/app/src/terminal/bootstrap.rs @@ -13,7 +13,7 @@ use crate::{ #[cfg(feature = "local_fs")] use super::{ model::session::{BootstrapSessionType, SessionInfo}, - warpify::settings::{PIPENV_SUBSHELL_COMMAND_REGEX, POETRY_SUBSHELL_COMMAND_REGEX}, + zaplexify::settings::{PIPENV_SUBSHELL_COMMAND_REGEX, POETRY_SUBSHELL_COMMAND_REGEX}, }; lazy_static! { @@ -27,7 +27,7 @@ lazy_static! { /// errors const BYTE_ORDER_MARK: &str = "\u{FEFF}"; -/// Returns `true` if Zap should use an RC-file based bootstrap (e.g. dump the bootstrap script to +/// Returns `true` if Zaplex should use an RC-file based bootstrap (e.g. dump the bootstrap script to /// a temp file and `source` it) for a newly spawned session with the given `shell_type`, and /// associated `session_type` and `subshell_initialization_info`. /// @@ -81,7 +81,7 @@ pub fn should_use_rc_file_bootstrap_method( && shell_type == ShellType::Zsh) || is_msys2 } - BootstrapSessionType::WarpifiedRemote => false, + BootstrapSessionType::ZaplexifiedRemote => false, } } @@ -215,7 +215,7 @@ pub fn init_subshell_command( Some(shell_type) => { let subshell_script = init_subshell_script_for_shell(shell_type, &crate::ASSETS, vars, ctx); - format!(r#" [ -z $WARP_BOOTSTRAPPED ] && eval '{subshell_script}'"#) + format!(r#" [ -z $ZAPLEX_BOOTSTRAPPED ] && eval '{subshell_script}'"#) } None => init_subshell_script_for_unknown_shell(&crate::ASSETS), } @@ -237,7 +237,7 @@ fn init_subshell_script_for_shell( // Prepend environment variable settings to the script let env_setup_script = format!( - "export WARP_HONOR_PS1={}; {}", + "export ZAPLEX_HONOR_PS1={}; {}", honor_ps1_env_var_value, env_vars .iter() diff --git a/app/src/terminal/cli_agent_sessions/event/mod.rs b/app/src/terminal/cli_agent_sessions/event/mod.rs index fb34729e1f..6bdfe74203 100644 --- a/app/src/terminal/cli_agent_sessions/event/mod.rs +++ b/app/src/terminal/cli_agent_sessions/event/mod.rs @@ -57,8 +57,8 @@ pub struct CLIAgentEvent { #[cfg_attr(not(feature = "local_tty"), allow(dead_code))] const VERSIONED_PARSERS: &[EventParser] = &[v1::parse]; -/// The current CLI agent protocol version this build of Zap supports. -/// Exported as the `WARP_CLI_AGENT_PROTOCOL_VERSION` env var on the PTY +/// The current CLI agent protocol version this build of Zaplex supports. +/// Exported as the `ZAPLEX_CLI_AGENT_PROTOCOL_VERSION` env var on the PTY /// so plugins can negotiate a compatible payload format. #[cfg_attr(not(feature = "local_tty"), allow(dead_code))] pub const fn current_protocol_version() -> u32 { @@ -82,7 +82,7 @@ pub fn parse_event(title: Option<&str>, body: &str) -> Option { None => { log::error!( "Received CLI agent event with unsupported schema version \ - {version}. The CLI agent plugin or Zap may need to be updated." + {version}. The CLI agent plugin or Zaplex may need to be updated." ); None } diff --git a/app/src/terminal/cli_agent_sessions/mod.rs b/app/src/terminal/cli_agent_sessions/mod.rs index 8e876e8b1a..5623e511e4 100644 --- a/app/src/terminal/cli_agent_sessions/mod.rs +++ b/app/src/terminal/cli_agent_sessions/mod.rs @@ -131,7 +131,7 @@ pub struct CLIAgentSession { /// `None` if the plugin predates version reporting or hasn't connected yet. pub plugin_version: Option, /// `None` when the session is local. - /// `Some("user@hostname")` when running over SSH (warpified or legacy). + /// `Some("user@hostname")` when running over SSH (zaplexified or legacy). /// Used as a key for per-host plugin install failure tracking. pub remote_host: Option, /// Draft text saved from the rich input composer when it was closed. diff --git a/app/src/terminal/cli_agent_sessions/plugin_manager/claude.rs b/app/src/terminal/cli_agent_sessions/plugin_manager/claude.rs index d87adefdac..67d6bd4bbc 100644 --- a/app/src/terminal/cli_agent_sessions/plugin_manager/claude.rs +++ b/app/src/terminal/cli_agent_sessions/plugin_manager/claude.rs @@ -118,11 +118,11 @@ impl CliAgentPluginManager for ClaudeCodePluginManager { } fn install_success_message(&self) -> &'static str { - "Zap plugin installed. Please run /reload-plugins to activate." + "Zaplex plugin installed. Please run /reload-plugins to activate." } fn update_success_message(&self) -> &'static str { - "Zap plugin updated. Please run /reload-plugins to activate." + "Zaplex plugin updated. Please run /reload-plugins to activate." } fn install_instructions(&self) -> &'static PluginInstructions { @@ -212,7 +212,7 @@ fn check_installed(claude_dir: &Path) -> bool { .unwrap_or(false) } -/// Reads the installed version string for the Zap plugin, if present. +/// Reads the installed version string for the Zaplex plugin, if present. fn installed_version(claude_dir: &Path) -> Option { let plugins_path = claude_dir.join("plugins").join("installed_plugins.json"); let contents = fs::read_to_string(plugins_path).ok()?; diff --git a/app/src/terminal/cli_agent_sessions/plugin_manager/gemini.rs b/app/src/terminal/cli_agent_sessions/plugin_manager/gemini.rs index c1a3b47379..3bac23afa5 100644 --- a/app/src/terminal/cli_agent_sessions/plugin_manager/gemini.rs +++ b/app/src/terminal/cli_agent_sessions/plugin_manager/gemini.rs @@ -107,11 +107,11 @@ impl CliAgentPluginManager for GeminiPluginManager { } fn install_success_message(&self) -> &'static str { - "Zap plugin installed. Please restart Gemini CLI to activate." + "Zaplex plugin installed. Please restart Gemini CLI to activate." } fn update_success_message(&self) -> &'static str { - "Zap plugin updated. Please restart Gemini CLI to activate." + "Zaplex plugin updated. Please restart Gemini CLI to activate." } fn install_instructions(&self) -> &'static PluginInstructions { @@ -160,7 +160,7 @@ fn check_installed(extensions_dir: &Path) -> bool { serde_json::from_str::(&contents).is_ok() } -/// Reads the installed version string for the Zap extension, if present. +/// Reads the installed version string for the Zaplex extension, if present. fn installed_version(extensions_dir: &Path) -> Option { let manifest_path = extensions_dir .join(EXTENSION_NAME) diff --git a/app/src/terminal/cli_agent_sessions/plugin_manager/gemini_tests.rs b/app/src/terminal/cli_agent_sessions/plugin_manager/gemini_tests.rs index d699d19c65..77b47f4239 100644 --- a/app/src/terminal/cli_agent_sessions/plugin_manager/gemini_tests.rs +++ b/app/src/terminal/cli_agent_sessions/plugin_manager/gemini_tests.rs @@ -41,7 +41,7 @@ fn installed_when_extension_present() { let json = serde_json::json!({ "name": "warp", "version": "1.0.0", - "description": "Zap terminal integration for Gemini CLI" + "description": "Zaplex terminal integration for Gemini CLI" }); fs::write( ext_dir.join("gemini-extension.json"), diff --git a/app/src/terminal/cli_agent_sessions/plugin_manager/mod.rs b/app/src/terminal/cli_agent_sessions/plugin_manager/mod.rs index 394c0c0556..cb86ac9b5e 100644 --- a/app/src/terminal/cli_agent_sessions/plugin_manager/mod.rs +++ b/app/src/terminal/cli_agent_sessions/plugin_manager/mod.rs @@ -137,20 +137,20 @@ pub(crate) async fn run_cli_command_logged( } } -/// Manages the Zap notification plugin for a specific CLI agent. +/// Manages the Zaplex notification plugin for a specific CLI agent. /// /// Each supported CLI agent has its own implementation that knows how to /// check installation state and perform install/update operations. #[async_trait] pub(crate) trait CliAgentPluginManager: Send + Sync { - /// The minimum plugin version required by this Zap build. + /// The minimum plugin version required by this Zaplex build. fn minimum_plugin_version(&self) -> &'static str; /// Whether this agent supports one-click auto-install/update. /// When `false`, the footer always opens the manual instructions modal. fn can_auto_install(&self) -> bool; - /// Whether the Zap notification plugin is installed. + /// Whether the Zaplex notification plugin is installed. /// Default returns `false` (no filesystem check). fn is_installed(&self) -> bool { false @@ -162,7 +162,7 @@ pub(crate) trait CliAgentPluginManager: Send + Sync { false } - /// Install the Zap notification plugin. + /// Install the Zaplex notification plugin. /// Default returns an error — only agents with `can_auto_install() == true` should override. async fn install(&self) -> Result<(), PluginInstallError> { Err(PluginInstallError { @@ -171,7 +171,7 @@ pub(crate) trait CliAgentPluginManager: Send + Sync { }) } - /// Update the Zap notification plugin to the latest version. + /// Update the Zaplex notification plugin to the latest version. /// Default returns an error — only agents with `can_auto_install() == true` should override. async fn update(&self) -> Result<(), PluginInstallError> { Err(PluginInstallError { @@ -182,12 +182,12 @@ pub(crate) trait CliAgentPluginManager: Send + Sync { /// Toast message shown after a successful auto-install. fn install_success_message(&self) -> &'static str { - "Zap plugin installed. Please restart the session to activate." + "Zaplex plugin installed. Please restart the session to activate." } /// Toast message shown after a successful auto-update. fn update_success_message(&self) -> &'static str { - "Zap plugin updated. Please restart the session to activate." + "Zaplex plugin updated. Please restart the session to activate." } /// Manual installation instructions for the modal UI. @@ -204,12 +204,12 @@ pub(crate) trait CliAgentPluginManager: Send + Sync { } /// Returns a plugin manager for the given CLI agent, or `None` if the agent -/// doesn't have Zap notification plugin support. +/// doesn't have Zaplex notification plugin support. pub(crate) fn plugin_manager_for(agent: CLIAgent) -> Option> { plugin_manager_for_with_shell(agent, None, None, None) } /// Returns a plugin manager for the given CLI agent, or `None` if the agent -/// doesn't have Zap notification plugin support. +/// doesn't have Zaplex notification plugin support. /// /// When a shell path and type are provided, plugin commands run through that shell. /// When `path_env_var` is provided, it is set as the PATH for plugin commands diff --git a/app/src/terminal/daemon_tty/event_loop.rs b/app/src/terminal/daemon_tty/event_loop.rs new file mode 100644 index 0000000000..ef222fc83a --- /dev/null +++ b/app/src/terminal/daemon_tty/event_loop.rs @@ -0,0 +1,882 @@ +use crate::remote_server::manager::{RemoteServerManager, RemoteServerManagerEvent}; +use crate::terminal::{ + event_listener::ChannelEventListener, model::ansi::Processor, + writeable_pty::Message as EventLoopMessage, SizeInfo, TerminalModel, +}; +use async_channel::Receiver; +use parking_lot::FairMutex; +use remote_server::client::RemoteServerClient; +use std::io; +use std::sync::Arc; +use warp_core::SessionId; +use warpui::{Entity, ModelContext, SingletonEntity}; + +use super::terminal_manager::OpenSessionParams; + +/// Cap on input buffered while the transport is down. A reconnect window is +/// normally seconds, but a laptop can sleep for hours — without a bound, typed +/// input / pastes would grow this `Vec` unboundedly. 256 KiB is far more than any +/// realistic burst of keystrokes; past it we drop the oldest input (a terminal +/// tolerates lost keystrokes far better than unbounded memory). +const MAX_PENDING_INPUT_BYTES: usize = 256 * 1024; + +/// Safety valve for output buffered before `OpenSession` resolves (the daemon +/// auto-attaches and starts the shell/bootstrap before the response reaches us). +/// The open window is normally sub-second, so this is far more than any bootstrap +/// burst; past it we stop buffering (the early bootstrap prefix is preserved) +/// rather than grow without bound if an open hangs on a chatty session. +const MAX_PENDING_OUTPUT_BYTES: usize = 1024 * 1024; + +/// Drives a terminal backed by a *daemon-hosted* PTY session. +/// +/// Unlike [`crate::terminal::remote_tty`]'s event loop, which speaks the +/// websocket SSH-proxy protocol, this one is transport-agnostic: live PTY +/// output arrives as [`RemoteServerManagerEvent::SessionOutput`] pushes from the +/// remote-server protocol, and input/resize are routed back through the live +/// [`RemoteServerClient`]. This is what lets a session survive a transport drop +/// — the daemon owns the PTY and the replay buffer; the client is just an +/// attached view. +/// +/// The daemon is responsible for bootstrapping the shell (Zaplexify init) when it +/// spawns the PTY, so — unlike the websocket path — this event loop never writes +/// a bootstrap script itself. Keeping bootstrap server-side is what makes a +/// later reattach clean: it must happen exactly once, not on every client +/// connection. +pub(super) struct EventLoop { + terminal_model: Arc>, + parser: Processor, + channel_event_listener: ChannelEventListener, + /// The manager/connection session used to resolve the live client. + connection_session_id: SessionId, + /// The daemon's PTY session id (from `OpenSession`). `None` until the open + /// request resolves; until then input is buffered in `pending_input`. + pty_session_id: Option, + /// Input/resize messages received before the session id is known. Flushed, + /// in order, once `OpenSession` resolves. + pending_input: Vec, + /// Output `(pty_session_id, seq, bytes)` pushed for our connection before the + /// `OpenSession` response arrives (the daemon auto-attaches and starts the + /// shell immediately). Rendered, in order, in `on_session_opened`, so the + /// initial shell/bootstrap output isn't lost on a fresh tab. + pending_output: Vec<(String, u64, Vec)>, + /// The `OpenSession` request, held until the transport is `Connected`. Taken + /// (once) by `try_open`. `None` after the session has been opened. + pending_open: Option<(OpenSessionParams, SizeInfo)>, + /// The host's startup command, captured from `OpenSessionParams` and run once + /// (taken) when the session opens — the daemon-path analog of the local-PTY + /// SSH startup-command injector. `None` for adopted sessions. + startup_command: Option, + /// Byte offset just past the last `SessionOutput` byte we've rendered. Sent + /// as `last_seq` on re-attach so the daemon replays only what we missed. + last_seq: u64, +} + +impl EventLoop { + /// Starts the event loop: subscribes to live output, begins draining + /// input, and opens the daemon-hosted session. + pub(super) fn start( + model: Arc>, + event_loop_rx: Receiver, + channel_event_listener: ChannelEventListener, + size_info: SizeInfo, + connection_session_id: SessionId, + open_params: OpenSessionParams, + adopt_pty_session_id: Option, + ctx: &mut ModelContext, + ) -> Self { + let mut event_loop = Self::new(model, channel_event_listener, connection_session_id); + match adopt_pty_session_id { + // Adopt an existing daemon session: attach + replay on connect. + Some(id) => event_loop.pty_session_id = Some(id), + // Open a fresh session once the transport is connected. + None => event_loop.pending_open = Some((open_params, size_info)), + } + + // Output path: live PTY bytes arrive as manager pushes. Filter to our + // own daemon session and feed them through the ANSI processor. The + // connect-state arms gate `OpenSession` on the transport being ready. + let manager = RemoteServerManager::handle(ctx); + ctx.subscribe_to_model(&manager, |me, event, ctx| match event { + RemoteServerManagerEvent::SessionOutput { + session_id, + pty_session_id, + seq, + bytes, + .. + } => { + if me.is_our_session(pty_session_id) { + me.process_pty_bytes(bytes); + me.last_seq = *seq + bytes.len() as u64; + } else if me.pty_session_id.is_none() && *session_id == me.connection_session_id { + // Output for our connection before `OpenSession` resolved — the + // daemon auto-attaches and starts the shell/bootstrap before the + // response reaches us. Buffer it (drained in `on_session_opened`) + // so the initial output isn't lost; stop past the cap so a hung + // open can't grow this without bound. + let buffered: usize = me.pending_output.iter().map(|(_, _, b)| b.len()).sum(); + if buffered < MAX_PENDING_OUTPUT_BYTES { + me.pending_output + .push((pty_session_id.clone(), *seq, bytes.clone())); + } + } + } + RemoteServerManagerEvent::SessionExited { + pty_session_id, + exit_code, + .. + } if me.is_our_session(pty_session_id) => { + me.on_session_exited(*exit_code); + } + RemoteServerManagerEvent::SessionConnected { session_id, .. } + if *session_id == me.connection_session_id => + { + me.on_transport_connected(ctx); + } + // Transport reconnected (SSH blip): the daemon session kept running — + // re-attach and replay what we missed (§9). + RemoteServerManagerEvent::SessionReconnected { session_id, .. } + if *session_id == me.connection_session_id => + { + me.reattach(ctx); + } + RemoteServerManagerEvent::SessionConnectionFailed { + session_id, + phase, + error, + } if *session_id == me.connection_session_id => + { + me.on_connect_failed(&format!("{phase:?}"), error); + } + _ => {} + }); + + // Input path: drain the channel with `ctx` access so resizes and + // keystrokes can be routed to the live client. + ctx.spawn_stream_local(event_loop_rx, Self::on_event_loop_message, |_, _| ()); + + // If the transport is already connected, act now (open or adopt); + // otherwise the `SessionConnected` arm above does it once it connects. + event_loop.on_transport_connected(ctx); + + event_loop + } + + /// On (initial) transport connect: open a fresh session if one is pending, + /// otherwise attach to the adopted session id. + fn on_transport_connected(&mut self, ctx: &mut ModelContext) { + if self.pending_open.is_some() { + self.try_open(ctx); + } else if self.pty_session_id.is_some() { + self.reattach(ctx); + } + } + + fn new( + terminal_model: Arc>, + channel_event_listener: ChannelEventListener, + connection_session_id: SessionId, + ) -> Self { + Self { + terminal_model, + parser: Processor::default(), + channel_event_listener, + connection_session_id, + pty_session_id: None, + pending_input: Vec::new(), + pending_output: Vec::new(), + pending_open: None, + startup_command: None, + last_seq: 0, + } + } + + /// On transport reconnect: re-attach to the still-running daemon session and + /// replay everything produced while we were gone, reconstructing the grid. + /// Falls back to opening the session if it was never opened (reconnect raced + /// the initial open). + fn reattach(&mut self, ctx: &mut ModelContext) { + let Some(pty_session_id) = self.pty_session_id.clone() else { + self.try_open(ctx); + return; + }; + let Some(client) = self.client(ctx) else { + return; // The reconnected client isn't registered yet. + }; + let last_seq = self.last_seq; + log::info!("daemon_tty: re-attaching pty_session_id={pty_session_id} from seq {last_seq}"); + let future = async move { client.attach_session(pty_session_id, last_seq).await }; + ctx.spawn(future, |me, result, ctx| match result { + Ok(attached) => { + // If the daemon's ring evicted output we never saw, the replay + // starts past our cursor (base_seq > last_seq) — a hole. Applying + // post-gap bytes (which may omit the clears/cursor moves that were + // evicted) onto the stale grid corrupts it, so reset the screen + // first and tell the user scrollback was truncated. + if attached.base_seq > me.last_seq { + me.process_pty_bytes(b"\x1b[H\x1b[2J\x1b[3J"); + me.write_notice("scrollback truncated during a long disconnect"); + } + if !attached.replay.is_empty() { + me.process_pty_bytes(&attached.replay); + } + me.last_seq = attached.base_seq + attached.replay.len() as u64; + // Transport is back and we're re-attached — flush input buffered + // during the outage so keystrokes/resizes aren't lost (§9). + me.flush_pending_input(ctx); + } + Err(err) => { + // Surface attach failure (e.g. the session exited in the race + // between listing and adopting) instead of a blank tab. + log::error!("Failed to re-attach daemon session: {err:?}"); + me.write_notice(&format!("could not re-attach session: {err}")); + } + }); + } + + fn is_our_session(&self, pty_session_id: &str) -> bool { + self.pty_session_id.as_deref() == Some(pty_session_id) + } + + /// Resolves the live client for this session from the manager, if any. + fn client(&self, ctx: &mut ModelContext) -> Option> { + let session_id = self.connection_session_id; + let manager = RemoteServerManager::handle(ctx); + manager.read(ctx, |manager, _ctx| { + manager.client_for_session(session_id).cloned() + }) + } + + /// Opens the daemon session if the transport is connected and a pending + /// request is still outstanding. Idempotent: a no-op once opened, and a + /// no-op (leaving the request pending) while the transport is not yet + /// connected — the `SessionConnected` arm calls this again when it is. + fn try_open(&mut self, ctx: &mut ModelContext) { + if self.pty_session_id.is_some() || self.pending_open.is_none() { + return; + } + let Some(client) = self.client(ctx) else { + return; // Not connected yet; wait for `SessionConnected`. + }; + let (open_params, size_info) = self + .pending_open + .take() + .expect("pending_open is Some (checked above)"); + self.open_session(client, open_params, size_info, ctx); + } + + /// Issues the `OpenSession` request over a connected client. The initial + /// size is taken from the terminal model so the daemon-side PTY matches + /// what the user sees. + fn open_session( + &mut self, + client: Arc, + open_params: OpenSessionParams, + size_info: SizeInfo, + ctx: &mut ModelContext, + ) { + let OpenSessionParams { + cwd, + shell, + env, + ring_ceiling_bytes, + startup_command, + } = open_params; + // Run once when the session opens (see `on_session_opened`). + self.startup_command = startup_command; + let rows = size_info.rows as u32; + let cols = size_info.columns as u32; + log::info!("daemon_tty: issuing OpenSession (cwd={cwd:?}, shell={shell:?}, {rows}x{cols}, ring_ceiling={ring_ceiling_bytes:?})"); + let future = + async move { client.open_session(cwd, shell, env, rows, cols, ring_ceiling_bytes).await }; + ctx.spawn(future, |me, result, ctx| match result { + Ok(opened) => me.on_session_opened(opened.session_id, ctx), + Err(err) => { + // The transport is up (so the connect-failure path never fired), + // but the daemon refused to open the session (bad cwd, unspawnable + // shell, fd exhaustion, …). Surface it instead of leaving a blank, + // hung tab; drop the pending open so a later event can't reopen it. + log::error!("daemon_tty: OpenSession failed: {err:?}"); + me.write_notice(&format!("could not start session: {err}")); + me.pending_open = None; + } + }); + } + + fn on_connect_failed(&mut self, phase: &str, error: &str) { + log::error!( + "daemon connect failed for {:?} at {phase}: {error}", + self.connection_session_id + ); + // Surface the failure in the tab so the user sees *why* instead of a + // blank/hung view (the connection never produced any PTY output). + self.write_notice(&format!("connection failed ({phase}): {error}")); + // Drop the pending open so a later spurious event can't reopen it. + self.pending_open = None; + } + + fn on_session_opened(&mut self, pty_session_id: String, ctx: &mut ModelContext) { + log::info!("daemon_tty: session opened, pty_session_id={pty_session_id}"); + self.pty_session_id = Some(pty_session_id.clone()); + // Render output the daemon produced before this response arrived (it + // auto-attaches and starts the shell immediately), so the initial + // shell/bootstrap output isn't missing from a fresh tab. In seq order. + let pending = std::mem::take(&mut self.pending_output); + for (pty, seq, bytes) in pending { + if pty == pty_session_id { + self.process_pty_bytes(&bytes); + self.last_seq = seq + bytes.len() as u64; + } + } + // Run the host's startup command once, after the session is open — the + // daemon-path analog of the local-PTY SSH startup-command injector. Sent + // as input + newline (bash/zsh execute byte), the same way the daemon + // injects its own bootstrap. `take()` ensures it never re-runs on reattach. + if let Some(cmd) = self.startup_command.take() { + if !cmd.is_empty() { + let mut bytes = cmd.into_bytes(); + bytes.push(b'\n'); + self.dispatch_message( + &pty_session_id, + EventLoopMessage::Input(std::borrow::Cow::Owned(bytes)), + ctx, + ); + } + } + // Flush any input that arrived before the session was addressable. + self.flush_pending_input(ctx); + } + + /// Flush input buffered while the session wasn't addressable — either before + /// the first open (pre-`pty_session_id`) or while the transport was down + /// mid-session (the reconnect window). A no-op when nothing is pending or no + /// session id exists yet. Any message whose client is *still* unavailable is + /// re-buffered by `dispatch_message`, so it survives until the next flush. + fn flush_pending_input(&mut self, ctx: &mut ModelContext) { + let Some(pty_session_id) = self.pty_session_id.clone() else { + return; + }; + let pending = std::mem::take(&mut self.pending_input); + for message in pending { + self.dispatch_message(&pty_session_id, message, ctx); + } + } + + fn on_event_loop_message(&mut self, message: EventLoopMessage, ctx: &mut ModelContext) { + match self.pty_session_id.clone() { + Some(pty_session_id) => self.dispatch_message(&pty_session_id, message, ctx), + None => self.buffer_pending(message), + } + } + + /// Buffer an input/resize while the session isn't addressable (pre-open or + /// transport down). Coalesces resizes (only the latest matters) and bounds the + /// buffered input bytes, dropping the oldest input past the cap so a long + /// outage can't grow `pending_input` without limit. + fn buffer_pending(&mut self, message: EventLoopMessage) { + if matches!(message, EventLoopMessage::Resize(_)) { + // Intermediate window sizes are irrelevant — keep only the latest. + self.pending_input + .retain(|m| !matches!(m, EventLoopMessage::Resize(_))); + } + self.pending_input.push(message); + + let mut total: usize = self + .pending_input + .iter() + .map(|m| match m { + EventLoopMessage::Input(b) => b.len(), + _ => 0, + }) + .sum(); + if total > MAX_PENDING_INPUT_BYTES { + log::warn!( + "daemon_tty: buffered input exceeded {MAX_PENDING_INPUT_BYTES} bytes during an \ + outage — dropping oldest input" + ); + let mut i = 0; + while total > MAX_PENDING_INPUT_BYTES && i < self.pending_input.len() { + if let EventLoopMessage::Input(b) = &self.pending_input[i] { + total -= b.len(); + self.pending_input.remove(i); + } else { + i += 1; + } + } + } + } + + fn dispatch_message( + &mut self, + pty_session_id: &str, + message: EventLoopMessage, + ctx: &mut ModelContext, + ) { + let Some(client) = self.client(ctx) else { + // Transport is down (e.g. an SSH blip mid-session). Buffer instead of + // dropping so keystrokes/resizes survive the outage — `reattach` + // flushes them once the transport reconnects (§9 resilience). This is + // the whole point of the native session layer: a drop must not lose + // input that was typed during the gap. + log::debug!( + "daemon_tty: buffering {message:?} for {pty_session_id} (transport down, will flush on reattach)" + ); + self.buffer_pending(message); + return; + }; + let result = match message { + EventLoopMessage::Input(bytes) => { + client.send_session_input(pty_session_id.to_string(), bytes.into_owned()) + } + EventLoopMessage::Resize(size_info) => client.send_resize_session( + pty_session_id.to_string(), + size_info.rows as u32, + size_info.columns as u32, + ), + // The daemon owns the PTY lifecycle; a client-side shutdown simply + // detaches this view — the session keeps running for reattachment. + EventLoopMessage::Shutdown | EventLoopMessage::ChildExited => { + client.send_detach_session(pty_session_id.to_string()) + } + }; + if let Err(err) = result { + log::error!("Failed to send message to daemon session {pty_session_id}: {err:?}"); + } + } + + fn on_session_exited(&mut self, exit_code: Option) { + log::info!( + "Daemon session {:?} exited (code {exit_code:?})", + self.pty_session_id + ); + let notice = match exit_code { + Some(code) => format!("session ended (exit code {code})"), + None => "session ended".to_string(), + }; + self.write_notice(¬ice); + } + + /// Writes a Zaplex notice line (e.g. a connection error or session-ended + /// message) into the terminal via the normal ANSI path, so the user sees it + /// in the tab rather than a blank/hung view. Rendered in bold red. + fn write_notice(&mut self, text: &str) { + let line = format!("\r\n\x1b[1;31m[zaplex] {text}\x1b[0m\r\n"); + self.process_pty_bytes(line.as_bytes()); + } + + /// Processes a byte slice through the [`Processor`], identical to the + /// local- and remote-PTY paths. + fn process_pty_bytes(&mut self, bytes: &[u8]) { + let mut terminal_model = self.terminal_model.lock(); + self.parser + .parse_bytes(&mut *terminal_model, bytes, &mut io::sink()); + self.channel_event_listener.send_wakeup_event(); + } +} + +impl Entity for EventLoop { + type Event = (); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::borrow::Cow; + use warp_core::HostId; + use warpui::{App, ModelHandle}; + + const OUR_PTY: &str = "pty-ours"; + const HOST: &str = "test-host"; + + /// A [`ChannelEventListener`] whose wakeup channel we keep. `process_pty_bytes` + /// fires a wakeup *after* feeding the bytes through the ANSI processor into the + /// terminal model, so an observed wakeup proves the output reached the parser + /// and model for our session (the shared parser's rendering itself is covered + /// by the terminal-model / ANSI tests — here we test daemon-session routing). + fn test_listener() -> (ChannelEventListener, async_channel::Receiver<()>) { + let (wakeups_tx, wakeups_rx) = async_channel::unbounded(); + let (events_tx, _events_rx) = async_channel::unbounded(); + let (pty_reads_tx, _pty_reads_rx) = async_broadcast::broadcast(1); + ( + ChannelEventListener::new(wakeups_tx, events_tx, pty_reads_tx), + wakeups_rx, + ) + } + + fn output_event( + conn: SessionId, + pty: &str, + seq: u64, + bytes: &[u8], + ) -> RemoteServerManagerEvent { + RemoteServerManagerEvent::SessionOutput { + session_id: conn, + host_id: HostId::new(HOST.to_string()), + pty_session_id: pty.to_string(), + seq, + bytes: bytes.to_vec(), + } + } + + fn drain(rx: &async_channel::Receiver) { + while rx.try_recv().is_ok() {} + } + + /// Starts an EventLoop that has *adopted* `OUR_PTY` (so it is immediately + /// addressable without a connected client to open) on a real + /// `RemoteServerManager` singleton. The manager is what the loop subscribes + /// to for live `SessionOutput`, so emitting from it drives the real path. + fn start_adopted_loop( + app: &mut App, + conn: SessionId, + ) -> ( + ModelHandle, + ModelHandle, + Arc>, + async_channel::Receiver<()>, + ) { + let manager = app.add_singleton_model(RemoteServerManager::new); + let (listener, wakeups_rx) = test_listener(); + let model = Arc::new(FairMutex::new(TerminalModel::mock(None, Some(listener.clone())))); + // The input stream isn't exercised here; dropping the sender just closes it. + let (_event_loop_tx, event_loop_rx) = async_channel::unbounded::(); + let size = SizeInfo::new_without_font_metrics(24, 80); + let model_for_loop = model.clone(); + let event_loop = app.add_model(|ctx| { + EventLoop::start( + model_for_loop, + event_loop_rx, + listener, + size, + conn, + OpenSessionParams::default(), + Some(OUR_PTY.to_string()), + ctx, + ) + }); + (manager, event_loop, model, wakeups_rx) + } + + /// The core client-side output path: a live `SessionOutput` push for our + /// daemon session is fed to the terminal (proven by the repaint wakeup) and + /// advances `last_seq` (= seq + len, the replay cursor) — while a push for a + /// *different* `pty_session_id` on the same connection is ignored. + #[test] + fn session_output_routes_to_terminal_and_filters_by_pty() { + App::test((), |mut app| async move { + let conn = SessionId::from(7u64); + let (manager, event_loop, _model, wakeups_rx) = start_adopted_loop(&mut app, conn); + + // Delivery is synchronous: `ctx.emit` queues an effect that + // `flush_effects` dispatches to subscribers before `update` returns. + manager.update(&mut app, |_m, ctx| { + ctx.emit(output_event(conn, OUR_PTY, 0, b"hello-daemon")); + }); + + assert!( + !wakeups_rx.is_empty(), + "our SessionOutput must reach the parser/model and request a repaint" + ); + assert_eq!( + event_loop.read(&app, |me, _| me.last_seq), + b"hello-daemon".len() as u64, + "last_seq must advance to seq + bytes.len() (the replay cursor)" + ); + + // A push for another session on the same connection is filtered out. + drain(&wakeups_rx); + manager.update(&mut app, |_m, ctx| { + ctx.emit(output_event(conn, "pty-someone-else", 999, b"NOT-OURS")); + }); + assert!( + wakeups_rx.is_empty(), + "output for a foreign pty_session_id must not reach our terminal" + ); + assert_eq!( + event_loop.read(&app, |me, _| me.last_seq), + b"hello-daemon".len() as u64, + "foreign output must not advance our last_seq" + ); + + // A contiguous follow-up chunk for our session advances the cursor by + // its own length from the new seq. + manager.update(&mut app, |_m, ctx| { + ctx.emit(output_event(conn, OUR_PTY, 12, b"-more")); + }); + assert_eq!( + event_loop.read(&app, |me, _| me.last_seq), + (b"hello-daemon".len() + b"-more".len()) as u64, + "last_seq tracks the latest seq + len" + ); + }); + } + + /// Keystrokes that arrive before `OpenSession` resolves are buffered in order + /// so nothing typed during the connect window is lost. On open we attempt to + /// flush; with no live client the input is *retained* (re-buffered), never + /// dropped — it flushes for real once a client is available. + #[test] + fn input_before_session_open_is_buffered_and_not_lost() { + App::test((), |mut app| async move { + // Held for the duration so the singleton stays registered. + let _manager = app.add_singleton_model(RemoteServerManager::new); + let conn = SessionId::from(9u64); + let (listener, _wakeups_rx) = test_listener(); + let model = Arc::new(FairMutex::new(TerminalModel::mock(None, Some(listener.clone())))); + let (_event_loop_tx, event_loop_rx) = async_channel::unbounded::(); + let size = SizeInfo::new_without_font_metrics(24, 80); + let model_for_loop = model.clone(); + // `None` = open a fresh session; with no connected client it never + // resolves, so `pty_session_id` stays `None` and input must buffer. + let event_loop = app.add_model(|ctx| { + EventLoop::start( + model_for_loop, + event_loop_rx, + listener, + size, + conn, + OpenSessionParams::default(), + None, + ctx, + ) + }); + + event_loop.update(&mut app, |me, ctx| { + me.on_event_loop_message(EventLoopMessage::Input(Cow::Owned(b"a".to_vec())), ctx); + me.on_event_loop_message(EventLoopMessage::Input(Cow::Owned(b"b".to_vec())), ctx); + }); + event_loop.read(&app, |me, _| { + assert!(me.pty_session_id.is_none(), "session not opened yet"); + assert_eq!(me.pending_input.len(), 2, "input must be buffered before open"); + }); + + // Opening records the id and attempts to flush. With no live client + // the input can't be sent yet, so it must be *retained* (re-buffered), + // not dropped — preserving the no-loss guarantee until a client exists. + event_loop.update(&mut app, |me, ctx| { + me.on_session_opened("pty-late".to_string(), ctx); + }); + event_loop.read(&app, |me, _| { + assert_eq!(me.pty_session_id.as_deref(), Some("pty-late")); + assert_eq!( + me.pending_input.len(), + 2, + "without a live client the flushed input must be retained, not lost" + ); + }); + }); + } + + /// Regression (§9 resilience): once a session is open, input that arrives + /// while the transport is down (the reconnect window) must be buffered, not + /// dropped — otherwise keystrokes typed during an SSH blip are lost. The + /// adopted loop has a `pty_session_id` but no registered client, which is + /// exactly the "session open, transport down" state. + #[test] + fn input_during_transport_outage_is_buffered_not_dropped() { + App::test((), |mut app| async move { + let conn = SessionId::from(13u64); + let (_manager, event_loop, _model, _wakeups_rx) = start_adopted_loop(&mut app, conn); + + event_loop.read(&app, |me, _| { + assert_eq!( + me.pty_session_id.as_deref(), + Some(OUR_PTY), + "adopted loop is open (has a pty id) but has no live client" + ); + }); + + // Session is open, transport is down (no client): input must buffer. + event_loop.update(&mut app, |me, ctx| { + me.on_event_loop_message(EventLoopMessage::Input(Cow::Owned(b"x".to_vec())), ctx); + me.on_event_loop_message( + EventLoopMessage::Resize(SizeInfo::new_without_font_metrics(40, 100)), + ctx, + ); + }); + event_loop.read(&app, |me, _| { + assert_eq!( + me.pending_input.len(), + 2, + "input during the outage must be buffered (flushed on reattach), not dropped" + ); + }); + }); + } + + /// The host's startup command runs once when the session opens — the + /// daemon-path analog of the local-PTY SSH startup-command injector. With no + /// live client the queued command lands in `pending_input` (sent for real on + /// reattach), so we assert it was queued as `command + "\n"`. + #[test] + fn startup_command_is_queued_as_input_on_open() { + App::test((), |mut app| async move { + let _manager = app.add_singleton_model(RemoteServerManager::new); + let conn = SessionId::from(17u64); + let (listener, _wakeups_rx) = test_listener(); + let model = Arc::new(FairMutex::new(TerminalModel::mock(None, Some(listener.clone())))); + let (_event_loop_tx, event_loop_rx) = async_channel::unbounded::(); + let size = SizeInfo::new_without_font_metrics(24, 80); + let model_for_loop = model.clone(); + let event_loop = app.add_model(|ctx| { + EventLoop::start( + model_for_loop, + event_loop_rx, + listener, + size, + conn, + OpenSessionParams::default(), + None, + ctx, + ) + }); + + event_loop.update(&mut app, |me, ctx| { + me.startup_command = Some("tmux attach".to_string()); + me.on_session_opened("pty-x".to_string(), ctx); + }); + + event_loop.read(&app, |me, _| { + assert!(me.startup_command.is_none(), "startup command must be taken (run once)"); + assert_eq!(me.pending_input.len(), 1, "startup command queued as input"); + match &me.pending_input[0] { + EventLoopMessage::Input(bytes) => { + assert_eq!(&**bytes, b"tmux attach\n", "command + execute newline"); + } + other => panic!("expected Input, got {other:?}"), + } + }); + }); + } + + /// During a long outage the buffered input must stay bounded: consecutive + /// resizes coalesce to the latest, and input past the byte cap drops oldest- + /// first — so a sleeping laptop can't grow `pending_input` without limit. + #[test] + fn buffered_input_is_capped_and_resizes_coalesce() { + App::test((), |mut app| async move { + let conn = SessionId::from(19u64); + // Adopted loop: pty id set, no live client → everything buffers. + let (_manager, event_loop, _model, _wakeups_rx) = start_adopted_loop(&mut app, conn); + + event_loop.update(&mut app, |me, ctx| { + me.on_event_loop_message( + EventLoopMessage::Resize(SizeInfo::new_without_font_metrics(20, 60)), + ctx, + ); + me.on_event_loop_message( + EventLoopMessage::Resize(SizeInfo::new_without_font_metrics(30, 90)), + ctx, + ); + // 5 x 100 KiB = 500 KiB of input, over the 256 KiB cap. + for _ in 0..5 { + me.on_event_loop_message( + EventLoopMessage::Input(Cow::Owned(vec![b'x'; 100 * 1024])), + ctx, + ); + } + }); + + event_loop.read(&app, |me, _| { + let resizes = me + .pending_input + .iter() + .filter(|m| matches!(m, EventLoopMessage::Resize(_))) + .count(); + assert_eq!(resizes, 1, "consecutive resizes coalesce to the latest"); + let input_bytes: usize = me + .pending_input + .iter() + .map(|m| match m { + EventLoopMessage::Input(b) => b.len(), + _ => 0, + }) + .sum(); + assert!( + input_bytes <= MAX_PENDING_INPUT_BYTES, + "buffered input must be capped (was {input_bytes})" + ); + }); + }); + } + + /// Output the daemon pushes before `OpenSession` resolves (it auto-attaches + /// and starts the shell immediately) must not be lost: it is buffered while + /// the pty id is unknown, then rendered when `on_session_opened` records the id. + #[test] + fn output_before_open_is_buffered_then_rendered() { + App::test((), |mut app| async move { + let manager = app.add_singleton_model(RemoteServerManager::new); + let conn = SessionId::from(23u64); + let (listener, wakeups_rx) = test_listener(); + let model = Arc::new(FairMutex::new(TerminalModel::mock(None, Some(listener.clone())))); + let (_event_loop_tx, event_loop_rx) = async_channel::unbounded::(); + let size = SizeInfo::new_without_font_metrics(24, 80); + let model_for_loop = model.clone(); + // Fresh open (adopt = None): with no live client the open never + // resolves, so pty_session_id stays None and output must buffer. + let event_loop = app.add_model(|ctx| { + EventLoop::start( + model_for_loop, + event_loop_rx, + listener, + size, + conn, + OpenSessionParams::default(), + None, + ctx, + ) + }); + + // Daemon pushes output for our connection before OpenSession resolves. + manager.update(&mut app, |_m, ctx| { + ctx.emit(output_event(conn, "pty-late", 0, b"BOOT")); + }); + event_loop.read(&app, |me, _| { + assert!(me.pty_session_id.is_none(), "not opened yet"); + assert_eq!( + me.pending_output.len(), + 1, + "pre-open output must be buffered, not dropped" + ); + }); + + // Opening renders the buffered output (proven by the repaint wakeup), + // advances last_seq, and clears the buffer. + drain(&wakeups_rx); + event_loop.update(&mut app, |me, ctx| { + me.on_session_opened("pty-late".to_string(), ctx) + }); + assert!( + !wakeups_rx.is_empty(), + "buffered pre-open output must be rendered on open" + ); + event_loop.read(&app, |me, _| { + assert!(me.pending_output.is_empty(), "buffer drained on open"); + assert_eq!( + me.last_seq, + b"BOOT".len() as u64, + "last_seq advances past the replayed pre-open output" + ); + }); + }); + } + + /// A connect failure must surface in the tab — `on_connect_failed` renders a + /// notice through the terminal (so the user sees *why* instead of a blank / + /// hung view), which requests a repaint. + #[test] + fn connect_failure_writes_a_visible_notice() { + App::test((), |mut app| async move { + let conn = SessionId::from(11u64); + let (_manager, event_loop, _model, wakeups_rx) = start_adopted_loop(&mut app, conn); + drain(&wakeups_rx); + event_loop.update(&mut app, |me, _| { + me.on_connect_failed("Connect", "ssh: connect timed out") + }); + assert!( + !wakeups_rx.is_empty(), + "a connect failure must render a notice and request a repaint" + ); + }); + } +} diff --git a/app/src/terminal/daemon_tty/mod.rs b/app/src/terminal/daemon_tty/mod.rs new file mode 100644 index 0000000000..4d14b052a2 --- /dev/null +++ b/app/src/terminal/daemon_tty/mod.rs @@ -0,0 +1,17 @@ +//! Terminal backed by a *daemon-hosted* PTY session. +//! +//! This is the client side of the native persistent remote-session layer: the +//! remote daemon owns the PTY and an output replay buffer, and streams its +//! output over the remote-server protocol. The client is an attached *view* of +//! that session, so the session survives an SSH/transport drop and can be +//! reattached. +//! +//! It is deliberately a sibling of [`super::remote_tty`] rather than a fork of +//! it: both reuse the same shared terminal-manager helpers, and only the +//! transport (the [`event_loop`]) differs. [`super::local_tty`] (localhost and +//! plain-vanilla SSH) stays the default and is untouched by this module. + +mod event_loop; +mod terminal_manager; + +pub use terminal_manager::{DaemonSessionRequest, OpenSessionParams, TerminalManager}; diff --git a/app/src/terminal/daemon_tty/terminal_manager.rs b/app/src/terminal/daemon_tty/terminal_manager.rs new file mode 100644 index 0000000000..1845deef3e --- /dev/null +++ b/app/src/terminal/daemon_tty/terminal_manager.rs @@ -0,0 +1,268 @@ +use crate::ai::blocklist::InputConfig; +use crate::context_chips::prompt_type::PromptType; +use crate::pane_group::TerminalViewResources; +use crate::persistence::ModelEvent; +use crate::terminal::daemon_tty::event_loop::EventLoop; +use crate::terminal::event_listener::ChannelEventListener; +use crate::terminal::model::session::Sessions; +use crate::terminal::model_events::ModelEventDispatcher; +use crate::terminal::shell::{ShellName, ShellType}; +use crate::terminal::writeable_pty::terminal_manager_util::{ + init_pty_controller_model, wire_up_pty_controller_with_view, +}; +use crate::terminal::writeable_pty::{self, Message}; +use crate::terminal::{terminal_manager, ShellLaunchState, SizeInfo, TerminalModel, TerminalView}; +use async_channel::{Receiver, Sender}; +use parking_lot::FairMutex; +use pathfinder_geometry::vector::Vector2F; +use std::any::Any; +use std::collections::HashMap; +use std::sync::mpsc::SyncSender; +use std::sync::Arc; +use warp_core::SessionId; +use warpui::{AppContext, ModelHandle, SingletonEntity, ViewHandle, WindowId}; + +// Reuses the same `EventLoopSender` impl for `Sender` defined by +// `remote_tty`; do not re-implement it here (it would violate coherence). +type PtyController = writeable_pty::PtyController>; + +/// Parameters for opening a fresh daemon-hosted session. The initial size is +/// derived from the terminal model, so it is intentionally not included here. +#[derive(Clone, Debug, Default)] +pub struct OpenSessionParams { + pub cwd: Option, + pub shell: Option, + pub env: HashMap, + /// Per-host scrollback ceiling in bytes for the daemon-side OutputRing. + /// `None`/0 → daemon default. Derived from the host's `ring_ceiling_mb`. + pub ring_ceiling_bytes: Option, + /// The host's saved startup command, run once after the session opens + /// (mirrors the local-PTY SSH path). `None`/empty → nothing is run. + pub startup_command: Option, +} + +/// A request to back a newly created terminal with a daemon-hosted session on an +/// already-identified (possibly not-yet-connected) remote-server connection. +/// Carried through `NewTerminalOptions` into `create_session`. +#[derive(Clone, Debug)] +pub struct DaemonSessionRequest { + /// The manager/connection session id the daemon session lives on. Allocated + /// up front (see `headless_connect::alloc_daemon_session_id`); the terminal + /// waits for it to reach `Connected` before issuing `OpenSession`. + pub connection_session_id: SessionId, + pub open_params: OpenSessionParams, + /// `None` opens a fresh session; `Some(pty_session_id)` adopts an existing + /// daemon session (attach + replay instead of open) — the multi-session + /// "adopt a running session" path (Stage 4). + pub adopt_pty_session_id: Option, +} + +/// A [`crate::terminal::TerminalManager`] whose PTY lives in the remote daemon +/// and survives transport drops. Sibling of [`crate::terminal::remote_tty`]; +/// both build on the shared terminal-manager helpers and differ only in the +/// transport their [`EventLoop`] speaks. +pub struct TerminalManager { + model: Arc>, + + // Hold references to the PTYController and EventLoop so the UI framework + // doesn't deallocate them for lack of strong references. + _pty_controller: ModelHandle, + + _event_loop: ModelHandle, + + view: ViewHandle, + + /// The manager/connection session this daemon tab rides on. Used to + /// deregister the local connection when the tab is permanently closed (the + /// remote daemon session keeps running for later re-adopt). + connection_session_id: SessionId, +} + +impl TerminalManager { + /// Creates a terminal manager backed by a daemon-hosted PTY session. + /// + /// `connection_session_id` identifies an already-connected remote-server + /// session (the manager resolves the live client from it); the session is + /// opened on that connection via `OpenSession`. + #[allow(clippy::too_many_arguments)] + pub fn create_model( + resources: TerminalViewResources, + initial_size: Vector2F, + model_event_sender: Option>, + window_id: WindowId, + initial_input_config: Option, + connection_session_id: SessionId, + open_params: OpenSessionParams, + adopt_pty_session_id: Option, + ctx: &mut AppContext, + ) -> ModelHandle> { + // Create all the channels we need for communication. + let (wakeups_tx, wakeups_rx) = async_channel::unbounded(); + let (events_tx, events_rx) = async_channel::unbounded(); + let (executor_command_tx, executor_command_rx) = async_channel::unbounded(); + + // Empty PTY-reads broadcaster: nothing consumes raw PTY-read broadcasts + // for a protocol-backed PTY. Capacity is 1 (not 0) because + // `async_broadcast` asserts a minimum capacity of 1. + let (pty_reads_tx, _pty_reads_rx) = async_broadcast::broadcast(1); + + let channel_event_proxy = ChannelEventListener::new(wakeups_tx, events_tx, pty_reads_tx); + + // Initialize the sessions model. + let sessions: ModelHandle = + ctx.add_model(|ctx| Sessions::new(executor_command_tx, ctx)); + + let model_events = + ctx.add_model(|ctx| ModelEventDispatcher::new(events_rx, sessions.clone(), ctx)); + + // Create the terminal model. + let model = terminal_manager::create_terminal_model( + None, /* startup_directory */ + None, /* restored_blocks */ + initial_size, + channel_event_proxy.clone(), + // TODO: thread the real shell type through once the daemon reports it. + ShellLaunchState::ShellSpawned { + available_shell: None, + display_name: ShellName::blank(), + shell_type: ShellType::Zsh, + }, + ctx, + ); + + let size_info = *model.block_list().size(); + let colors = model.colors(); + let model = Arc::new(FairMutex::new(model)); + + let (event_loop_tx, event_loop_rx) = async_channel::unbounded(); + + let event_loop = Self::create_and_start_event_loop( + model.clone(), + channel_event_proxy.clone(), + event_loop_rx, + size_info, + connection_session_id, + open_params, + adopt_pty_session_id, + ctx, + ); + + // Initialize the PtyController. + let pty_controller = init_pty_controller_model( + event_loop_tx.clone(), + executor_command_rx, + model_events.clone(), + sessions.clone(), + model.clone(), + ctx, + ); + + let cloned_model = model.clone(); + let prompt_type = + ctx.add_model(|ctx| PromptType::new_dynamic_from_sessions(sessions.clone(), ctx)); + let view = ctx.add_typed_action_view(window_id, |ctx| { + TerminalView::new( + resources, + wakeups_rx, + model_events.clone(), + cloned_model, + sessions.clone(), + size_info, + colors, + model_event_sender.clone(), + prompt_type, + initial_input_config, + None, // conversation_restoration - not used for daemon sessions + None, // inactive_pty_reads_rx + ctx, + ) + }); + + wire_up_pty_controller_with_view( + &pty_controller, + &view, + model.clone(), + sessions, + model_event_sender, + ctx, + ); + + // Create the terminal manager itself. + let terminal_manager = Self { + model, + view, + _pty_controller: pty_controller, + _event_loop: event_loop, + connection_session_id, + }; + + ctx.add_model(|_ctx| { + let manager: Box = Box::new(terminal_manager); + manager + }) + } + + #[allow(clippy::too_many_arguments)] + fn create_and_start_event_loop( + terminal_model: Arc>, + channel_event_listener: ChannelEventListener, + event_loop_rx: Receiver, + size_info: SizeInfo, + connection_session_id: SessionId, + open_params: OpenSessionParams, + adopt_pty_session_id: Option, + ctx: &mut AppContext, + ) -> ModelHandle { + ctx.add_model(|ctx| { + EventLoop::start( + terminal_model, + event_loop_rx, + channel_event_listener, + size_info, + connection_session_id, + open_params, + adopt_pty_session_id, + ctx, + ) + }) + } +} + +impl crate::terminal::TerminalManager for TerminalManager { + fn model(&self) -> Arc> { + self.model.clone() + } + + /// On a *permanent* tab close, tear down the local connection so it doesn't + /// leak until app exit. We only drop the per-session transport (the ssh/proxy + /// slave) + the manager's bookkeeping; the remote daemon session is left + /// running (detached) for later re-adopt, and the per-host shared + /// ControlMaster is preserved for sibling tabs (`stop_control_master = false`). + /// `Moved`/`HiddenForClose` keep the tab alive, so they must not tear down. + fn on_view_detached( + &self, + detach_type: crate::pane_group::pane::DetachType, + app: &mut AppContext, + ) { + if matches!(detach_type, crate::pane_group::pane::DetachType::Closed) { + crate::remote_server::manager::RemoteServerManager::handle(app).update( + app, + |mgr, ctx| { + mgr.deregister_session(self.connection_session_id, false, ctx); + }, + ); + } + } + + fn view(&self) -> ViewHandle { + self.view.clone() + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} diff --git a/app/src/terminal/event.rs b/app/src/terminal/event.rs index d39515a2f1..1fdc15a041 100644 --- a/app/src/terminal/event.rs +++ b/app/src/terminal/event.rs @@ -18,7 +18,7 @@ use crate::terminal::ClipboardType; use crate::util::AsciiDebug; use super::history::HistoryEntry; -use super::model::ansi::{FinishUpdateValue, WarpificationUnavailableReason}; +use super::model::ansi::{FinishUpdateValue, ZaplexificationUnavailableReason}; use super::model::block::BlockId; use super::model::session::{SessionId, SessionInfo}; use super::model::terminal_model::{BlockIndex, ExitReason, TmuxInstallationState}; @@ -82,7 +82,7 @@ pub enum Event { }, /// See comment above [crate::terminal::ModelEvent::DetectedEndOfSshLogin]. DetectedEndOfSshLogin(SshLoginStatus), - RemoteWarpificationIsUnavailable(WarpificationUnavailableReason), + RemoteZaplexificationIsUnavailable(ZaplexificationUnavailableReason), SshTmuxInstaller(TmuxInstallationState), TmuxInstallFailed { line: String, @@ -94,8 +94,8 @@ pub enum Event { SourcedRcFileInSubshell(SourcedRcFileInSubshellEvent), /// Emitted when the active block's prompt has been updated. PromptUpdated, - /// Emitted when the honor_ps1 state of the shell is out-of-sync with Zap's settings. - /// This can happen in cases such as when the user changes between PS1 and Zap prompt inside + /// Emitted when the honor_ps1 state of the shell is out-of-sync with Zaplex's settings. + /// This can happen in cases such as when the user changes between PS1 and Zaplex prompt inside /// of an SSH session (the bindkeys are sent to the SSH session but not the local session, so /// they are out-of-sync when the user exits SSH). HonorPS1OutOfSync, @@ -140,7 +140,7 @@ pub enum Event { }, BootstrapPrecmdDone, /// A pluggable notification triggered via OSC 9 or OSC 777 escape sequences. - /// External programs can use this to trigger notifications in Zap. + /// External programs can use this to trigger notifications in Zaplex. /// /// References: /// - OSC 9: @@ -179,9 +179,9 @@ pub enum TerminalMode { #[derive(Clone, Debug)] pub enum SshLoginStatus { /// We have some evidence login is complete but should check again. - RecheckBeforeWarpifying, + RecheckBeforeZaplexifying, /// We have high confidence login is complete. - ReadyToWarpify, + ReadyToZaplexify, } #[derive(Clone, Debug)] @@ -259,7 +259,7 @@ pub enum BlockType { /// This is a block containing background process output. Background(Arc), - /// This is a block containing static/hardcoded content (e.g. the subshell Warpification + /// This is a block containing static/hardcoded content (e.g. the subshell Zaplexification /// welcome block). Static, } @@ -431,8 +431,8 @@ impl Debug for Event { Event::DetectedEndOfSshLogin(check_type) => { write!(f, "DetectedEndOfSshLogin: {check_type:?}") } - Event::RemoteWarpificationIsUnavailable(_) => { - write!(f, "RemoteWarpificationIsUnavailable") + Event::RemoteZaplexificationIsUnavailable(_) => { + write!(f, "RemoteZaplexificationIsUnavailable") } Event::SshTmuxInstaller(installer) => { write!(f, "SshTmuxInstaller({installer:?})") diff --git a/app/src/terminal/general_settings.rs b/app/src/terminal/general_settings.rs index 6833296b1e..ef153a0485 100644 --- a/app/src/terminal/general_settings.rs +++ b/app/src/terminal/general_settings.rs @@ -13,7 +13,7 @@ define_settings_group!(GeneralSettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "general.show_warning_before_quitting", - description: "Whether to show a warning dialog before quitting Zap.", + description: "Whether to show a warning dialog before quitting Zaplex.", }, quit_on_last_window_closed: QuitOnLastWindowClosed { type: bool, @@ -22,7 +22,7 @@ define_settings_group!(GeneralSettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "general.quit_on_last_window_closed", - description: "Whether to quit Zap when the last window is closed.", + description: "Whether to quit Zaplex when the last window is closed.", }, restore_session: RestoreSession { type: bool, @@ -31,7 +31,7 @@ define_settings_group!(GeneralSettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "general.restore_session", - description: "Whether to restore the previous session when Zap starts up.", + description: "Whether to restore the previous session when Zaplex starts up.", }, persist_conversations: PersistConversations { type: bool, @@ -100,7 +100,7 @@ define_settings_group!(GeneralSettings, settings: [ sync_to_cloud: SyncToCloud::Never, private: true, }, - // One-time flag tracking whether the Zap launch modal has already been + // One-time flag tracking whether the Zaplex launch modal has already been // shown to the user. Not user-visible; modeled as a setting so it's only // shown once per user regardless of the number of devices they use. did_check_to_trigger_zap_launch_modal: DidShowZapLaunchModal { diff --git a/app/src/terminal/grid_renderer.rs b/app/src/terminal/grid_renderer.rs index 2e126901f0..96c398aead 100644 --- a/app/src/terminal/grid_renderer.rs +++ b/app/src/terminal/grid_renderer.rs @@ -627,7 +627,7 @@ fn render_grid_without_ligatures<'a>( // Skip the cursor cell when CLI agent rich input is open // AND the agent draws its own cursor (SHOW_CURSOR is off). - // When Zap draws the cursor (SHOW_CURSOR on), we keep the cell + // When Zaplex draws the cursor (SHOW_CURSOR on), we keep the cell // and only suppress the draw_cursor call. if hide_cursor_cell && visible_cursor_shape.is_none() @@ -1150,7 +1150,7 @@ fn render_grid_with_ligatures<'a>( // Skip the cursor cell when CLI agent rich input is open // AND the agent draws its own cursor (SHOW_CURSOR is off). - // When Zap draws the cursor (SHOW_CURSOR on), we keep the cell + // When Zaplex draws the cursor (SHOW_CURSOR on), we keep the cell // and only suppress the draw_cursor call. if hide_cursor_cell && visible_cursor_shape.is_none() diff --git a/app/src/terminal/grid_renderer/cell_glyph_cache.rs b/app/src/terminal/grid_renderer/cell_glyph_cache.rs index bb09ff8c85..3d3b6bba9b 100644 --- a/app/src/terminal/grid_renderer/cell_glyph_cache.rs +++ b/app/src/terminal/grid_renderer/cell_glyph_cache.rs @@ -1,5 +1,5 @@ //! This module defines CellGlyphCache, a struct which manages the caching of glyph values for cells -//! when rendering Grids within Zap. +//! when rendering Grids within Zaplex. use warpui::elements::DEFAULT_LINE_HEIGHT_RATIO; use warpui::fonts::{Cache as FontCache, FamilyId, FontId, GlyphId, Properties}; diff --git a/app/src/terminal/history_tests.rs b/app/src/terminal/history_tests.rs index e65b694f30..eb1c928790 100644 --- a/app/src/terminal/history_tests.rs +++ b/app/src/terminal/history_tests.rs @@ -509,7 +509,7 @@ fn test_multiple_machines() { SessionInfo::new_for_test() .with_id(0) .with_shell_type(ShellType::Zsh) - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::ZaplexifiedRemote) .with_hostname("prod".to_string()) .with_user("user".to_string()) .with_ssh_socket_path(PathBuf::from("~/.ssh/12345")) @@ -521,7 +521,7 @@ fn test_multiple_machines() { SessionInfo::new_for_test() .with_id(1) .with_shell_type(ShellType::Zsh) - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::ZaplexifiedRemote) .with_hostname("dev".to_string()) .with_user("user2".to_string()) .with_ssh_socket_path(PathBuf::from("~/.ssh/12345")) diff --git a/app/src/terminal/input.rs b/app/src/terminal/input.rs index a3a5ed514c..3145c2ada0 100644 --- a/app/src/terminal/input.rs +++ b/app/src/terminal/input.rs @@ -329,7 +329,7 @@ use super::{ ExecuteCommandEvent, SyncInputType, TerminalAction, PADDING_LEFT as TERMINAL_VIEW_PADDING_LEFT, }, - warpify::SubshellSource, + zaplexify::SubshellSource, History, HistoryEntry, SizeInfo, TerminalModel, UpArrowHistoryConfig, }; use crate::ai::blocklist::agent_view::{ @@ -854,7 +854,7 @@ struct ViewerCommandExecutionRequest { /// Where a command execution request originates from. #[derive(Clone)] pub enum CommandExecutionSource { - /// A non-shared command execution request from Zap AI++. + /// A non-shared command execution request from Zaplex AI++. /// Shared commands use the SharedSession variant instead. AI { /// Metadata associated with the execution. @@ -2260,7 +2260,7 @@ impl Input { AgentInputFooterEvent::PluginInstalled(agent) => { ctx.emit(Event::RegisterPluginListener(*agent)); } - // Zap Wave 7-3:`AgentInputFooterEvent::OpenEnvironmentManagementPane` handler + // Zaplex Wave 7-3:`AgentInputFooterEvent::OpenEnvironmentManagementPane` handler // Physically deleted with ambient-agent UI subsystem. #[cfg(not(target_family = "wasm"))] AgentInputFooterEvent::OpenPluginInstructionsPane(agent, kind) => { @@ -5059,7 +5059,7 @@ impl Input { } (InputType::AI, _) => { // Follow the `agent_indicator` pattern (see `app/src/tab.rs`): - // * `None` (no conversation, empty, passive, or untitled) => new conversation => "Zap anything" + // * `None` (no conversation, empty, passive, or untitled) => new conversation => "Zaplex anything" // * `InProgress` => agent running => "Steer" // * Any other status => finished => "Ask a follow up" match self @@ -5401,7 +5401,7 @@ impl Input { }); } - /// Predicts the next action using an AI model and past context on blocks within Zap. + /// Predicts the next action using an AI model and past context on blocks within Zaplex. /// Populates the autosuggestion with the predicted action, if any. Otherwise, falls back to /// existing autosuggestion logic. #[cfg_attr(target_family = "wasm", allow(unused_variables))] @@ -6559,7 +6559,7 @@ impl Input { .string_model; if shell_type == ShellType::Fish { - // Zap currently doesn't support newlines in Fish, just prepend the vars + // Zaplex currently doesn't support newlines in Fish, just prepend the vars let mut command = env_vars.export_variables_for_shell(ShellType::Fish); command.push(' '); Some(command) @@ -9749,7 +9749,7 @@ impl Input { /// Check if we can attach on filepaths paste or drag-drop fn can_attach_on_filepaths_paste_or_dragdrop(&self, ctx: &mut ViewContext) -> bool { - // Shared session viewers cannot attach images in Zap. + // Shared session viewers cannot attach images in Zaplex. let is_viewer = self.model.lock().shared_session_status().is_viewer(); if is_viewer { return false; @@ -10437,13 +10437,13 @@ impl Input { // CLI agent rich input in shell mode (! prefix) should allow completions // even though the active block is a long-running command. - // However, completions are disabled on warpified remote hosts because + // However, completions are disabled on zaplexified remote hosts because // in-band generators don't work in this context (with CLI agent). let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx) && CLIAgentSessionsModel::as_ref(ctx).is_input_open(self.terminal_view_id) && !self .active_session(ctx) - .is_some_and(|s| matches!(s.session_type(), SessionType::WarpifiedRemote { .. })); + .is_some_and(|s| matches!(s.session_type(), SessionType::ZaplexifiedRemote { .. })); // If the cursor is in a valid completion position, go into CompletionSuggestions mode if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) { @@ -11739,7 +11739,7 @@ impl Input { return; } - // Collect pending files for the spawn request. Cloud mode image attachments are disabled in Zap. + // Collect pending files for the spawn request. Cloud mode image attachments are disabled in Zaplex. let attachments: Vec = if false { let mut inputs: Vec = self .ai_context_model @@ -12911,7 +12911,7 @@ impl Input { .cloned(), workflow_selection_source: selected_workflow_state .workflow_selection_source, - // This is only `Some()` for ZapDrive workflows; we don't track + // This is only `Some()` for ZaplexDrive workflows; we don't track // ID for execution of local workflows because they have no such // unique ID. workflow_id: selected_workflow_state.workflow_type.server_id(), diff --git a/app/src/terminal/input/classic.rs b/app/src/terminal/input/classic.rs index 537bb56fb7..bcaa9920b0 100644 --- a/app/src/terminal/input/classic.rs +++ b/app/src/terminal/input/classic.rs @@ -18,7 +18,7 @@ use crate::{ }, settings::{SpacingMode, TerminalSettings}, view::TerminalAction, - warpify::render::{render_subshell_flag, render_subshell_flag_pole}, + zaplexify::render::{render_subshell_flag, render_subshell_flag_pole}, }, }; use pathfinder_geometry::vector::vec2f; diff --git a/app/src/terminal/input/cli_agent.rs b/app/src/terminal/input/cli_agent.rs index 2974b83d89..fcb0297445 100644 --- a/app/src/terminal/input/cli_agent.rs +++ b/app/src/terminal/input/cli_agent.rs @@ -148,7 +148,7 @@ impl Input { /// Keep the rich input editor's text colors legible when it's rendered on /// top of an alt-screen CLI agent's inferred background (e.g. OpenCode), - /// which does not respect the Zap theme. When no alt-screen-backed CLI + /// which does not respect the Zaplex theme. When no alt-screen-backed CLI /// agent rich input is active, restores the theme default text colors. /// /// This mirrors the contrast-adjustment pattern used for the use-agent diff --git a/app/src/terminal/input/decorations.rs b/app/src/terminal/input/decorations.rs index 90fe021e1c..f204c9c25b 100644 --- a/app/src/terminal/input/decorations.rs +++ b/app/src/terminal/input/decorations.rs @@ -1,4 +1,4 @@ -//! Zap input editor logic for decorating input text, such as +//! Zaplex input editor logic for decorating input text, such as //! applying syntax highlighting and error underlining. use std::{collections::HashMap, ops::Range}; diff --git a/app/src/terminal/input/models/data_source.rs b/app/src/terminal/input/models/data_source.rs index ba7599e168..a8c3a00861 100644 --- a/app/src/terminal/input/models/data_source.rs +++ b/app/src/terminal/input/models/data_source.rs @@ -433,7 +433,7 @@ impl SearchItem for ModelSearchItem { let header = render_model_spec_header(&title, &description, app); // BYOP uses dedicated score rendering: Context / Output (bar uses log2 normalization) + Cost = BilledToApi. - // Visually identical to the default Zap panel, just with different row semantics. + // Visually identical to the default Zaplex panel, just with different row semantics. if byop_llm_id::is_byop(&self.id) { if let Some((provider, _api_key, model_id)) = lookup_byop(app, &self.id) { let model_entry = provider.models.iter().find(|m| m.id == model_id); @@ -653,7 +653,7 @@ impl SearchItem for ModelSearchItem { } /// Returns true when a promo discount chip should be shown for a model. -/// Discounts only apply when the user is billing through Zap credits, +/// Discounts only apply when the user is billing through Zaplex credits, /// so we suppress the chip when the user is routing through their own API key. fn should_show_discount_chip(discount_percentage: Option, is_using_byok: bool) -> bool { discount_percentage.is_some_and(|p| p > 0.) && !is_using_byok diff --git a/app/src/terminal/input/models/model_spec_scores.rs b/app/src/terminal/input/models/model_spec_scores.rs index b8fe271ebc..425187423e 100644 --- a/app/src/terminal/input/models/model_spec_scores.rs +++ b/app/src/terminal/input/models/model_spec_scores.rs @@ -30,10 +30,10 @@ pub struct ModelSpecScoresLayout { /// but with different row semantics: /// - Context — context window, bar uses log2 normalization to map to 4K..2M /// - Output — max tokens per output, bar uses log2 normalization to map to 1K..128K -/// - Cost — forces `BilledToApi` branch (BYOP users use their own key, not Zap billing) +/// - Cost — forces `BilledToApi` branch (BYOP users use their own key, not Zaplex billing) /// /// When `context_window` / `max_output_tokens` is 0 (unfilled), pass None, displaying -/// a default "?" placeholder, consistent with Zap's default panel when data is missing. +/// a default "?" placeholder, consistent with Zaplex's default panel when data is missing. pub fn render_byop_spec_scores( context_window: Option, max_output_tokens: Option, diff --git a/app/src/terminal/input/slash_commands/data_source/mod.rs b/app/src/terminal/input/slash_commands/data_source/mod.rs index a104bbf8f7..b3f37f54cd 100644 --- a/app/src/terminal/input/slash_commands/data_source/mod.rs +++ b/app/src/terminal/input/slash_commands/data_source/mod.rs @@ -418,13 +418,13 @@ impl InlineItem { override_icon } else { match skill.provider { - SkillProvider::Zap => WarpIcon::Zap, + SkillProvider::Zaplex => WarpIcon::Zaplex, SkillProvider::Claude => WarpIcon::ClaudeLogo, SkillProvider::Codex => WarpIcon::OpenAILogo, SkillProvider::Gemini => WarpIcon::GeminiLogo, SkillProvider::Droid => WarpIcon::DroidLogo, SkillProvider::OpenCode => WarpIcon::OpenCodeLogo, - _ => WarpIcon::Zap, + _ => WarpIcon::Zaplex, } }; diff --git a/app/src/terminal/input/slash_commands/mod.rs b/app/src/terminal/input/slash_commands/mod.rs index 8fbd55df99..c1ba9dbb53 100644 --- a/app/src/terminal/input/slash_commands/mod.rs +++ b/app/src/terminal/input/slash_commands/mod.rs @@ -791,7 +791,7 @@ impl Input { self.open_repos_menu(ctx); } compact if command.name == commands::COMPACT.name => { - // Zap: `/compact` and `/compact-and` share the local session compression path -- + // Zaplex: `/compact` and `/compact-and` share the local session compression path -- // dispatch `WorkspaceAction::SummarizeAIConversation`, initial_prompt: None // means "compress but don't send follow-up prompt", only summarize silently into conversation. // Custom instructions (`/compact `) go into the prompt field; in BYOP build_chat_request diff --git a/app/src/terminal/input_test.rs b/app/src/terminal/input_test.rs index a363903fbd..2cdf4ef0f9 100644 --- a/app/src/terminal/input_test.rs +++ b/app/src/terminal/input_test.rs @@ -19,7 +19,6 @@ use crate::cloud_object::{ model::persistence::ObjectStoreModel, GenericStringObjectFormat, JsonObjectType, ObjectType, Owner, }; -use crate::pricing::PricingInfoModel; use crate::search::files::model::FileSearchModel; use crate::server::ids::ClientId; use crate::terminal::cli_agent_sessions::CLIAgentSessionsModel; @@ -147,7 +146,7 @@ pub fn initialize_app(app: &mut App) { app.add_singleton_model(|_| crate::code_review::git_status_update::GitStatusUpdateModel::new()); app.add_singleton_model(RepoMetadataModel::new); app.add_singleton_model(FileSearchModel::new); - // Zap: RepoOutlines removed, no longer registered. + // Zaplex: RepoOutlines removed, no longer registered. #[cfg(feature = "voice_input")] app.add_singleton_model(voice_input::VoiceInput::new); @@ -187,7 +186,6 @@ pub fn initialize_app(app: &mut App) { app.add_singleton_model(OneTimeModalModel::new); app.add_singleton_model(|_| WorkspaceRegistry::new()); app.add_singleton_model(|_| ToastStack); - app.add_singleton_model(|_| PricingInfoModel::new()); app.add_singleton_model(ByoLlmAuthBannerSessionState::new); app.add_singleton_model(|_| { crate::ai::ambient_agents::github_auth_notifier::GitHubAuthNotifier::new() diff --git a/app/src/terminal/line_editor_status.rs b/app/src/terminal/line_editor_status.rs index 413df97ba5..0311b1434d 100644 --- a/app/src/terminal/line_editor_status.rs +++ b/app/src/terminal/line_editor_status.rs @@ -9,7 +9,7 @@ use super::{shell::ShellType, ModelEvent, ModelEventDispatcher}; /// The duration after a precmd/end prompt hook (depending on the shell type) to wait before /// assuming the shell's line editor is active again. If we receive a preexec hook in that time, /// we assume there are multiple queued typeahead commands and wait to request input. -/// This prevents Zap sending an escape sequence to an arbitrary running program. +/// This prevents Zaplex sending an escape sequence to an arbitrary running program. /// /// To avoid flickering, this must be less than `BACKGROUND_OUTPUT_RENDER_DELAY_MS`, /// which is the delay before a background block is rendered. @@ -29,8 +29,8 @@ pub struct LineEditorStatus { /// /// When receiving an end prompt marker in zsh, this is used as a proxy to determine if the /// session is bootstrapped -- the prompt markers are emitted by zsh regardless of whether or - /// not its a Warpified session, so to in order properly signal downstream that the line editor - /// (for Warpified sessions) is active, we must check if there was a corresponding precmd + /// not its a Zaplexified session, so to in order properly signal downstream that the line editor + /// (for Zaplexified sessions) is active, we must check if there was a corresponding precmd /// emitted prior to the end prompt marker. /// /// Precmd is always emitted before prompt markers. diff --git a/app/src/terminal/local_shell/mod.rs b/app/src/terminal/local_shell/mod.rs index b4e29f2787..b43a6cf71e 100644 --- a/app/src/terminal/local_shell/mod.rs +++ b/app/src/terminal/local_shell/mod.rs @@ -207,7 +207,7 @@ pub async fn execute_command( // Build environment variables map. // Always include HOME to ensure the shell can expand ~ in rc files - this is critical - // when Zap is launched via launchd (Finder, Dock) with a minimal environment. + // when Zaplex is launched via launchd (Finder, Dock) with a minimal environment. let mut env_vars = HashMap::new(); if let Some(home) = dirs::home_dir().and_then(|h| h.to_str().map(|s| s.to_string())) { env_vars.insert("HOME".to_owned(), home); @@ -252,7 +252,7 @@ async fn capture_interactive_shell_env( }; // With the `-i` flag, shells may try to set themselves as the foreground process for their - // controlling terminal with `tcsetpgrp` [1]. If the Zap process itself tries to read from + // controlling terminal with `tcsetpgrp` [1]. If the Zaplex process itself tries to read from // stdin (for example, some Oz CLI commands have interactive inputs), it may get suspended with // a `SIGTTIN` or `SIGTTOU` signal. // diff --git a/app/src/terminal/local_tty/docker_sandbox.rs b/app/src/terminal/local_tty/docker_sandbox.rs index e67d734456..1830ec8951 100644 --- a/app/src/terminal/local_tty/docker_sandbox.rs +++ b/app/src/terminal/local_tty/docker_sandbox.rs @@ -1,6 +1,6 @@ //! Docker-sandbox-specific shell-starter types and helpers. //! -//! This module owns everything specific to running a Zap shell inside a +//! This module owns everything specific to running a Zaplex shell inside a //! `sbx`-managed Docker sandbox: the [`DockerSandboxShellStarter`] that //! carries per-instance state, the host-side mount-point layout, and the //! `sbx` binary resolution logic. @@ -37,7 +37,7 @@ const DOCKER_SANDBOX_NAME_PREFIX: &str = "warp-sandbox"; /// Root directory on the host under which Docker-sandbox scratch files /// (bash init scripts, empty workspace mount points) live. /// -/// Lives under the Zap per-user cache directory rather than `/tmp` so: +/// Lives under the Zaplex per-user cache directory rather than `/tmp` so: /// - other users on a multi-user host can't pre-create or symlink-attack the /// mount path, /// - file contents are protected by the user's home-directory permissions @@ -49,11 +49,11 @@ fn docker_sandbox_host_root() -> PathBuf { warp_core::paths::cache_dir().join("docker-sandbox") } -/// Resolves the absolute path to the `sbx` CLI binary using the Zap +/// Resolves the absolute path to the `sbx` CLI binary using the Zaplex /// process's `PATH`. /// -/// Zap's process `PATH` is minimal and often misses user-shell-installed -/// tools (e.g. homebrew on Apple Silicon when Zap is launched from Finder, +/// Zaplex's process `PATH` is minimal and often misses user-shell-installed +/// tools (e.g. homebrew on Apple Silicon when Zaplex is launched from Finder, /// or `~/.local/bin`). Prefer [`resolve_sbx_path_from_user_shell`], which /// captures the PATH from the user's interactive login shell, the same way /// MCP servers and LSP resolve binaries. @@ -86,7 +86,7 @@ pub fn resolve_sbx_path_from_user_shell( /// Wraps a [`DirectShellStarter`] and adds Docker-sandbox-specific parameters. /// -/// Each instance carries a unique `sandbox_id` so multiple Zap panes can run +/// Each instance carries a unique `sandbox_id` so multiple Zaplex panes can run /// independent sandboxes concurrently without colliding on container name or /// on the host-side init / workspace mount directories. The base Docker image /// is threaded down from the AvailableShell used to initialize this starter @@ -137,7 +137,7 @@ impl DockerSandboxShellStarter { format!("{DOCKER_SANDBOX_NAME_PREFIX}-{}", self.sandbox_id) } - /// Host directory where Zap writes this sandbox's bash init script. + /// Host directory where Zaplex writes this sandbox's bash init script. /// Mounted read-only into the container at the same absolute path. pub fn init_dir(&self) -> PathBuf { docker_sandbox_host_root() diff --git a/app/src/terminal/local_tty/server/event_loop.rs b/app/src/terminal/local_tty/server/event_loop.rs index 86357db2e9..1e430fbbf3 100644 --- a/app/src/terminal/local_tty/server/event_loop.rs +++ b/app/src/terminal/local_tty/server/event_loop.rs @@ -212,7 +212,7 @@ impl EventLoop { } // If we've been reparented to a different process, stop running - - // the original host Zap process died and we're now an orphan. + // the original host Zaplex process died and we're now an orphan. if nix::unistd::Pid::parent() != self.original_parent_pid { log::info!("Detected a change in parent process; shutting down terminal server."); break 'event_loop; diff --git a/app/src/terminal/local_tty/server/mod.rs b/app/src/terminal/local_tty/server/mod.rs index 9494572a47..6603c361f5 100644 --- a/app/src/terminal/local_tty/server/mod.rs +++ b/app/src/terminal/local_tty/server/mod.rs @@ -1,11 +1,11 @@ -//! Logic relating to a secondary process which Zap uses to spawn new shell +//! Logic relating to a secondary process which Zaplex uses to spawn new shell //! sessions. //! //! The purpose of this "server" process is to ensure that shell processes are //! created in as clean of a state as possible. In general, the act of creating //! a new process in Unix "leaks" some state from the parent into the child //! (e.g.: open file descriptors). By spawning this secondary process very -//! early during Zap's initialization, we can be more confident that there are +//! early during Zaplex's initialization, we can be more confident that there are //! no resources that will leak into shell processes (and, subsequently, into //! the programs the user runs within the shell). //! @@ -110,7 +110,7 @@ fn spawn_message_receiver_thread(socket_fd: RawFd, terminated_children: Arc impl Iterator { cfg_if::cfg_if! { @@ -49,7 +49,7 @@ pub fn extra_path_entries() -> impl Iterator { } /// Returns `true` if the given `path_or_command` is a valid, executable command or path to a -/// executable binary for one of Zap's supported shell types (bash, fish, zsh). +/// executable binary for one of Zaplex's supported shell types (bash, fish, zsh). pub fn is_valid_path_or_command_for_supported_shell(path_or_command: &str) -> bool { supported_shell_path_and_type(path_or_command).is_some() } @@ -70,7 +70,7 @@ pub enum ShellStarter { impl ShellStarter { /// Constructs a `ShellStarter` represent the shell binary (and corresponding arguments) to be - /// used to spawn a shell process for a new top-level Zap session. If a WSL Distribution is + /// used to spawn a shell process for a new top-level Zaplex session. If a WSL Distribution is /// given, then it will always construct a `ShellStarter` starting the default shell for that /// WSL Distribution. /// @@ -159,7 +159,7 @@ impl ShellStarter { if let Some(warp_shell_env_var) = warp_shell_path() { let (warp_shell_path, shell_type) = supported_shell_path_and_type(&warp_shell_env_var) .unwrap_or_else(|| { - panic!("Cannot spawn shell; $WARP_SHELL_PATH is invalid: {warp_shell_env_var}") + panic!("Cannot spawn shell; $ZAPLEX_SHELL_PATH is invalid: {warp_shell_env_var}") }); return Some( ShellStarterSource::Environment(DirectShellStarter { @@ -305,7 +305,7 @@ pub struct DirectShellStarter { shell_type: ShellType, shell_path: PathBuf, - /// Arguments to be passed to the shell binary at [`shell_path`] when spawning a new Zap + /// Arguments to be passed to the shell binary at [`shell_path`] when spawning a new Zaplex /// session. args: Vec, } @@ -315,7 +315,7 @@ pub struct WslShellStarter { shell_type: ShellType, shell_path: String, - /// Arguments to be passed to the shell binary at [`shell_path`] when spawning a new Zap + /// Arguments to be passed to the shell binary at [`shell_path`] when spawning a new Zaplex /// session. args: Vec, distribution: String, @@ -326,7 +326,7 @@ pub enum ShellStarterSource { /// The user chose the path by setting a custom shell path in settings or selecting a WSL /// distribution. Override(ShellStarter), - /// The user chose the path to the shell by setting the `WARP_SHELL_PATH` environment variable. + /// The user chose the path to the shell by setting the `ZAPLEX_SHELL_PATH` environment variable. Environment(DirectShellStarter), /// The default shell for the user (as indicated by the user's passwd entry on UNIX). /// On Windows, this an ordered list of shells hardcoded _by Warp_. @@ -563,7 +563,7 @@ fn arguments_for_session_spawning_command( // The --no-rcs option executes the minimal level of startup files so we can // take over. The one exception: "Commands are first read from /etc/zshenv; this cannot be overridden." // The -g option sets the HIST_IGNORE_SPACE option, which ignores a command from history if it - // begins with a space. We use this to hide Zap bootstrap commands from the history. + // begins with a space. We use this to hide Zaplex bootstrap commands from the history. vec![ "-c".to_owned().into(), format!("exec -a -zsh '{resolved_shell_path}' -g --no-rcs").into(), @@ -581,7 +581,7 @@ fn arguments_for_session_spawning_command( * 3. The rcfile option reads the startup script from a file * 4. Process substitution i.e. <() send the output of a process via * /dev/fd/ (or temp files if this is unavailable) to another process - * 5. Send an InitShell message to Zap through escape sequences. + * 5. Send an InitShell message to Zaplex through escape sequences. * The warp_send_message function is inlined here. * 6. We disable PS2 and the line editor to work around a gnarly bug involving * garbage being inserted in every line. We further disable PS1 and echo'ing @@ -626,7 +626,7 @@ fn arguments_for_session_spawning_command( // we want fish to source config files for us (we don't // manually do so in the bootstrap script like we do for zsh, for example). // `-f no-mark-prompt` disables OSC 133 (the non-standard FinalTerm escape codes). - // Fish's implementation of this breaks Zap by emitting `OSC 133 A` but not + // Fish's implementation of this breaks Zaplex by emitting `OSC 133 A` but not // `OSC 133 B` afterwards, which we have assumed. This is a temporary workaround. // See this issue: https://github.com/zerx-lab/warp/issues/7588 r#"exec '{}' -f no-mark-prompt --login --init-command '{}'"#, diff --git a/app/src/terminal/local_tty/terminal_manager.rs b/app/src/terminal/local_tty/terminal_manager.rs index 7d5583468a..082130bd74 100644 --- a/app/src/terminal/local_tty/terminal_manager.rs +++ b/app/src/terminal/local_tty/terminal_manager.rs @@ -3,7 +3,7 @@ use crate::auth::AuthState; use crate::auth::AuthStateProvider; use crate::terminal::model::terminal_model::ExitReason; use crate::terminal::shell::ShellName; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::zaplexify::settings::ZaplexifySettings; use crate::terminal::TerminalManager as _; use anyhow::Context as _; use async_broadcast::InactiveReceiver; @@ -556,7 +556,7 @@ impl TerminalManager { }); } - /// Sends bindkey to notify shell process to switch to Zap prompt logic for prompt + /// Sends bindkey to notify shell process to switch to Zaplex prompt logic for prompt /// with the combined prompt/command grid (we unset the PS1, but save the value for potential /// future restoration). pub fn send_switch_to_warp_prompt_bindkey(&self, app_ctx: &mut AppContext) { @@ -615,10 +615,10 @@ impl TerminalManager { // The TMUX SSH wrapper supercedes the original ControlMaster wrapper. let enable_ssh_wrapper = if FeatureFlag::SSHTmuxWrapper.is_enabled() { - *WarpifySettings::as_ref(ctx) - .enable_ssh_warpification + *ZaplexifySettings::as_ref(ctx) + .enable_ssh_zaplexification .value() - && !*WarpifySettings::as_ref(ctx).use_ssh_tmux_wrapper.value() + && !*ZaplexifySettings::as_ref(ctx).use_ssh_tmux_wrapper.value() } else { *SshSettings::as_ref(ctx).enable_legacy_ssh_wrapper.value() }; diff --git a/app/src/terminal/local_tty/unix.rs b/app/src/terminal/local_tty/unix.rs index f9097fb2ff..0769e1106c 100644 --- a/app/src/terminal/local_tty/unix.rs +++ b/app/src/terminal/local_tty/unix.rs @@ -262,7 +262,7 @@ fn build_host_shell_command( // Specify terminal name and capabilities. builder.env("TERM", "xterm-256color"); - builder.env("TERM_PROGRAM", "WarpTerminal"); + builder.env("TERM_PROGRAM", "ZaplexTerminal"); // Advertise 24-bit color support. builder.env("COLORTERM", "truecolor"); @@ -277,11 +277,11 @@ fn build_host_shell_command( // plugins can do warp-specific version checks without worrying // that the version env var might be coming from a different terminal // (for ex., in the ssh case). - builder.env("WARP_CLIENT_VERSION", version); + builder.env("ZAPLEX_CLIENT_VERSION", version); } else { // Local builds don't have GIT_RELEASE_TAG, so app_version() is None. // Use "local" so plugins can still distinguish this from a missing value. - builder.env("WARP_CLIENT_VERSION", "local"); + builder.env("ZAPLEX_CLIENT_VERSION", "local"); } // Set the `SHELL` environment variable to match the path of the shell we are using. @@ -296,9 +296,9 @@ fn build_host_shell_command( // Set whether or not we should utilize the SSH wrapper in this shell. if enable_ssh_wrapper { - builder.env("WARP_USE_SSH_WRAPPER", "1"); + builder.env("ZAPLEX_USE_SSH_WRAPPER", "1"); } else { - builder.env("WARP_USE_SSH_WRAPPER", "0"); + builder.env("ZAPLEX_USE_SSH_WRAPPER", "0"); } // For integration tests, put SSH control master sockets under the actual @@ -309,32 +309,32 @@ fn build_host_shell_command( // We currently don't support bootstrapping recursive SSH sessions so we will only run the SSH // logic if this flag is set. - builder.env("WARP_IS_LOCAL_SHELL_SESSION", "1"); + builder.env("ZAPLEX_IS_LOCAL_SHELL_SESSION", "1"); // Only advertise the protocol version when the HOA notifications feature is enabled. - // Without it, Zap can't render structured CLI agent notifications, + // Without it, Zaplex can't render structured CLI agent notifications, // so the plugin should fall back to legacy notifications. if FeatureFlag::HOANotifications.is_enabled() { builder.env( - "WARP_CLI_AGENT_PROTOCOL_VERSION", + "ZAPLEX_CLI_AGENT_PROTOCOL_VERSION", current_protocol_version().to_string(), ); } if shell_debug_mode { - builder.env("WARP_SHELL_DEBUG_MODE", "1"); + builder.env("ZAPLEX_SHELL_DEBUG_MODE", "1"); } if honor_ps1 { - builder.env("WARP_HONOR_PS1", "1"); + builder.env("ZAPLEX_HONOR_PS1", "1"); } else { - builder.env("WARP_HONOR_PS1", "0"); + builder.env("ZAPLEX_HONOR_PS1", "0"); } // Pass through any additional entries to add to PATH. let path_append = extra_path_entries() .map(|p| p.to_string_lossy().into_owned()) .join(":"); - builder.env("WARP_PATH_APPEND", path_append); + builder.env("ZAPLEX_PATH_APPEND", path_append); if matches!(shell_starter.shell_type(), ShellType::Bash) { // Set an initial very large value for HISTFILESIZE so that it @@ -343,7 +343,7 @@ fn build_host_shell_command( builder.env("HISTFILESIZE", sentinel_value); // Set a second environment variable that we can use to know whether // the user rcfiles set HISTFILESIZE or not. - builder.env("WARP_INITIAL_HISTFILESIZE", sentinel_value); + builder.env("ZAPLEX_INITIAL_HISTFILESIZE", sentinel_value); } // Pass the desired initial working directory as an environment variable @@ -356,7 +356,7 @@ fn build_host_shell_command( // directory could be on a network filesystem; deferring the `cd` to // shell bootstrap avoids that. if let Some(start_dir) = start_dir { - builder.env("WARP_INITIAL_WORKING_DIR", start_dir); + builder.env("ZAPLEX_INITIAL_WORKING_DIR", start_dir); } // Apply any caller-provided environment overrides last, so they win. @@ -472,7 +472,7 @@ fn spawn_command_in_pty( if is_isolated { // If running in a sandbox on Linux, adjust the OOM score // to make the child process more likely to be killed than the parent process - // in case of OOM. If the Zap process is killed while hosting an ambient + // in case of OOM. If the Zaplex process is killed while hosting an ambient // agent, its shared session will abruptly end with no user-visible error. // Instead, we want to kill whatever process the agent spawned that's using // lots of memory. This gives the agent a chance to gracefully fail. @@ -535,6 +535,47 @@ pub(crate) fn spawn_session_pty( if !env.contains_key("TERM") { command.env("TERM", "xterm-256color"); } + // Give the daemon-spawned shell the same Zaplex terminal *identity* that a + // locally spawned shell gets (see `build_host_shell_command`). TERM_PROGRAM + // is what the shell integration and plugins key off to recognize a Zaplex + // terminal; the bootstrap *script* (injected separately) supplies the shell + // integration, but the identity lives in the spawn env — without it the + // bootstrapped shell renders as a generic VT. The client may override any of + // these via `env`; otherwise we assert the Zaplex defaults. + if !env.contains_key("TERM_PROGRAM") { + command.env("TERM_PROGRAM", "ZaplexTerminal"); + } + if !env.contains_key("COLORTERM") { + command.env("COLORTERM", "truecolor"); + } + match ChannelState::app_version() { + Some(version) => { + if !env.contains_key("TERM_PROGRAM_VERSION") { + command.env("TERM_PROGRAM_VERSION", version); + } + if !env.contains_key("ZAPLEX_CLIENT_VERSION") { + command.env("ZAPLEX_CLIENT_VERSION", version); + } + } + None => { + if !env.contains_key("ZAPLEX_CLIENT_VERSION") { + command.env("ZAPLEX_CLIENT_VERSION", "local"); + } + } + } + // The daemon owns session persistence itself (its PTY + OutputRing survive + // drops); it must NOT be composed with the user's terminal multiplexer. We + // spawn a *login* shell (for the user's PATH/profile), but many users have + // their profile auto-launch byobu/tmux on login (e.g. + // `. /usr/bin/byobu-launch` in ~/.profile). Without suppressing it, the + // daemon's login shell joins the user's *existing* byobu session group, + // cross-contaminating I/O (the bootstrap script leaking into the user's + // live session, the daemon tab mirroring it). `BYOBU_DISABLE=1` is byobu's + // documented opt-out and makes the daemon shell stand alone. The client may + // still override via `env`. + if !env.contains_key("BYOBU_DISABLE") { + command.env("BYOBU_DISABLE", "1"); + } let size = SizeInfo::new_without_font_metrics(rows, cols); let info = spawn_command_in_pty(command, &size, true)?; // SAFETY: `leader_fd` is a freshly-opened PTY master fd that we now own. @@ -792,8 +833,8 @@ fn build_docker_sandbox_command( // TODO(advait): audit this list. It currently mirrors what the // pre-refactor host-shell `spawn` set when the starter happened to // be a Docker sandbox, so behaviour is unchanged from before the - // split. Many of these (e.g. `WARP_USE_SSH_WRAPPER`, - // `SSH_SOCKET_DIR`, `HISTFILESIZE`, `WARP_IS_LOCAL_SHELL_SESSION`) + // split. Many of these (e.g. `ZAPLEX_USE_SSH_WRAPPER`, + // `SSH_SOCKET_DIR`, `HISTFILESIZE`, `ZAPLEX_IS_LOCAL_SHELL_SESSION`) // are set on the *host* `sbx` process and may or may not propagate // into the container depending on `sbx`'s env passthrough rules. // Once we've validated what the container bootstrap actually needs, @@ -803,45 +844,45 @@ fn build_docker_sandbox_command( builder.env("USER", pw.name); builder.env("HOME", &home_dir); builder.env("TERM", "xterm-256color"); - builder.env("TERM_PROGRAM", "WarpTerminal"); + builder.env("TERM_PROGRAM", "ZaplexTerminal"); builder.env("COLORTERM", "truecolor"); builder.env_remove("DESKTOP_STARTUP_ID"); if let Some(version) = ChannelState::app_version() { builder.env("TERM_PROGRAM_VERSION", version); - builder.env("WARP_CLIENT_VERSION", version); + builder.env("ZAPLEX_CLIENT_VERSION", version); } else { - builder.env("WARP_CLIENT_VERSION", "local"); + builder.env("ZAPLEX_CLIENT_VERSION", "local"); } builder.env("SHELL", docker_starter.logical_shell_path()); if let Some(window_id) = window_id { builder.env("WINDOWID", format!("{window_id}")); } builder.env( - "WARP_USE_SSH_WRAPPER", + "ZAPLEX_USE_SSH_WRAPPER", if enable_ssh_wrapper { "1" } else { "0" }, ); builder.env("SSH_SOCKET_DIR", ssh_socket_dir()); - builder.env("WARP_IS_LOCAL_SHELL_SESSION", "1"); + builder.env("ZAPLEX_IS_LOCAL_SHELL_SESSION", "1"); if FeatureFlag::HOANotifications.is_enabled() { builder.env( - "WARP_CLI_AGENT_PROTOCOL_VERSION", + "ZAPLEX_CLI_AGENT_PROTOCOL_VERSION", current_protocol_version().to_string(), ); } if shell_debug_mode { - builder.env("WARP_SHELL_DEBUG_MODE", "1"); + builder.env("ZAPLEX_SHELL_DEBUG_MODE", "1"); } - builder.env("WARP_HONOR_PS1", if honor_ps1 { "1" } else { "0" }); + builder.env("ZAPLEX_HONOR_PS1", if honor_ps1 { "1" } else { "0" }); let path_append = extra_path_entries() .map(|p| p.to_string_lossy().into_owned()) .join(":"); - builder.env("WARP_PATH_APPEND", path_append); + builder.env("ZAPLEX_PATH_APPEND", path_append); // Sandbox shell is always bash (per the container image convention), // matching the host-shell path's behavior for bash shells. let sentinel_value = "57265949261"; builder.env("HISTFILESIZE", sentinel_value); - builder.env("WARP_INITIAL_HISTFILESIZE", sentinel_value); - // Intentionally do NOT set `WARP_INITIAL_WORKING_DIR` for sandboxes: + builder.env("ZAPLEX_INITIAL_HISTFILESIZE", sentinel_value); + // Intentionally do NOT set `ZAPLEX_INITIAL_WORKING_DIR` for sandboxes: // the container's init script cds into the sandbox home dir, not // the host's startup dir. @@ -862,7 +903,7 @@ fn build_docker_sandbox_command( /// the sandbox. /// /// Both paths are derived from `starter.sandbox_id` so multiple concurrent -/// Zap panes/sandboxes don't race on or share the same host directories. +/// Zaplex panes/sandboxes don't race on or share the same host directories. /// /// The actual sandbox creation + attachment happens via /// `sbx run --name warp-sandbox- shell WORKSPACE ... -- -c "cd /home/agent && exec bash --rcfile ..."` @@ -870,7 +911,7 @@ fn build_docker_sandbox_command( /// /// TODO(advait): Wire up cleanup on pane close. Today, closing a Docker /// sandbox pane leaves behind (1) the per-sandbox host init + workspace dirs -/// under the Zap cache dir, and (2) the stopped `warp-sandbox-` +/// under the Zaplex cache dir, and (2) the stopped `warp-sandbox-` /// container. Both are per-sandbox so they don't clobber each other, but /// they accumulate over repeated sessions. The right hook is likely on the /// PTY/pane lifecycle (alongside `Pty::kill`) and should: @@ -881,9 +922,9 @@ fn build_docker_sandbox_command( fn prepare_docker_sandbox(starter: &DockerSandboxShellStarter) -> Result<()> { // Build each per-sandbox subdirectory with mode 0700 so other local users // cannot traverse into them, which (combined with the parent living under - // the per-user Zap cache dir rather than `/tmp`) prevents the init + // the per-user Zaplex cache dir rather than `/tmp`) prevents the init // script from being read or symlink-attacked by anyone other than the - // Zap user. The file itself is left at the default mode so the + // Zaplex user. The file itself is left at the default mode so the // container's shell (which may run as a different uid than the host // user) can still read it via `--rcfile`. let mk_owner_only_dir = |path: &Path| -> Result<()> { diff --git a/app/src/terminal/local_tty/windows/environment.rs b/app/src/terminal/local_tty/windows/environment.rs index 893d28eb32..8820d842ab 100644 --- a/app/src/terminal/local_tty/windows/environment.rs +++ b/app/src/terminal/local_tty/windows/environment.rs @@ -17,14 +17,14 @@ use winreg::{ use crate::safe_info; use crate::terminal::local_tty::{shell::ShellStarter, PtyOptions}; -const HONOR_PS1_NAME: &str = "WARP_HONOR_PS1"; -const INITIAL_WORKING_DIR_NAME: &str = "WARP_INITIAL_WORKING_DIR"; -const USE_SSH_WRAPPER_NAME: &str = "WARP_USE_SSH_WRAPPER"; -const SHELL_DEBUG_MODE_NAME: &str = "WARP_SHELL_DEBUG_MODE"; +const HONOR_PS1_NAME: &str = "ZAPLEX_HONOR_PS1"; +const INITIAL_WORKING_DIR_NAME: &str = "ZAPLEX_INITIAL_WORKING_DIR"; +const USE_SSH_WRAPPER_NAME: &str = "ZAPLEX_USE_SSH_WRAPPER"; +const SHELL_DEBUG_MODE_NAME: &str = "ZAPLEX_SHELL_DEBUG_MODE"; const TERM_PROGRAM_NAME: &str = "TERM_PROGRAM"; -const IS_LOCAL_SESSION_NAME: &str = "WARP_IS_LOCAL_SHELL_SESSION"; +const IS_LOCAL_SESSION_NAME: &str = "ZAPLEX_IS_LOCAL_SHELL_SESSION"; const SSH_SOCKET_DIR: &str = "SSH_SOCKET_DIR"; -const PATH_APPEND_NAME: &str = "WARP_PATH_APPEND"; +const PATH_APPEND_NAME: &str = "ZAPLEX_PATH_APPEND"; const WSLENV: &str = "WSLENV"; const HISTIGNORE: &str = "HISTIGNORE"; @@ -88,7 +88,7 @@ pub(super) fn get_shell_environment_variables(options: &PtyOptions) -> Vec map_key(TERM_PROGRAM_NAME.into()), EnvEntry { preferred_key: TERM_PROGRAM_NAME.into(), - value: "WarpTerminal".into(), + value: "ZaplexTerminal".into(), }, ); @@ -102,18 +102,18 @@ pub(super) fn get_shell_environment_variables(options: &PtyOptions) -> Vec let client_version = ChannelState::app_version().unwrap_or("local"); env.insert( - map_key("WARP_CLIENT_VERSION".into()), + map_key("ZAPLEX_CLIENT_VERSION".into()), EnvEntry { - preferred_key: "WARP_CLIENT_VERSION".into(), + preferred_key: "ZAPLEX_CLIENT_VERSION".into(), value: client_version.into(), }, ); if FeatureFlag::HOANotifications.is_enabled() { env.insert( - map_key("WARP_CLI_AGENT_PROTOCOL_VERSION".into()), + map_key("ZAPLEX_CLI_AGENT_PROTOCOL_VERSION".into()), EnvEntry { - preferred_key: "WARP_CLI_AGENT_PROTOCOL_VERSION".into(), + preferred_key: "ZAPLEX_CLI_AGENT_PROTOCOL_VERSION".into(), value: current_protocol_version().to_string().into(), }, ); @@ -128,7 +128,7 @@ pub(super) fn get_shell_environment_variables(options: &PtyOptions) -> Vec }, ); - // Set WARP_PATH_APPEND with additional PATH entries to append + // Set ZAPLEX_PATH_APPEND with additional PATH entries to append let path_append = extra_path_entries() .map(|p| p.to_string_lossy().into_owned()) .join(";"); diff --git a/app/src/terminal/local_tty/windows/pipes.rs b/app/src/terminal/local_tty/windows/pipes.rs index 725b5b87cd..a7c75bc1b9 100644 --- a/app/src/terminal/local_tty/windows/pipes.rs +++ b/app/src/terminal/local_tty/windows/pipes.rs @@ -77,7 +77,7 @@ pub enum CreatePipeError { pub struct DuplexPipe { /// The client-side (e.g.: OpenConsole) end of the pipe. pub client: HANDLE, - /// The server-side (e.g.: Zap) end of the pipe. + /// The server-side (e.g.: Zaplex) end of the pipe. pub server: HANDLE, } diff --git a/app/src/terminal/meta_shortcuts.rs b/app/src/terminal/meta_shortcuts.rs index 15d866731e..bdc06518d7 100644 --- a/app/src/terminal/meta_shortcuts.rs +++ b/app/src/terminal/meta_shortcuts.rs @@ -1,6 +1,6 @@ use warpui::keymap::Keystroke; -/// Whether this keystroke should dispatch an action in Zap despite the +/// Whether this keystroke should dispatch an action in Zaplex despite the /// [`warpui::event::Event::KeyDown::is_composing`] being true. /// /// Generally, we ignore all `KeyDown` events if the `is_composing` field is true. However it's diff --git a/app/src/terminal/mod.rs b/app/src/terminal/mod.rs index 97c8bb5119..808952b106 100644 --- a/app/src/terminal/mod.rs +++ b/app/src/terminal/mod.rs @@ -31,9 +31,10 @@ pub mod block_list_element; pub mod block_list_viewport; pub mod blockgrid_element; mod blockgrid_renderer; -mod bootstrap; +pub mod bootstrap; pub mod color; mod command_corrections_denylist; +pub mod daemon_tty; pub mod dynamic_enum_suggestions; pub mod event; pub mod event_listener; @@ -68,7 +69,7 @@ pub mod safe_mode_settings; mod secret_regex_updater; pub mod session_settings; pub mod settings; -// Zap: removed share_block_modal (cloud block permanent link sharing) +// Zaplex: removed share_block_modal (cloud block permanent link sharing) pub mod shared_session; mod shell_launch_state; pub mod universal_developer_input; @@ -77,7 +78,7 @@ pub mod ssh; pub mod terminal_manager; mod terminal_size_element; pub mod view; -pub mod warpify; +pub mod zaplexify; mod waterfall_gap_element; mod writeable_pty; #[cfg(windows)] @@ -89,7 +90,7 @@ pub(crate) mod cli_agent_sessions; pub use mock_terminal_manager::MockTerminalManager; use model_events::{ModelEvent, ModelEventDispatcher}; -// Zap: removed ShareBlockModal pub use +// Zaplex: removed ShareBlockModal pub use pub use terminal_manager::TerminalManager; pub use block_list_settings::*; @@ -120,7 +121,7 @@ const MIN_COLUMNS: usize = 2; pub const PTY_READS_BROADCAST_CHANNEL_SIZE: usize = 1024; pub fn init(app: &mut AppContext) { - // Zap: removed share_block_modal::init + // Zaplex: removed share_block_modal::init view::init(app); } @@ -365,7 +366,7 @@ impl SizeInfo { } /// Create SizeInfo for a [`TerminalModel`] instance that doesn't have font metrics, - /// which comes from either a headless Zap instance or tests. + /// which comes from either a headless Zaplex instance or tests. pub fn new_without_font_metrics(rows: usize, cols: usize) -> Self { let width = cols as f32; let height = rows as f32; diff --git a/app/src/terminal/model/ansi/dcs_hooks.rs b/app/src/terminal/model/ansi/dcs_hooks.rs index be3819cace..7d31a9e0df 100644 --- a/app/src/terminal/model/ansi/dcs_hooks.rs +++ b/app/src/terminal/model/ansi/dcs_hooks.rs @@ -8,22 +8,22 @@ use std::collections::HashSet; use std::path::PathBuf; use warp_core::command::ExitCode; -/// Indicates that the following JSON-encoded message is hex-encoded for Zap's lifecycle hooks. +/// Indicates that the following JSON-encoded message is hex-encoded for Zaplex's lifecycle hooks. /// In DCS, it is used as the final char in the DCS start sequence. /// In OSC, it is used as the first parameter. pub(super) const HEX_ENCODED_JSON_MARKER: char = 'd'; -/// Indicates that the following JSON-encoded message is unencoded for Zap's lifecycle hooks. +/// Indicates that the following JSON-encoded message is unencoded for Zaplex's lifecycle hooks. /// In DCS, it is used as the final char in the DCS start sequence. /// In OSC, it is used as the first parameter. pub(super) const UNENCODED_JSON_MARKER: char = 'f'; -/// Indicates that the following message is a ANSI-C quoted message for receiving Zap's lifecycle +/// Indicates that the following message is a ANSI-C quoted message for receiving Zaplex's lifecycle /// hooks via key-value pairs. /// In OSC< it is used as the first parameter. pub(super) const UNENCODED_KV_MARKER: char = 'k'; -/// Enum representing all possible JSON payloads for Zap's DCS's. +/// Enum representing all possible JSON payloads for Zaplex's DCS's. #[derive(Serialize, Debug, Deserialize)] #[allow(clippy::upper_case_acronyms)] #[serde(tag = "hook")] @@ -69,9 +69,9 @@ pub(super) enum DProtoHook { FinishUpdate { value: FinishUpdateValue, }, - RemoteWarpificationIsUnavailable { + RemoteZaplexificationIsUnavailable { // If a value is provided, it's suggesting a way to install TMUX on the remote. - value: WarpificationUnavailableReason, + value: ZaplexificationUnavailableReason, }, SshTmuxInstaller { value: String, @@ -100,8 +100,8 @@ impl DProtoHook { DProtoHook::SourcedRcFileForWarp { .. } => "SourcedRcFileForWarp", DProtoHook::InitSsh { .. } => "InitSsh", DProtoHook::FinishUpdate { .. } => "FinishUpdate", - DProtoHook::RemoteWarpificationIsUnavailable { .. } => { - "RemoteWarpificationIsUnavailable" + DProtoHook::RemoteZaplexificationIsUnavailable { .. } => { + "RemoteZaplexificationIsUnavailable" } DProtoHook::SshTmuxInstaller { .. } => "SshTmuxInstaller", DProtoHook::TmuxInstallFailed { .. } => "TmuxInstallFailed", @@ -367,11 +367,11 @@ pub struct SystemDetails { pub writable_home: Option, } -/// The reason that warpification was not available when the user tried -/// to warpify. +/// The reason that zaplexification was not available when the user tried +/// to zaplexify. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all(serialize = "snake_case"))] -pub enum WarpificationUnavailableReason { +pub enum ZaplexificationUnavailableReason { TmuxFailed, UnsupportedTmuxVersion { #[serde(flatten)] @@ -495,7 +495,7 @@ pub struct PreexecValue { pub command: String, } -/// Received from the pty after the shell has finished executing Zap's +/// Received from the pty after the shell has finished executing Zaplex's /// bootstrap script. #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct BootstrappedValue { @@ -551,7 +551,7 @@ pub struct BootstrappedValue { pub rcfiles_end_time: Option>, /// Tags for known shell configurations/plugins, especially ones that are - /// incompatible with Zap. + /// incompatible with Zaplex. #[serde(deserialize_with = "parse_shell_options_list_deserializer", default)] pub shell_plugins: Option>, @@ -597,7 +597,7 @@ fn parse_float_from_string(s: String) -> Option> { s.parse::().map(|f| f.into()).ok() } -/// Received from the pty when Zap's SSH wrapper is executed, prior to +/// Received from the pty when Zaplex's SSH wrapper is executed, prior to /// bootstrapping the SSH session. #[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize, Clone)] pub struct PreInteractiveSSHSessionValue {} @@ -646,7 +646,7 @@ pub struct InitSubshellValue { } /// Emitted by a snippet included in the user's RC file, which signals a new session is being -/// created; if the session is for a subshell, this triggers Zap's bootstrap process. +/// created; if the session is for a subshell, this triggers Zaplex's bootstrap process. /// Otherwise, it's ignored. #[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)] pub struct SourcedRcFileForWarpValue { @@ -657,8 +657,8 @@ pub struct SourcedRcFileForWarpValue { /// Received from the pty via a shell line editor hook, whether readline (bash), /// ZLE, or the fish [command line editor](https://fishshell.com/docs/current/interactive.html#command-line-editor). -/// The binding is triggered when Zap writes the `ESC-i` escape sequence to the pty. -/// Zap usually does this after a block completes, to collect any typeahead +/// The binding is triggered when Zaplex writes the `ESC-i` escape sequence to the pty. +/// Zaplex usually does this after a block completes, to collect any typeahead /// that the user entered while the block was running (see /// [`TerminalView::request_input_buffer`]). #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] @@ -679,7 +679,7 @@ pub struct FinishUpdateValue { } /// Received from the pty right before the remote shell exits (via `exit`, -/// `logout`, Ctrl-D on an empty prompt, etc.). Lets the Zap client drop +/// `logout`, Ctrl-D on an empty prompt, etc.). Lets the Zaplex client drop /// per-session resources — in particular the `ssh … remote-server-proxy` /// child process that holds a multiplexed channel on the foreground ssh /// ControlMaster — before the user's outer ssh tunnel tries to close, so diff --git a/app/src/terminal/model/ansi/handler.rs b/app/src/terminal/model/ansi/handler.rs index 9407692397..83cf119842 100644 --- a/app/src/terminal/model/ansi/handler.rs +++ b/app/src/terminal/model/ansi/handler.rs @@ -236,28 +236,28 @@ pub trait Handler { /// Report text area size in characters. fn text_area_size_chars(&mut self, _: &mut W); - /// Callback for the Zap CommandFinished hook. + /// Callback for the Zaplex CommandFinished hook. fn command_finished(&mut self, _data: CommandFinishedValue) {} /// Process a prompt marker control sequence. fn prompt_marker(&mut self, _marker: PromptMarker) {} - /// Callback for the Zap precmd hook. + /// Callback for the Zaplex precmd hook. fn precmd(&mut self, _data: PrecmdValue) {} - /// Callback for the Zap preexec hook. + /// Callback for the Zaplex preexec hook. fn preexec(&mut self, _data: PreexecValue) {} - /// Callback for the Zap bootstrapped hook - called once when the shell is + /// Callback for the Zaplex bootstrapped hook - called once when the shell is /// bootstrapped fn bootstrapped(&mut self, _data: BootstrappedValue) {} - /// Callback for the Zap pre-interactive SSH session hook - called once + /// Callback for the Zaplex pre-interactive SSH session hook - called once /// before initiating an interactive SSH session (either with or without the /// SSH wrapper). fn pre_interactive_ssh_session(&mut self, _data: PreInteractiveSSHSessionValue) {} - /// Callback for the Zap ssh hook - called once after successfully connecting to + /// Callback for the Zaplex ssh hook - called once after successfully connecting to /// an SSH server fn ssh(&mut self, _data: SSHValue) {} @@ -265,8 +265,8 @@ pub trait Handler { /// logic into the PTY fn init_shell(&mut self, _data: InitShellValue) {} - /// Callback for the Zap exit-shell hook — emitted by the remote shell right - /// before it exits. Gives the Zap client a chance to drop per-session + /// Callback for the Zaplex exit-shell hook — emitted by the remote shell right + /// before it exits. Gives the Zaplex client a chance to drop per-session /// resources (e.g. the `ssh … remote-server-proxy` child) before the outer /// ssh tunnel starts tearing down, so its ControlMaster can exit cleanly /// rather than hanging on orphaned multiplexed channels. @@ -276,7 +276,7 @@ pub trait Handler { fn clear(&mut self, _data: ClearValue) {} /// Callback for the terminal when the shell reports the current line editor - /// input buffer (the reporting is itself triggered by Zap). + /// input buffer (the reporting is itself triggered by Zaplex). fn input_buffer(&mut self, _data: InputBufferValue) {} /// Callback emitted during the initialization process for subshells with where the shell type @@ -284,7 +284,7 @@ pub trait Handler { fn init_subshell(&mut self, _data: InitSubshellValue) {} /// Callback emitted when executing the user's RC file, which signals a new session is being - /// created. If the session is for a subshell, this should triggers Zap's bootstrap process. + /// created. If the session is for a subshell, this should triggers Zaplex's bootstrap process. /// Otherwise, it's ignored. fn sourced_rc_file(&mut self, _data: SourcedRcFileForWarpValue) {} @@ -295,9 +295,9 @@ pub trait Handler { /// assisted auto-update. fn finish_update(&mut self, _data: FinishUpdateValue) {} - /// Callback emitted from the warpify_ssh_session script if it's discovered - /// that we can't warpify the remote session. - fn remote_warpification_is_unavailable(&mut self, _data: WarpificationUnavailableReason) {} + /// Callback emitted from the zaplexify_ssh_session script if it's discovered + /// that we can't zaplexify the remote session. + fn remote_zaplexification_is_unavailable(&mut self, _data: ZaplexificationUnavailableReason) {} /// How tmux was installed. fn notify_ssh_tmux_is_installed(&mut self, _tmux_installation: TmuxInstallationState) {} @@ -395,7 +395,7 @@ pub trait Handler { } /// Callback for pluggable notifications triggered via OSC 9 or OSC 777 escape sequences. - /// These allow external programs to trigger notifications in Zap. + /// These allow external programs to trigger notifications in Zaplex. /// - OSC 9: Simple notification with just a body (iTerm2/Windows Terminal style) /// - OSC 777: Notification with title and body (urxvt style) fn pluggable_notification(&mut self, _title: Option, _body: String) {} diff --git a/app/src/terminal/model/ansi/mod.rs b/app/src/terminal/model/ansi/mod.rs index 2b82a16145..cdd50ad5e7 100644 --- a/app/src/terminal/model/ansi/mod.rs +++ b/app/src/terminal/model/ansi/mod.rs @@ -7,7 +7,7 @@ //! Internally, [`Performer`] delegates to finer-grained methods for handling //! PTY output implemented by the [`Handler`] trait -- this could be printing to //! the terminal, executing actions as a result of CSI or OSC sequences, -//! executing one of Zap's DCS hooks, etc. [`Handler`] should be implemented by +//! executing one of Zaplex's DCS hooks, etc. [`Handler`] should be implemented by //! an app-level model that updates the terminal's state accordingly. mod ansi_c_decoder; mod dcs_hooks; @@ -54,18 +54,18 @@ use warpui::color::ColorU; use super::kitty::parse_kitty_chunk; use super::terminal_model::TmuxInstallationState; -/// Marks an OSC as one that is sent by Zap logic registered in the shell. +/// Marks an OSC as one that is sent by Zaplex logic registered in the shell. /// /// 9277 spells out "WARP" on a dialpad :). -const WARP_IN_BAND_GENERATOR_OSC_MARKER: &[u8] = b"9277"; -const WARP_IN_BAND_GENERATOR_START_BYTE: &[u8] = b"A"; -const WARP_IN_BAND_GENERATOR_END_BYTE: &[u8] = b"B"; +const ZAPLEX_IN_BAND_GENERATOR_OSC_MARKER: &[u8] = b"9277"; +const ZAPLEX_IN_BAND_GENERATOR_START_BYTE: &[u8] = b"A"; +const ZAPLEX_IN_BAND_GENERATOR_END_BYTE: &[u8] = b"B"; /// Marks an OSC that is used for messages containing shell hooks. -const WARP_OSC_MARKER: &[u8] = b"9278"; +const ZAPLEX_OSC_MARKER: &[u8] = b"9278"; /// Marks an OSC that is used for resetting ConPTY's grid. This is useful for performing a series -/// of checks ensuring that Zap's grids and ConPTY's grid are in sync. -const WARP_RESET_GRID_OSC_MARKER: &[u8] = b"9279"; +/// of checks ensuring that Zaplex's grids and ConPTY's grid are in sync. +const ZAPLEX_RESET_GRID_OSC_MARKER: &[u8] = b"9279"; /// The amount of time a single synchronized update can take from the time the corresponding /// 'Set Mode' escape sequence is processed before a redraw is forced. @@ -82,23 +82,23 @@ lazy_static! { static ref SYNC_OUTPUT_MAX_BUFFER_SIZE: Byte = Byte::from_u64_with_unit(2, ByteUnit::MiB).expect("Can create byte size for sync output max buffer size"); } -const WARP_COMPLETIONS_OSC_MARKER: &[u8] = b"9280"; -const WARP_COMPLETIONS_START_BYTE: &[u8] = b"A"; -const WARP_COMPLETIONS_END_BYTE: &[u8] = b"B"; -const WARP_COMPLETIONS_MATCH_RESULT_BYTE: &[u8] = b"C"; +const ZAPLEX_COMPLETIONS_OSC_MARKER: &[u8] = b"9280"; +const ZAPLEX_COMPLETIONS_START_BYTE: &[u8] = b"A"; +const ZAPLEX_COMPLETIONS_END_BYTE: &[u8] = b"B"; +const ZAPLEX_COMPLETIONS_MATCH_RESULT_BYTE: &[u8] = b"C"; /// Denotes an OSC that sends metadata about the last match result. /// The sequence begins with `D?` followed by the field that should be updated. /// For example: `D?description'{OSC_PAYLOAD}` updates the description of the last match. -const WARP_COMPLETIONS_MATCH_UPDATE_METADATA: &[u8] = b"D?"; +const ZAPLEX_COMPLETIONS_MATCH_UPDATE_METADATA: &[u8] = b"D?"; /// Marks an OSC that tells the terminal that the shell is ready to receive /// the the string to run completions for. -const WARP_COMPLETIONS_PROMPT_BYTE: &[u8] = b"P"; +const ZAPLEX_COMPLETIONS_PROMPT_BYTE: &[u8] = b"P"; -const WARP_KV_START_BYTE: &[u8] = b"A"; -const WARP_KV_ENTRY_BYTE: &[u8] = b"B"; -const WARP_KV_END_BYTE: &[u8] = b"C"; +const ZAPLEX_KV_START_BYTE: &[u8] = b"A"; +const ZAPLEX_KV_ENTRY_BYTE: &[u8] = b"B"; +const ZAPLEX_KV_END_BYTE: &[u8] = b"C"; /// Parse colors in XParseColor format. #[allow(dead_code)] @@ -224,7 +224,7 @@ struct TmuxControlModeState { /// to issue multiple updates to the state of the PTY without causing a redraw /// between each update. /// -/// There are two mechanisms to prevent Zap from falling too behind: +/// There are two mechanisms to prevent Zaplex from falling too behind: /// 1. a timeout. After [`SYNC_OUTPUT_MAX_TIMEOUT`] has elapsed, a redraw will be forced. /// 2. a max buffer limit. After [`SYNC_OUTPUT_MAX_BUFFER_SIZE`] bytes have been buffered, /// a redraw will be forced. @@ -624,8 +624,8 @@ impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> { log::error!("Received hex-encoded SourcedRcFileForWarp escape sequence."); } Ok(DProtoHook::FinishUpdate { value }) => self.handler.finish_update(value), - Ok(DProtoHook::RemoteWarpificationIsUnavailable { value }) => { - self.handler.remote_warpification_is_unavailable(value) + Ok(DProtoHook::RemoteZaplexificationIsUnavailable { value }) => { + self.handler.remote_zaplexification_is_unavailable(value) } Ok(DProtoHook::SshTmuxInstaller { value }) => { if let Ok(tmux_installation) = TmuxInstallationState::from_str(&value) { @@ -697,14 +697,14 @@ impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> { fn handle_kv_marker(&mut self, params: &[&[u8]]) { match params.get(2) { - Some(&WARP_KV_START_BYTE) => { + Some(&ZAPLEX_KV_START_BYTE) => { let Some(hook) = params.get(3).map(|data| String::from_utf8_lossy(data)) else { log::error!("Start pending hook OSC did not contain shell hook"); return; }; self.handler.start_receiving_hook(hook.into()); } - Some(&WARP_KV_END_BYTE) => { + Some(&ZAPLEX_KV_END_BYTE) => { let Some(pending_shell_hook) = self.handler.finish_receiving_hook() else { return; }; @@ -715,7 +715,7 @@ impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> { ); self.handle_decoded_hook(Ok(hook)); } - Some(&WARP_KV_ENTRY_BYTE) => { + Some(&ZAPLEX_KV_ENTRY_BYTE) => { let Some(key) = params.get(3) else { log::error!("Pending hook update OSC did not contain key"); return; @@ -1074,22 +1074,22 @@ where } } - // Received a Zap OSC used for in-band generators. - WARP_IN_BAND_GENERATOR_OSC_MARKER => match params.get(1) { - Some(&WARP_IN_BAND_GENERATOR_START_BYTE) => { - log::info!("Received a Zap OSC marker for starting in-band command output."); + // Received a Zaplex OSC used for in-band generators. + ZAPLEX_IN_BAND_GENERATOR_OSC_MARKER => match params.get(1) { + Some(&ZAPLEX_IN_BAND_GENERATOR_START_BYTE) => { + log::info!("Received a Zaplex OSC marker for starting in-band command output."); self.handler.start_in_band_command_output(); } - Some(&WARP_IN_BAND_GENERATOR_END_BYTE) => { + Some(&ZAPLEX_IN_BAND_GENERATOR_END_BYTE) => { self.handler.end_in_band_command_output(true); } _ => { - log::warn!("Received a Zap OSC marker missing required param."); + log::warn!("Received a Zaplex OSC marker missing required param."); } }, - // Received a Zap OSC used for shell hooks. - WARP_OSC_MARKER => { + // Received a Zaplex OSC used for shell hooks. + ZAPLEX_OSC_MARKER => { let Some(json_marker_char) = params .get(1) .map(|json_marker_bytes| String::from_utf8_lossy(json_marker_bytes)) @@ -1106,12 +1106,12 @@ where .get(2) .map(|osc_data| String::from_utf8_lossy(osc_data)) else { - log::error!("Zap OSC marker did not contain payload"); + log::error!("Zaplex OSC marker did not contain payload"); return; }; safe_debug!( - safe: ("Received Zap OSC string for shell hook"), - full: ("Received Zap OSC string for shell hook with JSON payload: {:?}", data_str) + safe: ("Received Zaplex OSC string for shell hook"), + full: ("Received Zaplex OSC string for shell hook with JSON payload: {:?}", data_str) ); let decoded_data = hex::decode(&*data_str); self.handle_decoded_data(decoded_data); @@ -1122,12 +1122,12 @@ where .get(2) .map(|osc_data| String::from_utf8_lossy(osc_data)) else { - log::error!("Zap OSC marker did not contain payload"); + log::error!("Zaplex OSC marker did not contain payload"); return; }; safe_debug!( - safe: ("Received Zap OSC string for shell hook"), - full: ("Received Zap OSC string for shell hook with JSON payload: {:?}", data_str) + safe: ("Received Zaplex OSC string for shell hook"), + full: ("Received Zaplex OSC string for shell hook with JSON payload: {:?}", data_str) ); let hook = serde_json::from_str::(&data_str); self.handle_unencoded_hook(hook) @@ -1139,35 +1139,35 @@ where } } - WARP_RESET_GRID_OSC_MARKER => { - log::debug!("Received Zap OSC string for reset grid"); + ZAPLEX_RESET_GRID_OSC_MARKER => { + log::debug!("Received Zaplex OSC string for reset grid"); self.handler.on_reset_grid(); } - // Received a Zap OSC used for completions. - WARP_COMPLETIONS_OSC_MARKER => match params.get(1) { - Some(&WARP_COMPLETIONS_START_BYTE) => { + // Received a Zaplex OSC used for completions. + ZAPLEX_COMPLETIONS_OSC_MARKER => match params.get(1) { + Some(&ZAPLEX_COMPLETIONS_START_BYTE) => { let Some(format) = params .get(2) .map(|osc_data| String::from_utf8_lossy(osc_data)) .and_then(|format| CompletionsShellData::from_format_type(&format)) else { - log::warn!("Zap start completions OSC marker contained invalid format."); + log::warn!("Zaplex start completions OSC marker contained invalid format."); return; }; self.handler.start_completions_output(format); } - Some(&WARP_COMPLETIONS_END_BYTE) => { + Some(&ZAPLEX_COMPLETIONS_END_BYTE) => { self.handler.end_completions_output(); } - Some(&WARP_COMPLETIONS_MATCH_RESULT_BYTE) => { + Some(&ZAPLEX_COMPLETIONS_MATCH_RESULT_BYTE) => { // The payload for the OSC is contained in the third parameter. let Some(data_str) = params .get(2) .map(|osc_data| String::from_utf8_lossy(osc_data)) else { log::warn!( - "Zap completions match result OSC marker did not contain payload" + "Zaplex completions match result OSC marker did not contain payload" ); return; }; @@ -1177,7 +1177,7 @@ where self.handler .on_completion_result_received(shell_completion_result); } - Some(bytes) if bytes.starts_with(WARP_COMPLETIONS_MATCH_UPDATE_METADATA) => { + Some(bytes) if bytes.starts_with(ZAPLEX_COMPLETIONS_MATCH_UPDATE_METADATA) => { let Ok(parameter) = String::from_utf8(bytes.to_vec()) else { log::warn!( "Unable to convert update completions match parameter into a string" @@ -1191,7 +1191,7 @@ where .map(|osc_data| String::from_utf8_lossy(osc_data)) else { log::warn!( - "Zap completions match metadata OSC marker did not contain payload" + "Zaplex completions match metadata OSC marker did not contain payload" ); return; }; @@ -1206,15 +1206,15 @@ where ); } _ => { - log::warn!("Invalid Zap OSC marker parameter for completions match metadata: {parameter}"); + log::warn!("Invalid Zaplex OSC marker parameter for completions match metadata: {parameter}"); } } } - Some(&WARP_COMPLETIONS_PROMPT_BYTE) => { + Some(&ZAPLEX_COMPLETIONS_PROMPT_BYTE) => { self.handler.send_completions_prompt(); } _ => { - log::warn!("Received a Zap OSC completions marker missing required param."); + log::warn!("Received a Zaplex OSC completions marker missing required param."); } }, diff --git a/app/src/terminal/model/block.rs b/app/src/terminal/model/block.rs index 4ac4dea081..9b65a14788 100644 --- a/app/src/terminal/model/block.rs +++ b/app/src/terminal/model/block.rs @@ -635,7 +635,7 @@ pub enum BlockState { /// any particular execution or command. Background, - /// This block holds static content and is programmatically added to the blocklist by Zap. An + /// This block holds static content and is programmatically added to the blocklist by Zaplex. An /// example is the information subshell bootstrap "success" block. Static, } @@ -1526,7 +1526,7 @@ impl Block { } /// Whether we render the prompt on the same line, in the context of a finished block. Post-same - /// line prompt, we render on the same line for PS1, but not for Zap prompt! + /// line prompt, we render on the same line for PS1, but not for Zaplex prompt! pub fn render_prompt_on_same_line(&self) -> bool { self.honor_ps1() } @@ -1664,7 +1664,7 @@ impl Block { /// A command-grid is active in the period after we have received the precmd /// hook but before the command has started executing. This includes the time - /// when the shell echoes the command bytes that Zap wrote to the PTY. + /// when the shell echoes the command bytes that Zaplex wrote to the PTY. pub fn is_command_grid_active(&self) -> bool { self.state == BlockState::BeforeExecution } @@ -1882,7 +1882,7 @@ impl Block { if self.header_grid.honor_ps1() { self.block_banner_height() + self.padding_top() } else { - // Grid is drawn below custom Zap prompt in finished blocks. + // Grid is drawn below custom Zaplex prompt in finished blocks. self.block_banner_height() + self.padding_top() + self.prompt_height() @@ -1911,7 +1911,7 @@ impl Block { } /// Returns the ENTIRE HEIGHT of the prompt and command (no padding top or middle included). - /// In the case of combined grid: for Zap prompt, this includes the height of both the Zap prompt + /// In the case of combined grid: for Zaplex prompt, this includes the height of both the Zaplex prompt /// AND combined grid; for PS1, this is just the combined grid (PS1 is included there). pub fn prompt_and_command_height(&self) -> Lines { if !self.ready_to_render() { @@ -1920,7 +1920,7 @@ impl Block { // No padding between prompt and command in the case of PS1 (combined grid). self.header_grid.prompt_and_command_height() } else { - // Handle the case of Zap built-in prompt with combined grid. + // Handle the case of Zaplex built-in prompt with combined grid. // Note that we have non-zero `command_padding_top` in this case, unlike above! if self.header_grid.is_command_empty() { Lines::zero() @@ -2971,7 +2971,7 @@ impl ansi::Handler for Block { // If we're processing a prompt and we receive an initial blank line, // ignore it. This is sometimes used in prompts (e.g.: oh-my-zsh's // "re5et" theme) to separate the previous command's output from the - // prompt, but this is not needed in Zap due to us visually separating + // prompt, but this is not needed in Zaplex due to us visually separating // blocks. match self.header_grid.receiving_chars_for_prompt { Some(ansi::PromptKind::Initial) if !self.header_grid.prompt_has_received_content() => { diff --git a/app/src/terminal/model/block/serialized_block.rs b/app/src/terminal/model/block/serialized_block.rs index 43f8663981..f2c72946af 100644 --- a/app/src/terminal/model/block/serialized_block.rs +++ b/app/src/terminal/model/block/serialized_block.rs @@ -191,7 +191,7 @@ pub struct SerializedBlock { pub shell_host: Option, - /// JSON-serialized representation of the Zap prompt snapshot (Context Chips). Note that this + /// JSON-serialized representation of the Zaplex prompt snapshot (Context Chips). Note that this /// is different from PS1 and RPROMPT1 pub prompt_snapshot: Option, diff --git a/app/src/terminal/model/block_test.rs b/app/src/terminal/model/block_test.rs index 2df928ac04..8b74f3e926 100644 --- a/app/src/terminal/model/block_test.rs +++ b/app/src/terminal/model/block_test.rs @@ -1132,10 +1132,10 @@ fn test_command_is_not_empty_combined_grid() { ); } -/// Regression test (CORE-1947): checks Zap prompt case for is_command_empty. Specifically, +/// Regression test (CORE-1947): checks Zaplex prompt case for is_command_empty. Specifically, /// even if the combined grid's content _exactly_ matches the prompt grid's content (which is used /// for PS1 preview), we should NOT consider the command to be empty. The underlying cursor check should -/// be against (0, 0) in the combined grid, for the Zap prompt case, rather than checking against the +/// be against (0, 0) in the combined grid, for the Zaplex prompt case, rather than checking against the /// prompt grid (which we do in the PS1 active case). #[test] fn test_command_is_empty_warp_prompt() { @@ -1162,7 +1162,7 @@ fn test_command_is_empty_warp_prompt() { let mut prompt_grid = mock_blockgrid("abcde"); prompt_grid.finish(); - // Note that we are indicating Zap prompt, not PS1 here! + // Note that we are indicating Zaplex prompt, not PS1 here! let mut block = create_test_block_with_grids( block_index, prompt_and_command_grid, diff --git a/app/src/terminal/model/blockgrid.rs b/app/src/terminal/model/blockgrid.rs index 9a9e2798fe..75629229b2 100644 --- a/app/src/terminal/model/blockgrid.rs +++ b/app/src/terminal/model/blockgrid.rs @@ -333,7 +333,7 @@ impl BlockGrid { false, /* force_secrets_obfuscated */ RespectDisplayedOutput::No, ); - // We ignore any content that only contains line editor bindkeys that Zap sends to the + // We ignore any content that only contains line editor bindkeys that Zaplex sends to the // shell. Specifically `^[i` (requests the shell's input buffer) and `^P` (clears the // shell's input buffer). There are instances where these bindkeys will show up in // background blocks when PTY throughput is reduced. diff --git a/app/src/terminal/model/blocks.rs b/app/src/terminal/model/blocks.rs index b0beef747f..617e152640 100644 --- a/app/src/terminal/model/blocks.rs +++ b/app/src/terminal/model/blocks.rs @@ -2415,7 +2415,7 @@ impl BlockList { let next_block = self.block_at(next_block_index)?; let next_command_is_empty = next_block.is_command_empty(); // NOTE: there is a semantic difference here of seeking down to the next "prompt" (in the PS1 case) - // vs the next "command" (in the Zap prompt case), when using the combined grid, rather than + // vs the next "command" (in the Zaplex prompt case), when using the combined grid, rather than // directly going to the next "command" in both cases. let grid_type = GridType::PromptAndCommand; @@ -3633,10 +3633,10 @@ impl ansi::Handler for BlockList { } ClearMode::All => { // TODO(alokedesai): Investigate how we can call `clear_visible_screen` here to have - // Zap's custom logic for "clear". It's not immediately straightforward because a + // Zaplex's custom logic for "clear". It's not immediately straightforward because a // a running program that writes output, clears the visible screen, and then writes // more output should all be encapsulated within a single block, which wouldn't be - // quite right with Zap's custom clear screen logic. + // quite right with Zaplex's custom clear screen logic. } _ => {} } diff --git a/app/src/terminal/model/blocks/selection.rs b/app/src/terminal/model/blocks/selection.rs index 0de75d5923..3307ba9146 100644 --- a/app/src/terminal/model/blocks/selection.rs +++ b/app/src/terminal/model/blocks/selection.rs @@ -21,7 +21,7 @@ use crate::{ selection::{ExpandedSelectionRange, Selection, SelectionDirection}, terminal_model::{BlockIndex, WithinBlock}, }, - warpify::success_block::WarpifySuccessBlock, + zaplexify::success_block::ZaplexifySuccessBlock, GridType, }, }; @@ -955,11 +955,11 @@ impl BlockList { if let Some(active_window_id) = app.windows().active_window() { if let Some(ssh_block) = app - .view_with_id::(active_window_id, *view_id) + .view_with_id::(active_window_id, *view_id) { - let warpify_success_block = app.view(&ssh_block); + let zaplexify_success_block = app.view(&ssh_block); if let Some(selected_text) = - warpify_success_block.selected_text() + zaplexify_success_block.selected_text() { selected_texts.push(selected_text); } @@ -1074,10 +1074,10 @@ impl BlockList { } if let Some(ssh_block) = - app.view_with_id::(active_window_id, view_id) + app.view_with_id::(active_window_id, view_id) { - let warpify_success_block = app.view(&ssh_block); - if let Some(selected_text) = warpify_success_block.selected_text() { + let zaplexify_success_block = app.view(&ssh_block); + if let Some(selected_text) = zaplexify_success_block.selected_text() { selected_texts.push(selected_text); } } diff --git a/app/src/terminal/model/blocks_test.rs b/app/src/terminal/model/blocks_test.rs index 04699e4abc..b687a45753 100644 --- a/app/src/terminal/model/blocks_test.rs +++ b/app/src/terminal/model/blocks_test.rs @@ -1139,12 +1139,12 @@ pub fn test_block_heights_combined_prompt_command_grid_warp_prompt() { // We created one block. assert_eq!(block_list.blocks.len(), bootstrapped_block_list_len + 1); - // Note that this test is using the Zap prompt, hence the prompt is not included in the combined grid. + // Note that this test is using the Zaplex prompt, hence the prompt is not included in the combined grid. assert_eq!(first_block.prompt_and_command_grid().len(), 3); assert_eq!(first_block.output_grid().len(), 3); // In this case, we SHOULD consider command_padding_top since we have a combined prompt/command grid BUT - // we have the built-in Zap prompt, so there's padding between that prompt and the combined grid. + // we have the built-in Zaplex prompt, so there's padding between that prompt and the combined grid. // The combined grid _just_ has the command in this case! The PS1 is unset! // Hence, we expect heights of 8.5. assert_lines_approx_eq!(first_block.height(&AgentViewState::Inactive), 8.5); @@ -1186,7 +1186,7 @@ pub fn test_block_heights_combined_prompt_command_grid_ps1() { assert_eq!(first_block.prompt_and_command_grid().len(), 4); assert_eq!(first_block.output_grid().len(), 3); - // We have a 2-line prompt, adding 1 extra line to the combined grid (vs 0.6 default for Zap prompt). + // We have a 2-line prompt, adding 1 extra line to the combined grid (vs 0.6 default for Zaplex prompt). // Hence, we expect a height of 8.7 rather than 8.3. assert_lines_approx_eq!(first_block.height(&AgentViewState::Inactive), 8.7); } diff --git a/app/src/terminal/model/bootstrap.rs b/app/src/terminal/model/bootstrap.rs index 5294d09f4b..60e339d4ea 100644 --- a/app/src/terminal/model/bootstrap.rs +++ b/app/src/terminal/model/bootstrap.rs @@ -1,14 +1,14 @@ /// Stages during the course of bootstrapping the shell. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum BootstrapStage { - /// Zap is re-parsing historical blocks for this session. We haven't yet started + /// Zaplex is re-parsing historical blocks for this session. We haven't yet started /// bootstrapping. RestoreBlocks, - /// Zap is writing the bootstrap script into the running shell. + /// Zaplex is writing the bootstrap script into the running shell. WarpInput, /// Execution of any shell startup scripts such as .rc or .profile files. ScriptExecution, - /// Model is fully bootstrapped (i.e the `Bootstrap` message was successfully received by Zap). + /// Model is fully bootstrapped (i.e the `Bootstrap` message was successfully received by Zaplex). Bootstrapped, /// Model is fully bootstrapped and we've received the precmd that results from bootstrapping itself PostBootstrapPrecmd, diff --git a/app/src/terminal/model/early_output.rs b/app/src/terminal/model/early_output.rs index a833e2100d..ee24f14de5 100644 --- a/app/src/terminal/model/early_output.rs +++ b/app/src/terminal/model/early_output.rs @@ -22,17 +22,17 @@ mod tests; /// The approach we're using to detect user typeahead. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TypeaheadMode { - /// The shell reports its input buffer to Zap, and we use that for typeahead. + /// The shell reports its input buffer to Zaplex, and we use that for typeahead. ShellReported, - /// Zap matches user input against characters echoed to the PTY to estimate typeahead. + /// Zaplex matches user input against characters echoed to the PTY to estimate typeahead. /// This is only used on bash 3.2 and should be removed if we stop supporting /// such old bash versions. InputMatching, } -/// Model for "early" terminal output. Early output is output that Zap receives +/// Model for "early" terminal output. Early output is output that Zaplex receives /// from the PTY while no block is running. In concrete terms, it's output received -/// after a `BlockFinished` hook but before Zap has written the next command from +/// after a `BlockFinished` hook but before Zaplex has written the next command from /// the input editor to the PTY. /// /// This output belongs to one of two categories: @@ -207,9 +207,9 @@ impl EarlyOutput { // For most user-entered commands, we know when to switch from background // output to the active block's command grid because the input editor // marks the block as started right before it sends the command to the pty. - // When the command doesn't come from Zap, however, the active block isn't + // When the command doesn't come from Zaplex, however, the active block isn't // started until we receive the preexec hook. At this point, the shell has - // already written the command to the pty, resulting in Zap treating it as + // already written the command to the pty, resulting in Zaplex treating it as // background output. // We can't correctly identify the command in advance when this happens, so // instead we fix the block list afterwards. diff --git a/app/src/terminal/model/grid/ansi_handler.rs b/app/src/terminal/model/grid/ansi_handler.rs index 4d673750b4..4dd43b0297 100644 --- a/app/src/terminal/model/grid/ansi_handler.rs +++ b/app/src/terminal/model/grid/ansi_handler.rs @@ -81,7 +81,7 @@ pub(super) struct State { pub obfuscate_secrets: ObfuscateSecrets, /// Whether this Grid is in a shell context which supports handling the emoji presentation selector - /// correctly. Notably, Zsh does NOT support this well in bracketed paste mode (which we use for all Zap + /// correctly. Notably, Zsh does NOT support this well in bracketed paste mode (which we use for all Zaplex /// commands), which can lead to cursor misalignment issues. pub supports_emoji_presentation_selector: bool, @@ -408,7 +408,7 @@ impl ansi::Handler for GridHandler { // was introduced[2] - version 277. // // Since we didn't want to claim xterm functionalities that we haven't yet implemented in - // Zap, rather than passing the higher `Pv` value, we decided to use one of the + // Zaplex, rather than passing the higher `Pv` value, we decided to use one of the // hardcoded ones. `0;95;0` is set what iTerm2 sends. // [1] https://github.com/vim/vim/blob/20c370d9f2ee89cb854054edf71f5004f6efff77/src/term.c#L4630 @@ -428,7 +428,7 @@ impl ansi::Handler for GridHandler { fn report_xtversion(&mut self, writer: &mut W) { log::trace!("Reporting xtversion"); let version = ChannelState::app_version().unwrap_or(""); - let _ = writer.write_all(format!("\x1bP>|Zap({version})\x1b\\").as_bytes()); + let _ = writer.write_all(format!("\x1bP>|Zaplex({version})\x1b\\").as_bytes()); } fn device_status(&mut self, writer: &mut W, arg: usize) { diff --git a/app/src/terminal/model/grid/grid_handler.rs b/app/src/terminal/model/grid/grid_handler.rs index e6b5578cdd..b289036a34 100644 --- a/app/src/terminal/model/grid/grid_handler.rs +++ b/app/src/terminal/model/grid/grid_handler.rs @@ -287,8 +287,8 @@ impl AbsoluteRectangle { } /// Whether or not this Grid should keep track of a "Reset Grid" OSC. On Windows, ConPTY has an internal -/// grid that needs to be kept in sync with Zap's grids. We do this via clearing the ConPTY -/// grid before Zap starts populating a new grid. +/// grid that needs to be kept in sync with Zaplex's grids. We do this via clearing the ConPTY +/// grid before Zaplex starts populating a new grid. /// /// See here for more: https://docs.google.com/document/d/11fU_vVW8CH72W92QUnFJ1Kl31fGWNGbjkQQCK3TUaYk/edit?usp=sharing #[derive(Default, Clone, Copy)] diff --git a/app/src/terminal/model/grid/grid_handler_test.rs b/app/src/terminal/model/grid/grid_handler_test.rs index a957149c9e..f54240b8b0 100644 --- a/app/src/terminal/model/grid/grid_handler_test.rs +++ b/app/src/terminal/model/grid/grid_handler_test.rs @@ -82,9 +82,9 @@ fn regex_right() { #[rustfmt::skip] let blockgrid = mock_blockgrid("\ testing66\r\n\ - Zap\n\ + Zaplex\n\ 123\r\n\ - Zap\r\n\ + Zaplex\r\n\ 123\ "); @@ -107,9 +107,9 @@ fn regex_left() { #[rustfmt::skip] let blockgrid = mock_blockgrid("\ testing66\r\n\ - Zap\n\ + Zaplex\n\ 123\r\n\ - Zap\r\n\ + Zaplex\r\n\ 123\ "); @@ -131,7 +131,7 @@ fn regex_left() { fn nested_regex() { #[rustfmt::skip] let blockgrid = mock_blockgrid("\ - Wa -> Zap -> rp\r\n\ + Wa -> Zaplex -> rp\r\n\ rp\ "); @@ -760,7 +760,7 @@ fn test_find_url_omits_trailing_periods() { ); // Test that it handles a period in the middle of the URL path somewhere. - let blockgrid = mock_blockgrid("Visit https://github.com/warp.dev/Zap/issues."); + let blockgrid = mock_blockgrid("Visit https://github.com/warp.dev/Zaplex/issues."); assert_eq!( blockgrid .grid_handler diff --git a/app/src/terminal/model/grid/grid_storage/resize.rs b/app/src/terminal/model/grid/grid_storage/resize.rs index f03360cb21..643b9fcc23 100644 --- a/app/src/terminal/model/grid/grid_storage/resize.rs +++ b/app/src/terminal/model/grid/grid_storage/resize.rs @@ -217,7 +217,7 @@ impl GridStorage { // We rotate the cursor down to ensure that the wrapping subtraction logic below with // `num_wrapped` can be completed correctly, without saturating at (0, 0) (which previously // occurred, if the cursor isn't adjusted). Ultimately, the user-facing impact resulted in an - // incorrect cursor position comparison leading to incorrect block heights, since Zap + // incorrect cursor position comparison leading to incorrect block heights, since Zaplex // erroneously believed a command to be "empty" (when comparing the "end of prompt" cursor // to the "end of the command"). // Note that last_row is a wrapped line in this case (see `should_reflow` and `debug_assert` above)! diff --git a/app/src/terminal/model/header_grid.rs b/app/src/terminal/model/header_grid.rs index 44f2dc9618..211d0fa2a0 100644 --- a/app/src/terminal/model/header_grid.rs +++ b/app/src/terminal/model/header_grid.rs @@ -1,5 +1,5 @@ //! This module defines HeaderGrid, a struct which manages the prompt and command grid's within -//! Zap. This struct is abstracted away from Block, for the purposes of enabling same-line prompt, +//! Zaplex. This struct is abstracted away from Block, for the purposes of enabling same-line prompt, //! utilizing a combined prompt/command grid, with helper methods to expose the prompt and command. use std::{cmp::max, io}; @@ -123,7 +123,7 @@ pub struct HeaderGrid { /// with remote subshells correctly). /// TODO(CORE-2403): Rename this field to should_populate_prompt_preview_grid. ignore_next_prompt_preview: bool, - /// The height of the Zap prompt in lines (non-PS1). + /// The height of the Zaplex prompt in lines (non-PS1). warp_prompt_height_lines: f32, // whether to honor users ps1 and rprompt values, can be changed by a user in the settings // note that the change will only apply to the active block; historical blocks will keep the @@ -188,7 +188,7 @@ impl HeaderGrid { pub fn set_honor_ps1(&mut self, honor_ps1: bool) { self.honor_ps1 = honor_ps1; if !self.honor_ps1 { - // If we are switching to Zap prompt (from PS1), we need to clear the cached prompt end point + // If we are switching to Zaplex prompt (from PS1), we need to clear the cached prompt end point // and update the command start point appropriately! self.cached_prompt_end_point = Some(PromptEndPoint::EmptyPrompt); self.cached_command_start_point = Some(CommandStartPoint::CommandStart { @@ -315,7 +315,7 @@ impl HeaderGrid { /// the command to be finished. fn is_command_finished_and_empty(&self) -> bool { if !self.honor_ps1 { - // If we are using Zap prompt, we expect the combined grid cursor to be at the start, if + // If we are using Zaplex prompt, we expect the combined grid cursor to be at the start, if // the command is truly empty. return self.prompt_and_command_grid.finished() && self.prompt_and_command_grid.grid_handler().cursor_point() == Point::new(0, 0); @@ -1154,13 +1154,13 @@ impl ansi::Handler for HeaderGrid { honor_ps1 ); // We send a terminal event which will result in bindkeys being issued to the shell session, to - // switch the prompt mode via the $WARP_HONOR_PS1 environment variable. + // switch the prompt mode via the $ZAPLEX_HONOR_PS1 environment variable. self.event_proxy .send_terminal_event(Event::HonorPS1OutOfSync); // We synchronize the state of our `honor_ps1` setting with the value passed from the shell. // Note that we ALWAYS want this to be synced properly since the shell determines the prompt - // to be emitted. This may be de-synced from Zap settings in particular niche cases (which are + // to be emitted. This may be de-synced from Zaplex settings in particular niche cases (which are // bugs), however, we still want consistent behavior for the prompt in the blocklist (we want to // avoid double prompt or empty prompt issues). self.honor_ps1 = honor_ps1; diff --git a/app/src/terminal/model/rich_content.rs b/app/src/terminal/model/rich_content.rs index 7748db1869..36ccb1017c 100644 --- a/app/src/terminal/model/rich_content.rs +++ b/app/src/terminal/model/rich_content.rs @@ -4,7 +4,7 @@ pub enum RichContentType { AIBlock, EnterAgentView, - WarpifySuccessBlock, + ZaplexifySuccessBlock, InlineAgentViewHeader, AgentViewZeroState, TerminalViewZeroState, diff --git a/app/src/terminal/model/secrets.rs b/app/src/terminal/model/secrets.rs index 49ea1b6d75..55b5bdb190 100644 --- a/app/src/terminal/model/secrets.rs +++ b/app/src/terminal/model/secrets.rs @@ -526,8 +526,8 @@ pub mod regexes { /// We know those sections are JSON and should begin with '{"'. pub const JWT: &str = r"\b(ey[a-zA-z0-9_\-=]{10,}\.){2}[a-zA-z0-9_\-=]{10,}\b"; - /// Identifies a Zap API Key. Format: wk- followed by a version number and any combination of hex digits, hyphens, or periods. - pub const WARP_API_KEY: &str = r"\bwk-[0-9]+\.[A-Fa-f0-9.\-]+\b"; + /// Identifies a Zaplex API Key. Format: wk- followed by a version number and any combination of hex digits, hyphens, or periods. + pub const ZAPLEX_API_KEY: &str = r"\bwk-[0-9]+\.[A-Fa-f0-9.\-]+\b"; /// Returns a slice of regex strings that can be used to identify secrets. // NOTE: All regexes added here must also be added server-side in logic/ai/util.go. @@ -609,8 +609,8 @@ pub mod regexes { name: "Fireworks API Key", }, DefaultRegex { - pattern: WARP_API_KEY, - name: "Zap API Key", + pattern: ZAPLEX_API_KEY, + name: "Zaplex API Key", }, ]; } diff --git a/app/src/terminal/model/session.rs b/app/src/terminal/model/session.rs index 2f82e51f7c..1438973e0f 100644 --- a/app/src/terminal/model/session.rs +++ b/app/src/terminal/model/session.rs @@ -41,7 +41,7 @@ use command_executor::remote_server_executor::RemoteServerCommandExecutor; use parking_lot::{Mutex, RwLock}; use crate::terminal::shell::{Shell, ShellType}; -use crate::terminal::warpify::SubshellSource; +use crate::terminal::zaplexify::SubshellSource; use crate::terminal::History; use super::ansi::{BootstrappedValue, InitShellValue, SSHValue}; @@ -178,7 +178,11 @@ impl Sessions { | RemoteServerManagerEvent::BinaryCheckComplete { .. } | RemoteServerManagerEvent::BinaryInstallComplete { .. } | RemoteServerManagerEvent::ClientRequestFailed { .. } - | RemoteServerManagerEvent::ServerMessageDecodingError { .. } => {} + | RemoteServerManagerEvent::ServerMessageDecodingError { .. } + // Stage 2: the attached-remote terminal byte-source subscribes + // to these in a later increment; no-op here for now. + | RemoteServerManagerEvent::SessionOutput { .. } + | RemoteServerManagerEvent::SessionExited { .. } => {} RemoteServerManagerEvent::SessionReconnected { session_id: sid, client, @@ -347,7 +351,7 @@ impl Sessions { let session = Arc::new(session); self.sessions.insert(session.id(), session.clone()); - // For warpified-remote sessions, pick up the current host_id from + // For zaplexified-remote sessions, pick up the current host_id from // the manager so session.remote_host_id() is populated without // waiting for the next SessionConnected event. The // RemoteServerCommandExecutor already has its client baked in, so @@ -356,7 +360,7 @@ impl Sessions { if FeatureFlag::SshRemoteServer.is_enabled() && matches!( session_info.session_type, - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::ZaplexifiedRemote ) { if let Some(host_id) = RemoteServerManager::as_ref(ctx).host_id_for_session(session_id) @@ -504,7 +508,7 @@ impl Sessions { impl From for command_corrections::SessionType { fn from(session_type: SessionType) -> Self { match session_type { - SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote, + SessionType::ZaplexifiedRemote { .. } => command_corrections::SessionType::Remote, SessionType::Local => command_corrections::SessionType::Local, } } @@ -513,7 +517,7 @@ impl From for command_corrections::SessionType { impl From<&SessionType> for command_corrections::SessionType { fn from(session_type: &SessionType) -> Self { match session_type { - SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote, + SessionType::ZaplexifiedRemote { .. } => command_corrections::SessionType::Remote, SessionType::Local => command_corrections::SessionType::Local, } } @@ -598,7 +602,7 @@ impl SessionInfo { subshell_info: Option, launch_data: Option, legacy_ssh_session: Option, - is_warpified_ssh_session: bool, + is_zaplexified_ssh_session: bool, active_block_session_id: Option, ) -> Self { let is_legacy_ssh_session = match legacy_ssh_session { @@ -616,11 +620,11 @@ impl SessionInfo { // to determine if this is a local or remote session. let session_type = Self::determine_session_type( &init_shell_value, - is_warpified_ssh_session + is_zaplexified_ssh_session || matches!(&is_legacy_ssh_session, IsLegacySSHSession::Yes { .. }), ); - let spawning_session_id = if matches!(session_type, BootstrapSessionType::WarpifiedRemote) + let spawning_session_id = if matches!(session_type, BootstrapSessionType::ZaplexifiedRemote) || subshell_info.is_some() { active_block_session_id @@ -657,18 +661,18 @@ impl SessionInfo { #[cfg(not(feature = "remote_tty"))] fn determine_session_type( init_shell_value: &InitShellValue, - is_warpified_ssh_session: bool, + is_zaplexified_ssh_session: bool, ) -> BootstrapSessionType { match get_local_hostname() { Ok(local_hostname) => { // Ensures subshells are treated as local if local_hostname == init_shell_value.hostname && // Ensures `ssh localhost` is treated as remote - !is_warpified_ssh_session + !is_zaplexified_ssh_session { BootstrapSessionType::Local } else { - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::ZaplexifiedRemote } } Err(e) => { @@ -681,10 +685,10 @@ impl SessionInfo { #[cfg(feature = "remote_tty")] fn determine_session_type( _init_shell_value: &InitShellValue, - _is_warpified_ssh_session: bool, + _is_zaplexified_ssh_session: bool, ) -> BootstrapSessionType { // When the `remote_tty` feature is enabled--the session is always considered remote. - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::ZaplexifiedRemote } /// Returns a fully populated [`SessionInfo`] containing data derived from the given @@ -832,33 +836,33 @@ impl SessionInfo { /// which happens *after* the session is bootstrapped. #[derive(Clone, Debug, PartialEq, Eq)] pub enum BootstrapSessionType { - /// The session host is the same host where Zap is running. + /// The session host is the same host where Zaplex is running. Local, - /// The session host is a different host from where Zap is running. - WarpifiedRemote, + /// The session host is a different host from where Zaplex is running. + ZaplexifiedRemote, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum SessionType { - /// The session host is the same host where Zap is running. + /// The session host is the same host where Zaplex is running. Local, - /// The session host is a different host from where Zap is running. - /// Note that we only know this for sure when we Warpify a block. + /// The session host is a different host from where Zaplex is running. + /// Note that we only know this for sure when we Zaplexify a block. /// /// `host_id` is `Some` when the remote server feature flag is enabled and /// `RemoteServerManager` has completed the connection handshake. It is /// `None` when the feature flag is off or the connection hasn't been /// established yet. - WarpifiedRemote { host_id: Option }, + ZaplexifiedRemote { host_id: Option }, } impl From for SessionType { fn from(bst: BootstrapSessionType) -> Self { match bst { BootstrapSessionType::Local => SessionType::Local, - BootstrapSessionType::WarpifiedRemote => SessionType::WarpifiedRemote { host_id: None }, + BootstrapSessionType::ZaplexifiedRemote => SessionType::ZaplexifiedRemote { host_id: None }, } } } @@ -925,11 +929,11 @@ impl Session { self.session_type.lock().clone() } - /// Updates the `host_id` on a `WarpifiedRemote` session type after the + /// Updates the `host_id` on a `ZaplexifiedRemote` session type after the /// remote server handshake completes (or clears it on disconnect). pub fn set_remote_host_id(&self, host_id: Option) { let mut st = self.session_type.lock(); - if let SessionType::WarpifiedRemote { host_id: ref mut h } = *st { + if let SessionType::ZaplexifiedRemote { host_id: ref mut h } = *st { *h = host_id; } } @@ -977,7 +981,7 @@ impl Session { } pub fn is_subshell_or_ssh(&self) -> bool { - matches!(self.session_type(), SessionType::WarpifiedRemote { .. }) + matches!(self.session_type(), SessionType::ZaplexifiedRemote { .. }) || self.is_legacy_ssh_session() || self.subshell_info().is_some() } @@ -1400,7 +1404,7 @@ impl Session { self.read_history_for_local_session(is_kaspersky_running) .await } - BootstrapSessionType::WarpifiedRemote => self.read_history_for_remote_session().await, + BootstrapSessionType::ZaplexifiedRemote => self.read_history_for_remote_session().await, } } @@ -1489,22 +1493,22 @@ impl Session { /// Converts the given directory into a [`typed_path::TypedPathBuf`]. pub fn convert_directory_to_typed_path_buf(&self, pwd: String) -> TypedPathBuf { // We need to determine whether this session requires windows file paths - // or unix file paths. This needs to be resilient to warpified ssh. Some examples: + // or unix file paths. This needs to be resilient to zaplexified ssh. Some examples: // - bash on mac ---> unix // - powershell on linux ---> unix // - powershell on windows ---> windows // - wsl on windows ---> unix - // - warpified zsh --> unix + // - zaplexified zsh --> unix // If the host architecture is unix, we can infer unix file paths. This would break - // if we supported warpifying a powershell-on-windows SSH session. + // if we supported zaplexifying a powershell-on-windows SSH session. if cfg!(unix) { return TypedPathBuf::from_unix(pwd); } // We assume that we're on Windows. match self.shell_family() { - // Cases: WSL, MSYS2, warpified bash + // Cases: WSL, MSYS2, zaplexified bash ShellFamily::Posix => TypedPathBuf::from_unix(pwd), // Cases: powershell sessions ShellFamily::PowerShell => TypedPathBuf::from_windows(pwd), @@ -1525,7 +1529,7 @@ impl Display for Session { } } -/// Returns the hostname for the local machine where Zap is running. +/// Returns the hostname for the local machine where Zaplex is running. pub fn get_local_hostname() -> Result { cfg_if::cfg_if! { if #[cfg(not(target_family = "wasm"))] { @@ -1634,7 +1638,7 @@ pub mod testing { pub fn with_ssh_socket_path(mut self, socket_path: PathBuf) -> Self { if let BootstrapSessionType::Local = self.session_type { - self.session_type = BootstrapSessionType::WarpifiedRemote; + self.session_type = BootstrapSessionType::ZaplexifiedRemote; } self.is_legacy_ssh_session = IsLegacySSHSession::Yes { socket_path }; self @@ -1697,7 +1701,7 @@ pub mod testing { pub fn test_remote() -> Self { let info = SessionInfo::new_for_test() - .with_session_type(BootstrapSessionType::WarpifiedRemote) + .with_session_type(BootstrapSessionType::ZaplexifiedRemote) .with_shell_type(ShellType::Bash); // We only support UNIX-based remote sessions. let session_type = SessionType::from(info.session_type.clone()); Self { diff --git a/app/src/terminal/model/session/command_executor.rs b/app/src/terminal/model/session/command_executor.rs index 4652b16ae9..a4fed1cf7b 100644 --- a/app/src/terminal/model/session/command_executor.rs +++ b/app/src/terminal/model/session/command_executor.rs @@ -198,7 +198,7 @@ fn new_command_executor_for_local_tty_session( if FeatureFlag::SSHTmuxWrapper.is_enabled() && session_info.tmux_control_mode - // We don't allow nested tmux warpification, so if our parent session is already warified using + // We don't allow nested tmux zaplexification, so if our parent session is already warified using // tmux then we shouldn't. && !parent_session_info.is_some_and(|s| s.tmux_control_mode) { @@ -313,7 +313,7 @@ fn new_command_executor_for_local_tty_session( } } } - BootstrapSessionType::WarpifiedRemote + BootstrapSessionType::ZaplexifiedRemote if is_legacy_ssh_session && !FeatureFlag::InBandGeneratorsForSSH.is_enabled() && !force_use_in_band_generators => @@ -336,7 +336,7 @@ fn new_command_executor_for_local_tty_session( // This code path exists as a fail-safe for disabling in-band // generators if some unforeseen severe issue surfaces during or // shortly after subshells launch. The setting that triggers this - // codepath is only accessible via a user defaults command that a Zap + // codepath is only accessible via a user defaults command that a Zaplex // engineer would have given to the user via some first-hand // correspondence (e.g. GitHub issues). log::info!("creating a no-op executor!"); diff --git a/app/src/terminal/model/session/command_executor/in_band_command_executor.rs b/app/src/terminal/model/session/command_executor/in_band_command_executor.rs index 37bb32ac30..88dae31f5d 100644 --- a/app/src/terminal/model/session/command_executor/in_band_command_executor.rs +++ b/app/src/terminal/model/session/command_executor/in_band_command_executor.rs @@ -383,7 +383,7 @@ impl CommandExecutor for InBandCommandExecutor { /// /// The given `command` is executed in the active session using the /// `warp_run_generator_command`/`Zap-Run-GeneratorCommand` shell script API that is declared as - /// part of Zap's bootstrap script. + /// part of Zaplex's bootstrap script. /// /// Internally, `command` is added to a queue of commands to be executed serially (this is to /// avoid output from multiple commands corrupting one another since the pty is a single diff --git a/app/src/terminal/model/session/command_executor/local_command_executor.rs b/app/src/terminal/model/session/command_executor/local_command_executor.rs index 89ff5bf5be..d602bcb628 100644 --- a/app/src/terminal/model/session/command_executor/local_command_executor.rs +++ b/app/src/terminal/model/session/command_executor/local_command_executor.rs @@ -175,7 +175,7 @@ impl LocalCommandExecutor { // This sets then environment variables, including the PATH var. // We need to run the command with the PATH var set because if the - // user opened Zap through a parent process that didn't have the PATH var set + // user opened Zaplex through a parent process that didn't have the PATH var set // (i.e. outside of a shell, for example opening the app via Finder), // the subshell won't inherit the PATH var, but we need the PATH var // to reference executables we might run as part of generators. diff --git a/app/src/terminal/model/terminal_model.rs b/app/src/terminal/model/terminal_model.rs index cae00e73b9..748f83e73a 100644 --- a/app/src/terminal/model/terminal_model.rs +++ b/app/src/terminal/model/terminal_model.rs @@ -26,7 +26,7 @@ pub use crate::terminal::history::HistoryEntry; use super::ansi::{ FinishUpdateValue, InputBufferValue, Mode, PendingHook, TmuxInstallFailedInfo, - WarpificationUnavailableReason, + ZaplexificationUnavailableReason, }; use super::block::{ AgentInteractionMetadata, Block, BlockId, BlockMetadata, BlockSize, BlocklistEnvVarMetadata, @@ -362,7 +362,7 @@ enum IsReceivingHook { No, } -/// Information needed to render a warpify "success" block upon successful subshell bootstrap. +/// Information needed to render a zaplexify "success" block upon successful subshell bootstrap. #[derive(Debug, Clone)] pub struct SubshellSuccessBlockInfo { /// The ID of the newly bootstrapped subshell session. @@ -382,11 +382,11 @@ pub struct SubshellSuccessBlockInfo { #[derive(Debug, Default, Clone, Copy, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] pub enum TmuxInstallationState { - /// This means tmux was installed by Zap in this session, successfully or unsuccessfully. + /// This means tmux was installed by Zaplex in this session, successfully or unsuccessfully. /// It also means we had root access and used a package manager to install tmux and all /// dependencies. InstalledByWarpRootInThisSession, - /// This means tmux was installed by Zap in this session, successfully or unsuccessfully. + /// This means tmux was installed by Zaplex in this session, successfully or unsuccessfully. InstalledByWarpInThisSession, InstalledByWarpInPriorSession, /// This means that warp did not install it locally. It was either installed by the user @@ -489,7 +489,7 @@ pub struct TerminalModel { pending_legacy_ssh_session: Option, /// This variable allows us to differentiate between warp-initiated and user-initiated invocations of - /// control mode. Whenever we attempt to warpify an ssh session, we track the context of when warp initiated + /// control mode. Whenever we attempt to zaplexify an ssh session, we track the context of when warp initiated /// control mode, indicating that we expect the shell to enter control mode. We reset to None whenever /// the active block finishes. If we enter control mode and option is None, then we know it's user-initiated. pending_warp_initiated_control_mode: Option, @@ -1011,7 +1011,7 @@ impl SelectedBlocks { pub enum TerminalInputState { /// Alt-screen on which programs like vim run is visible. AltScreen, - /// Zap Input View is visible. + /// Zaplex Input View is visible. InputEditor, /// Block-list is visible but input will go to the running command. LongRunningCommand, @@ -2176,7 +2176,7 @@ impl TerminalModel { pub fn set_custom_title(&mut self, custom_title: Option) { self.custom_title.clone_from(&custom_title); // If the custom title set by the user is None, we "reset" to whatever the title was set by - // the shell / Zap itself. + // the shell / Zaplex itself. self.send_title_event(match custom_title { Some(_) => custom_title, None => self.title.clone(), @@ -2255,7 +2255,7 @@ impl TerminalModel { /// a line of output that is not a known SSH output, we consider that to be some mild evidence that /// login is complete. Though, because that output line might be a false alarm (i.e., it could be /// an SSH banner OR a line like "Permission denied."), we wait some amount of time and check again - /// before indicating we're ready for warpification. + /// before indicating we're ready for zaplexification. pub fn check_for_end_of_ssh_login(&mut self, confirmation_check: bool) { let Some(mut ssh_login_state) = self.notify_on_end_of_ssh_login.clone() else { return; @@ -2278,7 +2278,7 @@ impl TerminalModel { SshLoginState::LastLogin | SshLoginState::PromptDetected => { self.event_proxy .send_terminal_event(Event::DetectedEndOfSshLogin( - SshLoginStatus::ReadyToWarpify, + SshLoginStatus::ReadyToZaplexify, )); ssh_login_state.notification_state = SshLoginNotificationState::Completed; @@ -2289,7 +2289,7 @@ impl TerminalModel { if ssh_login_state.notification_state == SshLoginNotificationState::Monitoring { self.event_proxy .send_terminal_event(Event::DetectedEndOfSshLogin( - SshLoginStatus::RecheckBeforeWarpifying, + SshLoginStatus::RecheckBeforeZaplexifying, )); // We want to avoid emitting redundant events for the initial check. @@ -2299,7 +2299,7 @@ impl TerminalModel { } else { self.event_proxy .send_terminal_event(Event::DetectedEndOfSshLogin( - SshLoginStatus::ReadyToWarpify, + SshLoginStatus::ReadyToZaplexify, )); ssh_login_state.notification_state = SshLoginNotificationState::Completed; @@ -2331,7 +2331,7 @@ impl TerminalModel { self.pending_warp_initiated_control_mode.is_some() } - pub fn is_warpified_ssh(&self) -> bool { + pub fn is_zaplexified_ssh(&self) -> bool { matches!( self.tmux_control_mode_context, Some(TmuxControlModeContext::WarpInitiatedForSsh { .. }) @@ -2969,8 +2969,8 @@ impl ansi::Handler for TerminalModel { ); if is_tmux_ssh { self.event_proxy - .send_terminal_event(Event::RemoteWarpificationIsUnavailable( - WarpificationUnavailableReason::UnsupportedShell { + .send_terminal_event(Event::RemoteZaplexificationIsUnavailable( + ZaplexificationUnavailableReason::UnsupportedShell { shell_name: data.shell, }, )) @@ -3016,8 +3016,8 @@ impl ansi::Handler for TerminalModel { })), _ => self .event_proxy - .send_terminal_event(Event::RemoteWarpificationIsUnavailable( - WarpificationUnavailableReason::UnsupportedShell { + .send_terminal_event(Event::RemoteZaplexificationIsUnavailable( + ZaplexificationUnavailableReason::UnsupportedShell { shell_name: data.shell, }, )), @@ -3029,9 +3029,9 @@ impl ansi::Handler for TerminalModel { .send_terminal_event(Event::FinishUpdate(data)); } - fn remote_warpification_is_unavailable(&mut self, data: WarpificationUnavailableReason) { + fn remote_zaplexification_is_unavailable(&mut self, data: ZaplexificationUnavailableReason) { self.event_proxy - .send_terminal_event(Event::RemoteWarpificationIsUnavailable(data)); + .send_terminal_event(Event::RemoteZaplexificationIsUnavailable(data)); } fn notify_ssh_tmux_is_installed(&mut self, tmux_installation: TmuxInstallationState) { diff --git a/app/src/terminal/model/terminal_model_test.rs b/app/src/terminal/model/terminal_model_test.rs index 06cb31bc2e..d18eabcda2 100644 --- a/app/src/terminal/model/terminal_model_test.rs +++ b/app/src/terminal/model/terminal_model_test.rs @@ -133,7 +133,7 @@ fn test_restored_blocks_on_different_host() { SerializedBlock { id: BlockId::new(), stylized_command: str_to_byte_vec("echo $TERM_PROGRAM"), - stylized_output: str_to_byte_vec("WarpTerminal"), + stylized_output: str_to_byte_vec("ZaplexTerminal"), pwd: Some("/".to_owned()), git_head: None, git_branch_name: None, @@ -295,7 +295,7 @@ fn test_restored_blocks_on_different_host() { } let blocks = model.block_list().blocks(); assert_eq!(blocks[0].command_to_string(), "echo $TERM_PROGRAM",); - assert_eq!(blocks[0].output_to_string(), "WarpTerminal",); + assert_eq!(blocks[0].output_to_string(), "ZaplexTerminal",); assert_eq!(blocks[1].command_to_string(), "pwd",); assert_eq!(blocks[1].output_to_string(), "/",); assert_eq!(blocks[2].command_to_string(), "uname",); diff --git a/app/src/terminal/model_events.rs b/app/src/terminal/model_events.rs index b76e5d4aca..538beace86 100644 --- a/app/src/terminal/model_events.rs +++ b/app/src/terminal/model_events.rs @@ -17,7 +17,7 @@ use warpui::SingletonEntity; use warpui::{Entity, ModelContext, ModelHandle}; use super::event::SshLoginStatus; -use super::model::ansi::{FinishUpdateValue, WarpificationUnavailableReason}; +use super::model::ansi::{FinishUpdateValue, ZaplexificationUnavailableReason}; use super::model::block::BlockId; use super::model::completions::ShellCompletion; use super::model::terminal_model::{ExitReason, TmuxControlModeContext, TmuxInstallationState}; @@ -197,7 +197,7 @@ impl ModelEventDispatcher { // Clip large durations to u64::MAX .min(u64::MAX as u128) as u64; send_telemetry_from_ctx!( - TelemetryEvent::SshTmuxWarpificationSuccess { + TelemetryEvent::SshTmuxZaplexificationSuccess { duration_ms, tmux_installation: control_mode.tmux_installation, }, @@ -249,8 +249,8 @@ impl ModelEventDispatcher { Event::DetectedEndOfSshLogin(check_type) => { ModelEvent::DetectedEndOfSshLogin(check_type) } - Event::RemoteWarpificationIsUnavailable(reason) => { - ModelEvent::RemoteWarpificationIsUnavailable(reason) + Event::RemoteZaplexificationIsUnavailable(reason) => { + ModelEvent::RemoteZaplexificationIsUnavailable(reason) } Event::SshTmuxInstaller(tmux_installation) => { ModelEvent::SshTmuxInstaller(tmux_installation) @@ -419,9 +419,9 @@ pub enum ModelEvent { }, /// Sent when a line of output from an interactive ssh session indicates login is complete. /// A line such as "Last login: Wed Oct 30" for example indicates login is complete. This is - /// useful for detecting when an ssh session becomes ready for warpification. + /// useful for detecting when an ssh session becomes ready for zaplexification. DetectedEndOfSshLogin(SshLoginStatus), - RemoteWarpificationIsUnavailable(WarpificationUnavailableReason), + RemoteZaplexificationIsUnavailable(ZaplexificationUnavailableReason), SshTmuxInstaller(TmuxInstallationState), TmuxInstallFailed { line: String, @@ -433,8 +433,8 @@ pub enum ModelEvent { InitSsh(InitSshEvent), /// Emitted when the active block's prompt has been updated. PromptUpdated, - /// Emitted when the honor_ps1 state of the shell is out-of-sync with Zap's settings. - /// This can happen in cases such as when the user changes between PS1 and Zap prompt inside + /// Emitted when the honor_ps1 state of the shell is out-of-sync with Zaplex's settings. + /// This can happen in cases such as when the user changes between PS1 and Zaplex prompt inside /// of an SSH session (the bindkeys are sent to the SSH session but not the local session, so /// they are out-of-sync when the user exits SSH). HonorPS1OutOfSync, diff --git a/app/src/terminal/prompt/mod.rs b/app/src/terminal/prompt/mod.rs index 857daeb8ee..63d24c23d9 100644 --- a/app/src/terminal/prompt/mod.rs +++ b/app/src/terminal/prompt/mod.rs @@ -22,7 +22,7 @@ pub fn user_and_host_name_string( ) -> Option { match session_type { SessionType::Local => None, - SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:")), + SessionType::ZaplexifiedRemote { .. } => Some(format!("{user}@{hostname}:")), } } diff --git a/app/src/terminal/prompt_render_helper.rs b/app/src/terminal/prompt_render_helper.rs index 1b360a4bed..bf4e0c0d58 100644 --- a/app/src/terminal/prompt_render_helper.rs +++ b/app/src/terminal/prompt_render_helper.rs @@ -66,7 +66,7 @@ pub fn should_render_ps1_prompt(terminal_model: &TerminalModel, app: &AppContext let session_settings = SessionSettings::as_ref(app); // In the context of session sharing, these values may differ from the local settings i.e. - // if the sharer is using PS1 and the viewer is not (using Zap prompt in non-SLP mode). + // if the sharer is using PS1 and the viewer is not (using Zaplex prompt in non-SLP mode). // In this case, we still want to render the prompt on the same line (PS1 should ALWAYS be // rendered on the same line). // Note that the product behavior for session sharing is normally to respect the local settings @@ -85,7 +85,7 @@ pub fn should_render_prompt_on_same_line( ) -> bool { // We render the prompt on the same line, in the input editor, if: // 1. The user is using a custom prompt (PS1) - // 2. The user has the same line prompt setting enabled for their Zap prompt. + // 2. The user has the same line prompt setting enabled for their Zaplex prompt. // If universal developer input is enabled, ignore PS1 rendering logic if is_universal_developer_input { diff --git a/app/src/terminal/remote_tty/event_loop.rs b/app/src/terminal/remote_tty/event_loop.rs index 7827026c10..a9649516b2 100644 --- a/app/src/terminal/remote_tty/event_loop.rs +++ b/app/src/terminal/remote_tty/event_loop.rs @@ -171,12 +171,12 @@ impl EventLoop { /// Writes environment variables that should be defined in the session /// before bootstrapping. This is a subset of the environment variables /// defined in `app/src/terminal/local_tty/unix.rs` that are necessary in - /// order to dogfood Zap on Web over the remote tty. + /// order to dogfood Zaplex on Web over the remote tty. async fn write_env_vars( sink: &mut impl Sink, is_honor_ps1_enabled: bool, ) -> anyhow::Result<()> { - let honor_ps1_env_var = format!(r#"WARP_HONOR_PS1="{}";"#, is_honor_ps1_enabled as u8); + let honor_ps1_env_var = format!(r#"ZAPLEX_HONOR_PS1="{}";"#, is_honor_ps1_enabled as u8); sink.send(Message::new_binary(honor_ps1_env_var.as_bytes().to_vec())) .await?; diff --git a/app/src/terminal/resizable_data.rs b/app/src/terminal/resizable_data.rs index 10ffe6fc17..ccf5b276d2 100644 --- a/app/src/terminal/resizable_data.rs +++ b/app/src/terminal/resizable_data.rs @@ -9,9 +9,9 @@ use warpui::WindowId; use crate::app_state::WindowSnapshot; pub const DEFAULT_UNIVERSAL_SEARCH_WIDTH: f32 = 700.; -pub const DEFAULT_WARP_AI_WIDTH: f32 = 410.; +pub const DEFAULT_ZAPLEX_AI_WIDTH: f32 = 410.; pub const DEFAULT_VOLTRON_WIDTH: f32 = 700.; -pub const DEFAULT_WARP_DRIVE_INDEX_WIDTH: f32 = 300.; +pub const DEFAULT_ZAPLEX_DRIVE_INDEX_WIDTH: f32 = 300.; pub const DEFAULT_SETTINGS_PANEL_WIDTH: f32 = 194.; pub const DEFAULT_LEFT_PANEL_WIDTH: f32 = 240.; pub const DEFAULT_RIGHT_PANEL_WIDTH: f32 = 480.; @@ -50,13 +50,13 @@ impl ModalSizes { .unwrap_or(DEFAULT_UNIVERSAL_SEARCH_WIDTH); let warp_ai_width = window_snapshot .warp_ai_width - .unwrap_or(DEFAULT_WARP_AI_WIDTH); + .unwrap_or(DEFAULT_ZAPLEX_AI_WIDTH); let voltron_width = window_snapshot .voltron_width .unwrap_or(DEFAULT_VOLTRON_WIDTH); let warp_drive_index_width = window_snapshot .warp_drive_index_width - .unwrap_or(DEFAULT_WARP_DRIVE_INDEX_WIDTH); + .unwrap_or(DEFAULT_ZAPLEX_DRIVE_INDEX_WIDTH); let settings_panel_width = DEFAULT_SETTINGS_PANEL_WIDTH; let left_panel_width = window_snapshot.left_panel_width.unwrap_or(left_panel_size); let right_panel_width = window_snapshot @@ -77,9 +77,9 @@ impl ModalSizes { pub fn default_with_panel_defaults(left_default: f32, right_default: f32) -> Self { ModalSizes { universal_search_width: resizable_state_handle(DEFAULT_UNIVERSAL_SEARCH_WIDTH), - warp_ai_width: resizable_state_handle(DEFAULT_WARP_AI_WIDTH), + warp_ai_width: resizable_state_handle(DEFAULT_ZAPLEX_AI_WIDTH), voltron_width: resizable_state_handle(DEFAULT_VOLTRON_WIDTH), - warp_drive_index_width: resizable_state_handle(DEFAULT_WARP_DRIVE_INDEX_WIDTH), + warp_drive_index_width: resizable_state_handle(DEFAULT_ZAPLEX_DRIVE_INDEX_WIDTH), settings_panel_width: resizable_state_handle(DEFAULT_SETTINGS_PANEL_WIDTH), left_panel_width: resizable_state_handle(left_default), right_panel_width: resizable_state_handle(right_default), @@ -105,9 +105,9 @@ impl Default for ModalSizes { fn default() -> Self { Self { universal_search_width: resizable_state_handle(DEFAULT_UNIVERSAL_SEARCH_WIDTH), - warp_ai_width: resizable_state_handle(DEFAULT_WARP_AI_WIDTH), + warp_ai_width: resizable_state_handle(DEFAULT_ZAPLEX_AI_WIDTH), voltron_width: resizable_state_handle(DEFAULT_VOLTRON_WIDTH), - warp_drive_index_width: resizable_state_handle(DEFAULT_WARP_DRIVE_INDEX_WIDTH), + warp_drive_index_width: resizable_state_handle(DEFAULT_ZAPLEX_DRIVE_INDEX_WIDTH), settings_panel_width: resizable_state_handle(DEFAULT_SETTINGS_PANEL_WIDTH), left_panel_width: resizable_state_handle(DEFAULT_LEFT_PANEL_WIDTH), right_panel_width: resizable_state_handle(DEFAULT_RIGHT_PANEL_WIDTH), diff --git a/app/src/terminal/secret_regex_updater.rs b/app/src/terminal/secret_regex_updater.rs index 5f55ca9f12..530d79dd4d 100644 --- a/app/src/terminal/secret_regex_updater.rs +++ b/app/src/terminal/secret_regex_updater.rs @@ -38,7 +38,7 @@ impl CustomSecretRegexUpdater { set_user_and_enterprise_secret_regexes(user_secrets, enterprise_secrets); - // Zap (Wave1-S4): the original telemetry-side `update_telemetry_secrets_regex` call + // Zaplex (Wave1-S4): the original telemetry-side `update_telemetry_secrets_regex` call // was removed entirely along with `server/telemetry/secret_redaction.rs`. The visual blurring of safety mode // is already fully covered by `set_user_and_enterprise_secret_regexes`; the telemetry-side // defence-in-depth redaction is now meaningless because there is no longer any outbound path. diff --git a/app/src/terminal/session_settings.rs b/app/src/terminal/session_settings.rs index ed5d6aa0ed..76b5b3f1ce 100644 --- a/app/src/terminal/session_settings.rs +++ b/app/src/terminal/session_settings.rs @@ -285,7 +285,7 @@ define_settings_group!(SessionSettings, settings: [ sync_to_cloud: SyncToCloud::Never, private: false, toml_path: "session.startup_shell_override", - description: "The shell to use when Zap starts up.", + description: "The shell to use when Zaplex starts up.", }, new_session_shell_override: NewSessionShellOverride { type: Option, @@ -303,7 +303,7 @@ define_settings_group!(SessionSettings, settings: [ sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, toml_path: "terminal.input.honor_ps1", - description: "Whether to use your shell's PS1 prompt instead of the Zap prompt.", + description: "Whether to use your shell's PS1 prompt instead of the Zaplex prompt.", }, saved_prompt: SavedPrompt { type: PromptSelection, diff --git a/app/src/terminal/shared_session/mod.rs b/app/src/terminal/shared_session/mod.rs index 34fd3d75a8..59afb97b37 100644 --- a/app/src/terminal/shared_session/mod.rs +++ b/app/src/terminal/shared_session/mod.rs @@ -21,7 +21,7 @@ pub mod presence_manager; pub mod render_util; pub(crate) mod selections; pub mod settings; -// Zap: removed share_modal (cloud shared session modal) +// Zaplex: removed share_modal (cloud shared session modal) pub mod viewer; #[cfg(test)] diff --git a/app/src/terminal/ssh/error.rs b/app/src/terminal/ssh/error.rs index 8c1dc36816..59acbfc4d8 100644 --- a/app/src/terminal/ssh/error.rs +++ b/app/src/terminal/ssh/error.rs @@ -1,9 +1,9 @@ use crate::appearance::Appearance; -use crate::terminal::model::ansi::WarpificationUnavailableReason; -use crate::terminal::warpify; -use crate::terminal::warpify::render::apply_spacing_styles; -use crate::terminal::warpify::render::build_description_row; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::model::ansi::ZaplexificationUnavailableReason; +use crate::terminal::zaplexify; +use crate::terminal::zaplexify::render::apply_spacing_styles; +use crate::terminal::zaplexify::render::build_description_row; +use crate::terminal::zaplexify::settings::ZaplexifySettings; use crate::ui_components::icons::Icon as UiIcon; use markdown_parser::FormattedText; use markdown_parser::FormattedTextFragment; @@ -35,7 +35,7 @@ const UNSUPPORTED_TMUX_VERSION_ERROR: &str = "The tmux version available on the remote machine is below 3.0. Please install tmux 3.0 or greater using a different method and try again."; const TMUX_FAILED_ERROR: &str = "tmux failed to execute on the remote machine. Please re-install tmux and try again."; -const WARPIFY_TIMEOUT_ERROR: &str = "Warpifying the session hit a timeout."; +const ZAPLEXIFY_TIMEOUT_ERROR: &str = "Zaplexifying the session hit a timeout."; const UNSUPPORTED_SHELL_ERROR: &str = "Unsupported shell. Please set bash, zsh, or fish as your default shell and try again."; const TMUX_INSTALL_FAILED_ERROR: &str = @@ -55,64 +55,64 @@ fn get_ssh_github_issue_url(title: &str) -> String { format!("{url}&title={title}") } -impl WarpificationUnavailableReason { +impl ZaplexificationUnavailableReason { fn error_message(&self) -> &'static str { match self { - WarpificationUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR, - WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => { + ZaplexificationUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR, + ZaplexificationUnavailableReason::UnsupportedTmuxVersion { .. } => { UNSUPPORTED_TMUX_VERSION_ERROR } - WarpificationUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR, - WarpificationUnavailableReason::Timeout { .. } => WARPIFY_TIMEOUT_ERROR, - WarpificationUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR, - WarpificationUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR, + ZaplexificationUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR, + ZaplexificationUnavailableReason::Timeout { .. } => ZAPLEXIFY_TIMEOUT_ERROR, + ZaplexificationUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR, + ZaplexificationUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR, } } fn error_title(&self) -> &'static str { match self { - WarpificationUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed", - WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => { + ZaplexificationUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed", + ZaplexificationUnavailableReason::UnsupportedTmuxVersion { .. } => { "Unsupported Tmux Version" } - WarpificationUnavailableReason::TmuxFailed => "tmux Failed", - WarpificationUnavailableReason::Timeout { + ZaplexificationUnavailableReason::TmuxFailed => "tmux Failed", + ZaplexificationUnavailableReason::Timeout { is_tmux_install, .. } => { if *is_tmux_install { "tmux Install Timeout" } else { - "SSH Warpify Timeout" + "SSH Zaplexify Timeout" } } - WarpificationUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell", - WarpificationUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed", + ZaplexificationUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell", + ZaplexificationUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed", } } } #[derive(Debug, Clone)] pub enum SshErrorBlockEvent { - ContinueWithoutWarpification, - WarpifyWithoutTmux, + ContinueWithoutZaplexification, + ZaplexifyWithoutTmux, } #[derive(Debug, Clone)] pub enum SshErrorBlockAction { - ContinueWithoutWarpification, - WarpifyWithoutTmux, + ContinueWithoutZaplexification, + ZaplexifyWithoutTmux, OpenUrl(String), AddSshHostToDenylist(String), Focus, } pub struct SshErrorBlock { - error_reason: WarpificationUnavailableReason, + error_reason: ZaplexificationUnavailableReason, ssh_host: Option, - warpify_without_tmux_button_mouse_state: MouseStateHandle, + zaplexify_without_tmux_button_mouse_state: MouseStateHandle, continue_button_mouse_state: MouseStateHandle, report_link_highlight_index: HighlightedHyperlink, - never_warpify_mouse_state_handle: MouseStateHandle, + never_zaplexify_mouse_state_handle: MouseStateHandle, block_mouse_state: MouseStateHandle, is_focused: bool, } @@ -123,17 +123,17 @@ pub fn init(app: &mut AppContext) { app.register_fixed_bindings([ FixedBinding::new( "enter", - SshErrorBlockAction::WarpifyWithoutTmux, + SshErrorBlockAction::ZaplexifyWithoutTmux, id!(SshErrorBlock::ui_name()), ), FixedBinding::new( "escape", - SshErrorBlockAction::ContinueWithoutWarpification, + SshErrorBlockAction::ContinueWithoutZaplexification, id!(SshErrorBlock::ui_name()), ), FixedBinding::new( "ctrl-c", - SshErrorBlockAction::ContinueWithoutWarpification, + SshErrorBlockAction::ContinueWithoutZaplexification, id!(SshErrorBlock::ui_name()), ), ]); @@ -141,14 +141,14 @@ pub fn init(app: &mut AppContext) { impl SshErrorBlock { #[allow(clippy::new_without_default)] - pub fn new(error_reason: WarpificationUnavailableReason, ssh_host: Option) -> Self { + pub fn new(error_reason: ZaplexificationUnavailableReason, ssh_host: Option) -> Self { Self { error_reason, ssh_host, - warpify_without_tmux_button_mouse_state: Default::default(), + zaplexify_without_tmux_button_mouse_state: Default::default(), continue_button_mouse_state: Default::default(), report_link_highlight_index: Default::default(), - never_warpify_mouse_state_handle: Default::default(), + never_zaplexify_mouse_state_handle: Default::default(), block_mouse_state: Default::default(), is_focused: false, } @@ -162,8 +162,8 @@ impl SshErrorBlock { fn should_show_report_to_warp_button(&self) -> bool { matches!( self.error_reason, - WarpificationUnavailableReason::Timeout { .. } - | WarpificationUnavailableReason::TmuxInstallFailed { .. } + ZaplexificationUnavailableReason::Timeout { .. } + | ZaplexificationUnavailableReason::TmuxInstallFailed { .. } ) } @@ -173,8 +173,8 @@ impl SshErrorBlock { theme: &WarpTheme, appearance: &Appearance, ) -> Box { - let header_contents = warpify::render::build_header_row( - "Error Warpifying session", + let header_contents = zaplexify::render::build_header_row( + "Error Zaplexifying session", Icon::new(UiIcon::AlertTriangle.into(), theme.ui_error_color()), theme, appearance, @@ -182,11 +182,11 @@ impl SshErrorBlock { .with_margin_right(8.) .finish(); - let right_hand_size = warpify::render::render_never_warpify_ssh_link( + let right_hand_size = zaplexify::render::render_never_zaplexify_ssh_link( &self.ssh_host, app, appearance, - self.never_warpify_mouse_state_handle.clone(), + self.never_zaplexify_mouse_state_handle.clone(), move |ctx, ssh_host| { ctx.dispatch_typed_action(SshErrorBlockAction::AddSshHostToDenylist( ssh_host.to_owned(), @@ -204,7 +204,7 @@ impl SshErrorBlock { row.add_child(right_hand_size); } - warpify::render::apply_spacing_styles(Container::new(row.finish())).finish() + zaplexify::render::apply_spacing_styles(Container::new(row.finish())).finish() } } @@ -227,7 +227,7 @@ impl View for SshErrorBlock { content.add_child(self.render_title_ui(app, theme, appearance)); - content.add_child(warpify::render::description_row( + content.add_child(zaplexify::render::description_row( self.error_reason.error_message(), theme, appearance, @@ -268,9 +268,9 @@ impl View for SshErrorBlock { ui_builder .button( ButtonVariant::Accent, - self.warpify_without_tmux_button_mouse_state.clone(), + self.zaplexify_without_tmux_button_mouse_state.clone(), ) - .with_centered_text_label(crate::t!("terminal-warpify-without-tmux")) + .with_centered_text_label(crate::t!("terminal-zaplexify-without-tmux")) .with_style(UiComponentStyles { font_size: Some(appearance.monospace_font_size()), ..Default::default() @@ -278,7 +278,7 @@ impl View for SshErrorBlock { .build() .with_cursor(Cursor::PointingHand) .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(SshErrorBlockAction::WarpifyWithoutTmux) + ctx.dispatch_typed_action(SshErrorBlockAction::ZaplexifyWithoutTmux) }) .finish(), ) @@ -291,7 +291,7 @@ impl View for SshErrorBlock { ButtonVariant::Secondary, self.continue_button_mouse_state.clone(), ) - .with_centered_text_label(crate::t!("terminal-continue-without-warpification")) + .with_centered_text_label(crate::t!("terminal-continue-without-zaplexification")) .with_style(UiComponentStyles { font_size: Some(appearance.monospace_font_size()), ..Default::default() @@ -299,7 +299,7 @@ impl View for SshErrorBlock { .build() .with_cursor(Cursor::PointingHand) .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWarpification) + ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutZaplexification) }) .finish(), ); @@ -343,21 +343,21 @@ impl TypedActionView for SshErrorBlock { fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - SshErrorBlockAction::WarpifyWithoutTmux => { - ctx.emit(SshErrorBlockEvent::WarpifyWithoutTmux) + SshErrorBlockAction::ZaplexifyWithoutTmux => { + ctx.emit(SshErrorBlockEvent::ZaplexifyWithoutTmux) } - SshErrorBlockAction::ContinueWithoutWarpification => { - ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification) + SshErrorBlockAction::ContinueWithoutZaplexification => { + ctx.emit(SshErrorBlockEvent::ContinueWithoutZaplexification) } SshErrorBlockAction::OpenUrl(url) => { ctx.open_url(url); } SshErrorBlockAction::AddSshHostToDenylist(ssh_host) => { - let settings = WarpifySettings::handle(ctx); - settings.update(ctx, |warpify, ctx| { - warpify.denylist_ssh_host(ssh_host, ctx); + let settings = ZaplexifySettings::handle(ctx); + settings.update(ctx, |zaplexify, ctx| { + zaplexify.denylist_ssh_host(ssh_host, ctx); }); - ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification); + ctx.emit(SshErrorBlockEvent::ContinueWithoutZaplexification); ctx.notify() } SshErrorBlockAction::Focus => { diff --git a/app/src/terminal/ssh/install_tmux.rs b/app/src/terminal/ssh/install_tmux.rs index 9e9ca955e5..6d847261e4 100644 --- a/app/src/terminal/ssh/install_tmux.rs +++ b/app/src/terminal/ssh/install_tmux.rs @@ -6,8 +6,8 @@ use crate::ai::blocklist::inline_action::requested_script::{RequestedScriptStatu use crate::appearance::Appearance; use crate::terminal::model::ansi::SystemDetails; use crate::terminal::model::escape_sequences; -use crate::terminal::warpify::render; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::zaplexify::render; +use crate::terminal::zaplexify::settings::ZaplexifySettings; use crate::ui_components::blended_colors; use crate::ui_components::icons::Icon as UiIcon; use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine}; @@ -35,7 +35,7 @@ pub struct TmuxInstallMethod { #[derive(Debug, Clone)] pub enum SshInstallTmuxBlockEvent { - InstallTmuxAndWarpify(TmuxInstallMethod), + InstallTmuxAndZaplexify(TmuxInstallMethod), ToggleScriptVisibility, Cancel, Interrupt, @@ -89,7 +89,7 @@ impl SshKeyEvent { pub struct SshInstallTmuxBlock { requested_script_mouse_states: RequestedScriptMouseStates, why_install_tmux_highlight_index: HighlightedHyperlink, - never_warpify_mouse_state_handle: MouseStateHandle, + never_zaplexify_mouse_state_handle: MouseStateHandle, block_mouse_state: MouseStateHandle, is_focused: bool, is_collapsed: bool, @@ -167,7 +167,7 @@ impl SshInstallTmuxBlock { Self { requested_script_mouse_states: Default::default(), why_install_tmux_highlight_index: Default::default(), - never_warpify_mouse_state_handle: Default::default(), + never_zaplexify_mouse_state_handle: Default::default(), block_mouse_state: Default::default(), is_focused: false, is_collapsed: true, @@ -220,7 +220,7 @@ impl SshInstallTmuxBlock { ctx: &mut ViewContext, ) { self.script_status = RequestedScriptStatus::Running; - ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWarpify( + ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndZaplexify( install_method, )); ctx.notify() @@ -320,7 +320,7 @@ impl SshInstallTmuxBlock { ) -> Box { let header_contents = render::build_header_row( "Install tmux?", - Icon::new(UiIcon::Zap.into(), theme.active_ui_detail()), + Icon::new(UiIcon::Zaplex.into(), theme.active_ui_detail()), theme, appearance, ) @@ -331,11 +331,11 @@ impl SshInstallTmuxBlock { let right_hand_size = is_awaiting_action .then(|| { - render::render_never_warpify_ssh_link( + render::render_never_zaplexify_ssh_link( &self.ssh_host, app, appearance, - self.never_warpify_mouse_state_handle.clone(), + self.never_zaplexify_mouse_state_handle.clone(), move |ctx, ssh_host| { ctx.dispatch_typed_action(SshInstallTmuxBlockAction::AddSshHostToDenylist( ssh_host.to_owned(), @@ -377,12 +377,12 @@ impl View for SshInstallTmuxBlock { ); let explanation = if self.outdated_version { - "In order to Warpify your SSH session, a more recent version of tmux (>=3.0) must be installed. " + "In order to Zaplexify your SSH session, a more recent version of tmux (>=3.0) must be installed. " } else { - "In order to Warpify your SSH session, tmux must be installed. " + "In order to Zaplexify your SSH session, tmux must be installed. " }; - let warpify_description = vec![ + let zaplexify_description = vec![ FormattedTextFragment::plain_text(explanation), FormattedTextFragment::hyperlink( crate::t!("terminal-ssh-why-need-tmux"), @@ -393,8 +393,8 @@ impl View for SshInstallTmuxBlock { let text_color = blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()); - let warpify_description = FormattedTextElement::new( - FormattedText::new([FormattedTextLine::Line(warpify_description)]), + let zaplexify_description = FormattedTextElement::new( + FormattedText::new([FormattedTextLine::Line(zaplexify_description)]), appearance.monospace_font_size(), appearance.monospace_font_family(), appearance.monospace_font_family(), @@ -409,7 +409,7 @@ impl View for SshInstallTmuxBlock { .finish(); content - .add_child(render::apply_spacing_styles(Container::new(warpify_description)).finish()); + .add_child(render::apply_spacing_styles(Container::new(zaplexify_description)).finish()); if let Some(root_install_state) = &self.system_install_state { content.add_child(self.render_system_install_ui(root_install_state, app)); @@ -494,9 +494,9 @@ impl TypedActionView for SshInstallTmuxBlock { ctx.emit(SshInstallTmuxBlockEvent::Interrupt); } (SshInstallTmuxBlockAction::AddSshHostToDenylist(ssh_host), true) => { - let settings = WarpifySettings::handle(ctx); - settings.update(ctx, |warpify, ctx| { - warpify.denylist_ssh_host(ssh_host, ctx); + let settings = ZaplexifySettings::handle(ctx); + settings.update(ctx, |zaplexify, ctx| { + zaplexify.denylist_ssh_host(ssh_host, ctx); }); ctx.emit(SshInstallTmuxBlockEvent::Cancel); ctx.notify(); @@ -523,16 +523,16 @@ pub fn install_tmux_script(system: &SystemDetails, app: &AppContext) -> Option { - bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_linux.sh") + bundled_asset!("ssh/bash_zsh/install_tmux_and_zaplexify_linux.sh") } ("Linux", _, "fish") => { - bundled_asset!("ssh/fish/install_tmux_and_warpify_linux.sh") + bundled_asset!("ssh/fish/install_tmux_and_zaplexify_linux.sh") } ("Darwin", "homebrew", "bash" | "zsh") => { - bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_brew.sh") + bundled_asset!("ssh/bash_zsh/install_tmux_and_zaplexify_brew.sh") } ("Darwin", "homebrew", "fish") => { - bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh") + bundled_asset!("ssh/fish/install_tmux_and_zaplexify_brew.sh") } _ => return None, }; @@ -561,19 +561,19 @@ pub fn install_root_tmux_script( system.shell.as_str(), ) { ("Linux", "apt", "bash" | "zsh") => { - bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_apt.sh") + bundled_asset!("ssh/bash_zsh/root/install_tmux_and_zaplexify_apt.sh") } ("Linux", "dnf", "bash" | "zsh") => { - bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_dnf.sh") + bundled_asset!("ssh/bash_zsh/root/install_tmux_and_zaplexify_dnf.sh") } ("Linux", "pacman", "bash" | "zsh") => { - bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_pacman.sh") + bundled_asset!("ssh/bash_zsh/root/install_tmux_and_zaplexify_pacman.sh") } ("Linux", "yum", "bash" | "zsh") => { - bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_yum.sh") + bundled_asset!("ssh/bash_zsh/root/install_tmux_and_zaplexify_yum.sh") } ("Linux", "zypper", "bash" | "zsh") => { - bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_zypper.sh") + bundled_asset!("ssh/bash_zsh/root/install_tmux_and_zaplexify_zypper.sh") } _ => return None, }; diff --git a/app/src/terminal/ssh/mod.rs b/app/src/terminal/ssh/mod.rs index 59729d266f..1f36c677e2 100644 --- a/app/src/terminal/ssh/mod.rs +++ b/app/src/terminal/ssh/mod.rs @@ -5,6 +5,6 @@ pub mod install_tmux; pub mod root_access; pub mod ssh_detection; pub mod util; -pub mod warpify; +pub mod zaplexify; -pub const SSH_WARPIFY_TIMEOUT_DURATION: Duration = Duration::from_secs(8); +pub const SSH_ZAPLEXIFY_TIMEOUT_DURATION: Duration = Duration::from_secs(8); diff --git a/app/src/terminal/ssh/ssh_detection.rs b/app/src/terminal/ssh/ssh_detection.rs index 3c2eab2ec5..856e94b340 100644 --- a/app/src/terminal/ssh/ssh_detection.rs +++ b/app/src/terminal/ssh/ssh_detection.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use warp_core::{features::FeatureFlag, settings::Setting}; use warp_util::path::ShellFamily; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::zaplexify::settings::ZaplexifySettings; /// The different possible outcomes of detecting an interactive SSH session. /// Also the payload for the [`crate::server::telemetry::TelemetryEvent::SshInteractiveSessionDetected`] event. @@ -12,8 +12,8 @@ pub enum SshInteractiveSessionDetected { FeatureDisabled, #[serde(rename = "host_denylisted")] HostDenylisted, - #[serde(rename = "warpify_prompt")] - ShouldPromptWarpification { + #[serde(rename = "zaplexify_prompt")] + ShouldPromptZaplexification { #[serde(skip)] command: String, #[serde(skip)] @@ -21,17 +21,17 @@ pub enum SshInteractiveSessionDetected { }, } -/// Determines whether a host could be warpified. -pub fn evaluate_warpify_ssh_host( +/// Determines whether a host could be zaplexified. +pub fn evaluate_zaplexify_ssh_host( command: &str, ssh_host: Option<&str>, shell_family: ShellFamily, - warpify_settings: &WarpifySettings, + zaplexify_settings: &ZaplexifySettings, ) -> SshInteractiveSessionDetected { - let should_prompt_ssh_tmux_wrapper = *warpify_settings.enable_ssh_warpification.value() - && *warpify_settings.use_ssh_tmux_wrapper.value(); - let matches_subshell = warpify_settings.is_denylisted_subshell_command(command) - || warpify_settings.is_compatible_subshell_command(command, shell_family); + let should_prompt_ssh_tmux_wrapper = *zaplexify_settings.enable_ssh_zaplexification.value() + && *zaplexify_settings.use_ssh_tmux_wrapper.value(); + let matches_subshell = zaplexify_settings.is_denylisted_subshell_command(command) + || zaplexify_settings.is_compatible_subshell_command(command, shell_family); if !should_prompt_ssh_tmux_wrapper || matches_subshell || !FeatureFlag::SSHTmuxWrapper.is_enabled() @@ -40,12 +40,12 @@ pub fn evaluate_warpify_ssh_host( } if let Some(ssh_host) = ssh_host { - if warpify_settings.is_ssh_host_denylisted(ssh_host) { + if zaplexify_settings.is_ssh_host_denylisted(ssh_host) { return SshInteractiveSessionDetected::HostDenylisted; } } - SshInteractiveSessionDetected::ShouldPromptWarpification { + SshInteractiveSessionDetected::ShouldPromptZaplexification { host: ssh_host.map(|host| host.to_owned()), command: command.to_string(), } diff --git a/app/src/terminal/ssh/util.rs b/app/src/terminal/ssh/util.rs index 5fbc1179f5..99ab7a85f3 100644 --- a/app/src/terminal/ssh/util.rs +++ b/app/src/terminal/ssh/util.rs @@ -78,7 +78,7 @@ pub fn check_ssh_login_state(block_output: &str) -> SshLoginState { } /// Represents the parsed components of an interactive SSH command. -/// For some [`SshWarpifyCommand`]s, we do not support parsing +/// For some [`SshZaplexifyCommand`]s, we do not support parsing /// a host or port In these cases, we can still parse to a valid /// empty `InteractiveSshCommand` to indicate that we did /// successfully detect an interactive SSH command. @@ -147,21 +147,21 @@ pub enum SshLikeCommand { DigitalOceanDroplet, } -/// TMUX SSH Warpification can be triggered by any command that +/// TMUX SSH Zaplexification can be triggered by any command that /// we determine to be an interactive SSH command. This enum /// represents the different types of SSH commands we support -/// for TMUX Warpification. `Ssh` means a literal `ssh` command, +/// for TMUX Zaplexification. `Ssh` means a literal `ssh` command, /// where all other commands are categorized as SSH-like commands. -pub enum SshWarpifyCommand { +pub enum SshZaplexifyCommand { Ssh, SshLike(SshLikeCommand), } -impl SshWarpifyCommand { +impl SshZaplexifyCommand { /// Not a literal `ssh` command, but another command that starts an interactive SSH - /// session that we can Warpify with TMUX. + /// session that we can Zaplexify with TMUX. pub fn is_ssh_like_command(&self) -> bool { - matches!(self, SshWarpifyCommand::SshLike(_)) + matches!(self, SshZaplexifyCommand::SshLike(_)) } } @@ -178,21 +178,21 @@ lazy_static! { static ref DIGITAL_OCEAN_DROPLET_REGEX: Regex = Regex::new(r"^doctl\s+compute\s+ssh\s.+").expect("digital ocean SSH regex invalid"); } -impl SshWarpifyCommand { - pub fn matches(command: &str) -> Option { +impl SshZaplexifyCommand { + pub fn matches(command: &str) -> Option { let command = if let Some(suffix) = command.strip_prefix("command ") { suffix } else { command }; if INTERACTIVE_SSH.is_match(command) { - Some(SshWarpifyCommand::Ssh) + Some(SshZaplexifyCommand::Ssh) } else if GCLOUD_REGEX.is_match(command) { - Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) + Some(SshZaplexifyCommand::SshLike(SshLikeCommand::Gcloud)) } else if ELASTIC_BEANSTALK_REGEX.is_match(command) { - Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) + Some(SshZaplexifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) } else if DIGITAL_OCEAN_DROPLET_REGEX.is_match(command) { - Some(SshWarpifyCommand::SshLike( + Some(SshZaplexifyCommand::SshLike( SshLikeCommand::DigitalOceanDroplet, )) } else { @@ -202,15 +202,15 @@ impl SshWarpifyCommand { } pub fn parse_interactive_ssh_command(command: &str) -> Option { - match SshWarpifyCommand::matches(command) { - Some(SshWarpifyCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command), - Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) => { + match SshZaplexifyCommand::matches(command) { + Some(SshZaplexifyCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command), + Some(SshZaplexifyCommand::SshLike(SshLikeCommand::Gcloud)) => { Some(InteractiveSshCommand::default()) } - Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => { + Some(SshZaplexifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => { Some(InteractiveSshCommand::default()) } - Some(SshWarpifyCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => { + Some(SshZaplexifyCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => { Some(InteractiveSshCommand::default()) } None => None, @@ -229,7 +229,7 @@ fn parse_ssh_command_tokens(command: &str) -> Option> { Some(tokens) } -/// Creates an sftp command that copies a given local file into the pwd in the warpified ssh session. +/// Creates an sftp command that copies a given local file into the pwd in the zaplexified ssh session. pub fn transfer_file_sftp_command( local_file_path: String, ssh_host: String, diff --git a/app/src/terminal/ssh/warpify.rs b/app/src/terminal/ssh/zaplexify.rs similarity index 69% rename from app/src/terminal/ssh/warpify.rs rename to app/src/terminal/ssh/zaplexify.rs index 6ad403c9b4..23d357b6a5 100644 --- a/app/src/terminal/ssh/warpify.rs +++ b/app/src/terminal/ssh/zaplexify.rs @@ -6,8 +6,8 @@ use warpui::assets::asset_cache::{AssetCache, AssetState}; use crate::ai::blocklist::inline_action::requested_action::RenderableAction; use crate::appearance::Appearance; use crate::terminal::shell::ShellType; -use crate::terminal::warpify; -use crate::terminal::warpify::render::SSH_DOCS_URL; +use crate::terminal::zaplexify; +use crate::terminal::zaplexify::render::SSH_DOCS_URL; use crate::ui_components::icons::Icon as UiIcon; use warpui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle}; use warpui::keymap::FixedBinding; @@ -18,19 +18,19 @@ use warpui::{ }; #[derive(Debug, Clone)] -pub enum SshWarpifyBlockEvent { - WarpifySession, +pub enum SshZaplexifyBlockEvent { + ZaplexifySession, Cancel, Interrupt, } #[derive(Debug, Clone, Eq, PartialEq)] -pub enum SshWarpifyBlockAction { +pub enum SshZaplexifyBlockAction { Interrupt, Focus, } -pub struct SshWarpifyBlock { +pub struct SshZaplexifyBlock { block_mouse_state: MouseStateHandle, ssh_command: String, } @@ -40,12 +40,12 @@ pub fn init(app: &mut AppContext) { app.register_fixed_bindings([FixedBinding::new( "ctrl-c", - SshWarpifyBlockAction::Interrupt, - id!(SshWarpifyBlock::ui_name()), + SshZaplexifyBlockAction::Interrupt, + id!(SshZaplexifyBlock::ui_name()), )]); } -impl SshWarpifyBlock { +impl SshZaplexifyBlock { #[allow(clippy::new_without_default)] pub fn new(ssh_command: String) -> Self { Self { @@ -60,18 +60,18 @@ impl SshWarpifyBlock { } } -impl Entity for SshWarpifyBlock { - type Event = SshWarpifyBlockEvent; +impl Entity for SshZaplexifyBlock { + type Event = SshZaplexifyBlockEvent; } -impl SshWarpifyBlock { +impl SshZaplexifyBlock { fn render_title_ui(&self, theme: &WarpTheme, appearance: &Appearance) -> Box { - let icon = Icon::new(UiIcon::Zap.into(), theme.active_ui_detail()); - warpify::render::header_row("Warpifying SSH Session...", icon, theme, appearance) + let icon = Icon::new(UiIcon::Zaplex.into(), theme.active_ui_detail()); + zaplexify::render::header_row("Zaplexifying SSH Session...", icon, theme, appearance) } } -pub fn warpify_description( +pub fn zaplexify_description( app: &AppContext, hyperlink_index: &HighlightedHyperlink, ) -> Box { @@ -80,11 +80,11 @@ pub fn warpify_description( let description = FormattedText::new(vec![FormattedTextLine::Line(vec![ FormattedTextFragment::plain_text( - "Bring Zap's features to your remote session. Blocks, full text editing, auto-complete, Oz, and more. " + "Bring Zaplex's features to your remote session. Blocks, full text editing, auto-complete, Oz, and more. " ), FormattedTextFragment::hyperlink(crate::t!("common-learn-more"), SSH_DOCS_URL), ])]); - warpify::render::build_description_row(description, theme, appearance, hyperlink_index.clone()) + zaplexify::render::build_description_row(description, theme, appearance, hyperlink_index.clone()) .with_hyperlink_font_color(appearance.theme().accent().into_solid()) .register_default_click_handlers(|url, _, ctx| { ctx.open_url(&url.url); @@ -92,9 +92,9 @@ pub fn warpify_description( .finish() } -impl View for SshWarpifyBlock { +impl View for SshZaplexifyBlock { fn ui_name() -> &'static str { - "SshWarpifyBlock" + "SshZaplexifyBlock" } fn render(&self, app: &AppContext) -> Box { @@ -124,39 +124,39 @@ impl View for SshWarpifyBlock { .finish() }) .on_click(|ctx, _, _| { - ctx.dispatch_typed_action(SshWarpifyBlockAction::Focus); + ctx.dispatch_typed_action(SshZaplexifyBlockAction::Focus); }) .finish() } } -impl TypedActionView for SshWarpifyBlock { - type Action = SshWarpifyBlockAction; +impl TypedActionView for SshZaplexifyBlock { + type Action = SshZaplexifyBlockAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - SshWarpifyBlockAction::Interrupt => { - ctx.emit(SshWarpifyBlockEvent::Interrupt); + SshZaplexifyBlockAction::Interrupt => { + ctx.emit(SshZaplexifyBlockEvent::Interrupt); } - SshWarpifyBlockAction::Focus => { + SshZaplexifyBlockAction::Focus => { self.focus(ctx); } } } } -/// Convert the begin_warpify_ssh_session script into a string. -pub fn begin_warpify_ssh_session_command(app: &AppContext) -> String { +/// Convert the begin_zaplexify_ssh_session script into a string. +pub fn begin_zaplexify_ssh_session_command(app: &AppContext) -> String { let asset = bundled_asset!("bootstrap/unknown_init_subshell.sh"); match AssetCache::as_ref(app).load_asset::(asset) { AssetState::Loaded { data } => data.to_string().replace("HOOK_NAME", "InitSsh"), - _ => panic!("ssh begin warpify script should be available as a string"), + _ => panic!("ssh begin zaplexify script should be available as a string"), } } -/// Convert the warpify_ssh_session script into a string. -pub fn warpify_ssh_session_command( +/// Convert the zaplexify_ssh_session script into a string. +pub fn zaplexify_ssh_session_command( uname: &str, shell_type: ShellType, app: &AppContext, @@ -164,14 +164,14 @@ pub fn warpify_ssh_session_command( let asset = match (uname, shell_type) { // Mac scripts must be less than 1020 characters due to macOS 15+ pty issue ("Darwin", ShellType::Zsh | ShellType::Bash) => { - bundled_asset!("ssh/bash_zsh/warpify_ssh_session_mac.sh") + bundled_asset!("ssh/bash_zsh/zaplexify_ssh_session_mac.sh") } // Mac scripts must be less than 1020 characters due to macOS 15+ pty issue - ("Darwin", ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session_mac.sh"), + ("Darwin", ShellType::Fish) => bundled_asset!("ssh/fish/zaplexify_ssh_session_mac.sh"), (_, ShellType::Zsh | ShellType::Bash) => { - bundled_asset!("ssh/bash_zsh/warpify_ssh_session.sh") + bundled_asset!("ssh/bash_zsh/zaplexify_ssh_session.sh") } - (_, ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session.sh"), + (_, ShellType::Fish) => bundled_asset!("ssh/fish/zaplexify_ssh_session.sh"), // PowerShell is not supported yet. (_, ShellType::PowerShell) => return None, }; @@ -179,9 +179,9 @@ pub fn warpify_ssh_session_command( // Todo(Jack): look into avoiding an allocation here. match AssetCache::as_ref(app).load_asset::(asset) { AssetState::Loaded { data } => Some(data.to_string()), - _ => panic!("ssh warpify script should be available as a string"), + _ => panic!("ssh zaplexify script should be available as a string"), } } #[cfg(test)] -#[path = "warpify_test.rs"] +#[path = "zaplexify_test.rs"] mod tests; diff --git a/app/src/terminal/ssh/warpify_test.rs b/app/src/terminal/ssh/zaplexify_test.rs similarity index 77% rename from app/src/terminal/ssh/warpify_test.rs rename to app/src/terminal/ssh/zaplexify_test.rs index a3fd89c3d4..e35ede4f95 100644 --- a/app/src/terminal/ssh/warpify_test.rs +++ b/app/src/terminal/ssh/zaplexify_test.rs @@ -37,50 +37,50 @@ fn get_script(asset_source: AssetSource, ctx: &AppContext) -> String { #[cfg_attr(windows, ignore = "TODO(CORE-3626)")] #[test] /// See [assert_script_is_short_enough_mac] for more information. -fn test_mac_warpification_script_size() { +fn test_mac_zaplexification_script_size() { App::test(Assets, |mut app| async move { initialize_app(&mut app); app.read(|ctx| { assert_script_is_short_enough_mac( - &begin_warpify_ssh_session_command(ctx), + &begin_zaplexify_ssh_session_command(ctx), "unknown_init_subshell.sh", false, ); assert_script_is_short_enough_mac( &get_script( - bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_brew.sh"), + bundled_asset!("ssh/bash_zsh/install_tmux_and_zaplexify_brew.sh"), ctx, ), - "install_tmux_and_warpify_brew.sh", + "install_tmux_and_zaplexify_brew.sh", false, ); assert_script_is_short_enough_mac( &get_script( - bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh"), + bundled_asset!("ssh/fish/install_tmux_and_zaplexify_brew.sh"), ctx, ), - "fish/install_tmux_and_warpify_brew.sh", + "fish/install_tmux_and_zaplexify_brew.sh", false, ); assert_script_is_short_enough_mac( - &warpify_ssh_session_command("Darwin", ShellType::Zsh, ctx) + &zaplexify_ssh_session_command("Darwin", ShellType::Zsh, ctx) .expect("Should get Darwin zsh script"), - "zsh warpify", + "zsh zaplexify", true, ); assert_script_is_short_enough_mac( - &warpify_ssh_session_command("Darwin", ShellType::Bash, ctx) + &zaplexify_ssh_session_command("Darwin", ShellType::Bash, ctx) .expect("Should get Darwin bash script"), - "bash warpify", + "bash zaplexify", true, ); assert_script_is_short_enough_mac( - &warpify_ssh_session_command("Darwin", ShellType::Fish, ctx) + &zaplexify_ssh_session_command("Darwin", ShellType::Fish, ctx) .expect("Should get Darwin fish script"), - "fish warpify", + "fish zaplexify", true, ) }); diff --git a/app/src/terminal/terminal_manager.rs b/app/src/terminal/terminal_manager.rs index 85b0c6ab7e..d2fcfb7586 100644 --- a/app/src/terminal/terminal_manager.rs +++ b/app/src/terminal/terminal_manager.rs @@ -18,7 +18,7 @@ use super::{ safe_mode_settings::get_secret_obfuscation_mode, session_settings::SessionSettings, settings::TerminalSettings, - view::{create_size_info_for_blocklist, WARP_PROMPT_HEIGHT_LINES}, + view::{create_size_info_for_blocklist, ZAPLEX_PROMPT_HEIGHT_LINES}, ShellLaunchState, SizeInfo, TerminalModel, TerminalView, }; use crate::pane_group::pane::DetachType; @@ -72,7 +72,7 @@ pub(super) fn compute_block_size(initial_size: Vector2F, ctx: &mut AppContext) - block_padding: terminal_spacing.block_padding, size: size_info, max_block_scroll_limit: maximum_grid_size, - warp_prompt_height_lines: WARP_PROMPT_HEIGHT_LINES, + warp_prompt_height_lines: ZAPLEX_PROMPT_HEIGHT_LINES, } } diff --git a/app/src/terminal/universal_developer_input.rs b/app/src/terminal/universal_developer_input.rs index 62845bafa8..b99bc68a83 100644 --- a/app/src/terminal/universal_developer_input.rs +++ b/app/src/terminal/universal_developer_input.rs @@ -136,7 +136,7 @@ impl AtContextMenuDisabledReason { .and_then(|session_id| sessions.get(session_id)) .map(|session| { let is_ssh_session = session.is_legacy_ssh_session() - || matches!(session.session_type(), SessionType::WarpifiedRemote { .. }); + || matches!(session.session_type(), SessionType::ZaplexifiedRemote { .. }); let is_subshell = session.subshell_info().is_some(); (is_ssh_session, is_subshell) }) diff --git a/app/src/terminal/view.rs b/app/src/terminal/view.rs index b5a3c6147f..5835ea8b9e 100644 --- a/app/src/terminal/view.rs +++ b/app/src/terminal/view.rs @@ -283,8 +283,8 @@ use crate::terminal::shared_session::{ }; use crate::terminal::ssh::ssh_detection::SshInteractiveSessionDetected; use crate::terminal::view::block_onboarding::onboarding_prompt_block::OnboardingPromptBlock; -use crate::terminal::warpify::{ - render::render_subshell_separator, settings::WarpifySettings, SubshellSource, +use crate::terminal::zaplexify::{ + render::render_subshell_separator, settings::ZaplexifySettings, SubshellSource, }; use crate::terminal::ShellLaunchData; use crate::terminal::{element_size_at_last_frame, HistoryEntry}; @@ -500,7 +500,7 @@ use super::available_shells::AvailableShell; use super::block_list_viewport::FindMatchScrollLocation; use super::event::SshLoginStatus; use super::find::FindOptions; -use super::model::ansi::{SystemDetails, WarpificationUnavailableReason}; +use super::model::ansi::{SystemDetails, ZaplexificationUnavailableReason}; use super::model::block::{ BlockSection, BlocklistEnvVarMetadata, LONG_RUNNING_COMMAND_DURATION_MS, }; @@ -517,25 +517,25 @@ use super::ssh::install_tmux::{ SshKeyEvent, TmuxInstallMethod, }; use super::ssh::root_access::RootAccess; -use super::ssh::ssh_detection::evaluate_warpify_ssh_host; +use super::ssh::ssh_detection::evaluate_zaplexify_ssh_host; use super::ssh::util::{ convert_script_to_one_line, parse_interactive_ssh_command, InteractiveSshCommand, - SshWarpifyCommand, + SshZaplexifyCommand, }; -use super::ssh::warpify::{ - begin_warpify_ssh_session_command, warpify_ssh_session_command, SshWarpifyBlock, - SshWarpifyBlockEvent, +use super::ssh::zaplexify::{ + begin_zaplexify_ssh_session_command, zaplexify_ssh_session_command, SshZaplexifyBlock, + SshZaplexifyBlockEvent, }; -use super::ssh::SSH_WARPIFY_TIMEOUT_DURATION; -use super::warpify::success_block::{WarpifySuccessBlock, WarpifySuccessBlockEvent}; -use super::warpify::trigger_state::{SshBlockState, WarpifyState}; -use super::warpify::WarpificationSource; +use super::ssh::SSH_ZAPLEXIFY_TIMEOUT_DURATION; +use super::zaplexify::success_block::{ZaplexifySuccessBlock, ZaplexifySuccessBlockEvent}; +use super::zaplexify::trigger_state::{SshBlockState, ZaplexifyState}; +use super::zaplexify::ZaplexificationSource; use super::{GridType, HistoryEvent}; use crate::antivirus::AntivirusInfo; use crate::terminal::links::should_directly_open_link; use crate::terminal::model_events::{AnsiHandlerEvent, ModelEvent, ModelEventDispatcher}; -use action::RememberForWarpification; -use block_banner::{render_warpification_banner, WarpificationMode, WarpifyBannerState}; +use action::RememberForZaplexification; +use block_banner::{render_zaplexification_banner, ZaplexificationMode, ZaplexifyBannerState}; use bookmarks::render_floating_block_snapshot; use command_corrections::rules::generic::history::History as CommandCorrectionsHistoryRule; use init::{INPUT_BOX_VISIBLE_KEY, TOGGLE_BLOCK_FILTER_KEYBINDING}; @@ -618,7 +618,7 @@ pub const WAKEUP_THROTTLE_PERIOD: Duration = pub const EXECUTE_PENDING_COMMAND_DELAY: Duration = Duration::from_millis(100); -pub const WARP_PROMPT_HEIGHT_LINES: f32 = 0.9; +pub const ZAPLEX_PROMPT_HEIGHT_LINES: f32 = 0.9; const SCROLLBAR_WIDTH: ScrollbarWidth = ScrollbarWidth::Auto; @@ -686,10 +686,10 @@ const DEBOUNCE_PERIOD: Duration = Duration::from_millis(40); /// Key used in user defaults to save whether the user has seen the banner. pub const ALIAS_EXPANSION_BANNER_SEEN_KEY: &str = "AliasExpansionBannerSeen"; -/// Delay between receiving preexec hook for a command we want to auto-warpify -/// and triggering the warpification (subshell bootstrapping). +/// Delay between receiving preexec hook for a command we want to auto-zaplexify +/// and triggering the zaplexification (subshell bootstrapping). /// Reached this number after experimenting with different values to find a reliable delay. -const AUTO_WARPIFY_DELAY: u64 = 1000; +const AUTO_ZAPLEXIFY_DELAY: u64 = 1000; /// Binding names to be customized if the user indicates they prefer /// Emacs-style keybindings instead of IDE-style keybindings. @@ -702,7 +702,7 @@ const DEFAULT_AI_BLOCK_HEIGHT: f32 = 96.; pub const DEFAULT_ASK_AI_AUTOSUGGESTION_TEXT: &str = "What happened here?"; -const WARP_MD_PATH: &str = "WARP.md"; +const ZAPLEX_MD_PATH: &str = "WARP.md"; pub const LONG_RUNNING_AGENT_REQUESTED_COMMAND_CONTEXT_KEY: &str = "LongRunningRequestedCommand"; pub const LONG_RUNNING_AGENT_REQUESTED_COMMAND_USER_TOOK_OVER_CONTEXT_KEY: &str = @@ -791,16 +791,16 @@ impl NotificationsTrigger { pub fn discovery_banner_copy(&self) -> &'static str { match self { NotificationsTrigger::LongRunningCommand(..) => { - "Zap can notify you when long-running commands finish." + "Zaplex can notify you when long-running commands finish." } NotificationsTrigger::AgentTaskCompleted(..) => { - "Zap can notify you when an agent finishes responding." + "Zaplex can notify you when an agent finishes responding." } NotificationsTrigger::NeedsAttention => { - "Zap can notify you when a command or agent needs your attention." + "Zaplex can notify you when a command or agent needs your attention." } NotificationsTrigger::PasswordPrompt => { - "Zap can notify you when you're prompted to enter a password." + "Zaplex can notify you when you're prompted to enter a password." } } } @@ -1624,7 +1624,7 @@ pub enum Event { OpenWorkflowModalWithWorkflowObject(SyncId), // Tell the pane group to open the workflow modal with an unsaved workflow. OpenWorkflowModalWithTemporary(Box), - ZapDriveObjectInPane(ObjectUid), + ZaplexDriveObjectInPane(ObjectUid), OpenSuggestedAgentModeWorkflowModal { workflow_and_id: SuggestedAgentModeWorkflowAndId, }, @@ -1673,7 +1673,7 @@ pub enum Event { BlockStarted { is_for_in_band_command: bool, }, - /// Tell the pane group to open a file within Zap. + /// Tell the pane group to open a file within Zaplex. OpenFileInWarp { path: PathBuf, /// The session that the file belongs to. @@ -1804,7 +1804,7 @@ pub enum Event { target: FileTarget, line_col: Option, }, - /// Zap: emitted when Ctrl/Cmd+clicking a file path in the output of a remote SSH session in the terminal. + /// Zaplex: emitted when Ctrl/Cmd+clicking a file path in the output of a remote SSH session in the terminal. /// Opens the remote file in the editor via the buffer-sync protocol, rather than the local `OpenFileWithTarget`. #[cfg(all(feature = "local_tty", feature = "local_fs"))] OpenRemoteFileFromTerminal { @@ -1864,7 +1864,7 @@ pub enum LongRunningCommandAgentInteractionState { #[derive(Clone, Copy, Debug)] pub enum LeftPanelTargetView { FileTree, - ZapDrive, + ZaplexDrive, } #[derive(Clone)] @@ -2232,7 +2232,7 @@ impl Default for TerminalViewStateChange { } /// Whether or not this is the active terminal session. The active session for a pane group -/// is the one used for executing workflows, Zap AI suggestions, etc. +/// is the one used for executing workflows, Zaplex AI suggestions, etc. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ActiveSessionState { Active, @@ -2412,7 +2412,7 @@ pub struct TerminalView { control_master_error_banner_state: ControlMasterErrorBannerState, /// Banner to show if we detect a configuration in the user's rc files that - /// is incompatible with Zap. + /// is incompatible with Zaplex. incompatible_configuration_banner: ViewHandle>, is_incompatible_configuration_banner_open: bool, @@ -2440,7 +2440,7 @@ pub struct TerminalView { #[cfg_attr(not(feature = "local_fs"), allow(dead_code))] file_link_scanning_join_handle: Option>, - /// Zap: cache of cwd directory listings for remote SSH sessions, used to precisely validate terminal file links. + /// Zaplex: cache of cwd directory listings for remote SSH sessions, used to precisely validate terminal file links. /// /// The key is `(session_id, cwd absolute path)`, the value is the directory's actual list of children; `None` /// means the listing for that cwd is being fetched asynchronously (daemon `ListDirectory` RPC). @@ -2513,7 +2513,7 @@ pub struct TerminalView { // If the agentic suggestions onboarding block is pending, mark it here. pending_onboarding_agentic_suggestions_block: bool, - /// The type of the subshell that we will bootstrap/"warpify"" on the next [`AfterBlockStarted`] + /// The type of the subshell that we will bootstrap/"zaplexify"" on the next [`AfterBlockStarted`] /// terminal model event. Will only be `Some` with a [`ShellType`] we can bootstrap. pending_auto_bootstrap_shell_type: Option, env_vars: Vec, @@ -2574,7 +2574,7 @@ pub struct TerminalView { find_model: ModelHandle, - warpify_state: WarpifyState, + zaplexify_state: ZaplexifyState, /// The keystroke bound to canceling a command. /// @@ -3483,7 +3483,7 @@ impl TerminalView { &ai_action_model.as_ref(ctx).shell_command_executor(ctx), Self::handle_shell_command_executor_event, ); - // Zap BYOP: subscribe to the suggest_prompt tool's chip event, rendering the prompt actively + // Zaplex BYOP: subscribe to the suggest_prompt tool's chip event, rendering the prompt actively // suggested by the model as a chip above the input. The original emit is gated by the PromptSuggestionsViaMAA cargo feature // (`action_model/execute/suggest_prompt.rs:56`); in OSS nobody subscribes by default → the chip // is never shown → the oneshot channel hangs forever → the conversation deadlocks. We removed the emit gate @@ -3558,7 +3558,7 @@ impl TerminalView { )), FormattedTextFragment::hyperlink_action( crate::t!("terminal-banner-settings"), - TerminalAction::ShowWarpifySettings, + TerminalAction::ShowZaplexifySettings, ), FormattedTextFragment::plain_text(crate::t!( "terminal-banner-completions-not-working-suffix" @@ -3921,7 +3921,7 @@ impl TerminalView { input_position_id, input_hoverable_handle: Default::default(), find_model, - warpify_state: Default::default(), + zaplexify_state: Default::default(), cancel_command_keystroke: keybinding_name_to_keystroke(CANCEL_COMMAND_KEYBINDING, ctx), is_file_drop_target: false, is_ssh_file_uploader: false, @@ -4206,7 +4206,11 @@ impl TerminalView { | RemoteServerManagerEvent::RepoMetadataSnapshot { .. } | RemoteServerManagerEvent::RepoMetadataUpdated { .. } | RemoteServerManagerEvent::BufferUpdated { .. } - | RemoteServerManagerEvent::RepoMetadataDirectoryLoaded { .. } => {} + | RemoteServerManagerEvent::RepoMetadataDirectoryLoaded { .. } + // Stage 2: handled by the attached-remote terminal byte-source + // (later increment), not in this view-level telemetry match. + | RemoteServerManagerEvent::SessionOutput { .. } + | RemoteServerManagerEvent::SessionExited { .. } => {} } }); } @@ -4342,7 +4346,7 @@ impl TerminalView { /// Returns whether this terminal view should subscribe to git status /// updates. We subscribe when: /// 1. Agent mode is active and its chip list includes `GitDiffStats`, or - /// 2. Terminal mode with the Zap prompt enabled and the git stats chip + /// 2. Terminal mode with the Zaplex prompt enabled and the git stats chip /// configured. #[cfg(feature = "local_fs")] fn should_subscribe_to_git_status(&self, ctx: &AppContext) -> bool { @@ -4354,7 +4358,7 @@ impl TerminalView { .contains(&ContextChipKind::GitDiffStats); } - // Terminal prompt path: the Zap prompt is active when honor_ps1 is + // Terminal prompt path: the Zaplex prompt is active when honor_ps1 is // off, or when UDI overrides PS1. GitDiffStats must also be in the // configured chip list. let is_using_warp_prompt = !*SessionSettings::as_ref(ctx).honor_ps1 @@ -5151,7 +5155,7 @@ impl TerminalView { log::info!( "[byop-diag] CLISubagentEvent::SpawnedSubagent received: \ block_id={block_id:?} task_id={task_id:?} conv={conversation_id:?} \ - → 创建 CLISubagentView 加进 cli_subagent_views map" + → Creating CLISubagentView and adding to cli_subagent_views map" ); let subagent_view = ctx.add_typed_action_view(|ctx| { CLISubagentView::new( @@ -6904,7 +6908,7 @@ impl TerminalView { /// events, allow it to handle the event. /// /// TODO(CORE-3415): We should probably remove the FixedBindings for ctrl-c - /// in the SSH warpification blocks and handle them here as well. + /// in the SSH zaplexification blocks and handle them here as well. fn maybe_handle_ctrl_c_in_rich_content_block(&mut self, ctx: &mut ViewContext) { if self.active_ai_block(ctx).is_some() { self.cancel_active_conversation_via_status_bar(ctx); @@ -6972,7 +6976,7 @@ impl TerminalView { /// the workspace to derive `PendingRemoteSession` without storing /// mutable state on the workspace itself. pub fn has_pending_ssh_command(&self) -> bool { - self.warpify_state.get_pending_ssh_host().is_some() && self.is_long_running() + self.zaplexify_state.get_pending_ssh_host().is_some() && self.is_long_running() } /// Like `is_long_running`, but also requires the user to be in control of the command @@ -7016,7 +7020,7 @@ impl TerminalView { fn control_sequence_on_terminal(&mut self, bytes: &[u8], ctx: &mut ViewContext) { if self.is_long_running() { - self.on_ssh_warpification_key_event(Some(SshKeyEvent::from_bytes(bytes)), ctx); + self.on_ssh_zaplexification_key_event(Some(SshKeyEvent::from_bytes(bytes)), ctx); self.write_user_bytes_to_pty(bytes.to_owned(), ctx); } else { safe_warn!( @@ -7085,7 +7089,7 @@ impl TerminalView { /// Generally, this should be control characters rather than printable characters. fn keydown_on_terminal(&mut self, characters: &str, ctx: &mut ViewContext) { if self.is_long_running() { - self.on_ssh_warpification_key_event(Some(SshKeyEvent::from_chars(characters)), ctx); + self.on_ssh_zaplexification_key_event(Some(SshKeyEvent::from_chars(characters)), ctx); self.highlighted_link.invalidate(); self.report_possible_typeahead(characters); self.write_user_bytes_to_pty(characters.as_bytes().to_vec(), ctx); @@ -7129,7 +7133,7 @@ impl TerminalView { /// We can assume `characters` consists of all printable characters, and therefore, /// can go into the input box. fn typed_characters_on_terminal(&mut self, characters: &str, ctx: &mut ViewContext) { - self.on_ssh_warpification_key_event(Some(SshKeyEvent::from_chars(characters)), ctx); + self.on_ssh_zaplexification_key_event(Some(SshKeyEvent::from_chars(characters)), ctx); if self.should_write_typed_chars_to_pty(ctx) { self.highlighted_link.invalidate(); @@ -7644,7 +7648,7 @@ impl TerminalView { triggered_by_rc_file_snippet: bool, ctx: &mut ViewContext, ) { - self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSubshellCommand, ctx); + self.dismiss_zaplexify_banner(&RememberForZaplexification::DoNotRememberSubshellCommand, ctx); // Record the active long-running block so we can hide it later once the remote // actually confirms subshell bootstrap is in progress. @@ -7657,7 +7661,7 @@ impl TerminalView { .is_active_and_long_running() { let block_id = model.block_list().active_block_id().clone(); - self.warpify_state.set_block_id(block_id); + self.zaplexify_state.set_block_id(block_id); } } @@ -7680,7 +7684,7 @@ impl TerminalView { /// Util method to update the ssh block, with a lock fn update_long_running_ssh_block_with_lock(&self, f: impl FnOnce(&mut Block)) -> bool { - if let Some(block_id) = self.warpify_state.block_id() { + if let Some(block_id) = self.zaplexify_state.block_id() { if let Some(block) = self .model .lock() @@ -7699,7 +7703,7 @@ impl TerminalView { self.update_long_running_ssh_block_with_lock(|block| { block.unhide(); }); - self.warpify_state.delete_state(); + self.zaplexify_state.delete_state(); ctx.notify(); } @@ -7711,33 +7715,33 @@ impl TerminalView { } fn clear_ssh_blocks(&mut self, ctx: &mut ViewContext) { - self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSSHHost, ctx); - if let Some(ssh_block) = self.warpify_state.ssh_block_state() { + self.dismiss_zaplexify_banner(&RememberForZaplexification::DoNotRememberSSHHost, ctx); + if let Some(ssh_block) = self.zaplexify_state.ssh_block_state() { let view_id = ssh_block.get_block_view_id(); self.remove_ssh_block_by_id(view_id); self.redetermine_global_focus(ctx); - self.warpify_state.clear_ssh_block_state(); + self.zaplexify_state.clear_ssh_block_state(); } } /// Collapses any expanded UX within SSH blocks. /// To ensure we can always see what we're typing, we collapse /// the SSH block when typing. - fn on_ssh_warpification_key_event( + fn on_ssh_zaplexification_key_event( &mut self, key_event: Option, ctx: &mut ViewContext, ) { - if self.warpify_state.ssh_block_state().is_some() { + if self.zaplexify_state.ssh_block_state().is_some() { if key_event.is_some_and(|key| key.is_ctrl_c()) { - send_telemetry_from_ctx!(TelemetryEvent::SshTmuxWarpifyBlockDismissed, ctx); + send_telemetry_from_ctx!(TelemetryEvent::SshTmuxZaplexifyBlockDismissed, ctx); self.cancel_bootstrap_workflow(ctx); - } else if self.warpify_state.should_prevent_input() { - self.warpify_state.focus(ctx); - self.warpify_state.collapse_ssh_block(ctx); + } else if self.zaplexify_state.should_prevent_input() { + self.zaplexify_state.focus(ctx); + self.zaplexify_state.collapse_ssh_block(ctx); self.update_scroll_position_locking( ScrollPositionUpdate::AfterRichBlockUpdated, ctx, @@ -7747,15 +7751,15 @@ impl TerminalView { } } - fn handle_remote_warpification_is_unavailable( + fn handle_remote_zaplexification_is_unavailable( &mut self, - reason: WarpificationUnavailableReason, + reason: ZaplexificationUnavailableReason, ctx: &mut ViewContext, ) { - // Stop the pending timeout on warpification. - self.warpify_state.abort_ssh_warpify_timeout(); + // Stop the pending timeout on zaplexification. + self.zaplexify_state.abort_ssh_zaplexify_timeout(); match &reason { - WarpificationUnavailableReason::TmuxNotInstalled { + ZaplexificationUnavailableReason::TmuxNotInstalled { system_details, root_access, } => { @@ -7787,7 +7791,7 @@ impl TerminalView { return; } } - WarpificationUnavailableReason::UnsupportedTmuxVersion { system_details } => { + ZaplexificationUnavailableReason::UnsupportedTmuxVersion { system_details } => { if system_details.writable_home != Some(true) { if let Some(shell_type) = ShellType::from_name(&system_details.shell) { self.trigger_subshell_bootstrap(Some(shell_type), false, ctx); @@ -7811,7 +7815,7 @@ impl TerminalView { self.add_ssh_error_block(reason, ctx); } - fn add_ssh_warpify_prompt( + fn add_ssh_zaplexify_prompt( &mut self, command: &str, ssh_host: Option, @@ -7819,14 +7823,14 @@ impl TerminalView { ) { self.clear_ssh_blocks(ctx); self.handle_action( - &TerminalAction::ShowWarpifySshBanner(command.to_owned(), ssh_host), + &TerminalAction::ShowZaplexifySshBanner(command.to_owned(), ssh_host), ctx, ); } /// This method assumes the active block in the blocklist is a long-running SSH command. - fn add_ssh_warpifying_block(&mut self, ctx: &mut ViewContext) { - // Shared session viewers can't initiate warpification currently. + fn add_ssh_zaplexifying_block(&mut self, ctx: &mut ViewContext) { + // Shared session viewers can't initiate zaplexification currently. if self.model.lock().shared_session_status().is_viewer() { return; } @@ -7848,17 +7852,17 @@ impl TerminalView { ) }; - let ssh_warpify_block_handle = - ctx.add_typed_action_view(|_| SshWarpifyBlock::new(full_ssh_command)); - ctx.subscribe_to_view(&ssh_warpify_block_handle, move |me, _, event, ctx| { - me.handle_ssh_warpify_block_event(event, ctx); + let ssh_zaplexify_block_handle = + ctx.add_typed_action_view(|_| SshZaplexifyBlock::new(full_ssh_command)); + ctx.subscribe_to_view(&ssh_zaplexify_block_handle, move |me, _, event, ctx| { + me.handle_ssh_zaplexify_block_event(event, ctx); }); self.insert_rich_content( None, - ssh_warpify_block_handle.clone(), - Some(RichContentMetadata::SshWarpifyBlock { - ssh_warpify_block_handle: ssh_warpify_block_handle.clone(), + ssh_zaplexify_block_handle.clone(), + Some(RichContentMetadata::SshZaplexifyBlock { + ssh_zaplexify_block_handle: ssh_zaplexify_block_handle.clone(), }), RichContentInsertionPosition::Append { insert_below_long_running_block: true, @@ -7866,15 +7870,15 @@ impl TerminalView { ctx, ); - ctx.focus(&ssh_warpify_block_handle); + ctx.focus(&ssh_zaplexify_block_handle); - self.warpify_state.set_block_id(hidden_ssh_block_id); - self.warpify_state - .set_ssh_block_state(SshBlockState::Warpifying { - handle: ssh_warpify_block_handle, + self.zaplexify_state.set_block_id(hidden_ssh_block_id); + self.zaplexify_state + .set_ssh_block_state(SshBlockState::Zaplexifying { + handle: ssh_zaplexify_block_handle, }); - self.warpify_ssh_session(ctx); + self.zaplexify_ssh_session(ctx); } /// This method assumes the active block in the blocklist is a long-running SSH command. @@ -7902,7 +7906,7 @@ impl TerminalView { ) }; - let ssh_host = self.warpify_state.get_pending_ssh_host(); + let ssh_host = self.zaplexify_state.get_pending_ssh_host(); let ssh_install_tmux_block_handle = ctx.add_typed_action_view(|_| { SshInstallTmuxBlock::new( @@ -7934,8 +7938,8 @@ impl TerminalView { send_telemetry_from_ctx!(TelemetryEvent::SshInstallTmuxBlockDisplayed, ctx); - self.warpify_state.set_block_id(hidden_ssh_block_id); - self.warpify_state + self.zaplexify_state.set_block_id(hidden_ssh_block_id); + self.zaplexify_state .set_ssh_block_state(SshBlockState::InstallTmux { handle: ssh_install_tmux_block_handle, }); @@ -7943,12 +7947,12 @@ impl TerminalView { fn add_ssh_error_block( &mut self, - error_reason: WarpificationUnavailableReason, + error_reason: ZaplexificationUnavailableReason, ctx: &mut ViewContext, ) { // If there's already an error block showing, don't overwrite the existing one. if matches!( - self.warpify_state.ssh_block_state(), + self.zaplexify_state.ssh_block_state(), Some(SshBlockState::Error { .. }) ) { return; @@ -7959,7 +7963,7 @@ impl TerminalView { block.unhide(); }); - let ssh_host = self.warpify_state.take_pending_ssh_host(); + let ssh_host = self.zaplexify_state.take_pending_ssh_host(); let ssh_error_block_handle = ctx.add_typed_action_view(|_| SshErrorBlock::new(error_reason.clone(), ssh_host)); @@ -7980,18 +7984,18 @@ impl TerminalView { ); send_telemetry_from_ctx!( - TelemetryEvent::SshTmuxWarpificationErrorBlock { + TelemetryEvent::SshTmuxZaplexificationErrorBlock { error: error_reason, - tmux_installation: self.warpify_state.tmux_installation(), + tmux_installation: self.zaplexify_state.tmux_installation(), }, ctx ); - self.warpify_state + self.zaplexify_state .set_ssh_block_state(SshBlockState::Error { handle: ssh_error_block_handle, }); - self.warpify_state.focus(ctx); + self.zaplexify_state.focus(ctx); } fn add_bootstrap_success_block( @@ -8014,16 +8018,16 @@ impl TerminalView { }); } - let warpification_source = match session_type { - BootstrapSessionType::WarpifiedRemote => WarpificationSource::Ssh, - BootstrapSessionType::Local => WarpificationSource::Subshell, + let zaplexification_source = match session_type { + BootstrapSessionType::ZaplexifiedRemote => ZaplexificationSource::Ssh, + BootstrapSessionType::Local => ZaplexificationSource::Subshell, }; let disable_tmux = FeatureFlag::SSHTmuxWrapper.is_enabled() - && matches!(warpification_source, WarpificationSource::Ssh) + && matches!(zaplexification_source, ZaplexificationSource::Ssh) && { !self.model.lock().tmux_control_mode_active() }; let ssh_success_block_handle = ctx.add_typed_action_view(|ctx| { - WarpifySuccessBlock::new( - warpification_source, + ZaplexifySuccessBlock::new( + zaplexification_source, spawning_command, subshell_info, shell, @@ -8037,9 +8041,9 @@ impl TerminalView { self.clear_ssh_blocks(ctx); self.insert_rich_content( - Some(RichContentType::WarpifySuccessBlock), + Some(RichContentType::ZaplexifySuccessBlock), ssh_success_block_handle.clone(), - Some(RichContentMetadata::WarpifySuccessBlock { + Some(RichContentMetadata::ZaplexifySuccessBlock { bootstrap_success_block_handle: ssh_success_block_handle.clone(), }), RichContentInsertionPosition::Append { @@ -8047,38 +8051,38 @@ impl TerminalView { }, ctx, ); - self.warpify_state - .set_ssh_block_state(SshBlockState::WarpifySuccess { + self.zaplexify_state + .set_ssh_block_state(SshBlockState::ZaplexifySuccess { handle: ssh_success_block_handle, }); let active_session_id = self.active_block_session_id(); - self.warpify_state.on_warpify_start(active_session_id); + self.zaplexify_state.on_zaplexify_start(active_session_id); self.refresh_warp_prompt(ctx); } - fn handle_ssh_warpify_block_event( + fn handle_ssh_zaplexify_block_event( &mut self, - event: &SshWarpifyBlockEvent, + event: &SshZaplexifyBlockEvent, ctx: &mut ViewContext, ) { - fn dismiss_ssh_warpify_block(me: &mut TerminalView, ctx: &mut ViewContext) { - send_telemetry_from_ctx!(TelemetryEvent::SshTmuxWarpifyBlockDismissed, ctx); + fn dismiss_ssh_zaplexify_block(me: &mut TerminalView, ctx: &mut ViewContext) { + send_telemetry_from_ctx!(TelemetryEvent::SshTmuxZaplexifyBlockDismissed, ctx); me.cancel_bootstrap_workflow(ctx); } match event { - SshWarpifyBlockEvent::Cancel => { - self.warpify_state.replace_timeout_id(); - dismiss_ssh_warpify_block(self, ctx); + SshZaplexifyBlockEvent::Cancel => { + self.zaplexify_state.replace_timeout_id(); + dismiss_ssh_zaplexify_block(self, ctx); } - SshWarpifyBlockEvent::Interrupt => { - dismiss_ssh_warpify_block(self, ctx); - self.warpify_state.abort_ssh_warpify_timeout(); + SshZaplexifyBlockEvent::Interrupt => { + dismiss_ssh_zaplexify_block(self, ctx); + self.zaplexify_state.abort_ssh_zaplexify_timeout(); self.user_write_ctrl_c_to_pty(ctx); } - SshWarpifyBlockEvent::WarpifySession => { - send_telemetry_from_ctx!(TelemetryEvent::SshTmuxWarpifyBlockAccepted, ctx); - self.add_ssh_warpifying_block(ctx); + SshZaplexifyBlockEvent::ZaplexifySession => { + send_telemetry_from_ctx!(TelemetryEvent::SshTmuxZaplexifyBlockAccepted, ctx); + self.add_ssh_zaplexifying_block(ctx); self.update_scroll_position_locking( ScrollPositionUpdate::AfterRichBlockUpdated, ctx, @@ -8104,13 +8108,13 @@ impl TerminalView { } SshInstallTmuxBlockEvent::Interrupt => { cancel_tmux_install(self, ctx); - self.warpify_state.abort_ssh_warpify_timeout(); + self.zaplexify_state.abort_ssh_zaplexify_timeout(); self.user_write_ctrl_c_to_pty(ctx); } - SshInstallTmuxBlockEvent::InstallTmuxAndWarpify(install_method) => { + SshInstallTmuxBlockEvent::InstallTmuxAndZaplexify(install_method) => { send_telemetry_from_ctx!(TelemetryEvent::SshInstallTmuxBlockAccepted, ctx); self.clear_ssh_blocks(ctx); - self.install_tmux_and_warpify(ctx, install_method); + self.install_tmux_and_zaplexify(ctx, install_method); self.update_scroll_position_locking( ScrollPositionUpdate::AfterRichBlockUpdated, ctx, @@ -8125,7 +8129,7 @@ impl TerminalView { ctx.notify(); } SshInstallTmuxBlockEvent::ToggleTmuxInstallVisibility => { - if let Some(ssh_block_id) = self.warpify_state.block_id() { + if let Some(ssh_block_id) = self.zaplexify_state.block_id() { if let Some(is_visible) = self .model .lock() @@ -8140,7 +8144,7 @@ impl TerminalView { } } SshInstallTmuxBlockEvent::UnhideTmuxInstall => { - if let Some(ssh_block_id) = self.warpify_state.block_id() { + if let Some(ssh_block_id) = self.zaplexify_state.block_id() { self.model .lock() .block_list_mut() @@ -8158,12 +8162,12 @@ impl TerminalView { ctx: &mut ViewContext, ) { match event { - SshErrorBlockEvent::WarpifyWithoutTmux => { - let shell_type = self.warpify_state.get_shell_type(); + SshErrorBlockEvent::ZaplexifyWithoutTmux => { + let shell_type = self.zaplexify_state.get_shell_type(); self.clear_ssh_blocks(ctx); self.trigger_subshell_bootstrap(shell_type, false, ctx); } - SshErrorBlockEvent::ContinueWithoutWarpification => { + SshErrorBlockEvent::ContinueWithoutZaplexification => { self.cancel_bootstrap_workflow(ctx); } } @@ -8171,19 +8175,19 @@ impl TerminalView { fn handle_ssh_success_block_events( &mut self, - event: &WarpifySuccessBlockEvent, + event: &ZaplexifySuccessBlockEvent, ctx: &mut ViewContext, ) { match event { - WarpifySuccessBlockEvent::ZapifySettings => { - ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); + ZaplexifySuccessBlockEvent::ZapifySettings => { + ctx.emit(Event::OpenSettings(SettingsSection::Zaplexify)); } } } - fn dismiss_warpify_banner( + fn dismiss_zaplexify_banner( &mut self, - remember_command: &RememberForWarpification, + remember_command: &RememberForZaplexification, ctx: &mut ViewContext, ) { { @@ -8191,66 +8195,66 @@ impl TerminalView { model.block_list_mut().set_active_block_banner(None); } - // Also clear the warpify footer so it doesn't linger after warpification + // Also clear the zaplexify footer so it doesn't linger after zaplexification // starts, fails, or is cancelled. - if FeatureFlag::WarpifyFooter.is_enabled() { + if FeatureFlag::ZaplexifyFooter.is_enabled() { self.use_agent_footer.update(ctx, |footer, ctx| { - footer.clear_warpify_mode(ctx); + footer.clear_zaplexify_mode(ctx); }); } match remember_command { - RememberForWarpification::RememberSubshellCommand(command) => { - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.denylist_subshell_command(command, ctx); + RememberForZaplexification::RememberSubshellCommand(command) => { + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify, ctx| { + zaplexify.denylist_subshell_command(command, ctx); }); } - RememberForWarpification::RememberSSHHost(host) => { - WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| { - warpify.denylist_ssh_host(host, ctx); + RememberForZaplexification::RememberSSHHost(host) => { + ZaplexifySettings::handle(ctx).update(ctx, |zaplexify, ctx| { + zaplexify.denylist_ssh_host(host, ctx); }); } - RememberForWarpification::DoNotRememberSubshellCommand - | RememberForWarpification::DoNotRememberSSHHost => {} + RememberForZaplexification::DoNotRememberSubshellCommand + | RememberForZaplexification::DoNotRememberSSHHost => {} } } - fn show_warpify_banner( + fn show_zaplexify_banner( &mut self, - input: WarpificationMode, + input: ZaplexificationMode, title: &str, lowercase_title: &str, - warpify_keybinding: Option, + zaplexify_keybinding: Option, telemetry_event: TelemetryEvent, ctx: &mut ViewContext, ) { - if FeatureFlag::WarpifyFooter.is_enabled() { + if FeatureFlag::ZaplexifyFooter.is_enabled() { return; } let mut model = self.model.lock(); - // Shared session viewers can't initiate warpification currently. - // Don't show the warpify banner when an agent is monitoring the command either. + // Shared session viewers can't initiate zaplexification currently. + // Don't show the zaplexify banner when an agent is monitoring the command either. if model.shared_session_status().is_viewer() || model.block_list().active_block().is_agent_monitoring() { return; } - let a11y_message = match &warpify_keybinding { + let a11y_message = match &zaplexify_keybinding { Some(keystroke) => format!( - "You can press {} to Warpify this {} for more Zap features.", + "You can press {} to Zaplexify this {} for more Zaplex features.", keystroke.displayed(), lowercase_title ), - None => format!("You can Warpify this {lowercase_title} for more Zap features."), + None => format!("You can Zaplexify this {lowercase_title} for more Zaplex features."), }; model .block_list_mut() - .set_active_block_banner(Some(WithinBlockBanner::WarpifyBanner( - WarpifyBannerState::new(input, warpify_keybinding), + .set_active_block_banner(Some(WithinBlockBanner::ZaplexifyBanner( + ZaplexifyBannerState::new(input, zaplexify_keybinding), ))); let a11y_content = AccessibilityContent::new( @@ -8407,7 +8411,7 @@ impl TerminalView { let a11y_content = AccessibilityContent::new( banner_title, - "Make sure you have enabled access for Zap notifications in System Preferences.", + "Make sure you have enabled access for Zaplex notifications in System Preferences.", WarpA11yRole::TextRole, ); ctx.emit_a11y_content(a11y_content); @@ -8483,7 +8487,7 @@ impl TerminalView { let should_start_new_conversation = suggestion.should_start_new_conversation; let conversation_id = banner_state.conversation_id; let trigger_block_id = trigger.as_ref().and_then(|t| t.block_id()); - // Zap BYOP: clone byop_action_id + prompt, used at the end of accept to notify the executor + // Zaplex BYOP: clone byop_action_id + prompt, used at the end of accept to notify the executor // (`complete_suggest_prompt_action(Accepted { query })` closes the oneshot channel). let byop_banner_for_completion = banner_state .byop_action_id @@ -8582,7 +8586,7 @@ impl TerminalView { ); } - // Zap BYOP: the chip actively suggested by the model was accepted by the user → notify the executor to close + // Zaplex BYOP: the chip actively suggested by the model was accepted by the user → notify the executor to close // the oneshot channel, so the BYOP loop gets the `Accepted{query}` result, and on its next turn the model // can see the tool_result of "the user has adopted and submitted that prompt". if let Some(banner) = byop_banner_for_completion.as_ref() { @@ -9129,7 +9133,7 @@ impl TerminalView { reset_focus } - /// Recomputes the chip values for the Zap prompt (i.e. _not_ PS1). + /// Recomputes the chip values for the Zaplex prompt (i.e. _not_ PS1). fn refresh_warp_prompt(&mut self, ctx: &mut ViewContext) { // Ask the per-repo sub-model to re-fetch metadata so the chip values // reflect the latest git state (branch, diff stats, etc.). @@ -9274,7 +9278,7 @@ impl TerminalView { /// Returns true if the block is considered remote. /// /// Note that we don't know for sure if a block is remote, because we can only detect - /// warpified remote blocks. + /// zaplexified remote blocks. /// /// For some organizations, we accept a regex list that we run against commands to /// further make the determination. @@ -9284,7 +9288,7 @@ impl TerminalView { command: Option<&str>, app: &AppContext, ) -> bool { - let is_warpified_remote = session_id + let is_zaplexified_remote = session_id .map(|id| { self.sessions .as_ref(app) @@ -9294,7 +9298,7 @@ impl TerminalView { }) .unwrap_or_default(); - if is_warpified_remote { + if is_zaplexified_remote { return true; } @@ -9611,7 +9615,7 @@ impl TerminalView { // If this block ran a possible subshell command, and it exited before the 1s timer // completed, abort showing the banner. - if let Some(abort_handle) = self.warpify_state.take_subshell_banner_abort_handle() { + if let Some(abort_handle) = self.zaplexify_state.take_subshell_banner_abort_handle() { abort_handle.abort(); } @@ -9645,9 +9649,9 @@ impl TerminalView { self.on_user_block_completed(&block_completed_event.block_id, ctx); } - // Clear any stale warpify mode so it doesn't leak into the next command's footer rendering. + // Clear any stale zaplexify mode so it doesn't leak into the next command's footer rendering. self.use_agent_footer.update(ctx, |footer, ctx| { - footer.clear_warpify_mode(ctx); + footer.clear_zaplexify_mode(ctx); }); self.hide_use_agent_footer_in_blocklist(ctx); if matches!(block_completed_event.block_type, BlockType::User(_)) { @@ -9732,7 +9736,7 @@ impl TerminalView { self.drop_hidden_passive_ai_blocks(ctx); // If the first word of the command is a shell alias, expand it - // for subshell/SSH detection. This enables warpification for + // for subshell/SSH detection. This enables zaplexification for // aliased SSH commands (e.g. `alias myssh='ssh user@host'`). let expanded_command = self .active_block_session_id() @@ -9742,19 +9746,19 @@ impl TerminalView { let alias_value = session.alias_value(first_word)?; Some(format!("{alias_value}{rest}")) }); - let warpify_command = expanded_command.as_deref().unwrap_or(command.as_str()); + let zaplexify_command = expanded_command.as_deref().unwrap_or(command.as_str()); - // Check if the current running command spawns a subshell eligible for Warpification. + // Check if the current running command spawns a subshell eligible for Zaplexification. let shell_family = self.shell_family(ctx); - let warpify_settings = WarpifySettings::as_ref(ctx); - let is_compatible_subshell_command = warpify_settings + let zaplexify_settings = ZaplexifySettings::as_ref(ctx); + let is_compatible_subshell_command = zaplexify_settings .is_compatible_subshell_command(command, shell_family) - || warpify_settings - .is_compatible_subshell_command(warpify_command, shell_family); - let command_is_denylisted = warpify_settings + || zaplexify_settings + .is_compatible_subshell_command(zaplexify_command, shell_family); + let command_is_denylisted = zaplexify_settings .is_denylisted_subshell_command(command) - || warpify_settings.is_denylisted_subshell_command(warpify_command); - // Never warpify or surface warpification for agent-requested commands. + || zaplexify_settings.is_denylisted_subshell_command(zaplexify_command); + // Never zaplexify or surface zaplexification for agent-requested commands. let has_ai_metadata = self .model .lock() @@ -9765,31 +9769,31 @@ impl TerminalView { if is_compatible_subshell_command { if command_is_denylisted || has_ai_metadata { - // Don't auto-warpify or surface warpification for these commands. + // Don't auto-zaplexify or surface zaplexification for these commands. } else if let Some(shell_type) = self.pending_auto_bootstrap_shell_type.take() { // If there is a subshell we're waiting to bootstrap until we receive // the preexec hook, now we can bootstrap it. - let auto_warpify_abort_handle = ctx.spawn_abortable( - Timer::after(Duration::from_millis(AUTO_WARPIFY_DELAY)), + let auto_zaplexify_abort_handle = ctx.spawn_abortable( + Timer::after(Duration::from_millis(AUTO_ZAPLEXIFY_DELAY)), move |me, _, ctx| { me.trigger_subshell_bootstrap(Some(shell_type), false, ctx); }, |_, _| (), ); - self.warpify_state - .add_auto_warpify_abort_handle(auto_warpify_abort_handle); + self.zaplexify_state + .add_auto_zaplexify_abort_handle(auto_zaplexify_abort_handle); } else { // Wait 1 second before showing the banner, just to make sure the // command stays running for a bit. If the command fails instantly, // we don't want to flicker the banner away so quickly. let command = command.clone(); - self.warpify_state + self.zaplexify_state .add_subshell_banner_abort_handle(ctx.spawn_abortable( Timer::after(*SUBSHELL_BANNER_DELAY_DURATION), |view, _, ctx| { - if FeatureFlag::WarpifyFooter.is_enabled() { - view.show_warpify_footer( - WarpificationMode::subshell(command), + if FeatureFlag::ZaplexifyFooter.is_enabled() { + view.show_zaplexify_footer( + ZaplexificationMode::subshell(command), ctx, ); } else { @@ -9805,16 +9809,16 @@ impl TerminalView { } else { if !has_ai_metadata { if let Some(ssh_host) = - parse_interactive_ssh_command(warpify_command).map(|cmd| cmd.host) + parse_interactive_ssh_command(zaplexify_command).map(|cmd| cmd.host) { if !self.model.lock().tmux_control_mode_active() { - self.warpify_state - .set_pending_ssh_host(warpify_command.to_string(), ssh_host); + self.zaplexify_state + .set_pending_ssh_host(zaplexify_command.to_string(), ssh_host); self.model.lock().start_notify_on_end_of_ssh_login(); ctx.emit(Event::TerminalViewStateChanged); } } else { - self.warpify_state.clear_pending_ssh_host(); + self.zaplexify_state.clear_pending_ssh_host(); ctx.spawn( Timer::after(Duration::from_millis( @@ -9909,14 +9913,14 @@ impl TerminalView { cloud_workflow_id, cloud_env_var_collection_id, }) => { - // To automatically warpify a subshell, we run the relevant command to open the + // To automatically zaplexify a subshell, we run the relevant command to open the // subshell and create a future to delay bootstrapping the subshell long enough for // the command to complete. We receive AfterBlockCompleted if the subshell command // returns an error or the user exits the subshell. Here we abort the future to // avoid an attempt to trigger bootstrapping if the subshell command failed. If the // future already resolved, abort has no effect. We handle this as early as possible // because the abort is time sensitive. - self.warpify_state.abort_auto_warpify(); + self.zaplexify_state.abort_auto_zaplexify(); let active_session = self .active_block_session_id() @@ -9965,7 +9969,7 @@ impl TerminalView { ); // On dogfood only, we're interested in the block commands, durations, - // and exit codes to trial Zap Analytics. + // and exit codes to trial Zaplex Analytics. if ChannelState::channel().is_dogfood() { send_telemetry_from_ctx!( TelemetryEvent::BlockCompletedOnDogfoodOnly { @@ -9991,14 +9995,14 @@ impl TerminalView { } let active_session_id = self.active_block_session_id(); if let Some(block_id) = self - .warpify_state - .get_completed_warpify_session_id(active_session_id, ctx) + .zaplexify_state + .get_completed_zaplexify_session_id(active_session_id, ctx) { self.remove_ssh_block_by_id(block_id); } - self.dismiss_warpify_banner( - &RememberForWarpification::DoNotRememberSubshellCommand, + self.dismiss_zaplexify_banner( + &RememberForZaplexification::DoNotRememberSubshellCommand, ctx, ); @@ -10517,21 +10521,21 @@ impl TerminalView { ModelEvent::DetectedEndOfSshLogin(check_type) => { self.handle_detected_end_of_ssh_login(check_type, ctx); } - ModelEvent::RemoteWarpificationIsUnavailable(reason) => { - self.handle_remote_warpification_is_unavailable(reason.clone(), ctx); + ModelEvent::RemoteZaplexificationIsUnavailable(reason) => { + self.handle_remote_zaplexification_is_unavailable(reason.clone(), ctx); } ModelEvent::SshTmuxInstaller(tmux_installation) => { - self.warpify_state + self.zaplexify_state .set_tmux_installation_state(*tmux_installation); } ModelEvent::TmuxInstallFailed { line, command } => { let system_details = self - .warpify_state + .zaplexify_state .ssh_block_state() .and_then(|s| s.get_system_details(ctx)); - self.warpify_state.abort_ssh_warpify_timeout(); + self.zaplexify_state.abort_ssh_zaplexify_timeout(); self.add_ssh_error_block( - WarpificationUnavailableReason::TmuxInstallFailed { + ZaplexificationUnavailableReason::TmuxInstallFailed { system_details, line: Some(line.to_string()), command: Some(command.to_string()), @@ -10556,7 +10560,7 @@ impl TerminalView { ModelEvent::InitSsh(event) => { let shell_type = event.shell_type; let uname = event.uname.as_ref().unwrap_or(&String::default()).clone(); - self.continue_warpify_ssh_session(&uname, shell_type, ctx); + self.continue_zaplexify_ssh_session(&uname, shell_type, ctx); } ModelEvent::SourcedRcFileInSubshell(event) => { send_telemetry_from_ctx!(TelemetryEvent::ReceivedSubshellRcFileDcs, ctx); @@ -10584,16 +10588,16 @@ impl TerminalView { has_ai_metadata, ) }; - // Never warpify for agent-requested commands. + // Never zaplexify for agent-requested commands. if has_ai_metadata { return; } - // To simplify the implementation, we do not support warpifying while SSH-warpified. + // To simplify the implementation, we do not support zaplexifying while SSH-zaplexified. if is_tmux_control_mode_active { return; } if is_ssh && !disable_tmux { - me.continue_warpify_ssh_session(&uname, shell_type, ctx); + me.continue_zaplexify_ssh_session(&uname, shell_type, ctx); } else { me.trigger_subshell_bootstrap(Some(shell_type), true, ctx); } @@ -10736,7 +10740,9 @@ impl TerminalView { RemoteServerManager::handle(ctx).update( ctx, |mgr: &mut RemoteServerManager, ctx| { - mgr.deregister_session(*session_id, ctx); + // Legacy ssh-wrapper path: per-session ControlMaster, stop it + // so the foreground ssh can exit cleanly. + mgr.deregister_session(*session_id, true, ctx); }, ); } @@ -10784,7 +10790,7 @@ impl TerminalView { ctx.emit(Event::RemoteServerSkipRequested { session_id }); } SshRemoteServerChoiceViewEvent::ZapifySettings => { - ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); + ctx.emit(Event::OpenSettings(SettingsSection::Zaplexify)); } }); @@ -11240,7 +11246,7 @@ impl TerminalView { // Desktop notifications for CLI agents use the user's notification settings // directly. CLI agent notifications are explicit agent lifecycle events, - // so they should still notify when Zap is focused. + // so they should still notify when Zaplex is focused. if matches!(status, CLIAgentSessionStatus::InProgress) { return; } @@ -11307,7 +11313,7 @@ impl TerminalView { self.update_incompatible_configuration_banner(session.shell().plugins(), ctx); if let Some(subshell_info) = session.subshell_info() { - self.warpify_state + self.zaplexify_state .add_subshell_separator(subshell_info, self.model.clone(), ctx); } @@ -11355,7 +11361,7 @@ impl TerminalView { } let is_subshell_or_ssh = session.is_subshell_or_ssh(); - let is_ssh_session = matches!(session.session_type(), SessionType::WarpifiedRemote { .. }) + let is_ssh_session = matches!(session.session_type(), SessionType::ZaplexifiedRemote { .. }) || session.is_legacy_ssh_session(); // Make sure we decorate any text that is already in the input. We @@ -11376,8 +11382,8 @@ impl TerminalView { }, ); - // If we were waiting for a successful warpification, it's come. Stop the timeout. - self.warpify_state.abort_ssh_warpify_timeout(); + // If we were waiting for a successful zaplexification, it's come. Stop the timeout. + self.zaplexify_state.abort_ssh_zaplexify_timeout(); if bootstrap_event.subshell_info.is_some() { self.add_bootstrap_success_block(bootstrap_event, ctx); @@ -11446,7 +11452,7 @@ impl TerminalView { // Now that the session is bootstrapped, update any restored AI blocks that were // created before bootstrapping with the shell launch data. This enables file link - // detection and the "Open in Zap" button on code blocks in restored conversations. + // detection and the "Open in Zaplex" button on code blocks in restored conversations. if let Some(shell_launch_data) = self.active_session.as_ref(ctx).shell_launch_data(ctx) { let ai_block_handles: Vec<_> = self .rich_content_views @@ -12324,7 +12330,7 @@ impl TerminalView { // https://github.com/warpdotdev/command-corrections/blob/df7848d4fb3da7883623e959889a296a07d88053/src/rules/cd/mod.rs#L31-L36 // We don't currently support dynamic rules over SSH, so we should not attempt to correct commands if // inside ssh session. - let is_ssh_command = SshWarpifyCommand::matches(input).is_some(); + let is_ssh_command = SshZaplexifyCommand::matches(input).is_some(); if is_ssh_command { return vec![]; } @@ -12405,7 +12411,7 @@ impl TerminalView { fn clear_prompt_suggestions(&mut self, ctx: &mut ViewContext) { if let Some(banner) = self.inline_banners_state.prompt_suggestions_banner.take() { - // Zap BYOP: if this chip came from the suggest_prompt tool, we need to cancel + // Zaplex BYOP: if this chip came from the suggest_prompt tool, we need to cancel // the corresponding oneshot channel, otherwise the BYOP loop hangs forever waiting for the result. self.complete_byop_suggest_prompt_if_needed(&banner, None, ctx); self.input.update(ctx, |input, ctx| { @@ -12575,7 +12581,7 @@ impl TerminalView { self.update_scroll_position_locking(ScrollPositionUpdate::AfterEnd, ctx); } - /// Zap BYOP: when the model actively calls the `suggest_prompt` tool, the executor emits this event carrying + /// Zaplex BYOP: when the model actively calls the `suggest_prompt` tool, the executor emits this event carrying /// prompt + label + action_id. /// /// **Design semantics**: `suggest_prompt` is fire-and-forget (aligned with opencode agentic tool behavior) @@ -13254,7 +13260,7 @@ impl TerminalView { } /// Shared logic for sending a desktop notification (or showing a discovery banner) - /// for any agent status change (both Zap's agent and any CLI agent). + /// for any agent status change (both Zaplex's agent and any CLI agent). fn send_agent_desktop_notification_or_show_banner( &mut self, trigger: NotificationsTrigger, @@ -13973,7 +13979,7 @@ impl TerminalView { .with_on_select_action(TerminalAction::OpenFileInWarp(path)) .into_item(), ); - // Because the default for cmd-click is to open in Zap, we also + // Because the default for cmd-click is to open in Zaplex, we also // have an open-in-editor option. items.push( MenuItemFields::new(crate::t!("menu-block-open-in-editor")) @@ -14094,7 +14100,7 @@ impl TerminalView { let is_copy_both_disabled = is_copy_commands_disabled && tail_block.output_to_string().trim().is_empty(); - // Zap: removed the "Share block..." / "Share session..." entries (cloud dependency) + // Zaplex: removed the "Share block..." / "Share session..." entries (cloud dependency) let _ = is_share_disabled; let mut items = vec![ @@ -14276,7 +14282,7 @@ impl TerminalView { ) => { // If selection is empty, only show non-block related options let items: Vec> = Vec::new(); - // Zap: removed session_sharing_context_menu_items (cloud shared session entry) + // Zaplex: removed session_sharing_context_menu_items (cloud shared session entry) items } _ => vec![], @@ -14671,9 +14677,9 @@ impl TerminalView { .into_item(), ); - // Zap: removed session_sharing_context_menu_items (cloud shared session entry) + // Zaplex: removed session_sharing_context_menu_items (cloud shared session entry) - // Section 2: AI Command Search, Ask Zap AI + // Section 2: AI Command Search, Ask Zaplex AI items.extend([ MenuItem::Separator, MenuItemFields::new(crate::t!("menu-input-command-search")) @@ -14905,7 +14911,7 @@ impl TerminalView { } } - // Zap: removed session_sharing_context_menu_items (cloud shared session entry) + // Zaplex: removed session_sharing_context_menu_items (cloud shared session entry) let current_shell = model.shell_launch_state().available_shell(); let mut pane_context_menu_items = self.pane_context_menu_items(current_shell, ctx); if !menu_items.is_empty() && !pane_context_menu_items.is_empty() { @@ -15170,7 +15176,7 @@ impl TerminalView { ); items.push(MenuItem::Separator); - // Remote conversation sharing was removed in Zap; share-conversation menu item omitted. + // Remote conversation sharing was removed in Zaplex; share-conversation menu item omitted. let _ = ai_conversation_id; items.push( @@ -16196,7 +16202,7 @@ impl TerminalView { } } - /// Zap: if the session that owns the currently active block is a remote-server session, returns its `HostId`. + /// Zaplex: if the session that owns the currently active block is a remote-server session, returns its `HostId`. /// /// Used when Ctrl/Cmd+clicking a file path in the terminal, to decide whether to take the local or remote buffer-sync /// open flow. Non-remote-server sessions return `None` (keeping local behavior unchanged). @@ -16218,7 +16224,7 @@ impl TerminalView { } } - /// Zap: treats the absolute path resolved from a terminal file link as a remote path and constructs a `RemotePath`. + /// Zaplex: treats the absolute path resolved from a terminal file link as a remote path and constructs a `RemotePath`. /// Remote SSH hosts are all Unix, and the path string is assembled from the remote cwd reported by shell-integration. #[cfg(all(feature = "local_tty", feature = "local_fs"))] fn remote_path_from_terminal_path( @@ -16228,7 +16234,7 @@ impl TerminalView { let path_str = path.to_str()?; let standardized = warp_util::standardized_path::StandardizedPath::try_new(path_str) .map_err(|e| { - log::warn!("无法将终端文件路径转换为远端路径 {path_str:?}: {e}"); + log::warn!("Failed to convert terminal file path to remote path {path_str:?}: {e}"); }) .ok()?; Some(crate::code::buffer_location::RemotePath::new( @@ -16237,7 +16243,7 @@ impl TerminalView { )) } - /// Zap: determines whether the remote path in a terminal file link points to a directory. + /// Zaplex: determines whether the remote path in a terminal file link points to a directory. /// /// This is based on the cached remote cwd directory listing (fetched and written by `link_detection.rs`). /// Returns `false` if not cached or not a directory (treated as a file). @@ -16259,7 +16265,7 @@ impl TerminalView { }) } - /// Zap: `cd` into the given directory in the current (remote) terminal session. + /// Zaplex: `cd` into the given directory in the current (remote) terminal session. /// /// Aligned with the behavior of clicking a directory link locally — a remote directory cannot be opened in the editor, so instead /// run `cd

` in that remote shell session. @@ -16281,7 +16287,7 @@ impl TerminalView { ) { ctx.notify(); - // Zap: remote SSH sessions open remote files via the buffer-sync protocol. + // Zaplex: remote SSH sessions open remote files via the buffer-sync protocol. #[cfg(all(feature = "local_tty", feature = "local_fs"))] if let Some(host_id) = self.active_session_remote_host_id(ctx) { // Remote directory click: do not open in the editor; instead `cd` into it in that remote session. @@ -16321,7 +16327,7 @@ impl TerminalView { ) { ctx.notify(); - // Zap: remote SSH sessions open remote files via the buffer-sync protocol. + // Zaplex: remote SSH sessions open remote files via the buffer-sync protocol. // Remote files are always opened in the embedded code editor, ignoring `target` (an external editor cannot access remote files). #[cfg(all(feature = "local_tty", feature = "local_fs"))] if let Some(host_id) = self.active_session_remote_host_id(ctx) { @@ -16409,7 +16415,7 @@ impl TerminalView { self.paste(true, ctx); } - /// Tell the pane group to open a file within Zap. + /// Tell the pane group to open a file within Zaplex. fn open_file_in_warp(&mut self, path: PathBuf, ctx: &mut ViewContext) { if let Some(session) = self .active_block_session_id() @@ -16876,7 +16882,7 @@ impl TerminalView { .and_then(|id| self.sessions.as_ref(ctx).get(id)) { if let Some(info) = session.subshell_info() { - self.warpify_state + self.zaplexify_state .add_subshell_separator(info, self.model.clone(), ctx); } } @@ -17905,9 +17911,9 @@ impl TerminalView { env_var_collection_block.clear_selection(ctx); }); } - Some(RichContentMetadata::WarpifySuccessBlock { .. }) => { - // TODO(Simon): We should be checking for WarpifySuccessBlocks here as well. - // The `WarpifySuccessBlock` implements a `SelectableArea`. + Some(RichContentMetadata::ZaplexifySuccessBlock { .. }) => { + // TODO(Simon): We should be checking for ZaplexifySuccessBlocks here as well. + // The `ZaplexifySuccessBlock` implements a `SelectableArea`. } _ => {} } @@ -18139,10 +18145,10 @@ impl TerminalView { } AIBlockEvent::OpenCitation(citation) => match citation { AIAgentCitation::WarpDriveObject { uid } => { - ctx.emit(Event::ZapDriveObjectInPane(uid.clone())); + ctx.emit(Event::ZaplexDriveObjectInPane(uid.clone())); } AIAgentCitation::WarpDocumentation { path: _ } => { - // The Zap fork does not inherit the upstream docs.warp.dev documentation site, + // The Zaplex fork does not inherit the upstream docs.warp.dev documentation site, // so clicking this kind of citation does not navigate anywhere for now. } AIAgentCitation::WebPage { url } => { @@ -18154,7 +18160,7 @@ impl TerminalView { } AIBlockEvent::OpenWorkflow { sync_id } => { if let Some(object) = ObjectStoreModel::as_ref(ctx).get_workflow(sync_id) { - ctx.emit(Event::ZapDriveObjectInPane(object.uid())); + ctx.emit(Event::ZaplexDriveObjectInPane(object.uid())); } } AIBlockEvent::OpenSuggestedAgentModeWorkflowModal { workflow_and_id } => { @@ -19807,7 +19813,7 @@ impl TerminalView { } } } else if self.is_long_running() { - self.on_ssh_warpification_key_event(None, ctx); + self.on_ssh_zaplexification_key_event(None, ctx); let sequence = EscCodes::build_escape_sequence(self.model.lock().deref(), &[EscCodes::ARROW_UP]); self.write_user_bytes_to_pty(sequence, ctx); @@ -20404,7 +20410,7 @@ impl TerminalView { self.update_incompatible_configuration_banner(session.shell().plugins(), ctx) } - // honor_ps1 affects whether the Zap prompt is active, which + // honor_ps1 affects whether the Zaplex prompt is active, which // determines if we need git status updates. self.update_git_status_subscription(ctx); } @@ -20807,7 +20813,7 @@ impl TerminalView { let prompt = Text::new_inline( Self::block_prompt(model, sessions, index), appearance.monospace_font_family(), - appearance.monospace_font_size() * WARP_PROMPT_HEIGHT_LINES, + appearance.monospace_font_size() * ZAPLEX_PROMPT_HEIGHT_LINES, ) .with_style(Properties::default().weight(appearance.monospace_font_weight())) .with_color(terminal_theme_prompt) @@ -20819,7 +20825,7 @@ impl TerminalView { let duration = Text::new_inline( duration_string, appearance.monospace_font_family(), - appearance.monospace_font_size() * WARP_PROMPT_HEIGHT_LINES, + appearance.monospace_font_size() * ZAPLEX_PROMPT_HEIGHT_LINES, ) .with_style(Properties::default().weight(appearance.monospace_font_weight())) .with_color(terminal_theme_prompt) @@ -21082,10 +21088,10 @@ impl TerminalView { let render_context = self.get_terminal_view_render_context(model, app); let enforce_minimum_contrast = *FontSettings::as_ref(app).enforce_minimum_contrast; - // Zap: the condition for alt-screen to render the cli subagent overlay is relaxed from the original + // Zaplex: the condition for alt-screen to render the cli subagent overlay is relaxed from the original // `is_agent_in_control()` to `is_agent_in_control_or_tagged_in()`. The original condition only considered the handoff path // (the agent takes over LRC control), missing the user-initiated tag-in path (`SetInputModeAgent` → - // `tag_in_agent_for_user_long_running_command`). The latter is the main entry point for the overlay under the Zap BYOP + // `tag_in_agent_for_user_long_running_command`). The latter is the main entry point for the overlay under the Zaplex BYOP // pipeline (controller `send_request_input` detects tagged-in → injects // `lrc_command_id` → chat_stream synthesizes a virtual subagent → spawns CLISubagentView); // without relaxing it, even if the view is created, alt-screen still does not mount it, and the model's reply is never seen. @@ -21240,7 +21246,7 @@ impl TerminalView { let mut subshell_separators = HashMap::new(); - for (id, command) in self.warpify_state.get_subshell_separators() { + for (id, command) in self.zaplexify_state.get_subshell_separators() { subshell_separators.insert(*id, render_subshell_separator(command.clone(), appearance)); } @@ -21252,8 +21258,8 @@ impl TerminalView { .active_block() .block_banner() .map(|banner| match banner { - WithinBlockBanner::WarpifyBanner(state) => { - render_warpification_banner(state, appearance, app) + WithinBlockBanner::ZaplexifyBanner(state) => { + render_zaplexification_banner(state, appearance, app) } }); @@ -22381,7 +22387,7 @@ impl TerminalView { } Settings => { if FeatureFlag::SSHTmuxWrapper.is_enabled() { - ctx.emit(Event::OpenSettings(SettingsSection::Warpify)); + ctx.emit(Event::OpenSettings(SettingsSection::Zaplexify)); } else { ctx.emit(Event::OpenSettings(SettingsSection::Features)); } @@ -22491,7 +22497,7 @@ impl TerminalView { } /// Replace the terminal input buffer with the given command that is meant to open a subshell. - /// Set a flag that we should automatically bootstrap AKA "warpify" the subshell when we + /// Set a flag that we should automatically bootstrap AKA "zaplexify" the subshell when we /// receive the [`AfterBlockStarted`] event. pub fn insert_subshell_command_and_bootstrap_if_supported( &mut self, @@ -22725,7 +22731,7 @@ impl TerminalView { shell_type: ShellType, ctx: &mut ViewContext, ) { - // Attempt to auto warpify the subshell when bootstrapped + // Attempt to auto zaplexify the subshell when bootstrapped self.pending_auto_bootstrap_shell_type = Some(shell_type); self.input.update(ctx, |input, ctx| { @@ -22847,7 +22853,7 @@ impl TerminalView { return; }; - let sshed = self.model.lock().is_warpified_ssh() || session.is_legacy_ssh_session(); + let sshed = self.model.lock().is_zaplexified_ssh() || session.is_legacy_ssh_session(); if sshed && !paths.is_empty() && FeatureFlag::SshDragAndDrop.is_enabled() { self.initiate_ssh_file_upload(paths, ctx); } else { @@ -22925,31 +22931,31 @@ impl TerminalView { .and_then(|info| info.ssh_connection_info.clone()) } - fn warpify_ssh_session(&mut self, ctx: &mut ViewContext) { - self.warpify_state.set_shell_detection_in_progress(); - self.begin_ssh_warpify_timeout(SSH_WARPIFY_TIMEOUT_DURATION, ctx); + fn zaplexify_ssh_session(&mut self, ctx: &mut ViewContext) { + self.zaplexify_state.set_shell_detection_in_progress(); + self.begin_ssh_zaplexify_timeout(SSH_ZAPLEXIFY_TIMEOUT_DURATION, ctx); self.clear_line_editor_and_write_to_pty( - convert_script_to_one_line(&begin_warpify_ssh_session_command(ctx)).into_bytes(), + convert_script_to_one_line(&begin_zaplexify_ssh_session_command(ctx)).into_bytes(), ctx, ); } - fn continue_warpify_ssh_session( + fn continue_zaplexify_ssh_session( &mut self, uname: &str, shell_type: ShellType, ctx: &mut ViewContext, ) { - self.warpify_state.set_shell_type(&shell_type); + self.zaplexify_state.set_shell_type(&shell_type); self.model.lock().set_pending_warp_initiated_control_mode(); - if let Some(script) = warpify_ssh_session_command(uname, shell_type, ctx) { + if let Some(script) = zaplexify_ssh_session_command(uname, shell_type, ctx) { self.clear_line_editor_and_write_to_pty_with_mac_workaround_hack( convert_script_to_one_line(&script).into_bytes(), ctx, ); } else { self.add_ssh_error_block( - WarpificationUnavailableReason::UnsupportedShell { + ZaplexificationUnavailableReason::UnsupportedShell { shell_name: shell_type.name().to_string(), }, ctx, @@ -22957,7 +22963,7 @@ impl TerminalView { } } - fn install_tmux_and_warpify( + fn install_tmux_and_zaplexify( &mut self, ctx: &mut ViewContext, install_method: &TmuxInstallMethod, @@ -22973,27 +22979,27 @@ impl TerminalView { ); } - fn begin_ssh_warpify_timeout(&mut self, duration: Duration, ctx: &mut ViewContext) { - let timeout_id = self.warpify_state.replace_timeout_id(); + fn begin_ssh_zaplexify_timeout(&mut self, duration: Duration, ctx: &mut ViewContext) { + let timeout_id = self.zaplexify_state.replace_timeout_id(); let active_block_id = self.model.lock().block_list().active_block_id().clone(); let system_details = self - .warpify_state + .zaplexify_state .ssh_block_state() .and_then(|s| s.get_system_details(ctx)) .to_owned(); - self.warpify_state.add_ssh_warpify_timeout_handle(ctx.spawn( + self.zaplexify_state.add_ssh_zaplexify_timeout_handle(ctx.spawn( async move { Timer::after(duration).await; (timeout_id, active_block_id, system_details) }, |terminal_view, (timeout_id, active_block_id, system_details), ctx| { let is_shell_detection = - terminal_view.warpify_state.is_shell_detection_in_progress(); - if timeout_id == terminal_view.warpify_state.timeout_id() + terminal_view.zaplexify_state.is_shell_detection_in_progress(); + if timeout_id == terminal_view.zaplexify_state.timeout_id() && terminal_view.model.lock().block_list().active_block_id() == &active_block_id { terminal_view.add_ssh_error_block( - WarpificationUnavailableReason::Timeout { + ZaplexificationUnavailableReason::Timeout { is_tmux_install: false, is_shell_detection, system_details, @@ -23011,7 +23017,7 @@ impl TerminalView { ctx: &mut ViewContext, ) { match check_type { - SshLoginStatus::RecheckBeforeWarpifying => { + SshLoginStatus::RecheckBeforeZaplexifying => { // After we receive a line of output from ssh that is NOT prompting for user input (unlike "Enter passphrase: "), // we wait and repeat the check after a small delay in case the state returned to something that's user-input bound. // For example, say the output that kicked off this event was "Permission denied, please try again." and @@ -23033,35 +23039,35 @@ impl TerminalView { }, ); } - SshLoginStatus::ReadyToWarpify => { - // After the confirmation check, we are confident enough to auto-warpify or offer warpification. - let Some(command) = &self.warpify_state.get_pending_ssh_command() else { + SshLoginStatus::ReadyToZaplexify => { + // After the confirmation check, we are confident enough to auto-zaplexify or offer zaplexification. + let Some(command) = &self.zaplexify_state.get_pending_ssh_command() else { return; }; - let ssh_host = &self.warpify_state.get_pending_ssh_host(); + let ssh_host = &self.zaplexify_state.get_pending_ssh_host(); let shell_family = self.shell_family(ctx); - let warpify_settings = WarpifySettings::as_ref(ctx); + let zaplexify_settings = ZaplexifySettings::as_ref(ctx); - let ssh_interactive_session_event = evaluate_warpify_ssh_host( + let ssh_interactive_session_event = evaluate_zaplexify_ssh_host( command, ssh_host.as_deref(), shell_family, - warpify_settings, + zaplexify_settings, ); - if let SshInteractiveSessionDetected::ShouldPromptWarpification { + if let SshInteractiveSessionDetected::ShouldPromptZaplexification { ref host, ref command, } = ssh_interactive_session_event { - if FeatureFlag::WarpifyFooter.is_enabled() { - self.show_warpify_footer( - WarpificationMode::ssh(command.clone(), host.to_owned()), + if FeatureFlag::ZaplexifyFooter.is_enabled() { + self.show_zaplexify_footer( + ZaplexificationMode::ssh(command.clone(), host.to_owned()), ctx, ); } else { - self.add_ssh_warpify_prompt(command, host.to_owned(), ctx) + self.add_ssh_zaplexify_prompt(command, host.to_owned(), ctx) } } @@ -23101,12 +23107,12 @@ impl TerminalView { self.shell_indicator_type } - /// Shows the warpify footer for a detected subshell/SSH command. - fn show_warpify_footer(&mut self, mode: WarpificationMode, ctx: &mut ViewContext) { + /// Shows the zaplexify footer for a detected subshell/SSH command. + fn show_zaplexify_footer(&mut self, mode: ZaplexificationMode, ctx: &mut ViewContext) { let model = self.model.lock(); - // Shared session viewers can't initiate warpification currently. - // Don't show the warpify footer when an agent is monitoring the command either. + // Shared session viewers can't initiate zaplexification currently. + // Don't show the zaplexify footer when an agent is monitoring the command either. if model.shared_session_status().is_viewer() || model.block_list().active_block().is_agent_monitoring() { @@ -23116,11 +23122,11 @@ impl TerminalView { let is_ssh = mode.is_ssh(); self.use_agent_footer.update(ctx, |footer, ctx| { - footer.set_warpify_mode(mode, ctx); + footer.set_zaplexify_mode(mode, ctx); }); self.maybe_show_use_agent_footer_in_blocklist(ctx); - send_telemetry_from_ctx!(TelemetryEvent::WarpifyFooterShown { is_ssh }, ctx); + send_telemetry_from_ctx!(TelemetryEvent::ZaplexifyFooterShown { is_ssh }, ctx); } fn show_initialization_block(&mut self) { @@ -23307,8 +23313,8 @@ impl TypedActionView for TerminalView { "Showed initialization block", WarpA11yRole::TextareaRole, )), - ShowWarpifySettings => Custom(AccessibilityContent::new_without_help( - "Opened Warpify Settings", + ShowZaplexifySettings => Custom(AccessibilityContent::new_without_help( + "Opened Zaplexify Settings", WarpA11yRole::ButtonRole, )), OpenFilesPalette { .. } => Custom(AccessibilityContent::new_without_help( @@ -23359,7 +23365,7 @@ impl TypedActionView for TerminalView { | ControlSequence(_) | TriggerSubshellBootstrap | ShowSubshellBanner(_) - | DismissWarpifyBanner(_) + | DismissZaplexifyBanner(_) | OpenBlockListContextMenu | AliasExpansionBanner(_) | VimModeBanner(_) @@ -23368,8 +23374,8 @@ impl TypedActionView for TerminalView { | OnboardingFlow(_) | ImportSettings | DragAndDropFiles(_) - | WarpifySSHSession - | ShowWarpifySshBanner(_, _) + | ZaplexifySSHSession + | ShowZaplexifySshBanner(_, _) | NotifySshErrorBlock(_) | ToggleBlockFilterOnSelectedOrLastBlock(_) | SetMarkedText { .. } @@ -23823,35 +23829,35 @@ impl TypedActionView for TerminalView { TriggerSubshellBootstrap => self.trigger_subshell_bootstrap(None, false, ctx), ShowSubshellBanner(command) => { // Abort handle is no longer needed since we've waited the 1s already. - self.warpify_state.take_subshell_banner_abort_handle(); + self.zaplexify_state.take_subshell_banner_abort_handle(); - let warpify_keybinding = - keybinding_name_to_keystroke("terminal:warpify_subshell", ctx); - self.show_warpify_banner( - WarpificationMode::subshell(command.to_owned()), + let zaplexify_keybinding = + keybinding_name_to_keystroke("terminal:zaplexify_subshell", ctx); + self.show_zaplexify_banner( + ZaplexificationMode::subshell(command.to_owned()), "Subshell", "subshell", - warpify_keybinding, + zaplexify_keybinding, TelemetryEvent::ShowSubshellBanner, ctx, ); } - ShowWarpifySshBanner(command, host) => { - let warpify_keybinding = - keybinding_name_to_keystroke("terminal:warpify_ssh_session", ctx); - self.show_warpify_banner( - WarpificationMode::ssh(command.to_string(), host.to_owned()), + ShowZaplexifySshBanner(command, host) => { + let zaplexify_keybinding = + keybinding_name_to_keystroke("terminal:zaplexify_ssh_session", ctx); + self.show_zaplexify_banner( + ZaplexificationMode::ssh(command.to_string(), host.to_owned()), "SSH Session", "SSH session", - warpify_keybinding, - TelemetryEvent::SshTmuxWarpifyBannerDisplayed, + zaplexify_keybinding, + TelemetryEvent::SshTmuxZaplexifyBannerDisplayed, ctx, ); } - DismissWarpifyBanner(remember) => { - self.dismiss_warpify_banner(remember, ctx); + DismissZaplexifyBanner(remember) => { + self.dismiss_zaplexify_banner(remember, ctx); if remember.is_ssh() { - send_telemetry_from_ctx!(TelemetryEvent::SshTmuxWarpifyBlockDismissed, ctx); + send_telemetry_from_ctx!(TelemetryEvent::SshTmuxZaplexifyBlockDismissed, ctx); } else { send_telemetry_from_ctx!( TelemetryEvent::DeclineSubshellBootstrap { @@ -23921,11 +23927,11 @@ impl TypedActionView for TerminalView { DragAndDropFiles(paths) => { self.drag_and_drop_files(paths, ctx); } - WarpifySSHSession => self.add_ssh_warpifying_block(ctx), + ZaplexifySSHSession => self.add_ssh_zaplexifying_block(ctx), NotifySshErrorBlock(action) => { if let Some(SshBlockState::Error { handle: ssh_error_block_handle, - }) = self.warpify_state.ssh_block_state() + }) = self.zaplexify_state.ssh_block_state() { ssh_error_block_handle.update(ctx, |error_block, ctx| { error_block.handle_action(action, ctx); @@ -24062,7 +24068,7 @@ impl TypedActionView for TerminalView { else { return; }; - let sshed = self.model.lock().is_warpified_ssh() || session.is_legacy_ssh_session(); + let sshed = self.model.lock().is_zaplexified_ssh() || session.is_legacy_ssh_session(); if sshed && !self.is_file_drop_target { self.is_file_drop_target = true; ctx.notify(); @@ -24099,7 +24105,7 @@ impl TypedActionView for TerminalView { LoadAgentModeConversation => { self.load_agent_mode_conversation(ctx); } - ShowWarpifySettings => ctx.emit(Event::OpenSettings(SettingsSection::Warpify)), + ShowZaplexifySettings => ctx.emit(Event::OpenSettings(SettingsSection::Zaplexify)), DeleteAttachment { index } => { self.ai_context_model.update(ctx, |context_model, ctx| { context_model.remove_pending_attachment(*index, ctx); @@ -24252,7 +24258,7 @@ impl TypedActionView for TerminalView { OpenProjectRulesPane => { if let Some(current_dir) = self.pwd() { let mut warp_md_path = PathBuf::from(¤t_dir); - warp_md_path.push(WARP_MD_PATH); + warp_md_path.push(ZAPLEX_MD_PATH); #[cfg(feature = "local_fs")] ctx.emit(Event::OpenCodeInWarp { source: CodeSource::ProjectRules { path: warp_md_path }, @@ -24993,27 +24999,27 @@ impl View for TerminalView { context.set.insert(init::ROOT_AMBIENT_AGENT_PANE_KEY); } - if let Some(WithinBlockBanner::WarpifyBanner(state)) = + if let Some(WithinBlockBanner::ZaplexifyBanner(state)) = model_lock.block_list().active_block().block_banner() { if state.is_ssh() { - context.set.insert("SshWarpificationBanner"); + context.set.insert("SshZaplexificationBanner"); } else { context.set.insert("SubshellBanner"); } } - // Also set the warpify context when the footer (flag-gated replacement + // Also set the zaplexify context when the footer (flag-gated replacement // for the in-block banner) is active, so the ctrl-i keybinding works. - if let Some(warpify_mode) = self.use_agent_footer.as_ref(app).warpify_mode(app) { - if warpify_mode.is_ssh() { - context.set.insert("SshWarpificationBanner"); + if let Some(zaplexify_mode) = self.use_agent_footer.as_ref(app).zaplexify_mode(app) { + if zaplexify_mode.is_ssh() { + context.set.insert("SshZaplexificationBanner"); } else { context.set.insert("SubshellBanner"); } } - if let Some(SshBlockState::Error { .. }) = self.warpify_state.ssh_block_state() { + if let Some(SshBlockState::Error { .. }) = self.zaplexify_state.ssh_block_state() { context.set.insert(SSH_ERROR_BLOCK_VISIBLE_KEY); } diff --git a/app/src/terminal/view/action.rs b/app/src/terminal/view/action.rs index a39a5475aa..0d32601ecc 100644 --- a/app/src/terminal/view/action.rs +++ b/app/src/terminal/view/action.rs @@ -74,7 +74,7 @@ pub enum OnboardingVersion { /// This represents whether entering a subshell for a particular command should become automatic in /// the future, or to ask again. #[derive(Clone, Debug)] -pub enum RememberForWarpification { +pub enum RememberForZaplexification { /// If yes, need to transmit the command itself so it can be persisted to user-defaults RememberSubshellCommand(String), RememberSSHHost(String), @@ -82,22 +82,22 @@ pub enum RememberForWarpification { DoNotRememberSSHHost, } -impl RememberForWarpification { +impl RememberForZaplexification { pub fn as_bool(&self) -> bool { match self { - RememberForWarpification::RememberSubshellCommand(_) => true, - RememberForWarpification::RememberSSHHost(_) => true, - RememberForWarpification::DoNotRememberSubshellCommand => false, - RememberForWarpification::DoNotRememberSSHHost => false, + RememberForZaplexification::RememberSubshellCommand(_) => true, + RememberForZaplexification::RememberSSHHost(_) => true, + RememberForZaplexification::DoNotRememberSubshellCommand => false, + RememberForZaplexification::DoNotRememberSSHHost => false, } } pub fn is_ssh(&self) -> bool { match self { - RememberForWarpification::RememberSSHHost(_) => true, - RememberForWarpification::DoNotRememberSSHHost => true, - RememberForWarpification::RememberSubshellCommand(_) => false, - RememberForWarpification::DoNotRememberSubshellCommand => false, + RememberForZaplexification::RememberSSHHost(_) => true, + RememberForZaplexification::DoNotRememberSSHHost => true, + RememberForZaplexification::RememberSubshellCommand(_) => false, + RememberForZaplexification::DoNotRememberSubshellCommand => false, } } } @@ -295,14 +295,14 @@ pub enum TerminalAction { }, /// Starts a subshell in the active session. TriggerSubshellBootstrap, - /// If the user says "no" to Warpification, possibly requesting not to be asked again - DismissWarpifyBanner(RememberForWarpification), + /// If the user says "no" to Zaplexification, possibly requesting not to be asked again + DismissZaplexifyBanner(RememberForZaplexification), /// Triggers the banner asking to turn the running block into a subshell. The String is the /// command that the user entered. ShowSubshellBanner(String), - /// Triggers the banner asking to Warpify the active ssh session. The String is the + /// Triggers the banner asking to Zaplexify the active ssh session. The String is the /// command that the user entered. - ShowWarpifySshBanner(String, Option), + ShowZaplexifySshBanner(String, Option), InsertMostRecentCommandCorrection, AliasExpansionBanner(AliasExpansionBannerAction), OpenInWarpBanner(OpenInWarpBannerAction), @@ -319,8 +319,8 @@ pub enum TerminalAction { /// it if possible. SelectAIAttachedBlock(BlockIndex), DragAndDropFiles(Vec), - /// Triggers an ssh session to warpify, even if there is no Warpify Block. - WarpifySSHSession, + /// Triggers an ssh session to zaplexify, even if there is no Zaplexify Block. + ZaplexifySSHSession, NotifySshErrorBlock(SshErrorBlockAction), /// Sets the input mode to Agent Mode SetInputModeAgent, @@ -343,7 +343,7 @@ pub enum TerminalAction { ShowInitializationBlock, /// This is for debugging, dev only for now LoadAgentModeConversation, - ShowWarpifySettings, + ShowZaplexifySettings, /// Removes a pending attachment (image or file) by index in the unified list. DeleteAttachment { index: usize, @@ -551,9 +551,9 @@ impl fmt::Debug for TerminalAction { OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"), AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"), TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"), - DismissWarpifyBanner(remember) => write!(f, "DismissWarpifyBanner({remember:?})"), + DismissZaplexifyBanner(remember) => write!(f, "DismissZaplexifyBanner({remember:?})"), ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"), - ShowWarpifySshBanner(_, _) => f.write_str("ShowWarpifySshBanner"), + ShowZaplexifySshBanner(_, _) => f.write_str("ShowZaplexifySshBanner"), InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"), AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"), OpenInWarpBanner(action) => write!(f, "OpenInWarpBanner({action:?})"), @@ -581,7 +581,7 @@ impl fmt::Debug for TerminalAction { ExecuteRewindFromInlineMenu { .. } => write!(f, "ExecuteRewindFromInlineMenu"), SelectAIAttachedBlock(_) => write!(f, "SelectAIAttachedBlock"), DragAndDropFiles(_) => write!(f, "DragAndDropFiles"), - WarpifySSHSession => write!(f, "WarpifySSHSession"), + ZaplexifySSHSession => write!(f, "ZaplexifySSHSession"), NotifySshErrorBlock(action) => write!(f, "NotifySshErrorBlock({action:?})"), SetInputModeAgent => write!(f, "SetInputModeAgent"), SetInputModeTerminal => write!(f, "SetInputModeTerminal"), @@ -602,7 +602,7 @@ impl fmt::Debug for TerminalAction { SelectAgenticSuggestion(index) => write!(f, "SelectAgenticSuggestion({index:?})"), ShowInitializationBlock => write!(f, "ShowInitializationBlock"), LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"), - ShowWarpifySettings => write!(f, "ShowWarpifySettings"), + ShowZaplexifySettings => write!(f, "ShowZaplexifySettings"), DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"), ToggleAutoexecuteMode => write!(f, "ToggleAutoexecuteMode"), ToggleQueueNextPrompt => write!(f, "ToggleQueueNextPrompt"), diff --git a/app/src/terminal/view/ambient_agent/host_selector.rs b/app/src/terminal/view/ambient_agent/host_selector.rs index bb21758f22..fbbf10965f 100644 --- a/app/src/terminal/view/ambient_agent/host_selector.rs +++ b/app/src/terminal/view/ambient_agent/host_selector.rs @@ -39,13 +39,13 @@ const MENU_HEADER_LABEL: &str = "Execution host"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Host { - Zap, + Zaplex, } impl Host { fn display_name(self) -> &'static str { match self { - Host::Zap => "Zap", + Host::Zaplex => "Zaplex", } } } @@ -77,7 +77,7 @@ impl HostSelector { // field is exercised at construction time (not just written to on // `SelectHost`), so it stays out of clippy's `field is never read` // warning while still serving as the source of truth for the label. - let selected = Host::Zap; + let selected = Host::Zaplex; let button = ctx.add_typed_action_view(|_ctx| { ActionButton::new(selected.display_name(), NakedHeaderButtonTheme) @@ -190,7 +190,7 @@ fn build_menu_items( ) }; - vec![header, item_for(Host::Zap)] + vec![header, item_for(Host::Zaplex)] } impl Entity for HostSelector { diff --git a/app/src/terminal/view/ambient_agent/mod.rs b/app/src/terminal/view/ambient_agent/mod.rs index 993630bbf0..4722479d31 100644 --- a/app/src/terminal/view/ambient_agent/mod.rs +++ b/app/src/terminal/view/ambient_agent/mod.rs @@ -1,5 +1,5 @@ mod block; -// Zap Wave 7-2: `first_time_setup` to be deleted physically with ambient agent UI. +// Zaplex Wave 7-2: `first_time_setup` to be deleted physically with ambient agent UI. mod footer; mod harness_selector; mod host_selector; diff --git a/app/src/terminal/view/block_banner/mod.rs b/app/src/terminal/view/block_banner/mod.rs index 1c87d28a3d..a423267ecd 100644 --- a/app/src/terminal/view/block_banner/mod.rs +++ b/app/src/terminal/view/block_banner/mod.rs @@ -6,9 +6,9 @@ //! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height //! needs to be taken into account. -mod warpify; +mod zaplexify; -pub use warpify::*; +pub use zaplexify::*; use warpui::{ elements::{ ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle, @@ -28,22 +28,22 @@ pub const BLOCK_BANNER_HEIGHT: f32 = CONSTRAINED_BANNER_HEIGHT + BANNER_TOP_MARG pub const BLOCK_BANNER_DESCRIPTION_MAX_HEIGHT: f32 = 24.; pub enum WithinBlockBanner { - WarpifyBanner(WarpifyBannerState), + ZaplexifyBanner(ZaplexifyBannerState), } impl WithinBlockBanner { pub fn banner_height(&self) -> f32 { - match self.warpify_mode() { - Some(WarpificationMode::Ssh { .. }) => { + match self.zaplexify_mode() { + Some(ZaplexificationMode::Ssh { .. }) => { BLOCK_BANNER_HEIGHT + BLOCK_BANNER_DESCRIPTION_MAX_HEIGHT } - Some(WarpificationMode::Subshell { .. }) | None => BLOCK_BANNER_HEIGHT, + Some(ZaplexificationMode::Subshell { .. }) | None => BLOCK_BANNER_HEIGHT, } } - pub fn warpify_mode(&self) -> Option<&WarpificationMode> { + pub fn zaplexify_mode(&self) -> Option<&ZaplexificationMode> { match self { - WithinBlockBanner::WarpifyBanner(state) => Some(&state.mode), + WithinBlockBanner::ZaplexifyBanner(state) => Some(&state.mode), } } } diff --git a/app/src/terminal/view/block_banner/warpify.rs b/app/src/terminal/view/block_banner/zaplexify.rs similarity index 75% rename from app/src/terminal/view/block_banner/warpify.rs rename to app/src/terminal/view/block_banner/zaplexify.rs index d453299db2..cd6fa34543 100644 --- a/app/src/terminal/view/block_banner/warpify.rs +++ b/app/src/terminal/view/block_banner/zaplexify.rs @@ -16,8 +16,8 @@ use warpui::{ use crate::{ appearance::Appearance, terminal::{ - ssh::warpify::warpify_description, - view::{RememberForWarpification, TerminalAction}, + ssh::zaplexify::zaplexify_description, + view::{RememberForZaplexification, TerminalAction}, }, themes::theme::Fill, ui_components::blended_colors, @@ -29,7 +29,7 @@ const CLOSE_BUTTON_DIAMETER: f32 = 20.0; const STANDARD_PADDING: f32 = 8.0; #[derive(Clone)] -pub enum WarpificationMode { +pub enum ZaplexificationMode { Ssh { command: String, host: Option, @@ -40,7 +40,7 @@ pub enum WarpificationMode { }, } -impl WarpificationMode { +impl ZaplexificationMode { pub fn ssh(command: String, host: Option) -> Self { Self::Ssh { command, @@ -58,32 +58,32 @@ impl WarpificationMode { } } -impl WarpificationMode { +impl ZaplexificationMode { pub fn is_ssh(&self) -> bool { matches!(self, Self::Ssh { .. }) } } -pub struct WarpifyBannerState { - pub mode: WarpificationMode, +pub struct ZaplexifyBannerState { + pub mode: ZaplexificationMode, pub height: f32, pub accept_button_mouse_state: MouseStateHandle, pub dont_ask_button_mouse_state: MouseStateHandle, pub dismiss_button_mouse_state: MouseStateHandle, - /// This keybinding gets rendered in the Warpification banner, but we can't look it up + /// This keybinding gets rendered in the Zaplexification banner, but we can't look it up /// during render as a &mut AppContext is not available then. This needs to get /// looked up during action handling and cached here. - pub initialize_warpify_keybinding: Option, + pub initialize_zaplexify_keybinding: Option, pub hover_state: MouseStateHandle, } -impl WarpifyBannerState { - pub fn new(mode: WarpificationMode, initialize_warpify_keybinding: Option) -> Self { +impl ZaplexifyBannerState { + pub fn new(mode: ZaplexificationMode, initialize_zaplexify_keybinding: Option) -> Self { Self { mode, height: 0.0, - initialize_warpify_keybinding, + initialize_zaplexify_keybinding, accept_button_mouse_state: Default::default(), dont_ask_button_mouse_state: Default::default(), dismiss_button_mouse_state: Default::default(), @@ -97,40 +97,40 @@ impl WarpifyBannerState { pub fn title(&self) -> &str { match &self.mode { - WarpificationMode::Ssh { .. } => "Warpify SSH session", - WarpificationMode::Subshell { .. } => "Warpify subshell", + ZaplexificationMode::Ssh { .. } => "Zaplexify SSH session", + ZaplexificationMode::Subshell { .. } => "Zaplexify subshell", } } pub fn action(&self) -> TerminalAction { match &self.mode { - WarpificationMode::Ssh { .. } => TerminalAction::WarpifySSHSession, - WarpificationMode::Subshell { .. } => TerminalAction::TriggerSubshellBootstrap, + ZaplexificationMode::Ssh { .. } => TerminalAction::ZaplexifySSHSession, + ZaplexificationMode::Subshell { .. } => TerminalAction::TriggerSubshellBootstrap, } } - fn remember_for_warpification(&self, should_remember: bool) -> RememberForWarpification { + fn remember_for_zaplexification(&self, should_remember: bool) -> RememberForZaplexification { match &self.mode { - WarpificationMode::Ssh { command, host, .. } => { + ZaplexificationMode::Ssh { command, host, .. } => { let Some(host) = host else { if should_remember { - return RememberForWarpification::RememberSubshellCommand( + return RememberForZaplexification::RememberSubshellCommand( command.to_owned(), ); } - return RememberForWarpification::DoNotRememberSSHHost; + return RememberForZaplexification::DoNotRememberSSHHost; }; if should_remember { - RememberForWarpification::RememberSSHHost(host.to_owned()) + RememberForZaplexification::RememberSSHHost(host.to_owned()) } else { - RememberForWarpification::DoNotRememberSSHHost + RememberForZaplexification::DoNotRememberSSHHost } } - WarpificationMode::Subshell { command } => { + ZaplexificationMode::Subshell { command } => { if should_remember { - RememberForWarpification::RememberSubshellCommand(command.to_owned()) + RememberForZaplexification::RememberSubshellCommand(command.to_owned()) } else { - RememberForWarpification::DoNotRememberSubshellCommand + RememberForZaplexification::DoNotRememberSubshellCommand } } } @@ -140,19 +140,19 @@ impl WarpifyBannerState { /// This banner is shown when the user runs a command which is recognized as a subshell-compatible /// command. It asks if they want to bootstrap a subshell and, if so, whether we should ask again /// next time they run the same command. -pub fn render_warpification_banner( - state: &WarpifyBannerState, +pub fn render_zaplexification_banner( + state: &ZaplexifyBannerState, appearance: &Appearance, app: &AppContext, ) -> Box { let yes_button = render_yes_button( state, - &state.initialize_warpify_keybinding, + &state.initialize_zaplexify_keybinding, &state.accept_button_mouse_state, appearance, ); - let remember = state.remember_for_warpification(true); + let remember = state.remember_for_zaplexification(true); let dont_ask_button = Container::new( appearance .ui_builder() @@ -163,7 +163,7 @@ pub fn render_warpification_banner( .with_text_label(crate::t!("common-do-not-show-again")) .build() .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner( + ctx.dispatch_typed_action(TerminalAction::DismissZaplexifyBanner( remember.to_owned(), )); }) @@ -172,7 +172,7 @@ pub fn render_warpification_banner( .with_margin_right(16.) .finish(); - let do_not_remember = state.remember_for_warpification(false); + let do_not_remember = state.remember_for_zaplexification(false); let close_button = appearance .ui_builder() .close_button( @@ -181,7 +181,7 @@ pub fn render_warpification_banner( ) .build() .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner( + ctx.dispatch_typed_action(TerminalAction::DismissZaplexifyBanner( do_not_remember.to_owned(), )); }) @@ -202,11 +202,11 @@ pub fn render_warpification_banner( render_block_banner( |hover_state| { - if let WarpificationMode::Ssh { + if let ZaplexificationMode::Ssh { hyperlink_index, .. } = &state.mode { - let description = Container::new(warpify_description(app, hyperlink_index)) + let description = Container::new(zaplexify_description(app, hyperlink_index)) .with_uniform_margin(STANDARD_PADDING) .with_margin_top(4.) .finish(); @@ -227,12 +227,12 @@ pub fn render_warpification_banner( } fn render_yes_button( - state: &WarpifyBannerState, - initialize_warpification_keybinding: &Option, + state: &ZaplexifyBannerState, + initialize_zaplexification_keybinding: &Option, mouse_state: &MouseStateHandle, appearance: &Appearance, ) -> Box { - let yes_button = match initialize_warpification_keybinding { + let yes_button = match initialize_zaplexification_keybinding { Some(keystroke) => appearance .ui_builder() .keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone()) diff --git a/app/src/terminal/view/block_onboarding/onboarding_agentic_suggestions_block.rs b/app/src/terminal/view/block_onboarding/onboarding_agentic_suggestions_block.rs index d802ea01f2..a888d29a65 100644 --- a/app/src/terminal/view/block_onboarding/onboarding_agentic_suggestions_block.rs +++ b/app/src/terminal/view/block_onboarding/onboarding_agentic_suggestions_block.rs @@ -153,7 +153,7 @@ impl OnboardingAgenticSuggestionsBlock { let matrix_save_directory = themes_dir() .into_os_string() .into_string() - .unwrap_or("the Zap themes directory.".to_string()); + .unwrap_or("the Zaplex themes directory.".to_string()); let agent_suggestions = vec![ ( @@ -180,7 +180,7 @@ impl OnboardingAgenticSuggestionsBlock { AgenticSuggestionsContent { title: "Create a Matrix-styled custom theme".to_string(), description: "Make your terminal look like you entered the Matrix".to_string(), - prompt: format!("First check if {matrix_save_directory} exists, and create this path if it doesn't already exist. Then create a matrix theme for my Zap terminal without a background image field, following exact YAML structure on the warp website without any extra or missing fields. Call it matrix.yaml and save it in the directory we previously created. Once you've verified that the theme is correct and ready to be applied, let me know by only saying 'The matrix theme is now available at .'."), + prompt: format!("First check if {matrix_save_directory} exists, and create this path if it doesn't already exist. Then create a matrix theme for my Zaplex terminal without a background image field, following exact YAML structure on the warp website without any extra or missing fields. Call it matrix.yaml and save it in the directory we previously created. Once you've verified that the theme is correct and ready to be applied, let me know by only saying 'The matrix theme is now available at .'."), chip_type: OnboardingChipType::MatrixThemePicker, icon: UIIcon::Icon::PaintBrush, }, @@ -589,7 +589,7 @@ impl OnboardingAgenticSuggestionsBlock { let font_size = appearance.monospace_font_size(); let font_color = current_theme.main_text_color(current_theme.background()); - const WELCOME_TEXT_LINE_ONE: &str = "Welcome to Zap!"; + const WELCOME_TEXT_LINE_ONE: &str = "Welcome to Zaplex!"; const WELCOME_TEXT_LINE_TWO_PART_ONE: &str = "Here are a few examples of how to leverage the power of AI in your terminal using"; const WELCOME_TEXT_LINE_TWO_PART_TWO: &str = " Agent Mode"; diff --git a/app/src/terminal/view/block_onboarding/onboarding_prompt_block.rs b/app/src/terminal/view/block_onboarding/onboarding_prompt_block.rs index 1bb2da2319..5d7af7ce9d 100644 --- a/app/src/terminal/view/block_onboarding/onboarding_prompt_block.rs +++ b/app/src/terminal/view/block_onboarding/onboarding_prompt_block.rs @@ -63,9 +63,9 @@ impl OnboardingPromptBlock { let font_color = current_theme.main_text_color(current_theme.background()); // Copy - https://docs.google.com/document/d/1zttBLI5Mw07kUupvrMQoC5aTwTXSHIUOIFFnxZ8GQEU/edit - const LINE_ONE: &str = "Next, let’s set up your prompt. Zap has a custom prompt builder or you can select PS1 to honor your pre-existing prompt configuration."; + const LINE_ONE: &str = "Next, let’s set up your prompt. Zaplex has a custom prompt builder or you can select PS1 to honor your pre-existing prompt configuration."; const LINE_TWO: &str = - "Zap works with many custom prompts like oh-my-zsh, Starship, Powerlevel10K. "; + "Zaplex works with many custom prompts like oh-my-zsh, Starship, Powerlevel10K. "; const LINK_DESTINATION: &str = ""; @@ -329,7 +329,7 @@ impl OnboardingPromptBlock { fn render_warp_prompt_button_interior(&self, appearance: &Appearance) -> Box { // Pixel values pulled from Figma mocks // https://www.figma.com/file/y888viqzWBoMpFTxQqkQEN/Activation?node-id=568:1595&mode=dev - const HEADER_TEXT: &str = "Zap prompt"; + const HEADER_TEXT: &str = "Zaplex prompt"; const HEADER_MARGIN_LEFT: f32 = 4.; const SECTION_MARGIN_TOP: f32 = 8.; const OUTER_CORNER_RADIUS: f32 = 4.; diff --git a/app/src/terminal/view/blocklist_filter.rs b/app/src/terminal/view/blocklist_filter.rs index 3c1f74dced..cc68e2da5c 100644 --- a/app/src/terminal/view/blocklist_filter.rs +++ b/app/src/terminal/view/blocklist_filter.rs @@ -5,7 +5,7 @@ use crate::ai::agent::{conversation::AIConversation, task::Task, AIAgentExchange /// Returns whether a task's exchanges should be shown in the blocklist. pub(super) fn should_show_task_in_blocklist(task: &Task) -> bool { // All tasks are visible in the blocklist aside from CLI (long-running command), - // Zap documentation search, and conversation search subtasks. + // Zaplex documentation search, and conversation search subtasks. !task.is_cli_subagent() && !task.is_warp_documentation_search_subagent() && !task.is_conversation_search_subagent() diff --git a/app/src/terminal/view/bookmarks.rs b/app/src/terminal/view/bookmarks.rs index 8c7d034e05..9fd82771ba 100644 --- a/app/src/terminal/view/bookmarks.rs +++ b/app/src/terminal/view/bookmarks.rs @@ -6,7 +6,7 @@ use warpui::{ }; use crate::appearance::Appearance; -use crate::terminal::{model::block::Block, view::WARP_PROMPT_HEIGHT_LINES}; +use crate::terminal::{model::block::Block, view::ZAPLEX_PROMPT_HEIGHT_LINES}; pub(super) fn render_floating_block_snapshot( block: &Block, @@ -29,7 +29,7 @@ pub(super) fn render_floating_block_snapshot( .with_style(UiComponentStyles { font_color: Some(font_color), // Preview prompt font size should scale the same way as the block prompt. - font_size: Some(appearance.monospace_font_size() * WARP_PROMPT_HEIGHT_LINES), + font_size: Some(appearance.monospace_font_size() * ZAPLEX_PROMPT_HEIGHT_LINES), font_family_id: Some(appearance.monospace_font_family()), ..Default::default() }) @@ -72,7 +72,7 @@ pub(super) fn render_floating_block_snapshot( .with_style(UiComponentStyles { font_color: Some(sub_font_color), font_size: Some( - appearance.monospace_font_size() * WARP_PROMPT_HEIGHT_LINES, + appearance.monospace_font_size() * ZAPLEX_PROMPT_HEIGHT_LINES, ), font_family_id: Some(appearance.monospace_font_family()), ..Default::default() diff --git a/app/src/terminal/view/init.rs b/app/src/terminal/view/init.rs index 8480d52fdf..e41575593f 100644 --- a/app/src/terminal/view/init.rs +++ b/app/src/terminal/view/init.rs @@ -83,12 +83,12 @@ fn init_overlapping_keybindings(app: &mut AppContext) { app.register_fixed_bindings([ FixedBinding::new( escape_key, - TerminalAction::NotifySshErrorBlock(SshErrorBlockAction::ContinueWithoutWarpification), + TerminalAction::NotifySshErrorBlock(SshErrorBlockAction::ContinueWithoutZaplexification), id!(SSH_ERROR_BLOCK_VISIBLE_KEY) & block_action_context(), ), FixedBinding::new( cmd_or_ctrl_enter, - TerminalAction::NotifySshErrorBlock(SshErrorBlockAction::ContinueWithoutWarpification), + TerminalAction::NotifySshErrorBlock(SshErrorBlockAction::ContinueWithoutZaplexification), id!(SSH_ERROR_BLOCK_VISIBLE_KEY) & block_action_context(), ), ]); @@ -101,8 +101,8 @@ pub fn init(app: &mut AppContext) { app.register_binding_validator::(is_binding_pty_compliant); init_overlapping_keybindings(app); - // Register input mode bindings before warpify bindings so ctrl-i warpifies - // instead of opening inline agent when a warpify banner is visible. + // Register input mode bindings before zaplexify bindings so ctrl-i zaplexifies + // instead of opening inline agent when a zaplexify banner is visible. register_input_mode_bindings(app); app.register_fixed_bindings([ @@ -342,8 +342,8 @@ pub fn init(app: &mut AppContext) { | (id!("Terminal") & !id!("IMEOpen") & id!(flags::CLI_AGENT_RICH_INPUT_OPEN)), ), EditableBinding::new( - "terminal:warpify_subshell", - crate::t!("keybinding-desc-terminal-warpify-subshell"), + "terminal:zaplexify_subshell", + crate::t!("keybinding-desc-terminal-zaplexify-subshell"), TerminalAction::TriggerSubshellBootstrap, ) .with_key_binding("ctrl-i") @@ -351,16 +351,16 @@ pub fn init(app: &mut AppContext) { id!("Terminal") & !id!("IMEOpen") & id!("LongRunningCommand") & id!("SubshellBanner"), ), EditableBinding::new( - "terminal:warpify_ssh_session", - crate::t!("keybinding-desc-terminal-warpify-ssh-session"), - TerminalAction::WarpifySSHSession, + "terminal:zaplexify_ssh_session", + crate::t!("keybinding-desc-terminal-zaplexify-ssh-session"), + TerminalAction::ZaplexifySSHSession, ) .with_key_binding("ctrl-i") .with_context_predicate( id!("Terminal") & !id!("IMEOpen") & id!("LongRunningCommand") - & id!("SshWarpificationBanner"), + & id!("SshZaplexificationBanner"), ), EditableBinding::new( ACCEPT_PROMPT_SUGGESTION_KEYBINDING, @@ -583,7 +583,7 @@ pub fn init(app: &mut AppContext) { .with_context_predicate( id!("Terminal") & id!("TerminalView_NonEmptyBlockList") & !id!("AltScreen"), ), - // Zap: removed terminal:open_share_block_modal keybinding (cloud-based share block) + // Zaplex: removed terminal:open_share_block_modal keybinding (cloud-based share block) EditableBinding::new( "terminal:bookmark_selected_block", crate::t!("keybinding-desc-terminal-bookmark-selected-block"), @@ -963,7 +963,7 @@ pub fn init(app: &mut AppContext) { ) .with_context_predicate(id!("Terminal") & id!(flags::HAS_SETTINGS_TO_IMPORT_FLAG))]); - // Zap: removed terminal:share_current_session / terminal:stop_sharing_current_session keybindings (cloud-based shared session) + // Zaplex: removed terminal:share_current_session / terminal:stop_sharing_current_session keybindings (cloud-based shared session) app.register_editable_bindings([EditableBinding::new( TOGGLE_BLOCK_FILTER_KEYBINDING, @@ -1055,7 +1055,7 @@ fn register_input_mode_bindings(app: &mut AppContext) { // A context predicate that matches when the input mode bindings are // available for use. Disabled when a CLI agent session is active — the - // Zap agent should not be tagged into a CLI agent's command, and the + // Zaplex agent should not be tagged into a CLI agent's command, and the // `!` prefix is the only way to toggle shell mode in the rich input. let base_context = id!(flags::IS_ANY_AI_ENABLED) & (id!("Input") | id!("Terminal")) diff --git a/app/src/terminal/view/inline_banner/alias_expansion.rs b/app/src/terminal/view/inline_banner/alias_expansion.rs index 6dd0fd89db..613702a842 100644 --- a/app/src/terminal/view/inline_banner/alias_expansion.rs +++ b/app/src/terminal/view/inline_banner/alias_expansion.rs @@ -91,7 +91,7 @@ pub fn render_alias_expansion_banner( InlineBannerStyle::VeryLowPriority, appearance, InlineBannerContent { - title: "Zap can auto-expand aliases.".into(), + title: "Zaplex can auto-expand aliases.".into(), buttons, content: Some(content), close_button: Some(close_button), diff --git a/app/src/terminal/view/inline_banner/open_in_warp.rs b/app/src/terminal/view/inline_banner/open_in_warp.rs index ddea028521..0d8c84e212 100644 --- a/app/src/terminal/view/inline_banner/open_in_warp.rs +++ b/app/src/terminal/view/inline_banner/open_in_warp.rs @@ -46,11 +46,11 @@ impl OpenInWarpBannerState { } } -/// Given an openable file, format a file-specific title for the Open in Zap banner. +/// Given an openable file, format a file-specific title for the Open in Zaplex banner. fn file_title_text(openable_path: &OpenablePath) -> String { match openable_path.file_type { OpenableFileType::Markdown => { - "Did you know that Zap can directly display Markdown files?".to_string() + "Did you know that Zaplex can directly display Markdown files?".to_string() } OpenableFileType::Code | OpenableFileType::Text => { cfg_if::cfg_if! { @@ -61,13 +61,13 @@ fn file_title_text(openable_path: &OpenablePath) -> String { match language.as_ref().map(|language| language.display_name()) { Some(display_name) => { - format!("Did you know that Zap can directly edit {display_name} files?") + format!("Did you know that Zaplex can directly edit {display_name} files?") } - None => "Did you know that Zap can directly edit code?".to_string(), + None => "Did you know that Zaplex can directly edit code?".to_string(), } } else { // The `languages` crate is not available on WASM, so use a fallback message. - "Did you know that Zap can directly edit code?".to_string() + "Did you know that Zaplex can directly edit code?".to_string() } } } @@ -80,8 +80,8 @@ pub fn render_open_in_warp_banner( appearance: &Appearance, ) -> Box { let button_text = match state.target.file_type { - OpenableFileType::Markdown => "View in Zap", - OpenableFileType::Code | OpenableFileType::Text => "Edit in Zap", + OpenableFileType::Markdown => "View in Zaplex", + OpenableFileType::Code | OpenableFileType::Text => "Edit in Zaplex", }; let open_button = InlineBannerTextButton { diff --git a/app/src/terminal/view/inline_banner/prompt_suggestions.rs b/app/src/terminal/view/inline_banner/prompt_suggestions.rs index 9acf3052ea..a9bf870c4c 100644 --- a/app/src/terminal/view/inline_banner/prompt_suggestions.rs +++ b/app/src/terminal/view/inline_banner/prompt_suggestions.rs @@ -109,7 +109,7 @@ pub struct PromptSuggestionBannerState { /// The server request token, used to construct a debug link (dogfood only). pub server_request_token: Option, - /// Zap BYOP: chips generated when the model actively calls the `suggest_prompt` tool carry an action_id; + /// Zaplex BYOP: chips generated when the model actively calls the `suggest_prompt` tool carry an action_id; /// when accept/reject is called, `complete_suggest_prompt_action` must be called to close the oneshot channel /// so the BYOP loop gets the result and continues to the next iteration. `None` means this chip came from a different path /// (e.g., passive suggestion from MAA server side), no need to go through BYOP completion callback. diff --git a/app/src/terminal/view/inline_banner/shell_process_terminated.rs b/app/src/terminal/view/inline_banner/shell_process_terminated.rs index 742de5fe35..14471745f7 100644 --- a/app/src/terminal/view/inline_banner/shell_process_terminated.rs +++ b/app/src/terminal/view/inline_banner/shell_process_terminated.rs @@ -22,7 +22,7 @@ pub fn render_shell_process_terminated_banner( color_override: Some(appearance.theme().foreground().into_solid()), }), content: Some(vec![Text::new( - "The output from Zap's initialization script is visible above to assist with debugging.", + "The output from Zaplex's initialization script is visible above to assist with debugging.", appearance.ui_font_family(), appearance.ui_font_size(), )]), diff --git a/app/src/terminal/view/inline_banner/ssh.rs b/app/src/terminal/view/inline_banner/ssh.rs index 37da9a8bd6..8de6f241f6 100644 --- a/app/src/terminal/view/inline_banner/ssh.rs +++ b/app/src/terminal/view/inline_banner/ssh.rs @@ -38,12 +38,12 @@ pub fn render_inline_ssh_wrapper_banner( let (style, title) = if state.wrapper_enabled { ( InlineBannerStyle::LowPriority, - "Zap SSH wrapper enabled".to_string(), + "Zaplex SSH wrapper enabled".to_string(), ) } else { ( InlineBannerStyle::VeryLowPriority, - "Zap SSH wrapper disabled".to_string(), + "Zaplex SSH wrapper disabled".to_string(), ) }; let buttons = vec![ diff --git a/app/src/terminal/view/inline_banner/vim_mode.rs b/app/src/terminal/view/inline_banner/vim_mode.rs index 86d7098d08..eb09e08f45 100644 --- a/app/src/terminal/view/inline_banner/vim_mode.rs +++ b/app/src/terminal/view/inline_banner/vim_mode.rs @@ -46,7 +46,7 @@ pub fn render_vim_mode_banner( InlineBannerStyle::LowPriority, appearance, InlineBannerContent { - title: "Enable Zap's Vim keybindings?".to_string(), + title: "Enable Zaplex's Vim keybindings?".to_string(), buttons, close_button: Some(close_button), ..Default::default() diff --git a/app/src/terminal/view/link_detection.rs b/app/src/terminal/view/link_detection.rs index 36ca2674c9..56322ff15f 100644 --- a/app/src/terminal/view/link_detection.rs +++ b/app/src/terminal/view/link_detection.rs @@ -433,7 +433,7 @@ impl super::TerminalView { // where we can spawn a local tty. #[cfg(feature = "local_fs")] impl super::TerminalView { - /// Zap: Determine if a given session is a remote-server (SSH) session. + /// Zaplex: Determine if a given session is a remote-server (SSH) session. /// /// When `local_tty` is not enabled / on wasm / `SshRemoteServer` feature flag is disabled, /// always return `false`, keeping local behavior unchanged. @@ -463,7 +463,7 @@ impl super::TerminalView { false } - /// Zap: Obtain directory listing validation context for a remote session's cwd. + /// Zaplex: Obtain directory listing validation context for a remote session's cwd. /// /// Cache hit returns `Remote(Some(..))` directly; cache miss asynchronously initiates /// daemon `ListDirectory` RPC to fetch the directory listing, returning `Remote(None)` @@ -579,7 +579,7 @@ impl super::TerminalView { ) { use crate::util::file::LinkValidationContext; - // Zap: Determine if the hovered block's session is a remote-server session. + // Zaplex: Determine if the hovered block's session is a remote-server session. // Remote session files are not on local disk; use `LinkValidationContext::Remote` // with daemon-fetched real directory listing for precise validation. let block_session_id = match position { @@ -596,7 +596,7 @@ impl super::TerminalView { // For AltScreen we scan for relative path with the current working directory. // For BlockList we scan for relative path with the pwd of the hovered block. // - // Zap: Remote session block `pwd()` is the remote cwd reported by shell-integration; + // Zaplex: Remote session block `pwd()` is the remote cwd reported by shell-integration; // concatenated, it yields the correct remote absolute path, so remote blocks also // participate in scanning (no longer skipped). let pwd_to_scan_for = match position { @@ -616,7 +616,7 @@ impl super::TerminalView { .and_then(|block| block.pwd().map(String::from)), }; - // Zap: Remote sessions use cached cwd directory listing for precise validation; + // Zaplex: Remote sessions use cached cwd directory listing for precise validation; // local sessions keep `Local`. let validation_ctx = match (&pwd_to_scan_for, block_session_id) { #[cfg(all(feature = "local_tty", not(target_family = "wasm")))] diff --git a/app/src/terminal/view/open_in_warp.rs b/app/src/terminal/view/open_in_warp.rs index 658c34d123..a38a1bbd5c 100644 --- a/app/src/terminal/view/open_in_warp.rs +++ b/app/src/terminal/view/open_in_warp.rs @@ -45,7 +45,7 @@ const LEARN_MORE_MARKDOWN_URL: &str = ""; const LEARN_MORE_CODE_URL: &str = ""; -/// A path to a file that can be opened in Zap, along with its type. +/// A path to a file that can be opened in Zaplex, along with its type. #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpenablePath { pub path: PathBuf, @@ -99,7 +99,7 @@ impl TerminalView { } } - /// Whether or not the "Open in Zap" banner is open. + /// Whether or not the "Open in Zaplex" banner is open. #[cfg(feature = "integration_tests")] pub fn is_open_in_warp_banner_open(&self) -> bool { self.inline_banners_state.open_in_warp_banner.is_some() @@ -129,7 +129,7 @@ impl TerminalView { } /// Insert a suggestion banner for opening the file `openable_path`, originating from - /// `session`, in a Zap pane. + /// `session`, in a Zaplex pane. fn suggest_open_in_warp( &mut self, openable_path: OpenablePath, @@ -237,7 +237,7 @@ impl TerminalView { match &self.inline_banners_state.open_in_warp_banner { Some(banner_state) => { ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help( - format!("Open {} in Zap", banner_state.target.path.display()), + format!("Open {} in Zaplex", banner_state.target.path.display()), WarpA11yRole::UserAction, )) } @@ -246,14 +246,14 @@ impl TerminalView { } OpenInWarpBannerAction::Close => { ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help( - "Close View in Zap banner", + "Close View in Zaplex banner", WarpA11yRole::UserAction, )) } OpenInWarpBannerAction::LearnMore => { ActionAccessibilityContent::Custom(AccessibilityContent::new( crate::t!("common-learn-more"), - "Learn more about opening Markdown files in Zap", + "Learn more about opening Markdown files in Zaplex", WarpA11yRole::UserAction, )) } @@ -266,7 +266,7 @@ lazy_static! { HashSet::from(["bat", "cat", "glow", "less", "open"]); } -/// Examines `command` for a file openable in Zap, returning the resolved path and type if found. +/// Examines `command` for a file openable in Zaplex, returning the resolved path and type if found. async fn check_openable_in_warp( command: String, working_directory: Option, @@ -321,7 +321,7 @@ async fn check_openable_in_warp( ); if async_fs::metadata(&resolved).await.is_ok() { - // We've found a file that exists and can be opened in Zap. + // We've found a file that exists and can be opened in Zaplex. return Some(OpenablePath { path: resolved, file_type, diff --git a/app/src/terminal/view/open_in_warp_tests.rs b/app/src/terminal/view/open_in_warp_tests.rs index 31cb5a95ef..1e095a0573 100644 --- a/app/src/terminal/view/open_in_warp_tests.rs +++ b/app/src/terminal/view/open_in_warp_tests.rs @@ -40,7 +40,7 @@ fn test_any_text_file_type_supported() { TopLevelCommandCaseSensitivity::CaseInsensitive, EscapeChar::Backslash, )); - // .svg is a non-markdown file, and we allow opening it in the Zap Editor (as a "code" file). + // .svg is a non-markdown file, and we allow opening it in the Zaplex Editor (as a "code" file). assert_eq!( result, Some(OpenablePath { diff --git a/app/src/terminal/view/pane_impl.rs b/app/src/terminal/view/pane_impl.rs index 2f80c4e344..5030770a53 100644 --- a/app/src/terminal/view/pane_impl.rs +++ b/app/src/terminal/view/pane_impl.rs @@ -439,7 +439,7 @@ impl BackingView for TerminalView { .into_item(), ); } - // Zap: Remove "Share session" entry from pane header (cloud-side shared session) + // Zaplex: Remove "Share session" entry from pane header (cloud-side shared session) // Split-pane related items. if self.split_pane_state(ctx).is_in_split_pane() { diff --git a/app/src/terminal/view/rich_content.rs b/app/src/terminal/view/rich_content.rs index 401139d293..048ab6a2cd 100644 --- a/app/src/terminal/view/rich_content.rs +++ b/app/src/terminal/view/rich_content.rs @@ -11,14 +11,14 @@ use crate::{ model::{ blocks::RichContentItem, rich_content::RichContentType, terminal_model::BlockIndex, }, - ssh::{error::SshErrorBlock, install_tmux::SshInstallTmuxBlock, warpify::SshWarpifyBlock}, + ssh::{error::SshErrorBlock, install_tmux::SshInstallTmuxBlock, zaplexify::SshZaplexifyBlock}, view::{ ambient_agent::AmbientAgentEntryBlock, block_onboarding::onboarding_agentic_suggestions_block::OnboardingAgenticSuggestionsBlock, ssh_remote_server_choice_view::SshRemoteServerChoiceView, ssh_remote_server_failed_banner::SshRemoteServerFailedBanner, }, - warpify::success_block::WarpifySuccessBlock, + zaplexify::success_block::ZaplexifySuccessBlock, TerminalView, }, }; @@ -207,8 +207,8 @@ pub enum RichContentMetadata { EnvVarCollectionBlock { env_var_collection_block_handle: ViewHandle, }, - SshWarpifyBlock { - ssh_warpify_block_handle: ViewHandle, + SshZaplexifyBlock { + ssh_zaplexify_block_handle: ViewHandle, }, SshInstallTmuxBlock { ssh_install_tmux_block_handle: ViewHandle, @@ -222,8 +222,8 @@ pub enum RichContentMetadata { SshRemoteServerFailedBanner { handle: ViewHandle, }, - WarpifySuccessBlock { - bootstrap_success_block_handle: ViewHandle, + ZaplexifySuccessBlock { + bootstrap_success_block_handle: ViewHandle, }, AgentViewEntry(AgentViewEntryMetadata), AmbientAgentBlock { diff --git a/app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs b/app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs index 2eb1c7388b..45dc2967d9 100644 --- a/app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs +++ b/app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs @@ -429,7 +429,7 @@ impl ConversationEndedTombstoneView { #[cfg(not(target_family = "wasm"))] { // Hide for non-Oz harnesses (e.g. Claude, Gemini): they can't be - // forked into a local Zap conversation. Unknown harness (None) is + // forked into a local Zaplex conversation. Unknown harness (None) is // treated as allowed so plain conversations and pre-load tasks still // show the button. let harness_allows_continue = diff --git a/app/src/terminal/view/shared_session/view_impl.rs b/app/src/terminal/view/shared_session/view_impl.rs index 3834c6ce63..e3e5007bfb 100644 --- a/app/src/terminal/view/shared_session/view_impl.rs +++ b/app/src/terminal/view/shared_session/view_impl.rs @@ -164,12 +164,12 @@ impl TerminalView { } fn update_shared_session_pane_header(&mut self, _ctx: &mut ViewContext) { - // Zap Phase 2a: pane-header sharing UI is gone, so the pane no + // Zaplex Phase 2a: pane-header sharing UI is gone, so the pane no // longer tracks `ShareableObject::Session`. The shared-session itself // still runs; it just doesn't surface a "share" button in the header. } - // Zap: Share Session path is cut off, the following two methods retain signatures but are no-op, + // Zaplex: Share Session path is cut off, the following two methods retain signatures but are no-op, // no longer emit `Event::OpenShareSessionModal{,DeniedModal}`, nor reach cloud collaboration session service. pub fn open_share_session_modal( &mut self, @@ -206,7 +206,7 @@ impl TerminalView { /// 5. Once the session is registered with [`shared_session::manager::Manager`], it /// will emit an event for relevant subscribers (e.g. the Workspace will need to /// re-render when a share starts for tab indicator, share button, etc.) - // Zap: Shared Session network entry is cut off, attempt_to_share_session is overall no-op, + // Zaplex: Shared Session network entry is cut off, attempt_to_share_session is overall no-op, // no longer set SharePending state, no longer emit StartSharingCurrentSession, no longer trigger telemetry. pub fn attempt_to_share_session( &mut self, @@ -259,7 +259,7 @@ impl TerminalView { self.pane_configuration.update(ctx, |pane_config, ctx| { pane_config.refresh_pane_header_overflow_menu_items(ctx); - // Zap Phase 2a: sharing dialog + pane-header `ShareableObject` + // Zaplex Phase 2a: sharing dialog + pane-header `ShareableObject` // bookkeeping removed; the shared session continues without a UI // entry point. pane_config.notify_header_content_changed(ctx); @@ -349,7 +349,7 @@ impl TerminalView { self.pane_configuration.update(ctx, |pane_config, ctx| { pane_config.refresh_pane_header_overflow_menu_items(ctx); - // Zap Phase 2a: removed `set_shareable_object` (cloud sharing UI gone). + // Zaplex Phase 2a: removed `set_shareable_object` (cloud sharing UI gone). pane_config.notify_header_content_changed(ctx); }); diff --git a/app/src/terminal/view/ssh_file_upload.rs b/app/src/terminal/view/ssh_file_upload.rs index 7477aba472..7ce935a54e 100644 --- a/app/src/terminal/view/ssh_file_upload.rs +++ b/app/src/terminal/view/ssh_file_upload.rs @@ -187,7 +187,7 @@ impl FileUpload { } } - /// Creates an sftp command that copies a given local file into the PWD of the warpified ssh session, if any. + /// Creates an sftp command that copies a given local file into the PWD of the zaplexified ssh session, if any. fn transfer_file_sftp_command(&self, file_upload: &FileUploadInfo) -> String { // "sftp " let mut command = String::from("sftp "); diff --git a/app/src/terminal/view/ssh_remote_server_choice_view.rs b/app/src/terminal/view/ssh_remote_server_choice_view.rs index 2b5fd01800..4b1cf78a61 100644 --- a/app/src/terminal/view/ssh_remote_server_choice_view.rs +++ b/app/src/terminal/view/ssh_remote_server_choice_view.rs @@ -1,14 +1,14 @@ //! Inline block view that asks the user whether they want to install -//! Zap's SSH extension on the remote host the shell just connected to, +//! Zaplex's SSH extension on the remote host the shell just connected to, //! or continue without installing (falling back to the existing -//! ControlMaster warpification path). +//! ControlMaster zaplexification path). //! //! Designed from frame 6050:2448 of the Figma file //! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization). //! //! The view owns: //! - a child [`KeyboardNavigableButtons`] handle for the two selectable -//! cards ("Install Zap's SSH extension" / "Continue without installing"), +//! cards ("Install Zaplex's SSH extension" / "Continue without installing"), //! - the [`SessionId`] this prompt is scoped to (used for event forwarding), //! - the current "Don't ask me this again" checked state (purely local to //! this prompt instance; persisted to `ssh_extension_install_mode` only @@ -37,7 +37,7 @@ use crate::{ send_telemetry_from_ctx, server::telemetry::TelemetryEvent, terminal::model::session::SessionId, - terminal::warpify::settings::{SshExtensionInstallMode, WarpifySettings}, + terminal::zaplexify::settings::{SshExtensionInstallMode, ZaplexifySettings}, ui_components::blended_colors, Appearance, }; @@ -170,11 +170,11 @@ impl SshRemoteServerChoiceView { .with_child(Container::new(checkbox_label).with_margin_left(4.).finish()) .finish(); - // Right: "Manage Warpify settings" link. + // Right: "Manage Zaplexify settings" link. let manage_settings_link = appearance .ui_builder() .link( - crate::t!("ssh-remote-choice-manage-warpify-settings"), + crate::t!("ssh-remote-choice-manage-zaplexify-settings"), None, Some(Box::new(|ctx| { ctx.dispatch_typed_action(SshRemoteServerChoiceViewAction::ZapifySettings); @@ -263,7 +263,7 @@ impl TypedActionView for SshRemoteServerChoiceView { SshRemoteServerChoiceViewAction::Install => { if self.do_not_ask_again { let mode = SshExtensionInstallMode::AlwaysInstall; - WarpifySettings::handle(ctx).update(ctx, |settings, ctx| { + ZaplexifySettings::handle(ctx).update(ctx, |settings, ctx| { if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) { log::error!("Failed to persist ssh_extension_install_mode: {e}"); } @@ -280,7 +280,7 @@ impl TypedActionView for SshRemoteServerChoiceView { SshRemoteServerChoiceViewAction::Skip => { if self.do_not_ask_again { let mode = SshExtensionInstallMode::NeverInstall; - WarpifySettings::handle(ctx).update(ctx, |settings, ctx| { + ZaplexifySettings::handle(ctx).update(ctx, |settings, ctx| { if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) { log::error!("Failed to persist ssh_extension_install_mode: {e}"); } diff --git a/app/src/terminal/view/ssh_remote_server_failed_banner.rs b/app/src/terminal/view/ssh_remote_server_failed_banner.rs index ee21db5dd5..3dcc58edf5 100644 --- a/app/src/terminal/view/ssh_remote_server_failed_banner.rs +++ b/app/src/terminal/view/ssh_remote_server_failed_banner.rs @@ -1,5 +1,5 @@ //! Banner shown when the remote-server binary check, installation, or connection fails on the remote host. -//! We fall back to the existing Warpification behavior and display this banner so the user knows why advanced features are unavailable. +//! We fall back to the existing Zaplexification behavior and display this banner so the user knows why advanced features are unavailable. use warp_core::ui::theme::color::internal_colors; use warp_core::ui::theme::AnsiColorIdentifier; @@ -16,7 +16,7 @@ use crate::{terminal::model::session::SessionId, ui_components::icons::Icon, App const BANNER_BODY: &str = "While advanced features like file browsing and code review are currently \ - disabled, the rest of your Warpified experience is fully available."; + disabled, the rest of your Zaplexified experience is fully available."; #[derive(Clone, Debug)] pub enum SshRemoteServerFailedBannerAction { diff --git a/app/src/terminal/view/testing.rs b/app/src/terminal/view/testing.rs index a622a25f12..708eceb918 100644 --- a/app/src/terminal/view/testing.rs +++ b/app/src/terminal/view/testing.rs @@ -15,7 +15,7 @@ cfg_if::cfg_if! { }; use crate::terminal::model::session::Sessions; use crate::terminal::model_events::ModelEventDispatcher; - use crate::terminal::view::WARP_PROMPT_HEIGHT_LINES; + use crate::terminal::view::ZAPLEX_PROMPT_HEIGHT_LINES; use crate::terminal::{SizeInfo, TerminalModel}; use crate::context_chips::prompt_type::PromptType; @@ -68,7 +68,7 @@ impl TerminalView { block_padding, size: size_info, max_block_scroll_limit, - warp_prompt_height_lines: WARP_PROMPT_HEIGHT_LINES, + warp_prompt_height_lines: ZAPLEX_PROMPT_HEIGHT_LINES, }; let terminal_view_resources = TerminalViewResources { diff --git a/app/src/terminal/view/use_agent_footer/mod.rs b/app/src/terminal/view/use_agent_footer/mod.rs index 18300ff0de..af583c3dd6 100644 --- a/app/src/terminal/view/use_agent_footer/mod.rs +++ b/app/src/terminal/view/use_agent_footer/mod.rs @@ -14,10 +14,10 @@ use crate::terminal::cli_agent_sessions::{ }; use base64::Engine; use warpui::clipboard::{ClipboardContent, ImageData}; -mod warpify_footer; +mod zaplexify_footer; pub use crate::terminal::CLIAgent; -use warpify_footer::{WarpifyFooterView, WarpifyFooterViewEvent}; +use zaplexify_footer::{ZaplexifyFooterView, ZaplexifyFooterViewEvent}; use std::sync::{Arc, LazyLock}; use std::time::Duration; @@ -74,7 +74,7 @@ use crate::{ use warp_terminal::model::escape_sequences::{BRACKETED_PASTE_END, BRACKETED_PASTE_START}; use super::{RichContentInsertionPosition, TerminalAction, TerminalView}; -use crate::terminal::view::block_banner::WarpificationMode; +use crate::terminal::view::block_banner::ZaplexificationMode; /// Small delay inserted between separate PTY writes to CLI agents. /// (Used both for the mode-switch prefix split and for the `DelayedEnter` @@ -243,18 +243,18 @@ impl TerminalView { UseAgentToolbarEvent::HideRichInput => { self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx); } - UseAgentToolbarEvent::Warpify { mode } => { + UseAgentToolbarEvent::Zaplexify { mode } => { self.hide_use_agent_footer_in_blocklist(ctx); match mode { - WarpificationMode::Ssh { .. } => { - self.handle_action(&TerminalAction::WarpifySSHSession, ctx); + ZaplexificationMode::Ssh { .. } => { + self.handle_action(&TerminalAction::ZaplexifySSHSession, ctx); } - WarpificationMode::Subshell { .. } => { + ZaplexificationMode::Subshell { .. } => { self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx); } } send_telemetry_from_ctx!( - TelemetryEvent::WarpifyFooterAcceptedWarpify { + TelemetryEvent::ZaplexifyFooterAcceptedZaplexify { is_ssh: mode.is_ssh() }, ctx @@ -280,11 +280,11 @@ impl TerminalView { ) -> bool { let ai_settings = AISettings::as_ref(app); - // If a warpify mode is set, that means ssh or subshell is detected and we should show the footer. + // If a zaplexify mode is set, that means ssh or subshell is detected and we should show the footer. if self .use_agent_footer .as_ref(app) - .warpify_mode(app) + .zaplexify_mode(app) .is_some() { return true; @@ -299,7 +299,7 @@ impl TerminalView { if let Some(agent) = cli_agent { // For CLI agent commands, only check the CLI agent footer setting. // This is independent of the global AI toggle so that users who - // disable Zap AI still get the footer for third-party coding agents. + // disable Zaplex AI still get the footer for third-party coding agents. if !*ai_settings.should_render_cli_agent_footer { return false; } @@ -415,7 +415,7 @@ impl TerminalView { if !self.model.lock().is_alt_screen_active() { self.use_agent_footer.update(ctx, |footer, ctx| { - footer.clear_warpify_mode(ctx); + footer.clear_zaplexify_mode(ctx); }); self.hide_use_agent_footer_in_blocklist(ctx); } @@ -955,8 +955,8 @@ pub struct UseAgentToolbar { // Shared agent input footer (renders CLI agent mode when a CLI session is active). agent_input_footer: ViewHandle, - // Warpify footer UI (shown when a subshell/SSH command is detected). - warpify_footer_view: ViewHandle, + // Zaplexify footer UI (shown when a subshell/SSH command is detected). + zaplexify_footer_view: ViewHandle, // `true` if the user has dismissed the footer. // @@ -1029,11 +1029,11 @@ impl UseAgentToolbar { me.handle_agent_input_footer_event(event, ctx); }); - let warpify_footer_view = - ctx.add_typed_action_view(|ctx| WarpifyFooterView::new(terminal_model.clone(), ctx)); + let zaplexify_footer_view = + ctx.add_typed_action_view(|ctx| ZaplexifyFooterView::new(terminal_model.clone(), ctx)); - ctx.subscribe_to_view(&warpify_footer_view, |me, _, event, ctx| { - me.handle_warpify_footer_event(event, ctx); + ctx.subscribe_to_view(&zaplexify_footer_view, |me, _, event, ctx| { + me.handle_zaplexify_footer_event(event, ctx); }); ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| { @@ -1059,7 +1059,7 @@ impl UseAgentToolbar { dismiss_button, dont_show_again_button, agent_input_footer, - warpify_footer_view, + zaplexify_footer_view, terminal_model, did_user_dismiss: false, } @@ -1095,19 +1095,19 @@ impl UseAgentToolbar { } } - fn handle_warpify_footer_event( + fn handle_zaplexify_footer_event( &mut self, - event: &WarpifyFooterViewEvent, + event: &ZaplexifyFooterViewEvent, ctx: &mut ViewContext, ) { match event { - WarpifyFooterViewEvent::Warpify { mode } => { - ctx.emit(UseAgentToolbarEvent::Warpify { mode: mode.clone() }); + ZaplexifyFooterViewEvent::Zaplexify { mode } => { + ctx.emit(UseAgentToolbarEvent::Zaplexify { mode: mode.clone() }); } - WarpifyFooterViewEvent::UseAgent => { + ZaplexifyFooterViewEvent::UseAgent => { ctx.emit(UseAgentToolbarEvent::UseAgent); } - WarpifyFooterViewEvent::Dismiss => { + ZaplexifyFooterViewEvent::Dismiss => { ctx.emit(UseAgentToolbarEvent::Dismiss); } } @@ -1116,7 +1116,7 @@ impl UseAgentToolbar { pub(in crate::terminal) fn notify_and_notify_children(&mut self, ctx: &mut ViewContext) { ctx.notify(); self.agent_input_footer.update(ctx, |_, ctx| ctx.notify()); - self.warpify_footer_view.update(ctx, |_, ctx| ctx.notify()); + self.zaplexify_footer_view.update(ctx, |_, ctx| ctx.notify()); self.button.update(ctx, |_, ctx| ctx.notify()); self.give_control_back_button .update(ctx, |_, ctx| ctx.notify()); @@ -1136,30 +1136,30 @@ impl UseAgentToolbar { .map(|session| session.agent) } - /// Sets the current warpification mode. When set, the footer shows the - /// warpify view instead of the CLI agent or regular "Use agent" views. - pub(in crate::terminal) fn set_warpify_mode( + /// Sets the current zaplexification mode. When set, the footer shows the + /// zaplexify view instead of the CLI agent or regular "Use agent" views. + pub(in crate::terminal) fn set_zaplexify_mode( &mut self, - mode: WarpificationMode, + mode: ZaplexificationMode, ctx: &mut ViewContext, ) { - self.warpify_footer_view.update(ctx, |view, ctx| { + self.zaplexify_footer_view.update(ctx, |view, ctx| { view.set_mode(mode, ctx); }); ctx.notify(); } - /// Clears the warpification mode so the footer reverts to its default behavior. - pub(in crate::terminal) fn clear_warpify_mode(&mut self, ctx: &mut ViewContext) { - self.warpify_footer_view.update(ctx, |view, ctx| { + /// Clears the zaplexification mode so the footer reverts to its default behavior. + pub(in crate::terminal) fn clear_zaplexify_mode(&mut self, ctx: &mut ViewContext) { + self.zaplexify_footer_view.update(ctx, |view, ctx| { view.clear_mode(ctx); }); ctx.notify(); } - /// Returns the current warpification mode, if set. - pub(in crate::terminal) fn warpify_mode(&self, app: &AppContext) -> Option { - self.warpify_footer_view.as_ref(app).mode().cloned() + /// Returns the current zaplexification mode, if set. + pub(in crate::terminal) fn zaplexify_mode(&self, app: &AppContext) -> Option { + self.zaplexify_footer_view.as_ref(app).mode().cloned() } /// Returns whether there's a current CLI agent (like Claude Code). @@ -1185,8 +1185,8 @@ pub enum UseAgentToolbarEvent { OpenRichInput, /// Hide the rich input editor (same as Escape). HideRichInput, - /// User chose to warpify the subshell/SSH session. - Warpify { mode: WarpificationMode }, + /// User chose to zaplexify the subshell/SSH session. + Zaplexify { mode: ZaplexificationMode }, /// User chose to use the agent. UseAgent, } @@ -1201,9 +1201,9 @@ impl View for UseAgentToolbar { } fn render(&self, app: &AppContext) -> Box { - // If a warpify mode is set, delegate rendering to the warpify footer view. - if self.warpify_footer_view.as_ref(app).mode().is_some() { - return ChildView::new(&self.warpify_footer_view).finish(); + // If a zaplexify mode is set, delegate rendering to the zaplexify footer view. + if self.zaplexify_footer_view.as_ref(app).mode().is_some() { + return ChildView::new(&self.zaplexify_footer_view).finish(); } // Hide the toolbar entirely when CLI rich input is open, diff --git a/app/src/terminal/view/use_agent_footer/warpify_footer.rs b/app/src/terminal/view/use_agent_footer/zaplexify_footer.rs similarity index 64% rename from app/src/terminal/view/use_agent_footer/warpify_footer.rs rename to app/src/terminal/view/use_agent_footer/zaplexify_footer.rs index c7e1e099f3..b12f270bb9 100644 --- a/app/src/terminal/view/use_agent_footer/warpify_footer.rs +++ b/app/src/terminal/view/use_agent_footer/zaplexify_footer.rs @@ -16,33 +16,33 @@ use crate::{ }; use super::{AgentFooterButtonTheme, USE_AGENT_KEYSTROKE}; -use crate::terminal::view::block_banner::WarpificationMode; +use crate::terminal::view::block_banner::ZaplexificationMode; /// Footer view rendered for detected subshell/SSH commands, offering both -/// "Warpify" and "Use agent" buttons in a horizontal row. -pub(super) struct WarpifyFooterView { +/// "Zaplexify" and "Use agent" buttons in a horizontal row. +pub(super) struct ZaplexifyFooterView { terminal_model: Arc>, - warpify_button: ViewHandle, + zaplexify_button: ViewHandle, use_agent_button: ViewHandle, dismiss_button: ViewHandle, - mode: Option, + mode: Option, } -impl WarpifyFooterView { +impl ZaplexifyFooterView { pub fn new(terminal_model: Arc>, ctx: &mut ViewContext) -> Self { let button_size = ButtonSize::XSmall; - let warpify_button = ctx.add_typed_action_view(|_ctx| { + let zaplexify_button = ctx.add_typed_action_view(|_ctx| { ActionButton::new( - crate::t!("terminal-warpify-subshell"), + crate::t!("terminal-zaplexify-subshell"), AgentFooterButtonTheme::new(None), ) - .with_icon(Icon::Zap) + .with_icon(Icon::Zaplex) .with_size(button_size) - .with_tooltip(crate::t!("terminal-warpify-subshell-tooltip")) + .with_tooltip(crate::t!("terminal-zaplexify-subshell-tooltip")) .with_tooltip_alignment(TooltipAlignment::Left) .on_click(|ctx| { - ctx.dispatch_typed_action(WarpifyFooterViewAction::Warpify); + ctx.dispatch_typed_action(ZaplexifyFooterViewAction::Zaplexify); }) }); @@ -57,7 +57,7 @@ impl WarpifyFooterView { .with_tooltip(crate::t!("terminal-use-agent-tooltip")) .with_tooltip_alignment(TooltipAlignment::Left) .on_click(|ctx| { - ctx.dispatch_typed_action(WarpifyFooterViewAction::UseAgent); + ctx.dispatch_typed_action(ZaplexifyFooterViewAction::UseAgent); }) }); @@ -68,28 +68,28 @@ impl WarpifyFooterView { ) .with_size(button_size) .on_click(|ctx| { - ctx.dispatch_typed_action(WarpifyFooterViewAction::Dismiss); + ctx.dispatch_typed_action(ZaplexifyFooterViewAction::Dismiss); }) }); Self { terminal_model, - warpify_button, + zaplexify_button, use_agent_button, dismiss_button, mode: None, } } - /// Updates the warpify button label, keybinding, and stores the current warpification mode. - pub fn set_mode(&mut self, mode: WarpificationMode, ctx: &mut ViewContext) { + /// Updates the zaplexify button label, keybinding, and stores the current zaplexification mode. + pub fn set_mode(&mut self, mode: ZaplexificationMode, ctx: &mut ViewContext) { let (label, binding_name) = match mode { - WarpificationMode::Ssh { .. } => { - ("Warpify SSH session", "terminal:warpify_ssh_session") + ZaplexificationMode::Ssh { .. } => { + ("Zaplexify SSH session", "terminal:zaplexify_ssh_session") } - WarpificationMode::Subshell { .. } => ("Warpify subshell", "terminal:warpify_subshell"), + ZaplexificationMode::Subshell { .. } => ("Zaplexify subshell", "terminal:zaplexify_subshell"), }; - self.warpify_button.update(ctx, |button, ctx| { + self.zaplexify_button.update(ctx, |button, ctx| { button.set_label(label, ctx); button.set_keybinding(Some(KeystrokeSource::Binding(binding_name)), ctx); }); @@ -97,15 +97,15 @@ impl WarpifyFooterView { ctx.notify(); } - /// Returns the current warpification mode, if set. - pub fn mode(&self) -> Option<&WarpificationMode> { + /// Returns the current zaplexification mode, if set. + pub fn mode(&self) -> Option<&ZaplexificationMode> { self.mode.as_ref() } - /// Clears the warpification mode. + /// Clears the zaplexification mode. pub fn clear_mode(&mut self, ctx: &mut ViewContext) { self.mode = None; - self.warpify_button.update(ctx, |button, ctx| { + self.zaplexify_button.update(ctx, |button, ctx| { button.set_keybinding(None, ctx); }); ctx.notify(); @@ -113,25 +113,25 @@ impl WarpifyFooterView { } #[derive(Debug, Clone)] -pub enum WarpifyFooterViewAction { - Warpify, +pub enum ZaplexifyFooterViewAction { + Zaplexify, UseAgent, Dismiss, } -pub enum WarpifyFooterViewEvent { - Warpify { mode: WarpificationMode }, +pub enum ZaplexifyFooterViewEvent { + Zaplexify { mode: ZaplexificationMode }, UseAgent, Dismiss, } -impl Entity for WarpifyFooterView { - type Event = WarpifyFooterViewEvent; +impl Entity for ZaplexifyFooterView { + type Event = ZaplexifyFooterViewEvent; } -impl View for WarpifyFooterView { +impl View for ZaplexifyFooterView { fn ui_name() -> &'static str { - "WarpifyFooterView" + "ZaplexifyFooterView" } fn render(&self, _app: &AppContext) -> Box { @@ -141,7 +141,7 @@ impl View for WarpifyFooterView { .with_spacing(4.) .with_main_axis_size(MainAxisSize::Max) .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child(ChildView::new(&self.warpify_button).finish()) + .with_child(ChildView::new(&self.zaplexify_button).finish()) .with_child(ChildView::new(&self.use_agent_button).finish()) .with_child(Expanded::new(1., Empty::new().finish()).finish()) .with_child(ChildView::new(&self.dismiss_button).finish()); @@ -160,24 +160,24 @@ impl View for WarpifyFooterView { } } -impl TypedActionView for WarpifyFooterView { - type Action = WarpifyFooterViewAction; +impl TypedActionView for ZaplexifyFooterView { + type Action = ZaplexifyFooterViewAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - WarpifyFooterViewAction::Warpify => { + ZaplexifyFooterViewAction::Zaplexify => { if let Some(mode) = self.mode.clone() { self.clear_mode(ctx); - ctx.emit(WarpifyFooterViewEvent::Warpify { mode }); + ctx.emit(ZaplexifyFooterViewEvent::Zaplexify { mode }); } } - WarpifyFooterViewAction::UseAgent => { + ZaplexifyFooterViewAction::UseAgent => { self.clear_mode(ctx); - ctx.emit(WarpifyFooterViewEvent::UseAgent); + ctx.emit(ZaplexifyFooterViewEvent::UseAgent); } - WarpifyFooterViewAction::Dismiss => { + ZaplexifyFooterViewAction::Dismiss => { self.clear_mode(ctx); - ctx.emit(WarpifyFooterViewEvent::Dismiss); + ctx.emit(ZaplexifyFooterViewEvent::Dismiss); } } } diff --git a/app/src/terminal/view/zero_state_block.rs b/app/src/terminal/view/zero_state_block.rs index 69d4e0326f..af144c2f0c 100644 --- a/app/src/terminal/view/zero_state_block.rs +++ b/app/src/terminal/view/zero_state_block.rs @@ -151,7 +151,7 @@ impl View for TerminalViewZeroStateBlock { .with_child( Container::new( ConstrainedBox::new( - Icon::Zap + Icon::Zaplex .to_warpui_icon(theme.main_text_color(theme.background())) .finish(), ) diff --git a/app/src/terminal/view_test.rs b/app/src/terminal/view_test.rs index 1608aa2e36..800a4eb070 100644 --- a/app/src/terminal/view_test.rs +++ b/app/src/terminal/view_test.rs @@ -2400,7 +2400,7 @@ fn test_bash_vim_banner_already_shown() { .set_value(BannerState::Dismissed, ctx); }); - // Ensure Zap's vim keybindings are off. + // Ensure Zaplex's vim keybindings are off. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(false, ctx); }); @@ -2457,7 +2457,7 @@ fn test_bash_vim_banner_on() { .set_value(BannerState::NotDismissed, ctx); }); - // Ensure Zap's vim keybindings are off. + // Ensure Zaplex's vim keybindings are off. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(false, ctx); }); @@ -2513,7 +2513,7 @@ fn test_bash_vim_banner_off() { .set_value(BannerState::NotDismissed, ctx); }); - // Ensure Zap's vim keybindings are on. + // Ensure Zaplex's vim keybindings are on. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(true, ctx); }); @@ -2570,7 +2570,7 @@ fn test_zsh_vim_banner_on() { .set_value(BannerState::NotDismissed, ctx); }); - // Ensure Zap's vim keybindings are off. + // Ensure Zaplex's vim keybindings are off. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(false, ctx); }); @@ -2626,7 +2626,7 @@ fn test_zsh_vim_banner_off() { .set_value(BannerState::NotDismissed, ctx); }); - // Ensure Zap's vim keybindings are on. + // Ensure Zaplex's vim keybindings are on. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(true, ctx); }); @@ -2676,7 +2676,7 @@ fn test_fish_vim_banner_on() { view.set_focus_handle(focus_handle, ctx); }); - // Ensure Zap's vim keybindings are off. + // Ensure Zaplex's vim keybindings are off. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(false, ctx); }); @@ -2725,7 +2725,7 @@ fn test_fish_vim_banner_off() { view.set_focus_handle(focus_handle, ctx); }); - // Ensure Zap's vim keybindings are on. + // Ensure Zaplex's vim keybindings are on. AppEditorSettings::handle(&app).update(&mut app, |editor_settings, ctx| { let _ = editor_settings.vim_mode.set_value(true, ctx); }); diff --git a/app/src/terminal/writeable_pty/bootstrap_file/windows.rs b/app/src/terminal/writeable_pty/bootstrap_file/windows.rs index 19eb397959..ade2bcc458 100644 --- a/app/src/terminal/writeable_pty/bootstrap_file/windows.rs +++ b/app/src/terminal/writeable_pty/bootstrap_file/windows.rs @@ -107,7 +107,7 @@ impl Drop for TempBootstrapFile { /// Returns the path to the permanent bootstrap file in bytes, if it exists. /// /// Currently we only create a permanent bootstrap file for PowerShell, located -/// alongside the Zap executable. +/// alongside the Zaplex executable. pub fn path_to_permanent_bootstrap_file(shell_type: ShellType) -> Option> { if shell_type != ShellType::PowerShell { return None; diff --git a/app/src/terminal/writeable_pty/pty_controller.rs b/app/src/terminal/writeable_pty/pty_controller.rs index 0eaac03a77..9d4eaa59b0 100644 --- a/app/src/terminal/writeable_pty/pty_controller.rs +++ b/app/src/terminal/writeable_pty/pty_controller.rs @@ -32,11 +32,11 @@ use super::Message; /// Byte sequence to emulate the user pressing ENTER, used to execute a command in the shell. const COMMAND_ENTER: &[u8] = &[escape_sequences::C0::CR, escape_sequences::C0::LF]; /// Used to let the shell know we are switching to the PS1 prompt via a bindkey \ep. This will -/// restore the PS1 from the saved PS1 value (we had unset the PS1 for Zap prompt). +/// restore the PS1 from the saved PS1 value (we had unset the PS1 for Zaplex prompt). const SWITCH_TO_PS1_ESCAPE_SEQUENCE: &[u8] = &[escape_sequences::C0::ESC, b'p']; -/// Used to let the shell know we are switching to the Zap prompt via a bindkey \ew. This will -/// unset the PS1 to ensure we don't have a double prompt (PS1 and Zap prompt). -const SWITCH_TO_WARP_PROMPT_ESCAPE_SEQUENCE: &[u8] = &[escape_sequences::C0::ESC, b'w']; +/// Used to let the shell know we are switching to the Zaplex prompt via a bindkey \ew. This will +/// unset the PS1 to ensure we don't have a double prompt (PS1 and Zaplex prompt). +const SWITCH_TO_ZAPLEX_PROMPT_ESCAPE_SEQUENCE: &[u8] = &[escape_sequences::C0::ESC, b'w']; /// Represents a single call to write bytes to the PTY asynchronously. enum PtyWrite { @@ -149,7 +149,7 @@ impl PtyController { me.tmux_control_mode = None; } ModelEvent::HonorPS1OutOfSync => { - // We force re-sync the PS1 state of Zap settings with the shell's environment variable, $WARP_HONOR_PS1, via + // We force re-sync the PS1 state of Zaplex settings with the shell's environment variable, $ZAPLEX_HONOR_PS1, via // a bindkey (which triggers a shell function). let honor_ps1 = *SessionSettings::as_ref(ctx).honor_ps1; if honor_ps1 { @@ -289,12 +289,12 @@ impl PtyController { } } - /// Sends bindkey to notify shell process to switch to Zap prompt logic for prompt + /// Sends bindkey to notify shell process to switch to Zaplex prompt logic for prompt /// with the combined prompt/command grid (we unset the PS1, but save the value for potential /// future restoration). pub fn send_switch_to_warp_prompt_bindkey(&mut self, ctx: &mut ModelContext) { self.pending_writes.push_back(PtyWrite::Bytes { - bytes: SWITCH_TO_WARP_PROMPT_ESCAPE_SEQUENCE.into(), + bytes: SWITCH_TO_ZAPLEX_PROMPT_ESCAPE_SEQUENCE.into(), }); self.execute_next_queued_write(ctx); diff --git a/app/src/terminal/writeable_pty/remote_server_controller.rs b/app/src/terminal/writeable_pty/remote_server_controller.rs index a0bf030eda..c6a0f0a578 100644 --- a/app/src/terminal/writeable_pty/remote_server_controller.rs +++ b/app/src/terminal/writeable_pty/remote_server_controller.rs @@ -8,15 +8,15 @@ use std::sync::Arc; use warp_core::SessionId; use warpui::{Entity, ModelContext, ModelHandle, SingletonEntity, WeakModelHandle}; -use crate::terminal::warpify::settings::SshExtensionInstallMode; +use crate::terminal::zaplexify::settings::SshExtensionInstallMode; use crate::remote_server::manager::{RemoteServerManager, RemoteServerManagerEvent}; use crate::remote_server::ssh_transport::SshTransport; -// Zap Wave 3-1: `ServerApiProvider` no longer used by this file — `auth_client` +// Zaplex Wave 3-1: `ServerApiProvider` no longer used by this file — `auth_client` // call sites physically deleted along with AuthClient. use crate::terminal::model::session::{IsLegacySSHSession, SessionInfo}; use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher}; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::zaplexify::settings::ZaplexifySettings; use crate::{send_telemetry_from_ctx, TelemetryEvent}; use remote_server::setup::{ PreinstallCheckResult, PreinstallStatus, RemoteLibc, RemotePlatform, UnsupportedReason, @@ -146,7 +146,11 @@ impl RemoteServerController { | RemoteServerManagerEvent::BufferUpdated { .. } | RemoteServerManagerEvent::SetupStateChanged { .. } | RemoteServerManagerEvent::ClientRequestFailed { .. } - | RemoteServerManagerEvent::ServerMessageDecodingError { .. } => {} + | RemoteServerManagerEvent::ServerMessageDecodingError { .. } + // Stage 2: session output/exit are consumed by the attached-remote + // terminal byte-source (later increment), not by this controller. + | RemoteServerManagerEvent::SessionOutput { .. } + | RemoteServerManagerEvent::SessionExited { .. } => {} }); Self { @@ -292,7 +296,7 @@ impl RemoteServerController { }); } Ok(false) => { - let install_mode = *WarpifySettings::as_ref(ctx) + let install_mode = *ZaplexifySettings::as_ref(ctx) .ssh_extension_install_mode .value(); match install_mode { diff --git a/app/src/terminal/warpify/mod.rs b/app/src/terminal/zaplexify/mod.rs similarity index 96% rename from app/src/terminal/warpify/mod.rs rename to app/src/terminal/zaplexify/mod.rs index 237396d3e8..57fe40d018 100644 --- a/app/src/terminal/warpify/mod.rs +++ b/app/src/terminal/zaplexify/mod.rs @@ -10,7 +10,7 @@ use channel_versions::overrides::TargetOS; use warpui::AssetProvider; #[derive(Debug)] -pub enum WarpificationSource { +pub enum ZaplexificationSource { Ssh, Subshell, } @@ -33,7 +33,7 @@ fn get_subshell_bootstrap_success_block_path(shell_type: ShellType) -> Option<&' } } -/// Returns OutputGrid bytes to be rendered in the hardcoded "Warpified subshell" block that's added +/// Returns OutputGrid bytes to be rendered in the hardcoded "Zaplexified subshell" block that's added /// to the blocklist upon successful subshell bootstrap. /// /// The exact block contents varies based on whether or not the session is local or remote, in diff --git a/app/src/terminal/warpify/render.rs b/app/src/terminal/zaplexify/render.rs similarity index 93% rename from app/src/terminal/warpify/render.rs rename to app/src/terminal/zaplexify/render.rs index 54cfe04ab9..499783f687 100644 --- a/app/src/terminal/warpify/render.rs +++ b/app/src/terminal/zaplexify/render.rs @@ -16,7 +16,7 @@ use warpui::ui_components::components::UiComponent as _; use warpui::ui_components::components::UiComponentStyles; use warpui::{AppContext, Element, EventContext, PaintContext, SingletonEntity as _}; -use super::settings::WarpifySettings; +use super::settings::ZaplexifySettings; use super::SubshellSource; /// The flag font size varies with the monospace font width, but if it gets too big it will start @@ -28,7 +28,7 @@ const SUBSHELL_FLAG_HORIZONTAL_PADDING: f32 = 8.; const SUBSHELL_FLAG_VERTICAL_PADDING: f32 = 1.; // TODO(liam): remove this once figuring out how to get theme color in layout() -const WARP_DRIVE_ENV_VAR_COLLECTION_ICON_COLOR: u32 = 0xC464FFFF; +const ZAPLEX_DRIVE_ENV_VAR_COLLECTION_ICON_COLOR: u32 = 0xC464FFFF; const ICON_MARGIN: f32 = 4.; const TERMINAL_ICON: &str = "bundled/svg/terminal.svg"; pub const HORIZONTAL_TEXT_MARGIN: f32 = 20.; @@ -93,7 +93,7 @@ fn green_check_icon(appearance: &Appearance, size: f32) -> Box { .finish() } -/// UI helper to render the ssh command that caused the warpification prompt. +/// UI helper to render the ssh command that caused the zaplexification prompt. pub fn build_command_row( command: String, theme: &WarpTheme, @@ -162,32 +162,32 @@ pub fn description_row(text: &str, theme: &WarpTheme, appearance: &Appearance) - .finish() } -/// Renders a "Never Warpify this host" link or nothing. -pub fn render_never_warpify_ssh_link( +/// Renders a "Never Zaplexify this host" link or nothing. +pub fn render_never_zaplexify_ssh_link( ssh_host: &Option, app: &AppContext, appearance: &Appearance, mouse_state_handle: MouseStateHandle, - on_never_warpify: fn(&mut EventContext<'_>, ssh_host: String), + on_never_zaplexify: fn(&mut EventContext<'_>, ssh_host: String), ) -> Option> { let Some(ssh_host) = ssh_host else { return None; }; - let settings = WarpifySettings::handle(app); + let settings = ZaplexifySettings::handle(app); if settings.as_ref(app).is_ssh_host_denylisted(ssh_host) { - // Should only happen if user manually attempts to Warpify a denylisted host. + // Should only happen if user manually attempts to Zaplexify a denylisted host. return None; } let link = appearance .ui_builder() .link( - "Never Warpify this host".into(), + "Never Zaplexify this host".into(), None, Some(Box::new({ let ssh_host = ssh_host.clone(); - move |ctx| on_never_warpify(ctx, ssh_host.to_owned()) + move |ctx| on_never_zaplexify(ctx, ssh_host.to_owned()) })), mouse_state_handle, ) @@ -207,7 +207,7 @@ fn get_subshell_flag_info(subshell_source: &SubshellSource, theme: &WarpTheme) - match subshell_source { SubshellSource::EnvVarCollection(environment_name) => ( environment_name.to_string(), - Fill::Solid(ColorU::from_u32(WARP_DRIVE_ENV_VAR_COLLECTION_ICON_COLOR)), + Fill::Solid(ColorU::from_u32(ZAPLEX_DRIVE_ENV_VAR_COLLECTION_ICON_COLOR)), ), SubshellSource::Command(command) => (command.to_string(), theme.subshell_background()), } diff --git a/app/src/terminal/warpify/settings.rs b/app/src/terminal/zaplexify/settings.rs similarity index 84% rename from app/src/terminal/warpify/settings.rs rename to app/src/terminal/zaplexify/settings.rs index 5d7bc38cee..0c991ada07 100644 --- a/app/src/terminal/warpify/settings.rs +++ b/app/src/terminal/zaplexify/settings.rs @@ -10,60 +10,60 @@ use warp_util::path::ShellFamily; use warpui::{AppContext, ModelContext}; use warpui::{Entity, SingletonEntity}; -use crate::terminal::ssh::util::{parse_interactive_ssh_command, SshWarpifyCommand}; +use crate::terminal::ssh::util::{parse_interactive_ssh_command, SshZaplexifyCommand}; // Cannot directly use Vec here b/c Regex doesn't impl Eq, Serialize, and Deserialize. -maybe_define_setting!(AddedSubshellCommands, group: WarpifySettings, { +maybe_define_setting!(AddedSubshellCommands, group: ZaplexifySettings, { type: Vec, default: Vec::new(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.subshells.added_subshell_commands", + toml_path: "zaplexify.subshells.added_subshell_commands", description: "Additional regex patterns for commands that should be recognized as subshells.", }); -maybe_define_setting!(SubshellCommandsDenylist, group: WarpifySettings, { +maybe_define_setting!(SubshellCommandsDenylist, group: ZaplexifySettings, { type: Vec, default: Vec::new(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.subshells.subshell_commands_denylist", - description: "Commands that should not trigger the subshell warpification prompt.", + toml_path: "zaplexify.subshells.subshell_commands_denylist", + description: "Commands that should not trigger the subshell zaplexification prompt.", }); -maybe_define_setting!(SshHostsDenylist, group: WarpifySettings, { +maybe_define_setting!(SshHostsDenylist, group: ZaplexifySettings, { type: Vec, default: Vec::new(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.ssh.ssh_hosts_denylist", - description: "SSH hosts that should not trigger the warpification prompt.", + toml_path: "zaplexify.ssh.ssh_hosts_denylist", + description: "SSH hosts that should not trigger the zaplexification prompt.", }); -maybe_define_setting!(EnableSshWarpification, group: WarpifySettings, { +maybe_define_setting!(EnableSshZaplexification, group: ZaplexifySettings, { type: bool, default: true, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.ssh.enable_ssh_warpification", - description: "Whether to enable Zap features in SSH sessions.", + toml_path: "zaplexify.ssh.enable_ssh_zaplexification", + description: "Whether to enable Zaplex features in SSH sessions.", }); -maybe_define_setting!(UseSshTmuxWrapper, group: WarpifySettings, { +maybe_define_setting!(UseSshTmuxWrapper, group: ZaplexifySettings, { type: bool, default: false, supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()), sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.ssh.use_ssh_tmux_wrapper", - description: "Whether to use a tmux-based wrapper for SSH warpification.", + toml_path: "zaplexify.ssh.use_ssh_tmux_wrapper", + description: "Whether to use a tmux-based wrapper for SSH zaplexification.", }); -/// Controls how Zap handles the SSH extension (remote server binary) when connecting +/// Controls how Zaplex handles the SSH extension (remote server binary) when connecting /// to a remote host that does not already have it installed. #[derive( Default, @@ -88,17 +88,17 @@ pub enum SshExtensionInstallMode { AlwaysAsk, /// Automatically install and connect without prompting. AlwaysInstall, - /// Never install; fall back to legacy warpification. + /// Never install; fall back to legacy zaplexification. NeverInstall, } -maybe_define_setting!(SshExtensionInstallModeSetting, group: WarpifySettings, { +maybe_define_setting!(SshExtensionInstallModeSetting, group: ZaplexifySettings, { type: SshExtensionInstallMode, default: SshExtensionInstallMode::default(), supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, - toml_path: "warpify.ssh.ssh_extension_install_mode", + toml_path: "zaplexify.ssh.ssh_extension_install_mode", description: "Controls SSH extension installation behavior.", }); @@ -123,7 +123,7 @@ impl SshExtensionInstallMode { /// Normally we use the define_settings_group! macro for singleton models of settings like this. /// However, this model needs to do some extra processing on the added_subshell_commands and store /// an enriched representation in parsed_added_subshell_commands. -pub struct WarpifySettings { +pub struct ZaplexifySettings { /// A list of regexes that users can add to define new subshell-compatible commands. This /// represents the raw, serialized value. Therefore, it is Vec. pub added_subshell_commands: AddedSubshellCommands, @@ -133,9 +133,9 @@ pub struct WarpifySettings { /// needs to be kept up-to-date as added_subshell_commands changes. See the Self::register /// method for how this is done. pub parsed_added_subshell_commands: Vec>, - /// A list of commands that we shouldn't attempt to warpify. These can be added either b/c the + /// A list of commands that we shouldn't attempt to zaplexify. These can be added either b/c the /// "don't ask again" button was clicked in the trigger banner, or it was added explicitly on - /// the Warpify settings page. This represents the raw, serialized value. + /// the Zaplexify settings page. This represents the raw, serialized value. pub subshell_command_denylist: SubshellCommandsDenylist, /// This is subshell_command_denylist compiled to actual executable Regex. This is a Result as we /// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering @@ -144,11 +144,11 @@ pub struct WarpifySettings { /// method for how this is done. pub parsed_subshell_command_denylist: Vec>, - /// A list of hosts that we shouldn't attempt to warpify. This supports regex. + /// A list of hosts that we shouldn't attempt to zaplexify. This supports regex. /// These can be added either b/c the "don't ask again" button was clicked in the trigger banner, - /// or it was added explicitly on the Warpify settings page. + /// or it was added explicitly on the Zaplexify settings page. /// While this could live in the `SshSettings` group, the custom processing shared with the other - /// subshell logic better justifies it living in the `WarpifySettings` group. + /// subshell logic better justifies it living in the `ZaplexifySettings` group. pub ssh_hosts_denylist: SshHostsDenylist, /// This is ssh_hosts_denylist compiled to actual executable Regex. This is a Result as we /// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering @@ -157,10 +157,10 @@ pub struct WarpifySettings { /// method for how this is done. pub parsed_ssh_hosts_denylist: Vec>, - /// This setting controls whether we should ever warpify ssh sessions. - pub enable_ssh_warpification: EnableSshWarpification, + /// This setting controls whether we should ever zaplexify ssh sessions. + pub enable_ssh_zaplexification: EnableSshZaplexification, - /// This setting controls whether we should prompt the user to warpify an ssh session using the + /// This setting controls whether we should prompt the user to zaplexify an ssh session using the /// tmux wrapper instead of the default legacy wrapper. pub use_ssh_tmux_wrapper: UseSshTmuxWrapper, @@ -214,7 +214,7 @@ lazy_static! { /// define_settings_group! macro, which is the basic template for user-defaults-backed settings. /// I have separated this stuff from the other impl block, which contains the subshell-specific /// logic, because this is basically boilerplate. -impl WarpifySettings { +impl ZaplexifySettings { fn new_from_storage(ctx: &mut ModelContext) -> Self { let added_subshell_commands = AddedSubshellCommands::new_from_storage(ctx); let subshell_command_denylist = SubshellCommandsDenylist::new_from_storage(ctx); @@ -230,7 +230,7 @@ impl WarpifySettings { subshell_command_denylist, parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist), ssh_hosts_denylist, - enable_ssh_warpification: EnableSshWarpification::new_from_storage(ctx), + enable_ssh_zaplexification: EnableSshZaplexification::new_from_storage(ctx), use_ssh_tmux_wrapper: UseSshTmuxWrapper::new_from_storage(ctx), ssh_extension_install_mode: SshExtensionInstallModeSetting::new_from_storage(ctx), } @@ -253,7 +253,7 @@ impl WarpifySettings { subshell_command_denylist, parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist), ssh_hosts_denylist, - enable_ssh_warpification: EnableSshWarpification::new(None), + enable_ssh_zaplexification: EnableSshZaplexification::new(None), use_ssh_tmux_wrapper: UseSshTmuxWrapper::new(None), ssh_extension_install_mode: SshExtensionInstallModeSetting::new(None), } @@ -266,26 +266,26 @@ impl WarpifySettings { let handle = ctx.add_singleton_model(Self::new_from_storage); handle.clone().update(ctx, |_, ctx| { ctx.subscribe_to_model(&handle, |me, event, _| match event { - WarpifySettingsChangedEvent::AddedSubshellCommands { .. } => { + ZaplexifySettingsChangedEvent::AddedSubshellCommands { .. } => { me.parsed_added_subshell_commands = Self::parse_added_subshell_commands(&me.added_subshell_commands) } - WarpifySettingsChangedEvent::SubshellCommandsDenylist { .. } => { + ZaplexifySettingsChangedEvent::SubshellCommandsDenylist { .. } => { me.parsed_subshell_command_denylist = Self::parse_subshell_command_denylist(&me.subshell_command_denylist) } - WarpifySettingsChangedEvent::SshHostsDenylist { .. } => { + ZaplexifySettingsChangedEvent::SshHostsDenylist { .. } => { me.parsed_ssh_hosts_denylist = Self::parse_ssh_hosts_denylist(&me.ssh_hosts_denylist) } - WarpifySettingsChangedEvent::EnableSshWarpification { .. } => {} - WarpifySettingsChangedEvent::UseSshTmuxWrapper { .. } => {} - WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {} + ZaplexifySettingsChangedEvent::EnableSshZaplexification { .. } => {} + ZaplexifySettingsChangedEvent::UseSshTmuxWrapper { .. } => {} + ZaplexifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {} }) }); register_settings_events!( - WarpifySettings, + ZaplexifySettings, added_subshell_commands, AddedSubshellCommands, handle.clone(), @@ -293,7 +293,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + ZaplexifySettings, subshell_command_denylist, SubshellCommandsDenylist, handle.clone(), @@ -301,15 +301,15 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, - enable_ssh_warpification, - EnableSshWarpification, + ZaplexifySettings, + enable_ssh_zaplexification, + EnableSshZaplexification, handle.clone(), ctx ); register_settings_events!( - WarpifySettings, + ZaplexifySettings, use_ssh_tmux_wrapper, UseSshTmuxWrapper, handle.clone(), @@ -317,7 +317,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + ZaplexifySettings, ssh_extension_install_mode, SshExtensionInstallModeSetting, handle.clone(), @@ -325,7 +325,7 @@ impl WarpifySettings { ); register_settings_events!( - WarpifySettings, + ZaplexifySettings, ssh_hosts_denylist, SshHostsDenylist, handle, @@ -335,9 +335,9 @@ impl WarpifySettings { } /// This is also something that would normally be generated by -/// define_settings_group!(WarpifySettings). Since we didn't use that macro we define it manually +/// define_settings_group!(ZaplexifySettings). Since we didn't use that macro we define it manually /// here. It's the event emitted by the setter methods when a setting value changes. -pub enum WarpifySettingsChangedEvent { +pub enum ZaplexifySettingsChangedEvent { AddedSubshellCommands { change_event_reason: ChangeEventReason, }, @@ -347,7 +347,7 @@ pub enum WarpifySettingsChangedEvent { SshHostsDenylist { change_event_reason: ChangeEventReason, }, - EnableSshWarpification { + EnableSshZaplexification { change_event_reason: ChangeEventReason, }, UseSshTmuxWrapper { @@ -358,15 +358,15 @@ pub enum WarpifySettingsChangedEvent { }, } -impl Entity for WarpifySettings { - type Event = WarpifySettingsChangedEvent; +impl Entity for ZaplexifySettings { + type Event = ZaplexifySettingsChangedEvent; } -impl SingletonEntity for WarpifySettings {} +impl SingletonEntity for ZaplexifySettings {} /// This is the other impl block for this model. This one contains the actual subshell-specific /// logic. -impl WarpifySettings { +impl ZaplexifySettings { fn is_built_in_subshell_match(command: &str) -> bool { for command_regex in SUBSHELL_COMMAND_REGEXES.iter() { if command_regex.is_match(command) { @@ -392,7 +392,7 @@ impl WarpifySettings { } if !self.use_ssh_tmux_wrapper.value() - && SshWarpifyCommand::matches(command) + && SshZaplexifyCommand::matches(command) .is_some_and(|command| command.is_ssh_like_command()) { return true; @@ -404,8 +404,8 @@ impl WarpifySettings { } } - // While in-band generators are our best option for warpifying ssh sessions from powershell, hard-code - // the warpify subshell banner to show up. + // While in-band generators are our best option for zaplexifying ssh sessions from powershell, hard-code + // the zaplexify subshell banner to show up. if matches!(shell_family, ShellFamily::PowerShell) && parse_interactive_ssh_command(command).is_some() { @@ -485,7 +485,7 @@ impl WarpifySettings { new_added_commands_list.push(command_to_add.trim().to_owned()); // The set_value method generated by the maybe_define_setting! macro will take - // care of emitting the WarpifySettingsChangedEvent::AddedSubshellCommands event to keep + // care of emitting the ZaplexifySettingsChangedEvent::AddedSubshellCommands event to keep // parsed_added_subshell_commands in sync. self.added_subshell_commands .set_value(new_added_commands_list, ctx) @@ -494,7 +494,7 @@ impl WarpifySettings { ctx.notify(); } - /// Check if the user has asked us to remember a command and avoid asking to warpify a subshell. + /// Check if the user has asked us to remember a command and avoid asking to zaplexify a subshell. pub fn is_denylisted_subshell_command(&self, command: &str) -> bool { let command = command.trim(); self.parsed_subshell_command_denylist diff --git a/app/src/terminal/warpify/settings_test.rs b/app/src/terminal/zaplexify/settings_test.rs similarity index 88% rename from app/src/terminal/warpify/settings_test.rs rename to app/src/terminal/zaplexify/settings_test.rs index a2c7f2807c..4bba7959ce 100644 --- a/app/src/terminal/warpify/settings_test.rs +++ b/app/src/terminal/zaplexify/settings_test.rs @@ -1,5 +1,5 @@ #[cfg(windows)] -use super::WarpifySettings; +use super::ZaplexifySettings; #[cfg(windows)] #[test] @@ -19,7 +19,7 @@ fn test_wsl_subshell_detection_success() { .iter() .for_each(|cmd| { assert!( - WarpifySettings::is_built_in_subshell_match(cmd), + ZaplexifySettings::is_built_in_subshell_match(cmd), "{} failed to match", *cmd ) @@ -50,7 +50,7 @@ fn test_wsl_subshell_detection_fail() { .iter() .for_each(|cmd| { assert!( - !WarpifySettings::is_built_in_subshell_match(cmd), + !ZaplexifySettings::is_built_in_subshell_match(cmd), "{} accidentally matched", *cmd ) diff --git a/app/src/terminal/warpify/success_block.rs b/app/src/terminal/zaplexify/success_block.rs similarity index 74% rename from app/src/terminal/warpify/success_block.rs rename to app/src/terminal/zaplexify/success_block.rs index 68f0448e0c..be21da389d 100644 --- a/app/src/terminal/warpify/success_block.rs +++ b/app/src/terminal/zaplexify/success_block.rs @@ -24,26 +24,26 @@ use warpui::{ }; use super::render::{HORIZONTAL_TEXT_MARGIN, SSH_DOCS_URL, SUBSHELL_DOCS_URL}; -use super::settings::WarpifySettings; -use super::{render, subshell_bootstrap_success_block_bytes, WarpificationSource}; +use super::settings::ZaplexifySettings; +use super::{render, subshell_bootstrap_success_block_bytes, ZaplexificationSource}; const VERTICAL_TEXT_MARGIN: f32 = 16.; #[derive(Debug, Clone)] -pub enum WarpifySuccessBlockEvent { +pub enum ZaplexifySuccessBlockEvent { ZapifySettings, } #[derive(Debug, Clone, Eq, PartialEq)] -pub enum WarpifySuccessBlockAction { - ClearAutoWarpifySnippet, +pub enum ZaplexifySuccessBlockAction { + ClearAutoZaplexifySnippet, ZapifySettings, OpenUrl(String), } -struct AutoWarpifySnippet { +struct AutoZaplexifySnippet { /// On subshell initialization, this will contain the output grid to display, - /// containing info like how to auto-warpify the subshell. + /// containing info like how to auto-zaplexify the subshell. output_grid: Cow<'static, str>, /// The output grid needs to be selectable to allow users to copy the command to their clipboard. selection_handle: SelectionHandle, @@ -55,24 +55,24 @@ struct AutoWarpifySnippet { can_write_to_rc: bool, } -pub struct WarpifySuccessBlock { - source: WarpificationSource, +pub struct ZaplexifySuccessBlock { + source: ZaplexificationSource, spawning_command: String, learn_more_link_mouse_states: MouseStateHandle, - auto_warpify_snippet: Option, + auto_zaplexify_snippet: Option, } -impl WarpifySuccessBlock { +impl ZaplexifySuccessBlock { #[allow(clippy::new_without_default)] pub fn new( - source: WarpificationSource, + source: ZaplexificationSource, spawning_command: String, subshell_info: Option, shell: Shell, disable_tmux: bool, ctx: &mut ViewContext, ) -> Self { - ctx.subscribe_to_model(&WarpifySettings::handle(ctx), move |_, _, _, ctx| { + ctx.subscribe_to_model(&ZaplexifySettings::handle(ctx), move |_, _, _, ctx| { ctx.notify(); }); @@ -80,17 +80,17 @@ impl WarpifySuccessBlock { // getting the OS to write to the correct RC file. let remote_os = TargetOS::Linux; - let is_auto_warpify_configured = subshell_info + let is_auto_zaplexify_configured = subshell_info .as_ref() .map(|info| info.was_triggered_by_rc_file_snippet) .unwrap_or_default(); - let auto_warpify_snippet = if is_auto_warpify_configured { + let auto_zaplexify_snippet = if is_auto_zaplexify_configured { None } else { subshell_info.and_then(|subshell_info| { - // If warpification wasn't triggered automatically, show a snippet about - // how to automatically warpify. + // If zaplexification wasn't triggered automatically, show a snippet about + // how to automatically zaplexify. (!subshell_info.was_triggered_by_rc_file_snippet).then(|| { let (command, is_executable) = subshell_bootstrap_success_block_bytes( &subshell_info, @@ -113,12 +113,12 @@ impl WarpifySuccessBlock { }) }) }; - let auto_warpify_snippet = auto_warpify_snippet.map(|(output_grid, can_write_to_rc)| { - AutoWarpifySnippet { + let auto_zaplexify_snippet = auto_zaplexify_snippet.map(|(output_grid, can_write_to_rc)| { + AutoZaplexifySnippet { description: (if !output_grid.is_empty() { - "Run the following to automatically Warpify in the future:" + "Run the following to automatically Zaplexify in the future:" } else { - "In remote subshells, Zap runs commands in the background to power completions, syntax highlighting, and other features." + "In remote subshells, Zaplex runs commands in the background to power completions, syntax highlighting, and other features." }).into(), output_grid: output_grid.into(), selection_handle: Default::default(), @@ -133,12 +133,12 @@ impl WarpifySuccessBlock { source, learn_more_link_mouse_states: Default::default(), spawning_command, - auto_warpify_snippet, + auto_zaplexify_snippet, } } pub fn selected_text(&self) -> Option { - self.auto_warpify_snippet + self.auto_zaplexify_snippet .as_ref() .and_then(|snippet| snippet.selected_text.read().clone()) } @@ -156,8 +156,8 @@ impl WarpifySuccessBlock { pub fn render_title_ui(&self, theme: &WarpTheme, appearance: &Appearance) -> Box { let header_contents = render::build_header_row( - "Session Warpified", - Icon::new(UiIcon::Zap.into(), theme.active_ui_detail()), + "Session Zaplexified", + Icon::new(UiIcon::Zaplex.into(), theme.active_ui_detail()), theme, appearance, ) @@ -185,8 +185,8 @@ impl WarpifySuccessBlock { fn render_learn_more_link(&self, appearance: &Appearance) -> Box { let url = match self.source { - WarpificationSource::Ssh => SSH_DOCS_URL, - WarpificationSource::Subshell => SUBSHELL_DOCS_URL, + ZaplexificationSource::Ssh => SSH_DOCS_URL, + ZaplexificationSource::Subshell => SUBSHELL_DOCS_URL, }; let font_family_id = appearance.monospace_font_family(); @@ -198,7 +198,7 @@ impl WarpifySuccessBlock { None, Some(Box::new({ move |ctx| { - ctx.dispatch_typed_action(WarpifySuccessBlockAction::OpenUrl( + ctx.dispatch_typed_action(ZaplexifySuccessBlockAction::OpenUrl( url.to_owned(), )); } @@ -215,13 +215,13 @@ impl WarpifySuccessBlock { .finish() } - /// Fired when a block ends and we are not in a Warpified session. - pub fn on_warpified_session_complete(&mut self, ctx: &mut ViewContext) { - self.clear_auto_warpify_snippet(ctx); + /// Fired when a block ends and we are not in a Zaplexified session. + pub fn on_zaplexified_session_complete(&mut self, ctx: &mut ViewContext) { + self.clear_auto_zaplexify_snippet(ctx); } - pub fn clear_auto_warpify_snippet(&mut self, ctx: &mut ViewContext) { - self.auto_warpify_snippet = None; + pub fn clear_auto_zaplexify_snippet(&mut self, ctx: &mut ViewContext) { + self.auto_zaplexify_snippet = None; ctx.notify(); } @@ -232,16 +232,16 @@ impl WarpifySuccessBlock { appearance: &Appearance, ) -> Option> { let theme = appearance.theme(); - let auto_warpify_snippet = self.auto_warpify_snippet.as_ref()?; + let auto_zaplexify_snippet = self.auto_zaplexify_snippet.as_ref()?; - if auto_warpify_snippet.output_grid.is_empty() { + if auto_zaplexify_snippet.output_grid.is_empty() { return None; } - let shell_language = ProgrammingLanguage::Shell(auto_warpify_snippet.shell_type); + let shell_language = ProgrammingLanguage::Shell(auto_zaplexify_snippet.shell_type); let runnable_command = render_runnable_code_snippet( - &auto_warpify_snippet.output_grid, - if auto_warpify_snippet.can_write_to_rc { + &auto_zaplexify_snippet.output_grid, + if auto_zaplexify_snippet.can_write_to_rc { Some(&shell_language) } else { None @@ -252,7 +252,7 @@ impl WarpifySuccessBlock { code_snippet.to_string(), )); - ctx.dispatch_typed_action(WarpifySuccessBlockAction::ClearAutoWarpifySnippet); + ctx.dispatch_typed_action(ZaplexifySuccessBlockAction::ClearAutoZaplexifySnippet); } })), Some(Box::new({ @@ -260,19 +260,19 @@ impl WarpifySuccessBlock { ctx.dispatch_typed_action(WorkspaceAction::CopyTextToClipboard(code_snippet)); } })), - Some(auto_warpify_snippet.code_snippet_handles.clone()), + Some(auto_zaplexify_snippet.code_snippet_handles.clone()), app, ); let semantic_selection = SemanticSelection::as_ref(app); - let selected_text = auto_warpify_snippet.selected_text.clone(); + let selected_text = auto_zaplexify_snippet.selected_text.clone(); - // TODO(Simon): Implement full selection and copying functionality for the WarpifySuccessBlock. + // TODO(Simon): Implement full selection and copying functionality for the ZaplexifySuccessBlock. // Look to the `EnvVarCollectionBlock` for the existing implementation paradigm. We don't // yet have a robust way of ensuring that every aspect of text selection is implemented // properly, so be extra careful not to miss any details! let output_grid = SelectableArea::new( - auto_warpify_snippet.selection_handle.clone(), + auto_zaplexify_snippet.selection_handle.clone(), move |selection_args, _, _| { *selected_text.write() = selection_args.selection; }, @@ -286,7 +286,7 @@ impl WarpifySuccessBlock { .with_child( Container::new( Text::new( - auto_warpify_snippet.description.clone(), + auto_zaplexify_snippet.description.clone(), appearance.monospace_font_family(), appearance.monospace_font_size(), ) @@ -308,15 +308,15 @@ impl WarpifySuccessBlock { } } -impl Entity for WarpifySuccessBlock { - type Event = WarpifySuccessBlockEvent; +impl Entity for ZaplexifySuccessBlock { + type Event = ZaplexifySuccessBlockEvent; } -pub const WARPIFY_SUCCESS_BLOCK_VISIBLE_KEY: &str = "WarpifySuccessBlockVisible"; +pub const ZAPLEXIFY_SUCCESS_BLOCK_VISIBLE_KEY: &str = "ZaplexifySuccessBlockVisible"; -impl View for WarpifySuccessBlock { +impl View for ZaplexifySuccessBlock { fn ui_name() -> &'static str { - "WarpifySuccessBlock" + "ZaplexifySuccessBlock" } fn render(&self, app: &AppContext) -> Box { @@ -341,19 +341,19 @@ impl View for WarpifySuccessBlock { } } -impl TypedActionView for WarpifySuccessBlock { - type Action = WarpifySuccessBlockAction; +impl TypedActionView for ZaplexifySuccessBlock { + type Action = ZaplexifySuccessBlockAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - WarpifySuccessBlockAction::ZapifySettings => { - ctx.emit(WarpifySuccessBlockEvent::ZapifySettings); + ZaplexifySuccessBlockAction::ZapifySettings => { + ctx.emit(ZaplexifySuccessBlockEvent::ZapifySettings); } - WarpifySuccessBlockAction::OpenUrl(url) => { + ZaplexifySuccessBlockAction::OpenUrl(url) => { ctx.open_url(url); } - WarpifySuccessBlockAction::ClearAutoWarpifySnippet => { - self.clear_auto_warpify_snippet(ctx); + ZaplexifySuccessBlockAction::ClearAutoZaplexifySnippet => { + self.clear_auto_zaplexify_snippet(ctx); } } } diff --git a/app/src/terminal/warpify/trigger_state.rs b/app/src/terminal/zaplexify/trigger_state.rs similarity index 80% rename from app/src/terminal/warpify/trigger_state.rs rename to app/src/terminal/zaplexify/trigger_state.rs index 3019e5170f..35dcc13e07 100644 --- a/app/src/terminal/warpify/trigger_state.rs +++ b/app/src/terminal/zaplexify/trigger_state.rs @@ -14,12 +14,12 @@ use crate::terminal::{ }, settings::TerminalSettings, shell::ShellType, - ssh::{error::SshErrorBlock, install_tmux::SshInstallTmuxBlock, warpify::SshWarpifyBlock}, + ssh::{error::SshErrorBlock, install_tmux::SshInstallTmuxBlock, zaplexify::SshZaplexifyBlock}, TerminalModel, TerminalView, }; use std::{collections::HashMap, sync::Arc}; -use super::success_block::WarpifySuccessBlock; +use super::success_block::ZaplexifySuccessBlock; /// A unique identifier for a subshell separator. pub type SeparatorId = usize; @@ -47,11 +47,11 @@ impl SubshellSeparatorState { #[derive(Debug)] pub enum SshBlockState { - Warpifying { - handle: ViewHandle, + Zaplexifying { + handle: ViewHandle, }, - WarpifySuccess { - handle: ViewHandle, + ZaplexifySuccess { + handle: ViewHandle, }, InstallTmux { handle: ViewHandle, @@ -68,8 +68,8 @@ impl SshBlockState { pub fn get_block_view_id(&self) -> EntityId { match self { - SshBlockState::Warpifying { handle, .. } => handle.id(), - SshBlockState::WarpifySuccess { handle, .. } => handle.id(), + SshBlockState::Zaplexifying { handle, .. } => handle.id(), + SshBlockState::ZaplexifySuccess { handle, .. } => handle.id(), SshBlockState::InstallTmux { handle, .. } => handle.id(), SshBlockState::Error { handle } => handle.id(), } @@ -81,18 +81,18 @@ impl SshBlockState { SshBlockState::InstallTmux { handle, .. } => { handle.update(ctx, |block, ctx| block.collapse_script(ctx)) } - SshBlockState::Warpifying { .. } => false, - SshBlockState::WarpifySuccess { .. } => false, + SshBlockState::Zaplexifying { .. } => false, + SshBlockState::ZaplexifySuccess { .. } => false, SshBlockState::Error { .. } => false, } } pub fn focus(&mut self, ctx: &mut ViewContext) { match self { - SshBlockState::Warpifying { handle } => { + SshBlockState::Zaplexifying { handle } => { handle.update(ctx, |block, ctx| block.focus(ctx)); } - SshBlockState::WarpifySuccess { .. } => {} + SshBlockState::ZaplexifySuccess { .. } => {} SshBlockState::InstallTmux { handle } => { handle.update(ctx, |block, ctx| block.focus(ctx)); } @@ -107,24 +107,24 @@ impl SshBlockState { SshBlockState::InstallTmux { handle, .. } => { Some(handle.read(app, |view, _| view.system_details())) } - SshBlockState::Warpifying { .. } => None, - SshBlockState::WarpifySuccess { .. } => None, + SshBlockState::Zaplexifying { .. } => None, + SshBlockState::ZaplexifySuccess { .. } => None, SshBlockState::Error { .. } => None, } } - pub fn on_warpified_session_complete( + pub fn on_zaplexified_session_complete( &self, ctx: &mut ViewContext, ) -> Option { match self { - SshBlockState::InstallTmux { .. } | SshBlockState::Warpifying { .. } => { + SshBlockState::InstallTmux { .. } | SshBlockState::Zaplexifying { .. } => { let block_id = self.get_block_view_id(); return Some(block_id); } - SshBlockState::WarpifySuccess { handle } => { + SshBlockState::ZaplexifySuccess { handle } => { handle.update(ctx, |block, ctx| { - block.on_warpified_session_complete(ctx); + block.on_zaplexified_session_complete(ctx); }); } SshBlockState::Error { .. } => {} @@ -133,29 +133,29 @@ impl SshBlockState { } } -/// Temporary state used to trigger Warpification. +/// Temporary state used to trigger Zaplexification. #[derive(Default)] -struct WarpifyTriggerState { +struct ZaplexifyTriggerState { block_id: Option, - /// Lets us abort an attempt to auto warpify if the subshell command + /// Lets us abort an attempt to auto zaplexify if the subshell command /// hasn't completed. - auto_warpify_abort_handle: Option, + auto_zaplexify_abort_handle: Option, /// The subshell banner waits 1s before showing. This is to see that the command stays running /// for a while without exiting. We store the abort handle here so that the /// TerminalEvent::BlockCompleted event can abort the banner. subshell_banner_abort_handle: Option, - /// The command which may trigger ssh Warpification + /// The command which may trigger ssh Zaplexification pending_command: Option, - /// The Host which may trigger ssh Warpification - pending_warpify_ssh_host: Option, + /// The Host which may trigger ssh Zaplexification + pending_zaplexify_ssh_host: Option, /// Which, if any, SSH block is currently added to the blocklist. ssh_block_state: Option, - ssh_warpify_timeout_handle: Option, + ssh_zaplexify_timeout_handle: Option, shell_type: Option, @@ -165,17 +165,17 @@ struct WarpifyTriggerState { } #[derive(Default)] -pub struct WarpifyState { +pub struct ZaplexifyState { session_id: Option, - pending_state: Option, + pending_state: Option, /// Stores the metadata needed to render any separators above the first block of a subshell. subshell_separator_state: SubshellSeparatorState, /// A unique-enough ID that is used to validate that a timeout is still valid. timeout_id: u8, } -impl WarpifyState { +impl ZaplexifyState { pub fn delete_state(&mut self) { self.pending_state.take(); } @@ -244,32 +244,32 @@ impl WarpifyState { .and_then(|state| state.subshell_banner_abort_handle.take()) } - pub fn add_auto_warpify_abort_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) { + pub fn add_auto_zaplexify_abort_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) { let pending_state = self.pending_state.get_or_insert_with(Default::default); - pending_state.auto_warpify_abort_handle = Some(spawned_future_handle); + pending_state.auto_zaplexify_abort_handle = Some(spawned_future_handle); } - pub fn abort_auto_warpify(&mut self) { + pub fn abort_auto_zaplexify(&mut self) { if let Some(abort_handle) = self .pending_state .as_mut() - .and_then(|state| state.auto_warpify_abort_handle.take()) + .and_then(|state| state.auto_zaplexify_abort_handle.take()) { abort_handle.abort(); }; } - pub fn add_ssh_warpify_timeout_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) { + pub fn add_ssh_zaplexify_timeout_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) { let pending_state = self.pending_state.get_or_insert_with(Default::default); - pending_state.ssh_warpify_timeout_handle = Some(spawned_future_handle); + pending_state.ssh_zaplexify_timeout_handle = Some(spawned_future_handle); } - pub fn abort_ssh_warpify_timeout(&mut self) { + pub fn abort_ssh_zaplexify_timeout(&mut self) { self.replace_timeout_id(); if let Some(handle) = self .pending_state .as_mut() - .and_then(|state| state.ssh_warpify_timeout_handle.take()) + .and_then(|state| state.ssh_zaplexify_timeout_handle.take()) { handle.abort(); }; @@ -312,31 +312,31 @@ impl WarpifyState { pub fn get_pending_ssh_host(&self) -> Option { self.pending_state .as_ref() - .and_then(|state: &WarpifyTriggerState| state.pending_warpify_ssh_host.clone()) + .and_then(|state: &ZaplexifyTriggerState| state.pending_zaplexify_ssh_host.clone()) } pub fn get_pending_ssh_command(&self) -> Option { self.pending_state .as_ref() - .and_then(|state: &WarpifyTriggerState| state.pending_command.clone()) + .and_then(|state: &ZaplexifyTriggerState| state.pending_command.clone()) } pub fn take_pending_ssh_host(&mut self) -> Option { self.pending_state .as_mut() - .and_then(|state: &mut WarpifyTriggerState| state.pending_warpify_ssh_host.take()) + .and_then(|state: &mut ZaplexifyTriggerState| state.pending_zaplexify_ssh_host.take()) } pub fn clear_pending_ssh_host(&mut self) { if let Some(ref mut pending_state) = self.pending_state.as_mut() { - pending_state.pending_warpify_ssh_host = None; + pending_state.pending_zaplexify_ssh_host = None; } } pub fn set_pending_ssh_host(&mut self, command: String, ssh_host: Option) { let pending_state = self.pending_state.get_or_insert_with(Default::default); pending_state.pending_command = Some(command); - pending_state.pending_warpify_ssh_host = ssh_host; + pending_state.pending_zaplexify_ssh_host = ssh_host; } pub fn set_tmux_installation_state(&mut self, tmux_installation: TmuxInstallationState) { @@ -383,10 +383,10 @@ impl WarpifyState { } /// Called once whenever we get a local block completed, as opposed to a remote ssh block - /// and we have a Warpify Success block. - fn on_warpified_session_complete( + /// and we have a Zaplexify Success block. + fn on_zaplexified_session_complete( &mut self, - state: WarpifyTriggerState, + state: ZaplexifyTriggerState, ctx: &mut ViewContext, ) -> Option { self.clear_ssh_block_state(); @@ -394,16 +394,16 @@ impl WarpifyState { let Some(block) = &state.ssh_block_state else { return None; }; - block.on_warpified_session_complete(ctx) + block.on_zaplexified_session_complete(ctx) } - pub fn on_warpify_start(&mut self, active_session_id: Option) { + pub fn on_zaplexify_start(&mut self, active_session_id: Option) { self.session_id = active_session_id; } - /// Called whenever a block is completed, to determine whether a Warpified session + /// Called whenever a block is completed, to determine whether a Zaplexified session /// has been completed. - pub fn get_completed_warpify_session_id( + pub fn get_completed_zaplexify_session_id( &mut self, active_session_id: Option, ctx: &mut ViewContext, @@ -412,7 +412,7 @@ impl WarpifyState { return None; } if let Some(state) = self.pending_state.take() { - return self.on_warpified_session_complete(state, ctx); + return self.on_zaplexified_session_complete(state, ctx); }; None } diff --git a/app/src/test_util/settings.rs b/app/src/test_util/settings.rs index f75c036aee..6fab3f763b 100644 --- a/app/src/test_util/settings.rs +++ b/app/src/test_util/settings.rs @@ -29,7 +29,7 @@ pub fn initialize_settings_for_tests_with_mode( general_settings::GeneralSettings, keys_settings::KeysSettings, ligature_settings::LigatureSettings, safe_mode_settings::SafeModeSettings, session_settings::SessionSettings, settings::TerminalSettings, - shared_session::settings::SharedSessionSettings, warpify::settings::WarpifySettings, + shared_session::settings::SharedSessionSettings, zaplexify::settings::ZaplexifySettings, BlockListSettings, }, undo_close::UndoCloseSettings, @@ -47,7 +47,7 @@ pub fn initialize_settings_for_tests_with_mode( AccessibilitySettings::register(app); app.update(AISettings::register_and_subscribe_to_events); AliasExpansionSettings::register(app); - // Zap Wave 7-3: `AmbientAgentSettings` removed along with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `AmbientAgentSettings` removed along with ambient-agent UI subsystem. AppEditorSettings::register(app); BlockVisibilitySettings::register(app); BlockListSettings::register(app); @@ -62,7 +62,7 @@ pub fn initialize_settings_for_tests_with_mode( crate::util::file::external_editor::EditorSettings::register(app); } - // Zap Wave 7-3: `AmbientAgentSettings` removed along with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `AmbientAgentSettings` removed along with ambient-agent UI subsystem. FontSettings::register(app); GeneralSettings::register(app); GPUSettings::register(app); @@ -83,7 +83,7 @@ pub fn initialize_settings_for_tests_with_mode( ScrollSettings::register(app); SelectionSettings::register(app); app.update(|ctx| { - WarpifySettings::register(ctx); + ZaplexifySettings::register(ctx); }); SessionSettings::register(app); SshSettings::register(app); diff --git a/app/src/test_util/terminal.rs b/app/src/test_util/terminal.rs index d46b26540f..579d4553c9 100644 --- a/app/src/test_util/terminal.rs +++ b/app/src/test_util/terminal.rs @@ -28,7 +28,6 @@ use crate::ai::restored_conversations::RestoredAgentConversations; use crate::auth::AuthManager; use crate::auth::AuthStateProvider; use crate::changelog_model::ChangelogModel; -use crate::pricing::PricingInfoModel; use crate::suggestions::ignored_suggestions_model::IgnoredSuggestionsModel; use crate::terminal::view::inline_banner::ByoLlmAuthBannerSessionState; use crate::undo_close::UndoCloseStack; @@ -93,7 +92,7 @@ pub fn initialize_app_for_terminal_view(app: &mut App) { app.add_singleton_model(RepoMetadataModel::new); app.add_singleton_model(FileSearchModel::new); app.add_singleton_model(|_| GitStatusUpdateModel::new()); - // Zap: RepoOutlines removed, no longer registered. + // Zaplex: RepoOutlines removed, no longer registered. app.add_singleton_model(HomeDirectoryWatcher::new_for_test); app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing); app.add_singleton_model(SkillManager::new); @@ -112,7 +111,6 @@ pub fn initialize_app_for_terminal_view(app: &mut App) { app.add_singleton_model(OneTimeModalModel::new); app.add_singleton_model(|_| WorkspaceRegistry::new()); app.add_singleton_model(|_| IgnoredSuggestionsModel::new(vec![])); - app.add_singleton_model(|_| PricingInfoModel::new()); app.add_singleton_model(AIDocumentModel::new); app.add_singleton_model(ByoLlmAuthBannerSessionState::new); app.add_singleton_model(|_| GitHubAuthNotifier::new()); diff --git a/app/src/test_util/virtual_fs.rs b/app/src/test_util/virtual_fs.rs index a2eb209e2c..ba496e57e9 100644 --- a/app/src/test_util/virtual_fs.rs +++ b/app/src/test_util/virtual_fs.rs @@ -4,15 +4,15 @@ pub use virtual_fs::{Dirs, Stub, VirtualFS}; pub trait WarpDirs { #[allow(dead_code)] fn git_repository_fixture(&self) -> PathBuf { - Zap::fixtures().join("git_repository") + Zaplex::fixtures().join("git_repository") } } impl WarpDirs for Dirs {} -pub struct Zap; +pub struct Zaplex; -impl Zap { +impl Zaplex { #[allow(dead_code)] pub fn executable() -> PathBuf { let mut path = { diff --git a/app/src/themes/default_themes.rs b/app/src/themes/default_themes.rs index 7762593e4b..71cbfe59e6 100644 --- a/app/src/themes/default_themes.rs +++ b/app/src/themes/default_themes.rs @@ -1,10 +1,9 @@ -use asset_macro::bundled_or_fetched_asset; use pathfinder_color::ColorU; use warp_core::ui::{ color::{blend::Blend, coloru_with_opacity, OPAQUE}, theme::{ - color::CustomDetails, AnsiColor, AnsiColors, Details, Fill, HorizontalGradient, Image, - TerminalColors, VerticalGradient, WarpTheme, + AnsiColor, AnsiColors, Details, Fill, HorizontalGradient, TerminalColors, + VerticalGradient, WarpTheme, }, }; use warp_core::ui::theme::ui_colors::UiColors; @@ -114,27 +113,6 @@ const DRACULA_BRIGHT_COLORS: AnsiColors = AnsiColors::new( AnsiColor::from_u32(0xFFFFFFFF), ); -const PHENOMENON_NORMAL_COLORS: AnsiColors = AnsiColors::new( - AnsiColor::from_u32(0x121212FF), - AnsiColor::from_u32(0xD22D1EFF), - AnsiColor::from_u32(0x1CA05AFF), - AnsiColor::from_u32(0xE5A01AFF), - AnsiColor::from_u32(0x3780E9FF), - AnsiColor::from_u32(0xBF409DFF), - AnsiColor::from_u32(0x799C92FF), - AnsiColor::from_u32(0xFAF9F6FF), -); -const PHENOMENON_BRIGHT_COLORS: AnsiColors = AnsiColors::new( - AnsiColor::from_u32(0x292929FF), - AnsiColor::from_u32(0xAE756FFF), - AnsiColor::from_u32(0x789B88FF), - AnsiColor::from_u32(0xBD9F65FF), - AnsiColor::from_u32(0x6F839FFF), - AnsiColor::from_u32(0xA57899FF), - AnsiColor::from_u32(0xBFC5C3FF), - AnsiColor::from_u32(0xFFFFFFFF), -); - const GRUVBOX_DARK_NORMAL_COLORS: AnsiColors = AnsiColors::new( AnsiColor::from_u32(0x282828FF), AnsiColor::from_u32(0xCC241DFF), @@ -177,27 +155,6 @@ const GRUVBOX_LIGHT_BRIGHT_COLORS: AnsiColors = AnsiColors::new( AnsiColor::from_u32(0x3C3836FF), ); -const SOLARFLARE_NORMAL_COLORS: AnsiColors = AnsiColors::new( - AnsiColor::from_u32(0x2E333DFF), - AnsiColor::from_u32(0xD66060FF), - AnsiColor::from_u32(0x64AF86FF), - AnsiColor::from_u32(0xCAA358FF), - AnsiColor::from_u32(0x5C80B2FF), - AnsiColor::from_u32(0xB766A1FF), - AnsiColor::from_u32(0x8069A1FF), - AnsiColor::from_u32(0xF0F4F7FF), -); -const SOLARFLARE_BRIGHT_COLORS: AnsiColors = AnsiColors::new( - AnsiColor::from_u32(0x37404AFF), - AnsiColor::from_u32(0xEB8282FF), - AnsiColor::from_u32(0x64AF86FF), - AnsiColor::from_u32(0xCAA358FF), - AnsiColor::from_u32(0x5C80B2FF), - AnsiColor::from_u32(0xB766A1FF), - AnsiColor::from_u32(0x8069A1FF), - AnsiColor::from_u32(0xFFFFFFFF), -); - const TOKYO_NIGHT_NORMAL_COLORS: AnsiColors = AnsiColors::new( AnsiColor::from_u32(0x15161EFF), AnsiColor::from_u32(0xF7768EFF), @@ -310,7 +267,7 @@ pub(super) fn vscode_2026_dark_colors() -> TerminalColors { } /// VS Code 2026 Dark built-in theme; color source: vscode/extensions/theme-defaults/themes/2026-dark.json. -/// Includes complete UiColors coverage, mapping VS Code's editor/panel colors to Zap UI components. +/// Includes complete UiColors coverage, mapping VS Code's editor/panel colors to Zaplex UI components. pub(super) fn vscode_2026_dark() -> WarpTheme { WarpTheme::new( Fill::Solid(ColorU::from_u32(0x191A1BFF)), @@ -364,10 +321,6 @@ pub(super) fn dracula_colors() -> TerminalColors { TerminalColors::new(DRACULA_NORMAL_COLORS, DRACULA_BRIGHT_COLORS) } -pub(super) fn phenomenon_colors() -> TerminalColors { - TerminalColors::new(PHENOMENON_NORMAL_COLORS, PHENOMENON_BRIGHT_COLORS) -} - pub(super) fn gruvbox_dark_colors() -> TerminalColors { TerminalColors::new(GRUVBOX_DARK_NORMAL_COLORS, GRUVBOX_DARK_BRIGHT_COLORS) } @@ -376,10 +329,6 @@ pub(super) fn gruvbox_light_colors() -> TerminalColors { TerminalColors::new(GRUVBOX_LIGHT_NORMAL_COLORS, GRUVBOX_LIGHT_BRIGHT_COLORS) } -pub(super) fn solarflare_colors() -> TerminalColors { - TerminalColors::new(SOLARFLARE_NORMAL_COLORS, SOLARFLARE_BRIGHT_COLORS) -} - pub(super) fn adeberry_colors() -> TerminalColors { TerminalColors::new(ADEBERRY_NORMAL_COLORS, ADEBERRY_BRIGHT_COLORS) } @@ -556,206 +505,19 @@ pub(super) fn fancy_dracula() -> WarpTheme { ) } -pub(super) fn phenomenon() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0x121212FF)), - ColorU::from_u32(0xFAF9F6FF), - Fill::Solid(ColorU::from_u32(0x2E5D9EFF)), - None, - Some(Details::Darker), - phenomenon_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/phenomenon_bg.jpg"), - opacity: 100, - }), - Some("Phenomenon".to_string()), - None, - ) -} - -/// Bundled themes with background images -pub(super) fn jellyfish() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0x1B1718FF)), - ColorU::white(), - Fill::Solid(ColorU::from_u32(0x538682FF)), - None, - Some(Details::Darker), - dark_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/jellyfish_bg.jpg"), - opacity: 30, - }), - Some("Jellyfish".to_string()), - None, - ) -} - -pub(super) fn koi() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0x211719FF)), - ColorU::white(), - Fill::Solid(ColorU::from_u32(0xFF3131FF)), - None, - Some(Details::Darker), - dark_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/koi_bg.jpg"), - opacity: 30, - }), - Some("Koi".to_string()), - None, - ) -} - -pub(super) fn leafy() -> WarpTheme { +/// Zaplex Dark - the default theme. Deep navy background with the blue->purple +/// accent from the zaplex splash screen; uses the well-tuned Tokyo Night ANSI +/// palette for readable terminal colors. Solid fill, no background image. +pub(super) fn zaplex_dark() -> WarpTheme { WarpTheme::new( - Fill::Solid(ColorU::black()), - ColorU::white(), - Fill::Solid(ColorU::from_u32(0x55972DFF)), + Fill::Solid(ColorU::from_u32(0x0E1320FF)), + ColorU::from_u32(0xE6EAF3FF), + Fill::Solid(ColorU::from_u32(0x6C82F2FF)), None, Some(Details::Darker), - dark_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/leafy_bg.jpg"), - opacity: 30, - }), - Some("Leafy".to_string()), - None, - ) -} - -pub(super) fn marble() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0xE3E3E3FF)), - ColorU::black(), - Fill::Solid(ColorU::from_u32(0x585858FF)), - None, - Some(Details::Lighter), - light_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/marble_bg.jpg"), - opacity: 50, - }), - Some("Marble".to_string()), - None, - ) -} - -pub(super) fn pink_city() -> WarpTheme { - let details = CustomDetails { - ..CustomDetails::lighter_details() - }; - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0xFBEFF6FF)), - ColorU::black(), - Fill::Solid(ColorU::from_u32(0xE10087FF)), - None, - Some(Details::Custom(details)), - light_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/pink_city_bg.jpg"), - opacity: 40, - }), - Some("Pink City".to_string()), - None, - ) -} - -pub(super) fn snowy() -> WarpTheme { - WarpTheme::new( - Fill::VerticalGradient(VerticalGradient::new( - ColorU::from_u32(0xFFFFFFFF), - ColorU::from_u32(0xDEE6EBFF), - )), - ColorU::black(), - Fill::Solid(ColorU::from_u32(0x647E90FF)), - None, - Some(Details::Lighter), - light_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/snowy_bg.jpg"), - opacity: 20, - }), - Some("Snowy".to_string()), - None, - ) -} - -pub(super) fn red_rock() -> WarpTheme { - WarpTheme::new( - Fill::VerticalGradient(VerticalGradient::new( - ColorU::from_u32(0x211719FF) - .blend(&coloru_with_opacity(ColorU::from_u32(0x4C3435FF), 45)), - ColorU::from_u32(0x211719FF) - .blend(&coloru_with_opacity(ColorU::from_u32(0xD3032FF), 45)), - )), - ColorU::white(), - Fill::Solid(ColorU::from_u32(0x9F4147FF)), - None, - Some(Details::Darker), - dark_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/red_rock_bg.jpg"), - opacity: 30, - }), - Some("Red Rock".to_string()), - None, - ) -} - -pub(super) fn dark_city() -> WarpTheme { - WarpTheme::new( - Fill::VerticalGradient(VerticalGradient::new( - ColorU::from_u32(0x01181FFF) - .blend(&coloru_with_opacity(ColorU::from_u32(0x1A363FFF), 45)), - ColorU::from_u32(0x01181FFF) - .blend(&coloru_with_opacity(ColorU::from_u32(0x1A4551FF), 45)), - )), - ColorU::white(), - Fill::Solid(ColorU::from_u32(0xE9072DFF)), - None, - Some(Details::Darker), - dark_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/dark_city_bg.jpg"), - opacity: 20, - }), - Some("Dark City".to_string()), - None, - ) -} - -pub(super) fn sent_referral_reward() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0x334567FF)), - ColorU::white(), - Fill::Solid(ColorU::from_u32(0xCD51FFFF)), - None, - Some(Details::Darker), - dark_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/sent_referral_reward_bg.jpg"), - opacity: 100, - }), - Some("Zap Referral".to_string()), - None, - ) -} - -pub(super) fn solar_flare() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0x1B1C18FF)), - ColorU::from_u32(0xDDE6EEFF), - Fill::Solid(ColorU::from_u32(0x34895CFF)), + tokyo_night_colors(), None, - Some(Details::Darker), - solarflare_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/solarflare_bg.jpg"), - opacity: 20, - }), - Some("Solar Flare".to_string()), + Some("Zaplex Dark".to_string()), None, ) } @@ -817,20 +579,3 @@ pub(super) fn wezterm_classic() -> WarpTheme { None, ) } - -pub(super) fn received_referral_reward() -> WarpTheme { - WarpTheme::new( - Fill::Solid(ColorU::from_u32(0xFFFFFFFF)), - ColorU::black(), - Fill::Solid(ColorU::from_u32(0xCD51FFFF)), - None, - Some(Details::Lighter), - light_mode_colors(), - Some(Image { - source: bundled_or_fetched_asset!("jpg/received_referral_reward_bg.jpg"), - opacity: 100, - }), - Some("Received Referral Reward".to_string()), - None, - ) -} diff --git a/app/src/themes/mod.rs b/app/src/themes/mod.rs index 1f4de56182..31e63adb25 100644 --- a/app/src/themes/mod.rs +++ b/app/src/themes/mod.rs @@ -11,7 +11,7 @@ use warp_core::ui::theme::WarpTheme; pub fn onboarding_theme_picker_themes() -> [WarpTheme; 4] { [ - default_themes::phenomenon(), + default_themes::zaplex_dark(), default_themes::dark_theme(), default_themes::light_theme(), default_themes::adeberry(), diff --git a/app/src/themes/theme.rs b/app/src/themes/theme.rs index 0ee8cf28ac..0b83a99007 100644 --- a/app/src/themes/theme.rs +++ b/app/src/themes/theme.rs @@ -40,18 +40,11 @@ const THUMBNAIL_MARGIN: f32 = 10.; )] #[schemars(description = "The color theme.", rename_all = "snake_case")] pub enum ThemeKind { - // Need an alias for backwards-compatibility: Originally we only had a single reward theme - // so it was named `ReferralReward`. - #[serde(alias = "ReferralReward")] - #[schemars(skip)] - SentReferralReward, - #[schemars(skip)] - ReceivedReferralReward, + #[default] + #[schemars(description = "Zaplex Dark")] + ZaplexDark, #[schemars(description = "Adeberry")] Adeberry, - #[schemars(description = "Phenomenon")] - Phenomenon, - #[default] #[schemars(description = "Dark")] Dark, #[schemars(description = "Dracula")] @@ -64,40 +57,22 @@ pub enum ThemeKind { FancyDracula, #[schemars(description = "Cyber Wave")] CyberWave, - #[schemars(description = "Solar Flare")] - SolarFlare, #[schemars(description = "Solarized Dark")] SolarizedDark, #[schemars(description = "Willow Dream")] WillowDream, #[schemars(description = "Light")] Light, - #[schemars(description = "Dark City")] - DarkCity, #[schemars(description = "Gruvbox Dark")] GruvboxDark, - #[schemars(description = "Red Rock")] - RedRock, - #[schemars(description = "Jellyfish")] - JellyFish, - #[schemars(description = "Leafy")] - Leafy, #[schemars(description = "WezTerm Classic")] WezTermClassic, #[schemars(description = "VS Code 2026 Dark")] VsCode2026Dark, - #[schemars(description = "Koi")] - Koi, #[schemars(description = "Solarized Light")] SolarizedLight, - #[schemars(description = "Snowy")] - Snowy, #[schemars(description = "Gruvbox Light")] GruvboxLight, - #[schemars(description = "Pink City")] - PinkCity, - #[schemars(description = "Marble")] - Marble, #[schemars(description = "A user-provided custom theme loaded from a file.")] Custom(CustomTheme), /// Base16 themes are a special case of custom themes with their own semantics for ANSI colors that override "bright" color variants. @@ -120,6 +95,7 @@ impl From for ThemeKind { impl std::fmt::Display for ThemeKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let value = match &self { + ThemeKind::ZaplexDark => "Zaplex Dark", ThemeKind::Light => "Light", ThemeKind::Dark => "Dark", ThemeKind::Dracula => "Dracula", @@ -129,24 +105,12 @@ impl std::fmt::Display for ThemeKind { ThemeKind::SolarizedLight => "Solarized Light", ThemeKind::GruvboxDark => "Gruvbox Dark", ThemeKind::GruvboxLight => "Gruvbox Light", - ThemeKind::JellyFish => "Jellyfish", - ThemeKind::Koi => "Koi", - ThemeKind::Leafy => "Leafy", - ThemeKind::Marble => "Marble", - ThemeKind::PinkCity => "Pink City", - ThemeKind::Snowy => "Snowy", - ThemeKind::DarkCity => "Dark City", - ThemeKind::RedRock => "Red Rock", ThemeKind::CyberWave => "Cyber Wave", ThemeKind::WillowDream => "Willow Dream", ThemeKind::FancyDracula => "Fancy Dracula", - ThemeKind::Phenomenon => "Phenomenon", - ThemeKind::SolarFlare => "Solar Flare", ThemeKind::Adeberry => "Adeberry", ThemeKind::WezTermClassic => "WezTerm Classic", ThemeKind::VsCode2026Dark => "VS Code 2026 Dark", - ThemeKind::SentReferralReward => "Zap Referral", - ThemeKind::ReceivedReferralReward => "Referred to Zap", ThemeKind::Custom(custom_theme) => custom_theme.name.as_str(), ThemeKind::CustomBase16(custom_theme) => custom_theme.name.as_str(), ThemeKind::InMemory(in_memory_theme) => in_memory_theme.name.as_str(), @@ -308,11 +272,7 @@ impl WarpThemeConfig { pub fn new() -> Self { // preload with built-in themes let theme_map: HashMap = HashMap::from_iter([ - (ThemeKind::SentReferralReward, sent_referral_reward()), - ( - ThemeKind::ReceivedReferralReward, - received_referral_reward(), - ), + (ThemeKind::ZaplexDark, zaplex_dark()), (ThemeKind::Dark, dark_theme()), (ThemeKind::Light, light_theme()), (ThemeKind::SolarizedDark, solarized_dark()), @@ -322,19 +282,9 @@ impl WarpThemeConfig { (ThemeKind::OneDark, one_dark()), (ThemeKind::GruvboxDark, gruvbox_dark()), (ThemeKind::GruvboxLight, gruvbox_light()), - (ThemeKind::JellyFish, jellyfish()), - (ThemeKind::Koi, koi()), - (ThemeKind::Leafy, leafy()), - (ThemeKind::Marble, marble()), - (ThemeKind::PinkCity, pink_city()), - (ThemeKind::Snowy, snowy()), - (ThemeKind::DarkCity, dark_city()), - (ThemeKind::RedRock, red_rock()), (ThemeKind::CyberWave, cyber_wave()), (ThemeKind::WillowDream, willow_dream()), (ThemeKind::FancyDracula, fancy_dracula()), - (ThemeKind::Phenomenon, phenomenon()), - (ThemeKind::SolarFlare, solar_flare()), (ThemeKind::Adeberry, adeberry()), (ThemeKind::WezTermClassic, wezterm_classic()), (ThemeKind::VsCode2026Dark, vscode_2026_dark()), diff --git a/app/src/themes/theme_chooser.rs b/app/src/themes/theme_chooser.rs index 9337b540c8..7c02cf2c27 100644 --- a/app/src/themes/theme_chooser.rs +++ b/app/src/themes/theme_chooser.rs @@ -173,7 +173,7 @@ pub fn init(app: &mut AppContext) { } fn theme_chooser_items(theme_config: &WarpThemeConfig) -> Vec { - // Zap decentralization: no more referral concept, all themes (including the two originally referral-gated themes) + // Zaplex decentralization: no more referral concept, all themes (including the two originally referral-gated themes) // are always visible to local users. let mut theme_items: Vec = theme_config .theme_items() diff --git a/app/src/ui_components/menu_button.rs b/app/src/ui_components/menu_button.rs index 3b123e9064..df5eb47c7a 100644 --- a/app/src/ui_components/menu_button.rs +++ b/app/src/ui_components/menu_button.rs @@ -87,7 +87,7 @@ where button_with_menu } -/// Variant with surface_1 hover background for Zap Drive items +/// Variant with surface_1 hover background for Zaplex Drive items #[allow(clippy::too_many_arguments)] pub fn icon_button_with_context_menu_drive( icon: Icon, @@ -123,7 +123,7 @@ where button_with_menu } -/// Variant with surface_1 hover background for Zap Drive items (highlighted) +/// Variant with surface_1 hover background for Zaplex Drive items (highlighted) pub fn highlight_icon_button_with_context_menu_drive( icon: Icon, on_click_action: F, diff --git a/app/src/ui_components/mod.rs b/app/src/ui_components/mod.rs index c8473aa0b6..8927277913 100644 --- a/app/src/ui_components/mod.rs +++ b/app/src/ui_components/mod.rs @@ -1,6 +1,6 @@ -//! Zap UI Components module contains functions and structs that implement our internal components +//! Zaplex UI Components module contains functions and structs that implement our internal components //! used for the apps design (our buttons with styling, headers and panels etc.) as well definition -//! of colors (aka blended colors from the figma designs derived from Zap theme) and icons used +//! of colors (aka blended colors from the figma designs derived from Zaplex theme) and icons used //! within the app. pub(crate) mod avatar; pub(crate) mod blended_colors; diff --git a/app/src/uri/browser_url_handler.rs b/app/src/uri/browser_url_handler.rs index 8b37de07cf..d922b9f92e 100644 --- a/app/src/uri/browser_url_handler.rs +++ b/app/src/uri/browser_url_handler.rs @@ -1,6 +1,6 @@ use url::Url; -const DEFAULT_TITLE: &str = "Zap"; +const DEFAULT_TITLE: &str = "Zaplex"; const BASE_APP_PATH: &str = "/app"; pub fn update_browser_url(url: Option, force_redirect: bool) { diff --git a/app/src/uri/docker.rs b/app/src/uri/docker.rs index 59d2a7813e..b890a42580 100644 --- a/app/src/uri/docker.rs +++ b/app/src/uri/docker.rs @@ -28,7 +28,7 @@ impl TryFrom for DockerContainerId { )) } else if input.chars().any(|c| !c.is_ascii_hexdigit()) { Err(anyhow!( - "Could not find valid docker container id to open warpified shell" + "Could not find valid docker container id to open zaplexified shell" )) } else { Ok(DockerContainerId(input)) @@ -44,7 +44,7 @@ impl Display for DockerContainerId { /// Given a Url with query parameters in the correct format, dispatch an action to create a new tab /// (or open a new window if there is no window), then run a command to open a subshell into the -/// specified Docker container, and then warpify that new subshell. +/// specified Docker container, and then zaplexify that new subshell. pub fn open_docker_container(url: &Url, ctx: &mut AppContext) -> Result<()> { let query_params: HashMap = url .query_pairs() @@ -109,7 +109,7 @@ pub fn open_docker_container(url: &Url, ctx: &mut AppContext) -> Result<()> { ); send_telemetry_from_app_ctx!( - TelemetryEvent::OpenAndWarpifyDockerSubshell { shell_type }, + TelemetryEvent::OpenAndZaplexifyDockerSubshell { shell_type }, ctx ); diff --git a/app/src/uri/mod.rs b/app/src/uri/mod.rs index 6070e04fc3..6bebca9871 100644 --- a/app/src/uri/mod.rs +++ b/app/src/uri/mod.rs @@ -6,7 +6,7 @@ pub mod web_intent_parser; pub mod browser_url_handler; use crate::ai::agent::api::ServerConversationToken; -use crate::drive::ZapDriveObjectSettings; +use crate::drive::ZaplexDriveObjectSettings; use crate::launch_configs::launch_config::LaunchConfig; use crate::linear::{LinearAction, LinearIssueWork}; use crate::root_view::{open_new_window_get_handles, OpenLaunchConfigArg}; @@ -18,7 +18,7 @@ use crate::util::openable_file_type::{ use crate::workspace::active_terminal_in_window; use crate::workspace::{Workspace, WorkspaceAction, WorkspaceRegistry}; use crate::{cloud_object::ObjectType, workspace::ToastStack}; -use crate::{drive::ZapDriveObjectArgs, view_components::DismissibleToast}; +use crate::{drive::ZaplexDriveObjectArgs, view_components::DismissibleToast}; use crate::ai::ambient_agents::github_auth_notifier::GitHubAuthNotifier; use crate::settings_view::SettingsSection; @@ -49,7 +49,7 @@ pub struct OpenMCPSettingsArgs { } /// Source query parameter value indicating auth was initiated from agent setup. -/// Zap Wave 7-3: URI handler / Settings UI removed; only used by `update_environment_form` as a transitional bridge +/// Zaplex Wave 7-3: URI handler / Settings UI removed; only used by `update_environment_form` as a transitional bridge /// during agent UI refactoring (the latter doesn't use the URL it constructs anyway). pub const CLOUD_SETUP_SOURCE: &str = "cloud_setup"; @@ -103,8 +103,8 @@ impl UriHost { match self { UriHost::Auth => { safe_info!( - safe: ("Ignored auth URL because Zap has no cloud login flow"), - full: ("Ignored auth URL {url} because Zap has no cloud login flow") + safe: ("Ignored auth URL because Zaplex has no cloud login flow"), + full: ("Ignored auth URL {url} because Zaplex has no cloud login flow") ); } UriHost::Action => { @@ -203,10 +203,10 @@ impl UriHost { ctx.root_view_id(window_id) .map(|view_id| (window_id, view_id)) }); - let args = ZapDriveObjectArgs { + let args = ZaplexDriveObjectArgs { object_type, server_id, - settings: ZapDriveObjectSettings { + settings: ZaplexDriveObjectSettings { focused_folder_id, invitee_email, }, @@ -255,7 +255,7 @@ impl UriHost { if let Some(settings_sub_page) = settings_sub_page { match settings_sub_page.as_str() { "environments" => { - // Zap Wave 7-3: warp://settings/environments URI handler removed along with ambient-agent UI subsystem. + // Zaplex Wave 7-3: warp://settings/environments URI handler removed along with ambient-agent UI subsystem. // Still retaining GitHub auth completion notification — other independent components may need to listen. GitHubAuthNotifier::handle(ctx).update(ctx, |notifier, ctx| { notifier.notify_auth_completed(ctx); @@ -276,12 +276,12 @@ impl UriHost { ctx, ); } - // Zap Wave 3-1: "platform" URI route originally pointed to + // Zaplex Wave 3-1: "platform" URI route originally pointed to // `SettingsSection::OzCloudAPIKeys` (cloud API key management page), // physically removed along with the UI. Arm retained to record original intent, physically handled as no-op. "platform" => { log::warn!( - "warp://settings/platform route is retired in Zap; ignoring request" + "warp://settings/platform route is retired in Zaplex; ignoring request" ); } "appearance" => { @@ -659,7 +659,7 @@ impl Action { Self::Docker | Self::OpenRepo | Self::NewAgentConversation => W::default(), Self::NewTab => W::ShowPrimaryWindow(WindowActivationFallbackBehavior::Notify { title: "New tab created".to_owned(), - description: "Go to Zap to see your new tab.".to_owned(), + description: "Go to Zaplex to see your new tab.".to_owned(), }), Self::NewWindow => W::Nothing, } @@ -743,7 +743,7 @@ fn get_primary_window( enum OpenFileAction { /// Open in the markdown notebook pane. Notebook, - /// Open in Zap's code/text editor pane. + /// Open in Zaplex's code/text editor pane. Editor, /// Open a session at the parent directory and queue the file as the pending command, /// or just open a session at the directory path if `path` is a directory. @@ -805,7 +805,7 @@ fn open_file(window_id: Option, path: PathBuf, ctx: &mut AppContext) { openable_file_type::resolve_file_target_to_open_in_warp, }; - // Open text/code files in Zap's code editor, respecting the user's layout preference. + // Open text/code files in Zaplex's code editor, respecting the user's layout preference. let editor_settings = EditorSettings::as_ref(ctx); let target = resolve_file_target_to_open_in_warp(&path, editor_settings, None); diff --git a/app/src/uri/parse_url_paths.rs b/app/src/uri/parse_url_paths.rs index f8c1439231..4fa86e631b 100644 --- a/app/src/uri/parse_url_paths.rs +++ b/app/src/uri/parse_url_paths.rs @@ -1,12 +1,12 @@ use crate::cloud_object::extract_server_id_and_object_type_from_warp_drive_link; -use crate::drive::ZapDriveObjectArgs; +use crate::drive::ZaplexDriveObjectArgs; use crate::ChannelState; use url::Url; #[derive(PartialEq, Debug)] pub enum WarpWebLink { Session, - DriveObject(Box), + DriveObject(Box), } pub fn get_item_data_from_warp_link(url: &Url) -> Option { diff --git a/app/src/uri/web_intent_parser.rs b/app/src/uri/web_intent_parser.rs index cd6fb2f7d7..8fb1627de7 100644 --- a/app/src/uri/web_intent_parser.rs +++ b/app/src/uri/web_intent_parser.rs @@ -122,8 +122,8 @@ impl WebIntent { } } -/// Attempts to rewrite a Zap web URL into a native desktop intent URL (warp://...). -/// Returns `None` if the URL is not a recognized Zap web intent. +/// Attempts to rewrite a Zaplex web URL into a native desktop intent URL (warp://...). +/// Returns `None` if the URL is not a recognized Zaplex web intent. pub fn maybe_rewrite_web_url_to_intent(url: &Url) -> Option { WebIntent::try_from_url(url) .ok() diff --git a/app/src/user_config/mod.rs b/app/src/user_config/mod.rs index 6b9667d7f2..4310f53b71 100644 --- a/app/src/user_config/mod.rs +++ b/app/src/user_config/mod.rs @@ -25,7 +25,7 @@ pub use imp::{load_launch_configs, load_theme_configs}; lazy_static! { pub static ref LAUNCH_CONFIG_COMMENT: String = format!( - "# Zap Launch Configuration + "# Zaplex Launch Configuration # # # Use this to start a certain configuration of windows, tabs, and panes. @@ -193,13 +193,13 @@ pub fn tab_configs_dir() -> PathBuf { } /// Returns the path to the directory containing the built-in default tab configs. -/// These are shipped with Zap and user-editable (Zap does not overwrite modifications). +/// These are shipped with Zaplex and user-editable (Zaplex does not overwrite modifications). #[cfg_attr(target_family = "wasm", expect(dead_code))] pub fn default_tab_configs_dir() -> PathBuf { base_dir().join("default_tab_configs") } -/// Returns whether the path points to a tab config TOML file under one of Zap's +/// Returns whether the path points to a tab config TOML file under one of Zaplex's /// tab config directories. #[cfg(feature = "local_fs")] pub fn is_tab_config_toml(path: &Path) -> bool { diff --git a/app/src/util/bindings.rs b/app/src/util/bindings.rs index 8d70d5a38b..cf627e3b1e 100644 --- a/app/src/util/bindings.rs +++ b/app/src/util/bindings.rs @@ -86,7 +86,7 @@ pub enum CustomAction { CopyBlock, CopyBlockCommand, CopyBlockOutput, - // Zap Wave 6-8: `ViewSharedBlocks` physically deleted along with `ShowBlocksView` settings page + // Zaplex Wave 6-8: `ViewSharedBlocks` physically deleted along with `ShowBlocksView` settings page // and `workspace:show_settings_shared_blocks_page` keybinding. CloseTab, CloseOtherTabs, @@ -116,9 +116,9 @@ pub enum CustomAction { WindowsPaste, #[cfg(windows)] WindowsCopy, - /// Also applies to legacy Zap AI (toggles the panel) + /// Also applies to legacy Zaplex AI (toggles the panel) NewAgentModePane, - /// Also applies to legacy Zap AI (attaches the selection to the panel editor) + /// Also applies to legacy Zaplex AI (attaches the selection to the panel editor) AttachSelectionAsAgentModeContext, OpenAIFactCollection, OpenMCPServerCollection, @@ -135,8 +135,8 @@ pub enum CustomAction { lazy_static! { /// Maps for converting from custom tags back to the action enum /// This layer of indirection is necessary because the UI framework can't - /// know about particular Zap specific actions, so it deals with all actions - /// as plain isizes. Within Zap though we want to deal with them as the enum type. + /// know about particular Zaplex specific actions, so it deals with all actions + /// as plain isizes. Within Zaplex though we want to deal with them as the enum type. pub static ref CUSTOM_TAG_TO_ACTION: HashMap = HashMap::from_iter(all::().map(|action| { (action as isize, action) })); @@ -196,7 +196,7 @@ lazy_static! { /// compliant. We weren't always diligent about avoiding bindings that could conflict with /// character codes, unfortunately some bindings on Mac currently conflict with the PTY. We have /// this allowlist to special case these legacy actions for the purposes of binding validation. - pub static ref MAC_PTY_NON_COMPLIANT_ACTIONS: HashSet<&'static str> = HashSet::from_iter(["terminal:warpify_subshell", "terminal:open_block_list_context_menu_via_keybinding"]); + pub static ref MAC_PTY_NON_COMPLIANT_ACTIONS: HashSet<&'static str> = HashSet::from_iter(["terminal:zaplexify_subshell", "terminal:open_block_list_context_menu_via_keybinding"]); /// Set of actions on Windows that should be considered valid bindings even though they aren't /// PTY compliant. Windows users expect pasting to work using both `ctrl-v` and `ctrl-shift-v`, diff --git a/app/src/util/file.rs b/app/src/util/file.rs index c16a4d4f5f..9776a9cfae 100644 --- a/app/src/util/file.rs +++ b/app/src/util/file.rs @@ -30,7 +30,7 @@ pub enum ShellPathType { PlatformNative(PathBuf), } -/// Zap: Snapshot of real items in a remote directory (cwd). +/// Zaplex: Snapshot of real items in a remote directory (cwd). /// /// Populated with results returned by the daemon's `ListDirectory` RPC. The terminal link detector /// uses this for precise validation in remote sessions: it extracts the actual filename from candidate @@ -49,7 +49,7 @@ impl RemoteDirListing { } } -/// Zap: Validation source for terminal file links. +/// Zaplex: Validation source for terminal file links. /// /// Local sessions use the local filesystem `fs::metadata` to check if a path exists; /// remote SSH (remote-server) session files are not on the local disk, so local validation @@ -132,7 +132,7 @@ fn is_path_valid( return false; } - // Zap: Remote SSH session files are not on the local disk, so `fs::metadata` will necessarily fail. + // Zaplex: Remote SSH session files are not on the local disk, so `fs::metadata` will necessarily fail. // Use the real directory list cached from daemon `ListDirectory` for precise validation: // a candidate parsed path is valid ⇔ its parent directory exactly equals the cached cwd // and its filename is a known child item in that directory. @@ -165,7 +165,7 @@ fn is_path_valid( metadata.is_file() || (metadata.is_dir() && clean_path_result.line_and_column_num.is_none()) } -/// Zap: Determines whether a parsed remote path points to a directory. +/// Zaplex: Determines whether a parsed remote path points to a directory. /// /// Only called when clicking a link in a remote session and needing to decide /// "open file vs `cd` into directory"; the basis is the cached remote cwd directory list. diff --git a/app/src/util/file/external_editor/linux_tests.rs b/app/src/util/file/external_editor/linux_tests.rs index 862a8e80ac..7ad2bb3a00 100644 --- a/app/src/util/file/external_editor/linux_tests.rs +++ b/app/src/util/file/external_editor/linux_tests.rs @@ -173,7 +173,7 @@ fn test_remaining_substitutions() { Version=1.0 Type=Application Exec=echo %c && echo %i && echo %k && echo %% - Name=Zap Test Application + Name=Zaplex Test Application Icon=/foo/bar/icon.png "#; with_files("test_remaining_substitutions", data, |desktop, content| { @@ -184,13 +184,13 @@ fn test_remaining_substitutions() { assert!(result.is_ok()); // When constructing a command from argv, each token is an independent argument. - // %c → "Zap Test Application" (single argument, spaces preserved) + // %c → "Zaplex Test Application" (single argument, spaces preserved) // %i → "--icon" and "/foo/bar/icon.png" (two independent arguments) // %k → desktop file path // %% → "%" let cmd = result.unwrap(); let args: Vec<_> = cmd.get_args().collect(); - assert_eq!(args[0], "Zap Test Application"); + assert_eq!(args[0], "Zaplex Test Application"); assert_eq!(args[1], "&&"); assert_eq!(args[2], "echo"); assert_eq!(args[3], "--icon"); diff --git a/app/src/util/file/external_editor/mac.rs b/app/src/util/file/external_editor/mac.rs index 1957ac9893..6cbcc33c2a 100644 --- a/app/src/util/file/external_editor/mac.rs +++ b/app/src/util/file/external_editor/mac.rs @@ -347,8 +347,8 @@ pub fn open_file_path_with_line_and_col( } // NSWorkspace's default-app routing can hand files to a sibling - // Zap channel (e.g. Stable handling files while Preview is running). - // When the resolved default is a different Zap, open with the + // Zaplex channel (e.g. Stable handling files while Preview is running). + // When the resolved default is a different Zaplex, open with the // running channel's bundle directly. let bundle_id = unsafe { default_app_to_open_path(full_path) }; if let Some(bundle_id) = bundle_id.as_deref() { diff --git a/app/src/util/file/external_editor/mac_test.rs b/app/src/util/file/external_editor/mac_test.rs index 94a78fe729..5e4368d615 100644 --- a/app/src/util/file/external_editor/mac_test.rs +++ b/app/src/util/file/external_editor/mac_test.rs @@ -2,10 +2,10 @@ use super::is_zap_bundle; #[test] fn is_zap_bundle_recognises_zap_channels() { - // OSS (Zap) itself. - assert!(is_zap_bundle("dev.zap.Zap")); + // OSS (Zaplex) itself. + assert!(is_zap_bundle("dev.zaplex.Zaplex")); // Upstream Warp channels — also considered part of this app family, allowing default-app redirection. - assert!(is_zap_bundle("dev.warp.Zap")); + assert!(is_zap_bundle("dev.warp.Zaplex")); assert!(is_zap_bundle("dev.warp.WarpDev")); assert!(is_zap_bundle("dev.warp.WarpPreview")); assert!(is_zap_bundle("dev.warp.WarpOss")); diff --git a/app/src/util/file/external_editor/settings.rs b/app/src/util/file/external_editor/settings.rs index 9b75e0ce00..cdbbae2039 100644 --- a/app/src/util/file/external_editor/settings.rs +++ b/app/src/util/file/external_editor/settings.rs @@ -20,7 +20,7 @@ use settings::{ )] pub enum EditorChoice { SystemDefault, - Zap, + Zaplex, EnvEditor, #[schemars(description = "A specific external code editor.")] ExternalEditor(super::Editor), @@ -45,7 +45,7 @@ impl<'de> Deserialize<'de> for EditorChoice { #[derive(Deserialize)] enum EditorChoiceInner { SystemDefault, - Zap, + Zaplex, EnvEditor, ExternalEditor(super::Editor), } @@ -53,7 +53,7 @@ impl<'de> Deserialize<'de> for EditorChoice { match EditorChoiceCompat::deserialize(deserializer)? { EditorChoiceCompat::New(inner) => match inner { EditorChoiceInner::SystemDefault => Ok(EditorChoice::SystemDefault), - EditorChoiceInner::Zap => Ok(EditorChoice::Zap), + EditorChoiceInner::Zaplex => Ok(EditorChoice::Zaplex), EditorChoiceInner::EnvEditor => Ok(EditorChoice::EnvEditor), EditorChoiceInner::ExternalEditor(editor) => { Ok(EditorChoice::ExternalEditor(editor)) @@ -80,7 +80,7 @@ define_settings_group!(EditorSettings, settings: [ }, open_code_panels_file_editor: OpenCodePanelsFileEditor { type: EditorChoice, - default: EditorChoice::Zap, + default: EditorChoice::Zaplex, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: false, diff --git a/app/src/util/links.rs b/app/src/util/links.rs index f0150387fe..7407e32652 100644 --- a/app/src/util/links.rs +++ b/app/src/util/links.rs @@ -1,7 +1,7 @@ use crate::channel::ChannelState; -// Upstream Warp's documentation site/Slack/privacy policy are no longer applicable to the Zap fork. -// These constants are retained as placeholder empty strings, to be filled once Zap's own channels are established. +// Upstream Warp's documentation site/Slack/privacy policy are no longer applicable to the Zaplex fork. +// These constants are retained as placeholder empty strings, to be filled once Zaplex's own channels are established. // `ctx.open_url("")` is a harmless no-op at the UI call site. pub const USER_DOCS_URL: &str = ""; #[cfg_attr(not(target_os = "macos"), allow(dead_code))] diff --git a/app/src/util/openable_file_type.rs b/app/src/util/openable_file_type.rs index e9eb4037a2..3d5cc0fce4 100644 --- a/app/src/util/openable_file_type.rs +++ b/app/src/util/openable_file_type.rs @@ -1,4 +1,4 @@ -//! File type detection utilities for determining if files can be opened in Zap. +//! File type detection utilities for determining if files can be opened in Zaplex. #[cfg(feature = "local_fs")] use crate::util::file::external_editor::{settings::EditorChoice, Editor, EditorSettings}; @@ -26,7 +26,7 @@ pub enum EditorLayout { NewTab, } -/// The type of file that can be opened in Zap. The in-product treatment for "opening" a file +/// The type of file that can be opened in Zaplex. The in-product treatment for "opening" a file /// depends on its type. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OpenableFileType { @@ -41,11 +41,11 @@ pub enum OpenableFileType { /// The target application or viewer to use when opening a file. #[derive(Debug, Clone, PartialEq, Eq)] pub enum FileTarget { - /// Open in Zap's Markdown viewer. + /// Open in Zaplex's Markdown viewer. MarkdownViewer(EditorLayout), - /// Open in Zap's Code Editor. + /// Open in Zaplex's Code Editor. CodeEditor(EditorLayout), - /// Open in Zap's in-app image viewer. + /// Open in Zaplex's in-app image viewer. ImageViewer(EditorLayout), /// Open in an external editor (e.g. VS Code, Emacs). #[cfg(feature = "local_fs")] @@ -84,7 +84,7 @@ pub fn is_supported_image_file(path: impl AsRef) -> bool { } /// Returns true if `path` looks like a shell script the user intends to run when -/// "Open with Zap" is invoked from Finder/another app via a `file://` URL. +/// "Open with Zaplex" is invoked from Finder/another app via a `file://` URL. /// /// Policy: extension in {sh, bash, zsh, fish, ksh} with the user-execute bit set on Unix, /// or extension in {ps1, bat, cmd} on Windows (no x-bit concept). On Unix, files with no @@ -109,7 +109,7 @@ pub fn is_runnable_shell_script(path: &Path) -> bool { use std::os::unix::fs::PermissionsExt; // Match the documented routing policy: only the owner's execute bit counts. - // A file `chmod 070` belongs to a group, not to the user invoking Zap. + // A file `chmod 070` belongs to a group, not to the user invoking Zaplex. let has_user_x_bit = std::fs::metadata(path) .map(|m| m.permissions().mode() & 0o100 != 0) .unwrap_or(false); @@ -139,7 +139,7 @@ pub fn is_runnable_shell_script(_path: &Path) -> bool { false } -/// Determines if a file can be opened in Zap and returns its type. +/// Determines if a file can be opened in Zaplex and returns its type. /// Returns `None` if the file is binary and should not be opened. pub fn is_file_openable_in_warp(path: &Path) -> Option { if is_binary_file(path) { @@ -157,9 +157,9 @@ pub fn is_file_openable_in_warp(path: &Path) -> Option { } } -/// Only use this for UI elements that must explicitly open a file in Zap (i.e. "Open in New Tab"). +/// Only use this for UI elements that must explicitly open a file in Zaplex (i.e. "Open in New Tab"). /// Prefer `resolve_file_target` for all other cases to respect users' preferences. -/// This would also force any binary file to be opened in Zap's Code Editor, so you should likely check +/// This would also force any binary file to be opened in Zaplex's Code Editor, so you should likely check /// `is_file_openable_in_warp` before rendering any such UI Elements. #[cfg(feature = "local_fs")] pub fn resolve_file_target_to_open_in_warp( @@ -211,8 +211,8 @@ pub fn resolve_file_target_with_editor_choice( return FileTarget::MarkdownViewer(layout); } - // 2. Zap Code Editor (Explicit user preference) - if is_openable_in_warp && matches!(editor_choice, EditorChoice::Zap) { + // 2. Zaplex Code Editor (Explicit user preference) + if is_openable_in_warp && matches!(editor_choice, EditorChoice::Zaplex) { return FileTarget::CodeEditor(layout); } @@ -235,7 +235,7 @@ pub fn resolve_file_target_with_editor_choice( match editor_choice { EditorChoice::ExternalEditor(editor) => FileTarget::ExternalEditor(editor), EditorChoice::SystemDefault => FileTarget::SystemDefault, - EditorChoice::Zap | EditorChoice::EnvEditor => unreachable!("Already matched above"), + EditorChoice::Zaplex | EditorChoice::EnvEditor => unreachable!("Already matched above"), } } @@ -261,7 +261,7 @@ mod tests { assert_eq!( OpenCodePanelsFileEditor::default_value(), - EditorChoice::Zap + EditorChoice::Zaplex ); } @@ -284,7 +284,7 @@ mod tests { fn test_resolve_file_target_warp_uses_default_layout() { let target = resolve_file_target_with_editor_choice( Path::new("data.txt"), - EditorChoice::Zap, + EditorChoice::Zaplex, true, /* prefer_markdown_viewer */ EditorLayout::NewTab, None, @@ -298,7 +298,7 @@ mod tests { fn test_resolve_file_target_binary_is_system_generic() { let target = resolve_file_target_with_editor_choice( Path::new("video.mp4"), - EditorChoice::Zap, + EditorChoice::Zaplex, true, /* prefer_markdown_viewer */ EditorLayout::SplitPane, None, @@ -312,7 +312,7 @@ mod tests { fn test_resolve_file_target_image_uses_image_viewer() { let target = resolve_file_target_with_editor_choice( Path::new("photo.png"), - EditorChoice::Zap, + EditorChoice::Zaplex, true, /* prefer_markdown_viewer */ EditorLayout::NewTab, None, diff --git a/app/src/util/path_test.rs b/app/src/util/path_test.rs index ec182f196b..d435f60f3d 100644 --- a/app/src/util/path_test.rs +++ b/app/src/util/path_test.rs @@ -30,7 +30,7 @@ fn test_resolve_command() { &resolve_executable("env").unwrap(), Path::new("/usr/bin/env") ); - // This path exists in the Zap repo, so it should resolve. The `../` + // This path exists in the Zaplex repo, so it should resolve. The `../` // is because Rust unit tests run from the root of the crate (`app` in // this case). assert_eq!( diff --git a/app/src/util/sync.rs b/app/src/util/sync.rs index 5fac39a572..5c46562d1b 100644 --- a/app/src/util/sync.rs +++ b/app/src/util/sync.rs @@ -16,7 +16,7 @@ mod tests; /// /// Generally, a [condition variable](http://www.cs.cornell.edu/courses/cs3110/2012fa/recitations/rec16.html) /// lets tasks wait until some condition becomes true (for example, we might want to wait for the -/// user to have logged in, or for the initial load of Zap Drive objects to have finished). When +/// user to have logged in, or for the initial load of Zaplex Drive objects to have finished). When /// the condition becomes true, one or all of the waiting tasks can wake up and do their work. /// /// This [`Condition`] implementation models the simpler case where a condition becomes true and @@ -24,7 +24,7 @@ mod tests; /// starts waiting before the condition is met, it will block, but if the condition is already true, /// it continues immediately. /// -/// We use this in Zap Drive to wait for the initial load of changed objects to finish. Regular UI +/// We use this in Zaplex Drive to wait for the initial load of changed objects to finish. Regular UI /// framework events aren't suitable, because they don't tell us if the load had *already* /// finished - a task that subscribed too late would block forever! /// diff --git a/app/src/util/tooltips.rs b/app/src/util/tooltips.rs index add7b2349e..0c80b31c15 100644 --- a/app/src/util/tooltips.rs +++ b/app/src/util/tooltips.rs @@ -236,12 +236,12 @@ where .finish() } -/// Returns whether "Open in Zap" should be offered for the given file path. +/// Returns whether "Open in Zaplex" should be offered for the given file path. /// /// This checks: -/// - Whether Zap is already the default editor (skip if so) -/// - Whether this file is openable in Zap (skips binary files and directories) -/// - Whether Zap is an OS-level default editor (skips Markdown files) +/// - Whether Zaplex is already the default editor (skip if so) +/// - Whether this file is openable in Zaplex (skips binary files and directories) +/// - Whether Zaplex is an OS-level default editor (skips Markdown files) #[cfg(feature = "local_fs")] pub fn should_show_open_in_warp_link(path: &Path, app: &AppContext) -> bool { use crate::{ @@ -253,7 +253,7 @@ pub fn should_show_open_in_warp_link(path: &Path, app: &AppContext) -> bool { let settings = EditorSettings::as_ref(app); - if matches!(*settings.open_file_editor, EditorChoice::Zap) { + if matches!(*settings.open_file_editor, EditorChoice::Zaplex) { return false; } diff --git a/app/src/util/traffic_lights.rs b/app/src/util/traffic_lights.rs index 8b0f7e0397..1d3396408a 100644 --- a/app/src/util/traffic_lights.rs +++ b/app/src/util/traffic_lights.rs @@ -1,7 +1,7 @@ //! This module is meant to be a single source of truth for information about the windows' "traffic //! light" buttons, the minimize, maximize, and close buttons in the corner of the window, so named //! b/c of their resemblance to traffic lights on MacOS. How (whether or not) these are rendered -//! depends on the platform. The Zap app must use this information to avoid rendering UI elements +//! depends on the platform. The Zaplex app must use this information to avoid rendering UI elements //! underneath them. #[cfg(windows)] @@ -121,7 +121,7 @@ impl TrafficLightMouseStates { } } -/// Data the Zap app needs to avoid rendering anything below the traffic lights. +/// Data the Zaplex app needs to avoid rendering anything below the traffic lights. #[derive(Clone, Debug)] pub struct TrafficLightData { width: f32, diff --git a/app/src/util/windows.rs b/app/src/util/windows.rs index 5547ab7732..bb69e60102 100644 --- a/app/src/util/windows.rs +++ b/app/src/util/windows.rs @@ -17,7 +17,7 @@ static POWERSHELL_7_PATH: LazyLock> = LazyLock::new(find_powersh static POWERSHELL_5_PATH: LazyLock> = LazyLock::new(find_powershell_5_path); static WSL_PATH: LazyLock> = LazyLock::new(find_wsl_path); -/// Returns the location which Zap was installed to. +/// Returns the location which Zaplex was installed to. #[cfg(feature = "local_fs")] pub fn install_dir() -> Result { let current_exe = env::current_exe()?; diff --git a/app/src/voice/transcriber.rs b/app/src/voice/transcriber.rs index d0703f3eb7..6eb1629414 100644 --- a/app/src/voice/transcriber.rs +++ b/app/src/voice/transcriber.rs @@ -8,7 +8,7 @@ pub enum TranscribeError { #[error("Request failed due to lack of Voice quota.")] QuotaLimit, - #[error("Zap is currently overloaded. Please try again later.")] + #[error("Zaplex is currently overloaded. Please try again later.")] ServerOverloaded, #[error("Internal error occurred at transport layer.")] @@ -17,8 +17,8 @@ pub enum TranscribeError { #[error("Failed to deserialize JSON.")] Deserialization, - /// Zap has disabled voice transcription (the BYOP genai protocol cannot carry audio). - #[error("Voice transcription is unavailable in Zap.")] + /// Zaplex has disabled voice transcription (the BYOP genai protocol cannot carry audio). + #[error("Voice transcription is unavailable in Zaplex.")] Disabled, #[error(transparent)] @@ -55,7 +55,7 @@ impl VoiceTranscriber { } } - /// Zap (localization, Phase 4): creates a disabled transcriber. In the original + /// Zaplex (localization, Phase 4): creates a disabled transcriber. In the original /// semantics `Some(...)` meant a cloud STT backend was available and `None` meant /// "transcriber disabled"; after localization the cloud `ServerVoiceTranscriber` /// (which calls server_api.transcribe to send Wispr STT) is unavailable, so this constructor is used instead. diff --git a/app/src/voltron.rs b/app/src/voltron.rs index 72d30e9781..01602e569f 100644 --- a/app/src/voltron.rs +++ b/app/src/voltron.rs @@ -1,4 +1,4 @@ -//! Voltron (https://en.wikipedia.org/wiki/Voltron) in Zap is a code name for the UI element used +//! Voltron (https://en.wikipedia.org/wiki/Voltron) in Zaplex is a code name for the UI element used //! for workflows, ai commands and history search. As @charlespierce pointed out: it's 3 mini lions //! (features) coming together to form one super bot (this view). Hence the name. Don't ask me, I //! haven't watched it. diff --git a/app/src/warp_managed_paths_watcher.rs b/app/src/warp_managed_paths_watcher.rs index 7214f7e738..9a65515995 100644 --- a/app/src/warp_managed_paths_watcher.rs +++ b/app/src/warp_managed_paths_watcher.rs @@ -14,9 +14,9 @@ use warpui::{Entity, ModelContext, SingletonEntity}; #[cfg(not(target_family = "wasm"))] use watcher::{BulkFilesystemWatcher, BulkFilesystemWatcherEvent}; -/// Duration between filesystem watch events for the Zap managed paths watcher, in milliseconds. +/// Duration between filesystem watch events for the Zaplex managed paths watcher, in milliseconds. #[cfg(not(target_family = "wasm"))] -const WARP_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS: u64 = 500; +const ZAPLEX_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS: u64 = 500; pub(crate) fn warp_data_dir() -> PathBuf { warp_core::paths::data_dir() @@ -30,7 +30,7 @@ pub(crate) fn ensure_warp_watch_roots_exist() { let data_dir = warp_data_dir(); if let Err(err) = fs::create_dir_all(&data_dir) { log::warn!( - "Failed to create Zap data directory {}: {err}", + "Failed to create Zaplex data directory {}: {err}", data_dir.display() ); } @@ -39,7 +39,7 @@ pub(crate) fn ensure_warp_watch_roots_exist() { if config_local_dir != data_dir { if let Err(err) = fs::create_dir_all(&config_local_dir) { log::warn!( - "Failed to create Zap config directory {}: {err}", + "Failed to create Zaplex config directory {}: {err}", config_local_dir.display() ); } @@ -230,7 +230,7 @@ impl WarpManagedPathsWatcher { let watcher = if should_register_watcher { ctx.add_model(|ctx| { BulkFilesystemWatcher::new( - Duration::from_millis(WARP_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS), + Duration::from_millis(ZAPLEX_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS), ctx, ) }) @@ -250,7 +250,7 @@ impl WarpManagedPathsWatcher { data_dir.clone(), WatchFilter::with_filter(Arc::new(move |path| !path.starts_with(&worktrees_dir))), RecursiveMode::Recursive, - "Zap data directory", + "Zaplex data directory", ); if should_register_config_local_dir { Self::register_path( @@ -259,7 +259,7 @@ impl WarpManagedPathsWatcher { config_local_dir.clone(), WatchFilter::accept_all(), RecursiveMode::Recursive, - "Zap config directory", + "Zaplex config directory", ); } if let Some(warp_home_skills_dir) = warp_home_skills_dir() { @@ -274,7 +274,7 @@ impl WarpManagedPathsWatcher { warp_home_skills_dir, WatchFilter::accept_all(), RecursiveMode::Recursive, - "Zap home skills directory", + "Zaplex home skills directory", ); } } @@ -294,7 +294,7 @@ impl WarpManagedPathsWatcher { path == warp_home_mcp_config_path })), RecursiveMode::NonRecursive, - "Zap home MCP config directory", + "Zaplex home MCP config directory", ); } } @@ -390,7 +390,7 @@ mod tests { assert_eq!(path.config_path, warp_home_mcp_config_path); } (_, _, None) => {} - _ => panic!("Expected Zap MCP path when home directory is available"), + _ => panic!("Expected Zaplex MCP path when home directory is available"), } } diff --git a/app/src/wasm_nux_dialog.rs b/app/src/wasm_nux_dialog.rs index 93af21b53a..cd3735c1c2 100644 --- a/app/src/wasm_nux_dialog.rs +++ b/app/src/wasm_nux_dialog.rs @@ -33,9 +33,9 @@ pub enum WasmNUXDialogAction { SetWebAndClose, /// Closes the dialog and open on the desktop OpenNativeAndClose, - /// Open the Zap download page + /// Open the Zaplex download page OpenDownloadDesktopAppLink, - /// Open a link to learn more about Zap + /// Open a link to learn more about Zaplex LearnMore, } @@ -44,7 +44,7 @@ pub enum WasmNUXDialogEvent { } /// A dialog that prompts the user to: -/// * Download Zap if they haven't already +/// * Download Zaplex if they haven't already /// * Explicitly choose between native and web. pub struct WasmNUXDialog { close_mouse_state: MouseStateHandle, @@ -77,7 +77,7 @@ impl WasmNUXDialog { /// * The user hasn't dismissed the dialog /// /// If the user dismisses the dialog without choosing a preference, we'll continue to use the default autodetection - /// behavior: if Zap is installed, redirect to it; otherwise stay on the web. + /// behavior: if Zaplex is installed, redirect to it; otherwise stay on the web. pub fn should_display(app: &AppContext) -> bool { // Don't show on mobile devices - they can't use the desktop app if warpui::platform::wasm::is_mobile_device() { @@ -127,8 +127,8 @@ impl View for WasmNUXDialog { let appearance = Appearance::handle(app).as_ref(app); // There are two general cases with the dialog: - // 1. The user doesn't have Zap installed - treat them as a potential new user and encourage downloading Zap. - // 2. The user has Zap installed, but clicked through to the web - ask if they want to always default to web. + // 1. The user doesn't have Zaplex installed - treat them as a potential new user and encourage downloading Zaplex. + // 2. The user has Zaplex installed, but clicked through to the web - ask if they want to always default to web. // As a sub-state of case 1, if the user clicks the download button, we provide an intent into the app. let close_button = appearance @@ -264,8 +264,8 @@ impl TypedActionView for WasmNUXDialog { ctx.emit(WasmNUXDialogEvent::Close); } WasmNUXDialogAction::OpenNativeAndClose => { - // We intentionally do not set the native preference here, in case the user hasn't actually installed Zap. - // If they have, on subsequent loads, we'll detect that Zap is installed and redirect to the desktop. + // We intentionally do not set the native preference here, in case the user hasn't actually installed Zaplex. + // If they have, on subsequent loads, we'll detect that Zaplex is installed and redirect to the desktop. ctx.emit(WasmNUXDialogEvent::Close); if let Some(url) = web_intent_parser::parse_web_intent_from_current_url() { diff --git a/app/src/workflows/aliases.rs b/app/src/workflows/aliases.rs index 70a2cd1a95..69b732b67b 100644 --- a/app/src/workflows/aliases.rs +++ b/app/src/workflows/aliases.rs @@ -30,7 +30,7 @@ define_settings_group!(WorkflowAliases, settings: [ ]); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone, schemars::JsonSchema, SettingsValue)] -#[schemars(description = "A shortcut alias for a Zap Drive workflow.")] +#[schemars(description = "A shortcut alias for a Zaplex Drive workflow.")] pub struct WorkflowAlias { #[schemars(description = "The alias text that triggers this workflow.")] pub alias: String, diff --git a/app/src/workflows/command_parser_test.rs b/app/src/workflows/command_parser_test.rs index abed5ebe85..fdae94bdb9 100644 --- a/app/src/workflows/command_parser_test.rs +++ b/app/src/workflows/command_parser_test.rs @@ -10,7 +10,7 @@ lazy_static! { static ref WORKFLOW: Workflow = Workflow::Command { name: "Run single integration test with display".to_owned(), command: - "RUST_BACKTRACE=full WARP_SHELL_PATH={{shell_path}} cargo run -p integration --bin \ + "RUST_BACKTRACE=full ZAPLEX_SHELL_PATH={{shell_path}} cargo run -p integration --bin \ integration --features=with_real_display_in_integration_tests -- {{test_name}}" .to_owned(), arguments: vec![ @@ -167,7 +167,7 @@ fn test_compute_workflow_display_data_for_linked_history_command() { // It passes "/opt/homebrew/bin/fish" for the {{shell_path}} parameter and // test_command_search_loads_history for the {{test_name}} parameter. let linked_history_command = - "RUST_BACKTRACE=full WARP_SHELL_PATH=/opt/homebrew/bin/fish cargo run -p integration --bin \ + "RUST_BACKTRACE=full ZAPLEX_SHELL_PATH=/opt/homebrew/bin/fish cargo run -p integration --bin \ integration --features=with_real_display_in_integration_tests -- \ test_command_search_loads_history"; let display_data = @@ -300,7 +300,7 @@ fn test_compute_workflow_display_data_for_similar_but_unlinked_history_command() // This command is missing the "-p" from the workflow's command, so should not be linked to the // command. let similar_but_unlinked_history_command = - "RUST_BACKTRACE=full WARP_SHELL_PATH=/opt/homebrew/bin/fish cargo run integration --bin \ + "RUST_BACKTRACE=full ZAPLEX_SHELL_PATH=/opt/homebrew/bin/fish cargo run integration --bin \ integration --features=with_real_display_in_integration_tests -- \ test_command_search_loads_history"; assert!(compute_workflow_display_data_for_history_command( diff --git a/app/src/workflows/local_workflows.rs b/app/src/workflows/local_workflows.rs index b419a6a00e..5f74d5fa72 100644 --- a/app/src/workflows/local_workflows.rs +++ b/app/src/workflows/local_workflows.rs @@ -26,7 +26,7 @@ pub enum UseCache { No, } -/// Singleton model that loads and caches local (non-ZapDrive) workflows. +/// Singleton model that loads and caches local (non-ZaplexDrive) workflows. pub struct LocalWorkflows { app_workflows: Vec, @@ -44,12 +44,12 @@ impl LocalWorkflows { } } - /// Returns an iterator over hardcoded "application" workflows included in the Zap binary. + /// Returns an iterator over hardcoded "application" workflows included in the Zaplex binary. pub fn app_workflows(&self) -> impl Iterator { self.app_workflows.iter() } - /// Returns an iterator over the static set of workflows for 3rd party tools loaded from Zap's + /// Returns an iterator over the static set of workflows for 3rd party tools loaded from Zaplex's /// workflows GitHub repo. pub fn global_workflows( &self, @@ -184,7 +184,7 @@ pub(super) fn load_project_workflows(path: &Path) -> Vec { match git2::Repository::discover(path) { Ok(repository) => repository.workdir().map_or(Vec::new(), |workdir| { load_workflows(&workflows_dir( - workdir.join(warp_core::paths::WARP_CONFIG_DIR), + workdir.join(warp_core::paths::ZAPLEX_CONFIG_DIR), )) }), Err(_) => Vec::new(), @@ -224,7 +224,7 @@ pub fn prompt_chip_logging_workflow(shell_family: ShellFamily) -> Option) -> Self { - // Zap: no cloud backend = no client_id→server_id conversion events. Original UpdateManager + // Zaplex: no cloud backend = no client_id→server_id conversion events. Original UpdateManager // subscriptions + handle_update_manager_event are dead code; delete in Phase 2c-1. WorkflowManager { panes_by_hashed_id: HashMap::new(), @@ -59,7 +59,7 @@ impl WorkflowManager { pub fn create_pane( &mut self, source: &WorkflowOpenSource, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, mode: WorkflowViewMode, window_id: WindowId, ctx: &mut ModelContext, @@ -116,7 +116,7 @@ impl WorkflowManager { *initial_folder_id, ClientId::default(), ), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), mode, ctx, ); diff --git a/app/src/workflows/mod.rs b/app/src/workflows/mod.rs index b25f1cb78e..bb266285e4 100644 --- a/app/src/workflows/mod.rs +++ b/app/src/workflows/mod.rs @@ -50,14 +50,14 @@ pub enum WorkflowSource { location: NotebookLocation, }, - /// A hardcoded workflow type that allows Zap to surface features as Workflows (e.g. + /// A hardcoded workflow type that allows Zaplex to surface features as Workflows (e.g. /// a command to see our network log) App, } #[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, PartialOrd)] pub enum WorkflowSelectionSource { - ZapDrive, + ZaplexDrive, CommandPalette, UniversalSearch, Voltron, @@ -146,7 +146,7 @@ pub enum WorkflowType { Local(Workflow), /// Saved workflows from the local object store. Cloud(Box), - /// Ephemeral/transient workflows created from Zap AI output + /// Ephemeral/transient workflows created from Zaplex AI output AIGenerated { workflow: Workflow, origin: AIWorkflowOrigin, diff --git a/app/src/workflows/workflow_view.rs b/app/src/workflows/workflow_view.rs index 9470aea40a..f0bfdc0057 100644 --- a/app/src/workflows/workflow_view.rs +++ b/app/src/workflows/workflow_view.rs @@ -40,7 +40,7 @@ use crate::{ workflow_arg_selector::{WorkflowArgSelector, WorkflowArgSelectorEvent}, workflow_arg_type_helpers::{self, ArgumentEditorRowIndex}, }, - DriveObjectType, ObjectTypeAndId, ZapDriveObjectSettings, + DriveObjectType, ObjectTypeAndId, ZaplexDriveObjectSettings, }, editor::{ EditorOptions, EditorView, EnterAction, EnterSettings, Event as EditorEvent, @@ -172,7 +172,7 @@ const AI_ASSIST_LOADING_TEXT: &str = "Loading"; const ALIAS_HELP_TEXT: &str = "Aliases allow you to create short strings to execute workflows. Each alias can have different argument values and environment variables, and aliases are personal to you."; -const RUN_ON_DESKTOP_BUTTON_TEXT: &str = "Run in Zap"; +const RUN_ON_DESKTOP_BUTTON_TEXT: &str = "Run in Zaplex"; const RUN_ON_DESKTOP_BUTTON_WIDTH: f32 = 108.; const UNSAVED_CHANGES_TEXT: &str = "You have unsaved changes."; @@ -576,7 +576,7 @@ impl WorkflowView { { self.load( workflow.clone(), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), self.workflow_view_mode, ctx, ); @@ -596,7 +596,7 @@ impl WorkflowView { { self.load( workflow, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), self.workflow_view_mode, ctx, ); @@ -614,7 +614,7 @@ impl WorkflowView { if let Some(workflow) = workflow { self.load( workflow, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), self.workflow_view_mode, ctx, ); @@ -624,7 +624,7 @@ impl WorkflowView { pub fn wait_for_initial_load_then_load( &mut self, workflow_id: SyncId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, mode: WorkflowViewMode, window_id: WindowId, ctx: &mut ViewContext, @@ -673,7 +673,7 @@ impl WorkflowView { fn fetch_and_load_workflow( &mut self, workflow_id: ServerId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, mode: WorkflowViewMode, window_id: WindowId, ctx: &mut ViewContext, @@ -711,7 +711,7 @@ impl WorkflowView { pub fn load( &mut self, workflow: WorkflowObject, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, mode: WorkflowViewMode, ctx: &mut ViewContext, ) { @@ -1565,7 +1565,7 @@ impl WorkflowView { } /// Save the workflow and associated state. This makes a best-effort attempt to not - /// unnecessarily modify the backing Zap Drive object. + /// unnecessarily modify the backing Zaplex Drive object. fn save(&mut self, ctx: &mut ViewContext) { if FeatureFlag::WorkflowAliases.is_enabled() && self.are_aliases_dirty(ctx) { self.save_aliases(ctx); @@ -2465,7 +2465,7 @@ impl WorkflowView { .finish(); let button_with_tool_tip = appearance.ui_builder().tool_tip_on_element( - "Generate a title, descriptions, or parameters with Zap AI".to_string(), + "Generate a title, descriptions, or parameters with Zaplex AI".to_string(), self.ui_state_handles.ai_assist_tool_tip.clone(), rendered_button, ParentAnchor::TopMiddle, @@ -2625,7 +2625,7 @@ impl WorkflowView { }, ) else { self.display_error_toast( - "Autofill 需要 BYOP 模型。请到 Settings → AI 中配置一个 provider 与模型。" + "Autofill requires a BYOP model. Please configure a provider and model in Settings → AI." .to_string(), ctx, ); diff --git a/app/src/workspace/action.rs b/app/src/workspace/action.rs index 8a68e0fc26..e09b0a3633 100644 --- a/app/src/workspace/action.rs +++ b/app/src/workspace/action.rs @@ -253,15 +253,15 @@ pub enum WorkspaceAction { tab_position: RectF, }, DropTab, - /// Toggles the left panel. In Code Mode V1 this toggles Zap Drive. + /// Toggles the left panel. In Code Mode V1 this toggles Zaplex Drive. /// In Code Mode V2 this toggles the left panel which contains both the project explorer and - /// Zap Drive. This happens as explicit action from the user. + /// Zaplex Drive. This happens as explicit action from the user. ToggleLeftPanel, - /// Toggles directly to the Zap Drive tab of the left panel in Code Mode V2 + /// Toggles directly to the Zaplex Drive tab of the left panel in Code Mode V2 ToggleWarpDrive, - /// Unconditionally opens Zap Drive. This is used in the case of user lifecycle + /// Unconditionally opens Zaplex Drive. This is used in the case of user lifecycle /// events like new user onboarding or when the user joins a team. - ZapDrive, + ZaplexDrive, /// Toggles the right panel. This happens as an explicit action from the user. ToggleRightPanel, /// Opens the code review panel (right panel) without toggling. If already open, @@ -319,7 +319,7 @@ pub enum WorkspaceAction { }, // Decentralized branch: `Reauth` / `SignupAnonymousUser` / `SignInAnonymousWebUser` have been removed. OpenLink(String), - /// On WASM, opens a given URL in the desktop Zap app (if installed) or redirects to download page. + /// On WASM, opens a given URL in the desktop Zaplex app (if installed) or redirects to download page. #[cfg(target_family = "wasm")] OpenLinkOnDesktop(url::Url), ReopenClosedSession, @@ -344,7 +344,7 @@ pub enum WorkspaceAction { }, TerminateApp, CloseWindow, - /// Help the user call the Zap executable with the [`crate::args::DEBUG_DUMP_FLAG`]. + /// Help the user call the Zaplex executable with the [`crate::args::DEBUG_DUMP_FLAG`]. DumpDebugInfo, /// Log review comment send eligibility for panes in the active tab. LogReviewCommentSendStatusForActiveTab, @@ -386,7 +386,7 @@ pub enum WorkspaceAction { }, OpenAIFactCollection, OpenMCPServerCollection, - // Zap Wave 7-3: `OpenEnvironmentManagementPane` WorkspaceAction physically removed along with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `OpenEnvironmentManagementPane` WorkspaceAction physically removed along with ambient-agent UI subsystem. ToggleAIDocumentPane { document_id: AIDocumentId, document_version: AIDocumentVersion, @@ -474,10 +474,10 @@ pub enum WorkspaceAction { QueuePromptForConversation { prompt: String, }, - /// Install the Zap CLI command to /usr/local/bin + /// Install the Zaplex CLI command to /usr/local/bin #[cfg(target_os = "macos")] InstallCLI, - /// Uninstall the Zap CLI command from /usr/local/bin + /// Uninstall the Zaplex CLI command from /usr/local/bin #[cfg(target_os = "macos")] UninstallCLI, UndoRevertInCodeReviewPane { @@ -519,10 +519,10 @@ pub enum WorkspaceAction { /// Reset the AWS Bedrock login banner dismissed state (for debugging). #[cfg(debug_assertions)] DebugResetAwsBedrockLoginBannerDismissed, - /// Open the Zap Launch Modal (for debugging) + /// Open the Zaplex Launch Modal (for debugging) #[cfg(debug_assertions)] OpenZapLaunchModal, - /// Reset the Zap launch modal dismissed state (for debugging) + /// Reset the Zaplex launch modal dismissed state (for debugging) #[cfg(debug_assertions)] ResetZapLaunchModalState, /// Install the opencode-warp plugin from GitHub into the global opencode config. @@ -759,7 +759,7 @@ impl WorkspaceAction { | StartTabDrag | ToggleLeftPanel | ToggleWarpDrive - | ZapDrive + | ZaplexDrive | ClosePanel | ToggleRightPanel | OpenCodeReviewPanel(..) @@ -875,7 +875,7 @@ impl WorkspaceAction { FileRenamed { .. } => false, // File rename doesn't change workspace state #[cfg(feature = "local_fs")] FileDeleted { .. } => false, // File deletion doesn't change workspace state - // Zap Wave 7-3: `OpenEnvironmentManagementPane` WorkspaceAction physically removed along with ambient-agent UI subsystem. + // Zaplex Wave 7-3: `OpenEnvironmentManagementPane` WorkspaceAction physically removed along with ambient-agent UI subsystem. #[cfg(target_os = "linux")] DismissWaylandCrashRecoveryBannerAndOpenLink => false, #[cfg(target_family = "wasm")] diff --git a/app/src/workspace/active_session.rs b/app/src/workspace/active_session.rs index d10c79495a..74cb278b22 100644 --- a/app/src/workspace/active_session.rs +++ b/app/src/workspace/active_session.rs @@ -15,7 +15,7 @@ use crate::terminal::model::session::Session; /// Generally, if a more specific session is available, it should be preferred. For example, when /// opening a Markdown file from a file link in a block's output, that block's session should be /// the basis. However, sometimes there is no contextual session (such as when opening a file -/// in Zap from Finder, or when starting from a cloud object). In that case, the `ActiveSession` +/// in Zaplex from Finder, or when starting from a cloud object). In that case, the `ActiveSession` /// might be used, but it's often still better to be context-independent. #[derive(Default)] pub struct ActiveSession { diff --git a/app/src/workspace/cli_install.rs b/app/src/workspace/cli_install.rs index 1e4d97eaef..f7f5231234 100644 --- a/app/src/workspace/cli_install.rs +++ b/app/src/workspace/cli_install.rs @@ -29,7 +29,7 @@ fn create_symlink_with_admin(source: &Path, target: &Path) -> Result<()> { // Use osascript to run the ln command with admin privileges, with a custom prompt let script = format!( - "do shell script \"ln -sf {escaped_source} {escaped_target}\" with prompt \"Zap needs administrator privileges to install the command in /usr/local/bin.\" with administrator privileges" + "do shell script \"ln -sf {escaped_source} {escaped_target}\" with prompt \"Zaplex needs administrator privileges to install the command in /usr/local/bin.\" with administrator privileges" ); log::debug!("Creating symlink with admin privileges"); @@ -65,7 +65,7 @@ fn remove_file_with_admin(target: &Path) -> Result<()> { let escaped_target = ShellFamily::Posix.shell_escape(target_str); let script = format!( - "do shell script \"rm {escaped_target}\" with prompt \"Zap needs administrator privileges to uninstall the command from /usr/local/bin.\" with administrator privileges" + "do shell script \"rm {escaped_target}\" with prompt \"Zaplex needs administrator privileges to uninstall the command from /usr/local/bin.\" with administrator privileges" ); log::debug!("Removing file with admin privileges"); @@ -92,7 +92,7 @@ fn remove_file_with_admin(target: &Path) -> Result<()> { /// Install the CLI by creating a symlink (channel-specific target) /// /// This function: -/// 1. Detects the current Zap channel and finds the appropriate binary +/// 1. Detects the current Zaplex channel and finds the appropriate binary /// 2. Attempts to create a symlink without admin privileges first /// 3. Falls back to prompting for admin privileges if needed /// 4. Handles existing installations and edge cases diff --git a/app/src/workspace/global_actions.rs b/app/src/workspace/global_actions.rs index dead89e983..c4f8ecbaad 100644 --- a/app/src/workspace/global_actions.rs +++ b/app/src/workspace/global_actions.rs @@ -1,12 +1,12 @@ use crate::network::NetworkStatus; use crate::persistence::ModelEvent; -// Zap Wave 3-1: `AuthClient` trait and `workspace:debug_create_anonymous_user` +// Zaplex Wave 3-1: `AuthClient` trait and `workspace:debug_create_anonymous_user` // debug action physically removed together with auth subsystem sunset. use crate::app_state::get_app_state; use crate::terminal::alt_screen_reporting::AltScreenReporting; use crate::terminal::general_settings::GeneralSettings; use crate::workspace::cross_window_tab_drag::CrossWindowTabDrag; -// Zap Wave 3-1: `ServerApiProvider` is no longer used in this file; +// Zaplex Wave 3-1: `ServerApiProvider` is no longer used in this file; // `debug_create_anonymous_user` debug action physically removed with AuthClient. use ::settings::ToggleableSetting; use warp_core::execution_mode::AppExecutionMode; @@ -125,7 +125,7 @@ pub fn init_global_actions(app: &mut AppContext) { "workspace:toggle_debug_network_status", toggle_debug_network_status, ); - // Zap Wave 3-1: `workspace:debug_create_anonymous_user` global action physically removed + // Zaplex Wave 3-1: `workspace:debug_create_anonymous_user` global action physically removed // together with upstream anonymous user creation cloud endpoint. app.add_global_action("workspace:open_repository", open_repository); app.add_global_action("app:undo_close", undo_close); @@ -215,8 +215,8 @@ fn toggle_debug_network_status(_: &(), ctx: &mut AppContext) { }); } -// Zap Wave 3-1: `fn create_anonymous_user` debug entry point physically removed -// together with upstream anonymous user creation cloud endpoint. Zap no longer +// Zaplex Wave 3-1: `fn create_anonymous_user` debug entry point physically removed +// together with upstream anonymous user creation cloud endpoint. Zaplex no longer // has the concept of anonymous users. /// Reopens the last closed item (window or tab). diff --git a/app/src/workspace/hoa_onboarding/hoa_onboarding_flow.rs b/app/src/workspace/hoa_onboarding/hoa_onboarding_flow.rs index 9a5a8b9fe7..26f303bc2c 100644 --- a/app/src/workspace/hoa_onboarding/hoa_onboarding_flow.rs +++ b/app/src/workspace/hoa_onboarding/hoa_onboarding_flow.rs @@ -480,7 +480,7 @@ impl HoaOnboardingFlow { let formatted = FormattedText::new([FormattedTextLine::Line(vec![ FormattedTextFragment::plain_text( - "Zap pipes through notifications from any CLI coding agent into a unified notification center that works across all coding agents and harnesses. ", + "Zaplex pipes through notifications from any CLI coding agent into a unified notification center that works across all coding agents and harnesses. ", ), learn_more_fragment, ])]); diff --git a/app/src/workspace/home.rs b/app/src/workspace/home.rs index 72854daa9c..fcc21327a5 100644 --- a/app/src/workspace/home.rs +++ b/app/src/workspace/home.rs @@ -1,4 +1,4 @@ -//! Zap Home +//! Zaplex Home //! //! This is the landing page for new tabs if session creation isn't supported (e.g. on the web). //! It's intentionally small and local-only. @@ -8,12 +8,12 @@ use warpui::ViewContext; use super::view::Workspace; use crate::pane_group::{AnyPaneContent, FilePane}; -const WARP_HOME_TITLE: &str = "Welcome to Zap"; -const WARP_HOME_CONTENT: &str = r#" -Welcome to Zap. +const ZAPLEX_HOME_TITLE: &str = "Welcome to Zaplex"; +const ZAPLEX_HOME_CONTENT: &str = r#" +Welcome to Zaplex. Use this local workspace to: -* Create, view, and edit Zap Drive objects +* Create, view, and edit Zaplex Drive objects * Manage local settings * Work with local agent sessions, notebooks, and workflows"#; @@ -27,7 +27,7 @@ pub fn create_home_pane(ctx: &mut ViewContext) -> Box bool { use crate::workspace::view::{ LEFT_PANEL_AGENT_CONVERSATIONS_BINDING_NAME, LEFT_PANEL_GLOBAL_SEARCH_BINDING_NAME, LEFT_PANEL_PROJECT_EXPLORER_BINDING_NAME, LEFT_PANEL_SKILL_MANAGER_BINDING_NAME, - LEFT_PANEL_SSH_MANAGER_BINDING_NAME, LEFT_PANEL_WARP_DRIVE_BINDING_NAME, + LEFT_PANEL_SSH_MANAGER_BINDING_NAME, LEFT_PANEL_ZAPLEX_DRIVE_BINDING_NAME, NEW_AGENT_TAB_BINDING_NAME, NEW_TAB_BINDING_NAME, NEW_TERMINAL_TAB_BINDING_NAME, OPEN_GLOBAL_SEARCH_BINDING_NAME, TOGGLE_CONVERSATION_LIST_VIEW_BINDING_NAME, TOGGLE_NOTIFICATION_MAILBOX_BINDING_NAME, TOGGLE_PROJECT_EXPLORER_BINDING_NAME, TOGGLE_RIGHT_PANEL_BINDING_NAME, TOGGLE_TAB_CONFIGS_MENU_BINDING_NAME, - TOGGLE_VERTICAL_TABS_PANEL_BINDING_NAME, TOGGLE_WARP_DRIVE_BINDING_NAME, + TOGGLE_VERTICAL_TABS_PANEL_BINDING_NAME, TOGGLE_ZAPLEX_DRIVE_BINDING_NAME, }; pub use one_time_modal_model::OneTimeModalModel; pub use registry::WorkspaceRegistry; @@ -592,7 +592,7 @@ pub fn init(app: &mut AppContext) { ) .with_group(bindings::BindingGroup::Notebooks.as_str()) .with_custom_action(CustomAction::NewPersonalNotebook) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE)), + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE)), EditableBinding::new( "workspace:create_personal_workflow", BindingDescription::new(crate::t!( @@ -606,7 +606,7 @@ pub fn init(app: &mut AppContext) { ) .with_group(bindings::BindingGroup::Workflow.as_str()) .with_custom_action(CustomAction::NewPersonalWorkflow) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE)), + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE)), EditableBinding::new( "workspace:create_personal_folder", BindingDescription::new(crate::t!( @@ -619,7 +619,7 @@ pub fn init(app: &mut AppContext) { WorkspaceAction::CreatePersonalFolder, ) .with_group(bindings::BindingGroup::Folders.as_str()) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE) & id!("IsOnline")), + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE) & id!("IsOnline")), EditableBinding::new( NEW_TAB_BINDING_NAME, BindingDescription::new(crate::t!("keybinding-desc-workspace-new-tab")), @@ -714,12 +714,12 @@ pub fn init(app: &mut AppContext) { .with_enabled(|| FeatureFlag::GlobalSearch.is_enabled()) .with_custom_action(CustomAction::ToggleGlobalSearch), EditableBinding::new( - LEFT_PANEL_WARP_DRIVE_BINDING_NAME, + LEFT_PANEL_ZAPLEX_DRIVE_BINDING_NAME, BindingDescription::new(crate::t!("keybinding-desc-workspace-left-panel-warp-drive")), WorkspaceAction::ToggleWarpDrive, ) .with_group(bindings::BindingGroup::Navigation.as_str()) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE)) + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE)) .with_mac_key_binding("ctrl-4") .with_linux_or_windows_key_binding("alt-4"), EditableBinding::new( @@ -770,7 +770,7 @@ pub fn init(app: &mut AppContext) { // we use alt because we use ctrl-shift-f for find because ctrl-f needs to be reserved for the shell .with_linux_or_windows_key_binding("alt-shift-F"), EditableBinding::new( - TOGGLE_WARP_DRIVE_BINDING_NAME, + TOGGLE_ZAPLEX_DRIVE_BINDING_NAME, BindingDescription::new(crate::t!("keybinding-desc-workspace-toggle-warp-drive")) .with_custom_description( bindings::MAC_MENUS_CONTEXT, @@ -778,7 +778,7 @@ pub fn init(app: &mut AppContext) { ), WorkspaceAction::ToggleWarpDrive, ) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE)), + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE)), EditableBinding::new( TOGGLE_CONVERSATION_LIST_VIEW_BINDING_NAME, BindingDescription::new(crate::t!( @@ -1033,7 +1033,7 @@ pub fn init(app: &mut AppContext) { "workspace:search_drive", crate::t!("keybinding-desc-workspace-search-drive"), WorkspaceAction::OpenPalette { - mode: PaletteMode::ZapDrive, + mode: PaletteMode::ZaplexDrive, source: PaletteSource::Keybinding, query: None, }, @@ -1084,7 +1084,7 @@ pub fn init(app: &mut AppContext) { WorkspaceAction::ExportAllWarpDriveObjects, ) .with_group(bindings::BindingGroup::Settings.as_str()) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE))]); + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE))]); } // CLI install/uninstall actions (macOS only) @@ -1160,7 +1160,7 @@ pub fn init(app: &mut AppContext) { .with_context_predicate(id!("Workspace") & id!(flags::IS_ANY_AI_ENABLED)) .with_group(bindings::BindingGroup::WarpAi.as_str()) // We use the same custom action as AM so that we don't have - // two mac menu items for AM vs Zap AI since they are mutually exclusive. + // two mac menu items for AM vs Zaplex AI since they are mutually exclusive. .with_custom_action(CustomAction::NewAgentModePane), ]); @@ -1178,7 +1178,7 @@ pub fn init(app: &mut AppContext) { ) .with_group(bindings::BindingGroup::EnvVarCollection.as_str()) .with_custom_action(CustomAction::NewPersonalEnvVars) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE)), + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE)), EditableBinding::new( "workspace:create_personal_ai_prompt", BindingDescription::new(crate::t!( @@ -1193,7 +1193,7 @@ pub fn init(app: &mut AppContext) { .with_group(bindings::BindingGroup::WarpAi.as_str()) .with_custom_action(CustomAction::NewPersonalAIPrompt) .with_context_predicate( - id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE) & id!(flags::IS_ANY_AI_ENABLED), + id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE) & id!(flags::IS_ANY_AI_ENABLED), ), ]); @@ -1219,7 +1219,7 @@ pub fn init(app: &mut AppContext) { crate::t!("keybinding-desc-workspace-import-to-personal-drive"), WorkspaceAction::ImportToPersonalDrive, ) - .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_WARP_DRIVE))]); + .with_context_predicate(id!("Workspace") & id!(flags::ENABLE_ZAPLEX_DRIVE))]); // Register a debug-only action for writing the user's access token to the system clipboard // to aid debugging and development. @@ -1343,7 +1343,7 @@ fn add_open_setting_pages_as_editable_binding(app: &mut AppContext) { ) .with_group(bindings::BindingGroup::Settings.as_str()) .with_context_predicate(id!("Workspace")), - // Zap Wave 6-8: `workspace:show_settings_shared_blocks_page` keybinding physically removed + // Zaplex Wave 6-8: `workspace:show_settings_shared_blocks_page` keybinding physically removed // together with `ShowBlocksView` settings page and `CustomAction::ViewSharedBlocks`. EditableBinding::new( "workspace:show_settings_keyboard_shortcuts_page", @@ -1372,13 +1372,13 @@ fn add_open_setting_pages_as_editable_binding(app: &mut AppContext) { .with_context_predicate(id!("Workspace")) .with_custom_action(CustomAction::ShowAboutWarp), EditableBinding::new( - "workspace:show_settings_warpify_page", - BindingDescription::new(crate::t!("keybinding-desc-workspace-show-settings-warpify")) + "workspace:show_settings_zaplexify_page", + BindingDescription::new(crate::t!("keybinding-desc-workspace-show-settings-zaplexify")) .with_custom_description( bindings::MAC_MENUS_CONTEXT, - crate::t!("keybinding-desc-workspace-show-settings-warpify-menu"), + crate::t!("keybinding-desc-workspace-show-settings-zaplexify-menu"), ), - WorkspaceAction::ShowSettingsPage(SettingsSection::Warpify), + WorkspaceAction::ShowSettingsPage(SettingsSection::Zaplexify), ) .with_group(bindings::BindingGroup::Settings.as_str()) .with_context_predicate(id!("Workspace")), @@ -1397,9 +1397,9 @@ fn add_open_setting_pages_as_editable_binding(app: &mut AppContext) { ) .with_group(bindings::BindingGroup::Settings.as_str()) .with_context_predicate(id!("Workspace")), - // Zap Wave 6-8: `workspace:show_settings_referrals_page` keybinding physically removed + // Zaplex Wave 6-8: `workspace:show_settings_referrals_page` keybinding physically removed // with `ReferralsPageView` settings page. - // Zap Wave 7-3: `workspace:show_settings_environments_page` keybinding physically removed + // Zaplex Wave 7-3: `workspace:show_settings_environments_page` keybinding physically removed // with ambient-agent UI subsystem. EditableBinding::new( "workspace:show_mcp_servers_settings_page", diff --git a/app/src/workspace/one_time_modal_model.rs b/app/src/workspace/one_time_modal_model.rs index 76152d7114..ae4b166c28 100644 --- a/app/src/workspace/one_time_modal_model.rs +++ b/app/src/workspace/one_time_modal_model.rs @@ -1,7 +1,7 @@ use super::hoa_onboarding; use crate::auth::{AuthManager, AuthManagerEvent}; use crate::channel::{Channel, ChannelState}; -// Zap(localization, Phase 5): `PreferencesSyncer` has been physically deleted. +// Zaplex(localization, Phase 5): `PreferencesSyncer` has been physically deleted. use crate::settings::CodeSettings; use crate::terminal::general_settings::GeneralSettings; use settings::Setting as _; @@ -15,7 +15,7 @@ use warpui::{Entity, ModelContext, SingletonEntity, WindowId}; /// a modal is currently being shown and automatically triggers the modal when appropriate /// conditions are met (e.g., user becomes onboarded). pub struct OneTimeModalModel { - /// Whether the Zap launch modal is currently being shown. + /// Whether the Zaplex launch modal is currently being shown. is_zap_launch_modal_open: bool, /// Whether the HOA onboarding flow is currently being shown. is_hoa_onboarding_open: bool, @@ -41,7 +41,7 @@ impl OneTimeModalModel { .did_check_to_trigger_zap_launch_modal .set_value(true, ctx) { - log::warn!("Failed to mark Zap launch modal as dismissed: {e}"); + log::warn!("Failed to mark Zaplex launch modal as dismissed: {e}"); } }); } @@ -59,7 +59,7 @@ impl OneTimeModalModel { self.target_window_id } - /// Returns whether the Zap launch modal is currently open. + /// Returns whether the Zaplex launch modal is currently open. pub fn is_zap_launch_modal_open(&self) -> bool { self.is_zap_launch_modal_open && self.target_window_id.is_some() } @@ -165,7 +165,7 @@ impl OneTimeModalModel { fn check_and_trigger_zap_launch_modal(&mut self, ctx: &mut ModelContext) -> bool { // Only show if the feature flag is enabled. - if !FeatureFlag::ZapLaunchModal.is_enabled() { + if !FeatureFlag::ZaplexLaunchModal.is_enabled() { return false; } @@ -183,7 +183,7 @@ impl OneTimeModalModel { .did_check_to_trigger_zap_launch_modal .set_value(true, ctx) { - log::warn!("Failed to mark Zap launch modal as dismissed: {e}"); + log::warn!("Failed to mark Zaplex launch modal as dismissed: {e}"); } }); diff --git a/app/src/workspace/util.rs b/app/src/workspace/util.rs index d3430a032e..c26ec0efe7 100644 --- a/app/src/workspace/util.rs +++ b/app/src/workspace/util.rs @@ -274,7 +274,7 @@ pub enum TerminalSessionFallbackBehavior { /// Given a [`WindowId`], see if its [`Workspace`] contains an active [`TerminalView`] and return /// that. /// -/// Note that "active" is not the same as "focused" in Zap's pane management. +/// Note that "active" is not the same as "focused" in Zaplex's pane management. pub fn active_terminal_in_window( window_id: WindowId, ctx: &mut AppContext, diff --git a/app/src/workspace/view.rs b/app/src/workspace/view.rs index b61b42a8df..923e51fd4d 100644 --- a/app/src/workspace/view.rs +++ b/app/src/workspace/view.rs @@ -109,7 +109,7 @@ use crate::workspace::header_toolbar_item::HeaderToolbarItemKind; use crate::workspace::tab_settings::TabCloseButtonPosition; use crate::workspace::view::codex_modal::{CodexModal, CodexModalEvent}; use crate::workspace::view::zap_launch_modal::{ - ZapLaunchModal, ZapLaunchModalEvent, + ZaplexLaunchModal, ZaplexLaunchModalEvent, }; use crate::workspace::{ForkFromExchange, ForkedConversationDestination}; use crate::BlocklistAIHistoryModel; @@ -145,12 +145,12 @@ use crate::pane_group::{ use crate::quit_warning::UnsavedStateSummary; use crate::search::command_palette::view::NavigationMode; use crate::search::slash_command_menu::static_commands::commands; -// Zap Wave 3-1: the `AuthClient` trait was physically removed along with server_api/auth.rs. +// Zaplex Wave 3-1: the `AuthClient` trait was physically removed along with server_api/auth.rs. use crate::settings::{ AISettings, AISettingsChangedEvent, CodeSettings, CodeSettingsChangedEvent, CtrlTabBehavior, DefaultSessionMode, InputModeSettings, }; -// Zap Wave 7-3: the `environments_page::EnvironmentsPage` import was physically removed +// Zaplex Wave 7-3: the `environments_page::EnvironmentsPage` import was physically removed // along with the ambient-agent UI subsystem. use crate::settings_view::pane_manager::SettingsPaneManager; use crate::settings_view::{SettingsSection, SettingsView, SettingsViewEvent}; @@ -161,7 +161,7 @@ use crate::terminal::available_shells::AvailableShell; use crate::terminal::available_shells::AvailableShells; use crate::terminal::block_list_viewport::InputMode; use crate::terminal::ligature_settings::should_use_ligature_rendering; -use crate::terminal::warpify::settings::WarpifySettings; +use crate::terminal::zaplexify::settings::ZaplexifySettings; use crate::ui_components::avatar::{Avatar, AvatarContent, StatusElementTypes}; #[cfg(target_family = "wasm")] @@ -177,7 +177,7 @@ use repo_metadata::RemoteRepositoryIdentifier; #[cfg(target_family = "wasm")] use url::Url; -// Zap: removed SharedObjectsCreationDeniedModal (the cloud Drive quota-rejection dialog) +// Zaplex: removed SharedObjectsCreationDeniedModal (the cloud Drive quota-rejection dialog) #[cfg(target_family = "wasm")] use crate::wasm_nux_dialog::WasmNUXDialog; @@ -207,7 +207,7 @@ use crate::drive::import::modal::{ImportModal, ImportModalEvent}; use crate::drive::workflows::arguments::ArgumentsState; use crate::drive::workflows::modal::{WorkflowModal, WorkflowModalEvent}; use crate::drive::{ - DriveObjectType, DrivePanel, DrivePanelEvent, ObjectTypeAndId, ZapDriveObjectSettings, + DriveObjectType, DrivePanel, DrivePanelEvent, ObjectTypeAndId, ZaplexDriveObjectSettings, }; use crate::experiments::{BlockOnboarding, Experiment}; use crate::menu::{ @@ -511,8 +511,8 @@ const TAB_BAR_ICON_PADDING: f32 = 4.; const TAB_BAR_PILL_WIDTH: f32 = 100.; const PILL_FONT_SIZE: f32 = 12.; -// We use the word "Zap" in the Update Ready button to make it obvious that the terminal is Zap. -// This can lead to free advertising when users screen-share Zap when an update is available. +// We use the word "Zaplex" in the Update Ready button to make it obvious that the terminal is Zaplex. +// This can lead to free advertising when users screen-share Zaplex when an update is available. const TAB_BAR_OVERFLOW_MENU_WIDTH: f32 = 300.; #[cfg(not(target_family = "wasm"))] @@ -562,7 +562,7 @@ pub(crate) const TOGGLE_NOTIFICATION_MAILBOX_BINDING_NAME: &str = // these won't have to be public after we deprecate the code mode v1 project explorer which is defined in terminal pub(crate) const TOGGLE_PROJECT_EXPLORER_BINDING_NAME: &str = "workspace:toggle_project_explorer"; -pub(crate) const TOGGLE_WARP_DRIVE_BINDING_NAME: &str = "workspace:toggle_warp_drive"; +pub(crate) const TOGGLE_ZAPLEX_DRIVE_BINDING_NAME: &str = "workspace:toggle_warp_drive"; pub(crate) const TOGGLE_RIGHT_PANEL_BINDING_NAME: &str = "workspace:toggle_right_panel"; pub(crate) const TOGGLE_VERTICAL_TABS_PANEL_BINDING_NAME: &str = "workspace:toggle_vertical_tabs_panel"; @@ -578,7 +578,7 @@ pub(crate) const TOGGLE_TAB_CONFIGS_MENU_BINDING_NAME: &str = "workspace:toggle_ pub(crate) const LEFT_PANEL_PROJECT_EXPLORER_BINDING_NAME: &str = "workspace:left_panel_project_explorer"; pub(crate) const LEFT_PANEL_GLOBAL_SEARCH_BINDING_NAME: &str = "workspace:left_panel_global_search"; -pub(crate) const LEFT_PANEL_WARP_DRIVE_BINDING_NAME: &str = "workspace:left_panel_warp_drive"; +pub(crate) const LEFT_PANEL_ZAPLEX_DRIVE_BINDING_NAME: &str = "workspace:left_panel_warp_drive"; pub(crate) const LEFT_PANEL_AGENT_CONVERSATIONS_BINDING_NAME: &str = "workspace:left_panel_agent_conversations"; pub(crate) const LEFT_PANEL_SSH_MANAGER_BINDING_NAME: &str = "workspace:left_panel_ssh_manager"; @@ -612,7 +612,7 @@ const MAX_WINDOW_TITLE_LENGTH: usize = 80; pub const DEFAULT_USER_DISPLAY_NAME: &str = "User"; lazy_static! { - static ref OPENING_WARP_DRIVE_ON_START_UP: Arc> = Arc::new(Mutex::new(false)); + static ref OPENING_ZAPLEX_DRIVE_ON_START_UP: Arc> = Arc::new(Mutex::new(false)); static ref PANEL_CORNER_RADIUS: CornerRadius = CornerRadius::with_all(Radius::Pixels(8.)); static ref PANEL_HEADER_CORNER_RADIUS: CornerRadius = CornerRadius::with_top(Radius::Pixels(8.)); @@ -716,7 +716,7 @@ impl ShowTabBar { #[cfg(target_family = "wasm")] #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum SimplifiedWasmTabBarContent { - /// Viewing a Zap Drive object (notebook, workflow, env vars, AI facts, MCP servers) + /// Viewing a Zaplex Drive object (notebook, workflow, env vars, AI facts, MCP servers) WarpDriveObject, /// Participating in a shared session (viewer or writer). Contains the optional ambient agent task ID. SharedSession { task_id: Option }, @@ -856,6 +856,12 @@ pub struct Workspace { window_id: WindowId, pub(crate) tabs: Vec, active_tab_index: usize, + /// Tracks which tab (by pane-group id) currently hosts each adopted daemon + /// `pty_session_id`, so adopting the same running session again focuses the + /// existing tab instead of opening a second view onto it (which would split + /// input/output across two tabs). Stale entries (tab since closed) are pruned + /// opportunistically on the next adopt. + adopted_daemon_sessions: std::collections::HashMap, pub(crate) hovered_tab_index: Option, tab_bar_hover_state: MouseStateHandle, tab_fixed_width: Option, @@ -928,7 +934,7 @@ pub struct Workspace { theme_deletion_modal: ViewHandle, suggested_agent_mode_workflow_modal: ViewHandle, suggested_rule_modal: ViewHandle, - zap_launch_modal: ViewHandle, + zap_launch_modal: ViewHandle, codex_modal: ViewHandle, toast_stack: ViewHandle>, agent_toast_stack: ViewHandle, @@ -1341,7 +1347,7 @@ impl Workspace { if let Some(id) = id_to_force_expand { self.open_notebook( &NotebookSource::Existing(id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); @@ -1358,7 +1364,7 @@ impl Workspace { if let Some(id) = id_to_force_expand { self.open_workflow_with_existing( id, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, ); ObjectStoreModel::handle(ctx).update(ctx, |object_store_model, ctx| { @@ -2518,7 +2524,7 @@ impl Workspace { let suggested_rule_modal = Self::build_suggested_rule_modal(ctx); - let zap_launch_view = ctx.add_typed_action_view(ZapLaunchModal::new); + let zap_launch_view = ctx.add_typed_action_view(ZaplexLaunchModal::new); ctx.subscribe_to_view(&zap_launch_view, |me, _, event, ctx| { me.handle_zap_launch_modal_event(event, ctx); }); @@ -2630,7 +2636,7 @@ impl Workspace { me.handle_window_settings_changed_event(event, ctx); }); - // Show the Zap AI warm welcome iff the user hasn't dismissed it nor interacted with Zap AI before. + // Show the Zaplex AI warm welcome iff the user hasn't dismissed it nor interacted with Zaplex AI before. // Also, avoid showing it in integration tests to prevent interaction with other tests. let mut should_show_ai_assistant_warm_welcome: bool = !FeatureFlag::AgentMode.is_enabled() && AISettings::as_ref(ctx).is_any_ai_enabled(ctx) @@ -2643,7 +2649,7 @@ impl Workspace { .map(|dismissed: bool| !dismissed) .unwrap_or(true); - // Don't automatically show the Zap AI welcome during onboarding if the block onboarding flow is being used. + // Don't automatically show the Zaplex AI welcome during onboarding if the block onboarding flow is being used. // This way, we can delay the reveal until the end of the onboarding flow so as not to overwhelm the user. if matches!( BlockOnboarding::get_group(ctx), @@ -2795,7 +2801,7 @@ impl Workspace { }); let native_modal = Self::build_native_modal_view(ctx); - // Zap: removed the SharedObjectsCreationDeniedModal registration (the cloud Drive quota-rejection dialog) + // Zaplex: removed the SharedObjectsCreationDeniedModal registration (the cloud Drive quota-rejection dialog) ctx.subscribe_to_model(&AISettings::handle(ctx), |me, _, event, ctx| match event { AISettingsChangedEvent::IsAnyAIEnabled { .. } @@ -2850,6 +2856,7 @@ impl Workspace { let mut ws = Self { tabs: Vec::new(), active_tab_index: 0, + adopted_daemon_sessions: std::collections::HashMap::new(), hovered_tab_index: None, tab_bar_hover_state: Default::default(), traffic_light_mouse_states: Default::default(), @@ -3526,11 +3533,12 @@ impl Workspace { LeftPanelDisplayedTab::GlobalSearch => ToolPanelView::GlobalSearch { entry_focus: GlobalSearchEntryFocus::Results, }, - LeftPanelDisplayedTab::ZapDrive => ToolPanelView::ZapDrive, + LeftPanelDisplayedTab::ZaplexDrive => ToolPanelView::ZaplexDrive, LeftPanelDisplayedTab::ConversationListView => ToolPanelView::ConversationListView, LeftPanelDisplayedTab::SshManager => ToolPanelView::SshManager, LeftPanelDisplayedTab::ServerFileBrowser => ToolPanelView::ServerFileBrowser, LeftPanelDisplayedTab::SkillManager => ToolPanelView::SkillManager, + LeftPanelDisplayedTab::Cockpit => ToolPanelView::Cockpit, }; lp.restore_active_view_from_snapshot(active_view, ctx); lp.set_active_pane_group(pane_group.clone(), &self.working_directories_model, ctx); @@ -3596,26 +3604,26 @@ impl Workspace { placeholder_pane = Some(home_pane.as_pane().id()); self.add_tab_from_existing_pane(home_pane, 0, ctx); - // If we can't start a terminal session to run the onboarding flow, show the Zap Home - // placeholder along with Zap Drive. + // If we can't start a terminal session to run the onboarding flow, show the Zaplex Home + // placeholder along with Zaplex Drive. true }; let initial_tab = self.active_tab_pane_group().clone(); if open_warp_drive { - // We open Zap Drive automatically in two cases: - // * The user is new to Zap, and went through the overall onboarding flow + // We open Zaplex Drive automatically in two cases: + // * The user is new to Zaplex, and went through the overall onboarding flow // * The user is on the web, so we can't open a terminal session. let initial_load_complete = crate::cloud_object::model::persistence::ObjectStoreModel::as_ref(ctx) .initial_load_complete(); ctx.spawn(initial_load_complete, move |me, _, ctx| { - // New Zap users can have non-welcome objects if they were directly invited OR if + // New Zaplex users can have non-welcome objects if they were directly invited OR if // linked objects were copied over from an anonymous user. if ObjectStoreModel::as_ref(ctx).has_non_welcome_objects() { me.open_or_toggle_warp_drive(false, false, ctx); - // After opening Zap Drive, if we rendered the Zap Home placeholder panel, replace it with one of + // After opening Zaplex Drive, if we rendered the Zaplex Home placeholder panel, replace it with one of // the user's own objects. if show_warp_home { let object_store_model = ObjectStoreModel::as_ref(ctx); @@ -3715,7 +3723,7 @@ impl Workspace { fn show_local_conversation_not_found_toast(&mut self, ctx: &mut ViewContext) { self.toast_stack.update(ctx, |view, ctx| { let new_toast = DismissibleToast::error( - "Conversation is not available in local Zap history.".to_string(), + "Conversation is not available in local Zaplex history.".to_string(), ); view.add_ephemeral_toast(new_toast, ctx); }); @@ -3775,7 +3783,7 @@ impl Workspace { } } - // Check if focused pane is a Zap Drive object + // Check if focused pane is a Zaplex Drive object let focused_pane_id = pane_group.focused_pane_id(ctx); if focused_pane_id.is_warp_drive_object_pane() { return Some(SimplifiedWasmTabBarContent::WarpDriveObject); @@ -3897,9 +3905,9 @@ impl Workspace { }); // The panel is already open and no models are open, so just refocus the panel. - // If there is a modal open, it would sit above the Zap AI panel and we would end up - // focusing the Zap AI panel _behind_ the floating modal. Instead, we opt for the normal - // toggle behavior which will close the current modal view and then toggle Zap AI. + // If there is a modal open, it would sit above the Zaplex AI panel and we would end up + // focusing the Zaplex AI panel _behind_ the floating modal. Instead, we opt for the normal + // toggle behavior which will close the current modal view and then toggle Zaplex AI. if self.current_workspace_state.is_ai_assistant_panel_open && !self.ai_assistant_panel.is_self_or_child_focused(ctx) && !self.current_workspace_state.is_any_modal_open(ctx) @@ -3912,7 +3920,7 @@ impl Workspace { self.current_workspace_state.is_ai_assistant_panel_open = !self.current_workspace_state.is_ai_assistant_panel_open; - // Close any other modals that could be floating on top of the Zap AI panel. + // Close any other modals that could be floating on top of the Zaplex AI panel. self.current_workspace_state.close_all_modals(); if self.current_workspace_state.is_ai_assistant_panel_open { @@ -3949,8 +3957,8 @@ impl Workspace { .has_warp_drive_initialized_sections(app) } - /// Check if Zap Drive view is focused within. - /// Routes to the appropriate Zap Drive panel. + /// Check if Zaplex Drive view is focused within. + /// Routes to the appropriate Zaplex Drive panel. fn is_warp_drive_view_focused(&self, ctx: &mut ViewContext) -> bool { let app = ctx; self.left_panel_view.is_self_or_child_focused(app) @@ -4152,7 +4160,7 @@ impl Workspace { } /// This function shifts focus to the panel on the left. - /// The current focusable panels are: Zap Drive, theme chooser, AI, and resource center (keyboard shortcuts page only) + /// The current focusable panels are: Zaplex Drive, theme chooser, AI, and resource center (keyboard shortcuts page only) fn focus_left_panel(&mut self, ctx: &mut ViewContext) { // Starts from terminal if self.active_tab_pane_group().is_self_or_child_focused(ctx) { @@ -4172,7 +4180,7 @@ impl Workspace { { self.focus_active_tab(ctx); } - // Starts from a left panel: Zap Drive + // Starts from a left panel: Zaplex Drive else if self.is_warp_drive_view_focused(ctx) { if self.current_workspace_state.is_right_panel_open() { self.set_selected_object(None, ctx); @@ -4217,7 +4225,7 @@ impl Workspace { ctx.focus(&self.theme_chooser_view); } } - // Starts from a left panel: Zap Drive, theme chooser + // Starts from a left panel: Zaplex Drive, theme chooser else if self.is_warp_drive_view_focused(ctx) || self.theme_chooser_view.is_self_or_child_focused(ctx) { @@ -5229,7 +5237,7 @@ impl Workspace { let pane_group = self.active_tab_pane_group().clone(); self.handle_file_tree_event(pane_group, pane_group_event, ctx); } - LeftPanelEvent::ZapDrive(drive_event) => { + LeftPanelEvent::ZaplexDrive(drive_event) => { self.handle_warp_drive_event(drive_event, ctx); } LeftPanelEvent::ServerFileBrowser(event) => match event { @@ -5319,6 +5327,18 @@ impl Workspace { LeftPanelEvent::OpenSftpPane { node_id, server: _ } => { self.open_sftp_pane(node_id.clone(), ctx); } + LeftPanelEvent::AdoptDaemonSession { + server, + pty_session_id, + } => { + #[cfg(unix)] + self.adopt_daemon_session(server.clone(), pty_session_id.clone(), ctx); + #[cfg(not(unix))] + { + let _ = (server, pty_session_id); + log::warn!("AdoptDaemonSession ignored: daemon sessions are unix-only"); + } + } } } @@ -5445,6 +5465,18 @@ impl Workspace { (server.clone(), node_id.clone(), fallback_kind) } }; + + // Native persistent remote-session layer (Option B): a host with + // `session_resilience` enabled opens directly as a daemon-hosted session + // instead of a local PTY running `ssh`. Falls through to the normal path + // if the host isn't resilient or the auth isn't headless-capable. + #[cfg(unix)] + { + if self.try_open_daemon_ssh_terminal(&server_for_connection, ctx) { + return; + } + } + let cmd = warp_ssh_manager::build_ssh_command_line(&server_for_connection); let window_id = ctx.window_id(); @@ -5533,6 +5565,175 @@ impl Workspace { }); } + /// Native persistent remote-session path (Stage 2, Option B). Returns `true` + /// if the host was opened as a daemon-hosted session (caller should stop), or + /// `false` to fall through to the ordinary local-PTY SSH path. v1 only takes + /// the daemon path for resilient hosts with headless-capable (key) auth. + #[cfg(unix)] + fn try_open_daemon_ssh_terminal( + &mut self, + server: &warp_ssh_manager::SshServerInfo, + ctx: &mut ViewContext, + ) -> bool { + use crate::remote_server::headless_connect; + + if !server.session_resilience.is_enabled() { + return false; + } + if !headless_connect::is_headless_capable(server) { + log::info!( + "Host {} has session_resilience enabled but its auth is not \ + headless-capable (v1: key auth only); using the normal SSH path", + server.host + ); + return false; + } + + let session_id = headless_connect::alloc_daemon_session_id(); + let request = crate::terminal::daemon_tty::DaemonSessionRequest { + connection_session_id: session_id, + open_params: crate::terminal::daemon_tty::OpenSessionParams { + // Per-host scrollback ceiling (MiB → bytes); 0 → daemon default. + ring_ceiling_bytes: (server.ring_ceiling_mb > 0) + .then(|| server.ring_ceiling_mb as u64 * 1024 * 1024), + // Honor the host's saved startup command on the daemon path too, + // matching the local-PTY SSH path (run once after the session opens). + startup_command: server.startup_command.clone(), + ..Default::default() + }, + adopt_pty_session_id: None, + }; + + // Create the daemon-backed tab first: its event loop subscribes for + // `SessionConnected` on `session_id` before we kick off the connect. + self.add_tab_with_pane_layout( + PanesLayout::SingleTerminal(Box::new(NewTerminalOptions { + hide_homepage: true, + daemon_request: Some(request), + ..Default::default() + })), + Arc::new(HashMap::new()), + None, /* custom_tab_title */ + ctx, + ); + + self.spawn_daemon_session_connect(server.clone(), session_id, ctx); + true + } + + /// Adopts an already-running daemon session in a new tab: attaches to + /// `pty_session_id` (replay + live) instead of opening a fresh one. This is + /// the entry point the multi-session sidebar calls when the user picks a + /// listed session (the listing comes from `RemoteServerClient::list_sessions`). + #[cfg(unix)] + pub fn adopt_daemon_session( + &mut self, + server: warp_ssh_manager::SshServerInfo, + pty_session_id: String, + ctx: &mut ViewContext, + ) { + use crate::remote_server::headless_connect; + + // Prune entries whose tab has since closed, then, if this session is + // already open in a tab, focus that tab instead of opening a second view + // onto it (a second adopt would split input/output across two tabs). + let live_pg_ids: Vec = self.tabs.iter().map(|t| t.pane_group.id()).collect(); + self.adopted_daemon_sessions + .retain(|_, pg_id| live_pg_ids.contains(pg_id)); + if let Some(pg_id) = self.adopted_daemon_sessions.get(&pty_session_id).copied() { + if let Some(index) = self.tabs.iter().position(|t| t.pane_group.id() == pg_id) { + log::info!("daemon adopt: session {pty_session_id} already open — focusing its tab"); + self.activate_tab(index, ctx); + return; + } + } + + let session_id = headless_connect::alloc_daemon_session_id(); + let request = crate::terminal::daemon_tty::DaemonSessionRequest { + connection_session_id: session_id, + open_params: crate::terminal::daemon_tty::OpenSessionParams::default(), + adopt_pty_session_id: Some(pty_session_id.clone()), + }; + + self.add_tab_with_pane_layout( + PanesLayout::SingleTerminal(Box::new(NewTerminalOptions { + hide_homepage: true, + daemon_request: Some(request), + ..Default::default() + })), + Arc::new(HashMap::new()), + None, /* custom_tab_title */ + ctx, + ); + + // The freshly added tab is the active one — remember which tab hosts this + // adopted session so a later duplicate adopt focuses it. + if let Some(pane_group) = self.get_pane_group_view(self.active_tab_index) { + self.adopted_daemon_sessions + .insert(pty_session_id, pane_group.id()); + } + + self.spawn_daemon_session_connect(server, session_id, ctx); + } + + /// Establishes the headless SSH ControlMaster for a daemon session, then + /// connects the remote-server session on `session_id`. The daemon terminal + /// (already created) issues `OpenSession` once `SessionConnected` fires. + #[cfg(unix)] + fn spawn_daemon_session_connect( + &mut self, + server: warp_ssh_manager::SshServerInfo, + session_id: SessionId, + ctx: &mut ViewContext, + ) { + use crate::remote_server::auth_context::server_api_auth_context; + use crate::remote_server::headless_connect; + use crate::remote_server::manager::{RemoteServerInitPhase, RemoteServerManager}; + use crate::remote_server::ssh_transport::SshTransport; + + let auth_context = std::sync::Arc::new(server_api_auth_context( + AuthStateProvider::as_ref(ctx).get().clone(), + )); + let socket_path = headless_connect::control_socket_path(&server); + let host = server.host.clone(); + // Kept for the transport so reconnect can re-heal a dead ControlMaster. + let server_for_transport = server.clone(); + + // Off the main thread: bring up the ControlMaster + ensure the + // remote-server binary is installed, then connect the session. + ctx.spawn( + headless_connect::prepare_daemon_transport( + server, + socket_path.clone(), + auth_context.clone(), + ), + move |_workspace, result, ctx| match result { + Ok(()) => { + log::info!( + "daemon connect [{host}]: transport ready — connecting session {session_id:?}" + ); + let transport = SshTransport::new(socket_path, auth_context.clone()) + .with_self_heal(server_for_transport); + RemoteServerManager::handle(ctx).update(ctx, |mgr, ctx| { + // Persistent: a transport drop (ssh slave exit) must trigger + // reconnect — the daemon keeps the session alive (§9). + mgr.mark_session_persistent(session_id); + mgr.connect_session(session_id, transport, auth_context, ctx); + }); + } + Err(e) => { + log::error!("daemon connect [{host}] failed: {e}"); + // The daemon tab is already open and its event loop is waiting + // for manager events. Surface the failure so it shows the error + // instead of hanging on a blank view. + RemoteServerManager::handle(ctx).update(ctx, |mgr, ctx| { + mgr.fail_session(session_id, RemoteServerInitPhase::Connect, e, ctx); + }); + } + }, + ); + } + fn handle_right_panel_event(&mut self, event: RightPanelEvent, ctx: &mut ViewContext) { #[cfg(feature = "local_fs")] match event { @@ -5786,14 +5987,14 @@ impl Workspace { .unwrap_or_else(|| "".to_string()); format!( - "Zap 日志导出\n\ - 生成时间: {now}\n\ - 版本: {version}\n\ + "Zaplex Log Export\n\ + Generated at: {now}\n\ + Version: {version}\n\ channel: {channel}\n\ - 执行模式: {execution_mode:?}\n\ + Execution mode: {execution_mode:?}\n\ OS: {os}\n\ ARCH: {arch}\n\ - 日志目录: {log_dir_str}\n", + Log directory: {log_dir_str}\n", os = std::env::consts::OS, arch = std::env::consts::ARCH, ) @@ -6416,7 +6617,7 @@ impl Workspace { } /// The tab bar overflow menu is the context menu that appears when - /// a user clicks "Update Zap" in the top right of the tab bar. + /// a user clicks "Update Zaplex" in the top right of the tab bar. pub fn toggle_tab_bar_overflow_menu(&mut self, ctx: &mut ViewContext) { if self.show_tab_bar_overflow_menu { self.close_tab_bar_overflow_menu(ctx); @@ -6540,7 +6741,7 @@ impl Workspace { /// If the user is new and therefore has not seen the in app onboarding, /// triggers the welcome block to be shown after bootstrapping is completed. fn check_and_trigger_onboarding(&mut self, ctx: &mut ViewContext) -> bool { - // Zap: removed the first-launch agentic-suggestions welcome-block tutorial. Still marks the user as + // Zaplex: removed the first-launch agentic-suggestions welcome-block tutorial. Still marks the user as // onboarded, to avoid downstream code (e.g. the telemetry banner) treating an existing user as new. if !self.auth_state.is_onboarded().unwrap_or_default() { AuthManager::handle(ctx).update(ctx, |auth_manager, ctx| { @@ -6595,7 +6796,7 @@ impl Workspace { ctx.notify(); } - /// Opens the Zap Drive object identified by `uid` in a new pane + /// Opens the Zaplex Drive object identified by `uid` in a new pane /// if it has a pane representation. fn open_warp_drive_object_in_new_pane(&mut self, uid: &ObjectUid, ctx: &mut ViewContext) { let Some(object) = ObjectStoreModel::as_ref(ctx).get_by_uid(uid) else { @@ -6607,7 +6808,7 @@ impl Workspace { ObjectType::Notebook => { self.open_notebook( &NotebookSource::Existing(sync_id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); @@ -6615,7 +6816,7 @@ impl Workspace { ObjectType::Workflow => { self.open_workflow_in_pane( &WorkflowOpenSource::Existing(sync_id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), WorkflowViewMode::View, ctx, ); @@ -6644,7 +6845,7 @@ impl Workspace { pub fn open_notebook( &mut self, source: &NotebookSource, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, default_to_new_pane: bool, ) { @@ -6666,7 +6867,7 @@ impl Workspace { ); } // TODO(zap-cloud-removal Phase 5): this invitee_email/source notebook - // invitation path no longer has a UI entry point, but `ZapDriveObjectSettings.invitee_email` + // invitation path no longer has a UI entry point, but `ZaplexDriveObjectSettings.invitee_email` // is still passed in by the URL handler / drag-drop path. When the invitee concept is // retired in Phase 5, remove the field from the settings struct as well. let _ = settings; @@ -6688,7 +6889,7 @@ impl Workspace { }); } - // Get notebook ID to set Zap drive index selected state + // Get notebook ID to set Zaplex drive index selected state if let NotebookSource::Existing(notebook_id) = source { let focused_folder_id = settings.focused_folder_id.map(SyncId::ServerId); if !notebook_already_open && !default_to_new_pane { @@ -6717,11 +6918,11 @@ impl Workspace { } } - /// Open a Zap Drive workflow in response to an intent URL. + /// Open a Zaplex Drive workflow in response to an intent URL. pub fn open_workflow_from_intent( &mut self, workflow_id: SyncId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) { // If running workflows is supported, do so. Otherwise, or if the workflow isn't in memory, @@ -6764,7 +6965,7 @@ impl Workspace { pub fn open_workflow_in_pane( &mut self, source: &WorkflowOpenSource, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, mode: WorkflowViewMode, ctx: &mut ViewContext, ) { @@ -7388,7 +7589,7 @@ impl Workspace { }); } - // Zap Wave 7-3: `open_environment_management_pane` was physically removed along with the + // Zaplex Wave 7-3: `open_environment_management_pane` was physically removed along with the // ambient-agent UI subsystem. pub(super) fn active_session_view( @@ -7418,7 +7619,7 @@ impl Workspace { ctx.notify(); } - /// Find an active session and pre-fill the input editor the Zap executable with the + /// Find an active session and pre-fill the input editor the Zaplex executable with the /// [`warp_cli::Command::DumpDebugInfo`] subcommand. fn dump_debug_info(&mut self, ctx: &mut ViewContext) { if let Some(exec) = std::env::current_exe() @@ -7457,7 +7658,7 @@ impl Workspace { } } - /// Install the Zap CLI by creating a symlink in /usr/local/bin + /// Install the Zaplex CLI by creating a symlink in /usr/local/bin #[cfg(target_os = "macos")] fn install_cli(&mut self, ctx: &mut ViewContext) { ctx.spawn(async { cli_install::install_cli() }, |view, result, ctx| { @@ -7487,7 +7688,7 @@ impl Workspace { }); } - /// Uninstall the Zap CLI by removing the symlink from /usr/local/bin + /// Uninstall the Zaplex CLI by removing the symlink from /usr/local/bin #[cfg(target_os = "macos")] fn uninstall_cli(&mut self, ctx: &mut ViewContext) { ctx.spawn( @@ -7585,21 +7786,21 @@ impl Workspace { self.current_workspace_state.is_warp_drive_open = if toggle { !was_warp_drive_open } else { true }; - // Set selected object to None upon toggle close of Zap Drive + // Set selected object to None upon toggle close of Zaplex Drive if !self.current_workspace_state.is_warp_drive_open { self.set_selected_object(None, ctx); self.focus_active_tab(ctx); } - // Reset focused index when opening/toggling Zap Drive open + // Reset focused index when opening/toggling Zaplex Drive open if self.current_workspace_state.is_warp_drive_open { self.reset_focused_index_in_warp_drive(true, ctx); } ctx.notify(); - // Telemetry and welcome tip logic is only for when the user explicitly opens Zap Drive - // AND zap drive wasn't open before. There are other scenarios where we open Zap Drive like: + // Telemetry and welcome tip logic is only for when the user explicitly opens Zaplex Drive + // AND zap drive wasn't open before. There are other scenarios where we open Zaplex Drive like: // new user onboarding, user joins a team, etc so we want to avoid counting those. if explicit_user_action && !was_warp_drive_open @@ -7614,7 +7815,7 @@ impl Workspace { ); self.tips_completed.update(ctx, |tips_completed, ctx| { mark_feature_used_and_write_to_user_defaults( - Tip::Action(TipAction::ZapDrive), + Tip::Action(TipAction::ZaplexDrive), tips_completed, ctx, ); @@ -9228,7 +9429,7 @@ impl Workspace { // Proc same behavior as DrivePanelEvent::RunWorkflow self.run_cloud_workflow_in_active_input( workflow.clone(), - WorkflowSelectionSource::ZapDrive, + WorkflowSelectionSource::ZaplexDrive, TerminalSessionFallbackBehavior::default(), ctx, ); @@ -10573,7 +10774,7 @@ impl Workspace { pub fn add_tab_for_cloud_notebook( &mut self, notebook_id: SyncId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) { // TODO: We should validate that this notebook exists and fallback if it doesn't @@ -10591,7 +10792,7 @@ impl Workspace { fn add_tab_for_cloud_workflow( &mut self, workflow_id: SyncId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) { let panes_layout = PanesLayout::Snapshot(Box::new(PaneNodeSnapshot::Leaf(LeafSnapshot { @@ -11969,7 +12170,7 @@ impl Workspace { _ => self.open_navigation_palette(ctx), }, PaletteMode::LaunchConfig => self.open_launch_config_palette(ctx), - PaletteMode::ZapDrive => self.open_warp_drive_palette(ctx), + PaletteMode::ZaplexDrive => self.open_warp_drive_palette(ctx), PaletteMode::Files => self.open_files_palette(ctx), PaletteMode::Conversations => self.open_conversations_palette(ctx), } @@ -12062,7 +12263,7 @@ impl Workspace { } CommandPaletteEvent::OpenNotebook { id } => self.open_notebook( &NotebookSource::Existing(*id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ), @@ -12104,11 +12305,11 @@ impl Workspace { } /// This function is used when we set a selected object, which is an object open in an active pane. - /// We do not want to focus Zap Drive, instead we want to focus the editor of the open object. + /// We do not want to focus Zaplex Drive, instead we want to focus the editor of the open object. fn view_in_warp_drive(&mut self, item_id: WarpDriveItemId, ctx: &mut ViewContext) { self.open_left_panel(ctx); self.left_panel_view.update(ctx, |left_panel, ctx| { - left_panel.handle_action(&LeftPanelAction::ZapDrive, ctx); + left_panel.handle_action(&LeftPanelAction::ZaplexDrive, ctx); }); if let WarpDriveItemId::Object(object_id) = item_id { @@ -12123,7 +12324,7 @@ impl Workspace { }); } - /// This function is used when we want to view an item in Zap Drive AND focus Zap Drive. + /// This function is used when we want to view an item in Zaplex Drive AND focus Zaplex Drive. pub fn view_in_and_focus_warp_drive( &mut self, item_id: WarpDriveItemId, @@ -12169,7 +12370,7 @@ impl Workspace { } fn handle_changelog_event(&mut self, _event: &ChangelogEvent, _ctx: &mut ViewContext) { - // Zap is a localized fork; it does not rely on the private changelog service and does not pop up a toast/resource-center after updating. + // Zaplex is a localized fork; it does not rely on the private changelog service and does not pop up a toast/resource-center after updating. } fn manual_check_for_update(&self, ctx: &mut ViewContext) { @@ -12224,10 +12425,10 @@ impl Workspace { ctx: &mut ViewContext, ) { match event { - // Zap decentralized fork: the `CheckForUpdate` / `ZapDrive` event arms were physically + // Zaplex decentralized fork: the `CheckForUpdate` / `ZaplexDrive` event arms were physically // removed along with the same-named variants in `SettingsViewEvent`. Manual update checks // can still be triggered by `WorkspaceAction::CheckForUpdate` (the `workspace:check_for_updates` binding); - // Zap Drive can still be triggered by `WorkspaceAction::ZapDrive`. + // Zaplex Drive can still be triggered by `WorkspaceAction::ZaplexDrive`. SettingsViewEvent::Pane(_) | SettingsViewEvent::StartResize => {} SettingsViewEvent::ShowToast { message, flavor } => { self.toast_stack.update(ctx, |toast_stack, ctx| { @@ -12468,7 +12669,7 @@ impl Workspace { pane_group::Event::OpenCloudWorkflowForEdit(workflow_id) => self .open_workflow_with_existing( *workflow_id, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, ), pane_group::Event::OpenWorkflowModalWithTemporary(workflow) => { @@ -12530,7 +12731,7 @@ impl Workspace { } => { self.move_to_drive_space(*object_type_and_id, *space, ctx); } - pane_group::Event::ZapDriveLink { + pane_group::Event::ZaplexDriveLink { open_warp_drive_args, } => { let object_found = ObjectStoreModel::as_ref(ctx) @@ -12578,7 +12779,7 @@ impl Workspace { ctx, ), _ => { - log::warn!("Attempted to open an unsupported Zap Drive link") + log::warn!("Attempted to open an unsupported Zaplex Drive link") } } } @@ -13107,7 +13308,7 @@ impl Workspace { ctx.notify(); } pane_group::Event::ClearHoveredTabIndex => self.hovered_tab_index = None, - pane_group::Event::ZapDriveObjectInPane(uid) => { + pane_group::Event::ZaplexDriveObjectInPane(uid) => { self.open_warp_drive_object_in_new_pane(uid, ctx); } pane_group::Event::OpenSuggestedAgentModeWorkflowModal { workflow_and_id } => { @@ -13310,7 +13511,7 @@ impl Workspace { self.left_panel_view .read(ctx, |left_panel, _| match target_view { LeftPanelTargetView::FileTree => left_panel.is_file_tree_active(), - LeftPanelTargetView::ZapDrive => left_panel.is_warp_drive_active(), + LeftPanelTargetView::ZaplexDrive => left_panel.is_warp_drive_active(), }); if self.active_tab_pane_group().as_ref(ctx).left_panel_open && is_target_active { @@ -13325,7 +13526,7 @@ impl Workspace { self.left_panel_view.update(ctx, |left_panel, ctx| { let action = match target_view { LeftPanelTargetView::FileTree => LeftPanelAction::ProjectExplorer, - LeftPanelTargetView::ZapDrive => LeftPanelAction::ZapDrive, + LeftPanelTargetView::ZaplexDrive => LeftPanelAction::ZaplexDrive, }; left_panel.handle_action_with_force_open(&action, *force_open, ctx); }); @@ -13349,7 +13550,7 @@ impl Workspace { ctx, ); } - // Zap: Ctrl/Cmd+clicking a remote SSH file path in the terminal opens it via the buffer-sync protocol. + // Zaplex: Ctrl/Cmd+clicking a remote SSH file path in the terminal opens it via the buffer-sync protocol. #[cfg(all(feature = "local_tty", feature = "local_fs"))] pane_group::Event::OpenRemoteFileFromTerminal { remote_path, @@ -13368,7 +13569,7 @@ impl Workspace { pane_group::Event::OpenAgentProfileEditor { profile_id } => { self.open_execution_profile_editor_pane(None, *profile_id, ctx); } - // Zap Wave 7-3: the `pane_group::Event::OpenEnvironmentManagementPane` handler was physically + // Zaplex Wave 7-3: the `pane_group::Event::OpenEnvironmentManagementPane` handler was physically // removed along with the ambient-agent UI subsystem. pane_group::Event::LeftPanelToggled { is_open } => { // Only handle visibility changes from the active pane group. @@ -13634,7 +13835,7 @@ impl Workspace { } /// Insert the given command that should open a subshell. And set a flag that we should - /// automatically bootstrap AKA "warpify" that subshell if we support it. No-op if there is + /// automatically bootstrap AKA "zaplexify" that subshell if we support it. No-op if there is /// no active terminal session. pub fn insert_subshell_command_and_bootstrap_if_supported( &mut self, @@ -13730,7 +13931,7 @@ impl Workspace { // Check whether this remote session has an active remote server // connection (or is in the process of connecting). This is only - // true for Auto SSH Warpification (mode 1) sessions where + // true for Auto SSH Zaplexification (mode 1) sessions where // `connect_session` was called at `InitShell` time. let has_remote_server = is_remote && FeatureFlag::SshRemoteServer.is_enabled() @@ -13838,7 +14039,7 @@ impl Workspace { DrivePanelEvent::RunWorkflow(workflow) => { self.run_cloud_workflow_in_active_input( workflow.as_ref().clone(), - WorkflowSelectionSource::ZapDrive, + WorkflowSelectionSource::ZaplexDrive, TerminalSessionFallbackBehavior::default(), ctx, ); @@ -13866,27 +14067,27 @@ impl Workspace { DrivePanelEvent::OpenWorkflowModalWithWorkflowObject(workflow_id) => { self.open_workflow_with_existing( *workflow_id, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, ); } DrivePanelEvent::OpenSearch => { self.open_palette_action( - PaletteMode::ZapDrive, - PaletteSource::ZapDrive, + PaletteMode::ZaplexDrive, + PaletteSource::ZaplexDrive, None, ctx, ); } DrivePanelEvent::OpenNotebook(source) => { - self.open_notebook(source, &ZapDriveObjectSettings::default(), ctx, true) + self.open_notebook(source, &ZaplexDriveObjectSettings::default(), ctx, true) } DrivePanelEvent::OpenEnvVarCollection(source) => { self.open_env_var_collection(source, false, ctx) } DrivePanelEvent::OpenWorkflowInPane(source, mode) => self.open_workflow_in_pane( source, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), *mode, ctx, ), @@ -13894,7 +14095,7 @@ impl Workspace { self.open_ai_fact_collection_pane(None, None, ctx); send_telemetry_from_ctx!( TelemetryEvent::KnowledgePaneOpened { - entrypoint: KnowledgePaneEntrypoint::ZapDrive, + entrypoint: KnowledgePaneEntrypoint::ZaplexDrive, }, ctx ); @@ -13904,7 +14105,7 @@ impl Workspace { send_telemetry_from_ctx!( TelemetryEvent::MCPServerCollectionPaneOpened { - entrypoint: MCPServerCollectionPaneEntrypoint::ZapDrive, + entrypoint: MCPServerCollectionPaneEntrypoint::ZaplexDrive, }, ctx ); @@ -13913,7 +14114,7 @@ impl Workspace { ctx.focus(&self.left_panel_view); } DrivePanelEvent::OpenSharedObjectsCreationDeniedModal(_, _) => { - // Zap: the cloud Drive quota-rejection dialog has been removed, so the event is simply ignored + // Zaplex: the cloud Drive quota-rejection dialog has been removed, so the event is simply ignored } DrivePanelEvent::AttachPlanAsContext(id) => { self.attach_plan_as_context(*id, ctx); @@ -14307,7 +14508,7 @@ impl Workspace { AcceptNotebook(sync_id) => { self.open_notebook( &NotebookSource::Existing(*sync_id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); @@ -14319,7 +14520,7 @@ impl Workspace { ctx, ); } - ZapAI => { + ZaplexAI => { if !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) { return; } @@ -15011,7 +15212,7 @@ impl Workspace { } fn set_selected_object(&mut self, id: Option, ctx: &mut ViewContext) { - // Set Zap drive index selected state + // Set Zaplex drive index selected state self.update_warp_drive_view(ctx, |drive_panel, ctx| { drive_panel.set_selected_object(id, ctx); }); @@ -15126,7 +15327,7 @@ impl Workspace { let command = code.trim().to_string(); let args_state = ArgumentsState::for_command_workflow(&Default::default(), command.clone()); - let workflow = Workflow::new("Command from Zap AI", command) + let workflow = Workflow::new("Command from Zaplex AI", command) .with_arguments(args_state.arguments); self.run_workflow_in_active_input( &WorkflowType::AIGenerated { @@ -15153,11 +15354,11 @@ impl Workspace { fn handle_zap_launch_modal_event( &mut self, - event: &ZapLaunchModalEvent, + event: &ZaplexLaunchModalEvent, ctx: &mut ViewContext, ) { match event { - ZapLaunchModalEvent::Close => { + ZaplexLaunchModalEvent::Close => { OneTimeModalModel::handle(ctx).update(ctx, |model, ctx| { model.mark_zap_launch_modal_dismissed(ctx); }); @@ -15485,7 +15686,7 @@ impl Workspace { ctx.notify(); } - // Zap: removed open_shared_objects_creation_denied_modal (the cloud Drive quota-rejection dialog) + // Zaplex: removed open_shared_objects_creation_denied_modal (the cloud Drive quota-rejection dialog) /// Opens the workflow modal in the provided space and folder with no existing content (i.e. a new workflow modal). fn open_workflow_modal( @@ -15503,7 +15704,7 @@ impl Workspace { let owner = match space { Space::Team { team_uid } => { if !UserWorkspaces::has_capacity_for_shared_workflows(team_uid, ctx, 1) { - // Zap: the cloud quota-rejection dialog has been removed, so just return + // Zaplex: the cloud quota-rejection dialog has been removed, so just return return; } @@ -15534,7 +15735,7 @@ impl Workspace { fn open_workflow_with_existing( &mut self, workflow_id: SyncId, - settings: &ZapDriveObjectSettings, + settings: &ZaplexDriveObjectSettings, ctx: &mut ViewContext, ) { let source = WorkflowOpenSource::Existing(workflow_id); @@ -15554,7 +15755,7 @@ impl Workspace { }; self.open_workflow_in_pane( &source, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), WorkflowViewMode::Create, ctx, ); @@ -15575,7 +15776,7 @@ impl Workspace { }; self.open_workflow_in_pane( &source, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), WorkflowViewMode::Create, ctx, ); @@ -15641,7 +15842,7 @@ impl Workspace { let body = appearance .ui_builder() .wrappable_text( - "Ask Zap AI to explain errors, suggest commands or write scripts.".to_owned(), + "Ask Zaplex AI to explain errors, suggest commands or write scripts.".to_owned(), true, ) .with_style(UiComponentStyles { @@ -15793,7 +15994,7 @@ impl Workspace { .left_panel_views .first() .copied() - .unwrap_or(ToolPanelView::ZapDrive) + .unwrap_or(ToolPanelView::SshManager) { ToolPanelView::ProjectExplorer => { crate::t!("workspace-left-panel-project-explorer") @@ -15801,7 +16002,7 @@ impl Workspace { ToolPanelView::GlobalSearch { .. } => { crate::t!("workspace-left-panel-global-search") } - ToolPanelView::ZapDrive => crate::t!("workspace-left-panel-warp-drive"), + ToolPanelView::ZaplexDrive => crate::t!("workspace-left-panel-warp-drive"), ToolPanelView::ConversationListView => { crate::t!("workspace-left-panel-agent-conversations") } @@ -15814,6 +16015,7 @@ impl Workspace { ToolPanelView::SkillManager => { crate::t!("workspace-left-panel-skill-manager") } + ToolPanelView::Cockpit => crate::t!("workspace-left-panel-cockpit"), } } else { crate::t!("workspace-tools-panel-tooltip") @@ -15862,7 +16064,7 @@ impl Workspace { .left_panel_views .first() .copied() - .unwrap_or(ToolPanelView::ZapDrive) + .unwrap_or(ToolPanelView::SshManager) { ToolPanelView::ProjectExplorer => { crate::t!("workspace-left-panel-project-explorer") @@ -15870,7 +16072,7 @@ impl Workspace { ToolPanelView::GlobalSearch { .. } => { crate::t!("workspace-left-panel-global-search") } - ToolPanelView::ZapDrive => crate::t!("workspace-left-panel-warp-drive"), + ToolPanelView::ZaplexDrive => crate::t!("workspace-left-panel-warp-drive"), ToolPanelView::ConversationListView => { crate::t!("workspace-left-panel-agent-conversations") } @@ -15883,6 +16085,7 @@ impl Workspace { ToolPanelView::SkillManager => { crate::t!("workspace-left-panel-skill-manager") } + ToolPanelView::Cockpit => crate::t!("workspace-left-panel-cockpit"), } } else { crate::t!("workspace-tools-panel-tooltip") @@ -16188,7 +16391,7 @@ impl Workspace { .is_user_web_anonymous_user() .unwrap_or_default(); - // Simplified mode for viewing Zap Drive objects, shared sessions, or conversation transcripts on WASM + // Simplified mode for viewing Zaplex Drive objects, shared sessions, or conversation transcripts on WASM #[cfg(target_family = "wasm")] if let Some(content_type) = self.get_simplified_wasm_tab_bar_content(ctx) { // Use MainAxisAlignment::SpaceBetween and expand to fill width @@ -16197,10 +16400,10 @@ impl Workspace { .with_main_axis_size(MainAxisSize::Max); let bg_color = blended_colors::neutral_1(appearance.theme()); - // Left: Zap logo - clickable to link to warp.dev + // Left: Zaplex logo - clickable to link to warp.dev let warp_logo = Hoverable::new(self.mouse_states.warp_logo.clone(), |_state| { ConstrainedBox::new( - warp_core::ui::Icon::Zap + warp_core::ui::Icon::Zaplex .to_warpui_icon(appearance.theme().foreground()) .finish(), ) @@ -16215,7 +16418,7 @@ impl Workspace { .finish(); tab_bar.add_child(warp_logo); - // Right: Info button + run history button (for agent sessions) + "Open in Zap" button + // Right: Info button + run history button (for agent sessions) + "Open in Zaplex" button let mut right_row = Flex::row() .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_main_axis_size(MainAxisSize::Min); @@ -16239,7 +16442,7 @@ impl Workspace { ); } - // Hide "Open in Zap" button on mobile devices + // Hide "Open in Zaplex" button on mobile devices if !warpui::platform::wasm::is_mobile_device() { right_row.add_child(ChildView::new(&self.open_in_warp_button).finish()); } @@ -16715,7 +16918,7 @@ impl Workspace { .finish(), ); } else { - // Decentralized fork: no longer renders the Zap Essentials (lightbulb) button, keeping only the settings gear. + // Decentralized fork: no longer renders the Zaplex Essentials (lightbulb) button, keeping only the settings gear. target.add_child( Container::new(self.render_settings_button(appearance)) .with_margin_left(TAB_BAR_PADDING_LEFT) @@ -17450,12 +17653,12 @@ impl Workspace { AISettings::as_ref(app) .is_any_ai_enabled(app) .then(|| WorkspaceBannerButtonDetails { - text: "Fix with Oz".to_owned(), + text: "Fix with AI".to_owned(), action: WorkspaceAction::FixSettingsWithOz { error_description: error.to_string(), }, variant: BannerButtonVariant::Naked, - icon: Some(Icon::Oz), + icon: Some(Icon::AiAssistant), more_info_button_action: None, }); Some(WorkspaceBannerFields { @@ -18184,7 +18387,7 @@ impl Workspace { let general_settings = GeneralSettings::as_ref(app); let theme_settings = ThemeSettings::as_ref(app); let ssh_settings = SshSettings::as_ref(app); - let warpify_settings = WarpifySettings::as_ref(app); + let zaplexify_settings = ZaplexifySettings::as_ref(app); let terminal_settings = TerminalSettings::as_ref(app); let pane_settings = PaneSettings::as_ref(app); let keys_settings = KeysSettings::as_ref(app); @@ -18225,7 +18428,7 @@ impl Workspace { .value() .same_line_prompt_enabled() { - context.set.insert(flags::WARP_SAME_LINE_PROMPT_FLAG); + context.set.insert(flags::ZAPLEX_SAME_LINE_PROMPT_FLAG); } if *ssh_settings.enable_legacy_ssh_wrapper.value() { @@ -18237,7 +18440,7 @@ impl Workspace { context.set.insert(flags::SSH_AUTO_DISCOVERY_CONTEXT_FLAG); } - if *warpify_settings.use_ssh_tmux_wrapper.value() { + if *zaplexify_settings.use_ssh_tmux_wrapper.value() { context.set.insert(flags::SSH_TMUX_WRAPPER_CONTEXT_FLAG); } @@ -18532,7 +18735,7 @@ impl Workspace { fn process_updated_sync_state(&self, ctx: &mut ViewContext) { // If there is an active terminal, return a sync event that all // other synced terminals should apply to match it. - // If there is no active terminal (like when all Zap windows are + // If there is no active terminal (like when all Zaplex windows are // minimized), return an event to start syncing. let sync_event = self .active_tab_pane_group() @@ -18618,13 +18821,21 @@ impl Workspace { /// Computes the list of available left panel views based on current AI settings and feature flags. fn compute_left_panel_views(ctx: &AppContext) -> Vec { let mut views = vec![]; - if FeatureFlag::AgentViewConversationListView.is_enabled() - && AISettings::as_ref(ctx).is_any_ai_enabled(ctx) - && *AISettings::as_ref(ctx).show_conversation_history - { - views.push(ToolPanelView::ConversationListView); + + // Cockpit (account/usage/attention overview) leads the toolbelt — the + // glanceable daily-driver. Gated by the cockpit.enabled setting. + if *crate::cockpit::CockpitSettings::as_ref(ctx).enabled { + views.push(ToolPanelView::Cockpit); } + // Remote-dev core: SSH hosts, then remote files. + // openWarp-only: the SSH manager, no feature flag, always shown by default. + views.push(ToolPanelView::SshManager); + if FeatureFlag::ServerFileBrowser.is_enabled() && FeatureFlag::SshRemoteServer.is_enabled() { + views.push(ToolPanelView::ServerFileBrowser); + } + + // Local workspace tools. if cfg!(feature = "local_fs") && *CodeSettings::as_ref(ctx).show_project_explorer.value() { views.push(ToolPanelView::ProjectExplorer); } @@ -18636,18 +18847,23 @@ impl Workspace { entry_focus: GlobalSearchEntryFocus::Results, }); } - if WarpDriveSettings::is_warp_drive_enabled(ctx) { - views.push(ToolPanelView::ZapDrive); - } - // openWarp-only: the SSH manager, no feature flag, always shown by default. - views.push(ToolPanelView::SshManager); - if FeatureFlag::ServerFileBrowser.is_enabled() && FeatureFlag::SshRemoteServer.is_enabled() { - views.push(ToolPanelView::ServerFileBrowser); + + // Agent conversation history (local, BYOP). + if FeatureFlag::AgentViewConversationListView.is_enabled() + && AISettings::as_ref(ctx).is_any_ai_enabled(ctx) + && *AISettings::as_ref(ctx).show_conversation_history + { + views.push(ToolPanelView::ConversationListView); } + // openWarp-only: the Skill manager, no feature flag, shown by default in local_fs builds. if cfg!(feature = "local_fs") { views.push(ToolPanelView::SkillManager); } + + // Zaplex Drive (inherited Warp Drive) is preserved as a sidebar-panel template + // (enum variant, render arms and modules kept) but is no longer surfaced in the + // shipped toolbelt. See docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md views } @@ -18661,7 +18877,7 @@ impl Workspace { }); } - /// Opens a given URL in the desktop Zap app if installed, or redirects to download page. + /// Opens a given URL in the desktop Zaplex app if installed, or redirects to download page. #[cfg(target_family = "wasm")] fn open_link_on_desktop(&mut self, url: &Url, ctx: &mut ViewContext) { use crate::settings::app_installation_detection::{ @@ -18684,7 +18900,7 @@ impl Workspace { // Many users' browser settings will block Local Network Access so this will end up redirecting to download page, // even if they have the app installed. let toast_message = format!( - "Have Zap installed but redirecting to download page?\nEnable Local Network Access for the Zap web launcher in your browser." + "Have Zaplex installed but redirecting to download page?\nEnable Local Network Access for the Zaplex web launcher in your browser." ); self.toast_stack.update(ctx, |toast_stack, ctx| { toast_stack.add_persistent_toast(DismissibleToast::default(toast_message), ctx) @@ -19116,7 +19332,7 @@ impl TypedActionView for Workspace { owner: personal_drive, initial_folder_id: None, }, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); @@ -19146,7 +19362,7 @@ impl TypedActionView for Workspace { }; self.open_workflow_in_pane( &source, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), WorkflowViewMode::Create, ctx, ); @@ -19172,9 +19388,9 @@ impl TypedActionView for Workspace { self.finish_tab_rename(ctx); self.current_workspace_state.is_tab_being_dragged = true; } - ZapDrive => { + ZaplexDrive => { if WarpDriveSettings::is_warp_drive_enabled(ctx) { - self.open_left_panel_view(&LeftPanelAction::ZapDrive, ctx); + self.open_left_panel_view(&LeftPanelAction::ZaplexDrive, ctx); } } ToggleLeftPanel => { @@ -19212,7 +19428,7 @@ impl TypedActionView for Workspace { ctx ); } else if warp_drive_active { - // Tools panel opened with Zap Drive as the active view + // Tools panel opened with Zaplex Drive as the active view send_telemetry_from_ctx!( TelemetryEvent::WarpDriveOpened { source: WarpDriveSource::LeftPanelToolbelt, @@ -19650,7 +19866,7 @@ impl TypedActionView for Workspace { }); self.open_workflow_with_existing( *workflow_id, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, ); } @@ -19794,7 +20010,7 @@ impl TypedActionView for Workspace { ctx ); } - // Zap Wave 7-3: the `OpenEnvironmentManagementPane` WorkspaceAction handler was physically + // Zaplex Wave 7-3: the `OpenEnvironmentManagementPane` WorkspaceAction handler was physically // removed along with the ambient-agent UI subsystem. ToggleAIDocumentPane { document_id, @@ -19932,7 +20148,7 @@ impl TypedActionView for Workspace { } OpenNotebook { id } => self.open_notebook( &NotebookSource::Existing(*id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ), @@ -20082,7 +20298,7 @@ impl TypedActionView for Workspace { }; self.open_workflow_in_pane( &source, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), WorkflowViewMode::Create, ctx, ); @@ -20113,7 +20329,7 @@ impl TypedActionView for Workspace { } #[cfg(debug_assertions)] OpenZapLaunchModal => { - // Force open the Zap launch modal for debugging + // Force open the Zaplex launch modal for debugging OneTimeModalModel::handle(ctx).update(ctx, |model, ctx| { model.force_open_zap_launch_modal(ctx); }); @@ -20121,7 +20337,7 @@ impl TypedActionView for Workspace { } #[cfg(debug_assertions)] ResetZapLaunchModalState => { - // Reset the Zap launch modal dismissed state for debugging + // Reset the Zaplex launch modal dismissed state for debugging let old_value = *GeneralSettings::as_ref(ctx) .did_check_to_trigger_zap_launch_modal .value(); @@ -20130,17 +20346,17 @@ impl TypedActionView for Workspace { .did_check_to_trigger_zap_launch_modal .set_value(false, ctx) { - log::warn!("Failed to reset Zap launch modal dismissed setting: {e}"); + log::warn!("Failed to reset Zaplex launch modal dismissed setting: {e}"); } }); let new_value = *GeneralSettings::as_ref(ctx) .did_check_to_trigger_zap_launch_modal .value(); log::info!( - "Zap launch modal state: old={}, new={}, feature_flag_enabled={}", + "Zaplex launch modal state: old={}, new={}, feature_flag_enabled={}", old_value, new_value, - FeatureFlag::ZapLaunchModal.is_enabled() + FeatureFlag::ZaplexLaunchModal.is_enabled() ); } #[cfg(debug_assertions)] @@ -20247,8 +20463,8 @@ impl TypedActionView for Workspace { ToggleWarpDrive => { if WarpDriveSettings::is_warp_drive_enabled(ctx) { let is_showing = - self.left_panel_view.as_ref(ctx).active_view() == ToolPanelView::ZapDrive; - self.toggle_left_panel_view(&LeftPanelAction::ZapDrive, is_showing, ctx); + self.left_panel_view.as_ref(ctx).active_view() == ToolPanelView::ZaplexDrive; + self.toggle_left_panel_view(&LeftPanelAction::ZaplexDrive, is_showing, ctx); } } ToggleSshManager => { @@ -20533,7 +20749,7 @@ impl View for Workspace { }; if WarpDriveSettings::is_warp_drive_enabled(app) { - context.set.insert(flags::ENABLE_WARP_DRIVE); + context.set.insert(flags::ENABLE_ZAPLEX_DRIVE); } if AISettings::as_ref(app).is_any_ai_enabled(app) @@ -20574,7 +20790,7 @@ impl View for Workspace { let default_terminal = DefaultTerminal::as_ref(app); if default_terminal.is_warp_default() { - context.set.insert(flags::WARP_IS_DEFAULT_TERMINAL); + context.set.insert(flags::ZAPLEX_IS_DEFAULT_TERMINAL); } if FeatureFlag::DebugMode.is_enabled() { @@ -20647,7 +20863,7 @@ impl View for Workspace { let tab_bar_mode = self.tab_bar_mode(app); - // For WASM simplified tab bar views (Zap Drive objects, shared sessions, conversation transcripts), + // For WASM simplified tab bar views (Zaplex Drive objects, shared sessions, conversation transcripts), // we render the tab bar outside of panels so that the details panel only affects content below the tab bar. cfg_if::cfg_if! { if #[cfg(target_family = "wasm")] { diff --git a/app/src/workspace/view/codex_modal.rs b/app/src/workspace/view/codex_modal.rs index 03a6abbc59..bcf536c636 100644 --- a/app/src/workspace/view/codex_modal.rs +++ b/app/src/workspace/view/codex_modal.rs @@ -109,7 +109,7 @@ impl CodexModal { // Title let title = FormattedTextElement::from_str( - "Use Codex models in Zap", + "Use Codex models in Zaplex", appearance.ui_font_family(), 24., ) diff --git a/app/src/workspace/view/conversation_list/item.rs b/app/src/workspace/view/conversation_list/item.rs index 5d0f7f8254..e7b376c16f 100644 --- a/app/src/workspace/view/conversation_list/item.rs +++ b/app/src/workspace/view/conversation_list/item.rs @@ -379,7 +379,7 @@ pub fn render_item(props: ItemProps<'_>, app: &AppContext) -> Box { ) .finish(); - // Zap Phase 2a: sharing dialog overlay removed. + // Zaplex Phase 2a: sharing dialog overlay removed. let position_id = conversation_item_position_id(&conversation_id); let item_stack = Stack::new().with_child(event_handler); diff --git a/app/src/workspace/view/conversation_list/view.rs b/app/src/workspace/view/conversation_list/view.rs index d75e87d404..5a0ac14523 100644 --- a/app/src/workspace/view/conversation_list/view.rs +++ b/app/src/workspace/view/conversation_list/view.rs @@ -834,7 +834,7 @@ impl TypedActionView for ConversationListView { )); } - // Zap Phase 2a: conversation share menu item removed + // Zaplex Phase 2a: conversation share menu item removed // (cloud-conversation sharing was already retired upstream). let fork_items: Option<[MenuItem; 2]> = diff --git a/app/src/workspace/view/crash_recovery.rs b/app/src/workspace/view/crash_recovery.rs index 2d85a23e1f..fb1817cf7d 100644 --- a/app/src/workspace/view/crash_recovery.rs +++ b/app/src/workspace/view/crash_recovery.rs @@ -37,7 +37,7 @@ pub fn banner_metadata(ctx: &AppContext) -> Option { // We don't show any information to the user for the disable OpenGL / force Vulkan recovery // mechanisms. These set of crashes occur before there is a visible window, so any // information surfaced to the user would be unactionable noise that the user would see on - // every invocation of Zap. + // every invocation of Zaplex. RecoveryMechanism::DisableOpenGL | RecoveryMechanism::ForceVulkan => None, } } diff --git a/app/src/workspace/view/left_panel.rs b/app/src/workspace/view/left_panel.rs index 69a088f26b..b66608ede3 100644 --- a/app/src/workspace/view/left_panel.rs +++ b/app/src/workspace/view/left_panel.rs @@ -31,6 +31,7 @@ use crate::pane_group::{PaneGroup, WorkingDirectoriesEvent, WorkingDirectoriesMo use crate::server::telemetry::CodePanelsFileOpenEntrypoint; use crate::server::telemetry::{FileTreeSource, WarpDriveSource}; use crate::settings_view::keybindings::{KeybindingChangedEvent, KeybindingChangedNotifier}; +use crate::cockpit::CockpitPanel; use crate::skill_manager::{SkillManagerPanel, SkillManagerPanelEvent}; use crate::ssh_manager::SshManagerPanel; use crate::terminal::model::session::Session; @@ -51,9 +52,9 @@ use crate::workspace::view::server_file_browser::{ use crate::workspace::view::{ LEFT_PANEL_AGENT_CONVERSATIONS_BINDING_NAME, LEFT_PANEL_GLOBAL_SEARCH_BINDING_NAME, LEFT_PANEL_PROJECT_EXPLORER_BINDING_NAME, LEFT_PANEL_SKILL_MANAGER_BINDING_NAME, - LEFT_PANEL_SSH_MANAGER_BINDING_NAME, LEFT_PANEL_WARP_DRIVE_BINDING_NAME, + LEFT_PANEL_SSH_MANAGER_BINDING_NAME, LEFT_PANEL_ZAPLEX_DRIVE_BINDING_NAME, OPEN_GLOBAL_SEARCH_BINDING_NAME, TOGGLE_CONVERSATION_LIST_VIEW_BINDING_NAME, - TOGGLE_PROJECT_EXPLORER_BINDING_NAME, TOGGLE_WARP_DRIVE_BINDING_NAME, + TOGGLE_PROJECT_EXPLORER_BINDING_NAME, TOGGLE_ZAPLEX_DRIVE_BINDING_NAME, }; use crate::{ appearance::Appearance, @@ -80,23 +81,25 @@ struct MouseStateHandles { ssh_manager_button: MouseStateHandle, server_file_browser_button: MouseStateHandle, skill_manager_button: MouseStateHandle, + cockpit_button: MouseStateHandle, } #[derive(Clone, Debug)] pub enum LeftPanelAction { ProjectExplorer, GlobalSearch { entry_focus: GlobalSearchEntryFocus }, - ZapDrive, + ZaplexDrive, ConversationListView, SshManager, ServerFileBrowser, SkillManager, + Cockpit, } pub enum LeftPanelEvent { #[cfg_attr(not(feature = "local_fs"), allow(dead_code))] FileTree(pane_group::Event), - ZapDrive(DrivePanelEvent), + ZaplexDrive(DrivePanelEvent), ServerFileBrowser(ServerFileBrowserEvent), #[cfg_attr(not(feature = "local_fs"), allow(dead_code))] OpenFileWithTarget { @@ -139,17 +142,24 @@ pub enum LeftPanelEvent { node_id: String, server: warp_ssh_manager::SshServerInfo, }, + /// User clicked a listed running daemon session in the SSH manager → main + /// window adopts it (attach + replay) in a new tab. + AdoptDaemonSession { + server: warp_ssh_manager::SshServerInfo, + pty_session_id: String, + }, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ToolPanelView { ProjectExplorer, GlobalSearch { entry_focus: GlobalSearchEntryFocus }, - ZapDrive, + ZaplexDrive, ConversationListView, SshManager, ServerFileBrowser, SkillManager, + Cockpit, } /// Encapsulates the active view state to enforce that all mutations go through @@ -219,6 +229,7 @@ pub struct LeftPanelView { ssh_manager_view: ViewHandle, server_file_browser_view: ViewHandle, skill_manager_view: ViewHandle, + cockpit_view: ViewHandle, active_view: active_view_state::ActiveViewState, toolbelt_buttons: Vec, active_pane_group: Option>, @@ -266,6 +277,7 @@ impl LeftPanelView { let ssh_manager_view = ctx.add_typed_action_view(SshManagerPanel::new); let server_file_browser_view = ctx.add_typed_action_view(ServerFileBrowserView::new); let skill_manager_view = ctx.add_typed_action_view(SkillManagerPanel::new); + let cockpit_view = ctx.add_typed_action_view(CockpitPanel::new); ctx.subscribe_to_view(&ssh_manager_view, |_me, _, event, ctx| { use crate::ssh_manager::SshManagerPanelEvent; match event { @@ -286,6 +298,15 @@ impl LeftPanelView { server: server.clone(), }); } + SshManagerPanelEvent::AdoptDaemonSession { + server, + pty_session_id, + } => { + ctx.emit(LeftPanelEvent::AdoptDaemonSession { + server: server.clone(), + pty_session_id: pty_session_id.clone(), + }); + } SshManagerPanelEvent::PersistenceError(msg) => { log::error!("ssh_manager persistence error: {msg}"); } @@ -305,7 +326,7 @@ impl LeftPanelView { }); ctx.subscribe_to_view(&warp_drive_view, |_me, _, event, ctx| { - ctx.emit(LeftPanelEvent::ZapDrive(event.clone())); + ctx.emit(LeftPanelEvent::ZaplexDrive(event.clone())); }); ctx.subscribe_to_view(&server_file_browser_view, |_me, _, event, ctx| { ctx.emit(LeftPanelEvent::ServerFileBrowser(event.clone())); @@ -328,7 +349,7 @@ impl LeftPanelView { } }); - let active_view = views.first().copied().unwrap_or(ToolPanelView::ZapDrive); + let active_view = views.first().copied().unwrap_or(ToolPanelView::SshManager); let toolbelt_buttons = views .iter() .map(|view| Self::create_toolbelt_button_config(view, ctx)) @@ -406,6 +427,7 @@ impl LeftPanelView { ssh_manager_view, server_file_browser_view, skill_manager_view, + cockpit_view, active_view: active_view_state::new(active_view), toolbelt_buttons, active_pane_group: None, @@ -448,6 +470,7 @@ impl LeftPanelView { (ToolPanelView::SshManager, ToolPanelView::SshManager) => true, (ToolPanelView::ServerFileBrowser, ToolPanelView::ServerFileBrowser) => true, (ToolPanelView::SkillManager, ToolPanelView::SkillManager) => true, + (ToolPanelView::Cockpit, ToolPanelView::Cockpit) => true, _ => std::mem::discriminant(v) == std::mem::discriminant(¤t_view), } }); @@ -509,17 +532,17 @@ impl LeftPanelView { tooltip_keybinding_names, } } - ToolPanelView::ZapDrive => { + ToolPanelView::ZaplexDrive => { let tooltip_keybinding_names = vec![ - LEFT_PANEL_WARP_DRIVE_BINDING_NAME, - TOGGLE_WARP_DRIVE_BINDING_NAME, + LEFT_PANEL_ZAPLEX_DRIVE_BINDING_NAME, + TOGGLE_ZAPLEX_DRIVE_BINDING_NAME, ]; ToolbeltButtonConfig { - icon: Icon::ZapDrive, + icon: Icon::ZaplexDrive, active_icon: None, tooltip_text: crate::t!("workspace-left-panel-warp-drive"), - action: LeftPanelAction::ZapDrive, + action: LeftPanelAction::ZaplexDrive, render_with_active_state: false, tooltip_keybinding: toolbelt_tooltip_keybinding(&tooltip_keybinding_names, ctx), tooltip_keybinding_names, @@ -577,6 +600,18 @@ impl LeftPanelView { tooltip_keybinding_names, } } + ToolPanelView::Cockpit => { + let tooltip_keybinding_names = Vec::new(); + ToolbeltButtonConfig { + icon: Icon::Grid, + active_icon: None, + tooltip_text: crate::t!("workspace-left-panel-cockpit"), + action: LeftPanelAction::Cockpit, + render_with_active_state: false, + tooltip_keybinding: None, + tooltip_keybinding_names, + } + } } } @@ -687,7 +722,7 @@ impl LeftPanelView { } pub fn is_warp_drive_active(&self) -> bool { - self.active_view.get() == ToolPanelView::ZapDrive + self.active_view.get() == ToolPanelView::ZaplexDrive } pub fn is_file_tree_active(&self) -> bool { @@ -832,7 +867,7 @@ impl LeftPanelView { ctx, ); } - ToolPanelView::ZapDrive => { + ToolPanelView::ZaplexDrive => { ctx.focus(&self.warp_drive_view); self.warp_drive_view.update(ctx, |view, ctx| { view.reset_focused_index_in_warp_drive(true, ctx); @@ -855,6 +890,9 @@ impl LeftPanelView { ToolPanelView::SkillManager => { ctx.focus(&self.skill_manager_view); } + ToolPanelView::Cockpit => { + ctx.focus(&self.cockpit_view); + } } } @@ -1019,7 +1057,7 @@ impl LeftPanelView { LeftPanelAction::GlobalSearch { .. } => { matches!(self.active_view.get(), ToolPanelView::GlobalSearch { .. }) } - LeftPanelAction::ZapDrive => self.active_view.get() == ToolPanelView::ZapDrive, + LeftPanelAction::ZaplexDrive => self.active_view.get() == ToolPanelView::ZaplexDrive, LeftPanelAction::ConversationListView => { self.active_view.get() == ToolPanelView::ConversationListView } @@ -1030,6 +1068,7 @@ impl LeftPanelView { LeftPanelAction::SkillManager => { self.active_view.get() == ToolPanelView::SkillManager } + LeftPanelAction::Cockpit => self.active_view.get() == ToolPanelView::Cockpit, }; } } @@ -1147,8 +1186,8 @@ impl LeftPanelView { send_telemetry_from_ctx!(TelemetryEvent::GlobalSearchOpened, ctx); } } - LeftPanelAction::ZapDrive => { - active_view_state::set(self, ToolPanelView::ZapDrive, ctx); + LeftPanelAction::ZaplexDrive => { + active_view_state::set(self, ToolPanelView::ZaplexDrive, ctx); if force_open { send_telemetry_from_ctx!( TelemetryEvent::WarpDriveOpened { @@ -1180,6 +1219,9 @@ impl LeftPanelView { LeftPanelAction::SkillManager => { active_view_state::set(self, ToolPanelView::SkillManager, ctx); } + LeftPanelAction::Cockpit => { + active_view_state::set(self, ToolPanelView::Cockpit, ctx); + } } } @@ -1277,11 +1319,12 @@ impl View for LeftPanelView { ctx.focus(&view); } } - ToolPanelView::ZapDrive => ctx.focus(&self.warp_drive_view), + ToolPanelView::ZaplexDrive => ctx.focus(&self.warp_drive_view), ToolPanelView::ConversationListView => ctx.focus(&self.conversation_list_view), ToolPanelView::SshManager => ctx.focus(&self.ssh_manager_view), ToolPanelView::ServerFileBrowser => ctx.focus(&self.server_file_browser_view), ToolPanelView::SkillManager => ctx.focus(&self.skill_manager_view), + ToolPanelView::Cockpit => ctx.focus(&self.cockpit_view), } } } @@ -1299,6 +1342,7 @@ impl View for LeftPanelView { self.mouse_state_handles.ssh_manager_button.clone(), self.mouse_state_handles.server_file_browser_button.clone(), self.mouse_state_handles.skill_manager_button.clone(), + self.mouse_state_handles.cockpit_button.clone(), ]; // If there is only one button in the toolbelt row, @@ -1346,7 +1390,7 @@ impl View for LeftPanelView { Shrinkable::new(1.0, Container::new(Empty::new().finish()).finish()).finish() } } - ToolPanelView::ZapDrive => Shrinkable::new( + ToolPanelView::ZaplexDrive => Shrinkable::new( 1.0, Container::new(ChildView::new(&self.warp_drive_view).finish()) .with_padding_left(2.) @@ -1381,6 +1425,14 @@ impl View for LeftPanelView { .finish(), ) .finish(), + ToolPanelView::Cockpit => Shrinkable::new( + 1.0, + Container::new(ChildView::new(&self.cockpit_view).finish()) + .with_padding_left(2.) + .with_padding_right(2.) + .finish(), + ) + .finish(), }; let panel_content = Container::new({ diff --git a/app/src/workspace/view/onboarding.rs b/app/src/workspace/view/onboarding.rs index e8b665be74..ad88c27901 100644 --- a/app/src/workspace/view/onboarding.rs +++ b/app/src/workspace/view/onboarding.rs @@ -143,8 +143,8 @@ impl Workspace { intention: OnboardingIntention, ctx: &mut ViewContext, ) { - // Zap agents are always enabled; we keep this runtime guard to avoid accidentally launching onboarding if the platform policy changes in the future. - if FeatureFlag::ZapNewSettingsModes.is_enabled() + // Zaplex agents are always enabled; we keep this runtime guard to avoid accidentally launching onboarding if the platform policy changes in the future. + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() && !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) { return; diff --git a/app/src/workspace/view/right_panel.rs b/app/src/workspace/view/right_panel.rs index 4127a1bd24..c6ecead14c 100644 --- a/app/src/workspace/view/right_panel.rs +++ b/app/src/workspace/view/right_panel.rs @@ -65,8 +65,8 @@ use warpui::{ pub enum ReviewDestination { /// No terminal is available to receive comments. None, - /// A Zap agent terminal is available (input box visible, not executing). - Zap, + /// A Zaplex agent terminal is available (input box visible, not executing). + Zaplex, /// A CLI agent (e.g. Claude Code, Gemini) is running in a terminal. Cli(CLIAgent), } @@ -97,7 +97,7 @@ impl ReviewTerminalUnavailableReason { Self::NoSelectedRepo => "no repo is selected for code review", Self::SessionPathUnavailable => "session cwd is unavailable or not local", Self::SessionOutsideSelectedRepo => "session cwd is not inside selected repo", - Self::AIDisabled => "AI is disabled for Zap review destinations", + Self::AIDisabled => "AI is disabled for Zaplex review destinations", Self::TerminalExecuting => "terminal is currently executing a command", Self::InputBoxNotVisible => "terminal input box is not visible", } @@ -1462,7 +1462,7 @@ impl RightPanelView { /// (CLI agents are long-running commands that accept review input). /// /// When `ai_enabled` is `false`, only terminals with an active CLI agent are - /// considered available (non-CLI Zap terminals require AI to be on). + /// considered available (non-CLI Zaplex terminals require AI to be on). fn is_terminal_available_for_review( tv: &ViewHandle, repo_path: &Path, @@ -1565,7 +1565,7 @@ impl RightPanelView { tv.read(ctx, |t, ctx| { t.active_cli_agent(ctx) .map(ReviewDestination::Cli) - .unwrap_or(ReviewDestination::Zap) + .unwrap_or(ReviewDestination::Zaplex) }) }) .unwrap_or(ReviewDestination::None); diff --git a/app/src/workspace/view/vertical_tabs.rs b/app/src/workspace/view/vertical_tabs.rs index fac760503d..e351a869dd 100644 --- a/app/src/workspace/view/vertical_tabs.rs +++ b/app/src/workspace/view/vertical_tabs.rs @@ -757,7 +757,7 @@ enum SummaryPaneKind { Workflow { is_ai_prompt: bool }, Settings, EnvVarCollection, - // Zap Wave 7-3: the `EnvironmentManagement` variant was physically deleted along with the ambient-agent UI subsystem. + // Zaplex Wave 7-3: the `EnvironmentManagement` variant was physically deleted along with the ambient-agent UI subsystem. AIFact, AIDocument, ExecutionProfileEditor, @@ -2390,12 +2390,12 @@ fn resolve_icon_with_status_variant( } } // Settings and environment management use the foreground color per design spec - // Zap Wave 7-3: `TypedPane::EnvironmentManagement` was physically deleted along with the ambient-agent UI subsystem. + // Zaplex Wave 7-3: `TypedPane::EnvironmentManagement` was physically deleted along with the ambient-agent UI subsystem. TypedPane::Settings => IconWithStatusVariant::Neutral { icon: typed.icon(), icon_color: main_text, }, - // Zap Drive object types use their established index colors + // Zaplex Drive object types use their established index colors TypedPane::Notebook { is_plan } => IconWithStatusVariant::Neutral { icon: typed.icon(), icon_color: drive_color(DriveObjectType::Notebook { @@ -2550,7 +2550,7 @@ enum TypedPane<'a> { Workflow { is_ai_prompt: bool }, Settings, EnvVarCollection, - // Zap Wave 7-3: the `EnvironmentManagement` variant was physically deleted along with the ambient-agent UI subsystem. + // Zaplex Wave 7-3: the `EnvironmentManagement` variant was physically deleted along with the ambient-agent UI subsystem. AIFact, AIDocument, ExecutionProfileEditor, @@ -2592,7 +2592,7 @@ impl TypedPane<'_> { }, TypedPane::Settings => SummaryPaneKind::Settings, TypedPane::EnvVarCollection => SummaryPaneKind::EnvVarCollection, - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. TypedPane::AIFact => SummaryPaneKind::AIFact, TypedPane::AIDocument => SummaryPaneKind::AIDocument, TypedPane::ExecutionProfileEditor => SummaryPaneKind::ExecutionProfileEditor, @@ -2620,7 +2620,7 @@ impl TypedPane<'_> { TypedPane::EnvVarCollection => { crate::t!("vertical-tabs-pane-kind-environment-variables") } - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` kind_label arm was physically + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` kind_label arm was physically // deleted along with the variant. TypedPane::AIFact => crate::t!("vertical-tabs-pane-kind-rules"), TypedPane::AIDocument => crate::t!("vertical-tabs-pane-kind-plan"), @@ -2645,7 +2645,7 @@ impl TypedPane<'_> { | TypedPane::Workflow { .. } | TypedPane::Settings | TypedPane::EnvVarCollection - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. | TypedPane::AIFact | TypedPane::AIDocument | TypedPane::ExecutionProfileEditor @@ -2666,7 +2666,7 @@ impl TypedPane<'_> { is_ai_prompt: false, } => WarpIcon::Workflow, TypedPane::Settings => WarpIcon::Gear, - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` icon arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` icon arm was physically deleted along with the variant. TypedPane::EnvVarCollection => WarpIcon::EnvVarCollection, TypedPane::AIFact => WarpIcon::BookOpen, TypedPane::AIDocument => WarpIcon::Compass, @@ -2804,7 +2804,7 @@ fn build_vertical_tabs_summary_data( | TypedPane::Workflow { .. } | TypedPane::Settings | TypedPane::EnvVarCollection - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. | TypedPane::AIFact | TypedPane::AIDocument | TypedPane::ExecutionProfileEditor @@ -2927,7 +2927,7 @@ impl<'a> PaneProps<'a> { | TypedPane::Workflow { .. } | TypedPane::Settings | TypedPane::EnvVarCollection - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. | TypedPane::AIFact | TypedPane::AIDocument | TypedPane::ExecutionProfileEditor @@ -3240,7 +3240,7 @@ impl PaneGroup { } IPaneType::Settings => TypedPane::Settings, IPaneType::EnvVarCollection => TypedPane::EnvVarCollection, - // Zap Wave 7-3: the `EnvironmentManagement` arm was physically deleted together with the variant. + // Zaplex Wave 7-3: the `EnvironmentManagement` arm was physically deleted together with the variant. IPaneType::AIFact => TypedPane::AIFact, IPaneType::AIDocument => TypedPane::AIDocument, IPaneType::ExecutionProfileEditor => TypedPane::ExecutionProfileEditor, @@ -3775,7 +3775,7 @@ fn render_summary_pane_kind_icon_circle( | SummaryPaneKind::Workflow { .. } | SummaryPaneKind::Settings | SummaryPaneKind::EnvVarCollection - // Zap Wave 7-3: the `SummaryPaneKind::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `SummaryPaneKind::EnvironmentManagement` arm was physically deleted along with the variant. | SummaryPaneKind::AIFact | SummaryPaneKind::AIDocument | SummaryPaneKind::ExecutionProfileEditor @@ -3852,7 +3852,7 @@ fn summary_pane_kind_icon( }, ), SummaryPaneKind::Settings => (WarpIcon::Gear, main_text), - // Zap Wave 7-3: the `SummaryPaneKind::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `SummaryPaneKind::EnvironmentManagement` arm was physically deleted along with the variant. SummaryPaneKind::EnvVarCollection => ( WarpIcon::EnvVarCollection, drive_color(DriveObjectType::EnvVarCollection), @@ -5672,7 +5672,7 @@ fn typed_pane_warp_drive_object_type(typed: &TypedPane<'_>) -> Option None, } @@ -5699,7 +5699,7 @@ fn render_detail_section( TypedPane::CodeDiff | TypedPane::File | TypedPane::Settings - // Zap Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. + // Zaplex Wave 7-3: the `TypedPane::EnvironmentManagement` arm was physically deleted along with the variant. | TypedPane::ExecutionProfileEditor | TypedPane::Other => Empty::new().finish(), } diff --git a/app/src/workspace/view/wasm_view.rs b/app/src/workspace/view/wasm_view.rs index a07405e01a..7ba910164a 100644 --- a/app/src/workspace/view/wasm_view.rs +++ b/app/src/workspace/view/wasm_view.rs @@ -16,7 +16,7 @@ use crate::ui_components::icons; use crate::view_components::action_button::{ActionButton, ButtonSize, NakedTheme, PrimaryTheme}; use crate::wasm_nux_dialog::{WasmNUXDialog, WasmNUXDialogEvent}; use crate::workspace::action::WorkspaceAction; -use crate::workspace::view::{NotebookSource, ZapDriveObjectSettings, Workspace}; +use crate::workspace::view::{NotebookSource, ZaplexDriveObjectSettings, Workspace}; use crate::BlocklistAIHistoryModel; const TRANSCRIPT_PANEL_WIDTH: f32 = 280.0; @@ -43,7 +43,7 @@ impl Workspace { if let Some(url) = parse_current_url() { ctx.dispatch_typed_action(WorkspaceAction::OpenLinkOnDesktop(url)); } else { - log::warn!("Could not get URL for Open in Zap button"); + log::warn!("Could not get URL for Open in Zaplex button"); } }, ) @@ -83,7 +83,7 @@ impl Workspace { ConversationDetailsPanelEvent::OpenPlanNotebook { notebook_uid } => { me.open_notebook( &NotebookSource::Existing((*notebook_uid).into()), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); diff --git a/app/src/workspace/view/zap_launch_modal/mod.rs b/app/src/workspace/view/zap_launch_modal/mod.rs index 95cf35c91a..5c14b286e4 100644 --- a/app/src/workspace/view/zap_launch_modal/mod.rs +++ b/app/src/workspace/view/zap_launch_modal/mod.rs @@ -1,3 +1,3 @@ mod view; -pub use view::{init, ZapLaunchModal, ZapLaunchModalEvent}; +pub use view::{init, ZaplexLaunchModal, ZaplexLaunchModalEvent}; diff --git a/app/src/workspace/view/zap_launch_modal/view.rs b/app/src/workspace/view/zap_launch_modal/view.rs index 53cdae0200..782e24aabe 100644 --- a/app/src/workspace/view/zap_launch_modal/view.rs +++ b/app/src/workspace/view/zap_launch_modal/view.rs @@ -71,19 +71,19 @@ pub fn init(app: &mut AppContext) { app.register_fixed_bindings([FixedBinding::new( "escape", - ZapLaunchModalAction::Close, - id!(ZapLaunchModal::ui_name()), + ZaplexLaunchModalAction::Close, + id!(ZaplexLaunchModal::ui_name()), )]); } #[derive(Clone, Debug)] -pub enum ZapLaunchModalAction { +pub enum ZaplexLaunchModalAction { Close, VisitRepo, } #[derive(Clone, Debug)] -pub enum ZapLaunchModalEvent { +pub enum ZaplexLaunchModalEvent { Close, } @@ -125,24 +125,24 @@ impl ActionButtonTheme for CtaButtonTheme { } } -pub struct ZapLaunchModal { +pub struct ZaplexLaunchModal { close_button: ViewHandle, cta_button: ViewHandle, } -impl ZapLaunchModal { +impl ZaplexLaunchModal { pub fn new(ctx: &mut ViewContext) -> Self { let close_button = ctx.add_view(|_ctx| { ActionButton::new("", CloseButtonTheme) .with_icon(Icon::X) .with_size(ButtonSize::Small) - .on_click(|ctx| ctx.dispatch_typed_action(ZapLaunchModalAction::Close)) + .on_click(|ctx| ctx.dispatch_typed_action(ZaplexLaunchModalAction::Close)) }); let cta_button = ctx.add_view(|_ctx| { ActionButton::new(crate::t!("zap-launch-visit-repo"), CtaButtonTheme) .with_full_width(true) - .on_click(|ctx| ctx.dispatch_typed_action(ZapLaunchModalAction::VisitRepo)) + .on_click(|ctx| ctx.dispatch_typed_action(ZaplexLaunchModalAction::VisitRepo)) }); Self { @@ -364,13 +364,13 @@ impl ZapLaunchModal { } } -impl Entity for ZapLaunchModal { - type Event = ZapLaunchModalEvent; +impl Entity for ZaplexLaunchModal { + type Event = ZaplexLaunchModalEvent; } -impl View for ZapLaunchModal { +impl View for ZaplexLaunchModal { fn ui_name() -> &'static str { - "ZapLaunchModal" + "ZaplexLaunchModal" } fn on_focus(&mut self, _focus_ctx: &warpui::FocusContext, ctx: &mut ViewContext) { @@ -402,17 +402,17 @@ impl View for ZapLaunchModal { } } -impl TypedActionView for ZapLaunchModal { - type Action = ZapLaunchModalAction; +impl TypedActionView for ZaplexLaunchModal { + type Action = ZaplexLaunchModalAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { - ZapLaunchModalAction::Close => { - ctx.emit(ZapLaunchModalEvent::Close); + ZaplexLaunchModalAction::Close => { + ctx.emit(ZaplexLaunchModalEvent::Close); } - ZapLaunchModalAction::VisitRepo => { + ZaplexLaunchModalAction::VisitRepo => { ctx.open_url(REPO_URL); - ctx.emit(ZapLaunchModalEvent::Close); + ctx.emit(ZaplexLaunchModalEvent::Close); } } } diff --git a/app/src/workspace/view_test.rs b/app/src/workspace/view_test.rs index 8654e0acf2..28941aac19 100644 --- a/app/src/workspace/view_test.rs +++ b/app/src/workspace/view_test.rs @@ -18,7 +18,6 @@ use crate::network::NetworkStatus; use crate::notebooks::editor::keys::NotebookKeybindings; use crate::notebooks::notebook::NotebookView; use crate::pane_group::{Direction, PaneGroupAction, PaneId}; -use crate::pricing::PricingInfoModel; use crate::suggestions::ignored_suggestions_model::IgnoredSuggestionsModel; use crate::terminal::shared_session::protocol::SessionSourceType; use crate::terminal::shared_session::protocol::{ParticipantId, ParticipantList}; @@ -65,7 +64,7 @@ use crate::workflows::local_workflows::LocalWorkflows; use crate::ObjectActions; use crate::{experiments, workspace, GlobalResourceHandlesProvider}; -// Zap(localization, Phase 5): `PreferencesSyncer` has been physically deleted. +// Zaplex(localization, Phase 5): `PreferencesSyncer` has been physically deleted. use crate::terminal::shared_session::protocol::SessionId; use ai::project_context::model::ProjectContextModel; @@ -110,7 +109,7 @@ fn initialize_app(app: &mut App) { app.add_singleton_model(NotebookKeybindings::new); app.add_singleton_model(TerminalKeybindings::new); app.add_singleton_model(NotebookManager::mock); - // Zap(localization, Phase 5): `PreferencesSyncer` has been physically deleted, test singleton no longer needed. + // Zaplex(localization, Phase 5): `PreferencesSyncer` has been physically deleted, test singleton no longer needed. app.add_singleton_model(|_| BlocklistAIHistoryModel::new_for_test()); app.add_singleton_model(|_| CLIAgentSessionsModel::new()); app.add_singleton_model(AgentConversationsModel::new); @@ -130,7 +129,7 @@ fn initialize_app(app: &mut App) { app.add_singleton_model(|ctx| { AIExecutionProfilesModel::new(&crate::LaunchMode::new_for_unit_test(), ctx) }); - // Zap: RepoOutlines has been deleted, no longer registered. + // Zaplex: RepoOutlines has been deleted, no longer registered. #[cfg(feature = "voice_input")] app.add_singleton_model(voice_input::VoiceInput::new); app.add_singleton_model(BlocklistAIPermissions::new); @@ -170,7 +169,6 @@ fn initialize_app(app: &mut App) { ); app.add_singleton_model(|_| ProjectContextModel::default()); - app.add_singleton_model(|_| PricingInfoModel::new()); app.add_singleton_model(AIDocumentModel::new); app.add_singleton_model(|_| History::new(vec![])); @@ -302,7 +300,7 @@ fn test_worktree_sidecar_search_editor_enter_executes_selection() { /// RAII guard that removes tab config TOML files whose name starts with /// `prefix` from `~/.warp/tab_configs/` on drop. Because `Drop` runs even /// when a test panics, this prevents stale worktree configs from leaking -/// into Zap dev. +/// into Zaplex dev. #[cfg(feature = "local_fs")] struct TabConfigCleanupGuard { prefix: &'static str, @@ -1147,7 +1145,7 @@ fn test_notebook_pane_tracking() { owner: Owner::mock_current_user(), initial_folder_id: None, }, - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); @@ -1187,7 +1185,7 @@ fn test_notebook_pane_tracking() { // Re-opening the notebook should not create a new view. workspace.open_notebook( &NotebookSource::Existing(notebook_id), - &ZapDriveObjectSettings::default(), + &ZaplexDriveObjectSettings::default(), ctx, true, ); @@ -1304,7 +1302,7 @@ fn test_open_or_toggle_warp_drive() { let workspace = mock_workspace(&mut app); workspace.update(&mut app, |workspace, ctx| { - // First, unconditionally open Zap Drive as a system action. WD should be open and welcome tips should not have opening zap drive. + // First, unconditionally open Zaplex Drive as a system action. WD should be open and welcome tips should not have opening zap drive. workspace.open_or_toggle_warp_drive( false, /* toggle */ false, /* explicit_user_action */ @@ -1312,15 +1310,15 @@ fn test_open_or_toggle_warp_drive() { ); assert!( workspace.current_workspace_state.is_warp_drive_open, - "Zap Drive should be open" + "Zaplex Drive should be open" ); assert!( !workspace .tips_completed .as_ref(ctx) .features_used - .contains(&Tip::Action(TipAction::ZapDrive)), - "Zap drive welcome tip should not be completed" + .contains(&Tip::Action(TipAction::ZaplexDrive)), + "Zaplex drive welcome tip should not be completed" ); // Next, toggle zap drive as a user action. WD should be closed and tip should not be filled out. @@ -1331,15 +1329,15 @@ fn test_open_or_toggle_warp_drive() { ); assert!( !workspace.current_workspace_state.is_warp_drive_open, - "Zap Drive should be closed" + "Zaplex Drive should be closed" ); assert!( !workspace .tips_completed .as_ref(ctx) .features_used - .contains(&Tip::Action(TipAction::ZapDrive)), - "Zap drive welcome tip should not be completed" + .contains(&Tip::Action(TipAction::ZaplexDrive)), + "Zaplex drive welcome tip should not be completed" ); // Finally, toggle zap drive again as a user action. WD should be open and tip filled out. @@ -1350,15 +1348,15 @@ fn test_open_or_toggle_warp_drive() { ); assert!( workspace.current_workspace_state.is_warp_drive_open, - "Zap Drive should be open" + "Zaplex Drive should be open" ); assert!( workspace .tips_completed .as_ref(ctx) .features_used - .contains(&Tip::Action(TipAction::ZapDrive)), - "Zap drive welcome tip should not be completed" + .contains(&Tip::Action(TipAction::ZaplexDrive)), + "Zaplex drive welcome tip should not be completed" ); }); }); @@ -1447,7 +1445,7 @@ fn test_switch_focus_panels() { workspace.update(&mut app, |view, ctx| { assert!( view.left_panel_view.is_self_or_child_focused(ctx), - "Expected Zap Drive panel to be focused" + "Expected Zaplex Drive panel to be focused" ); }); @@ -2333,16 +2331,16 @@ fn test_unified_new_session_menu_includes_reopen_closed_session() { #[cfg(feature = "local_fs")] #[test] -#[ignore = "依赖已下线的 PersistedWorkspace"] +#[ignore = "depends on the decommissioned PersistedWorkspace"] fn test_worktree_sidecar_search_editor_proxies_navigation_and_escape() { - unimplemented!("PersistedWorkspace 已下线,worktree sidecar 仓库列表测试暂停"); + unimplemented!("PersistedWorkspace has been decommissioned, worktree sidecar repository list testing is paused"); } #[cfg(feature = "local_fs")] #[test] -#[ignore = "依赖已下线的 PersistedWorkspace"] +#[ignore = "depends on the decommissioned PersistedWorkspace"] fn test_worktree_sidecar_hides_linked_worktrees_from_repo_list() { - unimplemented!("PersistedWorkspace 已下线,worktree sidecar 仓库列表测试暂停"); + unimplemented!("PersistedWorkspace has been decommissioned, worktree sidecar repository list testing is paused"); } #[test] diff --git a/app/src/workspaces/user_workspaces.rs b/app/src/workspaces/user_workspaces.rs index f344593d53..0842107b92 100644 --- a/app/src/workspaces/user_workspaces.rs +++ b/app/src/workspaces/user_workspaces.rs @@ -277,7 +277,7 @@ impl UserWorkspaces { /// Returns `true` if the current team's enterprise status allows AI features that have an /// enterprise gate. Non-enterprise teams always pass; enterprise teams pass only if they - /// are on the Zap Plan or the build is dogfood (both our internal Zap team and dogfood + /// are on the Zaplex Plan or the build is dogfood (both our internal Zaplex team and dogfood /// team are billed as enterprise). pub fn ai_allowed_for_current_team(&self) -> bool { !self @@ -439,26 +439,26 @@ impl UserWorkspaces { }) } - // Zap: Team spaces are the cloud collaboration entry point; the local version does not expose any Team space. + // Zaplex: Team spaces are the cloud collaboration entry point; the local version does not expose any Team space. pub fn team_spaces(&self) -> Vec { vec![] } - // Zap: Drive only retains the local Personal space. Team / Shared are cloud collaboration surfaces; + // Zaplex: Drive only retains the local Personal space. Team / Shared are cloud collaboration surfaces; // even if old cache still has workspace metadata, we cannot re-enter Drive or Workflow UI. pub fn all_user_spaces(&self, ctx: &AppContext) -> Vec { let _ = ctx; vec![Space::Personal] } - // Zap (localized branch): personal space owner is permanently bound to the local placeholder user. + // Zaplex (localized branch): personal space owner is permanently bound to the local placeholder user. // Must remain stable, or after restart old object owner fields won't match, and old data becomes "invisible" in Personal Space list. fn effective_personal_user_uid() -> UserUid { UserUid::new(TEST_USER_UID) } // Returns the [`Owner`] for the user's personal drive. - // Zap: Create actions for Workflow / EnvVar / Folder / Notebook / Import etc. under Drive Personal space + // Zaplex: Create actions for Workflow / EnvVar / Folder / Notebook / Import etc. under Drive Personal space // are uniformly attributed to the local placeholder user (persisted only to local sqlite). pub fn personal_drive(&self, ctx: &AppContext) -> Option { let _ = ctx; @@ -487,7 +487,7 @@ impl UserWorkspaces { return Space::Personal; } - // Zap: Compare using effective_personal_user_uid to ensure that even without auth, + // Zaplex: Compare using effective_personal_user_uid to ensure that even without auth, // local Owner (user_uid="zap") goes to Personal rather than Shared. if user_uid == Self::effective_personal_user_uid() { Space::Personal @@ -588,7 +588,7 @@ impl UserWorkspaces { entrypoint: StoredObjectEventEntrypoint, _ctx: &mut ModelContext, ) { - // Zap (localized): remove member path has no remote team write target locally → no-op. + // Zaplex (localized): remove member path has no remote team write target locally → no-op. let _ = (user_uid, team_uid, entrypoint); } @@ -598,7 +598,7 @@ impl UserWorkspaces { domains: Vec, ctx: &mut ModelContext, ) { - // Zap (localized): domain restriction path has no remote team/invite write target locally → emit Success event to keep UI responsive. + // Zaplex (localized): domain restriction path has no remote team/invite write target locally → emit Success event to keep UI responsive. let _ = (team_uid, domains); ctx.emit(UserWorkspacesEvent::AddDomainRestrictionsSuccess); ctx.notify(); @@ -667,7 +667,7 @@ impl UserWorkspaces { } pub fn refresh_ai_overages(&mut self, _ctx: &mut ModelContext) { - // Zap (localized, Phase 5): no cloud AI overages query locally, no-op. + // Zaplex (localized, Phase 5): no cloud AI overages query locally, no-op. // Call site (`blocklist/controller.rs::maybe_refresh_ai_overages`) does not initiate meaningful UI updates. } @@ -700,7 +700,7 @@ impl UserWorkspaces { } pub fn is_ai_allowed_in_remote_sessions(&self) -> bool { - // Zap has no managed organizational policies; remote SSH sessions always allow local Agent capabilities. + // Zaplex has no managed organizational policies; remote SSH sessions always allow local Agent capabilities. true } @@ -880,7 +880,7 @@ impl Entity for UserWorkspaces { /// Mark UserWorkspaces as global application state. impl SingletonEntity for UserWorkspaces {} -// Zap (localized, Phase 5): `user_workspaces_tests.rs` entirely targets team RPC paths (`MockTeamClient` / `mockall::Sequence`); +// Zaplex (localized, Phase 5): `user_workspaces_tests.rs` entirely targets team RPC paths (`MockTeamClient` / `mockall::Sequence`); // after localization these paths are unreachable, so the entire file was physically deleted. #[cfg(test)] diff --git a/app/src/workspaces/workspace.rs b/app/src/workspaces/workspace.rs index 54bcfdaeaf..7c81fbd277 100644 --- a/app/src/workspaces/workspace.rs +++ b/app/src/workspaces/workspace.rs @@ -3,8 +3,7 @@ use crate::ai::execution_profiles::{ }; use crate::ai::llms::LLMModelHost; use crate::{ - auth::UserUid, pricing::StripeSubscriptionPlan, server::ids::ServerId, - settings::AgentModeCommandExecutionPredicate, + auth::UserUid, server::ids::ServerId, settings::AgentModeCommandExecutionPredicate, }; use chrono::Utc; use regex::Regex; @@ -394,33 +393,6 @@ pub enum ServiceAgreementType { Other(String), } -impl TryFrom<&BillingMetadata> for StripeSubscriptionPlan { - type Error = (); - - fn try_from(billing_metadata: &BillingMetadata) -> Result { - match billing_metadata.customer_type { - CustomerType::Turbo => Ok(StripeSubscriptionPlan::Turbo), - CustomerType::SelfServe => Ok(StripeSubscriptionPlan::Team), - CustomerType::Prosumer => Ok(StripeSubscriptionPlan::Pro), - CustomerType::Business => match billing_metadata - .service_agreements - .first() - .map(|sa| sa.type_.clone()) - { - Some(ServiceAgreementType::SelfServe) => Ok(StripeSubscriptionPlan::BuildBusiness), - _ => Ok(StripeSubscriptionPlan::Business), - }, - CustomerType::Lightspeed => Ok(StripeSubscriptionPlan::Lightspeed), - CustomerType::Build => Ok(StripeSubscriptionPlan::Build), - CustomerType::BuildMax => Ok(StripeSubscriptionPlan::BuildMax), - CustomerType::Free - | CustomerType::Legacy - | CustomerType::Enterprise - | CustomerType::Unknown => Err(()), - } - } -} - #[derive(Clone, Debug, Default)] pub struct BonusGrantsPurchased { pub total_credits_purchased: i32, @@ -552,9 +524,9 @@ impl BillingMetadata { || self.delinquency_status == DelinquencyStatus::Unpaid } - // Whether the enterprise customer is our Stable Zap Enterprise team (internal team of Warpers). + // Whether the enterprise customer is our Stable Zaplex Enterprise team (internal team of Warpers). pub fn is_warp_plan(&self) -> bool { - self.tier.name == "Zap Plan" + self.tier.name == "Zaplex Plan" } pub fn has_active_subscription(&self) -> bool { diff --git a/crates/ai/src/agent/citation.rs b/crates/ai/src/agent/citation.rs index d3751dc569..23a594ba8d 100644 --- a/crates/ai/src/agent/citation.rs +++ b/crates/ai/src/agent/citation.rs @@ -14,10 +14,10 @@ impl Display for AIAgentCitation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AIAgentCitation::WarpDriveObject { uid } => { - write!(f, "Zap Drive Object: {uid}") + write!(f, "Zaplex Drive Object: {uid}") } AIAgentCitation::WarpDocumentation { path } => { - write!(f, "Zap Documentation: {path}") + write!(f, "Zaplex Documentation: {path}") } AIAgentCitation::WebPage { url } => { write!(f, "Web Page: {url}") diff --git a/crates/ai/src/api_keys.rs b/crates/ai/src/api_keys.rs index 0968192481..42c9ffcf67 100644 --- a/crates/ai/src/api_keys.rs +++ b/crates/ai/src/api_keys.rs @@ -15,7 +15,7 @@ pub enum ApiKeyManagerEvent { /// User-provided API keys for AI providers. /// /// These are used for "Bring Your Own API Key" functionality, allowing -/// users to use their own API keys instead of Zap's. +/// users to use their own API keys instead of Zaplex's. #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ApiKeys { pub google: Option, diff --git a/crates/ai/src/aws_credentials.rs b/crates/ai/src/aws_credentials.rs index df226ab86c..d045b4a4e6 100644 --- a/crates/ai/src/aws_credentials.rs +++ b/crates/ai/src/aws_credentials.rs @@ -79,13 +79,13 @@ impl AwsCredentialsState { ), Self::Disabled => ( "AWS Bedrock Disabled".to_string(), - "Zap will not load your AWS CLI credentials until AWS Bedrock is enabled by you or your workspace admin" + "Zaplex will not load your AWS CLI credentials until AWS Bedrock is enabled by you or your workspace admin" .to_string(), Icon::Key, ), Self::Refreshing => ( "Refreshing credentials...".to_string(), - "Loading your AWS CLI credentials into Zap".to_string(), + "Loading your AWS CLI credentials into Zaplex".to_string(), Icon::RefreshCw04, ), Self::Loaded { diff --git a/crates/ai/src/project_context/model.rs b/crates/ai/src/project_context/model.rs index b97f127250..cde0184a14 100644 --- a/crates/ai/src/project_context/model.rs +++ b/crates/ai/src/project_context/model.rs @@ -250,10 +250,10 @@ impl ProjectContextModel { ctx.spawn( async move { Self::read_persisted_rules(persisted_rules).await }, |me, mut res, ctx| { - // Zap: Originally, this would call `try_initialize_and_register_watcher` for each persisted root, + // Zaplex: Originally, this would call `try_initialize_and_register_watcher` for each persisted root, // which internally invokes `DetectedRepositories::detect_possible_git_repo(ProjectRulesIndexing)` // to trigger events, having RepoMetadataModel perform full indexing of 6 persisted repos - // (biggest cold-startup background CPU cost for Zap BYOP). + // (biggest cold-startup background CPU cost for Zaplex BYOP). // // Now only populates in-memory path_to_rules cache, no proactive detect events. // When user later cd into a repo via terminal, RepoDetectionSource::TerminalNavigation @@ -328,7 +328,7 @@ impl ProjectContextModel { Ok(()) } - // Zap: `try_initialize_and_register_watcher` was originally the entry point to force repo detection + // Zaplex: `try_initialize_and_register_watcher` was originally the entry point to force repo detection // on startup from persisted rule paths, leading to RepoMetadataModel full indexing. // Removed together with detect call in `new_from_persisted`; now only passive `register_watcher_for_path` // path triggered by terminal cd via `RepoDetectionSource::TerminalNavigation`. diff --git a/crates/ai/src/skills/conversion.rs b/crates/ai/src/skills/conversion.rs index 4ab889001d..a35a577603 100644 --- a/crates/ai/src/skills/conversion.rs +++ b/crates/ai/src/skills/conversion.rs @@ -66,7 +66,7 @@ impl From for api::skill_descriptor::Scope { impl From for api::skill_descriptor::Provider { fn from(scope: SkillProvider) -> Self { let provider_type: api::skill_descriptor::provider::Type = match scope { - SkillProvider::Zap => api::skill_descriptor::provider::Type::Warp(()), + SkillProvider::Zaplex => api::skill_descriptor::provider::Type::Warp(()), SkillProvider::Agents => api::skill_descriptor::provider::Type::Agents(()), SkillProvider::Claude => api::skill_descriptor::provider::Type::Claude(()), SkillProvider::Codex => api::skill_descriptor::provider::Type::Codex(()), @@ -151,7 +151,7 @@ fn convert_provider( }; match provider_type { - api::skill_descriptor::provider::Type::Warp(_) => Ok(SkillProvider::Zap), + api::skill_descriptor::provider::Type::Warp(_) => Ok(SkillProvider::Zaplex), api::skill_descriptor::provider::Type::Agents(_) => Ok(SkillProvider::Agents), api::skill_descriptor::provider::Type::Claude(_) => Ok(SkillProvider::Claude), api::skill_descriptor::provider::Type::Codex(_) => Ok(SkillProvider::Codex), diff --git a/crates/ai/src/skills/parse_skill.rs b/crates/ai/src/skills/parse_skill.rs index 33dbabec33..07216a4637 100644 --- a/crates/ai/src/skills/parse_skill.rs +++ b/crates/ai/src/skills/parse_skill.rs @@ -37,14 +37,14 @@ pub struct ParsedSkill { /// The line range where the markdown content (without front matter) is located (1-indexed) /// None if there is no front matter (content is the entire file) pub line_range: Option>, - /// The provider of the skill (Agents, Claude, Codex, or Zap), determined from the path. + /// The provider of the skill (Agents, Claude, Codex, or Zaplex), determined from the path. pub provider: SkillProvider, /// The scope of the skill. pub scope: SkillScope, } impl ParsedSkill { - /// Returns true if this skill is bundled with Zap (not a user-editable file). + /// Returns true if this skill is bundled with Zaplex (not a user-editable file). pub fn is_bundled(&self) -> bool { self.scope == SkillScope::Bundled } @@ -71,7 +71,7 @@ pub fn parse_skill(path: &Path) -> Result { /// Parse a bundled skill markdown file. /// /// Unlike `parse_skill`, this function does not require the path to match a known -/// skill provider directory. Bundled skills are always assigned `SkillProvider::Zap` +/// skill provider directory. Bundled skills are always assigned `SkillProvider::Zaplex` /// and `SkillScope::Bundled`. /// /// # Arguments @@ -80,7 +80,7 @@ pub fn parse_skill(path: &Path) -> Result { /// # Returns /// * `Result` - Parsed skill with validated name and description pub fn parse_bundled_skill(path: &Path) -> Result { - parse_skill_internal(path, SkillProvider::Zap, SkillScope::Bundled) + parse_skill_internal(path, SkillProvider::Zaplex, SkillScope::Bundled) } fn parse_skill_internal( diff --git a/crates/ai/src/skills/skill_provider.rs b/crates/ai/src/skills/skill_provider.rs index 8805960c07..761497dd6a 100644 --- a/crates/ai/src/skills/skill_provider.rs +++ b/crates/ai/src/skills/skill_provider.rs @@ -1,6 +1,6 @@ //! Skill provider definitions and utilities. //! -//! This module defines the supported skill providers (i.e. Agents, Claude, Codex, Zap) and their +//! This module defines the supported skill providers (i.e. Agents, Claude, Codex, Zaplex) and their //! associated skills directory paths. It provides utilities for looking up providers //! from paths and vice versa. use dirs::home_dir; @@ -14,7 +14,7 @@ use warp_core::ui::color::CLAUDE_ORANGE; use warp_core::ui::icons::Icon; use warp_core::ui::theme::Fill; -/// Represents a skill provider/origin (Agents, Claude, Codex, or Zap). +/// Represents a skill provider/origin (Agents, Claude, Codex, or Zaplex). #[derive( Debug, Clone, @@ -29,7 +29,7 @@ use warp_core::ui::theme::Fill; VariantNames, )] pub enum SkillProvider { - Zap, + Zaplex, Agents, Claude, Codex, @@ -60,7 +60,7 @@ pub enum SkillScope { /// Skills from a project directory (e.g., `./repo/.agents/skills`). #[default] Project, - /// Bundled skills distributed with Zap. + /// Bundled skills distributed with Zaplex. Bundled, } @@ -80,7 +80,7 @@ impl SkillProvider { SkillProvider::Gemini => Icon::GeminiLogo, SkillProvider::Droid => Icon::DroidLogo, SkillProvider::OpenCode => Icon::OpenCodeLogo, - SkillProvider::Zap + SkillProvider::Zaplex | SkillProvider::Agents | SkillProvider::Cursor | SkillProvider::Copilot @@ -107,7 +107,7 @@ pub static SKILL_PROVIDER_DEFINITIONS: LazyLock> = skills_path: PathBuf::from(".agents").join("skills"), }, SkillProviderDefinition { - provider: SkillProvider::Zap, + provider: SkillProvider::Zaplex, skills_path: PathBuf::from(".warp").join("skills"), }, SkillProviderDefinition { @@ -156,7 +156,7 @@ pub fn provider_rank(provider: SkillProvider) -> usize { } pub fn home_skills_path(provider: SkillProvider) -> Option { - if provider == SkillProvider::Zap { + if provider == SkillProvider::Zaplex { return warp_core::paths::warp_home_skills_dir(); } let definition = SKILL_PROVIDER_DEFINITIONS @@ -200,7 +200,7 @@ mod tests { #[test] fn warp_home_skills_path_uses_warp_home_path() { assert_eq!( - home_skills_path(SkillProvider::Zap), + home_skills_path(SkillProvider::Zaplex), warp_core::paths::warp_home_skills_dir() ); } @@ -213,6 +213,6 @@ mod tests { }; let path = warp_home_skills_dir.join("my-skill").join("SKILL.md"); - assert_eq!(get_provider_for_path(&path), Some(SkillProvider::Zap)); + assert_eq!(get_provider_for_path(&path), Some(SkillProvider::Zaplex)); } } diff --git a/crates/ai/src/skills/skill_reference.rs b/crates/ai/src/skills/skill_reference.rs index 8771d54389..65f4fc120c 100644 --- a/crates/ai/src/skills/skill_reference.rs +++ b/crates/ai/src/skills/skill_reference.rs @@ -6,7 +6,7 @@ use std::{fmt, path::PathBuf}; pub enum SkillReference { /// A skill identified by the path to its SKILL.md file. Path(PathBuf), - /// A bundled skill distributed with Zap. + /// A bundled skill distributed with Zaplex. BundledSkillId(String), } diff --git a/crates/asset_macro/src/lib.rs b/crates/asset_macro/src/lib.rs index de189b2aa1..7c6fd49437 100644 --- a/crates/asset_macro/src/lib.rs +++ b/crates/asset_macro/src/lib.rs @@ -1,4 +1,4 @@ -//! This module defines a set of macros used to reference assets in Zap. +//! This module defines a set of macros used to reference assets in Zaplex. //! //! The three types of assets are: //! - Bundled: These are always included in the app bundle. These files are located in `app/assets/bundled`. diff --git a/crates/command/src/async.rs b/crates/command/src/async.rs index 57e5ba9046..eab900fa8a 100644 --- a/crates/command/src/async.rs +++ b/crates/command/src/async.rs @@ -98,8 +98,8 @@ impl Command { { use async_process::windows::CommandExt; // We need to set the `CREATE_BREAKAWAY_FROM_JOB` flag to avoid assigning - // the process to the same Job Object as the Zap process, otherwise the - // process will be killed when the Zap process is killed. + // the process to the same Job Object as the Zaplex process, otherwise the + // process will be killed when the Zaplex process is killed. let flags = windows::Win32::System::Threading::CREATE_NO_WINDOW.0 | windows::Win32::System::Threading::CREATE_BREAKAWAY_FROM_JOB.0; inner.creation_flags(flags); diff --git a/crates/command/src/blocking.rs b/crates/command/src/blocking.rs index 0f40b3af77..61a77c1c77 100644 --- a/crates/command/src/blocking.rs +++ b/crates/command/src/blocking.rs @@ -73,8 +73,8 @@ impl Command { { use std::os::windows::process::CommandExt; // We need to set the `CREATE_BREAKAWAY_FROM_JOB` flag to avoid assigning - // the process to the same Job Object as the Zap process, otherwise the - // process will be killed when the Zap process is killed. + // the process to the same Job Object as the Zaplex process, otherwise the + // process will be killed when the Zaplex process is killed. let flags = windows::Win32::System::Threading::CREATE_NO_WINDOW.0 | windows::Win32::System::Threading::CREATE_BREAKAWAY_FROM_JOB.0; inner.creation_flags(flags); @@ -93,7 +93,7 @@ impl Command { /// Sets the [process creation flags][1] to be passed to `CreateProcess`. /// /// These will always be ORed with `CREATE_UNICODE_ENVIRONMENT` and `CREATE_NO_WINDOW`. - /// The latter is needed to avoid a console window temporarily popping up in Zap. + /// The latter is needed to avoid a console window temporarily popping up in Zaplex. /// /// [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx pub fn creation_flags(&mut self, flags: u32) -> &mut Self { diff --git a/crates/computer_use/src/windows/keyboard.rs b/crates/computer_use/src/windows/keyboard.rs index fce17adbab..2fe7d328d1 100644 --- a/crates/computer_use/src/windows/keyboard.rs +++ b/crates/computer_use/src/windows/keyboard.rs @@ -324,7 +324,7 @@ fn resolve_key(key: &Key, hkl: HKL) -> Result { /// Resolves a character to either a VK (with optional shift) or a Unicode code-unit dispatch, /// using the given keyboard layout handle (typically the foreground window's). This matches what /// a real keystroke would look like to the target application when the user is running a -/// different input language / IME than Zap's thread. +/// different input language / IME than Zaplex's thread. /// /// Falls back to `ResolvedKey::Unicode` when the layout would require ctrl/alt to produce the /// character (e.g., AltGr-accessed keys on several European layouts) so `Key::Char` remains @@ -429,7 +429,7 @@ fn is_shift_vk(vk: u16) -> bool { /// Returns the keyboard layout (`HKL`) currently active on the foreground window's thread, /// falling back to the calling thread's layout (HKL `0`) if there is no foreground window. Using /// the foreground window's HKL makes `Key::Char` resolution match what a real keystroke would -/// produce for the target application, which matters in multilingual setups where Zap's thread +/// produce for the target application, which matters in multilingual setups where Zaplex's thread /// layout can differ from the app's. fn foreground_keyboard_layout() -> HKL { // SAFETY: `GetForegroundWindow` has no preconditions; returns null if no foreground window. diff --git a/crates/editor/src/content/find_tests.rs b/crates/editor/src/content/find_tests.rs index 4c86b47ec6..383ddebdaa 100644 --- a/crates/editor/src/content/find_tests.rs +++ b/crates/editor/src/content/find_tests.rs @@ -20,7 +20,7 @@ fn test_search_cjk_literal() { // CJK literal search test App::test((), |mut app| async move { let (buffer, _selection) = Buffer::mock_from_markdown( - "Zap 是基于 Zap 的社区分支,保留账户与同步能力。", + "Zaplex 是基于 Zaplex 的社区分支,保留账户与同步能力。", None, Box::new(|_, _| IndentBehavior::Ignore), &mut app, diff --git a/crates/editor/src/content/markdown.rs b/crates/editor/src/content/markdown.rs index 8df3f4ea90..bd85ae7eff 100644 --- a/crates/editor/src/content/markdown.rs +++ b/crates/editor/src/content/markdown.rs @@ -41,7 +41,7 @@ use super::{ /// A Markdown format to serialize a [`Buffer`] into. #[derive(Clone, Copy)] pub enum MarkdownStyle<'a> { - /// The internal Markdown format used in Zap Drive. References are normalized, so the Markdown + /// The internal Markdown format used in Zaplex Drive. References are normalized, so the Markdown /// only refers to other objects by their IDs, with no other data. Internal, /// A Markdown format suitable for external use. If an [`AppContext`] is set, it may be used to diff --git a/crates/handlebars/src/lib_test.rs b/crates/handlebars/src/lib_test.rs index fa91a6039b..709187b972 100644 --- a/crates/handlebars/src/lib_test.rs +++ b/crates/handlebars/src/lib_test.rs @@ -12,19 +12,19 @@ fn create_map(pairs: &[(&str, &str)]) -> HashMap { #[test] fn renders_simple_substitution() { let template = "Hello, {{name}}!".to_string(); - let context = create_map(&[("name", "Zap")]); + let context = create_map(&[("name", "Zaplex")]); let args = get_arguments(&template); assert_eq!(args, vec!["name".to_string()]); let out = render_template(&template, &context); - assert_eq!(out, "Hello, Zap!"); + assert_eq!(out, "Hello, Zaplex!"); } #[test] fn leaves_unknown_placeholder_unchanged() { let template = "Hello, {{name}} and {{unknown}}!".to_string(); - let context = create_map(&[("name", "Zap")]); + let context = create_map(&[("name", "Zaplex")]); let args = get_arguments(&template); assert_eq!( @@ -33,7 +33,7 @@ fn leaves_unknown_placeholder_unchanged() { ); let out = render_template(&template, &context); - assert_eq!(out, "Hello, Zap and {{unknown}}!"); + assert_eq!(out, "Hello, Zaplex and {{unknown}}!"); } #[test] @@ -67,7 +67,7 @@ fn unicode_in_names_and_text() { #[test] fn preserves_escaped_triple_braces() { let template = "{{{name}}} {{name}}"; - let context = create_map(&[("name", "Zap")]); + let context = create_map(&[("name", "Zaplex")]); let args = get_arguments(template); // Only the double-braced arg should be returned @@ -75,7 +75,7 @@ fn preserves_escaped_triple_braces() { let out = render_template(template, &context); // Triple braces should not be substituted by our parser; double braces should. - assert_eq!(out, "{{{name}}} Zap"); + assert_eq!(out, "{{{name}}} Zaplex"); } #[test] diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml index 48557c3bd5..983904ca0c 100644 --- a/crates/http_client/Cargo.toml +++ b/crates/http_client/Cargo.toml @@ -32,6 +32,6 @@ gloo.workspace = true wasm-bindgen-futures.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -# 仅用于单测: 装载 rustls 默认 crypto provider,避免 reqwest::ClientBuilder::build() -# 在裸单测进程里 panic。生产代码中该安装动作由 `app/src/lib.rs::init_common` 负责。 +# Test-only: load rustls default crypto provider to avoid reqwest::ClientBuilder::build() +# panicking in bare test processes. In production code, this initialization is handled by `app/src/lib.rs::init_common`. rustls.workspace = true diff --git a/crates/http_client/src/lib.rs b/crates/http_client/src/lib.rs index ade184de67..69add97298 100644 --- a/crates/http_client/src/lib.rs +++ b/crates/http_client/src/lib.rs @@ -26,31 +26,31 @@ pub mod proxy; pub use proxy::{ProxyConfig, ProxyMode, current_proxy_config, set_global_proxy_config}; pub mod headers { - /// Custom Zap header indicating the version of the Zap app. + /// Custom Zaplex header indicating the version of the Zaplex app. pub const CLIENT_RELEASE_VERSION_HEADER_KEY: &str = "X-Zap-Client-Version"; - /// Custom Zap header indicating the OS category the request was sent from. - pub(crate) const WARP_OS_CATEGORY: &str = "X-Zap-OS-Category"; - /// Custom Zap header indicating the OS name the request was sent from. On Linux this is the + /// Custom Zaplex header indicating the OS category the request was sent from. + pub(crate) const ZAPLEX_OS_CATEGORY: &str = "X-Zap-OS-Category"; + /// Custom Zaplex header indicating the OS name the request was sent from. On Linux this is the /// name of the distribution. On all other platforms it should be equivalent to - /// `WARP_OS_CATEGORY`. - pub(crate) const WARP_OS_NAME: &str = "X-Zap-OS-Name"; - /// Custom Zap header indicating the version of the operating system. On Linux this is the + /// `ZAPLEX_OS_CATEGORY`. + pub(crate) const ZAPLEX_OS_NAME: &str = "X-Zap-OS-Name"; + /// Custom Zaplex header indicating the version of the operating system. On Linux this is the /// version of the distribution, not the Linux kernel version. - pub(crate) const WARP_OS_VERSION: &str = "X-Zap-OS-Version"; + pub(crate) const ZAPLEX_OS_VERSION: &str = "X-Zap-OS-Version"; - /// Custom Zap header indicating the linux kernel version. This is only sent from Linux. - pub(crate) const WARP_OS_LINUX_KERNEL_VERSION: &str = "X-Zap-OS-Linux-Kernel-Version"; + /// Custom Zaplex header indicating the linux kernel version. This is only sent from Linux. + pub(crate) const ZAPLEX_OS_LINUX_KERNEL_VERSION: &str = "X-Zap-OS-Linux-Kernel-Version"; - /// Custom Zap header indicating the client role. We don't use the User-Agent header + /// Custom Zaplex header indicating the client role. We don't use the User-Agent header /// because it can't be set from WASM. - pub(crate) const WARP_CLIENT_ID: &str = "X-Zap-Client-ID"; + pub(crate) const ZAPLEX_CLIENT_ID: &str = "X-Zap-Client-ID"; } /// The environment variable containing extra HTTP headers to attach to requests. /// Only read when the channel is `Channel::Integration`. The value is a newline-separated /// list of `Name:Value` pairs, where each pair is split on the first colon. -const EXTRA_HTTP_HEADERS_ENV_VAR: &str = "WARP_EXTRA_HTTP_HEADERS"; +const EXTRA_HTTP_HEADERS_ENV_VAR: &str = "ZAPLEX_EXTRA_HTTP_HEADERS"; /// A wrapper around a `reqwest::Client` to execute requests. Returns a custom `RequestBuilder` type /// that ensures any call to the underlying `reqwest::Client` are properly adapted so that they can @@ -260,7 +260,7 @@ impl Client { fn add_warp_http_headers(mut builder: RequestBuilder) -> RequestBuilder { // Include the client ID header. if let Some(client_id) = execution_mode::current_client_id() { - builder = builder.header(headers::WARP_CLIENT_ID, client_id); + builder = builder.header(headers::ZAPLEX_CLIENT_ID, client_id); } // If there's an app version, include it as an HTTP request header. @@ -302,12 +302,12 @@ impl Client { // Operating system category. let category = os_system_info.category().to_string(); if let Ok(category) = HeaderValue::from_str(&category) { - builder = builder.header(headers::WARP_OS_CATEGORY, category); + builder = builder.header(headers::ZAPLEX_OS_CATEGORY, category); } // Operating system name. builder = builder.header( - headers::WARP_OS_NAME, + headers::ZAPLEX_OS_NAME, HeaderValue::from_static(os_system_info.name()), ); @@ -316,7 +316,7 @@ impl Client { .version() .and_then(|version| HeaderValue::from_str(version).ok()) { - builder = builder.header(headers::WARP_OS_VERSION, version); + builder = builder.header(headers::ZAPLEX_OS_VERSION, version); } // Linux kernel version. @@ -325,7 +325,7 @@ impl Client { .and_then(|kernel_version| HeaderValue::from_str(kernel_version).ok()) { builder = - builder.header(headers::WARP_OS_LINUX_KERNEL_VERSION, linux_kernel_version); + builder.header(headers::ZAPLEX_OS_LINUX_KERNEL_VERSION, linux_kernel_version); } } diff --git a/crates/http_client/src/proxy.rs b/crates/http_client/src/proxy.rs index 00843fbf9f..c4bc1886c8 100644 --- a/crates/http_client/src/proxy.rs +++ b/crates/http_client/src/proxy.rs @@ -1,6 +1,6 @@ //! Global HTTP proxy configuration. //! -//! See Issue #72: Zap needs a globally-configurable proxy setting that uniformly covers +//! See Issue #72: Zaplex needs a globally-configurable proxy setting that uniformly covers //! all outbound HTTP requests (BYOP model list fetching, autoupdate, conversation loading, etc.). //! //! Design points: diff --git a/crates/http_server/src/lib.rs b/crates/http_server/src/lib.rs index b5b175bc42..1286c9ca3d 100644 --- a/crates/http_server/src/lib.rs +++ b/crates/http_server/src/lib.rs @@ -3,11 +3,11 @@ use std::net::SocketAddr; use tower_http::trace::TraceLayer; use warpui::{Entity, ModelContext, SingletonEntity}; -// Spells "Zap" - should hopefully not conflict with other ports. +// Spells "Zaplex" - should hopefully not conflict with other ports. // Does not conflict with known ports on https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers const PORT: u16 = 9277; -/// A singleton model for the small HTTP server that is run by the Zap client. +/// A singleton model for the small HTTP server that is run by the Zaplex client. pub struct HttpServer { /// The tokio runtime that the HTTP server runs on. /// diff --git a/crates/integration/src/bin/integration.rs b/crates/integration/src/bin/integration.rs index e7c7a8eadd..3d0580bb56 100644 --- a/crates/integration/src/bin/integration.rs +++ b/crates/integration/src/bin/integration.rs @@ -8,7 +8,7 @@ use warp_cli::WorkerCommand; use warp_core::channel::{Channel, ChannelConfig, ChannelState}; use warp_core::AppId; -/// The Zap integration test runner. +/// The Zaplex integration test runner. #[derive(Debug, Default, Parser, Clone)] #[command(name = "warp-integration-test")] #[clap(args_conflicts_with_subcommands = true)] @@ -313,7 +313,7 @@ fn register_tests() -> HashMap<&'static str, BoxedBuilderFn> { register_test!(test_history_command_is_linked_to_local_workflow); register_test!(test_up_arrow_history_enters_shift_tab_for_workflow); - // Zap (localization, Phase 5): websocket integration tests removed with Listener physical deletion. + // Zaplex (localization, Phase 5): websocket integration tests removed with Listener physical deletion. register_test!(test_secret_is_obfuscated_on_copy); register_test!(test_secret_tooltip_shows_on_click); diff --git a/crates/integration/src/test.rs b/crates/integration/src/test.rs index 66ecd91609..7096aa4822 100644 --- a/crates/integration/src/test.rs +++ b/crates/integration/src/test.rs @@ -272,7 +272,7 @@ fn new_builder() -> Builder { pub fn test_add_workflows_to_warp_config() -> Builder { new_builder() .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); std::fs::create_dir_all(integration_testing::workflow::workflows_dir()) .expect("Should be able to create workflows dir"); @@ -326,7 +326,7 @@ pub fn test_add_workflows_to_warp_config() -> Builder { pub fn test_launch_warp_with_theme_in_warp_config() -> Builder { new_builder() .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); integration_testing::create_file_from_assets( TEST_ONLY_ASSETS, @@ -342,7 +342,7 @@ pub fn test_launch_warp_with_theme_in_warp_config() -> Builder { pub fn test_add_theme_to_warp_config() -> Builder { new_builder() .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); std::fs::create_dir_all(integration_testing::themes::themes_dir()) .expect("Should be able to create themes dir"); @@ -626,7 +626,7 @@ pub fn test_suggestions_menu_positioning() -> Builder { ), ) .with_step( - new_step_with_default_assertions("Open Zap Drive") + new_step_with_default_assertions("Open Zaplex Drive") .with_click_on_saved_position("workspace:toggle_left_panel") .add_assertion(assert_is_left_panel_open()), ) @@ -2485,7 +2485,7 @@ precmd_functions+=(_p9k_precmd) ) .with_step(wait_until_bootstrapped_single_pane_for_tab(1)) .with_step(check_banner_open(1, true)) - // If the user then switches back to the Zap prompt, we should close the banner. + // If the user then switches back to the Zaplex prompt, we should close the banner. .with_step( new_step_with_default_assertions("Disable honor_ps1").with_action(|app, _, _| { SessionSettings::handle(app).update(app, |session_settings, ctx| { @@ -3383,7 +3383,7 @@ pub fn test_custom_ps1_expansion_bash() -> Builder { ) } -/// Default auto title. We test that Zap's auto title is used and verify that that +/// Default auto title. We test that Zaplex's auto title is used and verify that that /// DISABLE_AUTO_TITLE is set correctly. pub fn test_auto_title() -> Builder { new_builder() @@ -3405,7 +3405,7 @@ pub fn test_auto_title() -> Builder { )) } -/// Validate that disabling Zap's auto title feature will not mess with oh-my-zsh settings. +/// Validate that disabling Zaplex's auto title feature will not mess with oh-my-zsh settings. pub fn test_warp_auto_title_disabled() -> Builder { new_builder() .set_should_run_test(|| { @@ -3421,20 +3421,20 @@ pub fn test_warp_auto_title_disabled() -> Builder { write_rc_files_for_test( &dir, r#" -WARP_DISABLE_AUTO_TITLE="1" +ZAPLEX_DISABLE_AUTO_TITLE="1" "#, [ShellRcType::Bash], ); write_rc_files_for_test( &dir, r#" -WARP_DISABLE_AUTO_TITLE="true" +ZAPLEX_DISABLE_AUTO_TITLE="true" "#, [ShellRcType::Zsh], ); }) .with_step(wait_until_bootstrapped_single_pane_for_tab(0)) - // If Zap title is disabled, we don't set the DISABLE_AUTO_TITLE env variable + // If Zaplex title is disabled, we don't set the DISABLE_AUTO_TITLE env variable .with_step(execute_command_for_single_terminal_in_tab( 0, "echo $DISABLE_AUTO_TITLE".to_string(), @@ -3446,7 +3446,7 @@ WARP_DISABLE_AUTO_TITLE="true" )) } -/// Checks that the tab title set by the user takes precedence over the Zap's default title and +/// Checks that the tab title set by the user takes precedence over the Zaplex's default title and /// doesn't require any additional setting from the user's POV. This is bash-specific test. pub fn test_warp_honors_user_title_bash() -> Builder { new_builder() @@ -3478,7 +3478,7 @@ PROMPT_COMMAND='echo -en "\033]0;TEST_TAB_TITLE\a"' )) } -/// Checks that the tab title set by the user takes precedence over the Zap's default title and +/// Checks that the tab title set by the user takes precedence over the Zaplex's default title and /// doesn't require any additional setting from the user's POV. This is zsh-specific test. pub fn test_warp_honors_user_title_zsh() -> Builder { new_builder() @@ -5580,7 +5580,7 @@ pub fn test_terminal_announces_capabilities_to_shell() -> Builder { 0, format!("echo ${var_prefix}TERM_PROGRAM"), ExpectedExitStatus::Success, - "WarpTerminal", + "ZaplexTerminal", )) .with_step(execute_command_for_single_terminal_in_tab( 0, @@ -6734,7 +6734,7 @@ pub fn test_context_chips_prompt_at_bootstrap() -> Builder { (String::from("SavedPrompt"), String::from("Default")), ])) .with_step( - new_step_with_default_assertions("Check Zap prompt") + new_step_with_default_assertions("Check Zaplex prompt") .add_assertion(assert_working_dir_is_present(0)), ) } diff --git a/crates/integration/src/test/ai_assistant.rs b/crates/integration/src/test/ai_assistant.rs index 386a02501e..f953c4d9a3 100644 --- a/crates/integration/src/test/ai_assistant.rs +++ b/crates/integration/src/test/ai_assistant.rs @@ -11,7 +11,7 @@ use warpui::async_assert; use super::new_builder; -/// Checks if the Ask Zap AI keybinding works correctly when a block is selected. +/// Checks if the Ask Zaplex AI keybinding works correctly when a block is selected. /// This is a regression test: https://linear.app/warpdotdev/issue/WAR-6758/warp-ai-ask-from-block-keybinding-doesnt-work-as-expected. pub fn test_ask_warp_ai_keybinding_for_selected_block() -> Builder { new_builder() diff --git a/crates/integration/src/test/bootstrapping.rs b/crates/integration/src/test/bootstrapping.rs index 9d08fcaedc..cc8455d238 100644 --- a/crates/integration/src/test/bootstrapping.rs +++ b/crates/integration/src/test/bootstrapping.rs @@ -104,7 +104,7 @@ pub fn test_paste_and_type_characters_before_bootstrap() -> Builder { .add_named_assertion("Validate block contents", assert_active_block_command_for_single_terminal_in_tab("Enter some user input: ", 0)) ) .with_step( - TestStep::new("Zap input should not start focused, since .rc file is reading user input") + TestStep::new("Zaplex input should not start focused, since .rc file is reading user input") .add_assertion(input_editor_is_not_focused(0)) ) .with_step( @@ -200,7 +200,7 @@ pub fn test_paste_and_type_characters_before_bootstrap() -> Builder { ) .with_step(wait_until_bootstrapped_single_pane_for_tab(0)) .with_step( - new_step_with_default_assertions("Zap input should be focused and keep buffered text") + new_step_with_default_assertions("Zaplex input should be focused and keep buffered text") .add_assertion(input_editor_is_focused(0)) .add_assertion(input_contains_string(0, "this is the pasted textthese are some typed characters".to_owned())) ) diff --git a/crates/integration/src/test/code_review.rs b/crates/integration/src/test/code_review.rs index 54c86538fd..b578b16476 100644 --- a/crates/integration/src/test/code_review.rs +++ b/crates/integration/src/test/code_review.rs @@ -167,7 +167,7 @@ fn code_review_scroll_anchor_builder( .expect("should write initial committed contents"); run_git(&repo_dir, &["init", "-b", "main"]); run_git(&repo_dir, &["config", "user.email", "test@example.com"]); - run_git(&repo_dir, &["config", "user.name", "Zap Integration Test"]); + run_git(&repo_dir, &["config", "user.name", "Zaplex Integration Test"]); run_git(&repo_dir, &["add", TEST_FILE_NAME]); run_git(&repo_dir, &["commit", "-m", "Initial commit"]); @@ -325,7 +325,7 @@ pub fn test_code_review_scroll_preserved_deleted_range() -> Builder { .expect("should write initial committed contents"); run_git(&repo_dir, &["init", "-b", "main"]); run_git(&repo_dir, &["config", "user.email", "test@example.com"]); - run_git(&repo_dir, &["config", "user.name", "Zap Integration Test"]); + run_git(&repo_dir, &["config", "user.name", "Zaplex Integration Test"]); run_git(&repo_dir, &["add", TEST_FILE_NAME]); run_git(&repo_dir, &["commit", "-m", "Initial commit"]); @@ -408,7 +408,7 @@ pub fn test_code_review_scroll_preserved_header_range() -> Builder { .expect("should write initial committed contents"); run_git(&repo_dir, &["init", "-b", "main"]); run_git(&repo_dir, &["config", "user.email", "test@example.com"]); - run_git(&repo_dir, &["config", "user.name", "Zap Integration Test"]); + run_git(&repo_dir, &["config", "user.name", "Zaplex Integration Test"]); run_git(&repo_dir, &["add", TEST_FILE_NAME]); run_git(&repo_dir, &["commit", "-m", "Initial commit"]); @@ -501,7 +501,7 @@ pub fn test_code_review_scroll_preserved_footer_range() -> Builder { run_git(&repo_dir, &["init", "-b", "main"]); run_git(&repo_dir, &["config", "user.email", "test@example.com"]); - run_git(&repo_dir, &["config", "user.name", "Zap Integration Test"]); + run_git(&repo_dir, &["config", "user.name", "Zaplex Integration Test"]); run_git(&repo_dir, &["add", FIRST_FILE_NAME, SECOND_FILE_NAME]); run_git(&repo_dir, &["commit", "-m", "Initial commit"]); @@ -595,7 +595,7 @@ pub fn test_code_review_scroll_preserved_second_file() -> Builder { run_git(&repo_dir, &["init", "-b", "main"]); run_git(&repo_dir, &["config", "user.email", "test@example.com"]); - run_git(&repo_dir, &["config", "user.name", "Zap Integration Test"]); + run_git(&repo_dir, &["config", "user.name", "Zaplex Integration Test"]); run_git(&repo_dir, &["add", FIRST_FILE_NAME, SECOND_FILE_NAME]); run_git(&repo_dir, &["commit", "-m", "Initial commit"]); diff --git a/crates/integration/src/test/file_tree.rs b/crates/integration/src/test/file_tree.rs index f0b2f91304..e9690b26b0 100644 --- a/crates/integration/src/test/file_tree.rs +++ b/crates/integration/src/test/file_tree.rs @@ -30,9 +30,9 @@ fn open_file_tree_panel(app: &mut App) { }); } -/// Test that clicking a file in the file tree opens it in Zap's editor. +/// Test that clicking a file in the file tree opens it in Zaplex's editor. /// This is a regression test for the bug where files were being opened in -/// external editors instead of Zap's built-in editor. +/// external editors instead of Zaplex's built-in editor. pub fn test_file_tree_opens_files_in_warp() -> Builder { new_builder() .with_setup(|utils| { @@ -79,7 +79,7 @@ pub fn test_file_tree_opens_files_in_warp() -> Builder { }), ) .with_step( - new_step_with_default_assertions("Verify file opened in Zap editor").add_assertion( + new_step_with_default_assertions("Verify file opened in Zaplex editor").add_assertion( assert_pane_title(0, 1, Regex::new(r"test_file\.txt$").unwrap()), ), ) @@ -238,7 +238,7 @@ pub fn test_file_tree_non_openable_files() -> Builder { .expect("Should be able to convert test dir to str"); write_all_rc_files_for_test(&test_dir, format!("cd {dir_string}")); - // Create a binary file that shouldn't be opened in Zap + // Create a binary file that shouldn't be opened in Zaplex std::fs::write(test_dir.join("test.bin"), vec![0u8, 1, 2, 3, 255]) .expect("Failed to create binary file"); }) @@ -251,14 +251,14 @@ pub fn test_file_tree_non_openable_files() -> Builder { new_step_with_default_assertions("Click on binary file") .with_click_on_saved_position("file_tree_item:test.bin") .add_assertion(|app, window_id| { - // The binary file should NOT open in a new pane in Zap + // The binary file should NOT open in a new pane in Zaplex // It should fall back to system default behavior let pane_group = pane_group_view(app, window_id, 0); pane_group.read(app, |pane_group, _ctx| { async_assert_eq!( pane_group.pane_count(), 1, - "Binary file should not open in Zap, should stay at 1 pane" + "Binary file should not open in Zaplex, should stay at 1 pane" ) }) }), diff --git a/crates/integration/src/test/history.rs b/crates/integration/src/test/history.rs index deaad8a388..05d12959a8 100644 --- a/crates/integration/src/test/history.rs +++ b/crates/integration/src/test/history.rs @@ -297,7 +297,7 @@ pub fn test_command_search_loads_history_from_nondefault_histfile_path() -> Buil /// histfile commands, effectively "enriching" them with metadata. /// /// Basically, if a user manually deletes a command from their shell histfile, it should not show -/// up in Zap -- so we effectively do a "left join" on commands from the histfile with commands +/// up in Zaplex -- so we effectively do a "left join" on commands from the histfile with commands /// loaded from sqlite. pub fn test_histfile_left_joined_with_persisted_history() -> Builder { new_builder() diff --git a/crates/integration/src/test/launch_configs.rs b/crates/integration/src/test/launch_configs.rs index 31bcb88633..aae78c4aec 100644 --- a/crates/integration/src/test/launch_configs.rs +++ b/crates/integration/src/test/launch_configs.rs @@ -33,7 +33,7 @@ use warp::{ pub fn test_add_launch_config_to_warp_config() -> Builder { new_builder() .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); std::fs::create_dir_all(integration_testing::launch_configs::launch_configs_dir()) .expect("Should be able to create launch configs dir"); @@ -135,7 +135,7 @@ pub fn test_open_launch_config_from_add_tab_menu_legacy() -> Builder { new_builder() .set_should_run_test(|| !FeatureFlag::ShellSelector.is_enabled()) .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); // Write a new launch config file. Launch config is named "Launch Config" let dir = integration_testing::launch_configs::launch_configs_dir(); diff --git a/crates/integration/src/test/notebooks.rs b/crates/integration/src/test/notebooks.rs index 942a5034a8..7f42aefa5c 100644 --- a/crates/integration/src/test/notebooks.rs +++ b/crates/integration/src/test/notebooks.rs @@ -165,7 +165,7 @@ pub fn test_open_in_warp_banner() -> Builder { .add_assertion(assert_open_in_warp_banner_open(0, 0)), ) .with_step( - new_step_with_default_assertions("Click Open in Zap banner") + new_step_with_default_assertions("Click Open in Zaplex banner") .with_click_on_saved_position_fn(|app, window_id| { let view = terminal_view(app, window_id, 0, 0); format!("open_in_warp_banner_button_{}", view.id()) diff --git a/crates/integration/src/test/session_restoration.rs b/crates/integration/src/test/session_restoration.rs index ac268f1026..aea96900c3 100644 --- a/crates/integration/src/test/session_restoration.rs +++ b/crates/integration/src/test/session_restoration.rs @@ -83,7 +83,7 @@ pub fn test_session_restoration() -> Builder { /// always be added. Mock data for this case: /// | command | output | shell | user | host | /// | ------------------ | ------------ | ----- | ---------- | ------------- | -/// | echo $TERM_PROGRAM | WarpTerminal | zsh | local:user | local:host | +/// | echo $TERM_PROGRAM | ZaplexTerminal | zsh | local:user | local:host | /// | pwd | / | bash | local:user | local:host | /// | uname | Linux | zsh | andy | ubuntu-test | /// | mkdir secrets | secrets | NULL | NULL | NULL | diff --git a/crates/integration/src/test/settings_file_errors.rs b/crates/integration/src/test/settings_file_errors.rs index ff0f5bbc7c..5ba1d7d960 100644 --- a/crates/integration/src/test/settings_file_errors.rs +++ b/crates/integration/src/test/settings_file_errors.rs @@ -106,7 +106,7 @@ pub fn test_settings_error_banner_on_reload_with_invalid_toml() -> Builder { new_builder() .with_setup(move |utils| { // Use a short watcher delay so the reload fires quickly. - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some("10".to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some("10".to_string())); // Create a valid settings file so the watcher is already tracking // it. The reload tests modify this file rather than creating a new @@ -187,7 +187,7 @@ pub fn test_settings_error_banner_on_reload_with_invalid_value() -> Builder { new_builder() .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some("10".to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some("10".to_string())); // Create the settings file at startup so the watcher tracks it. let path = toml_file_path(); diff --git a/crates/integration/src/test/settings_file_hot_reload.rs b/crates/integration/src/test/settings_file_hot_reload.rs index 225d980ce8..df19ad0b2e 100644 --- a/crates/integration/src/test/settings_file_hot_reload.rs +++ b/crates/integration/src/test/settings_file_hot_reload.rs @@ -2,7 +2,7 @@ //! //! Verifies that changes to `settings.toml` on disk are picked up by the //! filesystem watcher and pushed into the in-memory setting models, on every -//! platform where Zap watches `config_local_dir()`. +//! platform where Zaplex watches `config_local_dir()`. use settings::Setting as _; use std::time::Duration; @@ -33,7 +33,7 @@ pub fn test_settings_file_hot_reload_applies_new_values() -> Builder { new_builder() .with_setup(move |utils| { // Use a short watcher delay so each reload fires quickly. - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some("10".to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some("10".to_string())); // Write an initial valid settings file so the watcher is already // tracking it and the app reads a known value at startup. diff --git a/crates/integration/src/test/ssh.rs b/crates/integration/src/test/ssh.rs index 62d2245f24..b56cbd78ec 100644 --- a/crates/integration/src/test/ssh.rs +++ b/crates/integration/src/test/ssh.rs @@ -126,7 +126,7 @@ fn verify_login_shell(shell: &str) -> TestStep { "zsh" => "[[ -o login ]]", "fish" => "status --is-login", // For other shells, we don't actually start a login shell but do source /etc/profile. - _ => "test \"$WARP_PROFILE_LOADED\" = true", + _ => "test \"$ZAPLEX_PROFILE_LOADED\" = true", }; match shell { @@ -197,7 +197,7 @@ macro_rules! generate_can_bootstrap_tmux_ssh_test_for_shell { /// Ensure we can successfully ssh into a $shell remote shell and bootstrap it /// successfully. pub fn $fn_name() -> Builder { - fn warpify(builder: Builder) -> Builder { + fn zaplexify(builder: Builder) -> Builder { builder .with_step(enter_ssh_command($shell)) .with_step(wait_for_password_prompt(0 /*tab_idx*/, $shell)) @@ -209,7 +209,7 @@ macro_rules! generate_can_bootstrap_tmux_ssh_test_for_shell { .with_step(trigger_subshell_bootstrap()) } - fn assert_warpification(builder: Builder) -> Builder { + fn assert_zaplexification(builder: Builder) -> Builder { builder .with_step(assert_subshell_is_bootstrapped(0, 0)) .with_step(wait_until_bootstrapped_single_pane_for_tab(0)) @@ -234,14 +234,14 @@ macro_rules! generate_can_bootstrap_tmux_ssh_test_for_shell { .with_step(wait_until_bootstrapped_single_pane_for_tab(0)) .with_step(setup_gcloud_sdk()); // Install Tmux - let builder = warpify(builder).with_step( + let builder = zaplexify(builder).with_step( accept_tmux_install().set_post_step_pause(std::time::Duration::from_secs(3)), ); - // Quit SSH Session once we validate warpificaiton works with Tmux Install - let builder = assert_warpification(builder).with_step(run_exit_command()); + // Quit SSH Session once we validate zaplexificaiton works with Tmux Install + let builder = assert_zaplexification(builder).with_step(run_exit_command()); - // Validate we can Warpify when Tmux is already installed - assert_warpification(warpify(builder)) + // Validate we can Zaplexify when Tmux is already installed + assert_zaplexification(zaplexify(builder)) } }; } diff --git a/crates/integration/src/test/subshell.rs b/crates/integration/src/test/subshell.rs index b6079fa40c..d19b845393 100644 --- a/crates/integration/src/test/subshell.rs +++ b/crates/integration/src/test/subshell.rs @@ -16,7 +16,7 @@ use warp::{ }, terminal::wait_until_bootstrapped_single_pane_for_tab, }, - terminal::warpify::settings::AddedSubshellCommands, + terminal::zaplexify::settings::AddedSubshellCommands, }; use warpui::integration::{AssertionOutcome, TestStep}; use warpui::windowing::state::ApplicationStage; @@ -88,7 +88,7 @@ generate_can_bootstrap_remote_subshell_for_shell!(test_can_bootstrap_remote_bash // generate_can_bootstrap_remote_subshell_for_shell!(test_can_bootstrap_remote_fish_subshell, "fish"); // Test the flow of creating a new window and running a command that should create a subshell and -// automaticall bootstrapping AKA "warpifying" that subshell. +// automaticall bootstrapping AKA "zaplexifying" that subshell. pub fn test_can_auto_bootstrap() -> Builder { const SUBSHELL_COMMAND: &str = "zsh"; diff --git a/crates/integration/src/test/typeahead.rs b/crates/integration/src/test/typeahead.rs index 744759cb12..1acee58155 100644 --- a/crates/integration/src/test/typeahead.rs +++ b/crates/integration/src/test/typeahead.rs @@ -74,7 +74,7 @@ macro_rules! check_command { }; } -/// Tests that the shell reports its input buffer to the Zap typeahead model after +/// Tests that the shell reports its input buffer to the Zaplex typeahead model after /// a long-running command completes. pub fn test_input_reporting_posix_shells() -> Builder { // When the shell can report its input buffer, we can handle typeahead with diff --git a/crates/integration/src/test/workflows.rs b/crates/integration/src/test/workflows.rs index 92e4148b94..f763e52b81 100644 --- a/crates/integration/src/test/workflows.rs +++ b/crates/integration/src/test/workflows.rs @@ -93,7 +93,7 @@ pub fn test_create_team_workflow_pane_from_command_palette() -> Builder { pub fn test_loading_project_workflows() -> Builder { new_builder() .with_setup(move |utils| { - utils.set_env("WARP_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); + utils.set_env("ZAPLEX_CONFIG_WATCHER_DELAY_MS", Some((10).to_string())); }) .with_step(wait_until_bootstrapped_single_pane_for_tab(0)) .with_step( diff --git a/crates/integration/src/util.rs b/crates/integration/src/util.rs index d2689b192a..1ee84dcf79 100644 --- a/crates/integration/src/util.rs +++ b/crates/integration/src/util.rs @@ -139,7 +139,7 @@ pub fn write_rc_files_for_test( } } -/// Writes the same `rc_contents` for all possible shell types supported by Zap. +/// Writes the same `rc_contents` for all possible shell types supported by Zaplex. pub fn write_all_rc_files_for_test(dir: P, rc_contents: C) where P: AsRef, diff --git a/crates/integration/tests/INTEGRATION_TESTING.md b/crates/integration/tests/INTEGRATION_TESTING.md index c9e7fbfeb7..7db9208162 100644 --- a/crates/integration/tests/INTEGRATION_TESTING.md +++ b/crates/integration/tests/INTEGRATION_TESTING.md @@ -92,7 +92,7 @@ async_assert_eq!( Since many of our tests are async, I would recommend running in a loop locally before merging to avoid flakes e.g. ```sh for i in {0..100}; do - WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS=1 RUST_BACKTRACE=full WARP_SHELL_PATH=/bin/bash cargo run -p integration -- test_simple_example + WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS=1 RUST_BACKTRACE=full ZAPLEX_SHELL_PATH=/bin/bash cargo run -p integration -- test_simple_example if [ $? -ne 0 ]; then return; fi done ``` @@ -115,8 +115,8 @@ To run a specific integration test you can use: The `WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS="1"` will force the new terminal window to open, which helps a lot when iterating on your integration test implementation! ### Known issues / limitations -* To determine (from the `TestStep`) which shell is used for the test, you can try checking `WARP_SHELL_PATH` environment variable (that works within the CI on github) or check the passwd for the user (for local runs). -* Similarly you can run the test with a specific shell by setting the `WARP_SHELL_PATH` and then running the test. Note that if you're running with fish, you also need to pass in `--features fish_shell` until that feature flag is removed. For example: `WARP_SHELL_PATH=/usr/local/bin/fish`, then `cargo run --bin integration --features fish_shell -- test_simple_example` +* To determine (from the `TestStep`) which shell is used for the test, you can try checking `ZAPLEX_SHELL_PATH` environment variable (that works within the CI on github) or check the passwd for the user (for local runs). +* Similarly you can run the test with a specific shell by setting the `ZAPLEX_SHELL_PATH` and then running the test. Note that if you're running with fish, you also need to pass in `--features fish_shell` until that feature flag is removed. For example: `ZAPLEX_SHELL_PATH=/usr/local/bin/fish`, then `cargo run --bin integration --features fish_shell -- test_simple_example` * Bindings aren't exposed by default in integration tests, add them in the file of the original binding. Example from `editor/view.rs`: ```rust diff --git a/crates/integration/tests/common/mod.rs b/crates/integration/tests/common/mod.rs index 67d94fe47e..20de6c3797 100644 --- a/crates/integration/tests/common/mod.rs +++ b/crates/integration/tests/common/mod.rs @@ -27,7 +27,7 @@ pub fn run_integration_test(name: &str) -> Result<(), String> { // Propagate any Rust-related variables. || k.starts_with("RUST_") // Propagate any Zap-specific variables. - || k.starts_with("WARP_") + || k.starts_with("ZAPLEX_") || k.starts_with("WARPUI_") // Propagate any wgpu-specific variables. || k.starts_with("WGPU_") @@ -46,7 +46,7 @@ pub fn run_integration_test(name: &str) -> Result<(), String> { .arg(name) .env_clear() .envs(inherited_envs) - .env("WARP_INTEGRATION", "1") + .env("ZAPLEX_INTEGRATION", "1") .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .status() @@ -121,7 +121,7 @@ pub fn run_integration_test(name: &str) -> Result<(), String> { // Propagate any Rust-related variables. || k.starts_with("RUST_") // Propagate any Zap-specific variables. - || k.starts_with("WARP_") + || k.starts_with("ZAPLEX_") || k.starts_with("WARPUI_") // Propagate any wgpu-specific variables. || k.starts_with("WGPU_") @@ -157,7 +157,7 @@ pub fn run_integration_test(name: &str) -> Result<(), String> { .arg(name) .env_clear() .envs(inherited_envs) - .env("WARP_INTEGRATION", "1") + .env("ZAPLEX_INTEGRATION", "1") .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .stdin(Stdio::null()) diff --git a/crates/integration/tests/data/test_workflow.yaml b/crates/integration/tests/data/test_workflow.yaml index 13f7ffb92b..72d052f7bb 100644 --- a/crates/integration/tests/data/test_workflow.yaml +++ b/crates/integration/tests/data/test_workflow.yaml @@ -1,6 +1,6 @@ --- name: "Run Warp locally with shell" -command: "WARP_SHELL_PATH={{shell}} cargo run" +command: "ZAPLEX_SHELL_PATH={{shell}} cargo run" description: "Runs warp with the particular shell so devs don't need to change their login shell to test a different shell locally" arguments: - name: shell @@ -9,7 +9,7 @@ author: Warp Team shells: [] --- name: "Run Warp locally with shell 2: Electric Boogaloo" -command: "WARP_SHELL_PATH={{shell}} cargo run" +command: "ZAPLEX_SHELL_PATH={{shell}} cargo run" description: "Runs warp with the particular shell so devs don't need to change their login shell to test a different shell locally" arguments: - name: shell diff --git a/crates/ipc/src/lib.rs b/crates/ipc/src/lib.rs index 7e1042a2e3..bd50f5f009 100644 --- a/crates/ipc/src/lib.rs +++ b/crates/ipc/src/lib.rs @@ -4,11 +4,11 @@ //! corresponding typed "clients" ([`ServiceCaller`]s) which provide a typed interface to call the //! services across process boundaries. //! -//! This is intended to initially be used to support communication between the Zap app and +//! This is intended to initially be used to support communication between the Zaplex app and //! third-party plugins running in a separate "plugin host" process, but is designed generically to //! be extended to other use cases (such as the terminal server). Where possible, //! transport-specific details are abstracted out to eventually support the same protocol on top of -//! the WebWorkers `MessagePort` API in the browser for Zap on Web. +//! the WebWorkers `MessagePort` API in the browser for Zaplex on Web. //! //! On native platforms, this is implemented on top of the `interprocess` crate, which uses //! Unix Domain Sockets on Unix platforms and named pipes on Windows as the underlying transport. diff --git a/crates/isolation_platform/src/lib.rs b/crates/isolation_platform/src/lib.rs index 2505fab6d1..0b26033ef2 100644 --- a/crates/isolation_platform/src/lib.rs +++ b/crates/isolation_platform/src/lib.rs @@ -16,30 +16,30 @@ mod namespace; /// Environment variable set by the server to identify the isolation platform. /// The value should match one of the `IsolationPlatformType` variants in snake_case. #[cfg(not(target_family = "wasm"))] -const WARP_ISOLATION_PLATFORM_ENV: &str = "WARP_ISOLATION_PLATFORM"; +const ZAPLEX_ISOLATION_PLATFORM_ENV: &str = "ZAPLEX_ISOLATION_PLATFORM"; /// Environment variable containing the generic Zap-managed workload token that we use /// for isolation platforms that don't issue their own tokens. #[cfg(not(target_family = "wasm"))] -const WARP_WORKLOAD_TOKEN_ENV: &str = "WARP_WORKLOAD_TOKEN"; +const ZAPLEX_WORKLOAD_TOKEN_ENV: &str = "ZAPLEX_WORKLOAD_TOKEN"; -/// A kind of isolation platform. For our usage, isolation platforms are different ways where Zap +/// A kind of isolation platform. For our usage, isolation platforms are different ways where Zaplex /// can be sandboxed, such as VMs, containers, or cloud hosts. This may also include weaker forms /// of sandboxing such as Git worktrees. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] #[serde(rename_all = "snake_case")] pub enum IsolationPlatformType { - /// Zap is running within a Docker container. Note that this does *not* mean this is a Zap-hosted + /// Zaplex is running within a Docker container. Note that this does *not* mean this is a Zap-hosted /// Docker Sandboxes environment. Instead, it's likely a self-hosted agent. #[cfg(not(target_family = "wasm"))] Docker, - /// Zap is running within a Docker Sandbox, likely as a Zap-hosted agent. + /// Zaplex is running within a Docker Sandbox, likely as a Zap-hosted agent. #[cfg(not(target_family = "wasm"))] DockerSandbox, - /// Zap is running within a Kubernetes pod, likely as a self-hosted agent. + /// Zaplex is running within a Kubernetes pod, likely as a self-hosted agent. #[cfg(not(target_family = "wasm"))] Kubernetes, - /// Zap is running within a Namespace instance, likely as a Zap-hosted agent. + /// Zaplex is running within a Namespace instance, likely as a Zap-hosted agent. #[cfg(not(target_family = "wasm"))] Namespace, } @@ -133,11 +133,11 @@ pub async fn issue_workload_token( } } -/// Read a platform-agnostic workload token from the `WARP_WORKLOAD_TOKEN` environment variable. +/// Read a platform-agnostic workload token from the `ZAPLEX_WORKLOAD_TOKEN` environment variable. /// Returns a `WorkloadToken` with no expiration, or an error if the variable is missing/empty. #[cfg(not(target_family = "wasm"))] fn read_generic_workload_token() -> Result { - let token = std::env::var(WARP_WORKLOAD_TOKEN_ENV) + let token = std::env::var(ZAPLEX_WORKLOAD_TOKEN_ENV) .map_err(|_| IsolationPlatformError::GenericWorkloadTokenMissing)?; if token.is_empty() { return Err(IsolationPlatformError::GenericWorkloadTokenMissing); @@ -148,17 +148,17 @@ fn read_generic_workload_token() -> Result Option { - let value = std::env::var(WARP_ISOLATION_PLATFORM_ENV).ok()?; + let value = std::env::var(ZAPLEX_ISOLATION_PLATFORM_ENV).ok()?; match value.as_str() { "docker" => Some(IsolationPlatformType::Docker), "docker_sandbox" => Some(IsolationPlatformType::DockerSandbox), "kubernetes" => Some(IsolationPlatformType::Kubernetes), "namespace" => Some(IsolationPlatformType::Namespace), other => { - log::warn!("Unknown {WARP_ISOLATION_PLATFORM_ENV} value: {other}"); + log::warn!("Unknown {ZAPLEX_ISOLATION_PLATFORM_ENV} value: {other}"); None } } diff --git a/crates/markdown_parser/src/html_parser.rs b/crates/markdown_parser/src/html_parser.rs index 2ef23aeb27..da8d6ce404 100644 --- a/crates/markdown_parser/src/html_parser.rs +++ b/crates/markdown_parser/src/html_parser.rs @@ -28,7 +28,7 @@ const PHRASING_ELEMENT_TAGS: &[&str] = &[ "span", "i", "code", "strong", "em", "br", "a", "s", "u", "ins", ]; -pub const WARP_EMBED_ATTRIBUTE_NAME: &str = "data-warp-embedded-item"; +pub const ZAPLEX_EMBED_ATTRIBUTE_NAME: &str = "data-warp-embedded-item"; #[derive(Clone, Debug, PartialEq, Eq)] struct ListArg { @@ -267,7 +267,7 @@ pub fn parse_html(html: &str) -> Result { result.push_back(match node_name.as_str() { // If it's a code block, process its children node as plain text. "pre" => { - if let Some(val) = get_attribute(&attrs.borrow(), WARP_EMBED_ATTRIBUTE_NAME) + if let Some(val) = get_attribute(&attrs.borrow(), ZAPLEX_EMBED_ATTRIBUTE_NAME) { FormattedTextLine::Embedded(Mapping::from_iter([( Value::String("id".to_string()), diff --git a/crates/markdown_parser/src/markdown_parser_test.rs b/crates/markdown_parser/src/markdown_parser_test.rs index 5454f75f78..36a04c4ca9 100644 --- a/crates/markdown_parser/src/markdown_parser_test.rs +++ b/crates/markdown_parser/src/markdown_parser_test.rs @@ -2702,7 +2702,7 @@ fn test_parse_table_with_empty_cells() { #[test] fn test_parse_table_with_links() { - let source = "| Link | Text |\n| --- | --- |\n| [Zap](https://warp.dev) | normal |\n"; + let source = "| Link | Text |\n| --- | --- |\n| [Zaplex](https://warp.dev) | normal |\n"; let result = test_parse_markdown_with_gfm_tables(source); assert_eq!(result.len(), 1); @@ -2710,7 +2710,7 @@ fn test_parse_table_with_links() { assert_eq!(table.rows.len(), 1); let link_cell = &table.rows[0][0]; assert_eq!(link_cell.len(), 1); - assert_eq!(link_cell[0].text, "Zap"); + assert_eq!(link_cell[0].text, "Zaplex"); assert!(matches!( &link_cell[0].styles.hyperlink, Some(Hyperlink::Url(url)) if url == "https://warp.dev" diff --git a/crates/node_runtime/src/lib.rs b/crates/node_runtime/src/lib.rs index 2070a398dc..d2292f9712 100644 --- a/crates/node_runtime/src/lib.rs +++ b/crates/node_runtime/src/lib.rs @@ -1,4 +1,4 @@ -//! Node.js and npm runtime management for Zap. +//! Node.js and npm runtime management for Zaplex. //! //! This module provides functionality to install and manage Node.js/npm, //! supporting multiple platforms (macOS, Linux, Windows) and architectures @@ -157,7 +157,7 @@ pub fn npm_binary_path() -> Result { /// This function will: /// 1. Check if a valid Node.js installation already exists /// 2. Download the appropriate Node.js distribution for the current platform -/// 3. Extract it to the Zap data directory +/// 3. Extract it to the Zaplex data directory /// /// # Returns /// Returns the path to the Node.js installation directory on success. @@ -392,7 +392,7 @@ where /// Finds a working Node.js binary, preferring our custom installation over system node. /// /// This function checks: -/// 1. First, our custom Node.js installation in the Zap data directory +/// 1. First, our custom Node.js installation in the Zaplex data directory /// 2. Falls back to system Node.js if custom isn't available /// /// # Arguments @@ -453,7 +453,7 @@ pub async fn detect_system_node(path_env_var: impl AsRef) -> Result<()> { // (set via `.env("PATH", ...)`) is used for executable search. // `CreateProcessW` uses the parent process's PATH, not the child's // `lpEnvironment` PATH, so running `node` directly would find node.exe - // via Zap's inherited env rather than the captured interactive PATH. + // via Zaplex's inherited env rather than the captured interactive PATH. #[cfg(windows)] let output = Command::new("cmd.exe") .args(["/c", "node", "--version"]) diff --git a/crates/onboarding/examples/callout.rs b/crates/onboarding/examples/callout.rs index 3f7355ab65..b62e8e8633 100644 --- a/crates/onboarding/examples/callout.rs +++ b/crates/onboarding/examples/callout.rs @@ -81,7 +81,7 @@ impl View for RootView { let callout = self.callout.render( appearance, CalloutParams { - title: "Meet your Zap input".into(), + title: "Meet your Zaplex input".into(), text: "Your terminal input can detect natural language as well as commands.".into(), step: StepStatus::new(1, 2), right_button: CalloutButton { diff --git a/crates/onboarding/src/agent_onboarding_view.rs b/crates/onboarding/src/agent_onboarding_view.rs index e6172af644..d1ac733913 100644 --- a/crates/onboarding/src/agent_onboarding_view.rs +++ b/crates/onboarding/src/agent_onboarding_view.rs @@ -201,7 +201,7 @@ impl AgentOnboardingView { ctx.focus_self(); // Preload customize-slide images so they're ready when the user reaches that slide. - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { Self::preload_onboarding_images(ctx); } diff --git a/crates/onboarding/src/callout/model.rs b/crates/onboarding/src/callout/model.rs index aa9a72a5f5..259d341fe3 100644 --- a/crates/onboarding/src/callout/model.rs +++ b/crates/onboarding/src/callout/model.rs @@ -68,7 +68,7 @@ pub(super) enum AgentModalityCalloutState { MeetTerminalInput, /// Step 2: "Natural language support" with checkbox NaturalLanguageSupport, - /// Step 3: "Introducing Zap's new agent experience" (Agent intention only) + /// Step 3: "Introducing Zaplex's new agent experience" (Agent intention only) IntroducingAgentExperience, /// Step 4: "Updated agent input" (Agent intention only) UpdatedAgentInput, diff --git a/crates/onboarding/src/callout/view.rs b/crates/onboarding/src/callout/view.rs index 2d00aaa400..bc6fbc241c 100644 --- a/crates/onboarding/src/callout/view.rs +++ b/crates/onboarding/src/callout/view.rs @@ -58,7 +58,7 @@ fn get_universal_input_callout_options( ) -> Option { match state { UniversalInputCalloutState::MeetInput => Some(CalloutOptions { - title: localized_static("onboarding-callout-meet-input-title", "Meet the Zap input"), + title: localized_static("onboarding-callout-meet-input-title", "Meet the Zaplex input"), text: format!( "{} {} {}", localized( @@ -203,7 +203,7 @@ fn get_agent_modality_callout_options( "{} {}.", localized( "onboarding-callout-nl-support-text-prefix", - "Natural language input is off by default. If enabled, you can type requests in plain English and Zap will autodetect queries for the agent. You can always override them using" + "Natural language input is off by default. If enabled, you can type requests in plain English and Zaplex will autodetect queries for the agent. You can always override them using" ), keybindings.toggle_input_mode, ), @@ -231,7 +231,7 @@ fn get_agent_modality_callout_options( AgentModalityCalloutState::IntroducingAgentExperience => Some(CalloutOptions { title: localized_static( "onboarding-callout-new-agent-title", - "Introducing Zap's new agent experience", + "Introducing Zaplex's new agent experience", ), text: localized( "onboarding-callout-new-agent-text", diff --git a/crates/onboarding/src/lib.rs b/crates/onboarding/src/lib.rs index f67608db83..1e30a0839c 100644 --- a/crates/onboarding/src/lib.rs +++ b/crates/onboarding/src/lib.rs @@ -30,7 +30,7 @@ pub use localization::set_localizer; /// Shared by the intention slide's agent card checklist and the login slide's /// skip-login confirmation dialog so the two always stay in sync. pub const AI_FEATURES: &[&str] = &[ - "Zap agents", + "Zaplex agents", "Oz local agents platform", "Next command predictions", "Prompt suggestions", @@ -38,11 +38,11 @@ pub const AI_FEATURES: &[&str] = &[ "Agents over SSH", ]; -/// User-facing names of the Zap Drive features enabled when the terminal -/// intention is selected with Zap Drive turned on. Shared by the login slide's +/// User-facing names of the Zaplex Drive features enabled when the terminal +/// intention is selected with Zaplex Drive turned on. Shared by the login slide's /// skip-login confirmation dialog so the list stays in sync with any future /// surfaces that need it. -pub const WARP_DRIVE_FEATURES: &[&str] = &["Zap Drive", "Session Sharing"]; +pub const ZAPLEX_DRIVE_FEATURES: &[&str] = &["Zaplex Drive", "Session Sharing"]; pub mod components; mod visuals; diff --git a/crates/onboarding/src/model.rs b/crates/onboarding/src/model.rs index 416526bb14..de6dadd893 100644 --- a/crates/onboarding/src/model.rs +++ b/crates/onboarding/src/model.rs @@ -77,10 +77,10 @@ impl SelectedSettings { !agent_settings.disable_oz } SelectedSettings::Terminal { .. } => { - // With old onboarding (no ZapNewSettingsModes), Terminal + // With old onboarding (no ZaplexNewSettingsModes), Terminal // intent still leaves AI enabled; with new onboarding, // Terminal intent explicitly disables AI. - !FeatureFlag::ZapNewSettingsModes.is_enabled() + !FeatureFlag::ZaplexNewSettingsModes.is_enabled() } } } @@ -158,7 +158,7 @@ impl OnboardingStateModel { pub(crate) fn settings(&self) -> SelectedSettings { use warp_core::features::FeatureFlag; - let ui_customization = if FeatureFlag::ZapNewSettingsModes.is_enabled() { + let ui_customization = if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { Some(self.ui_customization.clone()) } else { None @@ -467,7 +467,7 @@ impl OnboardingStateModel { // If the user is past the agent slide, don't change the agent model from underneath them. // When the new settings modes flag is on, ThemePicker comes after the agent slides // so it must also be guarded. - let is_past_agent_slide = if FeatureFlag::ZapNewSettingsModes.is_enabled() { + let is_past_agent_slide = if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { matches!( self.step, OnboardingStep::ThirdParty | OnboardingStep::ThemePicker @@ -575,7 +575,7 @@ impl OnboardingStateModel { pub(crate) fn back(&mut self, ctx: &mut ModelContext) { use warp_core::features::FeatureFlag; - let theme_picker_last = FeatureFlag::ZapNewSettingsModes.is_enabled(); + let theme_picker_last = FeatureFlag::ZaplexNewSettingsModes.is_enabled(); let prev = if theme_picker_last { match self.step { @@ -610,7 +610,7 @@ impl OnboardingStateModel { pub(crate) fn next(&mut self, ctx: &mut ModelContext) { use warp_core::features::FeatureFlag; - let theme_picker_last = FeatureFlag::ZapNewSettingsModes.is_enabled(); + let theme_picker_last = FeatureFlag::ZaplexNewSettingsModes.is_enabled(); let is_last_step = if theme_picker_last { matches!(self.step, OnboardingStep::ThemePicker) diff --git a/crates/onboarding/src/slides/agent_slide.rs b/crates/onboarding/src/slides/agent_slide.rs index 9eeb2f1dc7..085614fa24 100644 --- a/crates/onboarding/src/slides/agent_slide.rs +++ b/crates/onboarding/src/slides/agent_slide.rs @@ -235,7 +235,7 @@ impl AgentSlide { .ui_builder() .paragraph(localized( "onboarding-agent-title", - "Customize your Zap Agent", + "Customize your Zaplex Agent", )) .with_style(UiComponentStyles { font_size: Some(36.), @@ -309,7 +309,7 @@ impl AgentSlide { .with_cross_axis_alignment(CrossAxisAlignment::Start) .with_child(upper_sections); - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let disable_oz_section = self.render_disable_oz_section(appearance, settings); col = col.with_child( Container::new(disable_oz_section) @@ -860,7 +860,7 @@ impl AgentSlide { .finish(); let label = Text::new( - localized("onboarding-agent-disable-warp-agent", "Disable Zap Agent"), + localized("onboarding-agent-disable-warp-agent", "Disable Zaplex Agent"), appearance.ui_font_family(), 14.0, ) @@ -911,7 +911,7 @@ impl AgentSlide { ); let step_index = 2; - let step_count = if warp_core::features::FeatureFlag::ZapNewSettingsModes.is_enabled() + let step_count = if warp_core::features::FeatureFlag::ZaplexNewSettingsModes.is_enabled() { 5 } else { @@ -929,7 +929,7 @@ impl AgentSlide { fn render_visual(&self, appearance: &Appearance, app: &AppContext) -> Box { let theme = appearance.theme(); - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let use_vertical = self .onboarding_state .as_ref(app) diff --git a/crates/onboarding/src/slides/customize_slide.rs b/crates/onboarding/src/slides/customize_slide.rs index 7d9cce75eb..e5e9e5497d 100644 --- a/crates/onboarding/src/slides/customize_slide.rs +++ b/crates/onboarding/src/slides/customize_slide.rs @@ -36,7 +36,7 @@ pub enum ToolsPanelSubSetting { ConversationHistory, ProjectExplorer, GlobalSearch, - ZapDrive, + ZaplexDrive, } #[derive(Debug, Clone)] @@ -147,7 +147,7 @@ impl CustomizeUISlide { .ui_builder() .paragraph(localized( "onboarding-customize-title", - "Customize your Zap", + "Customize your Zaplex", )) .with_style(UiComponentStyles { font_size: Some(36.), @@ -326,18 +326,18 @@ impl CustomizeUISlide { }); chips.push(ChipSpec { - label: localized("onboarding-customize-warp-drive", "Zap Drive"), + label: localized("onboarding-customize-warp-drive", "Zaplex Drive"), is_enabled: ui.show_warp_drive, mouse_state: self.chip_warp_drive_mouse.clone(), on_click: Box::new(|ctx, _, _| { ctx.dispatch_typed_action(CustomizeSlideAction::ToggleToolsSubSetting { - setting: ToolsPanelSubSetting::ZapDrive, + setting: ToolsPanelSubSetting::ZaplexDrive, }); }), on_hover: Some(Box::new(|is_hovered, ctx, _, _| { if is_hovered { ctx.dispatch_typed_action(CustomizeSlideAction::HoverToolsChip { - setting: ToolsPanelSubSetting::ZapDrive, + setting: ToolsPanelSubSetting::ZaplexDrive, }); } })), @@ -500,7 +500,7 @@ impl CustomizeUISlide { ]; /// Returns the image path for the current visual state. - /// When `ZapNewSettingsModes` is enabled, assets depend on the tab layout setting. + /// When `ZaplexNewSettingsModes` is enabled, assets depend on the tab layout setting. fn visual_image_path( selected_setting: Option, hovered_chip: Option, @@ -565,8 +565,8 @@ impl CustomizeUISlide { (ToolsPanelSubSetting::ProjectExplorer, false) => "async/png/onboarding/agent_intention/customize_fileexplorer_horizontal.png", (ToolsPanelSubSetting::GlobalSearch, true) => "async/png/onboarding/agent_intention/customize_filesearch_vertical.png", (ToolsPanelSubSetting::GlobalSearch, false) => "async/png/onboarding/agent_intention/customize_filesearch_horizontal.png", - (ToolsPanelSubSetting::ZapDrive, true) => "async/png/onboarding/agent_intention/customize_warpdrive_vertical.png", - (ToolsPanelSubSetting::ZapDrive, false) => "async/png/onboarding/agent_intention/customize_warpdrive_horizontal.png", + (ToolsPanelSubSetting::ZaplexDrive, true) => "async/png/onboarding/agent_intention/customize_warpdrive_vertical.png", + (ToolsPanelSubSetting::ZaplexDrive, false) => "async/png/onboarding/agent_intention/customize_warpdrive_horizontal.png", } } else { // Terminal: no conversation chip; ConversationHistory falls through to file explorer. @@ -575,8 +575,8 @@ impl CustomizeUISlide { (ToolsPanelSubSetting::ConversationHistory | ToolsPanelSubSetting::ProjectExplorer, false) => "async/png/onboarding/terminal_intention/terminal_customize_fileexplorer_horizontal.png", (ToolsPanelSubSetting::GlobalSearch, true) => "async/png/onboarding/terminal_intention/terminal_customize_filesearch_vertical.png", (ToolsPanelSubSetting::GlobalSearch, false) => "async/png/onboarding/terminal_intention/terminal_customize_filesearch_horizontal.png", - (ToolsPanelSubSetting::ZapDrive, true) => "async/png/onboarding/terminal_intention/terminal_customize_warpdrive_vertical.png", - (ToolsPanelSubSetting::ZapDrive, false) => "async/png/onboarding/terminal_intention/terminal_customize_warpdrive_horizontal.png", + (ToolsPanelSubSetting::ZaplexDrive, true) => "async/png/onboarding/terminal_intention/terminal_customize_warpdrive_vertical.png", + (ToolsPanelSubSetting::ZaplexDrive, false) => "async/png/onboarding/terminal_intention/terminal_customize_warpdrive_horizontal.png", } } } @@ -606,7 +606,7 @@ impl CustomizeUISlide { ) -> Box { let theme = appearance.theme(); - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let path = Self::visual_image_path(self.selected_setting, self.hovered_chip, intention, ui); let fg_layout = match self.selected_setting { @@ -805,7 +805,7 @@ impl TypedActionView for CustomizeUISlide { let current = model.ui_customization().show_global_search; model.set_show_global_search(!current, ctx); } - ToolsPanelSubSetting::ZapDrive => { + ToolsPanelSubSetting::ZaplexDrive => { let current = model.ui_customization().show_warp_drive; model.set_show_warp_drive(!current, ctx); } diff --git a/crates/onboarding/src/slides/intention_slide.rs b/crates/onboarding/src/slides/intention_slide.rs index 91fe88771d..0f34d914c5 100644 --- a/crates/onboarding/src/slides/intention_slide.rs +++ b/crates/onboarding/src/slides/intention_slide.rs @@ -81,7 +81,7 @@ impl IntentionSlide { let title = appearance .ui_builder() - .paragraph(localized("onboarding-intention-title", "Welcome to Zap")) + .paragraph(localized("onboarding-intention-title", "Welcome to Zaplex")) .with_style(UiComponentStyles { font_size: Some(36.), font_weight: Some(Weight::Medium), @@ -256,7 +256,7 @@ impl IntentionSlide { let checklist = { let items = [ - localized("onboarding-ai-feature-warp-agents", "Zap agents"), + localized("onboarding-ai-feature-warp-agents", "Zaplex agents"), localized( "onboarding-ai-feature-oz-cloud-agents-platform", "Oz local agents platform", @@ -431,7 +431,7 @@ impl IntentionSlide { }, ); - let new_settings_modes = FeatureFlag::ZapNewSettingsModes.is_enabled(); + let new_settings_modes = FeatureFlag::ZaplexNewSettingsModes.is_enabled(); let next_text = if !new_settings_modes && selected_index == 1 { localized("common-get-warping", "Get Zapping") } else { @@ -481,7 +481,7 @@ impl IntentionSlide { fn render_visual(&self, appearance: &Appearance, selected_index: usize) -> Box { let theme = appearance.theme(); - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let path = if selected_index == 1 { Self::VISUAL_IMAGE_PATHS[1] } else { @@ -554,7 +554,7 @@ impl IntentionSlide { fn next(&mut self, ctx: &mut ViewContext) { self.onboarding_state.update(ctx, |model, ctx| { - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { // Always advance to Customize slide; both intentions continue the flow. model.next(ctx); } else { diff --git a/crates/onboarding/src/slides/intro_slide.rs b/crates/onboarding/src/slides/intro_slide.rs index a8c85a8c43..cdda72e704 100644 --- a/crates/onboarding/src/slides/intro_slide.rs +++ b/crates/onboarding/src/slides/intro_slide.rs @@ -83,7 +83,7 @@ impl IntroSlide { let base_color: ColorU = internal_colors::fg_overlay_4(theme).into(); let shimmer_color: ColorU = theme.foreground().into(); let title = ShimmeringTextElement::new( - localized("onboarding-intention-title", "Welcome to Zap"), + localized("onboarding-intention-title", "Welcome to Zaplex"), appearance.ui_font_family(), 32., base_color, diff --git a/crates/onboarding/src/slides/project_slide.rs b/crates/onboarding/src/slides/project_slide.rs index 5aa8527220..2b4466f874 100644 --- a/crates/onboarding/src/slides/project_slide.rs +++ b/crates/onboarding/src/slides/project_slide.rs @@ -137,7 +137,7 @@ impl ProjectSlide { .ui_builder() .paragraph(localized( "onboarding-project-subtitle", - "Set up a project to optimize it for coding in Zap.", + "Set up a project to optimize it for coding in Zaplex.", )) .with_style(UiComponentStyles { font_size: Some(20.), @@ -298,7 +298,7 @@ impl ProjectSlide { ); let theme_picker_last = - warp_core::features::FeatureFlag::ZapNewSettingsModes.is_enabled(); + warp_core::features::FeatureFlag::ZaplexNewSettingsModes.is_enabled(); let (label, keystroke, action) = match settings { ProjectOnboardingSettings::Project { .. } => ( @@ -496,7 +496,7 @@ impl ProjectSlide { } self.onboarding_state.update(ctx, |model, ctx| { - if warp_core::features::FeatureFlag::ZapNewSettingsModes.is_enabled() { + if warp_core::features::FeatureFlag::ZaplexNewSettingsModes.is_enabled() { model.next(ctx); } else { model.complete(ctx); @@ -507,7 +507,7 @@ impl ProjectSlide { fn skip(&mut self, ctx: &mut ViewContext) { self.onboarding_state.update(ctx, |model, ctx| { model.set_project_selected_local_folder(None, ctx); - if warp_core::features::FeatureFlag::ZapNewSettingsModes.is_enabled() { + if warp_core::features::FeatureFlag::ZaplexNewSettingsModes.is_enabled() { model.next(ctx); } else { model.complete(ctx); diff --git a/crates/onboarding/src/slides/theme_picker_slide.rs b/crates/onboarding/src/slides/theme_picker_slide.rs index 3d660187a8..ffb981efae 100644 --- a/crates/onboarding/src/slides/theme_picker_slide.rs +++ b/crates/onboarding/src/slides/theme_picker_slide.rs @@ -141,7 +141,7 @@ impl ThemePickerSlide { let mut content = vec![self.render_header_text(appearance), theme_options_section]; - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { content.push(self.render_sync_with_os_section(appearance)); } @@ -151,7 +151,7 @@ impl ThemePickerSlide { let state = self.onboarding_state.as_ref(app); let is_terminal = matches!(state.intention(), OnboardingIntention::Terminal); let warp_drive_enabled = state.ui_customization().show_warp_drive; - if is_terminal && !warp_drive_enabled && FeatureFlag::ZapNewSettingsModes.is_enabled() + if is_terminal && !warp_drive_enabled && FeatureFlag::ZaplexNewSettingsModes.is_enabled() { content.push(self.render_disclaimer_section(appearance)); } @@ -248,7 +248,7 @@ impl ThemePickerSlide { }, ); - let theme_picker_last = FeatureFlag::ZapNewSettingsModes.is_enabled(); + let theme_picker_last = FeatureFlag::ZaplexNewSettingsModes.is_enabled(); let next_label = if theme_picker_last { localized("common-get-warping", "Get Warping") } else { @@ -463,7 +463,7 @@ impl ThemePickerSlide { appearance: &Appearance, app: &AppContext, ) -> Box { - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { let path = self.theme_visual_path(app); layout::onboarding_right_panel_with_bg(path, layout::FOREGROUND_LAYOUT_DEFAULT) } else { @@ -549,7 +549,7 @@ impl ThemePickerSlide { ui_builder .span(localized( "auth-local-privacy-note", - "Zap stores onboarding choices locally on this device.", + "Zaplex stores onboarding choices locally on this device.", )) .with_style(disclaimer_styles) .build() @@ -585,7 +585,7 @@ impl ThemePickerSlide { fn next(&mut self, ctx: &mut ViewContext) { self.onboarding_state.update(ctx, |model, ctx| { - if FeatureFlag::ZapNewSettingsModes.is_enabled() { + if FeatureFlag::ZaplexNewSettingsModes.is_enabled() { model.complete(ctx); } else { model.next(ctx); diff --git a/crates/persistence/migrations/2026-06-27-000000_add_session_resilience/down.sql b/crates/persistence/migrations/2026-06-27-000000_add_session_resilience/down.sql new file mode 100644 index 0000000000..f6017a3e1e --- /dev/null +++ b/crates/persistence/migrations/2026-06-27-000000_add_session_resilience/down.sql @@ -0,0 +1,7 @@ +-- Drop the column directly. Unlike the older `ssh_servers` rollbacks (which +-- rebuilt the table for pre-3.35 SQLite compatibility), this is safe and +-- preferred here: the app bundles SQLite >= 3.35 (libsqlite3-sys), which +-- supports ALTER TABLE ... DROP COLUMN, and the live `ssh_servers` schema has +-- since gained a modified auth_type CHECK and a credential_id foreign key — so +-- a hand-replicated rebuild would be more error-prone than a direct drop. +ALTER TABLE ssh_servers DROP COLUMN session_resilience; diff --git a/crates/persistence/migrations/2026-06-27-000000_add_session_resilience/up.sql b/crates/persistence/migrations/2026-06-27-000000_add_session_resilience/up.sql new file mode 100644 index 0000000000..46f1df10dc --- /dev/null +++ b/crates/persistence/migrations/2026-06-27-000000_add_session_resilience/up.sql @@ -0,0 +1,6 @@ +-- Per-host opt-in for the native persistent remote-session layer. +-- 'off' (the default) preserves today's behavior: SSH runs as a local PTY +-- executing `ssh host`. 'persist_only' / 'persist_plus_mosh' make the session +-- daemon-hosted (server-side persistence + replay/reattach; the latter also +-- selects the mosh-grade UDP transport, Phase B3). +ALTER TABLE ssh_servers ADD COLUMN session_resilience TEXT NOT NULL DEFAULT 'off'; diff --git a/crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/down.sql b/crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/down.sql new file mode 100644 index 0000000000..4316eebf6f --- /dev/null +++ b/crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/down.sql @@ -0,0 +1,3 @@ +-- Direct drop; the app bundles SQLite >= 3.35 (libsqlite3-sys) which supports +-- ALTER TABLE ... DROP COLUMN. +ALTER TABLE ssh_servers DROP COLUMN ring_ceiling_mb; diff --git a/crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/up.sql b/crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/up.sql new file mode 100644 index 0000000000..e0bc9f3c83 --- /dev/null +++ b/crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/up.sql @@ -0,0 +1,5 @@ +-- Per-host scrollback/replay buffer ceiling for a daemon session, in MiB. +-- 0 (the default) means "use the daemon's built-in default ceiling". Only +-- meaningful when session_resilience is enabled; it sizes the daemon-side +-- OutputRing that backs replay/reattach. +ALTER TABLE ssh_servers ADD COLUMN ring_ceiling_mb INTEGER NOT NULL DEFAULT 0; diff --git a/crates/persistence/src/model.rs b/crates/persistence/src/model.rs index 4191fa71f4..af29c98f94 100644 --- a/crates/persistence/src/model.rs +++ b/crates/persistence/src/model.rs @@ -716,7 +716,7 @@ pub struct Block { pub host: Option, pub is_background: bool, pub rprompt: Option, - /// JSON-serialized representation of the Zap prompt snapshot (Context Chips). Note that this + /// JSON-serialized representation of the Zaplex prompt snapshot (Context Chips). Note that this /// is different from PS1 and RPROMPT1 pub prompt_snapshot: Option, pub block_id: String, @@ -1476,6 +1476,8 @@ pub struct SshServerRow { pub notes: Option, pub last_connected_at: Option, pub credential_id: Option, + pub session_resilience: String, + pub ring_ceiling_mb: i32, } #[derive(Insertable, AsChangeset, Clone, Debug)] @@ -1490,6 +1492,8 @@ pub struct NewSshServer<'a> { pub startup_command: Option<&'a str>, pub notes: Option<&'a str>, pub credential_id: Option<&'a str>, + pub session_resilience: &'a str, + pub ring_ceiling_mb: i32, } // --- Sync Meta --------------------------------------------------------- diff --git a/crates/persistence/src/schema.rs b/crates/persistence/src/schema.rs index ea1131c11e..9368e452f8 100644 --- a/crates/persistence/src/schema.rs +++ b/crates/persistence/src/schema.rs @@ -391,6 +391,8 @@ diesel::table! { notes -> Nullable, last_connected_at -> Nullable, credential_id -> Nullable, + session_resilience -> Text, + ring_ceiling_mb -> Integer, } } diff --git a/crates/remote_server/proto/remote_server.proto b/crates/remote_server/proto/remote_server.proto index 942ac97517..1b5210d98e 100644 --- a/crates/remote_server/proto/remote_server.proto +++ b/crates/remote_server/proto/remote_server.proto @@ -596,6 +596,9 @@ message OpenSession { map env = 3; // Initial window size. SessionSize size = 4; + // Per-host scrollback/replay buffer ceiling in bytes for this session's + // OutputRing. Absent or 0 means the daemon uses its built-in default. + optional uint64 ring_ceiling_bytes = 5; } // Daemon → client: OpenSession succeeded, returns the newly assigned diff --git a/crates/remote_server/src/client/mod.rs b/crates/remote_server/src/client/mod.rs index f9300a0ad3..ee8d815baa 100644 --- a/crates/remote_server/src/client/mod.rs +++ b/crates/remote_server/src/client/mod.rs @@ -19,6 +19,8 @@ use crate::proto::{ ResolveConflictResponse, ResolvePath, ResolvePathResponse, RunCommandRequest, RunCommandResponse, SaveBuffer, SaveBufferResponse, ServerMessage, SessionBootstrapped, TextEdit, WriteFile, WriteFileChunk, WriteFileChunkResponse, + AttachSession, DetachSession, ListSessions, OpenSession, ResizeSession, SessionAttached, + SessionInput, SessionList, SessionOpened, SessionSize, }; use crate::protocol::{self, ProtocolError, RequestId}; @@ -79,6 +81,18 @@ pub enum ClientEvent { expected_client_version: u64, edits: Vec, }, + /// Live PTY output for a daemon-hosted session (push). `seq` is the byte + /// offset of the first byte in this chunk (monotonic), for replay alignment. + SessionOutput { + session_id: String, + seq: u64, + bytes: Vec, + }, + /// A daemon-hosted session's shell exited (push). + SessionExited { + session_id: String, + exit_code: Option, + }, /// A server message could not be decoded and had no parseable request_id. MessageDecodingError, } @@ -94,7 +108,7 @@ pub enum ClientEvent { /// This type does **not** own the child subprocess whose stdio backs it. /// For transports that spawn a subprocess (e.g. SSH), the caller is /// responsible for holding the `Child` for the lifetime of the session -/// so that `kill_on_drop` fires when teardown occurs. In Zap this is +/// so that `kill_on_drop` fires when teardown occurs. In Zaplex this is /// the `RemoteServerManager`, which stores the child in /// `RemoteSessionState` alongside the `Arc`. That /// way the child's lifetime is gated by the manager's session map @@ -127,7 +141,7 @@ impl RemoteServerClient { /// The caller retains ownership of the `Child` itself. Typically the /// caller spawns the `Command` with `kill_on_drop(true)` and stashes /// the returned `Child` somewhere whose lifetime matches the - /// session's (in Zap, on the `RemoteServerManager`'s + /// session's (in Zaplex, on the `RemoteServerManager`'s /// `RemoteSessionState`). Dropping the `Child` there triggers /// SIGKILL on the subprocess, regardless of how many /// `Arc` clones are still alive. @@ -375,7 +389,7 @@ impl RemoteServerClient { } } - /// Zap: List direct children of a directory on the remote host. + /// Zaplex: List direct children of a directory on the remote host. /// /// Used by terminal file link detection to precisely verify remote path form /// (local sessions use `fs::metadata` for this; remote files are not on the @@ -542,7 +556,7 @@ impl RemoteServerClient { /// Sends a buffer edit notification to the remote host. /// - /// Zap: Unlike other fire-and-forget notifications, buffer edit delivery failures must + /// Zaplex: Unlike other fire-and-forget notifications, buffer edit delivery failures must /// be reported. If we silently drop when `outbound_tx` is closed (connection dead), the /// local buffer continues to advance while the daemon doesn't receive the edit, causing /// invisible desynchronization. Return `Err` on failure to let the caller handle it. @@ -622,6 +636,146 @@ impl RemoteServerClient { } } + // ── Native session host (Stage 2 client side) ──────────────────────── + + /// Opens a new daemon-hosted PTY session and awaits its assigned id. + pub async fn open_session( + &self, + cwd: Option, + shell: Option, + env: std::collections::HashMap, + rows: u32, + cols: u32, + ring_ceiling_bytes: Option, + ) -> Result { + let request_id = RequestId::new(); + let msg = ClientMessage { + request_id: request_id.to_string(), + message: Some(client_message::Message::OpenSession(OpenSession { + cwd, + shell, + env, + size: Some(SessionSize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }), + ring_ceiling_bytes, + })), + }; + let response = self.send_request(request_id, msg).await?; + match response.message { + Some(server_message::Message::SessionOpened(resp)) => Ok(resp), + other => { + log::error!("Unexpected response variant for OpenSession: {other:?}"); + Err(ClientError::UnexpectedResponse) + } + } + } + + /// Attaches to an existing daemon-hosted session and awaits the attach + /// response (which carries the replay from `last_seq`; 0 = full available + /// replay). + pub async fn attach_session( + &self, + session_id: String, + last_seq: u64, + ) -> Result { + let request_id = RequestId::new(); + let msg = ClientMessage { + request_id: request_id.to_string(), + message: Some(client_message::Message::AttachSession(AttachSession { + session_id, + last_seq, + })), + }; + let response = self.send_request(request_id, msg).await?; + match response.message { + Some(server_message::Message::SessionAttached(resp)) => Ok(resp), + other => { + log::error!("Unexpected response variant for AttachSession: {other:?}"); + Err(ClientError::UnexpectedResponse) + } + } + } + + /// Lists the daemon's live sessions (Stage 4: multi-session UI / adopt). + pub async fn list_sessions(&self) -> Result { + let request_id = RequestId::new(); + let msg = ClientMessage { + request_id: request_id.to_string(), + message: Some(client_message::Message::ListSessions(ListSessions {})), + }; + let response = self.send_request(request_id, msg).await?; + match response.message { + Some(server_message::Message::SessionList(resp)) => Ok(resp), + other => { + log::error!("Unexpected response variant for ListSessions: {other:?}"); + Err(ClientError::UnexpectedResponse) + } + } + } + + /// Sends keyboard/mouse input bytes to a session's PTY (notification). + pub fn send_session_input( + &self, + session_id: String, + bytes: Vec, + ) -> Result<(), ClientError> { + let msg = ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::SessionInput(SessionInput { + session_id, + bytes, + })), + }; + self.outbound_tx.try_send(msg).map_err(|e| { + log::error!("Failed to enqueue session input: {e}"); + ClientError::Disconnected + }) + } + + /// Resizes a session's PTY (notification). + pub fn send_resize_session( + &self, + session_id: String, + rows: u32, + cols: u32, + ) -> Result<(), ClientError> { + let msg = ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::ResizeSession(ResizeSession { + session_id, + size: Some(SessionSize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }), + })), + }; + self.outbound_tx.try_send(msg).map_err(|e| { + log::error!("Failed to enqueue session resize: {e}"); + ClientError::Disconnected + }) + } + + /// Detaches from a session without terminating it; the daemon keeps it + /// alive for a later re-attach (notification). + pub fn send_detach_session(&self, session_id: String) -> Result<(), ClientError> { + let msg = ClientMessage { + request_id: String::new(), + message: Some(client_message::Message::DetachSession(DetachSession { + session_id, + })), + }; + self.outbound_tx.try_send(msg).map_err(|e| { + log::error!("Failed to enqueue session detach: {e}"); + ClientError::Disconnected + }) + } + /// Converts a server push message (empty request_id) into a domain event. fn push_message_to_event(msg: ServerMessage) -> Option { match msg.message? { @@ -639,6 +793,15 @@ impl RemoteServerClient { expected_client_version: push.expected_client_version, edits: push.edits, }), + server_message::Message::SessionOutput(push) => Some(ClientEvent::SessionOutput { + session_id: push.session_id, + seq: push.seq, + bytes: push.bytes, + }), + server_message::Message::SessionExited(push) => Some(ClientEvent::SessionExited { + session_id: push.session_id, + exit_code: push.exit_code, + }), other => { log::warn!("Unhandled push message variant: {other:?}"); None diff --git a/crates/remote_server/src/client_tests.rs b/crates/remote_server/src/client_tests.rs index 5ce766133d..e2004b5442 100644 --- a/crates/remote_server/src/client_tests.rs +++ b/crates/remote_server/src/client_tests.rs @@ -5,7 +5,8 @@ use crate::proto::{ client_message, read_file_chunk_response, resolve_path_response, run_command_response, server_message, write_file_chunk_response, ClientMessage, ErrorCode, FileSystemEntryKind, InitializeResponse, ReadFileChunkResponse, ReadFileChunkSuccess, ResolvePathResponse, - ResolvePathSuccess, RunCommandResponse, RunCommandSuccess, ServerMessage, + ResolvePathSuccess, RunCommandResponse, RunCommandSuccess, ServerMessage, SessionAttached, + SessionExited, SessionInfo, SessionList, SessionOpened, SessionOutput, SessionSize, WriteFileChunkResponse, WriteFileChunkSuccess, }; use crate::protocol; @@ -405,3 +406,275 @@ async fn server_returns_error_for_malformed_message_with_parseable_id() { other => panic!("expected ErrorResponse, got: {other:?}"), } } + +// ---- Native daemon session protocol (Stage 2) ----------------------------- +// +// Headless round-trip coverage for the client half of the daemon-hosted session +// protocol: OpenSession/AttachSession requests, SessionOutput/SessionExited +// server pushes surfacing as ClientEvents, and the fire-and-forget +// input/resize/detach frames. The server PTY-spawn half is covered separately +// by `session_pty_tests` in app/src/terminal/local_tty/unix.rs. + +#[tokio::test] +async fn open_session_round_trip() { + let (client, _disconnect_rx, _executor) = setup_mock_client(|msg| { + match &msg.message { + Some(client_message::Message::OpenSession(open)) => { + let size = open.size.as_ref().expect("OpenSession carries size"); + assert_eq!(size.rows, 30); + assert_eq!(size.cols, 100); + assert_eq!(open.cwd.as_deref(), Some("/home/me")); + assert_eq!(open.shell.as_deref(), Some("/bin/zsh")); + assert_eq!(open.env.get("FOO").map(String::as_str), Some("bar")); + assert_eq!(open.ring_ceiling_bytes, Some(8 * 1024 * 1024)); + } + other => panic!("expected OpenSession, got {other:?}"), + } + server_message::Message::SessionOpened(SessionOpened { + session_id: "sess-1".to_string(), + }) + }); + + let mut env = std::collections::HashMap::new(); + env.insert("FOO".to_string(), "bar".to_string()); + let resp = client + .open_session( + Some("/home/me".to_string()), + Some("/bin/zsh".to_string()), + env, + 30, + 100, + Some(8 * 1024 * 1024), + ) + .await + .unwrap(); + assert_eq!(resp.session_id, "sess-1"); +} + +#[tokio::test] +async fn attach_session_round_trip() { + let (client, _disconnect_rx, _executor) = setup_mock_client(|msg| { + match &msg.message { + Some(client_message::Message::AttachSession(att)) => { + assert_eq!(att.session_id, "sess-1"); + assert_eq!(att.last_seq, 42); + } + other => panic!("expected AttachSession, got {other:?}"), + } + server_message::Message::SessionAttached(SessionAttached { + session_id: "sess-1".to_string(), + size: Some(SessionSize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }), + base_seq: 42, + replay: b"replayed".to_vec(), + }) + }); + + let resp = client.attach_session("sess-1".to_string(), 42).await.unwrap(); + assert_eq!(resp.base_seq, 42); + assert_eq!(resp.replay, b"replayed"); +} + +#[tokio::test] +async fn session_output_push_surfaces_as_event() { + let (client_stream, server_stream) = tokio::io::duplex(4096); + let (server_read, server_write) = tokio::io::split(server_stream); + let (client_read, client_write) = tokio::io::split(client_stream); + let executor = executor::Background::default(); + let (_client, event_rx) = + RemoteServerClient::new(client_read.compat(), client_write.compat_write(), &executor); + + tokio::spawn(async move { + let _server_read = server_read; // keep the duplex open for the reader task + let mut writer = server_write.compat_write(); + protocol::write_server_message( + &mut writer, + &ServerMessage { + request_id: String::new(), // empty => push + message: Some(server_message::Message::SessionOutput(SessionOutput { + session_id: "sess-1".to_string(), + seq: 7, + bytes: b"hello pty".to_vec(), + })), + }, + ) + .await + .unwrap(); + std::future::pending::<()>().await; + }); + + match event_rx.recv().await.unwrap() { + ClientEvent::SessionOutput { + session_id, + seq, + bytes, + } => { + assert_eq!(session_id, "sess-1"); + assert_eq!(seq, 7); + assert_eq!(bytes, b"hello pty"); + } + other => panic!("expected SessionOutput, got {other:?}"), + } +} + +#[tokio::test] +async fn session_exited_push_surfaces_as_event() { + let (client_stream, server_stream) = tokio::io::duplex(4096); + let (server_read, server_write) = tokio::io::split(server_stream); + let (client_read, client_write) = tokio::io::split(client_stream); + let executor = executor::Background::default(); + let (_client, event_rx) = + RemoteServerClient::new(client_read.compat(), client_write.compat_write(), &executor); + + tokio::spawn(async move { + let _server_read = server_read; + let mut writer = server_write.compat_write(); + protocol::write_server_message( + &mut writer, + &ServerMessage { + request_id: String::new(), + message: Some(server_message::Message::SessionExited(SessionExited { + session_id: "sess-1".to_string(), + exit_code: Some(0), + })), + }, + ) + .await + .unwrap(); + std::future::pending::<()>().await; + }); + + match event_rx.recv().await.unwrap() { + ClientEvent::SessionExited { + session_id, + exit_code, + } => { + assert_eq!(session_id, "sess-1"); + assert_eq!(exit_code, Some(0)); + } + other => panic!("expected SessionExited, got {other:?}"), + } +} + +/// Reads a single fire-and-forget client frame from the server side of a duplex. +async fn read_one_client_frame( + client: RemoteServerClient, + send: impl FnOnce(&RemoteServerClient), + server_read: tokio::io::ReadHalf, +) -> ClientMessage { + send(&client); + let mut reader = server_read.compat(); + protocol::read_client_message(&mut reader).await.unwrap() +} + +#[tokio::test] +async fn send_session_input_sends_frame() { + let (client_stream, server_stream) = tokio::io::duplex(4096); + let (server_read, _server_write) = tokio::io::split(server_stream); + let (client_read, client_write) = tokio::io::split(client_stream); + let executor = executor::Background::default(); + let (client, _event_rx) = + RemoteServerClient::new(client_read.compat(), client_write.compat_write(), &executor); + + let msg = read_one_client_frame( + client, + |c| c.send_session_input("sess-1".to_string(), b"abc".to_vec()).unwrap(), + server_read, + ) + .await; + match msg.message { + Some(client_message::Message::SessionInput(si)) => { + assert_eq!(si.session_id, "sess-1"); + assert_eq!(si.bytes, b"abc"); + } + other => panic!("expected SessionInput, got {other:?}"), + } +} + +#[tokio::test] +async fn send_resize_session_sends_frame() { + let (client_stream, server_stream) = tokio::io::duplex(4096); + let (server_read, _server_write) = tokio::io::split(server_stream); + let (client_read, client_write) = tokio::io::split(client_stream); + let executor = executor::Background::default(); + let (client, _event_rx) = + RemoteServerClient::new(client_read.compat(), client_write.compat_write(), &executor); + + let msg = read_one_client_frame( + client, + |c| c.send_resize_session("sess-1".to_string(), 50, 120).unwrap(), + server_read, + ) + .await; + match msg.message { + Some(client_message::Message::ResizeSession(rs)) => { + assert_eq!(rs.session_id, "sess-1"); + let size = rs.size.expect("resize carries size"); + assert_eq!(size.rows, 50); + assert_eq!(size.cols, 120); + } + other => panic!("expected ResizeSession, got {other:?}"), + } +} + +#[tokio::test] +async fn send_detach_session_sends_frame() { + let (client_stream, server_stream) = tokio::io::duplex(4096); + let (server_read, _server_write) = tokio::io::split(server_stream); + let (client_read, client_write) = tokio::io::split(client_stream); + let executor = executor::Background::default(); + let (client, _event_rx) = + RemoteServerClient::new(client_read.compat(), client_write.compat_write(), &executor); + + let msg = read_one_client_frame( + client, + |c| c.send_detach_session("sess-1".to_string()).unwrap(), + server_read, + ) + .await; + match msg.message { + Some(client_message::Message::DetachSession(d)) => { + assert_eq!(d.session_id, "sess-1"); + } + other => panic!("expected DetachSession, got {other:?}"), + } +} + +#[tokio::test] +async fn list_sessions_round_trip() { + let (client, _disconnect_rx, _executor) = setup_mock_client(|msg| { + match &msg.message { + Some(client_message::Message::ListSessions(_)) => {} + other => panic!("expected ListSessions, got {other:?}"), + } + server_message::Message::SessionList(SessionList { + sessions: vec![ + SessionInfo { + session_id: "s1".to_string(), + title: "work".to_string(), + cwd: "/home/me/work".to_string(), + alive: true, + last_attached_epoch_millis: 123, + }, + SessionInfo { + session_id: "s2".to_string(), + title: "logs".to_string(), + cwd: "/var/log".to_string(), + alive: true, + last_attached_epoch_millis: 456, + }, + ], + }) + }); + + let resp = client.list_sessions().await.unwrap(); + assert_eq!(resp.sessions.len(), 2); + assert_eq!(resp.sessions[0].session_id, "s1"); + assert_eq!(resp.sessions[0].cwd, "/home/me/work"); + assert!(resp.sessions[0].alive); + assert_eq!(resp.sessions[1].last_attached_epoch_millis, 456); +} diff --git a/crates/remote_server/src/install_remote_server.sh b/crates/remote_server/src/install_remote_server.sh index 45ad7ec9a1..2136111340 100644 --- a/crates/remote_server/src/install_remote_server.sh +++ b/crates/remote_server/src/install_remote_server.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash -# 在远端主机安装 Zap CLI 二进制,用于 remote-server-proxy。 +# Install Zaplex CLI binary on remote host for remote-server-proxy. # -# setup.rs 会在运行时替换这些占位符: -# {download_base_url} - 例如 https://github.com/zerx-lab/warp/releases/latest/download -# {install_dir} - 例如 ~/.zap/remote-server -# {binary_name} - 例如 zap-oss -# {version_suffix} - 例如 -v0.2026...,没有 release tag 时为空 -# {staging_tarball_path} - SCP fallback 预上传 tarball 路径,常规下载路径为空 +# setup.rs will replace these placeholders at runtime: +# {download_base_url} - e.g. https://github.com/zerx-lab/warp/releases/latest/download +# {install_dir} - e.g. ~/.zap/remote-server +# {binary_name} - e.g. zaplex +# {version_suffix} - e.g. -v0.2026..., empty when no release tag +# {staging_tarball_path} - SCP fallback pre-uploaded tarball path, empty for normal download path set -e arch=$(uname -m) @@ -30,9 +30,9 @@ esac mkdir -p "$install_dir" tmpdir=$(mktemp -d "$install_dir/.install.XXXXXX") -# 尽力清理 staging 目录。这里失败不能覆盖真正的安装结果: -# trap 触发时二进制要么已经移动到最终路径,要么脚本已经因为 -# 其他原因失败,后者的错误更值得暴露给调用方。 +# Best-effort cleanup of staging directory. Failure here should not override actual installation result: +# when trap triggers, the binary is either already moved to final path, or +# the script failed for other reasons, the latter error is more important to expose to caller. cleanup() { rm -rf "$tmpdir" 2>/dev/null || true } @@ -60,7 +60,7 @@ tar -xzf "$tmpdir/zap.tar.gz" -C "$tmpdir" bin="$tmpdir/{binary_name}" if [ ! -f "$bin" ]; then - bin=$(find "$tmpdir" -type f \( -name 'zap-oss' -o -name 'warp-oss' -o -name 'oz*' \) ! -path "$tmpdir/resources/*" ! -name '*.tar.gz' | head -n1) + bin=$(find "$tmpdir" -type f \( -name 'zaplex' -o -name 'zap-oss' -o -name 'warp-oss' -o -name 'oz*' \) ! -path "$tmpdir/resources/*" ! -name '*.tar.gz' | head -n1) fi if [ -z "$bin" ]; then echo "no binary found in tarball" >&2; exit 1; fi chmod +x "$bin" diff --git a/crates/remote_server/src/manager.rs b/crates/remote_server/src/manager.rs index 9a8e76d0ef..f5074e985f 100644 --- a/crates/remote_server/src/manager.rs +++ b/crates/remote_server/src/manager.rs @@ -147,7 +147,7 @@ fn version_is_compatible(client: Option<&str>, server: &str) -> bool { /// Whether to enforce strict tag matching for the remote `server_version`. /// -/// For [`Channel::Oss`](Zap), locally-built source has no `GIT_RELEASE_TAG`, +/// For [`Channel::Oss`](Zaplex), locally-built source has no `GIT_RELEASE_TAG`, /// but SSH Extension may install a latest-release remote-server. /// Enforcing strict version check would cause a delete/reinstall/mismatch loop /// when client is `None` and server has a non-empty tag. Release builds avoid stale binaries @@ -372,6 +372,26 @@ pub enum RemoteServerManagerEvent { }, /// A server message could not be decoded (no parseable request_id). ServerMessageDecodingError { session_id: SessionId }, + + // --- Native session host (Stage 2) --- + /// Live PTY output for a daemon-hosted session. `session_id` is the + /// manager/connection session; `pty_session_id` is the daemon's PTY session + /// id (from OpenSession/AttachSession). `seq` is the byte offset of the + /// first byte in `bytes` (monotonic), for replay alignment. + SessionOutput { + session_id: SessionId, + host_id: HostId, + pty_session_id: String, + seq: u64, + bytes: Vec, + }, + /// A daemon-hosted session's shell exited. + SessionExited { + session_id: SessionId, + host_id: HostId, + pty_session_id: String, + exit_code: Option, + }, } impl RemoteServerManagerEvent { @@ -390,7 +410,9 @@ impl RemoteServerManagerEvent { | RemoteServerManagerEvent::BinaryCheckComplete { session_id, .. } | RemoteServerManagerEvent::BinaryInstallComplete { session_id, .. } | RemoteServerManagerEvent::ClientRequestFailed { session_id, .. } - | RemoteServerManagerEvent::ServerMessageDecodingError { session_id } => { + | RemoteServerManagerEvent::ServerMessageDecodingError { session_id } + | RemoteServerManagerEvent::SessionOutput { session_id, .. } + | RemoteServerManagerEvent::SessionExited { session_id, .. } => { Some(*session_id) } RemoteServerManagerEvent::HostConnected { .. } @@ -445,6 +467,12 @@ pub struct RemoteServerManager { /// Detected remote platform per session, populated during the binary check /// phase via `detect_platform()`. Used for telemetry. session_platforms: HashMap, + /// Sessions backed by a persistent daemon (native remote-session layer). For + /// these, a transport-child exit on a network blip does NOT mean the remote + /// session died — the daemon keeps it running — so `mark_session_disconnected` + /// must still attempt reconnect (the generic path skips reconnect on child + /// exit). Populated by `mark_session_persistent`; cleared on teardown. + persistent_session_ids: HashSet, } impl Entity for RemoteServerManager { @@ -463,9 +491,17 @@ impl RemoteServerManager { session_bootstrap_info: HashMap::new(), auth_context: None, session_platforms: HashMap::new(), + persistent_session_ids: HashSet::new(), } } + /// Marks `session_id` as backed by a persistent daemon, so a transport-child + /// exit is treated as a recoverable transport drop (reconnect) rather than a + /// terminal disconnect. Call before `connect_session` on the daemon path. + pub fn mark_session_persistent(&mut self, session_id: SessionId) { + self.persistent_session_ids.insert(session_id); + } + /// Returns a connected client for the given host by picking an arbitrary /// session from the host's session pool. pub fn client_for_host(&self, host_id: &HostId) -> Option<&Arc> { @@ -850,7 +886,7 @@ impl RemoteServerManager { // tag than the client expects, the binary on disk is stale. Remove it so // the next reconnect (or explicit reconnect by the user) will reinstall. // - // Under `Channel::Oss`(Zap), we temporarily reuse the official release binary; + // Under `Channel::Oss`(Zaplex), we temporarily reuse the official release binary; // the client has no `GIT_RELEASE_TAG`, so it will never match the server. // Therefore, strict version checking is skipped. See [`should_enforce_remote_version_check`] for details. let client_version = ChannelState::app_version(); @@ -919,10 +955,54 @@ impl RemoteServerManager { /// was in, because the entry is being removed from `sessions` /// outright. Unlike `SessionDisconnected`, this one never fires for /// spontaneous drops -- only for explicit teardown. - pub fn deregister_session(&mut self, session_id: SessionId, ctx: &mut ModelContext) { + /// Surface a *pre-connect* setup failure for `session_id` to subscribers, + /// for callers that fail before they could even invoke `connect_session` + /// (e.g. bringing up the ControlMaster or installing the remote-server + /// binary failed). Without this, a tab created for the session would hang + /// forever waiting for output. Emits the same `SessionConnectionFailed` + /// event the normal connect path emits on handshake failure, so consumers + /// (e.g. the daemon terminal) render the error instead of a blank view. + pub fn fail_session( + &mut self, + session_id: SessionId, + phase: RemoteServerInitPhase, + error: String, + ctx: &mut ModelContext, + ) { + log::error!("Pre-connect setup failed for session {session_id:?} at {phase:?}: {error}"); + ctx.emit(RemoteServerManagerEvent::SetupStateChanged { + session_id, + state: RemoteServerSetupState::Failed { + error: error.clone(), + }, + }); + ctx.emit(RemoteServerManagerEvent::SessionConnectionFailed { + session_id, + phase, + error, + }); + } + + /// Tears down the manager's tracking for `session_id` and drops its transport + /// (killing the per-session `ssh … remote-server-proxy` child via + /// `kill_on_drop`). + /// + /// `stop_control_master` controls whether the local SSH ControlMaster is also + /// forced to exit. Pass `true` for per-session masters (the legacy ssh-wrapper + /// path) so the foreground ssh can exit cleanly. Pass `false` for daemon + /// sessions: their ControlMaster is **per-host and shared** across daemon tabs, + /// so stopping it would drop sibling tabs' transports — leave it for reuse / + /// its own ControlPersist timeout. + pub fn deregister_session( + &mut self, + session_id: SessionId, + stop_control_master: bool, + ctx: &mut ModelContext, + ) { self.last_navigated_path.remove(&session_id); self.session_bootstrap_info.remove(&session_id); self.session_platforms.remove(&session_id); + self.persistent_session_ids.remove(&session_id); // Remove the session entry. Dropping the `RemoteSessionState` // here drops the transport's owned `Child` (if any), which @@ -965,13 +1045,16 @@ impl RemoteServerManager { // Force the local SSH ControlMaster to exit after teardown. // Spawned detached because the ssh subcommand may take a moment // to complete and we don't want to block the main thread on it. + // Skipped for daemon sessions (shared per-host master — see the doc above). #[cfg(not(target_family = "wasm"))] - if let Some(control_path) = control_path { - ctx.background_executor() - .spawn(async move { - crate::ssh::stop_control_master(&control_path).await; - }) - .detach(); + if stop_control_master { + if let Some(control_path) = control_path { + ctx.background_executor() + .spawn(async move { + crate::ssh::stop_control_master(&control_path).await; + }) + .detach(); + } } } @@ -1257,6 +1340,30 @@ impl RemoteServerManager { edits, }); } + ClientEvent::SessionOutput { + session_id: pty_session_id, + seq, + bytes, + } => { + ctx.emit(RemoteServerManagerEvent::SessionOutput { + session_id, + host_id, + pty_session_id, + seq, + bytes, + }); + } + ClientEvent::SessionExited { + session_id: pty_session_id, + exit_code, + } => { + ctx.emit(RemoteServerManagerEvent::SessionExited { + session_id, + host_id, + pty_session_id, + exit_code, + }); + } ClientEvent::MessageDecodingError => { ctx.emit(RemoteServerManagerEvent::ServerMessageDecodingError { session_id }); } @@ -1403,7 +1510,16 @@ impl RemoteServerManager { // terminal pane to hang 2-4 seconds before terminating; so skip auto-reconnect and go straight to Disconnected. // Only `exit_status.is_none()` (child still running but reader got EOF) — true transient network hiccup — // retains the reconnect logic. - let child_already_exited = exit_status.is_some(); + // + // EXCEPTION — persistent daemon sessions: the transport child is the + // per-connection ssh/proxy slave, which exits on a network blip while + // the remote daemon (a separate process) keeps the PTY session alive. + // So for these we must NOT skip reconnect on child exit; the + // self-healing ControlMaster + `SessionReconnected`/reattach path then + // restores the live view. (If the daemon truly died, reconnect just + // fails after the bounded retries.) + let is_persistent = self.persistent_session_ids.contains(&session_id); + let child_already_exited = exit_status.is_some() && !is_persistent; let Some(auth_context) = self.auth_context.clone().filter(|_| !child_already_exited) else { if child_already_exited { @@ -1418,6 +1534,7 @@ impl RemoteServerManager { but no auth context is available for reconnect" ); } + self.persistent_session_ids.remove(&session_id); self.sessions .insert(session_id, RemoteSessionState::Disconnected); self.remove_from_host_index(&host_id, session_id); @@ -1622,6 +1739,9 @@ impl RemoteServerManager { "Reconnect exhausted for session {session_id:?} after {} attempt(s)", params.attempt ); + // Terminal state — drop the persistent flag so the id doesn't linger + // in the set (daemon session ids are monotonic and never reused). + self.persistent_session_ids.remove(&session_id); self.sessions .insert(session_id, RemoteSessionState::Disconnected); ctx.emit(RemoteServerManagerEvent::SessionDisconnected { diff --git a/crates/remote_server/src/manager_tests.rs b/crates/remote_server/src/manager_tests.rs index d39e7665a4..2033a7dbce 100644 --- a/crates/remote_server/src/manager_tests.rs +++ b/crates/remote_server/src/manager_tests.rs @@ -43,7 +43,7 @@ fn version_compat_client_tagged_server_untagged() { #[test] fn version_compat_client_untagged_server_tagged() { - // **Critical scenario**: Zap client has no tag (cargo build), + // **Critical scenario**: Zaplex client has no tag (cargo build), // server is a release from official CDN (with tag). Original helper // would judge as incompatible, triggering `remove_remote_server_binary` → infinite loop. // This test only records that `version_is_compatible` behavior itself does not change; @@ -57,7 +57,7 @@ fn version_compat_client_untagged_server_tagged() { #[test] fn enforce_version_check_skipped_on_oss() { - // When Zap temporarily reuses official release binaries, client and server versions + // When Zaplex temporarily reuses official release binaries, client and server versions // are always inconsistent; strict validation must be skipped. assert!(!should_enforce_remote_version_check(Channel::Oss)); } diff --git a/crates/remote_server/src/preinstall_check.sh b/crates/remote_server/src/preinstall_check.sh index 06561ae100..a97aaa5551 100644 --- a/crates/remote_server/src/preinstall_check.sh +++ b/crates/remote_server/src/preinstall_check.sh @@ -1,31 +1,31 @@ #!/usr/bin/env bash -# Zap remote-server 二进制的预安装检查。 +# Zaplex remote-server binary pre-installation check. # -# stdout 输出结构化 key=value 摘要。退出码 0 表示探测完成; -# 非 0 表示探测过程失败,客户端会按 `status=unknown` 处理并 fail open。 +# stdout outputs structured key=value summary. Exit code 0 means detection complete; +# non-zero means detection process failed, client will treat as `status=unknown` and fail open. # -# 重要:Zap Linux remote-server 现在由 zap_release.yml 以 -# `x86_64-unknown-linux-musl` 目标静态链接构建(static-musl)。产物不依赖 -# 宿主的动态 libc,因此可以在任意 Linux x86_64 主机上运行 —— 包括旧 glibc -# 发行版(CentOS 7 = 2.17、Amazon Linux 2 = 2.26、Ubuntu 20.04 / Debian 11 -# = 2.31)以及 musl 发行版(Alpine 等)。 +# IMPORTANT: Zaplex Linux remote-server is now statically linked via zap_release.yml with +# `x86_64-unknown-linux-musl` target (static-musl). Artifacts don't depend on +# host's dynamic libc, can run on any Linux x86_64 host — including old glibc +# distributions (CentOS 7 = 2.17, Amazon Linux 2 = 2.26, Ubuntu 20.04 / Debian 11 +# = 2.31) and musl distributions (Alpine etc). # -# 既然二进制是静态的,libc 探测不再用于「门禁」,只作为遥测信息保留。 +# Since the binary is static, libc detection is no longer used as a "gating check", only kept as telemetry. set -u -# 历史字段:保留 required_glibc 以兼容旧客户端的解析逻辑。 -# 静态 musl 二进制实际上没有 glibc 下限,此处仅为向后兼容输出, -# 不再参与下面的 status 判定。 +# Legacy field: keep required_glibc for backward compatibility with old client parsing logic. +# Static musl binary actually has no glibc lower bound, output here is only for backward compatibility, +# no longer participates in the status determination below. required_glibc="2.17" echo "required_glibc=${required_glibc}" -# 1. 识别 libc family,并在 glibc 场景下识别版本(纯遥测,不影响 status)。 +# 1. Identify libc family, and identify version in glibc scenario (pure telemetry, doesn't affect status). libc_family="unknown" libc_version="" if version=$(getconf GNU_LIBC_VERSION 2>/dev/null); then - # 输出形如: "glibc 2.35" + # Output like: "glibc 2.35" libc_family="glibc" libc_version="${version##* }" elif ldd_out=$(ldd --version 2>&1 | head -n1); then @@ -45,12 +45,12 @@ fi echo "libc_family=${libc_family}" [ -n "$libc_version" ] && echo "libc_version=${libc_version}" -# 2. 判断支持状态。 +# 2. Determine support status. # -# remote-server 是静态 musl 二进制,不链接宿主 libc,所以任何 glibc 版本 -# (含 2.35 以下)以及 musl / uclibc 宿主都能运行。只要成功识别出这是一台 -# Linux x86_64 主机,就报告 `supported`;探测不出任何 libc 线索(连 -# getconf 和 ldd 都没有)时回退 `unknown`,让客户端 fail open 照常尝试安装。 +# remote-server is a static musl binary, doesn't link host libc, so any glibc version +# (including 2.35 and below) and musl / uclibc hosts can run it. As long as successfully +# identify this is a Linux x86_64 host, report `supported`; when unable to detect any libc clues +# (even getconf and ldd fail), fall back to `unknown`, let client fail open and try normal installation. status="unknown" reason="" diff --git a/crates/remote_server/src/setup.rs b/crates/remote_server/src/setup.rs index 63aa693a07..e4e8c3ef7e 100644 --- a/crates/remote_server/src/setup.rs +++ b/crates/remote_server/src/setup.rs @@ -262,14 +262,14 @@ pub fn parse_uname_output(output: &str) -> Result { /// - dev: `~/.warp-dev/remote-server` /// - local: `~/.warp-local/remote-server` /// - integration: `~/.warp-dev/remote-server` -/// - warp-oss: `~/.zap/remote-server` +/// - warp-oss: `~/.zaplex/remote-server` pub fn remote_server_dir() -> String { let warp_dir = match ChannelState::channel() { Channel::Stable => ".warp", Channel::Preview => ".warp-preview", Channel::Dev | Channel::Integration => ".warp-dev", Channel::Local => ".warp-local", - Channel::Oss => ".zap", + Channel::Oss => ".zaplex", }; format!("~/{warp_dir}/remote-server") } @@ -312,7 +312,7 @@ pub fn binary_name() -> &'static str { /// Returns the full path to the remote binary corresponding to the current channel and client version. /// /// Local builds preserve an unversioned suffix path so that `script/deploy_remote_server` can -/// overwrite the same development slot. Zap release builds with `GIT_RELEASE_TAG` use a versioned +/// overwrite the same development slot. Zaplex release builds with `GIT_RELEASE_TAG` use a versioned /// suffix, allowing new versions to naturally trigger reinstalls. Source-built local builds without /// a release tag still use an unversioned path. pub fn remote_server_binary() -> String { @@ -360,7 +360,7 @@ pub fn install_script(staging_tarball_path: Option<&str>) -> String { .replace("{staging_tarball_path}", staging_tarball_path.unwrap_or("")) } -/// Constructs the base URL for downloading Zap CLI release assets. +/// Constructs the base URL for downloading Zaplex CLI release assets. fn download_url() -> String { let release_path = match ChannelState::app_version() { Some(tag) => format!("download/{tag}"), @@ -379,7 +379,7 @@ fn version_suffix() -> String { } } -/// Returns the Zap CLI tarball URL for the specified remote platform. +/// Returns the Zaplex CLI tarball URL for the specified remote platform. pub fn download_tarball_url(platform: &RemotePlatform) -> String { format!( "{}/zap-{}-{}.tar.gz", @@ -389,7 +389,7 @@ pub fn download_tarball_url(platform: &RemotePlatform) -> String { ) } -/// Zap fork: In development mode (DEBUG source builds without release tags), +/// Zaplex fork: In development mode (DEBUG source builds without release tags), /// the SSH transport no longer downloads stale releases from GitHub. Instead, it cross-compiles /// the current `warp` binary locally and uploads it. The constants below describe the cross-compilation /// artifacts, coordinated with `script/deploy_remote_server` (same profile / features / target) @@ -412,10 +412,10 @@ pub const DEV_REMOTE_FEATURES: &str = "release_bundle,crash_reporting,standalone /// the standard used in `remote_server_binary()` / `download_url()` for "no release tag". /// Release builds always return `false`, with unchanged behavior. /// -/// Explicit override: set `WARP_REMOTE_SERVER_FROM_LOCAL=1` to force the local cross-compilation path +/// Explicit override: set `ZAPLEX_REMOTE_SERVER_FROM_LOCAL=1` to force the local cross-compilation path /// (`0` or unset means disabled). Used for temporary local remote-server debugging in release builds. pub fn is_dev_source_build() -> bool { - if let Some(raw) = std::env::var_os("WARP_REMOTE_SERVER_FROM_LOCAL") { + if let Some(raw) = std::env::var_os("ZAPLEX_REMOTE_SERVER_FROM_LOCAL") { let lossy = raw.to_string_lossy(); let trimmed = lossy.trim(); let disabled = diff --git a/crates/remote_server/src/setup_tests.rs b/crates/remote_server/src/setup_tests.rs index d9006e8acc..834f35d031 100644 --- a/crates/remote_server/src/setup_tests.rs +++ b/crates/remote_server/src/setup_tests.rs @@ -197,12 +197,12 @@ fn parse_preinstall_missing_status_falls_open() { #[test] fn oss_remote_server_dir_uses_zap_namespace() { - assert_eq!(remote_server_dir(), "~/.zap/remote-server"); + assert_eq!(remote_server_dir(), "~/.zaplex/remote-server"); } #[test] fn oss_binary_name_matches_zap_cli() { - assert_eq!(binary_name(), "warp-oss"); + assert_eq!(binary_name(), "zaplex"); } #[test] @@ -224,10 +224,10 @@ fn oss_download_tarball_url_uses_github_release_asset() { #[test] fn install_script_uses_zap_asset_and_staging_placeholder() { - let script = install_script(Some("~/.zap/remote-server/zap-upload.tar.gz")); + let script = install_script(Some("~/.zaplex/remote-server/zap-upload.tar.gz")); assert!(script - .contains("staging_tarball_path=\"~/.zap/remote-server/zap-upload.tar.gz\"")); + .contains("staging_tarball_path=\"~/.zaplex/remote-server/zap-upload.tar.gz\"")); assert!(script.contains("zap-$os_name-$arch_name.tar.gz")); assert!(!script.contains("app.warp.dev")); assert!(!script.contains("/download/cli")); diff --git a/crates/repo_metadata/src/lib.rs b/crates/repo_metadata/src/lib.rs index b907160dbf..5b61f5e7d6 100644 --- a/crates/repo_metadata/src/lib.rs +++ b/crates/repo_metadata/src/lib.rs @@ -1,4 +1,4 @@ -//! Repository metadata utilities for Zap. +//! Repository metadata utilities for Zaplex. //! //! This crate provides utilities for managing repository metadata, including file trees, //! gitignore processing, and filesystem watching capabilities.s diff --git a/crates/repo_metadata/src/local_model.rs b/crates/repo_metadata/src/local_model.rs index 595b2f746b..c779edb3ad 100644 --- a/crates/repo_metadata/src/local_model.rs +++ b/crates/repo_metadata/src/local_model.rs @@ -2,7 +2,7 @@ //! Repository metadata model singleton. //! //! This module provides a singleton model that manages repository metadata across -//! all repositories tracked by Zap. +//! all repositories tracked by Zaplex. use std::{ collections::HashMap, diff --git a/crates/repo_metadata/src/repository.rs b/crates/repo_metadata/src/repository.rs index 86ab3642b8..429b1db273 100644 --- a/crates/repo_metadata/src/repository.rs +++ b/crates/repo_metadata/src/repository.rs @@ -55,7 +55,7 @@ pub struct StartWatching { pub registration_future: BoxFuture<'static, Result<(), RepoMetadataError>>, } -/// Model for tracking a code repository that Zap is aware of. +/// Model for tracking a code repository that Zaplex is aware of. pub struct Repository { /// The root directory of the repository. root_dir: StandardizedPath, diff --git a/crates/serve-wasm/src/main.rs b/crates/serve-wasm/src/main.rs index e5c964431a..246e1682be 100644 --- a/crates/serve-wasm/src/main.rs +++ b/crates/serve-wasm/src/main.rs @@ -14,7 +14,7 @@ use std::time::Duration; use tower::ServiceBuilder; use tracing::Span; -/// A small webserver to serve the Zap wasm bundle and assets for local development. +/// A small webserver to serve the Zaplex wasm bundle and assets for local development. #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { @@ -38,7 +38,7 @@ async fn main() { let args = Args::parse(); - println!("Serving Zap on http://localhost:{}", args.port); + println!("Serving Zaplex on http://localhost:{}", args.port); serve(make_router(&args.directory), args.port).await } diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 70c700b92d..7156a3c80f 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -301,7 +301,7 @@ pub trait Setting { /// Returns the platforms that this setting is supported on. fn supported_platforms() -> SupportedPlatforms; - /// Returns whether and how this setting is synced to the cloud via Zap Drive. + /// Returns whether and how this setting is synced to the cloud via Zaplex Drive. fn sync_to_cloud() -> SyncToCloud; /// Returns whether this setting is private (not shown in the user-visible settings file). diff --git a/crates/settings/src/macros.rs b/crates/settings/src/macros.rs index 8500aa8482..bc2893cbee 100644 --- a/crates/settings/src/macros.rs +++ b/crates/settings/src/macros.rs @@ -1,5 +1,5 @@ //! This module defines a set of macros to standardize and simplify the process -//! of defining new settings within Zap. +//! of defining new settings within Zaplex. //! //! Settings are defined as enums or structs that implement [`Setting`], and are //! organized into groups in singleton models which contain one or more settings diff --git a/crates/simple_logger/src/manager.rs b/crates/simple_logger/src/manager.rs index 81c3c1785a..7e95c482af 100644 --- a/crates/simple_logger/src/manager.rs +++ b/crates/simple_logger/src/manager.rs @@ -39,7 +39,7 @@ fn log_directory_path(namespace: &str) -> PathBuf { let base_dir = warp_core::paths::secure_state_dir().unwrap_or_else(warp_core::paths::state_dir); if cfg!(windows) { base_dir - .join(warp_core::paths::WARP_LOGS_DIR) + .join(warp_core::paths::ZAPLEX_LOGS_DIR) .join(namespace) } else { base_dir.join(namespace) diff --git a/crates/virtual_fs/src/lib.rs b/crates/virtual_fs/src/lib.rs index 26b1f7969e..144722c208 100644 --- a/crates/virtual_fs/src/lib.rs +++ b/crates/virtual_fs/src/lib.rs @@ -21,7 +21,7 @@ pub struct Dirs { impl Dirs { #[allow(dead_code)] pub fn git_repository_fixture(&self) -> PathBuf { - Zap::fixtures().join("git_repository") + Zaplex::fixtures().join("git_repository") } } @@ -156,9 +156,9 @@ impl VirtualFS { } } -pub struct Zap; +pub struct Zaplex; -impl Zap { +impl Zaplex { #[allow(dead_code)] pub fn executable() -> PathBuf { let mut path = { diff --git a/crates/warp_cli/src/agent.rs b/crates/warp_cli/src/agent.rs index fc18db24d8..c4bb48ddc8 100644 --- a/crates/warp_cli/src/agent.rs +++ b/crates/warp_cli/src/agent.rs @@ -120,7 +120,7 @@ impl HiddenComputerUseArgs { /// The execution harness for an agent run. #[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq, Default)] pub enum Harness { - /// Use Zap's built-in MAA infrastructure (default). + /// Use Zaplex's built-in MAA infrastructure (default). #[default] #[value(name = "oz")] Oz, @@ -237,7 +237,7 @@ pub struct RunAgentArgs { /// Working directory for the agent #[arg(short = 'C', long = "cwd")] pub cwd: Option, - /// Display agent progress in the Zap interface. + /// Display agent progress in the Zaplex interface. #[arg(long = "gui", hide = true)] pub gui: bool, #[command(flatten)] @@ -254,7 +254,7 @@ pub struct RunAgentArgs { /// LEGACY: MCP servers to start before executing the agent, identified by UUID. #[arg(long = "mcp-server", value_name = "UUID", hide = true)] pub mcp_servers: Vec, - // Zap Wave 7-2: `--environment` parameter physically removed with cloud ambient agent subsystem. + // Zaplex Wave 7-2: `--environment` parameter physically removed with cloud ambient agent subsystem. /// Keep the agent's session open after the conversation completes. /// /// This is useful when you want to keep the session alive for follow-up interactions. @@ -285,7 +285,7 @@ pub struct RunAgentArgs { /// Execution harness for the agent run. /// - /// "oz" (default) uses Zap's built-in agent infrastructure. + /// "oz" (default) uses Zaplex's built-in agent infrastructure. /// "claude" delegates to the `claude` CLI. #[arg(long = "harness", value_name = "HARNESS", default_value_t = Harness::Oz, hide = true)] pub harness: Harness, diff --git a/crates/warp_cli/src/completions.rs b/crates/warp_cli/src/completions.rs index 90197dc5d3..e33083a596 100644 --- a/crates/warp_cli/src/completions.rs +++ b/crates/warp_cli/src/completions.rs @@ -5,7 +5,7 @@ use clap_complete::aot::{Shell, generate}; use crate::{Args, binary_name}; use warp_core::channel::ChannelState; -/// Generate shell completions for the Zap CLI and write them to stdout. +/// Generate shell completions for the Zaplex CLI and write them to stdout. pub fn generate_to_stdout(shell: Option) -> anyhow::Result<()> { let shell = match shell.or_else(Shell::from_env) { Some(s) => s, diff --git a/crates/warp_cli/src/config_file.rs b/crates/warp_cli/src/config_file.rs index 93b88b308d..938182376a 100644 --- a/crates/warp_cli/src/config_file.rs +++ b/crates/warp_cli/src/config_file.rs @@ -8,7 +8,7 @@ pub struct ConfigFileArgs { short = 'f', long = "file", value_name = "PATH", - env = "WARP_AGENT_CONFIG_FILE" + env = "ZAPLEX_AGENT_CONFIG_FILE" )] pub file: Option, } diff --git a/crates/warp_cli/src/lib.rs b/crates/warp_cli/src/lib.rs index 2510252a8c..70cc9aeb50 100644 --- a/crates/warp_cli/src/lib.rs +++ b/crates/warp_cli/src/lib.rs @@ -19,7 +19,7 @@ pub mod skill; pub mod agent; pub mod completions; pub mod config_file; -// Zap Wave 7-2: `environment` CLI removed along with cloud ambient agent main subsystem. +// Zaplex Wave 7-2: `environment` CLI removed along with cloud ambient agent main subsystem. pub mod json_filter; pub mod mcp; pub mod model; @@ -30,14 +30,14 @@ pub const OZ_PARENT_RUN_ID_ENV: &str = "OZ_PARENT_RUN_ID"; pub const OZ_CLI_ENV: &str = "OZ_CLI"; pub const OZ_HARNESS_ENV: &str = "OZ_HARNESS"; -/// Options related to the parent process that spawned this Zap instance. +/// Options related to the parent process that spawned this Zaplex instance. #[derive(Debug, Default, Clone, clap::Args)] pub struct ParentOpts { - /// The ID of the Zap process that spawned this one. + /// The ID of the Zaplex process that spawned this one. /// - /// Used by codepaths that attempt to detect when the parent Zap process + /// Used by codepaths that attempt to detect when the parent Zaplex process /// has terminated. Guaranteed to be [`None`] when this is the initial - /// Zap process, but may also be [`None`] for Zap child processes if the + /// Zaplex process, but may also be [`None`] for Zaplex child processes if the /// child process doesn't need to keep track of its parent. #[arg(long = "parent-pid", hide = true)] pub pid: Option, @@ -52,7 +52,7 @@ pub struct ParentOpts { } /// Hidden worker args used to scope remote-server proxy/daemon sockets by -/// Zap identity without exposing credentials. +/// Zaplex identity without exposing credentials. #[derive(Debug, Clone, Default, clap::Args)] pub struct RemoteServerIdentityArgs { /// Non-secret identity partition key for the remote-server daemon. @@ -64,7 +64,7 @@ pub struct RemoteServerIdentityArgs { #[derive(Debug, Default, Clone, clap::Args)] pub struct GlobalOptions { /// API key for server authentication. - #[arg(long = "api-key", global = true, env = "WARP_API_KEY")] + #[arg(long = "api-key", global = true, env = "ZAPLEX_API_KEY")] pub api_key: Option, /// Set the output format. @@ -73,17 +73,17 @@ pub struct GlobalOptions { global = true, value_enum, default_value_t = OutputFormat::Pretty, - env = "WARP_OUTPUT_FORMAT" + env = "ZAPLEX_OUTPUT_FORMAT" )] pub output_format: OutputFormat, } -/// Command-line argument parser for the main Zap binary. This is used across all channels. +/// Command-line argument parser for the main Zaplex binary. This is used across all channels. #[derive(Debug, Default, Parser, Clone)] #[command( name = "oz", display_name = "Oz", - about = r#"Zap local agent CLI + about = r#"Zaplex local agent CLI The Oz CLI is a tool for running and managing local coding agents. Use the CLI to: @@ -107,11 +107,11 @@ pub struct Args { args: AppArgs, } -/// Flags for the Zap application. Additional binaries, like test runners, may use this type +/// Flags for the Zaplex application. Additional binaries, like test runners, may use this type /// along with their own flags, or convert their flags into an `AppArgs` value. #[derive(Debug, Default, clap::Args, Clone)] pub struct AppArgs { - /// True if this instance of Zap was launched at the end of the auto-update process. + /// True if this instance of Zaplex was launched at the end of the auto-update process. #[arg(long = "finish-update", hide = true)] pub finish_update: bool, @@ -120,11 +120,11 @@ pub struct AppArgs { #[arg(long = "crash-recovery-mechanism", value_enum, requires = "ParentOpts")] pub crash_recovery_mechanism: Option, - /// Options related to the parent process that spawned this Zap instance. + /// Options related to the parent process that spawned this Zaplex instance. #[clap(flatten)] pub parent: ParentOpts, - /// URLs to open in Zap. + /// URLs to open in Zaplex. #[arg(hide = true)] pub urls: Vec, } @@ -140,7 +140,7 @@ impl Args { } else { use clap::FromArgMatches as _; - // Zap Wave 7-2: `warp environment` subcommand removed along with cloud ambient agent main subsystem. + // Zaplex Wave 7-2: `warp environment` subcommand removed along with cloud ambient agent main subsystem. // Previously, there was a reverse intercept here: checking and erroring early before clap parsing. // Now that the enum variant is removed, clap will naturally report “unrecognized subcommand”. @@ -188,7 +188,7 @@ impl Args { pub fn clap_command() -> clap::Command { let mut command = ::command(); - // Zap Wave 7-2: `environment` subcommand and `--environment` parameter removed along with cloud ambient agent + // Zaplex Wave 7-2: `environment` subcommand and `--environment` parameter removed along with cloud ambient agent // main subsystem — enum variants already removed from `CliCommand` and `RunAgentArgs`. // Hide the provider subcommand from help text @@ -223,12 +223,12 @@ impl Args { self.command.as_ref() } - /// Args for the main Zap application, if not running a subcommand. + /// Args for the main Zaplex application, if not running a subcommand. pub fn app_args(&self) -> &AppArgs { &self.args } - /// Extract the main Zap application args. + /// Extract the main Zaplex application args. pub fn into_app_args(self) -> AppArgs { self.args } @@ -254,9 +254,9 @@ impl Args { } } -/// Zap may spawn several worker processes - mostly servers that support the main application. +/// Zaplex may spawn several worker processes - mostly servers that support the main application. /// -/// These subcommands run those worker processes, which are bundled into the Zap binary. +/// These subcommands run those worker processes, which are bundled into the Zaplex binary. #[derive(Debug, Clone, Subcommand)] pub enum WorkerCommand { /// Run the terminal server. @@ -310,15 +310,15 @@ pub enum WorkerCommand { }, } -/// CLI-related subcommands. The command-line interface to Zap isn't a full SDK (e.g. with language bindings), -/// but it allows scripting some Zap functionality. +/// CLI-related subcommands. The command-line interface to Zaplex isn't a full SDK (e.g. with language bindings), +/// but it allows scripting some Zaplex functionality. #[derive(Debug, Clone, Subcommand)] pub enum CliCommand { /// Interact with Oz. #[command(subcommand)] Agent(crate::agent::AgentCommand), - // Zap Wave 7-2: `Environment` variant removed along with cloud ambient agent main subsystem. + // Zaplex Wave 7-2: `Environment` variant removed along with cloud ambient agent main subsystem. /// Manage MCP servers. #[command(subcommand)] MCP(crate::mcp::MCPCommand), @@ -335,13 +335,13 @@ pub enum CliCommand { Provider(crate::provider::ProviderCommand), } -/// A subcommand of the main Zap application. This includes all [`WorkerCommand`]s as well as app-specific debugging tools. +/// A subcommand of the main Zaplex application. This includes all [`WorkerCommand`]s as well as app-specific debugging tools. #[derive(Debug, Clone, Subcommand)] pub enum Command { #[clap(flatten)] Worker(WorkerCommand), - /// Commands that make up the Zap CLI. + /// Commands that make up the Zaplex CLI. #[clap(flatten)] CommandLine(Box), @@ -360,7 +360,7 @@ pub enum Command { /// For Powershell, add the following to $PROFILE: /// path\to\warp | Out-String | Invoke-Expression /// - /// If no shell is provided, this defaults to the shell that Zap was run from. + /// If no shell is provided, this defaults to the shell that Zaplex was run from. #[command(verbatim_doc_comment)] Completions { /// Shell to generate completions for. @@ -463,7 +463,7 @@ pub fn dump_debug_info_flag() -> String { format!("--{flag}") } -/// Returns a flag that sets the current process as the parent of a Zap subcommand to spawn. +/// Returns a flag that sets the current process as the parent of a Zaplex subcommand to spawn. pub fn parent_flag() -> String { let command = ::command(); let flag = command diff --git a/crates/warp_cli/src/lib_tests.rs b/crates/warp_cli/src/lib_tests.rs index d69a77634e..3b58f5caca 100644 --- a/crates/warp_cli/src/lib_tests.rs +++ b/crates/warp_cli/src/lib_tests.rs @@ -2,7 +2,7 @@ use super::*; use clap::Parser; use crate::agent::{AgentCommand, Harness}; -// Zap Wave 7-2: `environment` CLI physically deleted with cloud ambient agent main body. +// Zaplex Wave 7-2: `environment` CLI physically deleted with cloud ambient agent main body. #[test] fn agent_run_accepts_model() { @@ -260,7 +260,7 @@ fn run_command_is_removed() { assert!(result.is_err()); } -// Zap Wave 7-2: environment_image_list_parses / environment_create_accepts_description / +// Zaplex Wave 7-2: environment_image_list_parses / environment_create_accepts_description / // environment_create_description_max_length / environment_update_accepts_description / // environment_update_accepts_remove_description physically deleted with cloud ambient agent subsystem. diff --git a/crates/warp_completer/src/completer/engine/path_test.rs b/crates/warp_completer/src/completer/engine/path_test.rs index 1b636a52f6..4a49704e4a 100644 --- a/crates/warp_completer/src/completer/engine/path_test.rs +++ b/crates/warp_completer/src/completer/engine/path_test.rs @@ -25,7 +25,7 @@ fn test_split_path() { let path = TypedPathBuf::from_unix("/Users/warpuser"); let split_path = SplitPath::new( path.to_path(), - "~/Zap.app", + "~/Zaplex.app", Some("/Users/warpuser"), &['/'], ); @@ -35,36 +35,36 @@ fn test_split_path() { SplitPath { directory_absolute_path: path.clone(), directory_relative_path_name: "~/".to_owned(), - file_name: "Zap.app".to_owned() + file_name: "Zaplex.app".to_owned() } ); let split_path = SplitPath::new( path.to_path(), - "Zap.app/Contents", + "Zaplex.app/Contents", Some("/Users/warpuser"), &['/'], ); assert_eq!( split_path, SplitPath { - directory_absolute_path: TypedPathBuf::from("/Users/warpuser/Zap.app/"), - directory_relative_path_name: "Zap.app/".to_owned(), + directory_absolute_path: TypedPathBuf::from("/Users/warpuser/Zaplex.app/"), + directory_relative_path_name: "Zaplex.app/".to_owned(), file_name: "Contents".to_owned() } ); let split_path = SplitPath::new( path.to_path(), - "Zap.app/macOS/bin/warp.o", + "Zaplex.app/macOS/bin/warp.o", Some("/Users/warpuser"), &['/'], ); assert_eq!( split_path, SplitPath { - directory_absolute_path: TypedPathBuf::from("/Users/warpuser/Zap.app/macOS/bin/"), - directory_relative_path_name: "Zap.app/macOS/bin/".to_owned(), + directory_absolute_path: TypedPathBuf::from("/Users/warpuser/Zaplex.app/macOS/bin/"), + directory_relative_path_name: "Zaplex.app/macOS/bin/".to_owned(), file_name: "warp.o".to_owned() } ); diff --git a/crates/warp_completer/src/completer/suggest/mod.rs b/crates/warp_completer/src/completer/suggest/mod.rs index ea3f18af06..e2ede6c439 100644 --- a/crates/warp_completer/src/completer/suggest/mod.rs +++ b/crates/warp_completer/src/completer/suggest/mod.rs @@ -511,7 +511,7 @@ impl Default for CompleterOptions { } } -/// This is the public API for using Zap's completion engine. Note that +/// This is the public API for using Zaplex's completion engine. Note that /// the completion engines could end up performing I/O (e.g. calling generators, /// interacting with the file system, etc.), so you should ensure that you /// are on a background thread when using this API. diff --git a/crates/warp_completer/src/parsers/README.md b/crates/warp_completer/src/parsers/README.md index 0a85fd214b..9d5b5243b9 100644 --- a/crates/warp_completer/src/parsers/README.md +++ b/crates/warp_completer/src/parsers/README.md @@ -190,10 +190,10 @@ LiteRootNode { } ``` -For more involved inputs (say commands separated by `|` and/or having `;`) the lite parser will effectively create the necessary `LitePipeline`s to express it, let's explore what happens if we lite parse the input `warp config-set --extension-path="/path/to/dir" ; echo $WARP_VAR"` (*two pipelines here due to the ; character*), like so: +For more involved inputs (say commands separated by `|` and/or having `;`) the lite parser will effectively create the necessary `LitePipeline`s to express it, let's explore what happens if we lite parse the input `warp config-set --extension-path="/path/to/dir" ; echo $ZAPLEX_VAR"` (*two pipelines here due to the ; character*), like so: ```rust -let input = "warp config-set --extension-path=\"/path/to/dir\" ; echo $WARP_VAR"; +let input = "warp config-set --extension-path=\"/path/to/dir\" ; echo $ZAPLEX_VAR"; let start_offset = 0; let (tokens, _) = lex(input, start_offset); @@ -258,7 +258,7 @@ LiteRootNode { start: 55, end: 64, }, - item: "$WARP_VAR", + item: "$ZAPLEX_VAR", }, ], post_whitespace: None, diff --git a/crates/warp_completer/src/parsers/simple/lexer_test.rs b/crates/warp_completer/src/parsers/simple/lexer_test.rs index d67f625b02..9bab603886 100644 --- a/crates/warp_completer/src/parsers/simple/lexer_test.rs +++ b/crates/warp_completer/src/parsers/simple/lexer_test.rs @@ -165,7 +165,7 @@ fn test_multiple_whitespace() { #[test] fn test_backtick_escape_char() { - let source = r#"& "$HOME\Downloads\Zap` Setup.exe" /SP- /SILENT `t`"#; + let source = r#"& "$HOME\Downloads\Zaplex` Setup.exe" /SP- /SILENT `t`"#; let tokens: Vec<_> = Lexer::new(source, EscapeChar::Backtick, false) .map(|t| (t.item, t.span)) .collect(); @@ -177,7 +177,7 @@ fn test_backtick_escape_char() { (Token::Whitespace(" "), Span::new(1, 2)), (Token::DoubleQuote, Span::new(2, 3)), (Token::Dollar, Span::new(3, 4)), - (Token::Literal(r"HOME\Downloads\Zap"), Span::new(4, 23)), + (Token::Literal(r"HOME\Downloads\Zaplex"), Span::new(4, 23)), (Token::EscapeChar("`"), Span::new(23, 24)), (Token::Whitespace(" "), Span::new(24, 25)), (Token::Literal("Setup.exe"), Span::new(25, 34)), diff --git a/crates/warp_completer/src/signatures/legacy/mod.rs b/crates/warp_completer/src/signatures/legacy/mod.rs index 898b7f59e6..80fcf2f91d 100644 --- a/crates/warp_completer/src/signatures/legacy/mod.rs +++ b/crates/warp_completer/src/signatures/legacy/mod.rs @@ -48,7 +48,7 @@ impl CommandRegistry { registry } - /// Register signatures for Zap CLI commands. + /// Register signatures for Zaplex CLI commands. /// /// Ideally this would be done outside of the `warp_completer` crate, but it's not currently /// possible to configure the shared [`Self::global_instance`]. diff --git a/crates/warp_core/src/channel/mod.rs b/crates/warp_core/src/channel/mod.rs index 08e5b0654b..88331fa473 100644 --- a/crates/warp_core/src/channel/mod.rs +++ b/crates/warp_core/src/channel/mod.rs @@ -18,7 +18,7 @@ pub enum Channel { /// The internal-only HEAD build. Local, - /// The open-source build of Zap. + /// The open-source build of Zaplex. Oss, /// The integration test build. @@ -42,7 +42,7 @@ impl Channel { Channel::Preview => "oz-preview", Channel::Local => "oz-local", Channel::Integration => "oz-integration", - Channel::Oss => "zap-oss", + Channel::Oss => "zaplex", } } } @@ -55,7 +55,7 @@ impl fmt::Display for Channel { Channel::Dev => "dev", Channel::Integration => "integration", Channel::Local => "local", - Channel::Oss => "zap-oss", + Channel::Oss => "zaplex", }) } } diff --git a/crates/warp_core/src/channel/state.rs b/crates/warp_core/src/channel/state.rs index 377e91407f..b98d693114 100644 --- a/crates/warp_core/src/channel/state.rs +++ b/crates/warp_core/src/channel/state.rs @@ -37,7 +37,7 @@ pub struct ChannelState { impl ChannelState { pub fn init() -> Self { let channel = Channel::Oss; - let app_id = AppId::new("dev", "zap", "Zap"); + let app_id = AppId::new("dev", "zaplex", "Zaplex"); Self { channel, additional_features: Default::default(), @@ -89,11 +89,11 @@ impl ChannelState { /// Returns a profile name for isolating user data. This should be used to /// sandbox how user data is stored. /// - /// This is a debugging tool for isolating development instances of Zap, and is not + /// This is a debugging tool for isolating development instances of Zaplex, and is not /// supported in release builds. pub fn data_profile() -> Option { if cfg!(debug_assertions) { - std::env::var("WARP_DATA_PROFILE").ok() + std::env::var("ZAPLEX_DATA_PROFILE").ok() } else { None } @@ -102,7 +102,7 @@ impl ChannelState { /// Returns a value that should be used for namespacing persisted data. /// /// In release builds, this is identical to the app ID; in debug builds, - /// it optionally includes a suffix derived from the `WARP_DATA_PROFILE` + /// it optionally includes a suffix derived from the `ZAPLEX_DATA_PROFILE` /// environment variable. pub fn data_domain() -> String { match Self::data_profile() { @@ -245,12 +245,12 @@ impl ChannelState { // Dummy value--integration tests shouldn't support URL schemes. Channel::Integration => "warpintegration", Channel::Local => "warplocal", - Channel::Oss => "zap", + Channel::Oss => "zaplex", } } } -/// Zap Wave 5-5: `derive_http_origin_from_ws_url` was physically removed together with `rtc_http_url()`. +/// Zaplex Wave 5-5: `derive_http_origin_from_ws_url` was physically removed together with `rtc_http_url()`. #[cfg(all(test, not(feature = "test-util")))] #[path = "state_tests.rs"] diff --git a/crates/warp_core/src/channel/state_tests.rs b/crates/warp_core/src/channel/state_tests.rs index 4938d49064..4dcda53af2 100644 --- a/crates/warp_core/src/channel/state_tests.rs +++ b/crates/warp_core/src/channel/state_tests.rs @@ -1,6 +1,6 @@ use super::ChannelState; -// Zap Wave 5-5: `derive_http_origin_from_ws_url` call + 3 wss/ws path tests +// Zaplex Wave 5-5: `derive_http_origin_from_ws_url` call + 3 wss/ws path tests // to be physically deleted along with `ChannelState::rtc_http_url()`. /// `ChannelState::init()` (the static default for OSS builds) must satisfy diff --git a/crates/warp_core/src/execution_mode.rs b/crates/warp_core/src/execution_mode.rs index 7335a95042..efbf1f858a 100644 --- a/crates/warp_core/src/execution_mode.rs +++ b/crates/warp_core/src/execution_mode.rs @@ -4,12 +4,12 @@ use warpui::{Entity, ModelContext, SingletonEntity}; // Global execution mode, for logic that runs outside the UI framework. static GLOBAL_EXECUTION_MODE: OnceLock = OnceLock::new(); -/// Execution mode that Zap is running under. +/// Execution mode that Zaplex is running under. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ExecutionMode { - /// Zap is running as a normal desktop app. + /// Zaplex is running as a normal desktop app. App, - /// Zap is running as a CLI. + /// Zaplex is running as a CLI. Sdk, } @@ -24,9 +24,9 @@ impl ExecutionMode { } } -/// Model tracking the mode that Zap is running in. +/// Model tracking the mode that Zaplex is running in. /// -/// This gates functionality that's disabled when Zap is running in SDK mode. +/// This gates functionality that's disabled when Zaplex is running in SDK mode. #[derive(Clone, Debug)] pub struct AppExecutionMode { mode: ExecutionMode, @@ -82,7 +82,7 @@ impl AppExecutionMode { /// Whether telemetry should be sent synchronously at shutdown. /// - /// Zap has no telemetry sender, so shutdown must never wait on telemetry work. The method + /// Zaplex has no telemetry sender, so shutdown must never wait on telemetry work. The method /// remains as a compatibility surface for callers that still branch on the old capability. pub fn send_telemetry_at_shutdown(&self) -> bool { false @@ -100,7 +100,7 @@ impl AppExecutionMode { self.mode.client_id() } - /// If true, Zap is running in a sandbox like a Docker container or VM, rather than directly + /// If true, Zaplex is running in a sandbox like a Docker container or VM, rather than directly /// on a user machine. pub fn is_sandboxed(&self) -> bool { self.is_sandboxed diff --git a/crates/warp_core/src/interval_timer.rs b/crates/warp_core/src/interval_timer.rs index 3b94bc0bd2..e9aaaa7b54 100644 --- a/crates/warp_core/src/interval_timer.rs +++ b/crates/warp_core/src/interval_timer.rs @@ -61,14 +61,14 @@ impl IntervalTimer { }) } - /// When the `WARP_STARTUP_TRACE=1` environment variable is set, writes the timing + /// When the `ZAPLEX_STARTUP_TRACE=1` environment variable is set, writes the timing /// table collected by the IntervalTimer (per-step marginal_ms / cumulative_ms / name) /// to stderr as an ASCII table. /// Mainly used for local tuning -- it depends on no telemetry backend and is purely /// local diagnostics. /// Has no side effects and does not modify any state. pub fn print_trace_to_stderr_if_enabled(&self) { - let enabled = std::env::var("WARP_STARTUP_TRACE") + let enabled = std::env::var("ZAPLEX_STARTUP_TRACE") .map(|v| matches!(v.as_str(), "1" | "true" | "TRUE" | "yes")) .unwrap_or(false); if !enabled { @@ -76,7 +76,7 @@ impl IntervalTimer { } let stats = self.compute_stats(); eprintln!(); - eprintln!("=== WARP_STARTUP_TRACE ==="); + eprintln!("=== ZAPLEX_STARTUP_TRACE ==="); eprintln!("{:>8} {:>10} name", "step_ms", "total_ms"); for point in &stats { eprintln!( diff --git a/crates/warp_core/src/paths.rs b/crates/warp_core/src/paths.rs index c3847a52de..c574680529 100644 --- a/crates/warp_core/src/paths.rs +++ b/crates/warp_core/src/paths.rs @@ -28,24 +28,24 @@ use crate::{ /// /// This should be used, for example, as the base directory under which /// repository workflows would be stored (in "./.warp/workflows"). -pub const WARP_CONFIG_DIR: &str = ".warp"; +pub const ZAPLEX_CONFIG_DIR: &str = ".warp"; -/// The name of the folder that stores Zap execution logs and network logs. +/// The name of the folder that stores Zaplex execution logs and network logs. /// This is currently only used on Windows to maintain backwards compatibility. -pub const WARP_LOGS_DIR: &str = "logs"; +pub const ZAPLEX_LOGS_DIR: &str = "logs"; fn base_warp_config_dir_name() -> String { match ChannelState::channel() { // Preview shares the same directory as Stable for backward // compatibility — existing users already have config in `.warp`. - Channel::Stable | Channel::Preview => WARP_CONFIG_DIR.to_owned(), - Channel::Oss => ".zap".to_owned(), - Channel::Dev => format!("{WARP_CONFIG_DIR}-dev"), - Channel::Integration => format!("{WARP_CONFIG_DIR}-integration"), - Channel::Local => format!("{WARP_CONFIG_DIR}-local"), + Channel::Stable | Channel::Preview => ZAPLEX_CONFIG_DIR.to_owned(), + Channel::Oss => ".zaplex".to_owned(), + Channel::Dev => format!("{ZAPLEX_CONFIG_DIR}-dev"), + Channel::Integration => format!("{ZAPLEX_CONFIG_DIR}-integration"), + Channel::Local => format!("{ZAPLEX_CONFIG_DIR}-local"), } } -/// Returns the home-relative Zap config directory name for the current channel and data profile. +/// Returns the home-relative Zaplex config directory name for the current channel and data profile. /// /// This preserves the historical `.warp*` directory shape while still isolating dev, local, /// integration, oss, and optional development profiles. @@ -59,7 +59,7 @@ pub fn warp_home_config_dir_name() -> String { } } -/// Returns the home-relative Zap config directory for the current channel and data profile. +/// Returns the home-relative Zaplex config directory for the current channel and data profile. /// /// Unlike [`data_dir`] and [`config_local_dir`] on non-macOS platforms, this intentionally keeps /// Zap-authored, user-facing config under a `.warp*` directory in the home directory instead of @@ -86,12 +86,12 @@ pub fn warp_home_mcp_config_file_path() -> Option { #[cfg(target_os = "macos")] fn macos_config_dir_name() -> String { match ChannelState::channel() { - Channel::Stable => WARP_CONFIG_DIR.to_owned(), - Channel::Preview => format!("{WARP_CONFIG_DIR}-preview"), - Channel::Oss => ".zap".to_owned(), - Channel::Dev => format!("{WARP_CONFIG_DIR}-dev"), - Channel::Integration => format!("{WARP_CONFIG_DIR}-integration"), - Channel::Local => format!("{WARP_CONFIG_DIR}-local"), + Channel::Stable => ZAPLEX_CONFIG_DIR.to_owned(), + Channel::Preview => format!("{ZAPLEX_CONFIG_DIR}-preview"), + Channel::Oss => ".zaplex".to_owned(), + Channel::Dev => format!("{ZAPLEX_CONFIG_DIR}-dev"), + Channel::Integration => format!("{ZAPLEX_CONFIG_DIR}-integration"), + Channel::Local => format!("{ZAPLEX_CONFIG_DIR}-local"), } } @@ -140,7 +140,7 @@ pub fn base_config_dir() -> PathBuf { /// /// This is the appropriate home for files like our sqlite database, which /// contains durable but non-critical and non-portable data like what windows -/// the user had open and cached state of known Zap Drive objects. +/// the user had open and cached state of known Zaplex Drive objects. pub fn state_dir() -> PathBuf { let Some(project_dirs) = project_dirs() else { return PathBuf::new(); @@ -241,7 +241,7 @@ fn project_dirs_for_app_id( // Adjust the base application name so that we end up with // a directory like "zap" matching our Linux package name. let base_app_name = match app_id.application_name() { - "Zap" => "zap".to_owned(), + "Zaplex" => "zap".to_owned(), other if other.starts_with("Zap") => other.replace("Zap", "zap-"), _ => app_id.application_name().to_owned(), }; @@ -293,13 +293,13 @@ pub fn app_group_container_path() -> Option { LazyLock::force(&CONTAINER_PATH).clone() } -/// Returns the path to resources included in the Zap distribution. +/// Returns the path to resources included in the Zaplex distribution. /// /// Unlike [`warpui::AssetProvider`] assets, which are generally embedded in the binary, these are -/// stored on the filesystem alongside the rest of Zap. +/// stored on the filesystem alongside the rest of Zaplex. /// /// ## macOS -/// The resources directory is `$APP_DIR/Contents/Resources` (e.g. `/Applications/Zap.app/Contents/Resources`). +/// The resources directory is `$APP_DIR/Contents/Resources` (e.g. `/Applications/Zaplex.app/Contents/Resources`). /// /// ## Linux /// The resources directory is `$INSTALL_DIR/resources`, where `$INSTALL_DIR` depends on the @@ -307,7 +307,7 @@ pub fn app_group_container_path() -> Option { /// /// ## Windows /// The resources directory is `$INSTALL_DIR/resources`, where `$INSTALL_DIR` is the directory -/// containing the Zap executable (e.g. `C:\Program Files\WarpDev\resources`). +/// containing the Zaplex executable (e.g. `C:\Program Files\WarpDev\resources`). pub fn bundled_resources_dir() -> Option { cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { diff --git a/crates/warp_core/src/paths_tests.rs b/crates/warp_core/src/paths_tests.rs index e21f11fb75..e3c0cc81cc 100644 --- a/crates/warp_core/src/paths_tests.rs +++ b/crates/warp_core/src/paths_tests.rs @@ -8,11 +8,11 @@ fn test_data_dir_path() { // ChannelState, by default, is configured for Channel::Oss. cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { - assert_eq!(data_dir(), home_dir.join(".zap")); + assert_eq!(data_dir(), home_dir.join(".zaplex")); } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { assert_eq!(data_dir(), home_dir.join(".local/share/zap")); } else if #[cfg(windows)] { - assert_eq!(data_dir(), home_dir.join("AppData\\Roaming\\zap\\Zap\\data")); + assert_eq!(data_dir(), home_dir.join("AppData\\Roaming\\zap\\Zaplex\\data")); } else { unimplemented!("Need to update tests for current platform!"); } @@ -25,11 +25,11 @@ fn test_config_local_dir_path() { // ChannelState, by default, is configured for Channel::Oss. cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { - assert_eq!(config_local_dir(), home_dir.join(".zap")); + assert_eq!(config_local_dir(), home_dir.join(".zaplex")); } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { assert_eq!(config_local_dir(), home_dir.join(".config/zap")); } else if #[cfg(windows)] { - assert_eq!(config_local_dir(), home_dir.join("AppData\\Local\\zap\\Zap\\config")); + assert_eq!(config_local_dir(), home_dir.join("AppData\\Local\\zap\\Zaplex\\config")); } else { unimplemented!("Need to update tests for current platform!"); } @@ -40,8 +40,8 @@ fn test_config_local_dir_path() { fn test_warp_home_config_dir_path() { let home_dir = home_dir().expect("Should be able to compute home directory"); let expected_dir_name = match ChannelState::data_profile() { - Some(data_profile) => format!(".zap-{data_profile}"), - None => ".zap".to_string(), + Some(data_profile) => format!(".zaplex-{data_profile}"), + None => ".zaplex".to_string(), }; assert_eq!( @@ -53,7 +53,7 @@ fn test_warp_home_config_dir_path() { #[test] fn test_warp_home_skills_and_mcp_paths() { let Some(config_dir) = warp_home_config_dir() else { - panic!("Should be able to compute Zap home config directory"); + panic!("Should be able to compute Zaplex home config directory"); }; assert_eq!(warp_home_skills_dir(), Some(config_dir.join("skills"))); @@ -68,11 +68,11 @@ fn test_cache_dir_path() { // ChannelState, by default, is configured for Channel::Oss. cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { - assert_eq!(cache_dir(), home_dir.join("Library/Application Support/dev.zap.Zap")); + assert_eq!(cache_dir(), home_dir.join("Library/Application Support/dev.zaplex.Zaplex")); } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { assert_eq!(cache_dir(), home_dir.join(".cache/zap")); } else if #[cfg(windows)] { - assert_eq!(cache_dir(), home_dir.join("AppData\\Local\\zap\\Zap\\cache")); + assert_eq!(cache_dir(), home_dir.join("AppData\\Local\\zap\\Zaplex\\cache")); } else { unimplemented!("Need to update tests for current platform!"); } @@ -85,11 +85,11 @@ fn test_state_dir_path() { cfg_if::cfg_if! { // ChannelState, by default, is configured for Channel::Oss. if #[cfg(target_os = "macos")] { - assert_eq!(state_dir(), home_dir.join("Library/Application Support/dev.zap.Zap")); + assert_eq!(state_dir(), home_dir.join("Library/Application Support/dev.zaplex.Zaplex")); } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { assert_eq!(state_dir(), home_dir.join(".local/state/zap")); } else if #[cfg(windows)] { - assert_eq!(state_dir(), home_dir.join("AppData\\Local\\zap\\Zap\\data")); + assert_eq!(state_dir(), home_dir.join("AppData\\Local\\zap\\Zaplex\\data")); } else { unimplemented!("Need to update tests for current platform!"); } @@ -98,7 +98,7 @@ fn test_state_dir_path() { #[test] fn test_oss_secure_state_dir_is_disabled() { - // ChannelState defaults to Channel::Oss. Zap should not probe the official Zap App Group, + // ChannelState defaults to Channel::Oss. Zaplex should not probe the official Zaplex App Group, // otherwise macOS will identify it as accessing other app data and show a permissions // dialog on every startup. assert_eq!(secure_state_dir(), None); @@ -109,15 +109,15 @@ fn test_project_path_for_zap_dev_app_id() { // Covers the `starts_with("Zap")` branch in `project_dirs_for_app_id` on Linux, // which maps suffixed application names like `ZapDev` to a dashed lowercase // directory matching the Linux package name (e.g. `zap-dev`). - let project_dirs = project_dirs_for_app_id(AppId::new("dev", "zap", "ZapDev"), None) + let project_dirs = project_dirs_for_app_id(AppId::new("dev", "zaplex", "ZapDev"), None) .expect("should be able to compute project dirs"); cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { - assert_eq!(project_dirs.project_path(), "dev.zap.ZapDev"); + assert_eq!(project_dirs.project_path(), "dev.zaplex.ZapDev"); } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { assert_eq!(project_dirs.project_path(), "zap-dev"); } else if #[cfg(windows)] { - assert_eq!(project_dirs.project_path(), "zap\\ZapDev"); + assert_eq!(project_dirs.project_path(), "zaplex\\ZapDev"); } else { unimplemented!("Need to update tests for current platform!"); } @@ -126,15 +126,15 @@ fn test_project_path_for_zap_dev_app_id() { #[test] fn test_project_path_for_oss_app_id() { - let project_dirs = project_dirs_for_app_id(AppId::new("dev", "zap", "Zap"), None) + let project_dirs = project_dirs_for_app_id(AppId::new("dev", "zaplex", "Zaplex"), None) .expect("should be able to compute project dirs"); cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { - assert_eq!(project_dirs.project_path(), "dev.zap.Zap"); + assert_eq!(project_dirs.project_path(), "dev.zaplex.Zaplex"); } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { assert_eq!(project_dirs.project_path(), "zap"); } else if #[cfg(windows)] { - assert_eq!(project_dirs.project_path(), "zap\\Zap"); + assert_eq!(project_dirs.project_path(), "zaplex\\Zaplex"); } else { unimplemented!("Need to update tests for current platform!"); } diff --git a/crates/warp_core/src/telemetry.rs b/crates/warp_core/src/telemetry.rs index 90d078f4a0..c299176c3b 100644 --- a/crates/warp_core/src/telemetry.rs +++ b/crates/warp_core/src/telemetry.rs @@ -1,4 +1,4 @@ -// Zap: telemetry sending has been physically removed. These macros remain as +// Zaplex: telemetry sending has been physically removed. These macros remain as // compatibility shims for call sites that still describe local UI/business events. They // type-check the event expression in an unreachable branch without evaluating it, so // telemetry-only payload construction has no runtime cost while callers keep their existing diff --git a/crates/warp_core/src/ui/icons.rs b/crates/warp_core/src/ui/icons.rs index 45d653c851..13cb539f08 100644 --- a/crates/warp_core/src/ui/icons.rs +++ b/crates/warp_core/src/ui/icons.rs @@ -62,8 +62,8 @@ pub enum Icon { CloudOffline, Compass, CreateTeam, - ZapDrive, - Zap, + ZaplexDrive, + Zaplex, WarpLogoLight, ArrowLeft, ArrowBlockLeft, @@ -374,8 +374,8 @@ impl From for &'static str { Icon::CloudOffline => "bundled/svg/cloud-offline.svg", Icon::Compass => "bundled/svg/compass-3.svg", Icon::CreateTeam => "bundled/svg/create-team.svg", - Icon::ZapDrive => "bundled/svg/warp.svg", - Icon::Zap => "bundled/svg/warp-drive.svg", + Icon::ZaplexDrive => "bundled/svg/warp.svg", + Icon::Zaplex => "bundled/svg/warp-drive.svg", Icon::WarpLogoLight => "bundled/svg/warp-drive.svg", Icon::ArrowLeft => "bundled/svg/arrow-left.svg", Icon::ArrowBlockLeft => "bundled/svg/arrow-block-left.svg", diff --git a/crates/warp_core/src/ui/theme/ui_colors_tests.rs b/crates/warp_core/src/ui/theme/ui_colors_tests.rs index 1276b55bcd..7f08517d46 100644 --- a/crates/warp_core/src/ui/theme/ui_colors_tests.rs +++ b/crates/warp_core/src/ui/theme/ui_colors_tests.rs @@ -6,7 +6,7 @@ fn deserialize_empty_ui_colors() { let yaml = r##"--- {} "##; - let colors: UiColors = serde_yaml::from_str(yaml).expect("反序列化失败"); + let colors: UiColors = serde_yaml::from_str(yaml).expect("deserialization failed"); assert!(colors.surface_1.is_none()); assert!(colors.border.is_none()); assert!(colors.main_text.is_none()); @@ -24,7 +24,7 @@ focus_border: "#3994BCB3" selection: "#3994BC33" hover: "#FFFFFF0D" "##; - let colors: UiColors = serde_yaml::from_str(yaml).expect("反序列化失败"); + let colors: UiColors = serde_yaml::from_str(yaml).expect("deserialization failed"); assert_eq!(colors.surface_1.unwrap(), ColorU { r: 0x20, g: 0x21, b: 0x22, a: 255 }); assert_eq!(colors.surface_2.unwrap(), ColorU { r: 0x24, g: 0x25, b: 0x26, a: 255 }); @@ -58,7 +58,7 @@ fn serialize_ui_colors_skips_none() { success: None, link: None, }; - let yaml = serde_yaml::to_string(&colors).expect("序列化失败"); + let yaml = serde_yaml::to_string(&colors).expect("serialization failed"); assert!(yaml.contains("surface_1")); assert!(yaml.contains("border")); assert!(!yaml.contains("surface_2")); diff --git a/crates/warp_features/src/lib.rs b/crates/warp_features/src/lib.rs index e69f055ef1..8024abd3d7 100644 --- a/crates/warp_features/src/lib.rs +++ b/crates/warp_features/src/lib.rs @@ -62,10 +62,10 @@ pub enum FeatureFlag { /// Defaults Windows builds to the high-performance GPU with Vulkan as the preferred backend. WindowsHighPerformanceGpuDefault, - /// Zap Agent Mode. + /// Zaplex Agent Mode. AgentMode, - /// Whether the user is part of the Zap Alpha Program (AI Trusted Testers). + /// Whether the user is part of the Zaplex Alpha Program (AI Trusted Testers). /// This is enabled automatically for local and dev builds. /// Collect conversation and input autodetection data for agent mode. /// Also collects block data for Next Command, if enabled. @@ -84,7 +84,7 @@ pub enum FeatureFlag { /// Feature flag for cursor reflow fix (fixes part of the Alacritty resizing logic). ResizeFix, - /// Enable multiselect in Notebooks and Zap Text. + /// Enable multiselect in Notebooks and Zaplex Text. RichTextMultiselect, /// If enabled, the default input mode is set to waterfall for new users. @@ -109,10 +109,10 @@ pub enum FeatureFlag { /// Enable dynamic enum parameter types for workflow arguments DynamicWorkflowEnums, - /// Enables next action prediction within Zap, powered by AI. + /// Enables next action prediction within Zaplex, powered by AI. AgentPredict, - /// Enables receiving shared Zap Drive objects. + /// Enables receiving shared Zaplex Drive objects. SharedWithMe, /// Enables workflows for use with Agent Mode. @@ -149,7 +149,7 @@ pub enum FeatureFlag { /// tab. FullScreenZenMode, - /// Playground for reducing Zap UI clutter. + /// Playground for reducing Zaplex UI clutter. MinimalistUI, /// Enables support for using native shell completions to supplement our @@ -159,7 +159,7 @@ pub enum FeatureFlag { /// Adds avatar to the tab bar. AvatarInTabBar, - /// Adds aliases for executing Zap Drive workflows. + /// Adds aliases for executing Zaplex Drive workflows. WorkflowAliases, SshDragAndDrop, @@ -205,7 +205,7 @@ pub enum FeatureFlag { /// Enables Kitty image rendering KittyImages, - /// Enables support for Zap Packs. + /// Enables support for Zaplex Packs. WarpPacks, /// Enables auto-generated AI memories. @@ -298,7 +298,7 @@ pub enum FeatureFlag { /// Enables code symbols in AI context menu AIContextMenuCode, - /// Enables Zap Drive objects (like workflows) as context in AI context menu + /// Enables Zaplex Drive objects (like workflows) as context in AI context menu DriveObjectsAsContext, /// Expands code diff edits to replace the current pane instead of opening in a new tab. @@ -540,7 +540,7 @@ pub enum FeatureFlag { /// Enables conversation artifacts. ConversationArtifacts, - /// Enables auto-syncing ambient plans to Zap Drive. + /// Enables auto-syncing ambient plans to Zaplex Drive. SyncAmbientPlans, /// Enables platform skills support (--skill flag) for agent runs. @@ -551,9 +551,9 @@ pub enum FeatureFlag { /// Enables loading and returning bundled skills in the SkillManager. BundledSkills, - /// Enables the Zap launch modal announcing Zap going open-source. + /// Enables the Zaplex launch modal announcing Zaplex going open-source. /// When enabled, the HOA onboarding flow is suppressed. - ZapLaunchModal, + ZaplexLaunchModal, /// Updated tab styling (background colors, border, close button positioning, margins). NewTabStyling, @@ -572,7 +572,7 @@ pub enum FeatureFlag { ConversationsAsContext, /// Enables the rich input editor for CLI agents (e.g., Claude Code). - /// Ctrl-G intercepts the keystroke and opens Zap's input editor instead of $EDITOR. + /// Ctrl-G intercepts the keystroke and opens Zaplex's input editor instead of $EDITOR. CLIAgentRichInput, /// Enables incremental (diff-based) buffer updates for auto-reload instead of full replace. @@ -608,9 +608,9 @@ pub enum FeatureFlag { /// adopt the configured color when their working directory matches. DirectoryTabColors, - /// Enables the new settings to control visibility of Zap Drive, Code Review Panel, + /// Enables the new settings to control visibility of Zaplex Drive, Code Review Panel, /// and Project Explorer & Global Search features. - ZapNewSettingsModes, + ZaplexNewSettingsModes, /// Enables vertical tab layout as an alternative to the horizontal tab bar. VerticalTabs, @@ -620,21 +620,21 @@ pub enum FeatureFlag { HoaCodeReview, /// Enables the `--harness` flag for `oz agent run`, allowing external agent - /// CLIs (e.g. `claude`) to execute prompts instead of Zap's agent harness. + /// CLIs (e.g. `claude`) to execute prompts instead of Zaplex's agent harness. AgentHarness, /// Enables the upgraded CLI agent session tracking and notifications infrastructure. HOANotifications, - /// Enables the install/update chip for the OpenCode Zap plugin. + /// Enables the install/update chip for the OpenCode Zaplex plugin. /// Requires HOANotifications to also be enabled. OpenCodeNotifications, - /// Enables the install/update chip for the Codex Zap notification plugin. + /// Enables the install/update chip for the Codex Zaplex notification plugin. /// Requires HOANotifications to also be enabled. CodexNotifications, - /// Enables the install/update chip for the Gemini CLI Zap extension. + /// Enables the install/update chip for the Gemini CLI Zaplex extension. /// Requires HOANotifications to also be enabled. GeminiNotifications, @@ -647,8 +647,8 @@ pub enum FeatureFlag { /// When enabled, solo users (not on a team) can use BYO API keys. SoloUserByok, - /// Replaces the in-block warpification banner with a warpify footer. - WarpifyFooter, + /// Replaces the in-block zaplexification banner with a zaplexify footer. + ZaplexifyFooter, /// Guided onboarding flow for existing users introducing HOA features /// (vertical tabs, agent inbox, tab configs). @@ -750,7 +750,7 @@ pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[ FeatureFlag::ServerFileBrowser, ]; -/// Features enabled for feature preview build users (e.g.: Friends of Zap). +/// Features enabled for feature preview build users (e.g.: Friends of Zaplex). /// All PREVIEW_FLAGS are also automatically added to dogfood builds (WarpDev). pub const PREVIEW_FLAGS: &[FeatureFlag] = &[ FeatureFlag::MarkdownTables, @@ -766,7 +766,7 @@ pub const RELEASE_FLAGS: &[FeatureFlag] = &[ FeatureFlag::CrashReporting, // winit's IME path supports marked text on both macOS and Windows. // Windows requires this flag enabled to render IME preedit / input composition text; - // without it, only OS candidate window shows, and Zap discards marked text updates entirely. + // without it, only OS candidate window shows, and Zaplex discards marked text updates entirely. #[cfg(any(target_os = "macos", target_os = "windows"))] FeatureFlag::ImeMarkedText, FeatureFlag::BlocklistMarkdownTableRendering, @@ -857,7 +857,7 @@ impl FeatureFlag { Some("Enables rendering markdown tables inline in AI block list responses.") } MarkdownTables => Some("Enables rendering and interaction support for markdown tables in notebooks."), - SettingsFile => Some("Enables configuring Zap via a user-editable `settings.toml` file, with hot reload and error reporting for invalid values."), + SettingsFile => Some("Enables configuring Zaplex via a user-editable `settings.toml` file, with hot reload and error reporting for invalid values."), GitOperationsInCodeReview => Some("Enables commit, push, and create-PR actions directly from the code review panel."), _ => None, } diff --git a/crates/warp_logging/src/native.rs b/crates/warp_logging/src/native.rs index 800b349ad2..99ae73e092 100644 --- a/crates/warp_logging/src/native.rs +++ b/crates/warp_logging/src/native.rs @@ -416,7 +416,7 @@ fn write_log_bundle_zip_inner(zip_path: &Path, extras: &LogBundleExtras) -> Resu /// and any older logs for the active instance, written into the active /// log directory. Returns the resulting zip path. /// -/// Used for the "show in file manager after bundling" flow (Help menu → View Zap Logs). +/// Used for the "show in file manager after bundling" flow (Help menu → View Zaplex Logs). /// /// `extras` lets the caller append other diagnostic artifacts (MCP logs, autoupdate logs, diagnostic /// summaries, etc.); any missing or unreadable extra files are skipped and logged via `log::warn!`, @@ -515,7 +515,7 @@ fn init_internal( let stdout_is_a_tty = std::io::stdout().is_terminal(); let in_ci = env::var("CI").is_ok(); - let integration_test = env::var("WARP_INTEGRATION").is_ok(); + let integration_test = env::var("ZAPLEX_INTEGRATION").is_ok(); let use_logfile = match log_destination { Some(LogDestination::File) => true, Some(LogDestination::Stderr) => false, @@ -585,7 +585,7 @@ fn init_log_directory() -> Result { } else if #[cfg(any(target_os = "linux", target_os = "freebsd"))] { Ok(warp_core::paths::state_dir()) } else if #[cfg(windows)] { - Ok(warp_core::paths::state_dir().join(warp_core::paths::WARP_LOGS_DIR)) + Ok(warp_core::paths::state_dir().join(warp_core::paths::ZAPLEX_LOGS_DIR)) } else { Err(anyhow::anyhow!("Have not configured file-based logging for the current platform!")) } diff --git a/crates/warp_ripgrep/src/lib.rs b/crates/warp_ripgrep/src/lib.rs index a6b24d3904..a8d6fbee9c 100644 --- a/crates/warp_ripgrep/src/lib.rs +++ b/crates/warp_ripgrep/src/lib.rs @@ -7,7 +7,7 @@ mod types; /// On Unix, monitor the parent PID and exit this process if it changes. /// /// This is used by the ripgrep worker process to ensure we don't keep -/// searching if the main Zap process has exited. +/// searching if the main Zaplex process has exited. #[cfg(unix)] pub fn monitor_parent_and_exit_on_change(parent_pid: Option) { use nix::unistd::Pid; diff --git a/crates/warp_ripgrep/src/search.rs b/crates/warp_ripgrep/src/search.rs index acbdf28a04..98f8ed4da1 100644 --- a/crates/warp_ripgrep/src/search.rs +++ b/crates/warp_ripgrep/src/search.rs @@ -38,7 +38,7 @@ pub struct Match { /// Entry point for the ripgrep subprocess. /// /// Runs a ripgrep search in-process and writes JSON results to stdout. -/// The main Zap process spawns this via the `ripgrep-search` CLI +/// The main Zaplex process spawns this via the `ripgrep-search` CLI /// subcommand and reads the JSON output. pub fn run_search_subprocess( patterns: &[String], diff --git a/crates/warp_ssh_manager/Cargo.toml b/crates/warp_ssh_manager/Cargo.toml index 4d47a21ac1..8704b47b57 100644 --- a/crates/warp_ssh_manager/Cargo.toml +++ b/crates/warp_ssh_manager/Cargo.toml @@ -30,6 +30,6 @@ zeroize = "1.8" [dev-dependencies] diesel = { workspace = true, features = ["chrono", "sqlite", "returning_clauses_for_sqlite_3_35"] } diesel_migrations = "2.2.0" -# 静态链接 SQLite,避免依赖系统级 sqlite3.lib(Windows 默认没有)。 +# Statically link SQLite to avoid depending on system-level sqlite3.lib (not available by default on Windows). libsqlite3-sys = { version = "0.33.0", features = ["bundled"] } tempfile.workspace = true diff --git a/crates/warp_ssh_manager/src/lib.rs b/crates/warp_ssh_manager/src/lib.rs index 6722b65e7e..5cc2b0e601 100644 --- a/crates/warp_ssh_manager/src/lib.rs +++ b/crates/warp_ssh_manager/src/lib.rs @@ -25,6 +25,6 @@ pub use sync_provider::{ }; pub use types::ConnectionStatus; pub use types::{ - AuthType, NodeKind, OneKeyCredentialKind, ResolvedSshAuth, SshNode, SshOneKeyCredential, - SshServerInfo, + AuthType, NodeKind, OneKeyCredentialKind, ResolvedSshAuth, SessionResilience, SshNode, + SshOneKeyCredential, SshServerInfo, }; diff --git a/crates/warp_ssh_manager/src/repository.rs b/crates/warp_ssh_manager/src/repository.rs index fddba0759d..d3f8992167 100644 --- a/crates/warp_ssh_manager/src/repository.rs +++ b/crates/warp_ssh_manager/src/repository.rs @@ -13,8 +13,8 @@ use uuid::Uuid; use crate::secrets::SecretKind; use crate::types::{ - AuthType, NodeKind, OneKeyCredentialKind, ResolvedSshAuth, SshNode, SshOneKeyCredential, - SshServerInfo, + AuthType, NodeKind, OneKeyCredentialKind, ResolvedSshAuth, SessionResilience, SshNode, + SshOneKeyCredential, SshServerInfo, }; use persistence::model::{ NewSshNode, NewSshOneKeyCredential, NewSshServer, NewSyncMeta, SshNodeRow, @@ -102,6 +102,8 @@ impl SshRepository { startup_command: info.startup_command.as_deref(), notes: info.notes.as_deref(), credential_id: info.credential_id.as_deref(), + session_resilience: info.session_resilience.as_db_str(), + ring_ceiling_mb: info.ring_ceiling_mb as i32, }) .execute(conn)?; Ok(()) @@ -142,6 +144,8 @@ impl SshRepository { ssh_servers::startup_command.eq(info.startup_command.as_deref()), ssh_servers::notes.eq(info.notes.as_deref()), ssh_servers::credential_id.eq(info.credential_id.as_deref()), + ssh_servers::session_resilience.eq(info.session_resilience.as_db_str()), + ssh_servers::ring_ceiling_mb.eq(info.ring_ceiling_mb as i32), )) .execute(conn)?; if n == 0 { @@ -453,6 +457,14 @@ fn server_from_row(r: SshServerRow) -> Result notes: r.notes, last_connected_at: r.last_connected_at, credential_id: r.credential_id, + // Lenient on purpose: an unknown value (e.g. written by a newer client) + // degrades to `Off` rather than making the whole server unloadable. + // Explicit `Off` (not `unwrap_or_default`, whose default is now `PersistOnly` + // for *new* hosts) so a corrupt stored value never silently upgrades a + // saved host to persistent. + session_resilience: SessionResilience::parse(&r.session_resilience) + .unwrap_or(SessionResilience::Off), + ring_ceiling_mb: r.ring_ceiling_mb.max(0) as u32, }) } @@ -580,6 +592,10 @@ pub(crate) fn setup_in_memory() -> SqliteConnection { include_str!( "../../persistence/migrations/2026-06-09-160000_add_ssh_onekey_key_type/up.sql" ), + include_str!( + "../../persistence/migrations/2026-06-27-000000_add_session_resilience/up.sql" + ), + include_str!("../../persistence/migrations/2026-06-29-000000_add_ring_ceiling/up.sql"), ] { conn.batch_execute(up).unwrap(); } @@ -602,6 +618,8 @@ mod tests { startup_command: None, notes: None, last_connected_at: None, + session_resilience: SessionResilience::default(), + ring_ceiling_mb: 0, } } diff --git a/crates/warp_ssh_manager/src/ssh_command.rs b/crates/warp_ssh_manager/src/ssh_command.rs index 4ce8d66544..4fc516a999 100644 --- a/crates/warp_ssh_manager/src/ssh_command.rs +++ b/crates/warp_ssh_manager/src/ssh_command.rs @@ -393,11 +393,11 @@ impl AskpassSession { } // Write askpass helper script: read the first line from the file pointed to by - // %WARP_SSH_ASKPASS_FILE% and echo it to stdout. `set /p` reads the first line + // %ZAPLEX_SSH_ASKPASS_FILE% and echo it to stdout. `set /p` reads the first line // (stripping newline), `echo !PW!` outputs it. Use `setlocal enabledelayedexpansion` // + `!PW!` for delayed expansion to avoid password containing cmd special characters // (&, |, <, >, ^) being truncated by immediate expansion of %PW%. - let body = "@echo off\r\nsetlocal enabledelayedexpansion\r\nset /p PW=<\"%WARP_SSH_ASKPASS_FILE%\"\r\necho !PW!\r\nendlocal\r\n"; + let body = "@echo off\r\nsetlocal enabledelayedexpansion\r\nset /p PW=<\"%ZAPLEX_SSH_ASKPASS_FILE%\"\r\necho !PW!\r\nendlocal\r\n"; { let mut f = std::fs::OpenOptions::new() .write(true) @@ -417,7 +417,7 @@ impl AskpassSession { fn apply_env(&self, cmd: &mut command::r#async::Command) { cmd.env("SSH_ASKPASS", &self.script_path) .env("SSH_ASKPASS_REQUIRE", "force") - .env("WARP_SSH_ASKPASS_FILE", &self.password_path) + .env("ZAPLEX_SSH_ASKPASS_FILE", &self.password_path) .env_remove("DISPLAY"); } } diff --git a/crates/warp_ssh_manager/src/ssh_command_tests.rs b/crates/warp_ssh_manager/src/ssh_command_tests.rs index a4aec27366..5c36b1f92c 100644 --- a/crates/warp_ssh_manager/src/ssh_command_tests.rs +++ b/crates/warp_ssh_manager/src/ssh_command_tests.rs @@ -29,6 +29,8 @@ fn server() -> SshServerInfo { startup_command: None, notes: None, last_connected_at: None, + session_resilience: crate::types::SessionResilience::default(), + ring_ceiling_mb: 0, } } @@ -400,10 +402,10 @@ fn windows_askpass_script_is_spawnable() { // Spawn the askpass script using CreateProcessW to follow the same code path as ssh. // CREATE_NO_WINDOW simulates the environment when ssh spawns askpass (no console). - // Must set WARP_SSH_ASKPASS_FILE env var; the script uses it to locate the password file. + // Must set ZAPLEX_SSH_ASKPASS_FILE env var; the script uses it to locate the password file. let output = std::process::Command::new("cmd.exe") .raw_arg(format!("/c \"{}\"", script.display())) - .env("WARP_SSH_ASKPASS_FILE", &password_file) + .env("ZAPLEX_SSH_ASKPASS_FILE", &password_file) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/crates/warp_ssh_manager/src/ssh_config_parser.rs b/crates/warp_ssh_manager/src/ssh_config_parser.rs index ab177dc06d..7a5e33fa7d 100644 --- a/crates/warp_ssh_manager/src/ssh_config_parser.rs +++ b/crates/warp_ssh_manager/src/ssh_config_parser.rs @@ -16,7 +16,7 @@ use std::path::PathBuf; /// /// Fields are a subset of OpenSSH `ssh_config` — the minimal set selected by /// PRODUCT.md decisions I/J/K. `alias` is the literal alias from the `Host` line, -/// used as the `host` field when importing to `SshServerInfo`, so that when Zap +/// used as the `host` field when importing to `SshServerInfo`, so that when Zaplex /// subsequently starts `ssh`, OpenSSH can still apply advanced directives /// (`ProxyJump` etc.) corresponding to this alias in `~/.ssh/config`. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/warp_ssh_manager/src/sync_provider.rs b/crates/warp_ssh_manager/src/sync_provider.rs index b782089e6e..97b05cc59d 100644 --- a/crates/warp_ssh_manager/src/sync_provider.rs +++ b/crates/warp_ssh_manager/src/sync_provider.rs @@ -6,7 +6,7 @@ use crate::db::with_conn; use crate::repository::{SshRepository, SyncMetaRepository}; use crate::secrets::{KeychainSecretStore, SecretKind, SshSecretStore}; -use crate::types::{NodeKind, OneKeyCredentialKind}; +use crate::types::{NodeKind, OneKeyCredentialKind, SessionResilience}; use diesel::connection::{Connection, SimpleConnection}; use diesel::{QueryDsl, RunQueryDsl}; use serde::{Deserialize, Serialize}; @@ -47,11 +47,26 @@ pub struct SyncServer { pub notes: Option, #[serde(default)] pub credential_id: Option, + /// Native-session per-host opt-in (DB string form). Defaults to "off" so + /// payloads written by older clients deserialize cleanly. + #[serde(default = "default_session_resilience")] + pub session_resilience: String, + /// Per-host daemon scrollback ceiling in MiB (0 = daemon default). Defaults + /// to 0 so payloads written by older clients deserialize cleanly. + #[serde(default)] + pub ring_ceiling_mb: u32, pub password_encrypted: Option, pub passphrase_encrypted: Option, pub root_password_encrypted: Option, } +fn default_session_resilience() -> String { + // A sync payload predating this field represents a host from before persistence + // existed — keep it `off`. (Explicit, not the enum default, which is now + // `persist_only` for *new* hosts; we must not silently upgrade synced hosts.) + SessionResilience::Off.as_db_str().to_string() +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SyncOneKeyCredential { pub id: String, @@ -152,6 +167,8 @@ impl SyncDataProvider for SshSyncProvider { startup_command: server.startup_command.clone(), notes: server.notes.clone(), credential_id: server.credential_id.clone(), + session_resilience: server.session_resilience.as_db_str().to_string(), + ring_ceiling_mb: server.ring_ceiling_mb, password_encrypted: encrypt_optional(token, password.as_deref())?, passphrase_encrypted: encrypt_optional(token, passphrase.as_deref())?, root_password_encrypted: encrypt_optional(token, root_password.as_deref())?, @@ -335,6 +352,8 @@ impl SyncDataProvider for SshSyncProvider { startup_command: server.startup_command.as_deref(), notes: server.notes.as_deref(), credential_id: server.credential_id.as_deref(), + session_resilience: &server.session_resilience, + ring_ceiling_mb: server.ring_ceiling_mb as i32, }) .execute(conn)?; } @@ -607,6 +626,8 @@ mod tests { startup_command: None, notes: Some("test".to_string()), credential_id: None, + session_resilience: "off".to_string(), + ring_ceiling_mb: 0, password_encrypted: Some("enc123".to_string()), passphrase_encrypted: None, root_password_encrypted: Some("enc456".to_string()), @@ -632,6 +653,8 @@ mod tests { startup_command: None, notes: None, credential_id: None, + session_resilience: "off".to_string(), + ring_ceiling_mb: 0, password_encrypted: None, passphrase_encrypted: None, root_password_encrypted: None, @@ -664,6 +687,8 @@ mod tests { startup_command: None, notes: None, credential_id: None, + session_resilience: "off".to_string(), + ring_ceiling_mb: 0, password_encrypted: Some("enc".to_string()), passphrase_encrypted: None, root_password_encrypted: None, @@ -715,6 +740,8 @@ mod tests { assert!(parsed.onekey_credentials.is_empty()); assert_eq!(parsed.servers[0].credential_id, None); + // A payload predating the field deserializes to the "off" default. + assert_eq!(parsed.servers[0].session_resilience, "off"); } #[test] diff --git a/crates/warp_ssh_manager/src/types.rs b/crates/warp_ssh_manager/src/types.rs index e6143ba430..d6f83ff941 100644 --- a/crates/warp_ssh_manager/src/types.rs +++ b/crates/warp_ssh_manager/src/types.rs @@ -58,6 +58,49 @@ impl AuthType { } } +/// Per-host opt-in for the native persistent remote-session layer. +/// +/// `PersistOnly` is the **default for new hosts** — persistence is a core zaplex +/// feature (it's what makes a session survive transport drops), so new hosts opt +/// in by default. It only actually engages for headless-capable (key-auth) hosts; +/// others transparently fall back to the classic local-PTY `ssh` path. `Off` +/// keeps that classic behavior. Existing saved hosts keep their stored value. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +pub enum SessionResilience { + /// No persistence; classic local-PTY-runs-ssh. + Off, + /// Daemon-hosted session with server-side persistence + replay/reattach. + /// Default for new hosts. + #[default] + PersistOnly, + /// Persistence plus the mosh-grade UDP transport (Phase B3). + PersistPlusMosh, +} + +impl SessionResilience { + pub fn as_db_str(&self) -> &'static str { + match self { + SessionResilience::Off => "off", + SessionResilience::PersistOnly => "persist_only", + SessionResilience::PersistPlusMosh => "persist_plus_mosh", + } + } + + pub fn parse(s: &str) -> Option { + match s { + "off" => Some(SessionResilience::Off), + "persist_only" => Some(SessionResilience::PersistOnly), + "persist_plus_mosh" => Some(SessionResilience::PersistPlusMosh), + _ => None, + } + } + + /// Whether this host should run as a daemon-hosted session at all. + pub fn is_enabled(&self) -> bool { + !matches!(self, SessionResilience::Off) + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum OneKeyCredentialKind { Password, @@ -110,6 +153,12 @@ pub struct SshServerInfo { pub startup_command: Option, pub notes: Option, pub last_connected_at: Option, + /// Per-host opt-in for the native persistent remote-session layer. + pub session_resilience: SessionResilience, + /// Per-host scrollback/replay buffer ceiling for a daemon session, in MiB. + /// `0` means "use the daemon default". Only meaningful when + /// `session_resilience` is enabled (it sizes the daemon-side OutputRing). + pub ring_ceiling_mb: u32, } impl SshServerInfo { @@ -125,6 +174,8 @@ impl SshServerInfo { startup_command: None, notes: None, last_connected_at: None, + session_resilience: SessionResilience::default(), + ring_ceiling_mb: 0, } } @@ -141,6 +192,8 @@ impl SshServerInfo { startup_command: source.startup_command.clone(), notes: source.notes.clone(), last_connected_at: None, + session_resilience: source.session_resilience, + ring_ceiling_mb: source.ring_ceiling_mb, } } } diff --git a/crates/warp_terminal/src/model/block_id.rs b/crates/warp_terminal/src/model/block_id.rs index 12155048cf..da6461aa18 100644 --- a/crates/warp_terminal/src/model/block_id.rs +++ b/crates/warp_terminal/src/model/block_id.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; /// A globally unique ID for the block that is unique across all sessions. -/// For a block created as a result of pty output, it takes the form {WARP_SESSION_ID}-{NUM_ID}, +/// For a block created as a result of pty output, it takes the form {ZAPLEX_SESSION_ID}-{NUM_ID}, /// where NUM_ID is a monotonically increasing counter for the session. /// This is because the block ID comes from the precmd in this case, and it is expensive to create a UUID in the bootstrap script. /// For manually created blocks within the app, it is a UUID. diff --git a/crates/warp_terminal/src/shell/mod.rs b/crates/warp_terminal/src/shell/mod.rs index 688f6be51d..447aaac8e1 100644 --- a/crates/warp_terminal/src/shell/mod.rs +++ b/crates/warp_terminal/src/shell/mod.rs @@ -160,7 +160,7 @@ impl Shell { /// If the particular version of this shell supports input reporting, return the byte sequence /// to trigger input reporting. /// - /// These sequences are bound to Zap shell functions during session bootstrap that print the + /// These sequences are bound to Zaplex shell functions during session bootstrap that print the /// shell's input buffer, wrapped within the 'InputBuffer' DCS hook when triggered. PowerShell /// cannot use a binding that contains the letter "i" because it does virtual key code /// translation based on the current layout, and not all layouts have the letter "i". @@ -581,7 +581,7 @@ impl ShellType { } } - /// If true, Zap will bootstrap the shell if it's the login shell on the remote host. + /// If true, Zaplex will bootstrap the shell if it's the login shell on the remote host. pub fn is_fully_supported_remotely(&self) -> bool { match self { ShellType::Zsh | ShellType::Bash => true, @@ -654,7 +654,7 @@ impl ShellType { // Those suffixes are contained in `PATHEXT`. for ext in PATHEXT { // If the command ends with one of those suffixes, tell - // Zap about this command as-is and also sans-suffix, e.g. + // Zaplex about this command as-is and also sans-suffix, e.g. // "git" and "git.exe". if line.to_lowercase().ends_with(&ext.to_lowercase()) { let trimmed = &line[..line.len() - ext.len()]; @@ -693,7 +693,7 @@ impl ShellType { // Those suffixes are contained in `PATHEXT`. for ext in PATHEXT { // If the command ends with one of those suffixes, tell - // Zap about this command as-is and also sans-suffix, e.g. + // Zaplex about this command as-is and also sans-suffix, e.g. // "git" and "git.exe". if line.to_lowercase().ends_with(&ext.to_lowercase()) { let trimmed = &line[..line.len() - ext.len()]; @@ -821,7 +821,7 @@ impl ShellLaunchData { let windows_encoding = TypedPath::unix(path_str).with_windows_encoding(); PathBuf::try_from(windows_encoding).ok() } - // The container is Unix; Zap runs on the host, so paths are + // The container is Unix; Zaplex runs on the host, so paths are // already in the host's native encoding. Pass through unchanged. ShellLaunchData::DockerSandbox { .. } => Some(PathBuf::from(path_str)), } diff --git a/crates/warp_util/src/file_type.rs b/crates/warp_util/src/file_type.rs index 4e0e8761fd..6a93744af1 100644 --- a/crates/warp_util/src/file_type.rs +++ b/crates/warp_util/src/file_type.rs @@ -40,7 +40,7 @@ pub fn is_file_content_binary(path: impl AsRef) -> bool { is_buffer_binary(&buffer[..n]) } -/// Checks if a file is a binary file that should not be opened in Zap. +/// Checks if a file is a binary file that should not be opened in Zaplex. /// Note that we only check the file extension, not the file content. /// Returns true for common binary file extensions like images, videos, executables, etc. pub fn is_binary_file(path: impl AsRef) -> bool { diff --git a/crates/warp_util/src/path.rs b/crates/warp_util/src/path.rs index 59ad957122..bfc8296bcb 100644 --- a/crates/warp_util/src/path.rs +++ b/crates/warp_util/src/path.rs @@ -1,4 +1,4 @@ -//! This module contains utilities for dealing with file/directory paths throughout Zap. +//! This module contains utilities for dealing with file/directory paths throughout Zaplex. use std::borrow::Cow; use std::collections::HashMap; use std::env::{self, VarError}; @@ -77,10 +77,10 @@ const DIRS_IN_MSYS2_ROOT: [&[u8]; 14] = [ b"installerResources", ]; -/// \return any override shell launch path, reading from the WARP_SHELL_PATH variable. +/// \return any override shell launch path, reading from the ZAPLEX_SHELL_PATH variable. pub fn warp_shell_path() -> Option { // TODO(peter): we ought to tolerate non-Unicode paths here. - env::var("WARP_SHELL_PATH").ok() + env::var("ZAPLEX_SHELL_PATH").ok() } /// Abbreviates the session home directory in the given path to '~', if it is in the given path, diff --git a/crates/warp_web_event_bus/src/lib.rs b/crates/warp_web_event_bus/src/lib.rs index 9ab3fa1ca4..e6ea280f7d 100644 --- a/crates/warp_web_event_bus/src/lib.rs +++ b/crates/warp_web_event_bus/src/lib.rs @@ -4,7 +4,7 @@ use js_sys::ReferenceError; use serde::Serialize; use wasm_bindgen::JsCast; -/// Events emitted from Zap on Web to the host JavaScript app. +/// Events emitted from Zaplex on Web to the host JavaScript app. /// /// These must stay in sync with the [`WarpEvent` TypeScript type](https://github.com/warpdotdev/warp-server/blob/develop/client/src/warp-client/index.ts). #[derive(Debug, Clone, Serialize)] diff --git a/crates/warpui/build.rs b/crates/warpui/build.rs index 6dea7db84d..3540e6949f 100644 --- a/crates/warpui/build.rs +++ b/crates/warpui/build.rs @@ -1,5 +1,5 @@ // We can use `std::process:Command` here because this is invoked within a build script, -// _not_ within the Zap binary (where it could cause a terminal to temporarily flash on +// _not_ within the Zaplex binary (where it could cause a terminal to temporarily flash on // Windows). #![allow(clippy::disallowed_types)] diff --git a/crates/warpui/examples/animated-gradient-text/root_view.rs b/crates/warpui/examples/animated-gradient-text/root_view.rs index ffdf079c77..7f43236722 100644 --- a/crates/warpui/examples/animated-gradient-text/root_view.rs +++ b/crates/warpui/examples/animated-gradient-text/root_view.rs @@ -33,7 +33,7 @@ impl RootView { let end = ColorU::new(255, 255, 255, 255); Self { - text: "Zap shimmer: 👩‍💻with ligatures — fi fl 🇺🇸".to_string(), + text: "Zaplex shimmer: 👩‍💻with ligatures — fi fl 🇺🇸".to_string(), font_family, font_size: 28.0, start, diff --git a/crates/warpui/src/platform/linux/mod.rs b/crates/warpui/src/platform/linux/mod.rs index c894299892..839f1942ea 100644 --- a/crates/warpui/src/platform/linux/mod.rs +++ b/crates/warpui/src/platform/linux/mod.rs @@ -64,7 +64,7 @@ pub fn is_wsl() -> bool { } pub fn is_wayland_env_var_set() -> bool { - std::env::var_os("WARP_ENABLE_WAYLAND") + std::env::var_os("ZAPLEX_ENABLE_WAYLAND") .is_some_and(|warp_enable_wayland| warp_enable_wayland.eq_ignore_ascii_case("1")) } diff --git a/crates/warpui/src/platform/mac/delegate.rs b/crates/warpui/src/platform/mac/delegate.rs index 080744f376..b9dee9a271 100644 --- a/crates/warpui/src/platform/mac/delegate.rs +++ b/crates/warpui/src/platform/mac/delegate.rs @@ -285,7 +285,7 @@ impl platform::Delegate for AppDelegate { dispatch::Queue::main().exec_async(move || unsafe { // See https://developer.apple.com/documentation/appkit/nsapplication/1428455-orderfrontcharacterpalette. // If the `sender` argument is nil, the palette is shown relative to the - // first responder's cursor location. In our case, that will be the Zap + // first responder's cursor location. In our case, that will be the Zaplex // host view, with a location set via the `active_cursor_position` API. let () = msg_send![NSApp(), orderFrontCharacterPalette: nil]; }); diff --git a/crates/warpui/src/platform/mac/fonts.rs b/crates/warpui/src/platform/mac/fonts.rs index 7ab2a893ca..da7d2137e6 100644 --- a/crates/warpui/src/platform/mac/fonts.rs +++ b/crates/warpui/src/platform/mac/fonts.rs @@ -144,7 +144,7 @@ mod loader { LoadedSystemFonts(fonts) } - // We use font-kit's family handle to load fonts that come with Zap as + // We use font-kit's family handle to load fonts that come with Zaplex as // these binaries are already in memory and won't increase our memory load. pub fn load_font_family_from_bytes(name: &str, font_bytes: Vec>) -> Result { let mut fonts = Vec::with_capacity(font_bytes.len()); diff --git a/crates/warpui/src/platform/mac/menus.rs b/crates/warpui/src/platform/mac/menus.rs index 7277165546..b2337be521 100644 --- a/crates/warpui/src/platform/mac/menus.rs +++ b/crates/warpui/src/platform/mac/menus.rs @@ -190,8 +190,8 @@ fn resolve_standard_action(action: StandardAction) -> StandardMenuItemProperties match action { StandardAction::Close => make("Close Window", "performClose:", none, ""), - StandardAction::Quit => make("Quit Zap", "terminate:", cmd, "q"), - StandardAction::Hide => make("Hide Zap", "hide:", cmd, "h"), + StandardAction::Quit => make("Quit Zaplex", "terminate:", cmd, "q"), + StandardAction::Hide => make("Hide Zaplex", "hide:", cmd, "h"), StandardAction::HideOtherApps => { make("Hide Others", "hideOtherApplications:", cmd | option, "h") } diff --git a/crates/warpui/src/platform/mac/menus_tests.rs b/crates/warpui/src/platform/mac/menus_tests.rs index 935787e8f3..2da8a4d121 100644 --- a/crates/warpui/src/platform/mac/menus_tests.rs +++ b/crates/warpui/src/platform/mac/menus_tests.rs @@ -75,7 +75,7 @@ fn apply_changes_local_pool_memory_behavior() { for _ in 0..APPLY_CHANGES_ITERS { let changes = MenuItemPropertyChanges { - name: Some("Zap Menu Item".to_string()), + name: Some("Zaplex Menu Item".to_string()), keystroke: Some(Some(Keystroke { cmd: true, key: "k".to_string(), diff --git a/crates/warpui/src/platform/mac/rendering/wgpu/mod.rs b/crates/warpui/src/platform/mac/rendering/wgpu/mod.rs index cab3359f06..4e2649e6ca 100644 --- a/crates/warpui/src/platform/mac/rendering/wgpu/mod.rs +++ b/crates/warpui/src/platform/mac/rendering/wgpu/mod.rs @@ -59,7 +59,7 @@ impl Device { /// The raw-window-handle crate purposefully does not provide a blanket implementation of this trait /// for any implementation of [`RawWindowHandle`] or [`RawDisplayHandle`] because it's not /// guaranteed that the underlying window won't become invalid while the `WindowHandle` is alive. -/// In the case of Zap this _should_ be safe because we ultimately deallocate the native window +/// In the case of Zaplex this _should_ be safe because we ultimately deallocate the native window /// when [`crate::platform::mac::Window`] is deallocated (once a `Window` is deallocated, there /// are no pointers to the native window anymore, which cause it to be deallocated via the /// `warp_dealloc_window` callback). diff --git a/crates/warpui/src/platform/windows/mod.rs b/crates/warpui/src/platform/windows/mod.rs index 0e8c81f5d5..d23b042b3d 100644 --- a/crates/warpui/src/platform/windows/mod.rs +++ b/crates/warpui/src/platform/windows/mod.rs @@ -73,7 +73,7 @@ fn register_aumid_in_registry(app_id: &str) -> std::io::Result<()> { let subkey = format!("Software\\Classes\\AppUserModelId\\{app_id}"); let (key, _) = hkcu.create_subkey(&subkey)?; - // Derive a presentable display name from the AUMID suffix (e.g. dev.zap.Zap → Zap). + // Derive a presentable display name from the AUMID suffix (e.g. dev.zaplex.Zaplex → Zaplex). let display_name = app_id.rsplit('.').next().unwrap_or(app_id); key.set_value("DisplayName", &display_name.to_string())?; Ok(()) diff --git a/crates/warpui/src/rendering/wgpu/mod.rs b/crates/warpui/src/rendering/wgpu/mod.rs index 41cab965f3..fe800d23c1 100644 --- a/crates/warpui/src/rendering/wgpu/mod.rs +++ b/crates/warpui/src/rendering/wgpu/mod.rs @@ -33,7 +33,7 @@ pub fn reset_wgpu_instance(display_handle: Box) { // Check whether DirectComposition should be explicitly disabled on Windows. - let disable_dcomp = std::env::var("WARP_USE_DIRECT_COMPOSITION") + let disable_dcomp = std::env::var("ZAPLEX_USE_DIRECT_COMPOSITION") .ok() .is_some_and(|val| { let val = val.to_lowercase(); diff --git a/crates/warpui/src/rendering/wgpu/renderer/glyph.rs b/crates/warpui/src/rendering/wgpu/renderer/glyph.rs index 8faca09d67..a468d65a14 100644 --- a/crates/warpui/src/rendering/wgpu/renderer/glyph.rs +++ b/crates/warpui/src/rendering/wgpu/renderer/glyph.rs @@ -112,7 +112,7 @@ impl Pipeline { multisample: wgpu::MultisampleState::default(), multiview_mask: None, // Don't use a pipeline cache. Most desktop GPU drivers have their own internal caches, - // so we are unlikely to get much value out of this for the platforms Zap supports. + // so we are unlikely to get much value out of this for the platforms Zaplex supports. cache: None, }); diff --git a/crates/warpui/src/rendering/wgpu/renderer/image.rs b/crates/warpui/src/rendering/wgpu/renderer/image.rs index b850dc93d1..fce21d72ed 100644 --- a/crates/warpui/src/rendering/wgpu/renderer/image.rs +++ b/crates/warpui/src/rendering/wgpu/renderer/image.rs @@ -102,7 +102,7 @@ impl Pipeline { multisample: wgpu::MultisampleState::default(), multiview_mask: None, // Don't use a pipeline cache. Most desktop GPU drivers have their own internal caches, - // so we are unlikely to get much value out of this for the platforms Zap supports. + // so we are unlikely to get much value out of this for the platforms Zaplex supports. cache: None, }); diff --git a/crates/warpui/src/rendering/wgpu/renderer/rect.rs b/crates/warpui/src/rendering/wgpu/renderer/rect.rs index 0c3aa44a97..3702f5e69b 100644 --- a/crates/warpui/src/rendering/wgpu/renderer/rect.rs +++ b/crates/warpui/src/rendering/wgpu/renderer/rect.rs @@ -67,7 +67,7 @@ impl Pipeline { multisample: wgpu::MultisampleState::default(), multiview_mask: None, // Don't use a pipeline cache. Most desktop GPU drivers have their own internal caches, - // so we are unlikely to get much value out of this for the platforms Zap supports. + // so we are unlikely to get much value out of this for the platforms Zaplex supports. cache: None, }); diff --git a/crates/warpui/src/rendering/wgpu/resources.rs b/crates/warpui/src/rendering/wgpu/resources.rs index a13a15d085..5922497217 100644 --- a/crates/warpui/src/rendering/wgpu/resources.rs +++ b/crates/warpui/src/rendering/wgpu/resources.rs @@ -37,7 +37,7 @@ lazy_static! { /// of Mesa's llvmpipe software renderer. /// /// While lavapipe is theoretically Vulkan 1.3 compatible starting in version - /// 22.1.2, in practice, Zap windows don't render properly until 24.0.2. + /// 22.1.2, in practice, Zaplex windows don't render properly until 24.0.2. static ref MIN_SUPPORTED_LAVAPIPE_VERSION: Version<'static> = Version::from("24.0.2") .expect("should not fail to parse version"); diff --git a/crates/warpui/src/windowing/winit/app.rs b/crates/warpui/src/windowing/winit/app.rs index 0e1044e892..7e3e562861 100644 --- a/crates/warpui/src/windowing/winit/app.rs +++ b/crates/warpui/src/windowing/winit/app.rs @@ -48,7 +48,7 @@ pub enum CustomEvent { /// /// We use this to trigger [`platform::AppCallbacks::on_active_window_changed`] instead of /// winit's [`winit::event::WindowEvent::Focused`]. This is because winit's `Focused` event - /// actually fires twice when focus is transferred between 2 of Zap's own windows. But, we + /// actually fires twice when focus is transferred between 2 of Zaplex's own windows. But, we /// only want to fire `on_active_window_changed` once for that focus change. So, we coalesce /// multiple `Focused` events into a single `ActiveWindowChanged` event on the next tick of the /// [`winit::event_loop::EventLoop`]. diff --git a/crates/warpui/src/windowing/winit/delegate.rs b/crates/warpui/src/windowing/winit/delegate.rs index cd6d22b07d..6d3ec97a24 100644 --- a/crates/warpui/src/windowing/winit/delegate.rs +++ b/crates/warpui/src/windowing/winit/delegate.rs @@ -82,7 +82,7 @@ pub fn open_url_in_system(url: &str) { // 1. First attempt to open with `wslview`, since that is basically made to open stuff in wsl // 2. Use `cmd.exe /c start {url}` to open in the user's default windows browser // - If a user does not want this behavior, and wants all opening to go through - // WSL, they can set the env variable WARP_FORCE_WSL_BROWSER. + // WSL, they can set the env variable ZAPLEX_FORCE_WSL_BROWSER. // 3. Fall back to default linux url opening behavior. if platform::linux::is_wsl() { match open::with_detached(url, "wslview") { @@ -129,7 +129,7 @@ pub fn open_url_in_system(url: &str) { fn use_wsl_browser() -> bool { static USE_WSL_BROWSER: OnceLock = OnceLock::new(); USE_WSL_BROWSER - .get_or_init(|| std::env::var("WARP_FORCE_WSL_BROWSER").is_ok()) + .get_or_init(|| std::env::var("ZAPLEX_FORCE_WSL_BROWSER").is_ok()) .to_owned() } diff --git a/crates/warpui/src/windowing/winit/delegate/global_hotkey.rs b/crates/warpui/src/windowing/winit/delegate/global_hotkey.rs index 0030eb223b..f1d0b63ade 100644 --- a/crates/warpui/src/windowing/winit/delegate/global_hotkey.rs +++ b/crates/warpui/src/windowing/winit/delegate/global_hotkey.rs @@ -73,7 +73,7 @@ impl GlobalHotKeyHandler { thread::spawn(move || { while let Ok(event) = GlobalHotKeyEvent::receiver().recv() { // Trigger when the hotkey is released, _not_ pressed. This is due to an X11 - // quirk where focus is transferred out of Zap windows after a global hotkey + // quirk where focus is transferred out of Zaplex windows after a global hotkey // is pressed. This breaks our quake mode logic. However, focus is restored // when the hotkey is released. if event.state == HotKeyState::Released { diff --git a/crates/warpui/src/windowing/winit/event_loop/mod.rs b/crates/warpui/src/windowing/winit/event_loop/mod.rs index d7f1b88842..c273f7804c 100644 --- a/crates/warpui/src/windowing/winit/event_loop/mod.rs +++ b/crates/warpui/src/windowing/winit/event_loop/mod.rs @@ -921,7 +921,7 @@ impl EventLoop { }; // There is a winit bug such that events which cause a window to switch displays to - // one with a different scale factor resize the Zap window to an absurdly small + // one with a different scale factor resize the Zaplex window to an absurdly small // size, <157, 25> on my system when I repro it. Events include unplugging a // display, changing a display from extended to mirrored, and the like. We work // around that by listening for [`WindowEvent::ScaleFactorChanged`] and changing diff --git a/crates/warpui/src/windowing/winit/fonts.rs b/crates/warpui/src/windowing/winit/fonts.rs index 9563df9a45..e5592b0383 100644 --- a/crates/warpui/src/windowing/winit/fonts.rs +++ b/crates/warpui/src/windowing/winit/fonts.rs @@ -136,7 +136,7 @@ mod loader { } } -// We use font-kit's family handle to load fonts that come with Zap as +// We use font-kit's family handle to load fonts that come with Zaplex as // these binaries are already in memory and won't increase our memory load. fn load_font_family_from_bytes(name: &str, font_bytes: Vec>) -> Result { use owned_ttf_parser::OwnedFace; @@ -318,7 +318,7 @@ impl Default for TextLayoutSystem { /// mmap-backed font file data itself is not reloaded. /// /// This is acceptable because UI locale switching is rare (currently driven by Settings → -/// Language and "restart Zap" prompts), and users already expect a brief interruption. +/// Language and "restart Zaplex" prompts), and users already expect a brief interruption. /// If zero-latency hot-reload is needed later, the hook is at `app::i18n::set_locale`. fn rebuild_font_system_for_locale( store: &std::sync::Arc>, diff --git a/crates/warpui/src/windowing/winit/fonts/font_handle.rs b/crates/warpui/src/windowing/winit/fonts/font_handle.rs index 2cda7deb05..25bd7cd8a2 100644 --- a/crates/warpui/src/windowing/winit/fonts/font_handle.rs +++ b/crates/warpui/src/windowing/winit/fonts/font_handle.rs @@ -126,7 +126,7 @@ pub enum Error { }, /// A font was properly loaded, but did not have a codepoint - /// for the letter m, indicating it would not work within Zap. + /// for the letter m, indicating it would not work within Zaplex. #[error("Font {path} does not have a valid codepoint for the letter m")] Validate { path: PathBuf }, } diff --git a/crates/warpui/src/windowing/winit/window.rs b/crates/warpui/src/windowing/winit/window.rs index 0a491c7a71..7702e53515 100644 --- a/crates/warpui/src/windowing/winit/window.rs +++ b/crates/warpui/src/windowing/winit/window.rs @@ -90,7 +90,7 @@ pub(crate) struct WindowManager { windows: HashMap>, event_loop_proxy: EventLoopProxy, window_ordering: Mutex, - /// We assume this won't change throughout the life of the Zap process. + /// We assume this won't change throughout the life of the Zaplex process. os_window_manager_name: OnceCell>, /// This is a client for talking to the Xorg server directly instead of through winit. #[cfg(any(target_os = "linux", target_os = "freebsd"))] diff --git a/crates/warpui/src/windowing/winit/window/windows_wm.rs b/crates/warpui/src/windowing/winit/window/windows_wm.rs index 1cbd673933..7cc008ffb7 100644 --- a/crates/warpui/src/windowing/winit/window/windows_wm.rs +++ b/crates/warpui/src/windowing/winit/window/windows_wm.rs @@ -15,7 +15,7 @@ use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; use super::get_monitor_logical_bounds; impl WindowManager { - /// Returns the active Zap window. This will return an error if a different app's window is + /// Returns the active Zaplex window. This will return an error if a different app's window is /// active. fn get_active_window_handle(&self) -> Result> { let window_id = &self diff --git a/crates/warpui_core/build.rs b/crates/warpui_core/build.rs index d636cd45aa..c1a573f68a 100644 --- a/crates/warpui_core/build.rs +++ b/crates/warpui_core/build.rs @@ -1,5 +1,5 @@ // We can use `std::process:Command` here because this is invoked within a build script, -// _not_ within the Zap binary (where it could cause a terminal to temporarily flash on +// _not_ within the Zaplex binary (where it could cause a terminal to temporarily flash on // Windows). #![allow(clippy::disallowed_types)] diff --git a/crates/warpui_core/src/accessibility.rs b/crates/warpui_core/src/accessibility.rs index e3d9d97c8f..3732d4a884 100644 --- a/crates/warpui_core/src/accessibility.rs +++ b/crates/warpui_core/src/accessibility.rs @@ -3,8 +3,8 @@ //! disabilities to use certain software. In our case: we focus on blind users and their day-to-day //! life with screen readers. //! -//! ## How does a11y work in Zap? -//! Because Zap uses its own rust UI framework (warpui), we don’t benefit from the built-in +//! ## How does a11y work in Zaplex? +//! Because Zaplex uses its own rust UI framework (warpui), we don’t benefit from the built-in //! VoiceOver integration and objc NSAccessibility APIs. This is both good and bad for our app and //! the UI framework. //! @@ -63,7 +63,7 @@ pub struct AccessibilityContent { /// (currently unused) The rectangle that describes where the given element is on the screen. /// System’s APIs then draw a frame around that element, making it super clear what object /// the description is referring to. - /// Frame support is a work-in-progress in Zap and right now this field is omitted and not set. + /// Frame support is a work-in-progress in Zaplex and right now this field is omitted and not set. pub frame: Option, /// The role a given element has. Note that we use our own, WarpUI-defined roles (vs those that /// come from the NSAccessibility framework). The role describes the action/element/event role ( diff --git a/crates/warpui_core/src/actions.rs b/crates/warpui_core/src/actions.rs index 9b341da3a1..2763cb95ef 100644 --- a/crates/warpui_core/src/actions.rs +++ b/crates/warpui_core/src/actions.rs @@ -1,6 +1,6 @@ /// A StandardAction is one that corresponds to an action that /// must be dispatched and handled natively by NSApp (e.g. terminate:) -/// Use CustomActions for handling Zap specific actions. +/// Use CustomActions for handling Zaplex specific actions. /// /// Set a 'repr' here as we store these values as tags in menu items. #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive, Hash)] diff --git a/crates/warpui_core/src/event.rs b/crates/warpui_core/src/event.rs index 1102ae0ab2..f70d210951 100644 --- a/crates/warpui_core/src/event.rs +++ b/crates/warpui_core/src/event.rs @@ -153,12 +153,12 @@ pub enum Event { position: Vector2F, cmd: bool, shift: bool, - /// Whether this mouse event was initiated by the user or by Zap synthetically. + /// Whether this mouse event was initiated by the user or by Zaplex synthetically. /// We create such synthetic mouse events for certain behaviors behaviors within - /// Zap, such as triggering the correct tab to be "hovered" when the user closes + /// Zaplex, such as triggering the correct tab to be "hovered" when the user closes /// a tab to the left. In this case, there's no user-initiated mouse event, however, /// we create a synthetic mouse event so that we carry over the "hovered" state from - /// the old tab (closed) to the new tab (shifted). Certain elements within Zap, + /// the old tab (closed) to the new tab (shifted). Certain elements within Zaplex, /// such as the alt-screen, may not want such synthetic mouse events since it can /// interfere with actions such as mouse dragging. is_synthetic: bool, @@ -191,7 +191,7 @@ pub enum Event { TypedCharacters { chars: String, }, - /// Gets fired when user drags a file or folder into Zap. Note that there could exist + /// Gets fired when user drags a file or folder into Zaplex. Note that there could exist /// multiple file paths in one event as user could drag and drop multiple targets. DragAndDropFiles { paths: Vec, diff --git a/crates/warpui_core/src/image_cache_tests.rs b/crates/warpui_core/src/image_cache_tests.rs index 5593da0b25..a2f917ff28 100644 --- a/crates/warpui_core/src/image_cache_tests.rs +++ b/crates/warpui_core/src/image_cache_tests.rs @@ -342,7 +342,7 @@ fn test_preview_and_full_animation_requests_do_not_collide_in_rendered_image_cac fn test_svg_text_rasterizes_with_loaded_system_fonts() { let image_type = ImageType::try_from_bytes( br##" - Zap + Zaplex "##, ) @@ -376,7 +376,7 @@ fn test_svg_text_rasterizes_with_loaded_system_fonts() { let svg = format!( "\ - Zap\ + Zaplex\ " ); diff --git a/crates/warpui_core/src/integration/artifacts.rs b/crates/warpui_core/src/integration/artifacts.rs index d866a42769..8156ad28f2 100644 --- a/crates/warpui_core/src/integration/artifacts.rs +++ b/crates/warpui_core/src/integration/artifacts.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; pub const ARTIFACTS_KEY: &str = "test_artifacts"; -pub const ARTIFACTS_DIR_ENV_VAR: &str = "WARP_INTEGRATION_TEST_ARTIFACTS_DIR"; +pub const ARTIFACTS_DIR_ENV_VAR: &str = "ZAPLEX_INTEGRATION_TEST_ARTIFACTS_DIR"; pub struct TestArtifacts { dir: PathBuf, diff --git a/crates/warpui_core/src/integration/capture_recorder.rs b/crates/warpui_core/src/integration/capture_recorder.rs index e3666c1b30..ccacd4c6c4 100644 --- a/crates/warpui_core/src/integration/capture_recorder.rs +++ b/crates/warpui_core/src/integration/capture_recorder.rs @@ -23,7 +23,7 @@ pub const SCREENSHOT_PATH_KEY: &str = "pending_screenshot_path"; /// Environment variable that enables automatic video recording for all /// integration test steps. When set, the driver starts recording at the /// beginning of the test and writes the video on completion. -pub const CAPTURE_RECORDING_ENABLED_ENV_VAR: &str = "WARP_INTEGRATION_TEST_VIDEO"; +pub const CAPTURE_RECORDING_ENABLED_ENV_VAR: &str = "ZAPLEX_INTEGRATION_TEST_VIDEO"; /// A captured frame paired with the wall-clock time it was taken. struct TimestampedFrame { diff --git a/crates/warpui_core/src/integration/driver.rs b/crates/warpui_core/src/integration/driver.rs index 251ccefdbe..8c7081dcac 100644 --- a/crates/warpui_core/src/integration/driver.rs +++ b/crates/warpui_core/src/integration/driver.rs @@ -715,7 +715,7 @@ impl TestDriver { /// Returns whether video recording should be enabled for the given test name. /// -/// Checks the `WARP_INTEGRATION_TEST_VIDEO` environment variable: +/// Checks the `ZAPLEX_INTEGRATION_TEST_VIDEO` environment variable: /// - Unset or empty → recording disabled. /// - `"1"` or `"all"` → recording enabled for every test. /// - Any other value → treated as a comma-separated list of test names; @@ -724,10 +724,10 @@ impl TestDriver { /// Example: /// ```sh /// # Record all tests -/// WARP_INTEGRATION_TEST_VIDEO=1 cargo nextest run ... +/// ZAPLEX_INTEGRATION_TEST_VIDEO=1 cargo nextest run ... /// /// # Record only specific tests -/// WARP_INTEGRATION_TEST_VIDEO=test_foo,test_bar cargo nextest run ... +/// ZAPLEX_INTEGRATION_TEST_VIDEO=test_foo,test_bar cargo nextest run ... /// ``` fn video_recording_enabled_for_test(test_name: &str) -> bool { let Ok(value) = std::env::var(video_recorder::VIDEO_ENABLED_ENV_VAR) else { diff --git a/crates/warpui_core/src/integration/video_recorder.rs b/crates/warpui_core/src/integration/video_recorder.rs index 6fb53ee27a..f2184b3d5b 100644 --- a/crates/warpui_core/src/integration/video_recorder.rs +++ b/crates/warpui_core/src/integration/video_recorder.rs @@ -19,12 +19,12 @@ pub const SCREENSHOT_PATH_KEY: &str = "pending_screenshot_path"; /// Environment variable that enables automatic video recording for all /// integration test steps. When set, the driver starts recording at the /// beginning of the test and writes the video on completion. -pub const VIDEO_ENABLED_ENV_VAR: &str = "WARP_INTEGRATION_TEST_VIDEO"; +pub const VIDEO_ENABLED_ENV_VAR: &str = "ZAPLEX_INTEGRATION_TEST_VIDEO"; /// Environment variable that sets the output directory for video recordings /// and screenshots. Defaults to `$TMPDIR/warp_integration_video_captures` when /// unset. -pub const VIDEO_DIR_ENV_VAR: &str = "WARP_INTEGRATION_TEST_VIDEO_DIR"; +pub const VIDEO_DIR_ENV_VAR: &str = "ZAPLEX_INTEGRATION_TEST_VIDEO_DIR"; /// A captured frame paired with the wall-clock time it was taken. pub(super) struct TimestampedFrame { diff --git a/crates/warpui_core/src/notification.rs b/crates/warpui_core/src/notification.rs index ffde29091f..1b9b6d9e28 100644 --- a/crates/warpui_core/src/notification.rs +++ b/crates/warpui_core/src/notification.rs @@ -112,8 +112,8 @@ pub enum NotificationSendError { impl NotificationSendError { pub fn notifications_error_banner_title(&self) -> &str { match self { - NotificationSendError::PermissionsDenied | NotificationSendError::PermissionsNotYetGranted => "Zap tried to send you a notification for the last block but does not have permission.", - NotificationSendError::Other { .. } => "Zap tried to send you a notification for the last block, but something went wrong.", + NotificationSendError::PermissionsDenied | NotificationSendError::PermissionsNotYetGranted => "Zaplex tried to send you a notification for the last block but does not have permission.", + NotificationSendError::Other { .. } => "Zaplex tried to send you a notification for the last block, but something went wrong.", } } } diff --git a/crates/warpui_core/src/platform/keyboard.rs b/crates/warpui_core/src/platform/keyboard.rs index 97b74c058f..0f3716dcdd 100644 --- a/crates/warpui_core/src/platform/keyboard.rs +++ b/crates/warpui_core/src/platform/keyboard.rs @@ -84,7 +84,7 @@ pub enum PhysicalKey { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum KeyCode { /// ` on a US keyboard. This is also called a backtick or grave. - /// This is the 半角/全角/漢字 + /// This is the half-width/full-width/kanji /// (hankaku/zenkaku/kanji) key on Japanese keyboards Backquote, /// Used for both the US \\ (on the 101-key layout) and also for the key @@ -229,18 +229,18 @@ pub enum KeyCode { Space, /// Tab or Tab, - /// Japanese: (henkan) + /// Japanese: convert (henkan) Convert, - /// Japanese: カタカナ/ひらがな/ローマ字 + /// Japanese: katakana/hiragana/romaji /// (katakana/hiragana/romaji) KanaMode, - /// Korean: HangulMode 한/영 (han/yeong) + /// Korean: HangulMode Korean/English (han/yeong) /// - /// Japanese (Mac keyboard): (kana) + /// Japanese (Mac keyboard): kana (kana) Lang1, - /// Korean: Hanja (hanja) + /// Korean: Hanja Korean (hanja) /// - /// Japanese (Mac keyboard): (eisu) + /// Japanese (Mac keyboard): English (eisu) Lang2, /// Japanese (word-processing keyboard): Katakana Lang3, @@ -248,7 +248,7 @@ pub enum KeyCode { Lang4, /// Japanese (word-processing keyboard): Zenkaku/Hankaku Lang5, - /// Japanese: 無変換 (muhenkan) + /// Japanese: no conversion (muhenkan) NonConvert, /// . The forward delete key. /// Note that on Apple keyboards, the key labelled Delete on the main part of @@ -428,9 +428,9 @@ pub enum KeyCode { Select, /// Found on Sun’s USB keyboard. Undo, - /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. + /// Use for dedicated hiragana key found on some Japanese word processing keyboards. Hiragana, - /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. + /// Use for dedicated katakana key found on some Japanese word processing keyboards. Katakana, /// General-purpose function key. /// Usually found at the top of the keyboard. diff --git a/crates/warpui_core/src/windowing/state.rs b/crates/warpui_core/src/windowing/state.rs index 3f889231fa..ce44f65419 100644 --- a/crates/warpui_core/src/windowing/state.rs +++ b/crates/warpui_core/src/windowing/state.rs @@ -44,7 +44,7 @@ impl Display for ApplicationStage { pub struct State { /// The current stage of the app. pub stage: ApplicationStage, - /// The [`WindowId`] of the currently active (frontmost) window. If Zap goes out of focus, + /// The [`WindowId`] of the currently active (frontmost) window. If Zaplex goes out of focus, /// this will go back to None. pub active_window: Option, /// A stack of [`WindowId`]s which had been active before. diff --git a/crates/warpui_extras/src/secure_storage/mod.rs b/crates/warpui_extras/src/secure_storage/mod.rs index 05e9e5c069..c4e9fc7099 100644 --- a/crates/warpui_extras/src/secure_storage/mod.rs +++ b/crates/warpui_extras/src/secure_storage/mod.rs @@ -49,7 +49,7 @@ pub type Model = Box; /// /// The service name is used as a namespace for the application's secrets. It /// is recommended that this be a unique identifier for the application; one -/// common scheme is reverse-DNS notation (e.g.: "dev.warp.Zap"). +/// common scheme is reverse-DNS notation (e.g.: "dev.warp.Zaplex"). #[cfg(not(target_os = "windows"))] pub fn register(service_name: &str, ctx: &mut warpui::AppContext) { ctx.add_singleton_model(|_| -> Model { Box::new(imp::SecureStorage::new(service_name)) }); diff --git a/crates/warpui_extras/src/user_preferences/registry_backed.rs b/crates/warpui_extras/src/user_preferences/registry_backed.rs index b6234f400f..e33e322e6f 100644 --- a/crates/warpui_extras/src/user_preferences/registry_backed.rs +++ b/crates/warpui_extras/src/user_preferences/registry_backed.rs @@ -9,9 +9,9 @@ use windows_result::HRESULT; pub struct RegistryBackedPreferences { app_key_path: String, - /// Caches `HKCU\Software\Zap\` registry Key handle. + /// Caches `HKCU\Software\Zaplex\` registry Key handle. /// - /// Zap startup sequentially calls `read_value` on ~100 settings. + /// Zaplex startup sequentially calls `read_value` on ~100 settings. /// Each `CURRENT_USER.create(...)` call is a ~3ms synchronous system call, /// totaling 300ms+ (dominating the cold-startup `READ_USER_DEFAULTS_AND_INITIALIZE_SETTINGS` phase). /// The first successfully opened Key is cached here; subsequent reads reuse it, @@ -23,13 +23,13 @@ pub struct RegistryBackedPreferences { cached_key: Mutex>, } -static WARP_REGISTRY_BASE_PATH: &str = "Software\\Zap\\"; +static ZAPLEX_REGISTRY_BASE_PATH: &str = "Software\\Zaplex\\"; pub const KEY_NOT_FOUND_ERR: HRESULT = HRESULT::from_win32(0x80070002); impl RegistryBackedPreferences { /// Construct a separate registry path for each channel (stable, dev, local, etc.) pub fn new(app_name: &str) -> Self { - let app_key_path = WARP_REGISTRY_BASE_PATH.to_owned() + app_name; + let app_key_path = ZAPLEX_REGISTRY_BASE_PATH.to_owned() + app_name; // Warm up the Key at startup so the first setting read also avoids synchronous system calls. // Warmup failure is not an error: `with_warp_registry` will retry when needed. let initial_key = CURRENT_USER @@ -44,7 +44,7 @@ impl RegistryBackedPreferences { } } - /// Operates on the cached Zap registry Key via callback. First call invokes + /// Operates on the cached Zaplex registry Key via callback. First call invokes /// `CURRENT_USER.create(...)`; subsequent calls reuse the cached Key. /// If the Key lock is poisoned (previous panic), falls back to a one-time create /// without caching — behavior degrades but does not panic further. @@ -60,7 +60,7 @@ impl RegistryBackedPreferences { let key = CURRENT_USER .create(self.app_key_path.clone()) .map_err(|e| { - log::error!("unable to access Zap app key in Windows Registry: {e:#}"); + log::error!("unable to access Zaplex app key in Windows Registry: {e:#}"); super::Error::IoError(io::Error::from(e)) })?; return f(&key); @@ -71,7 +71,7 @@ impl RegistryBackedPreferences { let key = CURRENT_USER .create(self.app_key_path.clone()) .map_err(|e| { - log::error!("unable to access Zap app key in Windows Registry: {e:#}"); + log::error!("unable to access Zaplex app key in Windows Registry: {e:#}"); super::Error::IoError(io::Error::from(e)) })?; *guard = Some(key); diff --git a/crates/zap_sftp/tests/zap_sftp_error_tests.rs b/crates/zap_sftp/tests/zap_sftp_error_tests.rs index ab005008b9..964db210f0 100644 --- a/crates/zap_sftp/tests/zap_sftp_error_tests.rs +++ b/crates/zap_sftp/tests/zap_sftp_error_tests.rs @@ -13,42 +13,42 @@ use zap_sftp::error::{SftpChannelError, SftpError}; #[test] fn test_sftp_error_connection_failed() { let err = SftpError::ConnectionFailed("host unreachable".to_string()); - assert_eq!(format!("{err}"), "连接失败: host unreachable"); + assert_eq!(format!("{err}"), "Connection failed: host unreachable"); } /// Verify AuthFailed formatting #[test] fn test_sftp_error_auth_failed() { let err = SftpError::AuthFailed("bad password".to_string()); - assert_eq!(format!("{err}"), "认证失败: bad password"); + assert_eq!(format!("{err}"), "Authentication failed: bad password"); } /// Verify Timeout formatting #[test] fn test_sftp_error_timeout() { let err = SftpError::Timeout; - assert_eq!(format!("{err}"), "操作超时"); + assert_eq!(format!("{err}"), "Operation timeout"); } /// Verify NoSuchFile formatting #[test] fn test_sftp_error_no_such_file() { let err = SftpError::NoSuchFile("/tmp/missing.txt".to_string()); - assert_eq!(format!("{err}"), "文件未找到: /tmp/missing.txt"); + assert_eq!(format!("{err}"), "File not found: /tmp/missing.txt"); } /// Verify PermissionDenied formatting #[test] fn test_sftp_error_permission_denied() { let err = SftpError::PermissionDenied("/root/secret".to_string()); - assert_eq!(format!("{err}"), "权限不足: /root/secret"); + assert_eq!(format!("{err}"), "Permission denied: /root/secret"); } /// Verify General formatting #[test] fn test_sftp_error_general() { let err = SftpError::General("something went wrong".to_string()); - assert_eq!(format!("{err}"), "操作失败: something went wrong"); + assert_eq!(format!("{err}"), "Operation failed: something went wrong"); } // ============================================================ @@ -59,14 +59,14 @@ fn test_sftp_error_general() { #[test] fn test_sftp_channel_error_send_failed() { let err = SftpChannelError::SendFailed("channel closed".to_string()); - assert_eq!(format!("{err}"), "发送请求失败: channel closed"); + assert_eq!(format!("{err}"), "Send request failed: channel closed"); } /// Verify RecvFailed formatting #[test] fn test_sftp_channel_error_recv_failed() { let err = SftpChannelError::RecvFailed("timeout".to_string()); - assert_eq!(format!("{err}"), "接收响应失败: timeout"); + assert_eq!(format!("{err}"), "Receive response failed: timeout"); } // ============================================================ @@ -80,7 +80,7 @@ fn test_sftp_channel_error_from_sftp_error() { let channel_err: SftpChannelError = sftp_err.into(); match channel_err { SftpChannelError::Sftp(inner) => { - assert_eq!(format!("{inner}"), "操作失败: inner error"); + assert_eq!(format!("{inner}"), "Operation failed: inner error"); } _ => panic!("expected SftpChannelError::Sftp variant"), } diff --git a/crates/zap_sync/src/crypto.rs b/crates/zap_sync/src/crypto.rs index 1c6ffb489a..df1b2d58f2 100644 --- a/crates/zap_sync/src/crypto.rs +++ b/crates/zap_sync/src/crypto.rs @@ -200,13 +200,13 @@ mod tests { #[test] fn test_crypto_error_display_encrypt() { let err = CryptoError::Encrypt("something went wrong".to_string()); - assert_eq!(format!("{err}"), "加密失败: something went wrong"); + assert_eq!(format!("{err}"), "Encryption failed: something went wrong"); } #[test] fn test_crypto_error_display_decrypt() { let err = CryptoError::Decrypt("bad data".to_string()); - assert_eq!(format!("{err}"), "解密失败: bad data"); + assert_eq!(format!("{err}"), "Decryption failed: bad data"); } #[test] diff --git a/crates/zaplex_cockpit/Cargo.toml b/crates/zaplex_cockpit/Cargo.toml new file mode 100644 index 0000000000..05ca06cd41 --- /dev/null +++ b/crates/zaplex_cockpit/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "zaplex_cockpit" +version = "0.1.0" +edition = "2021" +authors.workspace = true +publish.workspace = true +license.workspace = true +description = "zaplex cockpit data spine: Claude/Codex account discovery + usage/cost/heat aggregation (read-only, never reads secrets)" + +[dependencies] +serde.workspace = true +serde_json.workspace = true +chrono.workspace = true +log.workspace = true +walkdir.workspace = true +base64.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/zaplex_cockpit/src/claude.rs b/crates/zaplex_cockpit/src/claude.rs new file mode 100644 index 0000000000..915d8738b1 --- /dev/null +++ b/crates/zaplex_cockpit/src/claude.rs @@ -0,0 +1,244 @@ +//! Claude Code account discovery + transcript usage parsing. +//! +//! Mirrors `claudeplex` `discover.ts`/`collect.ts`. Reads only account metadata +//! (`oauthAccount`) and per-message token counts — never tokens or message content. + +use std::fs; +use std::path::{Path, PathBuf}; + +use chrono::{DateTime, Utc}; +use serde_json::Value; +use walkdir::WalkDir; + +use crate::types::{Account, Provider, UsageEntry}; + +/// Directory-name fragments that mark a `.claude*` dir as a backup/scratch copy, not +/// a real account (mirrors `claudeplex` discover.ts exclusions). +const EXCLUDE_FRAGMENTS: &[&str] = &["mem", "backup", "bak", "old", "tmp", "temp", "observer"]; + +fn is_excluded(dir_name: &str) -> bool { + EXCLUDE_FRAGMENTS.iter().any(|f| dir_name.contains(f)) +} + +/// Resolve the `.claude.json` identity file for a config dir: prefer `/.claude.json`, +/// and for the default `~/.claude` fall back to `~/.claude.json` (the CLI's home file). +fn identity_json(config_dir: &Path, home: &Path, is_default: bool) -> Option { + let inside = config_dir.join(".claude.json"); + if inside.is_file() { + return Some(inside); + } + if is_default { + let home_file = home.join(".claude.json"); + if home_file.is_file() { + return Some(home_file); + } + } + None +} + +/// A config dir qualifies as an account if it has an identity file or a +/// `projects/`/`sessions/` subdir. +fn dir_qualifies(config_dir: &Path, home: &Path, is_default: bool) -> bool { + identity_json(config_dir, home, is_default).is_some() + || config_dir.join("projects").is_dir() + || config_dir.join("sessions").is_dir() +} + +/// Stable account key from the config dir, e.g. `claude:default`, `claude:work`. +fn account_key(config_dir: &Path, is_default: bool) -> String { + if is_default { + return "claude:default".to_string(); + } + let name = config_dir + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("account"); + // ".claude-work" → "work"; otherwise the raw dir name. + let suffix = name + .strip_prefix(".claude-") + .or_else(|| name.strip_prefix(".claude")) + .filter(|s| !s.is_empty()) + .unwrap_or(name); + format!("claude:{suffix}") +} + +/// Derive a plan label from `organizationRateLimitTier` / `organizationType`. +fn plan_label(rate_tier: Option<&str>, org_type: Option<&str>) -> Option { + if let Some(tier) = rate_tier { + if let Some(rest) = tier.strip_prefix("max_") { + // "max_20x" → "Max 20x" + return Some(format!("Max {rest}")); + } + } + if org_type == Some("claude_max") { + return Some("Max".to_string()); + } + org_type.map(|t| t.strip_prefix("claude_").unwrap_or(t).to_string()) +} + +/// Build an [`Account`] from a config dir + its identity JSON. Returns `None` if the +/// dir does not represent a real account (no `oauthAccount` and no transcripts). +fn account_from_dir(config_dir: &Path, home: &Path, is_default: bool) -> Option { + if !dir_qualifies(config_dir, home, is_default) { + return None; + } + let oauth = identity_json(config_dir, home, is_default) + .and_then(|p| fs::read_to_string(p).ok()) + .and_then(|s| serde_json::from_str::(&s).ok()) + .and_then(|v| v.get("oauthAccount").cloned()); + + let s = |v: &Value, k: &str| v.get(k).and_then(|x| x.as_str()).map(|x| x.to_string()); + let (email, display, org, role, plan) = match &oauth { + Some(o) => ( + s(o, "emailAddress"), + s(o, "displayName"), + s(o, "organizationName"), + s(o, "organizationRole"), + plan_label( + o.get("organizationRateLimitTier").and_then(|x| x.as_str()), + o.get("organizationType").and_then(|x| x.as_str()), + ), + ), + None => (None, None, None, None, None), + }; + + let label = email + .clone() + .or_else(|| display.clone()) + .or_else(|| org.clone()) + .unwrap_or_else(|| account_key(config_dir, is_default)); + + Some(Account { + provider: Provider::Claude, + key: account_key(config_dir, is_default), + config_dir: config_dir.to_path_buf(), + label, + email, + org, + role, + plan_tier: plan, + is_default, + }) +} + +/// Discover Claude accounts: the default `~/.claude`, any `~/.claude-*` config dirs, +/// and `$CLAUDE_CONFIG_DIR`. (A process scan for live non-default dirs — discover.ts — +/// is deferred; see the design doc.) +pub fn discover_accounts(home: &Path, config_dir_env: Option<&str>) -> Vec { + let mut candidates: Vec<(PathBuf, bool)> = Vec::new(); + candidates.push((home.join(".claude"), true)); + + if let Ok(read) = fs::read_dir(home) { + for entry in read.flatten() { + let name = entry.file_name(); + let Some(name) = name.to_str() else { continue }; + if name == ".claude" || !name.starts_with(".claude") { + continue; + } + // .claude-* dirs (skip the plain-file ~/.claude.json and excluded copies). + if is_excluded(name) || !entry.path().is_dir() { + continue; + } + candidates.push((entry.path(), false)); + } + } + + if let Some(env_dir) = config_dir_env.filter(|d| !d.is_empty()) { + let p = PathBuf::from(env_dir); + let is_default = p == home.join(".claude"); + if !candidates.iter().any(|(c, _)| *c == p) { + candidates.push((p, is_default)); + } + } + + let mut accounts = Vec::new(); + let mut seen = std::collections::HashSet::new(); + for (dir, is_default) in candidates { + if !seen.insert(dir.clone()) { + continue; + } + if let Some(acct) = account_from_dir(&dir, home, is_default) { + accounts.push(acct); + } + } + accounts +} + +/// Extract a [`UsageEntry`] from one parsed transcript line, or `None` if the line +/// is not an assistant turn with usage. Reads counts + model + timestamp only. +fn parse_line(v: &Value) -> Option { + if v.get("type")?.as_str()? != "assistant" { + return None; + } + let message = v.get("message"); + let usage = message + .and_then(|m| m.get("usage")) + .or_else(|| v.get("usage"))?; + let model = message + .and_then(|m| m.get("model")) + .or_else(|| v.get("model")) + .and_then(|m| m.as_str()) + .unwrap_or("unknown") + .to_string(); + let ts_str = v.get("timestamp").and_then(|t| t.as_str())?; + let ts = DateTime::parse_from_rfc3339(ts_str) + .ok()? + .with_timezone(&Utc); + let n = |k: &str| usage.get(k).and_then(|x| x.as_u64()).unwrap_or(0); + Some(UsageEntry { + ts, + provider: Provider::Claude, + model, + input: n("input_tokens"), + output: n("output_tokens"), + cache_create: n("cache_creation_input_tokens"), + cache_read: n("cache_read_input_tokens"), + reasoning: 0, + }) +} + +/// Parse a single Claude `.jsonl` transcript into usage entries (skips malformed lines). +pub fn parse_transcript(path: &Path) -> Vec { + let Ok(content) = fs::read_to_string(path) else { + return Vec::new(); + }; + content + .lines() + .filter(|l| !l.trim().is_empty()) + .filter_map(|l| serde_json::from_str::(l).ok()) + .filter_map(|v| parse_line(&v)) + .collect() +} + +/// All usage entries for an account with a transcript mtime at or after `since` +/// (widest window cutoff). Walks `/projects/**/*.jsonl`. +pub fn usage_for_account(account: &Account, since: DateTime) -> Vec { + let projects = account.config_dir.join("projects"); + let mut entries = Vec::new(); + for file in WalkDir::new(&projects) + .into_iter() + .flatten() + .filter(|e| e.file_type().is_file()) + .filter(|e| e.path().extension().and_then(|x| x.to_str()) == Some("jsonl")) + { + // Cheap mtime prefilter: skip transcripts untouched since the cutoff. + if let Ok(meta) = file.metadata() { + if let Ok(modified) = meta.modified() { + let modified: DateTime = modified.into(); + if modified < since { + continue; + } + } + } + entries.extend( + parse_transcript(file.path()) + .into_iter() + .filter(|e| e.ts >= since), + ); + } + entries +} + +#[cfg(test)] +#[path = "claude_tests.rs"] +mod tests; diff --git a/crates/zaplex_cockpit/src/claude_tests.rs b/crates/zaplex_cockpit/src/claude_tests.rs new file mode 100644 index 0000000000..19ea2a868e --- /dev/null +++ b/crates/zaplex_cockpit/src/claude_tests.rs @@ -0,0 +1,115 @@ +use super::*; +use std::fs; + +fn write(path: &Path, content: &str) { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, content).unwrap(); +} + +#[test] +fn discovers_default_and_alt_accounts_excludes_backups() { + let tmp = tempfile::tempdir().unwrap(); + let home = tmp.path(); + + // Default account: ~/.claude.json (home-level) + ~/.claude/projects/… + write( + &home.join(".claude.json"), + r#"{"oauthAccount":{"emailAddress":"me@example.com","displayName":"Me", + "organizationName":"Acme","organizationRole":"admin", + "organizationType":"claude_max","organizationRateLimitTier":"max_20x"}, + "accessToken":"SHOULD-NEVER-BE-SURFACED"}"#, + ); + fs::create_dir_all(home.join(".claude/projects")).unwrap(); + + // Alt account: ~/.claude-work/.claude.json + write( + &home.join(".claude-work/.claude.json"), + r#"{"oauthAccount":{"emailAddress":"work@example.com","organizationType":"claude_team"}}"#, + ); + + // Backup dir must be excluded. + write( + &home.join(".claude-backup/.claude.json"), + r#"{"oauthAccount":{"emailAddress":"nope@example.com"}}"#, + ); + + let accounts = discover_accounts(home, None); + assert_eq!(accounts.len(), 2, "default + work, backup excluded: {accounts:?}"); + + let default = accounts.iter().find(|a| a.is_default).unwrap(); + assert_eq!(default.key, "claude:default"); + assert_eq!(default.email.as_deref(), Some("me@example.com")); + assert_eq!(default.org.as_deref(), Some("Acme")); + assert_eq!(default.role.as_deref(), Some("admin")); + assert_eq!(default.plan_tier.as_deref(), Some("Max 20x")); + assert_eq!(default.label, "me@example.com"); + + let work = accounts.iter().find(|a| !a.is_default).unwrap(); + assert_eq!(work.key, "claude:work"); + assert_eq!(work.email.as_deref(), Some("work@example.com")); + assert_eq!(work.plan_tier.as_deref(), Some("team")); // "claude_team" → "team" +} + +#[test] +fn parse_transcript_extracts_only_assistant_usage() { + let tmp = tempfile::tempdir().unwrap(); + let path = tmp.path().join("session.jsonl"); + write( + &path, + concat!( + r#"{"type":"user","timestamp":"2026-06-30T09:59:00Z"}"#, + "\n", + r#"{"type":"assistant","timestamp":"2026-06-30T10:00:00Z","message":{"model":"claude-opus-4-8","usage":{"input_tokens":100,"output_tokens":20,"cache_creation_input_tokens":10,"cache_read_input_tokens":5}}}"#, + "\n", + "not json at all\n", + r#"{"type":"assistant","timestamp":"2026-06-30T10:05:00Z","message":{"model":"claude-sonnet-4-6","usage":{"input_tokens":50,"output_tokens":10}}}"#, + "\n", + ), + ); + + let entries = parse_transcript(&path); + assert_eq!(entries.len(), 2, "two assistant turns, user + junk skipped"); + + let first = &entries[0]; + assert_eq!(first.model, "claude-opus-4-8"); + assert_eq!(first.input, 100); + assert_eq!(first.output, 20); + assert_eq!(first.cache_create, 10); + assert_eq!(first.cache_read, 5); + assert_eq!(first.reasoning, 0); + assert_eq!(first.provider, Provider::Claude); + + let second = &entries[1]; + assert_eq!(second.model, "claude-sonnet-4-6"); + assert_eq!(second.cache_create, 0); // missing fields default to 0 +} + +#[test] +fn usage_for_account_respects_the_since_cutoff() { + let tmp = tempfile::tempdir().unwrap(); + let home = tmp.path(); + write( + &home.join(".claude.json"), + r#"{"oauthAccount":{"emailAddress":"me@example.com"}}"#, + ); + write( + &home.join(".claude/projects/p/s.jsonl"), + concat!( + r#"{"type":"assistant","timestamp":"2026-06-01T10:00:00Z","message":{"model":"claude-opus-4-8","usage":{"input_tokens":1}}}"#, + "\n", + r#"{"type":"assistant","timestamp":"2026-06-30T10:00:00Z","message":{"model":"claude-opus-4-8","usage":{"input_tokens":2}}}"#, + "\n", + ), + ); + + let account = discover_accounts(home, None) + .into_iter() + .find(|a| a.is_default) + .unwrap(); + let since = DateTime::parse_from_rfc3339("2026-06-15T00:00:00Z") + .unwrap() + .with_timezone(&Utc); + let entries = usage_for_account(&account, since); + assert_eq!(entries.len(), 1, "only the 06-30 entry passes the cutoff"); + assert_eq!(entries[0].input, 2); +} diff --git a/crates/zaplex_cockpit/src/codex.rs b/crates/zaplex_cockpit/src/codex.rs new file mode 100644 index 0000000000..0927f9e8fc --- /dev/null +++ b/crates/zaplex_cockpit/src/codex.rs @@ -0,0 +1,198 @@ +//! Codex account discovery + session (rollout) usage parsing. +//! +//! Net-new (no `claudeplex` prior art); the exact on-disk schema is only partly +//! confirmed (design doc §10), so parsing is deliberately **defensive**: it searches +//! each JSONL line for a token-usage object rather than assuming a fixed path. +//! +//! Privacy: reads `auth.json` only for `auth_mode` and decodes the **unverified** +//! `id_token` JWT payload for an `email` claim. Token strings are never stored. + +use std::fs; +use std::path::Path; + +use base64::Engine; +use chrono::{DateTime, Utc}; +use serde_json::Value; +use walkdir::WalkDir; + +use crate::types::{Account, Provider, UsageEntry}; + +/// Recursively find the first sub-value under `key` anywhere in `v`. +fn find<'a>(v: &'a Value, key: &str) -> Option<&'a Value> { + match v { + Value::Object(map) => { + if let Some(found) = map.get(key) { + return Some(found); + } + map.values().find_map(|val| find(val, key)) + } + Value::Array(arr) => arr.iter().find_map(|val| find(val, key)), + _ => None, + } +} + +/// Decode the (unverified) payload of a JWT and return its claims object. Never used +/// for auth — only to read a display `email` claim. Returns `None` on any malformation. +fn jwt_payload(token: &str) -> Option { + let payload_b64 = token.split('.').nth(1)?; + let bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(payload_b64) + .ok()?; + serde_json::from_slice(&bytes).ok() +} + +/// Discover the Codex account from `/auth.json`. Codex multi-account is +/// unconfirmed (design §10), so Increment 1 treats it as a single account. +pub fn discover_accounts(codex_home: &Path) -> Vec { + let auth_path = codex_home.join("auth.json"); + let Ok(raw) = fs::read_to_string(&auth_path) else { + return Vec::new(); + }; + let Ok(auth) = serde_json::from_str::(&raw) else { + return Vec::new(); + }; + + let auth_mode = auth + .get("auth_mode") + .and_then(|x| x.as_str()) + .map(|s| s.to_string()); + + // Email from the id_token JWT payload (best-effort; token itself is never stored). + let email = auth + .get("tokens") + .and_then(|t| t.get("id_token")) + .and_then(|x| x.as_str()) + .and_then(jwt_payload) + .and_then(|claims| { + claims + .get("email") + .and_then(|e| e.as_str()) + .map(|s| s.to_string()) + }); + + let label = email + .clone() + .or_else(|| auth_mode.clone()) + .unwrap_or_else(|| "codex".to_string()); + + vec![Account { + provider: Provider::Codex, + key: "codex:default".to_string(), + config_dir: codex_home.to_path_buf(), + label, + email, + org: None, + role: None, + plan_tier: auth_mode, // best-effort until plan claim is confirmed (§10) + is_default: true, + }] +} + +/// Read `input_tokens` / `output_tokens` / `cached_input_tokens` / +/// `reasoning_output_tokens` from a token-usage object. +fn tokens_from(obj: &Value) -> (u64, u64, u64, u64) { + let n = |k: &str| obj.get(k).and_then(|x| x.as_u64()).unwrap_or(0); + ( + n("input_tokens"), + n("output_tokens"), + n("cached_input_tokens"), + n("reasoning_output_tokens"), + ) +} + +/// Parse a Codex `rollout-*.jsonl` session into per-turn usage entries. +/// +/// Sums **per-turn** deltas (`last_token_usage`) to avoid double-counting the +/// cumulative `total_token_usage` envelope. `file_date` (from the `YYYY/MM/DD` path) +/// is the timestamp fallback when a line carries none. +pub fn parse_transcript(path: &Path, file_date: DateTime) -> Vec { + let Ok(content) = fs::read_to_string(path) else { + return Vec::new(); + }; + let mut current_model = String::from("unknown"); + let mut current_ts = file_date; + let mut entries = Vec::new(); + + for line in content.lines().filter(|l| !l.trim().is_empty()) { + let Ok(v) = serde_json::from_str::(line) else { + continue; + }; + if let Some(m) = find(&v, "model").and_then(|x| x.as_str()) { + current_model = m.to_string(); + } + if let Some(ts) = find(&v, "timestamp") + .and_then(|x| x.as_str()) + .and_then(|s| DateTime::parse_from_rfc3339(s).ok()) + { + current_ts = ts.with_timezone(&Utc); + } + // Per-turn usage; ignore cumulative `total_token_usage` to avoid double counts. + if let Some(usage) = find(&v, "last_token_usage") { + let (input, output, cached, reasoning) = tokens_from(usage); + if input + output + cached + reasoning > 0 { + entries.push(UsageEntry { + ts: current_ts, + provider: Provider::Codex, + model: current_model.clone(), + input, + output, + cache_create: 0, // Codex has no separate cache-write concept + cache_read: cached, + reasoning, + }); + } + } + } + entries +} + +/// Derive a coarse timestamp (midday UTC) from a `sessions/YYYY/MM/DD/` path, used as +/// the fallback when a rollout line carries no timestamp. +fn date_from_path(path: &Path) -> Option> { + let comps: Vec<&str> = path + .components() + .filter_map(|c| c.as_os_str().to_str()) + .collect(); + let pos = comps.iter().position(|c| *c == "sessions")?; + let y: i32 = comps.get(pos + 1)?.parse().ok()?; + let m: u32 = comps.get(pos + 2)?.parse().ok()?; + let d: u32 = comps.get(pos + 3)?.parse().ok()?; + chrono::NaiveDate::from_ymd_opt(y, m, d)? + .and_hms_opt(12, 0, 0) + .map(|naive| DateTime::from_naive_utc_and_offset(naive, Utc)) +} + +/// All Codex usage entries newer than `since`, from `/sessions/**/rollout-*.jsonl`. +pub fn usage_for_account(account: &Account, since: DateTime) -> Vec { + let sessions = account.config_dir.join("sessions"); + let mut entries = Vec::new(); + for file in WalkDir::new(&sessions) + .into_iter() + .flatten() + .filter(|e| e.file_type().is_file()) + { + let name = file.file_name().to_str().unwrap_or(""); + if !(name.starts_with("rollout-") && name.ends_with(".jsonl")) { + continue; + } + if let Ok(meta) = file.metadata() { + if let Ok(modified) = meta.modified() { + let modified: DateTime = modified.into(); + if modified < since { + continue; + } + } + } + let file_date = date_from_path(file.path()).unwrap_or(since); + entries.extend( + parse_transcript(file.path(), file_date) + .into_iter() + .filter(|e| e.ts >= since), + ); + } + entries +} + +#[cfg(test)] +#[path = "codex_tests.rs"] +mod tests; diff --git a/crates/zaplex_cockpit/src/codex_tests.rs b/crates/zaplex_cockpit/src/codex_tests.rs new file mode 100644 index 0000000000..6e0148f340 --- /dev/null +++ b/crates/zaplex_cockpit/src/codex_tests.rs @@ -0,0 +1,109 @@ +use super::*; +use base64::Engine; +use std::fs; + +fn write(path: &Path, content: &str) { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, content).unwrap(); +} + +fn fake_jwt(payload_json: &str) -> String { + let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload_json.as_bytes()); + format!("header.{payload}.signature") +} + +#[test] +fn discovers_account_and_reads_email_from_id_token_without_storing_tokens() { + let tmp = tempfile::tempdir().unwrap(); + let codex_home = tmp.path(); + let jwt = fake_jwt(r#"{"email":"c@example.com","chatgpt_plan_type":"pro"}"#); + write( + &codex_home.join("auth.json"), + &format!( + r#"{{"auth_mode":"chatgpt","tokens":{{"account_id":"acc_1","id_token":"{jwt}","access_token":"SECRET","refresh_token":"SECRET"}}}}"# + ), + ); + + let accounts = discover_accounts(codex_home); + assert_eq!(accounts.len(), 1); + let a = &accounts[0]; + assert_eq!(a.provider, Provider::Codex); + assert_eq!(a.key, "codex:default"); + assert_eq!(a.email.as_deref(), Some("c@example.com")); + assert_eq!(a.label, "c@example.com"); + assert_eq!(a.plan_tier.as_deref(), Some("chatgpt")); // auth_mode, best-effort + assert!(a.is_default); +} + +#[test] +fn missing_auth_json_yields_no_accounts() { + let tmp = tempfile::tempdir().unwrap(); + assert!(discover_accounts(tmp.path()).is_empty()); +} + +#[test] +fn parse_transcript_sums_per_turn_and_ignores_cumulative_envelope() { + let tmp = tempfile::tempdir().unwrap(); + let path = tmp.path().join("rollout-x.jsonl"); + write( + &path, + concat!( + r#"{"type":"turn_context","model":"gpt-5-codex","timestamp":"2026-06-30T10:00:00Z"}"#, + "\n", + // last_token_usage nested under "info" — exercises the recursive finder. + r#"{"type":"event_msg","timestamp":"2026-06-30T10:01:00Z","info":{"last_token_usage":{"input_tokens":200,"output_tokens":40,"cached_input_tokens":15,"reasoning_output_tokens":30}}}"#, + "\n", + // cumulative envelope must be ignored to avoid double-counting. + r#"{"type":"event_msg","timestamp":"2026-06-30T10:02:00Z","total_token_usage":{"input_tokens":999,"output_tokens":999}}"#, + "\n", + ), + ); + let file_date = DateTime::parse_from_rfc3339("2026-06-30T12:00:00Z") + .unwrap() + .with_timezone(&Utc); + + let entries = parse_transcript(&path, file_date); + assert_eq!(entries.len(), 1, "only the per-turn usage line counts"); + let e = &entries[0]; + assert_eq!(e.model, "gpt-5-codex"); + assert_eq!(e.input, 200); + assert_eq!(e.output, 40); + assert_eq!(e.cache_read, 15); + assert_eq!(e.reasoning, 30); + assert_eq!(e.cache_create, 0); + assert_eq!( + e.ts, + DateTime::parse_from_rfc3339("2026-06-30T10:01:00Z") + .unwrap() + .with_timezone(&Utc) + ); +} + +#[test] +fn usage_for_account_walks_sessions_tree() { + let tmp = tempfile::tempdir().unwrap(); + let codex_home = tmp.path(); + let jwt = fake_jwt(r#"{"email":"c@example.com"}"#); + write( + &codex_home.join("auth.json"), + &format!(r#"{{"auth_mode":"chatgpt","tokens":{{"id_token":"{jwt}"}}}}"#), + ); + write( + &codex_home.join("sessions/2026/06/30/rollout-abc.jsonl"), + concat!( + r#"{"type":"turn_context","model":"gpt-5-codex","timestamp":"2026-06-30T10:00:00Z"}"#, + "\n", + r#"{"type":"event_msg","timestamp":"2026-06-30T10:01:00Z","last_token_usage":{"input_tokens":10,"output_tokens":5}}"#, + "\n", + ), + ); + + let account = discover_accounts(codex_home).into_iter().next().unwrap(); + let since = DateTime::parse_from_rfc3339("2026-06-01T00:00:00Z") + .unwrap() + .with_timezone(&Utc); + let entries = usage_for_account(&account, since); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].input, 10); + assert_eq!(entries[0].provider, Provider::Codex); +} diff --git a/crates/zaplex_cockpit/src/format.rs b/crates/zaplex_cockpit/src/format.rs new file mode 100644 index 0000000000..aa357a13a0 --- /dev/null +++ b/crates/zaplex_cockpit/src/format.rs @@ -0,0 +1,106 @@ +//! Pure display formatting shared by the cockpit sidebar + pane: heat band/colour, +//! cost, humanized tokens, reset countdown. Warpui-free and fully unit-tested, so the +//! visual layers stay thin and the numbers-to-text logic is verified headlessly. + +use chrono::{DateTime, Utc}; + +/// Heat band, matching the claudeplex-desktop `LoadBar` thresholds. Input is the heat +/// *fraction* (work / budget), where 1.0 == 100% of budget. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HeatLevel { + /// < 35% + Ok, + /// 35–60% + Elevated, + /// 60–85% + High, + /// 85–100% + Critical, + /// >= 100% (over budget) + Over, +} + +impl HeatLevel { + pub fn from_fraction(fraction: f64) -> Self { + let pct = fraction * 100.0; + if pct >= 100.0 { + HeatLevel::Over + } else if pct >= 85.0 { + HeatLevel::Critical + } else if pct >= 60.0 { + HeatLevel::High + } else if pct >= 35.0 { + HeatLevel::Elevated + } else { + HeatLevel::Ok + } + } + + /// Reference hex from claudeplex `LoadBar` (green→red). The app maps this to a + /// `ColorU`; kept here so the palette is single-sourced + testable. + pub fn hex(self) -> &'static str { + match self { + HeatLevel::Ok => "#22c55e", + HeatLevel::Elevated => "#eab308", + HeatLevel::High => "#fb923c", + HeatLevel::Critical => "#f97316", + HeatLevel::Over => "#ef4444", + } + } +} + +/// Bar-fill fraction, clamped to 0..=1 (the % *label* may exceed 100%, the bar can't). +pub fn heat_fill(fraction: f64) -> f64 { + fraction.clamp(0.0, 1.0) +} + +/// Rounded percent label (not clamped): 0.62 -> "62%", 1.3 -> "130%". +pub fn heat_pct_label(fraction: f64) -> String { + format!("{}%", (fraction * 100.0).round() as i64) +} + +/// USD cost with 2 decimals: 4.2 -> "$4.20". +pub fn format_cost(usd: f64) -> String { + format!("${usd:.2}") +} + +/// Humanized token count: 42 -> "42", 3400 -> "3.4k", 300000 -> "300k", +/// 1_200_000 -> "1.2M", 6_000_000 -> "6M". +pub fn format_tokens(n: u64) -> String { + let (val, unit) = if n >= 1_000_000 { + (n as f64 / 1_000_000.0, "M") + } else if n >= 1_000 { + (n as f64 / 1_000.0, "k") + } else { + return n.to_string(); + }; + let s = format!("{val:.1}"); + let s = s.strip_suffix(".0").unwrap_or(&s); + format!("{s}{unit}") +} + +/// Relative reset countdown (claudeplex `resetIn`): "45m", "2h13m", "4d1h"; +/// "resetting" once past; "" when there is no active window. +pub fn format_reset(reset: Option>, now: DateTime) -> String { + let Some(reset) = reset else { + return String::new(); + }; + let ms = (reset - now).num_milliseconds(); + if ms <= 0 { + return "resetting".to_string(); + } + let total_min = ((ms as f64) / 60_000.0).round() as i64; + if total_min < 60 { + return format!("{total_min}m"); + } + let h = total_min / 60; + if h < 24 { + return format!("{}h{}m", h, total_min % 60); + } + let d = h / 24; + format!("{}d{}h", d, h % 24) +} + +#[cfg(test)] +#[path = "format_tests.rs"] +mod tests; diff --git a/crates/zaplex_cockpit/src/format_tests.rs b/crates/zaplex_cockpit/src/format_tests.rs new file mode 100644 index 0000000000..20f8460f3a --- /dev/null +++ b/crates/zaplex_cockpit/src/format_tests.rs @@ -0,0 +1,62 @@ +use super::*; +use chrono::{DateTime, Utc}; + +fn ts(s: &str) -> DateTime { + DateTime::parse_from_rfc3339(s).unwrap().with_timezone(&Utc) +} + +#[test] +fn heat_level_thresholds() { + assert_eq!(HeatLevel::from_fraction(0.0), HeatLevel::Ok); + assert_eq!(HeatLevel::from_fraction(0.34), HeatLevel::Ok); + assert_eq!(HeatLevel::from_fraction(0.35), HeatLevel::Elevated); + assert_eq!(HeatLevel::from_fraction(0.59), HeatLevel::Elevated); + assert_eq!(HeatLevel::from_fraction(0.60), HeatLevel::High); + assert_eq!(HeatLevel::from_fraction(0.84), HeatLevel::High); + assert_eq!(HeatLevel::from_fraction(0.85), HeatLevel::Critical); + assert_eq!(HeatLevel::from_fraction(0.99), HeatLevel::Critical); + assert_eq!(HeatLevel::from_fraction(1.0), HeatLevel::Over); + assert_eq!(HeatLevel::from_fraction(2.5), HeatLevel::Over); +} + +#[test] +fn heat_hex_matches_reference_palette() { + assert_eq!(HeatLevel::Ok.hex(), "#22c55e"); + assert_eq!(HeatLevel::Over.hex(), "#ef4444"); +} + +#[test] +fn heat_fill_clamps_but_label_does_not() { + assert_eq!(heat_fill(0.5), 0.5); + assert_eq!(heat_fill(1.3), 1.0); + assert_eq!(heat_fill(-0.2), 0.0); + assert_eq!(heat_pct_label(1.3), "130%"); + assert_eq!(heat_pct_label(0.615), "62%"); +} + +#[test] +fn cost_format() { + assert_eq!(format_cost(4.2), "$4.20"); + assert_eq!(format_cost(0.0), "$0.00"); + assert_eq!(format_cost(19.005), "$19.00"); +} + +#[test] +fn token_humanization() { + assert_eq!(format_tokens(42), "42"); + assert_eq!(format_tokens(999), "999"); + assert_eq!(format_tokens(3_400), "3.4k"); + assert_eq!(format_tokens(300_000), "300k"); + assert_eq!(format_tokens(1_200_000), "1.2M"); + assert_eq!(format_tokens(6_000_000), "6M"); +} + +#[test] +fn reset_countdown() { + let now = ts("2026-06-30T12:00:00Z"); + assert_eq!(format_reset(None, now), ""); + assert_eq!(format_reset(Some(ts("2026-06-30T11:59:00Z")), now), "resetting"); + assert_eq!(format_reset(Some(ts("2026-06-30T12:45:00Z")), now), "45m"); + assert_eq!(format_reset(Some(ts("2026-06-30T14:13:00Z")), now), "2h13m"); + assert_eq!(format_reset(Some(ts("2026-07-04T13:00:00Z")), now), "4d1h"); +} diff --git a/crates/zaplex_cockpit/src/lib.rs b/crates/zaplex_cockpit/src/lib.rs new file mode 100644 index 0000000000..8860707fd9 --- /dev/null +++ b/crates/zaplex_cockpit/src/lib.rs @@ -0,0 +1,65 @@ +//! zaplex cockpit — the read-only **data spine** for the "plex" half of the product. +//! +//! Discovers Claude Code + Codex accounts/subscriptions, aggregates their own +//! transcript token usage into rolling windows (5h block / today / week), and +//! derives cost (per-model pricing) and heat (load vs. budget). +//! +//! This crate is a **pure, headless-testable data layer**: no GUI, no network, and — +//! a hard privacy invariant — it reads only **token counts and account metadata**, +//! never token strings or transcript content. The `CockpitModel` / file-watch wiring +//! that surfaces this into the app lives in `app/src/cockpit/`. +//! +//! See `docs/superpowers/specs/2026-06-30-cockpit-increment1-account-usage-design.md`. + +pub mod claude; +pub mod codex; +pub mod format; +pub mod pricing; +pub mod types; +pub mod windows; + +pub use format::{ + format_cost, format_reset, format_tokens, heat_fill, heat_pct_label, HeatLevel, +}; +pub use pricing::{ModelPrice, PricingTable}; +pub use types::{Account, AccountUsage, CockpitSnapshot, Provider, UsageEntry, WindowTotals}; +pub use windows::{ + build_account_usage, window_5h, window_week, DEFAULT_BUDGET_5H, DEFAULT_BUDGET_WEEK, +}; + +use std::path::Path; + +use chrono::{DateTime, Utc}; + +/// Build a full cockpit snapshot from disk: discover Claude + Codex accounts, parse +/// their transcripts within the widest (week) window, and aggregate per-account +/// usage / cost / heat. +/// +/// `now` is explicit so windowing is deterministic and testable. `budget_5h` sizes +/// heat (0 = disable heat). This is the crate's single I/O entry point; the app's +/// `CockpitModel` calls it off the main thread on file-watch/reconcile ticks. +pub fn build_snapshot( + home: &Path, + codex_home: &Path, + claude_config_dir_env: Option<&str>, + now: DateTime, + budget_5h: u64, + pricing: &PricingTable, +) -> CockpitSnapshot { + let since = now - window_week(); + let mut accounts = Vec::new(); + + for account in claude::discover_accounts(home, claude_config_dir_env) { + let entries = claude::usage_for_account(&account, since); + accounts.push(build_account_usage(account, entries, now, budget_5h, pricing)); + } + for account in codex::discover_accounts(codex_home) { + let entries = codex::usage_for_account(&account, since); + accounts.push(build_account_usage(account, entries, now, budget_5h, pricing)); + } + + CockpitSnapshot { + accounts, + generated_at: now, + } +} diff --git a/crates/zaplex_cockpit/src/pricing.rs b/crates/zaplex_cockpit/src/pricing.rs new file mode 100644 index 0000000000..d708239d41 --- /dev/null +++ b/crates/zaplex_cockpit/src/pricing.rs @@ -0,0 +1,99 @@ +//! Per-model pricing table for deriving cost from token counts. +//! +//! **Approximation, by design.** LLM prices drift; this table is centralized and +//! overridable, and unknown models fall back to a *logged* zero (never silently +//! mispriced). Rates are USD per 1,000,000 tokens. Verify/refresh against current +//! Anthropic + OpenAI pricing when models change (see the Increment 1 design doc §5). + +use serde::{Deserialize, Serialize}; + +/// Price of one model, USD per 1M tokens, with distinct cache rates. +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct ModelPrice { + pub input: f64, + pub output: f64, + /// Cost to *write* the prompt cache (Claude 5m ephemeral rate). 0 where N/A. + pub cache_write: f64, + /// Cost to *read* from the prompt cache. + pub cache_read: f64, +} + +/// A model-name-substring → price table, matched case-insensitively, first match +/// wins (order entries specific → general). +#[derive(Clone, Debug)] +pub struct PricingTable { + entries: Vec<(String, ModelPrice)>, +} + +impl PricingTable { + /// Build a table from `(substring, price)` pairs (already ordered specific→general). + pub fn new(entries: Vec<(String, ModelPrice)>) -> Self { + Self { entries } + } + + /// Look up the price whose key is a case-insensitive substring of `model`. + pub fn price_for(&self, model: &str) -> Option { + let m = model.to_ascii_lowercase(); + self.entries + .iter() + .find(|(key, _)| m.contains(key.as_str())) + .map(|(_, price)| *price) + } + + /// Cost in USD for one turn's tokens. Reasoning tokens (Codex) bill as output. + /// Unknown models cost 0 and are logged at debug level (never silently mispriced). + pub fn cost_for( + &self, + model: &str, + input: u64, + output: u64, + cache_create: u64, + cache_read: u64, + reasoning: u64, + ) -> f64 { + let Some(p) = self.price_for(model) else { + log::debug!("zaplex_cockpit: no pricing for model {model:?}; costing as $0"); + return 0.0; + }; + (input as f64 * p.input + + (output + reasoning) as f64 * p.output + + cache_create as f64 * p.cache_write + + cache_read as f64 * p.cache_read) + / 1_000_000.0 + } +} + +impl Default for PricingTable { + /// Seeded from current Anthropic + OpenAI list prices (standard tier). Keys are + /// lowercase substrings matched against the transcript `model` field. + /// + /// TODO(pricing): refresh on model launches; verify OpenAI/Codex rates (their + /// transcripts + `~/.codex/models_cache.json` carry no price fields). + fn default() -> Self { + let m = |input, output, cache_write, cache_read| ModelPrice { + input, + output, + cache_write, + cache_read, + }; + // Order matters: more specific keys first. + Self::new(vec![ + // --- Anthropic (Claude) --- + ("opus".into(), m(15.0, 75.0, 18.75, 1.50)), + ("sonnet".into(), m(3.0, 15.0, 3.75, 0.30)), + ("haiku".into(), m(1.0, 5.0, 1.25, 0.10)), + // --- OpenAI (Codex / GPT-5 family) --- + // Codex transcripts report `cached_input_tokens` (→ cache_read); no + // separate cache-write concept, so cache_write mirrors input. + ("gpt-5-codex".into(), m(1.25, 10.0, 1.25, 0.125)), + ("codex".into(), m(1.25, 10.0, 1.25, 0.125)), + ("gpt-5".into(), m(1.25, 10.0, 1.25, 0.125)), + ("o4".into(), m(1.10, 4.40, 1.10, 0.275)), + ("o3".into(), m(2.0, 8.0, 2.0, 0.50)), + ]) + } +} + +#[cfg(test)] +#[path = "pricing_tests.rs"] +mod tests; diff --git a/crates/zaplex_cockpit/src/pricing_tests.rs b/crates/zaplex_cockpit/src/pricing_tests.rs new file mode 100644 index 0000000000..af02f9907a --- /dev/null +++ b/crates/zaplex_cockpit/src/pricing_tests.rs @@ -0,0 +1,52 @@ +use super::*; + +fn approx(a: f64, b: f64) { + assert!((a - b).abs() < 1e-9, "expected {b}, got {a}"); +} + +#[test] +fn substring_matching_picks_the_right_model() { + let t = PricingTable::default(); + assert_eq!(t.price_for("claude-opus-4-8"), t.price_for("OPUS")); + assert!(t.price_for("claude-sonnet-4-6").is_some()); + assert!(t.price_for("claude-haiku-4-5-20251001").is_some()); + assert!(t.price_for("gpt-5-codex").is_some()); + assert!(t.price_for("totally-unknown-model").is_none()); +} + +#[test] +fn codex_key_is_more_specific_than_gpt5() { + let t = PricingTable::default(); + // Both currently priced the same, but the specific key must resolve first so a + // future divergence in the table is honoured. + assert!(t.price_for("gpt-5-codex").is_some()); +} + +#[test] +fn opus_golden_cost_from_verified_transcript_block() { + // Appendix A of the design doc (a real opus turn). + let t = PricingTable::default(); + let cost = t.cost_for("claude-opus-4-8", 19370, 230, 9716, 19748, 0); + // (19370*15 + 230*75 + 9716*18.75 + 19748*1.50) / 1e6 + approx(cost, 0.519597); +} + +#[test] +fn one_million_each_opus() { + let t = PricingTable::default(); + // 1M input @ $15 + 1M output @ $75 = $90. + approx(t.cost_for("claude-opus-4-8", 1_000_000, 1_000_000, 0, 0, 0), 90.0); +} + +#[test] +fn reasoning_tokens_bill_as_output_for_codex() { + let t = PricingTable::default(); + // 1M output + 1M reasoning, both @ $10 output = $20. + approx(t.cost_for("gpt-5-codex", 0, 1_000_000, 0, 0, 1_000_000), 20.0); +} + +#[test] +fn unknown_model_costs_zero() { + let t = PricingTable::default(); + approx(t.cost_for("some-future-model", 1_000_000, 1_000_000, 0, 0, 0), 0.0); +} diff --git a/crates/zaplex_cockpit/src/types.rs b/crates/zaplex_cockpit/src/types.rs new file mode 100644 index 0000000000..a610181705 --- /dev/null +++ b/crates/zaplex_cockpit/src/types.rs @@ -0,0 +1,127 @@ +//! Core cockpit data types — pure, serde-friendly, no I/O and no secrets. +//! +//! Privacy invariant: these types carry only **token counts and account metadata**, +//! never token strings, transcript content, or any credential material. + +use std::path::PathBuf; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// The LLM CLI providers the cockpit understands. +/// +/// A minimal enum owned by this (pure) crate; the app's richer `CLIAgent` maps onto +/// it at the wiring layer. Increment 1 covers Claude Code + Codex. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Provider { + Claude, + Codex, +} + +impl Provider { + pub fn as_str(self) -> &'static str { + match self { + Provider::Claude => "claude", + Provider::Codex => "codex", + } + } +} + +/// A discovered account / subscription. Metadata only — never tokens. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Account { + pub provider: Provider, + /// Stable key derived from the provider + config dir, e.g. `claude:default`, + /// `claude:work`, `codex:default`. Stable across restarts for pinning later. + pub key: String, + /// The config directory this account was discovered from. + pub config_dir: PathBuf, + /// Human label (email/org/plan-derived; falls back to the dir name). + pub label: String, + pub email: Option, + pub org: Option, + pub role: Option, + /// Plan tier label, e.g. "Max 20x", "Max", "Pro" (best-effort, provider-specific). + pub plan_tier: Option, + /// Whether this is the provider's default config dir (`~/.claude`, `~/.codex`). + pub is_default: bool, +} + +/// One usage record extracted from a transcript line (one assistant turn/message). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct UsageEntry { + pub ts: DateTime, + pub provider: Provider, + pub model: String, + pub input: u64, + pub output: u64, + pub cache_create: u64, + pub cache_read: u64, + /// Codex reasoning output tokens (billed as output); 0 for Claude. + pub reasoning: u64, +} + +/// Aggregated token + cost totals over a time window. +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct WindowTotals { + pub input: u64, + pub output: u64, + pub cache_create: u64, + pub cache_read: u64, + pub reasoning: u64, + /// Load signal: `input + output + cache_create + reasoning` — excludes the cheap, + /// high-volume cache *reads* so heat/"launch-on-freest" reflect real work. + pub work: u64, + /// All billable tokens: `work + cache_read`. + pub total: u64, + pub cost_usd: f64, + /// Number of assistant messages/turns counted. + pub messages: u64, +} + +impl WindowTotals { + /// Fold one usage entry into the running totals, adding its cost via `pricing`. + pub fn add(&mut self, e: &UsageEntry, pricing: &crate::pricing::PricingTable) { + self.input += e.input; + self.output += e.output; + self.cache_create += e.cache_create; + self.cache_read += e.cache_read; + self.reasoning += e.reasoning; + self.work += e.input + e.output + e.cache_create + e.reasoning; + self.total += e.input + e.output + e.cache_create + e.cache_read + e.reasoning; + self.cost_usd += pricing.cost_for( + &e.model, + e.input, + e.output, + e.cache_create, + e.cache_read, + e.reasoning, + ); + self.messages += 1; + } +} + +/// Per-account usage across the cockpit's windows, plus derived reset times + heat. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AccountUsage { + pub account: Account, + /// Current rolling 5-hour block. + pub block5h: WindowTotals, + /// Current calendar day (UTC in Increment 1). + pub today: WindowTotals, + /// Current rolling 7-day block. + pub week: WindowTotals, + /// When the current 5h block resets (block start + 5h), if a block is active. + pub reset5h: Option>, + /// When the current 7d block resets, if a block is active. + pub reset_week: Option>, + /// `block5h.work / budget_5h`, clamped at 0; may exceed 1.0 (over budget). + pub heat: f64, +} + +/// A full cockpit snapshot: every discovered account with its usage. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CockpitSnapshot { + pub accounts: Vec, + pub generated_at: DateTime, +} diff --git a/crates/zaplex_cockpit/src/windows.rs b/crates/zaplex_cockpit/src/windows.rs new file mode 100644 index 0000000000..0903685ca4 --- /dev/null +++ b/crates/zaplex_cockpit/src/windows.rs @@ -0,0 +1,128 @@ +//! Time-window aggregation: ccusage-style rolling blocks (5h / 7d) + calendar +//! "today", producing [`WindowTotals`] and reset times, plus heat. +//! +//! All functions are pure and take an explicit `now`, so window boundaries are +//! deterministic and unit-testable without touching the clock. + +use chrono::{DateTime, Duration, Timelike, Utc}; + +use crate::pricing::PricingTable; +use crate::types::{Account, AccountUsage, UsageEntry, WindowTotals}; + +/// Rolling-block window for the "5h block" view. +pub fn window_5h() -> Duration { + Duration::hours(5) +} +/// Rolling-block window for the "week" view. +pub fn window_week() -> Duration { + Duration::days(7) +} + +/// Flat default budgets (token *work*) used for heat when no per-tier estimate or +/// user override applies (mirrors `claudeplex` instances.ts). Overridable upstream. +pub const DEFAULT_BUDGET_5H: u64 = 20_000_000; +pub const DEFAULT_BUDGET_WEEK: u64 = 300_000_000; + +fn floor_to_hour(ts: DateTime) -> DateTime { + ts.with_minute(0) + .and_then(|t| t.with_second(0)) + .and_then(|t| t.with_nanosecond(0)) + .unwrap_or(ts) +} + +/// A rolling activity block: a start (first activity floored to the hour) and its totals. +#[derive(Clone, Copy, Debug, PartialEq)] +struct Block { + start: DateTime, + totals: WindowTotals, +} + +/// Group time-ordered entries into ccusage-style rolling blocks: a block begins at +/// the first activity (floored to the hour); an entry starts a *new* block when it +/// falls outside `window` from the block start, or when the gap from the previous +/// entry is ≥ `window`. `entries` MUST be sorted ascending by `ts`. +fn rolling_blocks(entries: &[UsageEntry], window: Duration, pricing: &PricingTable) -> Vec { + let mut blocks: Vec = Vec::new(); + let mut last_ts: Option> = None; + for e in entries { + let start_new = match (blocks.last(), last_ts) { + (Some(b), Some(last)) => e.ts >= b.start + window || e.ts - last >= window, + _ => true, + }; + if start_new { + blocks.push(Block { + start: floor_to_hour(e.ts), + totals: WindowTotals::default(), + }); + } + blocks + .last_mut() + .expect("just pushed or existed") + .totals + .add(e, pricing); + last_ts = Some(e.ts); + } + blocks +} + +/// Totals + reset time for the block *currently active* at `now`: the most recent +/// block whose `[start, start + window)` contains `now`. If the last activity's +/// window has elapsed (user idle), returns empty totals and no reset. +fn current_window( + entries: &[UsageEntry], + now: DateTime, + window: Duration, + pricing: &PricingTable, +) -> (WindowTotals, Option>) { + match rolling_blocks(entries, window, pricing).last() { + Some(b) if now >= b.start && now < b.start + window => (b.totals, Some(b.start + window)), + _ => (WindowTotals::default(), None), + } +} + +/// Sum of entries whose timestamp is on the same UTC calendar day as `now`. +fn today_totals( + entries: &[UsageEntry], + now: DateTime, + pricing: &PricingTable, +) -> WindowTotals { + let today = now.date_naive(); + let mut totals = WindowTotals::default(); + for e in entries.iter().filter(|e| e.ts.date_naive() == today) { + totals.add(e, pricing); + } + totals +} + +/// Build the full per-account usage view (5h block / today / week + resets + heat). +/// `entries` may be in any order; it is sorted internally. +pub fn build_account_usage( + account: Account, + mut entries: Vec, + now: DateTime, + budget_5h: u64, + pricing: &PricingTable, +) -> AccountUsage { + entries.sort_by_key(|e| e.ts); + let (block5h, reset5h) = current_window(&entries, now, window_5h(), pricing); + let (week, reset_week) = current_window(&entries, now, window_week(), pricing); + let today = today_totals(&entries, now, pricing); + let heat = if budget_5h > 0 { + block5h.work as f64 / budget_5h as f64 + } else { + 0.0 + }; + AccountUsage { + account, + block5h, + today, + week, + reset5h, + reset_week, + heat, + } +} + +#[cfg(test)] +#[path = "windows_tests.rs"] +mod tests; diff --git a/crates/zaplex_cockpit/src/windows_tests.rs b/crates/zaplex_cockpit/src/windows_tests.rs new file mode 100644 index 0000000000..42667856b9 --- /dev/null +++ b/crates/zaplex_cockpit/src/windows_tests.rs @@ -0,0 +1,123 @@ +use super::*; +use crate::types::{Account, Provider, UsageEntry}; +use chrono::{DateTime, Utc}; + +fn ts(s: &str) -> DateTime { + DateTime::parse_from_rfc3339(s) + .unwrap() + .with_timezone(&Utc) +} + +fn entry(t: &str, input: u64, output: u64) -> UsageEntry { + UsageEntry { + ts: ts(t), + provider: Provider::Claude, + model: "claude-opus-4-8".into(), + input, + output, + cache_create: 0, + cache_read: 0, + reasoning: 0, + } +} + +fn acct() -> Account { + Account { + provider: Provider::Claude, + key: "claude:default".into(), + config_dir: "/tmp/x".into(), + label: "test".into(), + email: None, + org: None, + role: None, + plan_tier: None, + is_default: true, + } +} + +fn approx(a: f64, b: f64) { + assert!((a - b).abs() < 1e-9, "expected {b}, got {a}"); +} + +#[test] +fn windows_bucket_correctly_with_fixed_now() { + let entries = vec![ + entry("2026-06-28T08:00:00Z", 500, 50), // 2 days ago + entry("2026-06-30T10:00:00Z", 1000, 100), + entry("2026-06-30T11:00:00Z", 2000, 200), + ]; + let now = ts("2026-06-30T12:00:00Z"); + let pricing = PricingTable::default(); + let u = build_account_usage(acct(), entries, now, 6600, &pricing); + + // 5h block = the two same-day turns; older turn is a separate expired block. + assert_eq!(u.block5h.messages, 2); + assert_eq!(u.block5h.work, 3300); + assert_eq!(u.block5h.input, 3000); + assert_eq!(u.block5h.output, 300); + assert_eq!(u.reset5h, Some(ts("2026-06-30T15:00:00Z"))); + // opus: (3000*15 + 300*75)/1e6 + approx(u.block5h.cost_usd, 0.0675); + + // today (UTC) = same as the 5h block here. + assert_eq!(u.today.messages, 2); + assert_eq!(u.today.work, 3300); + + // week = all three turns in one rolling 7d block. + assert_eq!(u.week.messages, 3); + assert_eq!(u.week.work, 3850); + assert_eq!(u.reset_week, Some(ts("2026-07-05T08:00:00Z"))); + + // heat = 3300 / 6600. + approx(u.heat, 0.5); +} + +#[test] +fn idle_past_the_window_yields_empty_block_and_no_reset() { + let entries = vec![ + entry("2026-06-30T10:00:00Z", 1000, 100), + entry("2026-06-30T11:00:00Z", 2000, 200), + ]; + // 20:00 is > 5h after the block start (10:00 → resets 15:00). + let now = ts("2026-06-30T20:00:00Z"); + let pricing = PricingTable::default(); + let u = build_account_usage(acct(), entries, now, DEFAULT_BUDGET_5H, &pricing); + + assert_eq!(u.block5h.messages, 0); + assert_eq!(u.block5h.work, 0); + assert!(u.reset5h.is_none()); + approx(u.heat, 0.0); + + // Still within the 7d week block, so week stays populated. + assert_eq!(u.week.messages, 2); + assert!(u.reset_week.is_some()); +} + +#[test] +fn a_gap_of_at_least_the_window_starts_a_new_block() { + let entries = vec![ + entry("2026-06-30T10:00:00Z", 1000, 100), + entry("2026-06-30T16:00:00Z", 2000, 200), // gap 6h ≥ 5h → new block + ]; + let now = ts("2026-06-30T16:30:00Z"); + let pricing = PricingTable::default(); + let u = build_account_usage(acct(), entries, now, DEFAULT_BUDGET_5H, &pricing); + + // Current 5h block only contains the second turn. + assert_eq!(u.block5h.messages, 1); + assert_eq!(u.block5h.work, 2200); + assert_eq!(u.reset5h, Some(ts("2026-06-30T21:00:00Z"))); +} + +#[test] +fn empty_entries_are_all_zero() { + let now = ts("2026-06-30T12:00:00Z"); + let pricing = PricingTable::default(); + let u = build_account_usage(acct(), vec![], now, DEFAULT_BUDGET_5H, &pricing); + assert_eq!(u.block5h, WindowTotals::default()); + assert_eq!(u.today, WindowTotals::default()); + assert_eq!(u.week, WindowTotals::default()); + assert!(u.reset5h.is_none()); + assert!(u.reset_week.is_none()); + approx(u.heat, 0.0); +} diff --git a/crates/zaplex_cockpit/tests/snapshot.rs b/crates/zaplex_cockpit/tests/snapshot.rs new file mode 100644 index 0000000000..85e8d34d7c --- /dev/null +++ b/crates/zaplex_cockpit/tests/snapshot.rs @@ -0,0 +1,80 @@ +//! Integration test: `build_snapshot` over fixture Claude + Codex homes, via the +//! crate's public API only. + +use std::fs; +use std::path::Path; + +use chrono::{DateTime, Utc}; +use zaplex_cockpit::{build_snapshot, PricingTable, Provider, DEFAULT_BUDGET_5H}; + +fn write(path: &Path, content: &str) { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, content).unwrap(); +} + +fn ts(s: &str) -> DateTime { + DateTime::parse_from_rfc3339(s).unwrap().with_timezone(&Utc) +} + +#[test] +fn build_snapshot_aggregates_both_providers() { + let home_tmp = tempfile::tempdir().unwrap(); + let home = home_tmp.path(); + write( + &home.join(".claude.json"), + r#"{"oauthAccount":{"emailAddress":"me@example.com", + "organizationRateLimitTier":"max_20x","organizationType":"claude_max"}}"#, + ); + write( + &home.join(".claude/projects/p/s.jsonl"), + concat!( + r#"{"type":"assistant","timestamp":"2026-06-30T10:00:00Z","message":{"model":"claude-opus-4-8","usage":{"input_tokens":1000,"output_tokens":100}}}"#, + "\n", + ), + ); + + let codex_tmp = tempfile::tempdir().unwrap(); + let codex_home = codex_tmp.path(); + // No JWT id_token here (base64 isn't a dev-dep); the JWT-email path is covered by + // the crate's unit tests. The account is still discovered from auth_mode. + write( + &codex_home.join("auth.json"), + r#"{"auth_mode":"chatgpt","tokens":{"account_id":"acc_1"}}"#, + ); + write( + &codex_home.join("sessions/2026/06/30/rollout-a.jsonl"), + concat!( + r#"{"type":"turn_context","model":"gpt-5-codex","timestamp":"2026-06-30T10:30:00Z"}"#, + "\n", + r#"{"type":"event_msg","timestamp":"2026-06-30T10:31:00Z","last_token_usage":{"input_tokens":200,"output_tokens":50}}"#, + "\n", + ), + ); + + let now = ts("2026-06-30T12:00:00Z"); + let pricing = PricingTable::default(); + let snap = build_snapshot(home, codex_home, None, now, DEFAULT_BUDGET_5H, &pricing); + + assert_eq!(snap.generated_at, now); + assert_eq!(snap.accounts.len(), 2, "one Claude + one Codex account"); + + let claude = snap + .accounts + .iter() + .find(|a| a.account.provider == Provider::Claude) + .unwrap(); + assert_eq!(claude.account.plan_tier.as_deref(), Some("Max 20x")); + assert_eq!(claude.block5h.messages, 1); + assert_eq!(claude.block5h.input, 1000); + assert!(claude.block5h.cost_usd > 0.0); + + let codex = snap + .accounts + .iter() + .find(|a| a.account.provider == Provider::Codex) + .unwrap(); + assert_eq!(codex.account.label, "chatgpt"); + assert_eq!(codex.block5h.messages, 1); + assert_eq!(codex.block5h.output, 50); + assert_eq!(codex.block5h.cache_read, 0); +} diff --git a/crates/zaplex_remote_session/src/types.rs b/crates/zaplex_remote_session/src/types.rs index c276aac6fe..902f08e6b7 100644 --- a/crates/zaplex_remote_session/src/types.rs +++ b/crates/zaplex_remote_session/src/types.rs @@ -16,6 +16,14 @@ use serde::{Deserialize, Serialize}; /// "SSH PTY + no persistence" behaviour. pub const FEATURE_SESSION_HOST: &str = "session-host"; +/// Reserved capability name for the Phase B3 native UDP transport (mosh-grade +/// roaming + low latency). **Not yet advertised** by [`supported_features`] — +/// the transport is unimplemented; this only reserves the negotiation name so +/// client and daemon agree on it once it lands, keeping the capability handshake +/// honest (never advertise what we can't fulfil). See +/// `docs/superpowers/specs/2026-06-28-stage-b3-udp-transport-design.md`. +pub const FEATURE_UDP_TRANSPORT: &str = "udp-transport"; + /// A persistent session identifier assigned by the daemon. /// /// Unlike the protocol's existing `session_id: uint64` (which is the client's diff --git a/docs/superpowers/plans/2026-06-24-native-remote-session-layer.md b/docs/superpowers/plans/2026-06-24-native-remote-session-layer.md index 5a22789807..59f6b7846b 100644 --- a/docs/superpowers/plans/2026-06-24-native-remote-session-layer.md +++ b/docs/superpowers/plans/2026-06-24-native-remote-session-layer.md @@ -129,7 +129,7 @@ Lebt im erweiterten Daemon (`app/src/remote_server/unix/mod.rs` → ruft in `zap **Client-seitige Session-Persistenz (über App-Restart):** kleine Tabelle `remote_sessions` in `crates/persistence` (diesel-Migration unter `crates/persistence/migrations/`, schema.rs auto-generiert via `diesel.toml`): `{ session_id, host_node_id, identity_key, title, cwd, last_seq, last_attached_at }`. Beim Start: pro Host `ListSessions` → mit persistierten Einträgen abgleichen → wieder-attachbar anzeigen. -**Per-Verbindung-Setting (aus §3.5):** `SshServerInfo` (`crates/warp_ssh_manager/src/types.rs:99`) + Tabelle `ssh_servers` (`crates/persistence/src/model.rs:1463`) + CRUD (`repository.rs`) additiv um `session_resilience: Off | PersistOnly | PersistPlusMosh` erweitern (Feld + Migration + Row/NewRow + Mapping). Default `Off` (konservativ). Globales Default + Feature-Gate über `maybe_define_setting!` (`app/src/terminal/warpify/settings.rs`-Muster) und `warp_features` (`SSHTmuxWrapper`-Muster). +**Per-Verbindung-Setting (aus §3.5):** `SshServerInfo` (`crates/warp_ssh_manager/src/types.rs:99`) + Tabelle `ssh_servers` (`crates/persistence/src/model.rs:1463`) + CRUD (`repository.rs`) additiv um `session_resilience: Off | PersistOnly | PersistPlusMosh` erweitern (Feld + Migration + Row/NewRow + Mapping). Default `Off` (konservativ). Globales Default + Feature-Gate über `maybe_define_setting!` (`app/src/terminal/zaplexify/settings.rs`-Muster) und `warp_features` (`SSHTmuxWrapper`-Muster). **RAM-Ceiling pro Session:** Heute existiert **kein** RAM-Governor (nur zeilenbasierte Scrollback-Grenze `BlockSize::max_block_scroll_limit`). Der Ring-Buffer bekommt ein **byte-basiertes** Ceiling pro Session (Setting), und die Registry ein Gesamt-Ceiling pro Host — das ist zugleich der erste Baustein des in §3.2 genannten „RAM-Governor für die Fleet" (gemeinsame Primitive, §10). @@ -219,6 +219,6 @@ Stufen 0–4 = **Must-Have-Persistenz (B2)**. Stufe 5 = **B3-Kür**. Jede Stufe | Maus (Intercept + Write-back) | `app/src/terminal/alt_screen/mod.rs:11` · `app/src/terminal/writeable_pty/pty_controller.rs:40` | | SessionType / BootstrapSessionType | `app/src/terminal/model/session.rs:834,843` | | SSH-Host-Settings + ssh_servers + CRUD | `crates/warp_ssh_manager/src/types.rs:99` · `crates/persistence/src/model.rs:1463` · `crates/warp_ssh_manager/src/repository.rs` | -| Settings-Makro / Feature-Flags | `app/src/terminal/warpify/settings.rs:46` · `crates/warp_features/src/lib.rs:124` | +| Settings-Makro / Feature-Flags | `app/src/terminal/zaplexify/settings.rs:46` · `crates/warp_features/src/lib.rs:124` | | Workspace / Crate-Registrierung | `Cargo.toml:1` (`[workspace] members=["crates/*","app"]`, `[workspace.dependencies]`) | | Persistenz-Migrationen / Schema | `crates/persistence/migrations/` · `diesel.toml` · `crates/persistence/src/schema.rs` | diff --git a/docs/superpowers/specs/2026-06-25-stage2-client-attach-design.md b/docs/superpowers/specs/2026-06-25-stage2-client-attach-design.md new file mode 100644 index 0000000000..afb38eeaa1 --- /dev/null +++ b/docs/superpowers/specs/2026-06-25-stage2-client-attach-design.md @@ -0,0 +1,53 @@ +# Stage 2 Design — Client Attach + Block/Grid + Mouse + +> **Status:** Design + increment 1 in progress, created 2026-06-25. +> **Parent:** [native-remote-session-layer.md](../plans/2026-06-24-native-remote-session-layer.md) §6 + [Stage 1 spec](2026-06-24-stage1-session-host-design.md). +> **Builds on:** Stage 1 (merged) — the daemon owns PTYs and sends `SessionOutput`/`SessionExited` pushes, accepting `OpenSession`/`AttachSession`/`SessionInput`/`ResizeSession`. + +--- + +## 1. Goal + +Make a daemon-hosted session usable as a real terminal in the client: its +output renders into the grid/blocks, keyboard **and mouse** input flow back to +the PTY, and resize propagates — all without an external multiplexer (so the SGR +mouse path works end-to-end, plan §3.5 / §6). + +**Stage 2 scope:** one remote session, opened and driven from the client. +Replay-on-reconnect/persistence is Stage 3; multi-session UI/adopt is Stage 4. + +--- + +## 2. Verified seams (from code exploration) + +| Concern | Location | +|---|---| +| Client push → event | `crates/remote_server/src/client/mod.rs`: `ClientEvent` enum, `push_message_to_event()` | +| Client request/notify methods | same file (pattern: `initialize()` request, `send_buffer_edit()` notification) | +| Manager event forward | `crates/remote_server/src/manager.rs`: `forward_client_event()` → `RemoteServerManagerEvent` | +| **ANSI feed (core insertion point)** | `app/src/terminal/model/ansi/mod.rs`: `Processor::parse_bytes(&mut handler, bytes, writer)` — `TerminalModel` is the `Handler` | +| Local byte-flow reference | `app/src/terminal/local_tty/event_loop.rs` (feeds `parser.parse_bytes(&mut model, bytes, &mut sink/pty)`) | +| Remote TTY scaffold | `app/src/terminal/remote_tty/terminal_manager.rs` (async-channel eventloop) | +| Session type | `app/src/terminal/model/session.rs` (`SessionType` / `BootstrapSessionType`) | +| User input + mouse → PTY | `app/src/terminal/writeable_pty/pty_controller.rs` (`write_user_bytes_to_pty`), `app/src/terminal/alt_screen/mod.rs` (`should_intercept_mouse`) | + +**Core insertion point:** feed `SessionOutput.bytes` into `Processor::parse_bytes(&mut terminal_model, &bytes, &mut io::sink())` — identical to the local path, but with `io::sink()` as the "writer" (no local echo; the shell runs on the daemon). Grid/blocks update immediately; trigger a redraw wakeup. + +--- + +## 3. Increments + +1. **Client protocol layer** *(this PR)* — `ClientEvent::SessionOutput`/`SessionExited`, `push_message_to_event` arms, and client methods `open_session`/`attach_session`/`send_session_input`/`send_resize_session`/`send_detach_session`. Isolated to `remote_server`; `cargo check -p remote_server` green. `forward_client_event` has a placeholder arm (no app consumer yet). +2. **Manager → app events** — add `RemoteServerManagerEvent::SessionOutput`/`SessionExited`, emit them from `forward_client_event`; handle the new variants in app subscribers. +3. **Remote terminal byte source** — a `remote_tty` byte sink that feeds `SessionOutput` bytes into the terminal's `ansi::Processor` (`parse_bytes` + redraw). Open a session via `open_session`, route the model's bytes from the manager events. +4. **Input + resize + mouse** — route `write_user_bytes_to_pty` for a remote session to `client.send_session_input`; resize → `send_resize_session`; ensure `should_intercept_mouse` lets SGR mouse reports flow to `send_session_input` (no multiplexer nesting → the Warp tmux mouse bug is structurally absent). +5. **Session lifecycle** — `SessionExited` → close/mark the terminal; a session-type path (`session.rs`) that selects the attached-remote source instead of a local PTY. + +--- + +## 4. Notes / risks + +- **Mouse:** the whole point — because the daemon owns the real PTY and there is no multiplexer between, SGR mouse (`DECSET 1006/1000/1002/1003`) round-trips through the normal ansi path; mouse bytes just go back via `send_session_input`. +- **Redraw:** after `parse_bytes`, send the model's wakeup/redraw event (as the local event loop does) so the view updates. +- **Backpressure / ordering:** `SessionOutput` carries monotonic `seq`; Stage 2 consumes in arrival order (replay/seq-gap handling is Stage 3). +- **No async-model test harness** exists, so app-side increments are `cargo check`-verified + exercised via the manual `test-dispatch` job / real run; behaviour validation of the full path is via running the app (Stage 2 is where end-to-end UI testing becomes possible). diff --git a/docs/superpowers/specs/2026-06-27-stage2-increment3c-daemon-trigger-design.md b/docs/superpowers/specs/2026-06-27-stage2-increment3c-daemon-trigger-design.md new file mode 100644 index 0000000000..c4bd4f8d21 --- /dev/null +++ b/docs/superpowers/specs/2026-06-27-stage2-increment3c-daemon-trigger-design.md @@ -0,0 +1,120 @@ +# Stage 2 Increment 3c — Daemon-Session Trigger (Option B) + +> **Status:** Design, created 2026-06-27. +> **Parent:** [native-remote-session-layer.md](../plans/2026-06-24-native-remote-session-layer.md) §6/§7 + [Stage 2 design](2026-06-25-stage2-client-attach-design.md). +> **Builds on:** 3a (`daemon_tty` terminal manager, commit 492ebd4e) + 3b (`session_resilience` per-host setting, commit e4406460). + +--- + +## 1. Goal + +Make a saved SSH host with `session_resilience.is_enabled()` open **directly** as a +daemon-hosted session (Option B, user-decided): the remote daemon owns the PTY and a +replay buffer; the SSH connection is a pure control/transport channel. `local_tty` +(localhost + plain-vanilla ssh) and the existing non-resilient SSH path stay the +untouched default. + +Out of scope (later increments): replay-on-reattach/persistence reconnect (4), the +`session_resilience` settings UI + feature gate (4), mosh-grade UDP transport / B3. + +--- + +## 2. Verified seams + +| Concern | Location | +|---|---| +| SSH-host launch (the branch point) | `app/src/workspace/view.rs:5416` `open_ssh_terminal()` — resolves auth, builds `cmd`, opens a **local** tab, spawns password/startup/su injectors, queues `ssh` via `execute_command_or_set_pending` | +| Per-host opt-in (3b) | `SshServerInfo.session_resilience: SessionResilience` (`warp_ssh_manager::types`), `is_enabled()` | +| Daemon terminal (3a) | `app/src/terminal/daemon_tty/` — `TerminalManager::create_model(resources, size, model_event_sender, window_id, input_config, connection_session_id: SessionId, open_params: OpenSessionParams, ctx)` | +| Headless connect: ControlMaster args | `crates/remote_server/src/ssh.rs:17` `ssh_args(socket_path)` (for multiplexed cmds over an existing master) | +| Headless connect: auth machinery | `crates/warp_ssh_manager/src/ssh_command.rs` (`build_ssh_command_line`, `build_password_auth_stdin`, `test_connection` — already does a non-interactive ssh connect with password/key auth) | +| Transport + connect | `app/src/remote_server/ssh_transport.rs` `SshTransport::new(socket_path, auth_context)`; `RemoteServerManager::connect_session(session_id, transport, auth_context, ctx)` (`crates/remote_server/src/manager.rs:706`, **unchanged**) | +| Connected signal | `RemoteServerManagerEvent::SessionConnected { session_id, host_id }` | +| SessionId | `warp_core::SessionId(u64)`, `From` (allocate a fresh id for the headless session) | + +**Feasibility (confirmed by investigation):** headless connect is low-effort — spawn our +own `ssh -N -o ControlMaster=yes -o ControlPath= user@host` (with auth), +wait for the socket, build `SshTransport`, call `connect_session`. This bypasses the +interactive DCS bootstrap (`dcs_hooks.rs` → `SshInitShell` → `RemoteServerController`), +which today is what supplies `socket_path`. + +--- + +## 3. Design + +### 3.1 Tab/connect ordering (UX) + +A `daemon_tty` terminal has **no** local PTY — its bytes only arrive once the daemon +session is open, which needs a connected transport. Chosen model (premium UX, fits the +existing async pattern): **create the daemon tab immediately**, show a "connecting…" +state, and have `daemon_tty` defer `OpenSession` until its connection reaches +`Connected`. (Alternative — connect first, then create the tab — is simpler but adds a +pre-tab delay and a disappearing-action feel; rejected.) + +### 3.2 `daemon_tty` waits for `SessionConnected` (3c-i) + +Today `EventLoop::start` calls `open_session` eagerly (3a assumed an already-connected +session). Change: stash `(open_params, size_info)` as pending; on start, if the manager +already has a client for `connection_session_id`, open now; otherwise the existing +manager subscription gains a `SessionConnected { session_id } if session_id == +connection_session_id` arm that triggers the one-shot open. Also handle +`SessionConnectionFailed`/`SessionDisconnected` for our id → surface a clear error into +the terminal model (no silent dead tab). Isolated, `cargo check -p warp`-verifiable. + +### 3.3 Headless connect orchestrator (3c-ii) + +New, self-contained unit (likely `app/src/remote_server/headless_connect.rs`): given a +resolved `SshServerInfo` + auth + a freshly allocated `SessionId`: + +1. Compute a stable local ControlPath socket (e.g. hash of `host:port:user:identity_key` + under the existing ssh socket dir). +2. Spawn the master `ssh -N -o ControlMaster=yes -o ControlPersist=… -o + ControlPath= user@host`, reusing `ssh_command.rs` auth handling + (key/agent now; password via the existing stdin/askpass machinery). +3. Await the socket file, then `SshTransport::new(socket, auth_context)` → + `RemoteServerManager::connect_session(session_id, transport, auth_context, ctx)`. + The binary check/install is already part of `connect_session`'s precondition via the + manager; reuse `check_binary`/`install_binary` exactly as `RemoteServerController` does + if needed, or require a preinstalled daemon for v1 (decide during impl). + +**Auth scope for v1:** key/agent auth works cleanly headless. Password auth needs the +non-PTY injection path (`build_password_auth_stdin` / `SSH_ASKPASS`); if that proves +involved, v1 falls back to the normal (non-daemon) SSH path for password-only hosts and +logs why — never a broken tab. + +### 3.4 `open_ssh_terminal` branch (3c-iii) + +At the top of `open_ssh_terminal`, after auth resolution: if +`server.session_resilience.is_enabled()` (and auth is headless-capable), take the daemon +path — allocate `SessionId`, kick off the orchestrator (3c-ii), create the tab via +`daemon_tty::create_model(connection_session_id = that id, open_params = {cwd, shell, +env})` instead of the local-shell-+-queued-ssh path. The injectors and +`execute_command_or_set_pending` are skipped (no local PTY). Otherwise: unchanged. + +`create_session`/`add_new_session_tab_internal_*` need a daemon variant that routes to +`daemon_tty::create_model` (additive; `local_tty` stays the cfg/runtime default). + +--- + +## 4. Increments + +- **3c-i** — `daemon_tty` defers `OpenSession` until `SessionConnected`; handles + connect-failure into the model. `cargo check -p warp`. +- **3c-ii** — headless-connect orchestrator (spawn master + `connect_session`). Isolated; + unit-test the socket-path derivation; runtime-validate via the xl dispatch / a real run. +- **3c-iii** — `open_ssh_terminal` branch + daemon tab creation. End-to-end (output + renders, input/resize/**mouse** via `send_session_input` — mouse is structurally safe, + no multiplexer nesting). This is where Stage 2's end-to-end mouse/blocks goal lands. + +--- + +## 5. Risks + +- **Headless password auth** — main unknown; mitigated by key-first + graceful fallback. +- **ControlMaster lifecycle** — who owns/tears down the headless master (the daemon + session outlives the SSH channel by design). Tie master teardown to session close / + `ssh -O exit` (`ssh.rs`), not to a client tab close (a detached session must survive). +- **Daemon not installed** — reuse the manager's check/install, or require preinstalled + for v1 with a clear error. +- **No async-model test harness** — app-side increments are `cargo check`-verified + + exercised via xl dispatch / real run; full behaviour validation is by running the app. diff --git a/docs/superpowers/specs/2026-06-28-daemon-session-test-runbook.md b/docs/superpowers/specs/2026-06-28-daemon-session-test-runbook.md new file mode 100644 index 0000000000..c17c18fdba --- /dev/null +++ b/docs/superpowers/specs/2026-06-28-daemon-session-test-runbook.md @@ -0,0 +1,99 @@ +# Daemon Session — First Real-Host Test Runbook + +> Branch `feat/stage2-client-attach`. Bring-up of the native persistent +> remote-session layer against a real SSH host. Covers the **open** flow, the +> **drop/reconnect** survival, and the **adopt-sidebar** (list + re-attach running +> sessions). B3 UDP is out of scope. The code path is verified by headless tests + +> 8 Codex review rounds + a deep self-review; this runbook is for the steps only a +> real host can exercise (GUI render, mouse, real drop survival). + +## Build & install (handled for the tester) + +- DMG: built via `test-dmg.yml` (arch **aarch64**, fast/debug profile) under a tag, + delivered to the project `exports/` directory (you fetch it from there — you're on + the MacBook, not devhost). Install: open the DMG, drag to Applications. It is + ad-hoc self-signed; it installed cleanly last time (no `xattr` dance needed). +- Daemon binary on the target host (devhost): pre-placed at + `~/.zaplex/remote-server/zaplex-` (matching the DMG's `GIT_RELEASE_TAG`), so + the client's `check_binary` passes and the auto-download path (which points at the + upstream repo) is never taken. The tag must match the DMG; current: `v0.daemontest-0630`. + +## Preconditions + +- A saved SSH host that uses **key auth** (`AuthType::Key`, or a OneKey credential + of kind *Key*). Password hosts intentionally fall back to the normal SSH path — + they will **not** take the daemon path in v1. +- The key is usable **non-interactively**: loaded in an ssh-agent, or unencrypted. + (The ControlMaster spawns with `BatchMode=yes`; an encrypted key with no agent + will fail — that's expected v1.) +- Host reachable over SSH. The remote-server binary does **not** need to be + preinstalled — it auto-installs on first connect (watch for the install log). +- In the SSH server form, set **Session persistence = Persistent** and Save. + +## Steps + +1. Build + launch the app (debug is fine). +2. Open the saved host (the same action as a normal SSH connect). +3. A new tab should appear and, after the connect sequence, show a working remote + shell **with Zaplex blocks/prompt** (not a bare VT). +4. Type a few commands; resize the window; try the mouse in a TUI (e.g. `htop`). +5. **Drop test:** kill the network / sleep the laptop / `pkill -f "ssh .*ControlPath"` + briefly, then restore. The session should reconnect and replay — the shell + state (your scrollback/running program) survives. After a *long* drop where the + daemon ring evicted old output, the screen resets and shows a one-line notice + `[zaplex] scrollback truncated during a long disconnect` (instead of a garbled + grid) — that's expected. +6. **Adopt-sidebar:** right-click the host → **Running sessions**. It should list + the daemon session(s) on that host (title = cwd/shell). Click one → it re-attaches + in a new tab (replay + live). Adopting a session that's already open should + **focus the existing tab**, not open a duplicate. (Only offered for key-auth / + key-backed-OneKey hosts; otherwise you get a clear "needs key-based + authentication" line, not a cryptic ssh error.) +7. **Add-host UX:** the saved list shows **only hosts you added** — no auto-imported + entries. Click **+** → the "Add a host" block offers *Create a blank server* plus + on-demand `~/.ssh/config` suggestions (the list is otherwise untouched). +8. **Failure visibility:** a failed connect/open/attach now shows a red + `[zaplex] …` notice in the tab (e.g. `connection failed (…)`, `could not start + session: …`, `session ended`) instead of a blank/hung tab. + +## Expected client-log sequence (happy path) + +Filter the app log for `daemon connect` and `daemon_tty:`. In order: + +``` +daemon connect [HOST]: establishing ControlMaster +daemon connect [HOST]: checking remote-server binary +daemon connect [HOST]: binary present # or: binary missing — installing → install complete +daemon connect [HOST]: transport ready — connecting session SessionId(...) +daemon_tty: issuing OpenSession (cwd=…, shell=…, RxC) +daemon_tty: session opened, pty_session_id= +``` +After that, output streams into the tab. On a reconnect you'll also see: +``` +daemon_tty: re-attaching pty_session_id= from seq +``` + +## Failure modes → meaning → likely fix + +| Symptom / log | Meaning | Likely fix | +|---|---|---| +| Nothing daemon-related; opens a normal `ssh` tab | Host isn't taking the daemon path | session_resilience not `Persistent`, or auth isn't key (password/encrypted-key-without-agent) | +| `ControlMaster setup failed: …` | `ssh -f -N` couldn't authenticate/connect | key not in agent / wrong key_path / host unreachable / BatchMode rejected | +| `ControlMaster socket did not appear` | master backgrounded but no socket | `~/.ssh` not writable, or `-f` returned before socket bind — capture the ssh stderr | +| `remote-server binary check failed` / `install failed` | check/install over the master failed | host unsupported (libc/arch), or the master died between steps | +| `transport ready` but no `daemon_tty: session opened` | connect_session handshake or OpenSession failed | check the **remote** daemon log on the host (`~/.zaplex/remote-server/*/…` stderr); likely proxy/daemon spawn or protocol issue | +| Tab shows raw shell, **no blocks** | bootstrap didn't run | shell not bash/zsh/fish, or the init script didn't execute over the PTY — capture the first ~2 KB of session output | +| Reconnect doesn't replay | re-attach gap | capture `daemon_tty: re-attaching … from seq N` + whether output resumes | + +## Remote-side log (on the host) + +The daemon logs to its stderr (captured via the proxy). Useful lines: +`Daemon: opened session …`, `Daemon: bootstrapped session (bash)`, +`Daemon: attached conn … (replay … bytes …)`, `Daemon GC: reaped …`. + +## What to send back + +For any failure, the **last ~30 client-log lines** containing `daemon connect` / +`daemon_tty:` + what you saw on screen. If it gets to `session opened` but renders +wrong, also the first chunk of the tab's output. That's enough for me to pinpoint + +fix without a host. diff --git a/docs/superpowers/specs/2026-06-28-stage-b3-udp-transport-design.md b/docs/superpowers/specs/2026-06-28-stage-b3-udp-transport-design.md new file mode 100644 index 0000000000..93f20002d2 --- /dev/null +++ b/docs/superpowers/specs/2026-06-28-stage-b3-udp-transport-design.md @@ -0,0 +1,46 @@ +# Phase B3 Design — Native UDP Transport (mosh-grade) + +> **Status:** Design + reserved groundwork only. **NOT runtime-ready.** Created 2026-06-28. +> **Parent:** [native-remote-session-layer.md](../plans/2026-06-24-native-remote-session-layer.md) §8. +> **Honesty note:** a full mosh-grade transport is a large, networking-heavy subsystem that is **not** verifiable without a real client/host pair on a lossy/roaming network. This document is the design + the additive, compile-safe groundwork (a reserved capability name); it deliberately does **not** ship a half-working UDP datapath. + +--- + +## 1. Why B3 (and why it's last) + +B2 (Stages 2–4) already delivers the must-have: persistence, re-attach + replay, multi-session. B3 changes only the **transport beneath the session protocol** — from "SSH ControlMaster + proxy-stdio" to native UDP — to add **roaming** (survive IP changes) and **low latency** (predictive echo). The session protobuf protocol is unchanged. Order is strictly B2 → B3 (§8). + +## 2. Model (mosh-derived) + +1. **Bootstrap over SSH (reuse B2):** the existing headless ControlMaster connect (`headless_connect`) starts the daemon and, in the B3 case, also has the daemon mint a per-session **AEAD key** (e.g. AES-GCM/OCB) + a UDP port, returned over the secure SSH channel. No UDP port is open/usable without the key (mosh's security model). +2. **Switch to UDP:** thereafter the client speaks the *same* `ClientMessage`/`ServerMessage` protocol, but each datagram is AEAD-sealed under the session key and sent over UDP instead of the length-prefixed stdio stream. +3. **SSP-style state sync:** the protocol's existing monotonic `seq` (already used for ring replay) is the sync cursor — the client acks the last `seq` it has; the server sends only the delta. This is exactly the Stage 3 replay primitive, now driving steady-state UDP sync rather than just reconnect. +4. **Roaming for free:** the session is keyed by the AEAD key, not the source IP, so an IP change (Wi-Fi→cellular) just continues — the server accepts datagrams that authenticate, from any address. +5. **Predictive local echo:** client-side prediction of keystroke echo/cursor (mosh heuristic), reconciled against server confirmations. + +## 3. Where it slots in (seams) + +- **Transport trait:** `remote_server::transport::RemoteTransport` is already the abstraction (`SshTransport` is the B2 impl). B3 is a second impl, e.g. `UdpTransport`, selected at connect time. `daemon_tty` / `RemoteServerManager::connect_session` are already generic over it — no change to the session layer. +- **Capability negotiation:** `InitializeResponse.features` already carries the capability list (Stage 0). B3 adds `FEATURE_UDP_TRANSPORT` (reserved now in `zaplex_remote_session::types`). The daemon advertises it only once implemented; the client upgrades SSH→UDP only when both sides advertise it, else stays on B2 (graceful, never a hard dependency). +- **Feature gate:** a `warp_features` flag (e.g. `NativeUdpTransport`) gates the client-side upgrade attempt, so it can ship dark and be enabled per-channel. + +## 4. What's reserved now (this commit) + +- `zaplex_remote_session::types::FEATURE_UDP_TRANSPORT = "udp-transport"` — the negotiation name, documented as not-yet-advertised. `supported_features()` is unchanged (still honest: daemon does not claim UDP). + +That is the *entire* safe, additive footprint. Everything below is **remaining work**, not done here. + +## 5. Remaining work (the real subsystem — needs a real network to build/verify) + +- UDP socket lifecycle on both ends; datagram framing + MTU/fragmentation handling. +- AEAD seal/open (key from the SSH bootstrap), replay-window/nonce management. +- SSP-style delta sync loop on top of `seq` (sender keeps per-client acked-seq; resend/RTT/heartbeat; congestion/timeout policy). +- Roaming: accept authenticated datagrams from new source addresses; rebind. +- Predictive echo engine + reconciliation (the latency feel; substantial UI-side logic). +- Daemon: open UDP listener per session, hand key+port back over SSH at `OpenSession`/`Initialize`. +- Security review (key handling, nonce reuse, DoS on the open UDP port) before enabling. +- **Verification:** integration over a real lossy/roaming link — cannot be unit/headless-tested meaningfully; needs a client/host pair, packet loss injection, and IP-change testing. + +## 6. Recommendation + +Land B3 as a dedicated, separately-reviewed effort once B2 (Stages 2–4) has been exercised on real hosts (the GUI/real-host E2E). The reserved capability name keeps the door open without committing unverified code. Until then, B2's SSH-ControlMaster transport is the shipping path; it already gives persistence + re-attach (the user-visible payoff). Roaming/latency are the upside B3 adds. diff --git a/docs/superpowers/specs/2026-06-28-stage3-attach-replay-design.md b/docs/superpowers/specs/2026-06-28-stage3-attach-replay-design.md new file mode 100644 index 0000000000..6d94263400 --- /dev/null +++ b/docs/superpowers/specs/2026-06-28-stage3-attach-replay-design.md @@ -0,0 +1,45 @@ +# Stage 3 Design — Attach / Replay / Reconnect + +> **Status:** Design, created 2026-06-28. +> **Parent:** [native-remote-session-layer.md](../plans/2026-06-24-native-remote-session-layer.md) §6/§9 + Stage 1/2. +> **Goal:** the actual "survives the drop" payoff — a daemon session keeps running while the client is gone, and on reconnect the client re-attaches and replays the missed output so the grid/blocks reconstruct. + +--- + +## 1. What already holds (Stage 1/2) + +- The daemon owns the PTY + a per-session `OutputRing` (byte-capped, monotonic `seq`, `replay_from(from_seq) -> (base_seq, bytes)`). +- `deregister_connection` does **not** kill sessions — a dropped connection leaves the session running; the reader keeps appending output to the ring (the dead conn's pushes are harmlessly dropped). So output accumulates while detached. ✓ +- Client already has `attach_session(session_id, last_seq) -> SessionAttached{session_id, size, base_seq, replay}` and the `daemon_tty` event loop. + +## 2. Gaps Stage 3 closes + +| Gap | Fix | +|---|---| +| Daemon `AttachSession`/`DetachSession` are stubbed (reject `InvalidRequest`) | Real handlers (server, unix). | +| **Grace timer kills persistent sessions** — when the last conn drops, the daemon shuts down after `GRACE_PERIOD` (10 min), taking surviving sessions with it | Don't start the grace timer while live sessions exist. | +| Client doesn't re-attach after a transport reconnect | `daemon_tty` tracks `last_seq`, and on `SessionReconnected` calls `attach_session(last_seq)` + feeds `replay` through `parse_bytes`. | + +Out of scope (Stage 4): `ListSessions`/adopt, app-restart rehydration, per-host `session_resilience` res_idle GC / RAM ceiling settings. + +--- + +## 3. Increments + +- **S3a — server attach/detach + grace guard.** + - `handle_attach_session(conn_id, AttachSession{session_id, last_seq})`: look up the session; `(base_seq, replay) = ring.replay_from(last_seq)`; set `session.attached = conn_id` (re-route live output to the reconnected connection); reply `SessionAttached{session_id, size, base_seq, replay}`. Unknown id → `InvalidRequest`. + - `handle_detach_session(DetachSession{session_id})`: `session.attached = Uuid::nil()` (session keeps running; output buffers in the ring, live pushes become harmless no-ops). Notification, no reply. + - Grace guard: `deregister_connection` starts the grace timer only when **no live sessions** remain (a `has_live_sessions()` helper, cfg-gated since `sessions` is unix-only). Persistent sessions keep the daemon alive. (Detached-idle GC is Stage 4.) + - `ListSessions` stays rejected (Stage 4). + +- **S3b — headless replay test** (server_model_tests, unix, runs on xl): open a session; capture some SessionOutput; `deregister_connection` (simulate a drop) while writing more input; register a *new* connection; `AttachSession{last_seq}`; assert `SessionAttached.replay` contains the output produced while detached, and that live output now flows to the new connection. Runtime-proves the survives-drop core. + +- **S3c — client re-attach** (`daemon_tty`, compile-verified; runtime is part of the GUI/real-host E2E): track `last_seq` (`= out.seq + out.bytes.len()` of the latest SessionOutput); add a `SessionReconnected{session_id == connection_session_id}` arm that calls `attach_session(pty_session_id, last_seq)` and feeds the returned `replay` through `process_pty_bytes`. Per §9, keep persistent-session bootstrap state across disconnect rather than clearing it. NOTE: re-establishing the headless ControlMaster on an SSH drop (the daemon_tty transport) may need the orchestrator to re-run `ensure_control_master` — flagged for the E2E. + +--- + +## 4. Notes / risks + +- **seq is a byte offset** (not a message counter); `last_seq` the client sends is "everything I've already rendered". `replay_from` clamps to `base_seq` if the requested start was already evicted (ring overflow) — the client then has a gap (acceptable; the ring ceiling bounds it). +- **Re-route via `attached`**: `on_session_output` reads `session.attached` each chunk, so updating it on attach immediately redirects the live stream — no reader-task restart needed. +- Server pieces (S3a/S3b) are fully headless-testable; the client reconnect (S3c) needs the real transport-reconnect path and is validated in the GUI/real-host E2E. diff --git a/docs/superpowers/specs/2026-06-28-stage4-multisession-design.md b/docs/superpowers/specs/2026-06-28-stage4-multisession-design.md new file mode 100644 index 0000000000..3337dedcd1 --- /dev/null +++ b/docs/superpowers/specs/2026-06-28-stage4-multisession-design.md @@ -0,0 +1,35 @@ +# Stage 4 Design — Multi-Session + Lifecycle + +> **Status:** Design, created 2026-06-28. +> **Parent:** [native-remote-session-layer.md](../plans/2026-06-24-native-remote-session-layer.md) §5/§7/§12 + Stage 1–3. +> **Goal:** several sessions per host, list + adopt them, and bound their lifetime/RAM. + +--- + +## 1. Already done + +- Per-host `session_resilience` setting + DB migration + UI toggle (Stage 3b + UI). +- Per-session output ring with a byte ceiling (`RING_CEILING_BYTES = 4 MiB`, Stage 1) — a ceiling already "greift". +- Grace-timer guard so the daemon stays up while sessions live (Stage 3a). +- Proto is ready (Stage 0): `ListSessions {}`, `SessionInfo {session_id, title, cwd, alive, last_attached_epoch_millis}`, `SessionList { sessions }`. + +## 2. Increments + +- **S4a — ListSessions (server + client).** + - `Session` gains metadata: `cwd: Option`, `shell: String`, `last_attached_ms: u64` (epoch millis; 0 = never). Set on open (opener counts as attached); `last_attached_ms` refreshed on every `AttachSession`. + - `handle_list_sessions` → `SessionList` of `SessionInfo` over `self.sessions` (title derived from cwd basename else shell; `alive = true` for every registry entry — exited sessions are removed on reader-EOF/close). + - Client `list_sessions() -> SessionList` (request/response, mirrors `attach_session`). Dispatch: unix → handler; non-unix → reject. + +- **S4b — headless test** (server_model_tests, xl): open N sessions with distinct cwds → `ListSessions` → assert N entries with the right ids/cwds/alive; close one → list shrinks. + +- **S4c — detached-idle GC + RAM ceiling setting** (server, follow-up): + - Periodic sweep reaps sessions that are detached (their `attached` conn is gone) and idle (`last_attached_ms` older than `MAX_DETACHED_AGE`, default 24 h) → kill+reap+`SessionExited`. Bounds host RAM from abandoned sessions; complements the grace guard. + - Lift the ring ceiling from a constant to the per-host `session_resilience`-adjacent setting + a per-host total-RAM cap (registry-level). Wire from the persisted `ssh_servers` row. + +- **Adopt UI (GUI, user E2E):** sidebar lists `list_sessions()`; "Enter on a running session" → `attach_session(id, 0)` → full-history block. The protocol foundation (S4a) is what makes this real; the rendering is part of the GUI E2E. + +## 3. Notes + +- `alive`: registry membership == alive (the reader-EOF path and `CloseSession`/`handle_close_session` remove dead sessions and emit `SessionExited`). No `try_wait` in the read path (avoids reaping side effects mid-list). +- Timestamps via `SystemTime::now()` server-side (epoch millis); the client renders relative ages. +- S4a/S4b are fully headless-testable; the idle-GC (S4c) is testable with an injected/short max-age; the sidebar/adopt rendering is the GUI E2E. diff --git a/docs/superpowers/specs/2026-06-29-adopt-sidebar-design.md b/docs/superpowers/specs/2026-06-29-adopt-sidebar-design.md new file mode 100644 index 0000000000..aa18581a49 --- /dev/null +++ b/docs/superpowers/specs/2026-06-29-adopt-sidebar-design.md @@ -0,0 +1,81 @@ +# Adopt-Sidebar — Multi-Session UI (design) + +> Branch `feat/stage2-client-attach`. Surfaces a host's running daemon sessions +> in the SSH-manager sidebar and lets the user adopt one (attach + replay) in a +> new tab. Backend (protocol + adopt entry) is done; this is the UI + wiring. + +## Goal / key use case + +After an app restart or a transport drop, the daemon sessions on a host keep +running. The user opens the SSH-manager sidebar, sees the host's **running +sessions** listed under it (title = cwd basename / shell), clicks one, and it +opens in a new tab attached to the live session with full scrollback replay. +Also covers "open a second view of a running session" while connected. + +## What already exists + +- **Protocol:** `RemoteServerClient::list_sessions() -> SessionList` and the + daemon's `handle_list_sessions` (returns `SessionInfo { session_id, title, + cwd, alive, last_attached_epoch_millis }`). Runtime-tested. +- **Adopt entry:** `Workspace::adopt_daemon_session(server, pty_session_id, ctx)` + — creates a daemon tab in *adopt* mode (attach + replay) and connects. +- **Routing pattern:** panel emits `SshManagerPanelEvent` → `left_panel.rs` + re-emits `LeftPanelEvent` → `Workspace` handles (see `OpenSshTerminal`). + +## The gap (why it isn't just "render the list") + +The panel knows **nodes/servers** (host/user/port). The `RemoteServerManager` +keys connected sessions by **`HostId`** — which is reported by the daemon in the +initialize handshake and is **not derivable** from the saved server. So the +panel cannot, on its own, find a connected client for a node to call +`list_sessions`. And in the primary (post-restart) use case there is **no open +terminal / no existing connection** at all. + +## Architecture decision + +**Workspace orchestrates; the panel is a thin renderer.** Workspace already owns +the daemon-connect logic (`spawn_daemon_session_connect`: `ensure_control_master` +→ `check/install_binary` → `connect_session`) and learns the `HostId` on +`SessionConnected`. So: + +- **List:** the panel requests a listing for a node; Workspace ensures the + ControlMaster + connects a **list-only** session (no tab) + calls + `list_sessions`, then pushes the `Vec` back into the panel's + per-node state for rendering. Reuses the existing connect path (the running + daemon is reused; if none is running the connect spawns it, which is also what + surfaces zero sessions cleanly). +- **Adopt:** the panel emits the picked `pty_session_id`; Workspace calls + `adopt_daemon_session`. + +Routing (mirrors `OpenSshTerminal`), both directions: +- panel → `SshManagerPanelEvent::{RequestSessionList, AdoptDaemonSession}` → + `left_panel` → `LeftPanelEvent::…` → `Workspace`. +- Workspace → `panel.update(set host_sessions[node_id] = sessions)` via the + held panel handle (left_panel owns it). + +## Increments (each compiles warning-clean — wired end-to-end) + +1. **Routing + render together (vertical slice):** add the two events through + the chain; Workspace handler for `AdoptDaemonSession` → `adopt_daemon_session`; + Workspace handler for `RequestSessionList` → connect-list → push back; panel + gains `host_sessions: HashMap>` + renders session + child-rows under an **expanded** host + a per-host refresh affordance; row + click emits `AdoptDaemonSession`. (Build it as one slice so there's no + dead-code interim.) +2. **List-only connect path:** factor a `spawn_daemon_session_connect` variant + that connects without opening a tab and resolves `list_sessions`, used by the + `RequestSessionList` handler. +3. **Polish:** loading/empty/error states per host; refresh on + `SessionReconnected`; only show the expander for `session_resilience`-capable + key-auth hosts. + +## Open UX decision (before build) + +- **Fetch trigger:** on host-row **expand** (+ a manual refresh icon) — lazy, + no background connects. Alternative: auto-fetch on host-connect. +- **Scope:** any daemon-capable (key-auth, `session_resilience`) host via the + connect-to-list path (covers the post-restart case) — vs. only hosts that + already have an open daemon terminal (simpler, but misses the main use case). + +Recommended: **on-expand fetch + connect-to-list for any daemon-capable host** +(serves the post-restart adopt case, no background work). diff --git a/docs/superpowers/specs/2026-06-30-cockpit-increment1-account-usage-design.md b/docs/superpowers/specs/2026-06-30-cockpit-increment1-account-usage-design.md new file mode 100644 index 0000000000..47b814a781 --- /dev/null +++ b/docs/superpowers/specs/2026-06-30-cockpit-increment1-account-usage-design.md @@ -0,0 +1,262 @@ +# Cockpit — Increment 1 Design: Account & Usage Data Spine (Claude + Codex) + +> First increment of the zaplex **cockpit** — the "plex" half of the product: the +> overview that answers *what needs my attention, what am I spending, how loaded is +> each subscription* and (later) routes/switches work across multiple Claude + Codex +> subscriptions. This increment builds the **read-only data foundation only**; the +> UI, the live session inventory, and launch/switching are later increments. +> +> Grounded in: the on-disk footprint of the real CLIs (verified, see §3 + Appendix), +> the `claudeplex` reference TUI (`~/projects/zaplex/claudeplex/src`, Claude-only — +> Codex is net-new for us), and the existing zaplex model/watcher patterns. + +## 1. Goal & Non-goals + +**Goal.** A native Rust data layer that, for **both** Claude Code and Codex CLIs: +1. discovers the logged-in accounts / subscriptions (possibly several per provider), +2. aggregates token usage from the CLIs' own session transcripts into rolling + windows (5h block / today / week) with reset times, +3. derives **cost** (per-model pricing table) and **heat** (load vs. budget), +4. exposes the result as a `CockpitModel` singleton emitting change events, refreshed + by **file-watch** (not polling). + +This mirrors how the remote-session layer started: data/protocol first, UI later — +the proven increment style for this project. + +**Non-goals (explicitly deferred to later increments):** +- Any **UI** (account cards, heat bars, cost) — Increment 2. +- Live **session/agent inventory** + "needs attention" state — Increment 3. +- **Launch-on-freest** routing + credential-swap launching + subscription switching — Increment 4. +- **Multi-host** usage aggregation (over the daemon) + **persisted history**/charts — Increment 5. +- Reading or storing OAuth tokens/secrets (we read only token *counts* + account *metadata*). + +## 2. Why this first / mission fit + +The remote-session layer (done) ensures agents survive disconnects; the **cockpit is +the actual value proposition** ([[mission-and-reference-sources]]) — drastically +reducing mental load across many parallel Claude/Codex sessions. Everything the +cockpit shows or decides (heat bars, cost, "launch on freest", switching) is a +function of one thing: **per-account usage over time**. So the first increment is +that spine. It is also independently testable headless (no GUI, no network), exactly +like the remote-session data layer. + +Note: zaplex's inherited `AIRequestUsageModel` (`app/src/ai/request_usage_model.rs`) +was deliberately **gutted to an "unlimited" stub** (module doc lines 1-19) — the old +server-driven quota/cost concept was removed. The cockpit is its intended +replacement, not a competitor. Self-contained directive: both provider data layers +native from day 0, no Bun/`claudeplex` subprocess. + +## 3. Verified data sources + +### 3.1 Claude Code (matches `claudeplex` `discover.ts`/`collect.ts`) +- **Account discovery** = config *directories*, deduped from: a `$HOME` scan for + `.claude` and `.claude-*` (excluding `*mem/backup/bak/old/tmp/temp/observer`); + running processes carrying `CLAUDE_CONFIG_DIR=…` (so live-but-non-default accounts + are found); and `$CLAUDE_CONFIG_DIR`. A dir qualifies if it has `.claude.json` or a + `projects/` or `sessions/` subdir. (`claudeplex` `discover.ts:60-183`.) +- **Identity** from `/.claude.json` → `oauthAccount`: `emailAddress`, + `displayName`, `organizationName`, `organizationRole`, `organizationType`, + `organizationRateLimitTier`. **No tokens/expiry are read** — presence of + `oauthAccount` defines "a real account". The default dir `~/.claude` reads its + account from `~/.claude.json` in `$HOME`. (`collect.ts:461-487`.) + - Plan label: `organizationRateLimitTier` matched `max_(\d+x)` → "Max 20x"; else + `organizationType == "claude_max"` → "Max"; else strip `claude_`. +- **Usage** = parse `/projects//.jsonl` line by line; for each + line with `type=="assistant"` and `message.usage`, read `input_tokens`, + `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, + `message.model`, `timestamp`. (Verified on a real transcript — see Appendix A.) + +### 3.2 Codex (net-new — no `claudeplex` prior art; verified on disk here) +- **Account** = `~/.codex/auth.json`: `auth_mode`, `last_refresh`, + `tokens.{account_id,access_token,id_token,refresh_token}`. The email/plan are **not** + plaintext at top level — they are likely in the `id_token` JWT claims. We will + decode only the **unverified JWT payload** for `email`/plan-ish claims and **never + read or store** the token strings. (Open question §10: confirm claim names; possible + fallback to `~/.codex/*.sqlite`.) +- **Usage** = `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`. Line `type`s: + `session_meta`, `turn_context`, `response_item`, `event_msg`. Token fields present: + `input_tokens`, `output_tokens`, `cached_input_tokens`, `reasoning_output_tokens`, + `total_tokens`, and `last_token_usage` / `total_token_usage` envelopes + (`reasoning_output_tokens` is Codex-specific). (Verified — Appendix B.) +- **Multi-account**: Codex's multi-config story (a `CODEX_HOME`-style override?) is + unconfirmed — §10. + +### 3.3 Existing zaplex seams that already touch these paths +- `app/src/ai/mcp/mod.rs:49,93` — `MCPProvider::{Claude,Codex}` + `home_config_file_path()` + → `~/.claude.json`, `~/.codex/config.toml`. Reuse the provider enum + path helpers. +- `app/src/ai/mcp/file_mcp_watcher.rs` — already **watches** `~/.claude.json` and the + `~/.codex` subtree via `HomeDirectoryWatcher` and re-parses on change. This is the + template for cockpit discovery (watch, don't poll). +- `app/src/terminal/cli_agent.rs:134` — `CLIAgent::{Claude,Codex,Gemini,…}` — the + provider key to reuse. + +## 4. Data model (in `crates/zaplex_cockpit`) + +``` +Provider = CLIAgent::{Claude, Codex} // reuse existing enum +Account { provider, key /* stable "c1","c2"… by path */, config_dir, + label, email, org, role, plan_tier, is_default } +UsageEntry { ts, provider, model, input, output, cache_create, cache_read, + reasoning /* Codex */ } +WindowTotals { input, output, cache_create, cache_read, + work /* = input+output+cache_create */, total, + cost_usd, messages } +AccountUsage { account, block5h, today, week, + reset5h, reset_week, heat /* = work/budget, 0..1+ */ } +CockpitSnapshot { accounts: Vec, generated_at } +``` +`work` (excludes cheap cache reads) is the load signal for heat + later +launch-on-freest, per `claudeplex` `usage.ts:57-66`. + +## 5. Cost & heat — explicit approximations + +- **Cost** = per-model pricing table, USD per 1M tokens, with distinct + cache-write/cache-read rates, keyed by model-name substring: + `cost = (in·p_in + out·p_out + cache_create·p_cw + cache_read·p_cr) / 1e6`. + Seed from `claudeplex` `usage.ts:29-48` (opus/sonnet/haiku) **but refresh against + current Anthropic + OpenAI pricing** (use the `claude-api` skill for Anthropic + rates; Codex/OpenAI rates from their pricing page — `~/.codex/models_cache.json` + has model metadata but **no price fields**, confirmed). **Flag:** rates drift; keep + the table in one place, updatable, with an unknown-model fallback that is logged, + not silently mispriced. +- **Heat** = `work / budget` over the window, clamped for display. Budget is **not** + an API-exposed real cap (Anthropic/OpenAI don't publish per-plan token budgets); + `claudeplex` uses a flat guess (`BUDGET_5H=20M`, `BUDGET_WEEK=300M`, + `instances.ts:43-44`). We will (a) map `organizationRateLimitTier` → a per-tier + budget estimate where possible, (b) fall back to the flat guess, and (c) make both + overridable via settings/env. Document heat as an *estimate of headroom*, not a + guarantee. +- **Reset times** = ccusage-style rolling block (first activity floored to the hour; + a gap ≥ window starts a new block), computed for 5h and 7d (`usage.ts:108-120`). + +## 6. Architecture & integration + +- **New crate `crates/zaplex_cockpit`** (own-provenance naming, [[crate-naming-by-provenance]]). + Pure data layer; deps: `warpui` (Entity/SingletonEntity/ModelContext/spawner), + `ai` (CLIAgent), `warp_ssh_manager`? no — `watcher` (file-watch), `serde`/`serde_json`. + UI stays out (later in `app/src/cockpit/`). Wire like `zaplex_remote_session`: + root `Cargo.toml [workspace.dependencies]`, `app/Cargo.toml` dep gated on the + existing `local_fs` feature (disk access). +- **`CockpitModel`** — `SingletonEntity` (templates: `crates/ai/src/api_keys.rs:215` + storage-backed singleton; `crates/remote_server/src/manager.rs:478-496` for the + `spawner: ctx.spawner()` field). Holds the latest `CockpitSnapshot`; emits + `CockpitEvent::Updated`. Registered in `app/src/lib.rs` (~line 1601, after + `ApiKeyManager`) via `ctx.add_singleton_model(|ctx| CockpitModel::new(ctx))`. +- **Discovery = file-watch + reconcile tick:** + 1. Primary: wrap `BulkFilesystemWatcher` exactly like + `crates/watcher/src/home_watcher.rs` / `file_mcp_watcher.rs`; register + `~/.claude*` and `~/.codex*`. On a debounced change event, re-discover + re-parse + **off the main thread** via `ctx.spawn(async { read+parse }, |me, snap, ctx| { me.apply(snap); ctx.emit(Updated) })` + (the `file_mcp_watcher.rs:531` `spawn_config_parse` pattern; `async_fs::read_to_string`). + 2. Secondary: a low-frequency reconciliation tick (the `start_gc_timer` loop, + `app/src/remote_server/server_model.rs:2257-2285`: `background_executor().spawn` + + `spawner.spawn(|me,ctx| …).await`, break on `Err` = model dropped) — recomputes + window membership / reset times / heat decay even when no file changed (e.g. the + 5h block rolls over). ~30–60s is plenty. +- **Parsing performance** (mirror `claudeplex` `collect.ts:159-162,626`): cache parsed + transcripts by `(mtime, size)`; only parse files whose mtime is within the widest + window (week). Transcripts can be large and numerous. + +## 7. Settings & persistence + +- **`define_settings_group!(CockpitSettings, …)`** (template `app/src/settings/ssh.rs`; + register in `app/src/settings/init.rs:95`) for **scalar** policy only: + `cockpit.enabled` (bool), `cockpit.budget_5h` / `cockpit.budget_week` overrides (int, + 0 = use tier estimate), and `cockpit.switching_policy` (enum, consumed later). + Scalar settings can't hold a list of accounts — that's fine; accounts are + *discovered*, not configured. +- **Per-account pins** (custom label/color/order/hide, like `claudeplex` + `instances.json`): serialized JSON in secure storage (the `ApiKeyManager` pattern) — + **Increment 2+**, not now. +- **Persistence of history: deferred.** Increment 1 computes everything live from + transcripts (cached); no DB. If long-term trend charts are wanted later, add a + `cockpit_usage` table via the `ring_ceiling` migration pattern + (`crates/persistence/migrations/…` + `model.rs` + a repository in `zaplex_cockpit`). + +## 8. Increment plan + +- **Increment 1 (this doc) — data spine.** Crate + `CockpitModel` + Claude & Codex + account discovery + usage/cost/heat aggregation + watch+reconcile + `CockpitEvent` + + `CockpitSettings` (scalar) + headless unit tests. **No UI** beyond an optional + hidden debug command to dump the snapshot. +- **Increment 2 — UI.** Account cards (label/plan), heat bars, today/5h/week cost + + tokens, reset timers. Design-latte (the claudeplex-desktop polish: status glyphs, + load bars). Subscribes to `CockpitEvent::Updated`. +- **Increment 3 — live inventory.** Claude `/sessions/*.json` registry + pid + liveness + transcript-derived `working/needs-attention/idle`; the Codex equivalent; + "what needs my attention" surface. +- **Increment 4 — launch-on-freest + switching.** Pick lowest-`work` non-busy + account; launch the real CLI with the chosen account (env/config-dir swap; never an + API key). Multi-subscription balancing to dodge rate limits. +- **Increment 5 — multi-host + history.** Aggregate per-host snapshots over the + daemon; optional persisted history/trends. + +## 9. Test strategy (headless, no secrets) + +- **Fixtures, not real creds.** Build temp config dirs with synthetic `.claude.json` + (a fake `oauthAccount`) and fixture transcripts (Claude `.jsonl`, Codex + `rollout-*.jsonl`); assert: account discovery (count, label, plan tier), `UsageEntry` + extraction per provider (incl. Codex `reasoning_output_tokens`), window bucketing + with a **fixed `now`** (5h/today/week boundaries), cost against golden numbers, heat + = work/budget. Pin one known model's cost so a pricing-table edit is a conscious + test change. +- **Secrets:** the parser must read only token counts + account metadata; a test + asserts no token/secret field is ever surfaced. Never require a real `~/.claude`/`~/.codex`. +- Runs locally (`cargo test -p zaplex_cockpit`); no GUI/network. **Run the full + affected-crate suite, not just `-p warp`** (lesson from the remote-session work — + a sibling crate's tests silently broke when only the app crate was run). + +## 10. Risks & open questions + +1. **Codex account/plan discovery (net-new).** Confirm where email/plan live — + `id_token` JWT claims vs. a `~/.codex/*.sqlite` table. Decode only the unverified + JWT payload; never store tokens. Small spike at the start of Increment 1. +2. **Codex usage semantics.** `total_token_usage` may be cumulative-per-session vs. + `last_token_usage` per-turn — pick one consistently to avoid double-counting. + Prefer summing per-turn deltas (parity with the Claude per-message approach). +3. **Codex multi-account.** Is there a `CODEX_HOME`-style override enabling several + logins, or is it single-account? Determines whether discovery scans dirs (Claude) + or is a single `~/.codex` (Codex) in v1. +4. **Pricing & budget are approximations** (both flagged). Keep the pricing table + centralized + refresh on model launches; map tier→budget where possible, else the + flat guess, both overridable. +5. **Privacy.** Read only token counts + account metadata — **never transcript + content**. Document this prominently; it's a trust point for the product. +6. **Performance/footprint.** Many large transcripts → `(mtime,size)` cache + week + cutoff; the watcher must debounce (transcripts are appended frequently during an + active session). + +## Appendix A — Claude transcript usage block (verified) + +```json +{"type":"assistant","model":"claude-opus-4-8", + "usage":{"input_tokens":19370,"cache_creation_input_tokens":9716, + "cache_read_input_tokens":19748,"output_tokens":230, + "cache_creation":{"ephemeral_1h_input_tokens":9716,"ephemeral_5m_input_tokens":0}, + "service_tier":"standard"}} +``` + +## Appendix B — Codex session (verified) + +`~/.codex/sessions/2026/06/30/rollout-*.jsonl`; line `type`s: `session_meta`, +`turn_context`, `response_item`, `event_msg`. Token fields seen: `input_tokens`, +`output_tokens`, `cached_input_tokens`, `reasoning_output_tokens`, `total_tokens`, +`last_token_usage`, `total_token_usage`. `~/.codex/auth.json` keys: `auth_mode`, +`last_refresh`, `tokens.{access_token,account_id,id_token,refresh_token}` (values +never read/stored). + +## Appendix C — Code seams (verified, file:line) + +- Singleton + storage: `crates/ai/src/api_keys.rs:215` (ApiKeyManager); gutted + replacement target: `app/src/ai/request_usage_model.rs` (stub). +- Spawner round-trip / timer: `crates/remote_server/src/manager.rs:541-613`, + `app/src/remote_server/server_model.rs:2257-2285`. +- File-watch: `crates/watcher/src/{lib.rs:144,195,229,home_watcher.rs}`; + `app/src/ai/mcp/file_mcp_watcher.rs:531` (`spawn_config_parse`, `async_fs::read_to_string`). +- Provider enums + paths: `app/src/terminal/cli_agent.rs:134`, + `app/src/ai/mcp/mod.rs:49,93`. +- Settings: `app/src/settings/ssh.rs:5-28`, `app/src/settings/init.rs:95`, + `crates/settings/src/macros.rs:703`. +- Persistence pattern (if history later): `crates/persistence/migrations/2026-06-29-000000_add_ring_ceiling/`, + `crates/persistence/src/model.rs:1463-1497`, `crates/warp_ssh_manager/src/repository.rs`. +- Reference (Claude-only): `~/projects/zaplex/claudeplex/src/{discover,usage,collect,instances}.ts`. diff --git a/docs/superpowers/specs/2026-07-01-cockpit-native-integration-design.md b/docs/superpowers/specs/2026-07-01-cockpit-native-integration-design.md new file mode 100644 index 0000000000..1e8c87ac41 --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-cockpit-native-integration-design.md @@ -0,0 +1,152 @@ +# Cockpit — Native Integration Design (claudeplex, adapted the zaplex way) + +> **Goal.** A claudeplex user must find **everything** they had — at least parity, +> ideally more — but it must feel like claudeplex was built into zaplex *from day one*, +> not bolted on. We adapt, we don't blind-port. Precedent: the file-manager +> integration ([[filemanager-pane-mode-design]]) turned claudeplex-style "two fixed +> MC panes" into a **dynamic pane-mode on zaplex's native pane mechanism**. This doc +> does the same reframing for the cockpit. +> +> Supersedes the increment plan (§8) of the Increment-1 data-spine doc +> (2026-06-30-…); the data spine + app wiring already built ([[ui-ux-backlog-progress]], +> crate `zaplex_cockpit`, `app/src/cockpit/`) slot in unchanged as the data layer. + +## 1. The reframe (why this isn't a merge) + +claudeplex is a **cockpit wrapped around external infrastructure**: it shells out to +the `claude` CLI, keeps agents alive in **tmux**, browses hosts over its own **SFTP +commander**, and runs a **remote-control fleet** to survive lid-close. It had to build +all that because a TUI has none of it. + +**zaplex already owns every one of those primitives, natively:** + +| claudeplex builds… | …because zaplex already has it natively | +|---|---| +| tmux "remote-control fleet" (persistent, survives lid, RAM ceiling) | the **remote-session daemon** — persistent sessions, reconnect-replay, per-host **ring ceiling** ([[remote-session-arch-decision]], [[stage2-trigger-decision-option-b]]) | +| its own SFTP "Multi-host Commander" + host↔host copy | the **SSH manager** + the **file-manager pane-mode** (host↔host copy is P2/P3 there) | +| shelling out to `claude` + a parallel "agent registry" | **CLI agents as native terminal panes** (`add_tab_with_specific_agent`, the "Coding Agents" menu) — local *or* on a remote host via the daemon | +| "adopt a waiting session" | the daemon **adopt-session** feature (already built) | +| TranscriptView | **ConversationListView** (local BYOP history) + native scrollback | +| Lumen themes / bilingual / command palette | zaplex themes (Zaplex Dark), i18n (`warp.ftl`), command palette | + +So the cockpit is **not** the infrastructure — it is the **intelligence & orchestration +layer** that was missing: *which account has headroom, what needs my attention, what am +I spending, launch the next agent on the freest account.* Everything else is a **lens +over zaplex's existing panes / daemon sessions / hosts**, never a parallel world. + +This is the anti-foreign-body principle: the cockpit's "sessions" ARE zaplex panes; its +"launch" opens a native pane; its "fleet" IS the daemon; its "commander" IS the SSH +manager + FM pane. A claudeplex refugee sees all their features — expressed in zaplex's +own vocabulary. + +## 2. Parity checklist (claudeplex → native zaplex home) + +Every claudeplex feature, and where it lives natively. **Nothing is dropped.** + +| claudeplex feature | Native zaplex home | Status | +|---|---|---| +| Multi-account **load dashboard** (5h/wk usage, cost, reset, heat) | **Cockpit pane** (card grid over the data spine) | spine done; UI = this plan | +| Per-model heat (opus/sonnet bars) | data-spine extension: per-model window totals | small spine add | +| **Session monitor** (live/recent across accounts, grouped by folder, status active/monitor/waiting/stale, output tails) | **unified session inventory** = live zaplex panes + daemon sessions + discovered CLI transcripts, correlated to accounts | Increment C3 | +| **New-agent wizard** (account-by-capacity → folder, or folder → account) | native **"New Agent" launch** = `add_tab_with_specific_agent` + **account routing** (launch under the chosen/freest account's `CLAUDE_CONFIG_DIR`) + repo/worktree picker | Increment C4 | +| **Agent cockpit** (message, images, restart, kill) | it *is* a zaplex terminal pane running the agent — native drive; restart/kill/paste are pane actions | native today | +| **Adopt** waiting sessions | daemon **adopt-session** (built) surfaced from the cockpit | wire-up | +| Auto-discovery of accounts | data spine (`zaplex_cockpit::discover`) | done | +| **Multi-host Commander** (hosts, SFTP, host↔host copy, remote PTY) | SSH manager + **FM pane-mode** + remote terminal panes via daemon | via those tracks | +| **Remote-control fleet** (persistent, survives lid, RAM ceiling, serves mobile) | the **daemon session layer** (persistence + ring ceiling); mobile = MCP back-channel / mobile-companion roadmap | daemon done | +| Themes / bilingual / command palette / scriptable `--json` | zaplex themes, i18n, palette; `--json` = dump `CockpitSnapshot` (already serde) | mostly done | +| GitHub issue/PR/triage modals | agent-driven actions via the **Oz-repurpose "run on my agent"** pattern ([[…oz-repurpose…]]) + cockpit quick-actions | later increment | +| Account **drill-in** detail | cockpit pane detail/filter mode | Increment C2 | +| **Codex** (net-new; claudeplex is Claude-only) | first-class in the spine already → **more than claudeplex** | done | + +## 3. Native surfaces — three glance tiers (main-pane-vs-sidebar principle) + +Principle (user, approved 2026-07-01): the **main-area pane** is for the *overview / +deep dive* — big, roomy, nothing cramped; the **sidebar** is for what you want in view / +one click away *while working* — because nothing in the main area gets switched or +overlaid (your terminals/agents stay put). Applied, the cockpit is three tiers, each on +an existing zaplex mechanism — not one monolith. + +### 3.1 Toolbelt icon — the ambient indicator (always visible, even collapsed) +The Cockpit toolbelt icon carries an **attention badge**: colour = highest account heat, +count = "needs-you" sessions. You read the fleet's state without opening anything. + +### 3.2 Cockpit sidebar (toolbelt tab) — quick-access while working +A docked toolbelt tab (like SSH/Files), one click / hotkey, **no main-area switch**: +- compact **account list** — mini heat bar / %, plan badge, live dot, "needs-you" marker; +- **live-session quick-list** (active/waiting) → click **focuses the existing pane** + (never a duplicate); +- **quick-launch** — "New Agent" (on freest / on account) opens a pane in the main area. +The operational daily-driver. This tier + the badge ARE the "glanceable" half — there is +no separate tab-bar widget. + +### 3.3 Cockpit pane (main area) — the overview / deep dive, pane-mode like FM +Opened when you want the big picture (`PaneContent`, tab/split/promotable, multi-instance): +the full responsive **account-card grid** (`minmax(320px,1fr)`) — 5h/today/week cost+tokens +matrix, all **heat bars** (5h/wk, later per-model), **reset timers**, session breakdown, +aggregate header — plus account **drill-in** and (later) history/trends. The roomy +dashboard both references use. + +### 3.4 The live inventory IS zaplex's sessions (the key adaptation) +Both the sidebar's session list and the pane's session sections draw from ONE unified +inventory — never a parallel registry: +- **Live panes** — terminal panes running a CLI agent (local or remote), with real state + (running / waiting-for-input / idle) from the terminal/agent model. +- **Daemon sessions** — persistent remote sessions (attached/detached) across hosts. +- **Historic** — sessions discovered from CLI transcripts, for accounts with no live pane. +Correlated to account (by the config-dir/env launched under) + host. "Open" = **focus the +existing pane** / adopt a daemon session / open its transcript (ConversationListView) — +never a duplicate. This is the anti-foreign-body core. + +## 4. Actions, expressed natively +- **Launch agent** → the existing CLI-agent launch, extended with **account routing**: + "New Agent on " or **"on freest"** (lowest `work`, non-busy) sets the launch + env (`CLAUDE_CONFIG_DIR` / Codex equivalent) — never an API key. Opens a native pane + (local or, via the daemon, on a chosen host). +- **Adopt** a waiting/detached session → daemon adopt (built). +- **Open / drive** → focus the pane; drive = the terminal itself. +- **Commander** (host↔host files, remote PTY) → SSH manager + FM pane + daemon terminal. +- **Fix/ask/GitHub** → the Oz-repurpose "run this on my agent" one-shot. + +## 5. Revised increment plan (native-first) + +- **C1 — Data spine.** ✅ done (`zaplex_cockpit` + `CockpitModel` + watch + settings). +- **C2 — Sidebar + pane (spine-backed, read-only). Sidebar first (approved).** Both + surfaces share the data + card/formatting components (formatting helpers headless- + tested). Order: (a) the Cockpit **sidebar** toolbelt tab — account glance + (plan/heat/cost/reset) + the toolbelt icon with the heat part of the attention badge; + (b) the Cockpit **pane** — the full card grid + drill-in over the same components; + + the `--json`/debug snapshot dump. +- **C3 — Unified live inventory.** Correlate live panes + daemon sessions + transcript + history into one account-scoped session list with status; powers the sidebar's + **session quick-list + "needs-you" marker**, the pane's session sections, and the + badge's count; adds per-model heat. "open/adopt/focus" wiring. +- **C4 — Orchestration.** New-Agent launch with **account routing** + **launch-on-freest**; + repo/worktree picker; remote-host target via the daemon. +- **C5 — Multi-host aggregation + history.** Aggregate per-host snapshots over the + daemon; optional persisted trends; GitHub quick-actions via the agent. + +Each increment is a lens/action on existing primitives, shippable on its own, and keeps +the "not a foreign body" test: *would this have looked out of place if claudeplex had +been built into zaplex from the start?* + +## 6. Open questions +1. **Ambient indicator placement** — tab bar vs. a status strip vs. the left toolbelt? + (Needs a build to feel out.) +2. **Cockpit pane vs. also a compact toolbelt summary** — do we want both, or is the + pane + ambient indicator enough? +3. **Account routing mechanics for Codex** — Claude uses `CLAUDE_CONFIG_DIR`; the Codex + multi-account/override story is still unconfirmed (spine §10). +4. **Per-model heat** needs the spine to split window totals by model — small change, + do it in C2/C3. + +## Appendix — reference files +- claudeplex TUI dashboard: `~/projects/zaplex/claudeplex/src/render.ts` (card grid + `boxCard`/`gridRow`, `MIN_CARD=54`), `ui.ts` (`heat()` thresholds), `usage.ts`, + `instances.ts` (launch-on-freest), `hosts.ts`/`remote.ts` (commander/fleet). +- claudeplex-desktop: `views/OverviewMain.tsx` (the dashboard we mirror), + `AccountsSidebar.tsx`, `AccountDetail.tsx`, `components/LoadBar.tsx` (heat colours), + `shell/{Shell,ActivityBar,StatusBar}.tsx`, `views/{SessionsMain,NewAgentWizard,CockpitMain}.tsx`. +- zaplex natives: `crates/zaplex_cockpit` + `app/src/cockpit/` (spine), + daemon session layer, `app/src/ssh_manager/`, the FM pane-mode design, CLI agents + (`app/src/terminal/cli_agent.rs`), `ConversationListView`. diff --git a/docs/superpowers/specs/2026-07-01-filemanager-pane-mode-design.md b/docs/superpowers/specs/2026-07-01-filemanager-pane-mode-design.md new file mode 100644 index 0000000000..3df3db5913 --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-filemanager-pane-mode-design.md @@ -0,0 +1,230 @@ +# File Manager — Pane-Mode Design (the "MC, but better") + +> zaplex's integrated file manager is **not** a hardwired dual-pane MC widget and +> **not** a sidebar tool. It is a **view mode of a pane**: any terminal pane (local +> or remote) can toggle terminal ⇄ file-manager, keeping the same connection +> context. The classic MC two-column layout emerges when the user puts two panes +> into file-manager mode — it is a *superset* of MC (N file managers, any split/tab +> arrangement, even across tabs), not a copy of it. +> +> Grounded in the existing pane system (`app/src/pane_group/pane/`, trait +> `PaneContent` at `pane/mod.rs:590`), the existing SFTP backend abstraction +> (`app/src/sftp_manager/sftp_backend.rs`: `LiveSftpBackend` over `zap_sftp::Sftp`, +> `InMemorySftpBackend` over the local FS), the existing `sftp_pane.rs` / +> `file_pane.rs` / `terminal_pane.rs`, and the sidebar `server_file_browser.rs` +> (which this design retires as the primary entry point). See +> [[filemanager-pane-mode-design]], [[no-quick-wins-sustainable-only]], +> [[remote-session-arch-decision]]. + +## 1. Goal & Non-goals + +**Goal.** Make "file manager" a first-class **pane mode** so the user can: +1. toggle any terminal pane to a file-manager view (and back) **in place**, keeping + the pane's connection context (local cwd, or the host's SSH/daemon session); +2. arrange any number of file-manager panes in any split/tab layout (1 beside a + shell, 2 side-by-side = MC, 3+ anywhere, across tabs); +3. copy/move files between **any** active file-manager panes — including ones in + hidden tabs — with a clear picture of source and destination paths; +4. operate it fast: discoverable for beginners (icons/menus), keyboard-driven for + pros (hotkeys, MC-style function keys). + +**Non-goals (explicitly out / deferred):** +- Not a separate MC application, not a modal overlay, not a sidebar panel. The + sidebar `ServerFileBrowser` tab is retired as the primary FM entry point + (may remain as a quick "jump to host root" shortcut, TBD). +- No new remote transport: remote FM reuses the existing SFTP over the host's SSH + connection. (Daemon-native file ops are a possible later optimization, not P1.) +- No cloud/object-store anything (self-contained; see [[self-contained-audit-findings]]). +- Archive browsing (zip/tar as a directory), FTP/S3/other protocols — later, if ever. + +## 2. Why this shape (mission fit) + +zaplex is a premium remote-dev terminal for people running many local + remote +sessions ([[mission-and-reference-sources]]). A file manager that is *a mode of the +pane you already have* means: split your shell, toggle, and you are browsing exactly +where you were — including on the remote host the shell is connected to. The +cd-continuity and the "any layout" freedom are things MC structurally cannot do. +It also reuses the pane system (splits, tabs, promotion, drag-drop) instead of +adding a bespoke widget, which keeps the UX consistent and the surface area small. + +## 3. Core model + +### 3.1 Connection context (the linchpin) + +Introduce an explicit **`PaneContext`** shared by terminal and file-manager panes: + +``` +enum PaneContext { + Local, // the machine zaplex runs on; has a cwd + Remote { node_id: String }, // an SSH host node; browsed over that host's SFTP +} +``` + +- A terminal pane already *has* such a context implicitly (a local PTY, or a + daemon/SSH session to a host — see `terminal_pane.rs`, which already forwards + "open remote file" events to the workspace). We make it explicit and attach the + **current directory** to it (from the shell's cwd where available; otherwise last + browsed path or home). +- Toggling terminal ⇄ FM **preserves `PaneContext` and the current directory.** This + is the killer feature: `split → toggle` opens the FM rooted at the shell's cwd. + +### 3.2 Pane mode toggle + +Panes are trait objects (`PaneContent` + a `BackingView`), so "mode" is realized by +swapping the pane's content implementation **within the same pane slot** (same +`PaneId`, same position in the `PanesLayout`), preserving focus and neighbors: + +- `TerminalPane` ⇄ `FileManagerPane`, both carrying the same `PaneContext`. +- Toggling is reversible and cheap; the terminal session is **not** torn down when + showing the FM (it is suspended/hidden), so toggling back is instant and the shell + keeps running. (Trade-off vs. memory noted in §10.) +- Also allow **opening** an FM pane directly (new split / new tab) with a chosen + context, without starting from a terminal. + +### 3.3 The `FileManagerPane` + +A `FileManagerPane` = one `FsBackend` (see §4) + view state: +- current path (+ history: back/forward/up), selection set, sort, column config + (name/size/mtime/perms/owner), quick-filter/incremental-search, show-hidden. +- editable **path bar / breadcrumb**; **connection identity** shown in the header + (local hostname or remote host) so the user always knows *which machine*. +- registered in the **FM registry** (§5) on attach, deregistered on close. + +## 4. Filesystem backend abstraction + +Reuse and generalize the existing SFTP backend split. Today `sftp_backend.rs` has a +backend trait with `LiveSftpBackend` (real `zap_sftp::Sftp`) and `InMemorySftpBackend` +(local FS, used for tests). Promote this to a first-class **`FsBackend`** used by the +FM pane: + +``` +trait FsBackend { // async, cancelable + async fn list_dir(&self, path) -> Vec; + async fn stat/mkdir/rename/remove/read_link/set_perms(...); + fn open_read(&self, path) -> ByteStream; // for transfers + fn open_write(&self, path) -> ByteSink; + fn capabilities(&self) -> FsCaps; // perms? symlinks? rename-across-dir? +} +``` + +- `LocalFsBackend` — the local machine (generalize `InMemorySftpBackend` into a real + local backend). +- `SftpFsBackend` — wraps the host's existing SFTP connection (from + `LiveSftpBackend`); **no new connection** — piggybacks the SSH session zaplex + already holds for that host. +- Backends are keyed by `PaneContext`, so two FM panes on the same host share one + SFTP channel. + +## 5. Copy / move target model (the "better than MC" part) + +### 5.1 FM registry + +A singleton **`FileManagerRegistry`** model tracks every open `FileManagerPane`: +`{ pane_id, PaneContext, current_path (live), is_visible, tab_id }`, emitting change +events. This is what makes cross-tab targeting possible. + +### 5.2 The copy/move dialog + +When the user copies/moves the current selection, the dialog offers **all *other* +active FM panes** as destinations — including ones in hidden tabs — each shown as: + +``` + ● devhost : /srv/www/app/releases (visible, this tab) + ○ laptop : ~/projects/zaplex (hidden tab "build") + ○ prod-db : /var/backups (hidden tab "ops") + + Enter path manually… ★ Bookmarks… +``` + +Each row shows the destination's **live current path** — so, exactly like MC's +"other panel", the user sees precisely where files will land. + +### 5.3 Default target heuristic (MC speed for the MC case) + +- If the current tab has **exactly one other visible FM pane**, it is the + pre-selected default target → the classic MC flow: **F5 = copy, F6 = move**, one + keypress, no dialog (dialog still reachable for retargeting). +- Otherwise the dialog opens with the pick-list. So the common side-by-side case is + as fast as MC, and the general case (many FMs, cross-tab) is still one clear pick. +- Manual path entry + bookmarks always available. + +## 6. Transfer engine (the real backend work) + +Cross-context transfers are where the substance is; this gets its own module with a +proper progress/queue UI: + +- **local ↔ remote**: SFTP up/down over the host's connection. +- **remote-A ↔ remote-B**: relay through local (stream A→local→B); direct + server-to-server only if same host. +- **local ↔ local / same-remote**: native rename when possible, else copy+delete. +- A **transfer queue** with: per-item + aggregate progress, throughput/ETA, pause / + cancel, and **conflict resolution** (skip / overwrite / rename / newer-only, + apply-to-all). Must handle large files (streamed, not buffered), directory + recursion, symlinks, and permission/owner preservation per `FsCaps`. +- Transfers survive tab switches (they live in the queue model, not the pane) and + surface in a small global activity indicator. + +## 7. UX + +**Discoverable (beginners):** +- Pane header control: an icon toggle **terminal ⇄ files**. +- Right-click pane menu: "Switch to File Manager" / "Switch to Terminal". +- Command palette entries; the `+` menu can offer "File Manager" as a pane/tab. + +**Fast (pros) — proposed hotkeys (final binding TBD, must not clash):** +- Toggle current pane terminal⇄FM. +- **Toggle *all* panes in the current tab at once** — the "make this an MC" gesture. +- `F5` copy / `F6` move to default target; `F7` mkdir; `F8`/`Del` delete; `F2` + rename; `Tab` cycle focus between FM panes; `Enter` open; `Backspace` up. +- Optional classic MC function-key legend along the pane bottom (toggleable). + +**Visual quality:** +- The **active pane** gets an accent border (uses the new Zaplex-Dark accent + `#6C82F2`) — source vs. destination must be unambiguous. +- Selection, focus, and the "this is a remote path" state are visually distinct. +- **Drag & drop** between FM panes is the mouse-first path and reuses the exact + copy/move + conflict logic from §5–6. + +## 8. Phasing + +- **P1 — FM as a pane mode.** `PaneContext`, terminal⇄FM toggle, `FileManagerPane` + over `FsBackend` (Local + Sftp), single-pane browse + in-place ops + (open/rename/mkdir/delete/chmod), path bar, header identity. No cross-pane + transfer yet. Retire the sidebar browser as the primary entry. +- **P2 — Targeting + transfers.** FM registry, copy/move dialog with cross-tab + destinations + live paths, default-target heuristic (F5/F6), and the transfer + engine for **local↔remote** with the progress/conflict queue. +- **P3 — Full power.** remote↔remote relay, drag & drop, "toggle all panes in tab" + gesture, MC function-key parity/legend, bookmarks. + +## 9. Testability + +- `FsBackend` is mockable (the in-memory/local backend already exists) → browse + logic, ops, and the transfer engine are testable **headless** (no GUI, no network), + matching the project's data-first, unit-tested increment style. +- Registry + default-target heuristic are pure logic → unit tests over synthetic + pane/tab layouts. +- Conflict resolution + large-file streaming get dedicated tests (fixtures). + +## 10. Open questions + +1. **Suspended terminal on toggle**: keep the PTY alive+hidden (instant toggle, + more memory) vs. detach on toggle (cheaper, slower return)? Proposal: keep alive; + for daemon-backed remote sessions it survives regardless. +2. **Sidebar `ServerFileBrowser`**: retire fully, or keep as a lightweight "open FM + pane at host root" launcher? +3. **Exact hotkeys** and whether to ship the MC function-key legend on by default. +4. **remote↔remote direct** (SFTP server-to-server) where supported vs. always relay. +5. Do we expose a "sync/mirror two panes" power feature later, or keep scope to + copy/move? + +## Appendix — code anchors + +- Pane system / trait: `app/src/pane_group/pane/mod.rs` (`PaneContent` :590, + `AnyPaneContent` :663), layout `app/src/pane_group/mod.rs` (`PanesLayout` :773, + `PaneDragDropLocation` :789). +- Existing panes: `pane/terminal_pane.rs` (already forwards remote-file events), + `pane/sftp_pane.rs`, `pane/file_pane.rs`, `pane/ssh_server_pane.rs`. +- SFTP backend to generalize: `app/src/sftp_manager/sftp_backend.rs` + (`LiveSftpBackend`, `InMemorySftpBackend`), ops in `sftp_manager/sftp_ops.rs`, + `zap_sftp::Sftp`. +- Sidebar browser being retired as primary: `app/src/workspace/view/server_file_browser.rs`. diff --git a/docs/superpowers/specs/2026-07-01-oz-repurpose-fix-with-your-agent-design.md b/docs/superpowers/specs/2026-07-01-oz-repurpose-fix-with-your-agent-design.md new file mode 100644 index 0000000000..64c290d9fd --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-oz-repurpose-fix-with-your-agent-design.md @@ -0,0 +1,121 @@ +# Oz Repurpose — "Fix with " Design + +> Oz (Warp's built-in MAA agent) is being removed as a *distinct in-app agent* and +> de-branded (done). But its genuinely useful **contextual one-shot actions** — +> "here's an error/some context, fix/explain it" — should be **kept and routed to the +> user's own CLI agent** (Claude Code / Codex / Gemini / …), not to an in-app agent. +> That's the repurpose: zaplex supplies the *context and the button*; the user's +> chosen agent does the work. See [[self-contained-audit-findings]], +> [[branding-warpify-to-zaplexify]], [[filemanager-pane-mode-design]] (same +> "pane/connection" spirit), [[no-quick-wins-sustainable-only]]. +> +> Grounded in the real mechanism: `CLIAgent` (`app/src/terminal/cli_agent.rs:134`, +> 14 agents) with `command_prefix()` (:154) and `is_cli_agent_installed()` (:615, +> backed by a background install scan, `CLIAgentInstallEvent`); tab launch via +> `Workspace::add_tab_with_specific_agent(agent)` (`app/src/workspace/view.rs:3879`, +> runs `execute_command_or_set_pending(agent.command_prefix())`); the in-app +> agent-mode path `PaneGroup::add_terminal_pane_in_agent_mode(initial_query, …)` +> (`app/src/pane_group/mod.rs:6014`, which *prefills* the input); and the existing +> per-CLI harness command-builders (`app/src/ai/agent_sdk/driver/harness/` — +> claude_code, gemini). + +## 1. Goal & Non-goals + +**Goal.** Preserve the value of Oz's contextual actions by sending their context as a +prompt to **the user's installed CLI agent**: +1. **Flagship (P1):** the invalid-`settings.toml` banner's action (today + `FixSettingsWithOz`, button labelled "Fix with AI") launches the user's CLI agent + with a "fix this settings error" prompt instead of the in-app agent-mode. +2. A reusable **"ask my agent about this context"** primitive that other call sites + (command errors, command output) can adopt later. + +**Non-goals.** +- Not reviving/branding an in-app agent. The Oz/agent-mode infra stays flag-off and + preserved-as-template (it is *not* the target of these actions anymore). +- No new agent runtime — we shell out to the user's own CLI, which is the sanctioned + external dependency. +- Not touching the flag-gated agent-mode "Fix with"/permission prompts (they live in + the preserved template and aren't release-visible). + +## 2. The mechanism (verified) + +- **Which agents exist / are installed:** iterate `CLIAgent` variants, filter by + `is_cli_agent_installed(agent)`. Each has `command_prefix()` (e.g. `claude`, + `codex`, `gemini`). +- **Launching one:** `add_tab_with_specific_agent(agent)` opens a terminal tab and + runs the agent's command. It currently takes **no prompt**. +- **Delivering the prompt — two options:** + - **(A) Prefill (recommended for P1):** open the agent, then place the prompt in + the input so the user reviews and presses Enter. Uniform across all 14 agents (no + per-CLI flag knowledge), keeps the human in the loop, and mirrors the existing + `add_terminal_pane_in_agent_mode(initial_query)` prefill pattern. + - **(B) One-shot arg (later):** `claude -p ""` / `codex ""` etc. + Faster but the exact flag differs per CLI — best done through the harness + command-builders that already encode this, and only for agents that support it. + +## 3. Agent selection UX + +Given the set of installed agents `A`: +- **|A| == 0** → don't silently fall back to the in-app agent. Show a small hint / + action: "Install a coding agent (Claude Code, Codex, …)" linking to setup. (Reuse + the existing install affordance that the `+` "Coding Agents" menu already uses.) +- **|A| == 1** → button reads **"Fix with "** (e.g. "Fix with Claude"); one + click launches it with the prompt. +- **|A| > 1** → button reads **"Fix with AI ▾"** and opens a small picker listing the + installed agents ("Fix with Claude / Codex / Gemini"). Selecting one launches it. +- **Optional (P3):** a `default_coding_agent` setting to skip the picker; when set, + the button uses it directly and the picker is available via a caret. + +There is no "preferred coding agent" setting today (agents are chosen per-tab), so P1 +uses the installed-set rule above; P3 may add the setting. + +## 4. Design of the reusable primitive + +Introduce one workspace action that supersedes `FixSettingsWithOz`: + +``` +AskAgent { + prompt: String, // the fully-formed instruction incl. the context + agent: Option, // None → resolve via the selection rule (§3) + label_hint: AskAgentKind // Settings | CommandError | Explain | … (for analytics/UX) +} +``` + +Handler: resolve `agent` (or run the picker), then open a tab with that agent and +**prefill** the prompt (option A). `FixSettingsWithOz` becomes a thin caller that +builds the settings prompt and dispatches `AskAgent`. This keeps the existing button +wiring (`settings_view/settings_file_footer.rs`, `workspace/view.rs`) and just swaps +the target from agent-mode to the user's CLI. + +Prompt for the flagship (unchanged wording, retargeted): +`My settings.toml file has an error: {error_description}. Please fix it.` + +## 5. Phasing + +- **P1 — Flagship.** Repurpose `FixSettingsWithOz` → `AskAgent` (settings prompt), + installed-set selection (0/1/many), prefill delivery, dynamic button label. Remove + the last dependence of a release-visible action on the in-app agent-mode. +- **P2 — Generalize.** Adopt `AskAgent` for command-error / block-output context + ("Fix this command", "Explain this output") via right-click / block actions. +- **P3 — Polish.** One-shot delivery via harness for agents that support it; + `default_coding_agent` setting; support launching the agent against the **active + remote host** (tie-in with the daemon session layer) rather than always local. + +## 6. Testability & verification + +- Agent-selection logic (0/1/many, label text) is pure → unit-testable with a mocked + installed-set. +- The launch/prefill path touches terminal-tab creation + input prefill and needs a + **visual test build** to confirm timing (the prompt must land in the agent's input + after the CLI starts). So P1 lands together with a test DMG, not blind. + +## Appendix — code anchors +- `app/src/terminal/cli_agent.rs` — `CLIAgent` (:134), `command_prefix` (:154), + `is_cli_agent_installed` (:615), `CLIAgentInstallEvent` (:577). +- `app/src/workspace/view.rs` — `add_tab_with_specific_agent` (:3879), + `FixSettingsWithOz` handler (:19183), banner button (:17653). +- `app/src/workspace/action.rs` — `AddSpecificAgentTab` (:162), `FixSettingsWithOz` + (:622). +- `app/src/pane_group/mod.rs` — `add_terminal_pane_in_agent_mode` (:6014, prefill). +- `app/src/settings_view/settings_file_footer.rs` — the button (:252). +- `app/src/ai/agent_sdk/driver/harness/` — per-CLI command builders (claude_code, gemini). diff --git a/docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md b/docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md new file mode 100644 index 0000000000..8d9afec5c4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-self-contained-cleanup-plan.md @@ -0,0 +1,56 @@ +# Self-contained cleanup & template-preservation plan + +> Remove everything **Warp/Oz-specific and out-of-scope** from the shipped UI, but +> **preserve reusable pane/sidebar scaffolding as templates** so the later +> claudeplex migration (which may well need an agent panel again) does not have to +> rebuild from scratch. Guiding rule from the user (2026-07-01): *"alles Spezifische +> zu WARP und OZ entfernen, aber bestimmte Pane- und Sidebar-Elemente als Vorlagen +> aufheben."* +> +> Grounded in [[self-contained-audit-findings]]. The audit's headline still holds: +> there is **no live Warp-cloud client left** — this is de-Warp-ification of +> localized features, not cutting active cloud links. + +## Guiding principle: disable-don't-delete for anything with template value + +Three buckets. Default to the *least destructive* that still yields a clean, +self-contained shipped UI. + +1. **HIDE + PRESERVE (template):** keep the code compiled and revivable, remove it + from the shipped UI, strip Warp/Oz branding. Mechanism, in order of preference: + - It's already **runtime-flag-gated** and the flag is off in release → nothing to + do but confirm + de-brand. (Most Oz/Agent-Mode UI is here: `FeatureFlag::AgentMode` + is **not** in `RELEASE_FLAGS`, so it's off in release; it shows mainly in dev.) + - It's pushed unconditionally into a list → **stop pushing it**, but keep the enum + variant + its render match-arms (needed for exhaustive matches) and its module. + - Add a short `// PRESERVED TEMPLATE (not shipped): …` doc comment at the module + head so intent is obvious to the next person. +2. **HARD-DELETE:** only truly dead cloud stubs with **zero template value** — no UI, + no revival intent. (pricing/Stripe stub, telemetry send layer, `request_usage_model` + always-unlimited stub, unreachable session-sharing transport, orphaned Drive + sharing/ACL type shells.) Separate, later commits; each must keep the build green. +3. **KEEP (already local, in scope):** ConversationListView (local BYOP history), CLI + coding agents, voice, ambient agents, external_secrets, autoupdate/changelog. + +## Element-by-element + +| Element | Bucket | Action | +|---|---|---| +| **Zaplex Drive** left-panel tab | HIDE+PRESERVE | Stop pushing `ToolPanelView::ZaplexDrive` in `compute_left_panel_views` (view.rs:18839). Keep the enum variant, all `ZaplexDrive` render arms, `app/src/drive/`, `app/src/cloud_object/` as the **sidebar-panel template**. | +| **Drive** menu/keybindings (`ToggleWarpDrive`) | HIDE+PRESERVE | Remove/de-list the menu item + keybinding entry; keep the action code. | +| **Oz "Agent" `+`-menu entry / Agent Mode** | HIDE+PRESERVE | Already `FeatureFlag::AgentMode`-gated (off in release). Confirm off; this is the **agent-panel template** for the future BYOP panel. | +| **Oz branding** ("Fix with Oz", "Oz needs permission", `cli_command_name="oz"`, agent name) | DE-BRAND | Replace Oz-specific user-visible strings with neutral wording; the "Fix with …" flow is repurposed later to the user's own agent (separate increment). | +| **Left-panel bar order** | REORDER | Put the remote-dev core first: SSH → Server Files → Project → Global Search → Conversations → Skills. Fix `unwrap_or(ZaplexDrive)` fallbacks → `SshManager`. | +| **pricing/Stripe, telemetry send, request_usage, session-sharing transport, Drive ACL shells** | HARD-DELETE | Later dedicated commits; keep build green each step. | +| **zap_sync (Gist), models.dev fetch, resource_center warp.dev links** | FLAG (own decision) | Not in this pass; surfaced in [[self-contained-audit-findings]] for a separate call. | + +## Order of execution (step 2, low-risk first) + +1. **Left-panel bar**: drop Drive tab + reorder (this commit). ✔ low risk, visible. +2. **A1 Save** dirty-tracking; **A2** add-mode vs list distinction. +3. **Oz de-branding** sweep (strings only; no infra removal). +4. Drive menu item / keybinding de-listing. +5. Hard-delete the zero-value dead stubs (bucket 2), one green commit each. +6. Oz **repurpose** → "Fix with " — its own spec + implementation. + +Each step keeps the build green and the preserved templates compiling. diff --git a/docs/zaplex-concept.md b/docs/zaplex-concept.md index 1a8485d31e..d731263f69 100644 --- a/docs/zaplex-concept.md +++ b/docs/zaplex-concept.md @@ -102,7 +102,7 @@ Die Schicht, die Zap aus konzeptionellem Grund weglässt (Roadmap: „single acc - **RAM-Governor für die Fleet** — harter Ceiling, pro Session (~330 MB für Claude; Codex-Footprint bei Umsetzung messen, eigener Wert). - **Adopt-by-session-id** — Session, die in einer anderen Shell gestartet wurde, hier weiterführen (beide Provider). - **MC-style Dual-Pane File Manager** — Host↔Host-Copy ohne scp, weil `warp_files` Single-Pane ist. *(Provider-unabhängig — gehört zur MC-Hälfte.)* -- **Multiplexer-kompatibles Zapify** (zaplex' Shell-Integration, Pendant zu Warps „Warpify") — den Mehrwert (Blocks, Prompt-Status, Command-Status, Completions) auch *innerhalb* eines vom User betriebenen `tmux`/`screen`/`byobu` liefern, statt ihn dort abzuschalten. *(Provider-unabhängig — Terminal-/MC-Hälfte; Details §3.5.)* +- **Multiplexer-kompatibles Zapify** (zaplex' Shell-Integration, Pendant zu Warps „Zaplexify") — den Mehrwert (Blocks, Prompt-Status, Command-Status, Completions) auch *innerhalb* eines vom User betriebenen `tmux`/`screen`/`byobu` liefern, statt ihn dort abzuschalten. *(Provider-unabhängig — Terminal-/MC-Hälfte; Details §3.5.)* - **Native Session-Resilienz + mosh** — interaktive Remote-Sessions überleben Verbindungsabbrüche (Deckel zu, Akku leer, Netz-/Tailscale-Roaming), nahtloses Re-Attach mit erhaltener Historie, plus mosh-Eigenschaften (UDP-Roaming, predictive local echo). Macht externes `byobu` + `mosh` überflüssig. Pro Verbindung in den Settings schaltbar. *(Provider-unabhängig; Details §3.5.)* ### 3.3 Datenschicht — nativ in Rust, claudeplex als Vorlage @@ -138,18 +138,18 @@ Die rot markierte Zelle ist der einzige bewusste Asymmetrie-Punkt: Codex hat kei **Drei Lücken im heutigen Warp/Zap-Verhalten** (1–2 am Code verifiziert, 3 als Beobachtung): -1. **Warpify ⟂ Multiplexer.** Wer auf dem Host bereits in `tmux`/`screen`/`byobu` sitzt, verliert die Warpify-Features. Zaps SSH-Warpify startet einen **eigenen, privaten** `tmux -Lwarp -CC` (Control-Mode, eigener Socket — `app/assets/bundled/ssh/bash_zsh/warpify_ssh_session.sh`, `app/src/terminal/model/ansi/mod.rs`), der nicht mit dem Multiplexer des Users komponiert; der Ausfall ist sogar als Hook `RemoteWarpificationIsUnavailable` kodiert (`app/src/terminal/model/ansi/dcs_hooks.rs`). -2. **Keine Persistenz interaktiver Sessions.** SSH läuft über das native `ssh`-Binary + ControlMaster (`crates/warp_ssh_manager/`, `app/src/remote_server/ssh_transport.rs`). Reconnect ist nur für transiente Blips ausgelegt (`MAX_RECONNECT_ATTEMPTS = 2`, `RECONNECT_DELAY = 2s` — `crates/remote_server/src/manager.rs`); Deckel-zu/Akku-leer überschreiten das sofort. tmux dient im Code **nur** dem Warpify-Bootstrap, **nicht** der Persistenz (Feature-Flag `SSHTmuxWrapper`). -3. **Maus-Bedienung bricht im Multiplexer weg.** Beim Zugriff auf einen `byobu`-Host reagiert in Warp die Maus nicht mehr (User-Beobachtung). Ursache offen — entweder die deaktivierte Warpify-Integration oder fehlendes Mouse-Mode-Passthrough (Terminal sendet SGR-Mouse-Reporting nur, wenn der Multiplexer `mouse on` hat und die Mouse-Mode-Sequenzen durchreicht). Der Track-A-Spike klärt die Ursache. **Unabhängig davon gilt für zaplex als harte Anforderung: Maus-Bedienung (Klick, Selektion, Scroll) muss in jeder Remote-/Multiplexer-Session gewährleistet sein — auch wenn Zapify deaktiviert oder nicht verfügbar ist.** „Ehrlich degradieren" (§2.3) heißt hier: Komfort-Features (Blocks/Hooks) dürfen fehlen, die Maus nie. +1. **Zaplexify ⟂ Multiplexer.** Wer auf dem Host bereits in `tmux`/`screen`/`byobu` sitzt, verliert die Zaplexify-Features. Zaps SSH-Zaplexify startet einen **eigenen, privaten** `tmux -Lwarp -CC` (Control-Mode, eigener Socket — `app/assets/bundled/ssh/bash_zsh/zaplexify_ssh_session.sh`, `app/src/terminal/model/ansi/mod.rs`), der nicht mit dem Multiplexer des Users komponiert; der Ausfall ist sogar als Hook `RemoteZaplexificationIsUnavailable` kodiert (`app/src/terminal/model/ansi/dcs_hooks.rs`). +2. **Keine Persistenz interaktiver Sessions.** SSH läuft über das native `ssh`-Binary + ControlMaster (`crates/warp_ssh_manager/`, `app/src/remote_server/ssh_transport.rs`). Reconnect ist nur für transiente Blips ausgelegt (`MAX_RECONNECT_ATTEMPTS = 2`, `RECONNECT_DELAY = 2s` — `crates/remote_server/src/manager.rs`); Deckel-zu/Akku-leer überschreiten das sofort. tmux dient im Code **nur** dem Zaplexify-Bootstrap, **nicht** der Persistenz (Feature-Flag `SSHTmuxWrapper`). +3. **Maus-Bedienung bricht im Multiplexer weg.** Beim Zugriff auf einen `byobu`-Host reagiert in Warp die Maus nicht mehr (User-Beobachtung). Ursache offen — entweder die deaktivierte Zaplexify-Integration oder fehlendes Mouse-Mode-Passthrough (Terminal sendet SGR-Mouse-Reporting nur, wenn der Multiplexer `mouse on` hat und die Mouse-Mode-Sequenzen durchreicht). Der Track-A-Spike klärt die Ursache. **Unabhängig davon gilt für zaplex als harte Anforderung: Maus-Bedienung (Klick, Selektion, Scroll) muss in jeder Remote-/Multiplexer-Session gewährleistet sein — auch wenn Zapify deaktiviert oder nicht verfügbar ist.** „Ehrlich degradieren" (§2.3) heißt hier: Komfort-Features (Blocks/Hooks) dürfen fehlen, die Maus nie. **Architektur-Entscheidung (festgelegt 2026-06-24): Option 1 — nativer Persistenz-Layer.** zaplex **besitzt** einen eigenen nativen, persistenten Remote-Session-Layer und komponiert **nicht** mit vom User eingerichteten Multiplexern (tmux/byobu/screen) — gleiche Grundhaltung wie Warp, aber mit *echter* Persistenz (Warps remote-server überlebt den SSH-Drop, hat aber keine persistente Session-ID über App-Restart). Damit ist **Track B das Rückgrat** (Must-Have); **Track A** („Zapify im User-Multiplexer") entfällt als Primärpfad und kommt höchstens *optional/später*, eng gescoped auf plain `tmux` — **nicht** byobu/screen. -*Begründung aus dem Track-A-Upstream-Spike (2026-06-24):* Upstream gibt es nichts zu übernehmen — zap-Issue [#132](https://github.com/zerx-lab/zap/issues/132) ist ein stale Research-Issue ohne Code, Warps tmux-Warpify-Pfad ist offiziell **deprecated** (zugunsten der remote-server-Binary), der Maus-in-tmux-Bug [warpdotdev/Warp #5541](https://github.com/warpdotdev/Warp/issues/5541) ist „not planned" geschlossen. Die Deprecation ist eine **Produkt-/Wartungs-Entscheidung, keine technische Unmöglichkeit** — iTerm2s `tmux -CC`-Control-Mode-Integration beweist die Lösbarkeit seit ~2011. Compose mit fremden Configs (byobu-UI, Oh-My-Tmux ist von Warp als inkompatibel gelistet, `screen` hat gar keinen Control-Mode) wäre eine dauerhafte Wartungssteuer und kämpft gegen die Architektur — deshalb besitzt zaplex den Layer selbst. Die **Maus-Garantie** (Lücke 3) wird über diesen besessenen Layer erfüllt: zaplex kontrolliert die Remote-Session-Struktur end-to-end, statt SGR-Mouse durch einen fremden Multiplexer durchzureichen (der Warp-Maus-Bug rührt von der tmux-Schachtelung, nicht vom Warpify-Status). +*Begründung aus dem Track-A-Upstream-Spike (2026-06-24):* Upstream gibt es nichts zu übernehmen — zap-Issue [#132](https://github.com/zerx-lab/zap/issues/132) ist ein stale Research-Issue ohne Code, Warps tmux-Zaplexify-Pfad ist offiziell **deprecated** (zugunsten der remote-server-Binary), der Maus-in-tmux-Bug [warpdotdev/Warp #5541](https://github.com/warpdotdev/Warp/issues/5541) ist „not planned" geschlossen. Die Deprecation ist eine **Produkt-/Wartungs-Entscheidung, keine technische Unmöglichkeit** — iTerm2s `tmux -CC`-Control-Mode-Integration beweist die Lösbarkeit seit ~2011. Compose mit fremden Configs (byobu-UI, Oh-My-Tmux ist von Warp als inkompatibel gelistet, `screen` hat gar keinen Control-Mode) wäre eine dauerhafte Wartungssteuer und kämpft gegen die Architektur — deshalb besitzt zaplex den Layer selbst. Die **Maus-Garantie** (Lücke 3) wird über diesen besessenen Layer erfüllt: zaplex kontrolliert die Remote-Session-Struktur end-to-end, statt SGR-Mouse durch einen fremden Multiplexer durchzureichen (der Warp-Maus-Bug rührt von der tmux-Schachtelung, nicht vom Zaplexify-Status). **Stoßrichtungen (unter dieser Entscheidung):** -- **Track A — *(optional / später, kein Primärpfad)* Zapify im User-Multiplexer.** **Begriff (festgelegt):** Zaps Shell-Integration heißt upstream „Warpify"; unsere namensregel-konforme Variante heißt **Zapify** (`zaplex_*`). Wo wir das Verhalten neu bauen/erweitern, sprechen wir von Zapify; „Warpify" bleibt nur als Name des bestehenden Zap/Warp-Mechanismus und in vorhandenen Code-Pfaden (`app/src/terminal/warpify/…`) stehen. — Inhaltlich: den DCS/OSC-Hook-Strom (OSC `9277`–`9280`, `dcs_hooks.rs`) durch einen **vom User betriebenen** Multiplexer transportieren, statt einen eigenen `-Lwarp`-tmux danebenzustellen. Offene Punkte: (a) ~~Upstream-Stand prüfen~~ — **vom Spike beantwortet:** nichts upstream zu übernehmen, also selbst bauen (falls überhaupt verfolgt). (b) Passthrough-Framing (`tmux allow-passthrough`, `screen` restriktiver) — passieren die Hook-Sequenzen ungeschädigt? (c) Pro-Pane-Zuordnung der Blocks (Pane-ID als Hook-Dimension). (d) Modus „bestehende `$TMUX`/`STY`/byobu-Umgebung erkennen und darin bootstrappen". (e) Maus-Mode-Passthrough — warum bricht die Maus im `byobu`-Host weg, und wie bleibt SGR-Mouse-Reporting (`1006`/`1002`/`1003`) durch den Multiplexer erhalten (Maus ist harte Anforderung, siehe Lücke 3). +- **Track A — *(optional / später, kein Primärpfad)* Zapify im User-Multiplexer.** **Begriff (festgelegt):** Zaps Shell-Integration heißt upstream „Zaplexify"; unsere namensregel-konforme Variante heißt **Zapify** (`zaplex_*`). Wo wir das Verhalten neu bauen/erweitern, sprechen wir von Zapify; „Zaplexify" bleibt nur als Name des bestehenden Zap/Warp-Mechanismus und in vorhandenen Code-Pfaden (`app/src/terminal/zaplexify/…`) stehen. — Inhaltlich: den DCS/OSC-Hook-Strom (OSC `9277`–`9280`, `dcs_hooks.rs`) durch einen **vom User betriebenen** Multiplexer transportieren, statt einen eigenen `-Lwarp`-tmux danebenzustellen. Offene Punkte: (a) ~~Upstream-Stand prüfen~~ — **vom Spike beantwortet:** nichts upstream zu übernehmen, also selbst bauen (falls überhaupt verfolgt). (b) Passthrough-Framing (`tmux allow-passthrough`, `screen` restriktiver) — passieren die Hook-Sequenzen ungeschädigt? (c) Pro-Pane-Zuordnung der Blocks (Pane-ID als Hook-Dimension). (d) Modus „bestehende `$TMUX`/`STY`/byobu-Umgebung erkennen und darin bootstrappen". (e) Maus-Mode-Passthrough — warum bricht die Maus im `byobu`-Host weg, und wie bleibt SGR-Mouse-Reporting (`1006`/`1002`/`1003`) durch den Multiplexer erhalten (Maus ist harte Anforderung, siehe Lücke 3). - **Track B — *(Rückgrat / Primärpfad)* Native Session-Resilienz + mosh.** Eingebauter Mechanismus: mehrere Sessions pro Host, Überleben von Verbindungsabbrüchen mit nahtlosem Re-Attach, plus mosh-Eigenschaften (UDP-Transport mit State-Sync, predictive local echo, Roaming über IP-Wechsel). Umsetzungsoptionen, aufsteigend: - **B1: `mosh` orchestrieren** — zaplex ruft `mosh`/`mosh-server` am Host auf (setzt mosh am Host voraus; mosh allein persistiert nicht über Server-Restart). Schneller Latenz-/Roaming-Gewinn. - **B2: Eigener zaplex-Session-Daemon** — baut auf dem vorhandenen `remote-server`-Daemon auf (überlebt SSH-Drop bereits, `crates/remote_server/`), erweitert um persistente Session-IDs über App-Restart, Output-Replay-Buffer und Re-Attach-Protokoll (erweitert die `Connecting→Initializing→Connected→Reconnecting`-Zustandsmaschine). @@ -157,7 +157,7 @@ zaplex **besitzt** einen eigenen nativen, persistenten Remote-Session-Layer und **Reihenfolge:** B2 zuerst (Persistenz ist der schmerzhafteste Mangel und das Must-Have, baut auf vorhandenem `remote-server`-Code auf) → B3 (nativer mosh-grade UDP-Transport, sauber integriert) → B1 (mosh-Orchestrierung nur, falls der eigene Transport später kommt). Track A ist **kein** Primärpfad mehr (siehe Architektur-Entscheidung) und kommt nur optional/später. -**Pro-Verbindung-Setting (beide Tracks).** Host-Profile liegen in `SshServerInfo` (`crates/warp_ssh_manager/src/types.rs`) ↔ Tabelle `ssh_servers` (`crates/persistence/src/model.rs`), CRUD in `repository.rs`. Additiv erweiterbar um z. B. `zapify_multiplexer: Off|UseExisting|Managed` (Track A) und `session_resilience: Off|PersistOnly|PersistPlusMosh` (Track B): Felder am Struct + Diesel-Migration + CRUD + SSH-Manager-Panel. Default konservativ (aus). **Ehrlich degradieren**, wo Host-Voraussetzungen fehlen (kein tmux/mosh, alte tmux-Version — vorhandene Hooks `TmuxNotInstalled`/`UnsupportedTmuxVersion`, Auto-Install-Ansatz `SshTmuxInstaller` wiederverwenden). Globaler Default bleibt über die Warpify-Settings steuerbar (`app/src/terminal/warpify/settings.rs`); der Per-Host-Toggle überschreibt ihn. +**Pro-Verbindung-Setting (beide Tracks).** Host-Profile liegen in `SshServerInfo` (`crates/warp_ssh_manager/src/types.rs`) ↔ Tabelle `ssh_servers` (`crates/persistence/src/model.rs`), CRUD in `repository.rs`. Additiv erweiterbar um z. B. `zapify_multiplexer: Off|UseExisting|Managed` (Track A) und `session_resilience: Off|PersistOnly|PersistPlusMosh` (Track B): Felder am Struct + Diesel-Migration + CRUD + SSH-Manager-Panel. Default konservativ (aus). **Ehrlich degradieren**, wo Host-Voraussetzungen fehlen (kein tmux/mosh, alte tmux-Version — vorhandene Hooks `TmuxNotInstalled`/`UnsupportedTmuxVersion`, Auto-Install-Ansatz `SshTmuxInstaller` wiederverwenden). Globaler Default bleibt über die Zaplexify-Settings steuerbar (`app/src/terminal/zaplexify/settings.rs`); der Per-Host-Toggle überschreibt ihn. **Akzeptanz:** (A) zaplex liefert Blocks/Prompt/Completions in Remote-Sessions über den **eigenen** persistenten Layer — ohne dass der User `byobu`/`tmux` aufsetzen muss; ein optionaler Compose-Modus (plain `tmux`) wäre Kür, kein Akzeptanzkriterium. (B) Eine interaktive Session überlebt Deckel-zu/Akku-leer/Netzwechsel mit nahtlosem Re-Attach und erhaltener Historie; über Tailscale-Roaming bleibt das Tippgefühl latenzarm. (C) **Maus-Bedienung** (Klick, Selektion, Scroll) funktioniert in jeder Remote-/Multiplexer-Session zuverlässig — unabhängig vom Zapify-Status (harte Anforderung, nicht degradierbar). Ergebnis: kein externes `byobu` + `mosh` mehr nötig. diff --git a/resources/linux/arch/app/warp.sh.template b/resources/linux/arch/app/warp.sh.template index d056fbe53a..bb8aa18264 100644 --- a/resources/linux/arch/app/warp.sh.template +++ b/resources/linux/arch/app/warp.sh.template @@ -4,8 +4,8 @@ XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config} # Allow users to override command-line options if [[ -f $XDG_CONFIG_HOME/@@PACKAGE_NAME@@-flags.conf ]]; then - WARP_USER_FLAGS="$(grep -v '^#' $XDG_CONFIG_HOME/@@PACKAGE_NAME@@-flags.conf)" + ZAPLEX_USER_FLAGS="$(grep -v '^#' $XDG_CONFIG_HOME/@@PACKAGE_NAME@@-flags.conf)" fi # Launch -exec @@OPT_DIR@@/@@BINARY_NAME@@ $WARP_USER_FLAGS "$@" +exec @@OPT_DIR@@/@@BINARY_NAME@@ $ZAPLEX_USER_FLAGS "$@" diff --git a/script/analyze-prompt-cache.ps1 b/script/analyze-prompt-cache.ps1 index 6e126d5a3f..a116e93dfc 100644 --- a/script/analyze-prompt-cache.ps1 +++ b/script/analyze-prompt-cache.ps1 @@ -1,30 +1,29 @@ #requires -Version 5.1 <# .SYNOPSIS - 解析 Zap BYOP prompt cache 命中率(基于 chat_stream.rs::generate_byop_output - 在每次流末打印的 `[byop-cache]` 日志行)。 + Parse Zaplex BYOP prompt cache hit rate (based on `[byop-cache]` log lines printed at the end of each stream by chat_stream.rs::generate_byop_output). .DESCRIPTION - 1. 自动定位 Zap 日志文件:`%LOCALAPPDATA%\zap\Zap\data\logs\zap.log` - 2. grep 形如下面格式的行: + 1. Automatically locate Zaplex log file: `%LOCALAPPDATA%\zap\Zaplex\data\logs\zaplex.log` + 2. grep lines matching the format below: [byop-cache] prompt_tokens=N cache_read=R (X.X%) cache_create=W (Y.Y%) model=M compaction=L - 其中 compaction= 是 P2-16 添加的可选字段(none / inactive / active(hidden=N)) - 3. 按 model 分组聚合,输出每个模型的: - - 请求次数 - - 平均 cache_read ratio (主要命中指标) - - 平均 cache_create ratio (写入指标,首请求会高,后续应低) - - 总 prompt tokens / 总 cache_read tokens / 总 cache_create tokens - - 压缩相关请求统计(P2-16) - 4. 提供"对比模式"(-Tail N) 仅看最近 N 条记录,适合做 A/B + where compaction= is an optional field added by P2-16 (none / inactive / active(hidden=N)) + 3. Aggregate by model, output for each model: + - Request count + - Average cache_read ratio (primary hit indicator) + - Average cache_create ratio (write indicator, high on first request, should be low on subsequent) + - Total prompt tokens / total cache_read tokens / total cache_create tokens + - Compression-related request statistics (P2-16) + 4. Provide "comparison mode" (-Tail N) to only see recent N records, suitable for A/B comparison .PARAMETER LogPath - 自定义日志路径。默认从 Zap 标准位置查找。 + Custom log path. Defaults to looking in Zaplex's standard location. .PARAMETER Tail - 只分析最近 N 条 [byop-cache] 行(默认全部)。 + Only analyze recent N [byop-cache] lines (default: all). .PARAMETER Watch - 持续 tail 日志,实时打印新出现的命中率行(Ctrl+C 退出)。 + Continuously tail the log, print new hit rate lines in real-time (Ctrl+C to exit). .EXAMPLE .\analyze-prompt-cache.ps1 @@ -33,13 +32,13 @@ .EXAMPLE .\analyze-prompt-cache.ps1 -Watch .EXAMPLE - .\analyze-prompt-cache.ps1 -LogPath "D:\backup\zap.log" + .\analyze-prompt-cache.ps1 -LogPath "D:\backup\zaplex.log" .NOTES - 需要 Zap 启用 INFO 级日志(`[byop-cache]` 是 log::info!)。 - 若没有任何 `[byop-cache]` 行: - - 上游 provider 没返回 cache 字段(DeepSeek/Ollama 隐式缓存可能就是 0) - - 或者 RUST_LOG 把 INFO 过滤了 + Zaplex must have INFO level logging enabled (`[byop-cache]` is log::info!). + If there are no `[byop-cache]` lines: + - Upstream provider didn't return cache field (DeepSeek/Ollama implicit caching may be 0) + - Or RUST_LOG filtered out INFO level #> [CmdletBinding()] param( @@ -50,42 +49,42 @@ param( $ErrorActionPreference = 'Stop' -# ---------- 1. 定位日志 ---------- +# ---------- 1. Locate log ---------- function Resolve-ZapLog { param([string]$Override) if ($Override) { if (-not (Test-Path -LiteralPath $Override)) { - throw "指定的日志路径不存在: $Override" + throw "The specified log path does not exist: $Override" } return (Resolve-Path -LiteralPath $Override).Path } $candidates = @() if ($env:LOCALAPPDATA) { - # 当前版本路径(`crates/simple_logger/src/manager.rs::log_directory_path` Windows 分支) - $candidates += (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'zap\Zap\data\logs\zap.log') - # 备选(以前版本的路径) - $candidates += (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'zap\Zap\data\zap.log') - $candidates += (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'zap\Zap\zap.log') + # Current version path (Windows branch of `crates/simple_logger/src/manager.rs::log_directory_path`) + $candidates += (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'zap\Zaplex\data\logs\zaplex.log') + # Alternatives (paths from earlier versions) + $candidates += (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'zap\Zaplex\data\zaplex.log') + $candidates += (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'zap\Zaplex\zaplex.log') } if ($env:APPDATA) { - $candidates += (Join-Path -Path $env:APPDATA -ChildPath 'zap\Zap\data\logs\zap.log') - $candidates += (Join-Path -Path $env:APPDATA -ChildPath 'zap\Zap\data\zap.log') + $candidates += (Join-Path -Path $env:APPDATA -ChildPath 'zap\Zaplex\data\logs\zaplex.log') + $candidates += (Join-Path -Path $env:APPDATA -ChildPath 'zap\Zaplex\data\zaplex.log') } foreach ($c in $candidates) { if ($c -and (Test-Path -LiteralPath $c)) { return (Resolve-Path -LiteralPath $c).Path } } throw @" -未找到 Zap 日志文件。请检查以下位置或用 -LogPath 显式指定: +Zaplex log file not found. Please check the following locations or explicitly specify with -LogPath: $($candidates -join "`n ") -若 Zap 还没运行过,先启动一次再来跑此脚本。 +If Zaplex hasn't run yet, start it once before running this script. "@ } -# ---------- 2. 解析单行 ---------- -# 行格式(单行,可能因终端宽度被换行,但 log crate 自带换行只在末尾): +# ---------- 2. Parse line ---------- +# Line format (single line, may wrap due to terminal width, but log crate only has line breaks at end): # [byop-cache] prompt_tokens=12345 cache_read=10000 (81.0%) cache_create=200 (1.6%) model=claude-opus-4-7 compaction=none -# compaction=字段是 P2-16 添加的,取值:none / inactive / active(hidden=N)。 -# 为了兼容老日志,compaction 字段设为可选。 +# compaction= field is added by P2-16, values: none / inactive / active(hidden=N). +# For backward compatibility with old logs, compaction field is optional. $cacheLineRegex = [regex]'\[byop-cache\]\s+prompt_tokens=(?\d+)\s+cache_read=(?\d+)\s+\(\s*(?[\d\.]+)%\)\s+cache_create=(?\d+)\s+\(\s*(?[\d\.]+)%\)\s+model=(?\S+?)(?:\s+compaction=(?\S+))?$' function Parse-CacheLine { @@ -101,44 +100,44 @@ function Parse-CacheLine { ReadPct = [double]$m.Groups['read_pct'].Value CreatePct = [double]$m.Groups['create_pct'].Value Model = $m.Groups['model'].Value - # P2-16: 压缩状态. 取值: ''(老日志) / 'none' / 'inactive' / 'active(hidden=N)' + # P2-16: Compression status. Values: '' (old logs) / 'none' / 'inactive' / 'active(hidden=N)' Compaction = $compactionRaw Raw = $Line } } -# ---------- 3. 聚合输出 ---------- +# ---------- 3. Aggregate output ---------- function Format-Summary { param([System.Collections.IList]$Records) if ($Records.Count -eq 0) { - Write-Host '没有匹配到任何 [byop-cache] 行。' -ForegroundColor Yellow + Write-Host 'No [byop-cache] lines matched.' -ForegroundColor Yellow Write-Host @' -可能原因: - 1. 还没用 BYOP 路径发起过请求(Zap 启动后没和 AI 对话过) - 2. 上游 provider 没返回 cache 字段(DeepSeek/Ollama 服务端隐式缓存) - 3. RUST_LOG 把 INFO 级日志过滤了 - 检查启动环境变量 +Possible reasons: + 1. No requests made via BYOP path yet (Zaplex started but haven't chatted with AI) + 2. Upstream provider didn't return cache field (DeepSeek/Ollama server-side implicit caching) + 3. RUST_LOG filtered out INFO level logs - check startup environment variables -排查步骤: - $env:RUST_LOG = 'info' # 启动 Zap 前设置 - 在 Zap 中向 AI 发 2 条消息(同一对话),让其调起 BYOP - 然后重跑本脚本 +Troubleshooting steps: + $env:RUST_LOG = 'info' # Set before starting Zaplex + Send 2 messages in Zaplex to AI (same conversation) to trigger BYOP + Then run this script again '@ -ForegroundColor Yellow return } Write-Host '' - Write-Host '========== Zap BYOP Prompt Cache 命中率分析 ==========' -ForegroundColor Cyan - Write-Host ("总匹配行数: {0}" -f $Records.Count) + Write-Host '========== Zaplex BYOP Prompt Cache Hit Rate Analysis ==========' -ForegroundColor Cyan + Write-Host ("Total matched lines: {0}" -f $Records.Count) - # P2-16: 压缩相关汇总 + # P2-16: Compression-related summary $compactionActive = @($Records | Where-Object { $_.Compaction -like 'active*' }) if ($compactionActive.Count -gt 0) { - Write-Host (" └─ 其中走压缩路径: {0} 条" -f $compactionActive.Count) -ForegroundColor DarkYellow + Write-Host (" └─ Of which took compression path: {0} records" -f $compactionActive.Count) -ForegroundColor DarkYellow } Write-Host '' - # 按 model 分组 + # Group by model $byModel = $Records | Group-Object Model $byModel | ForEach-Object { @@ -155,55 +154,55 @@ function Format-Summary { $globalCreatePct = if ($sumPrompt -gt 0) { 100.0 * $sumCreate / $sumPrompt } else { 0.0 } Write-Host ("Model: {0}" -f $model) -ForegroundColor Green - Write-Host (" 请求数: {0}" -f $n) - Write-Host (" 总 prompt tokens: {0:N0}" -f $sumPrompt) - Write-Host (" 总 cache_read: {0:N0} ({1:F1}% of total)" -f $sumRead, $globalReadPct) - Write-Host (" 总 cache_create: {0:N0} ({1:F1}% of total)" -f $sumCreate, $globalCreatePct) - Write-Host (" 平均 read ratio: {0:F1}% <- 主要命中率指标(>=20% 算正常,>=50% 优秀)" -f $avgReadPct) - Write-Host (" 平均 create ratio:{0:F1}% <- 应该随轮数下降" -f $avgCreatePct) - - # 趋势分析(轮数 vs read ratio):看是否随对话推进命中率上升 + Write-Host (" Request count: {0}" -f $n) + Write-Host (" Total prompt tokens: {0:N0}" -f $sumPrompt) + Write-Host (" Total cache_read: {0:N0} ({1:F1}% of total)" -f $sumRead, $globalReadPct) + Write-Host (" Total cache_create: {0:N0} ({1:F1}% of total)" -f $sumCreate, $globalCreatePct) + Write-Host (" Avg read ratio: {0:F1}% <- Primary hit indicator (>=20% normal, >=50% excellent)" -f $avgReadPct) + Write-Host (" Avg create ratio: {0:F1}% <- Should decrease with rounds" -f $avgCreatePct) + + # Trend analysis (rounds vs read ratio): check if hit rate increases with conversation progress if ($n -ge 3) { $trend = $rs | ForEach-Object -Begin { $i = 0 } -Process { $i++ $marker = if ($_.Compaction -like 'active*') { '*' } else { '' } "{0}{1}:{2:F0}%" -f $i, $marker, $_.ReadPct } - Write-Host (" Read ratio 趋势: {0}" -f ($trend -join ' -> ')) + Write-Host (" Read ratio trend: {0}" -f ($trend -join ' -> ')) if ($rs | Where-Object { $_.Compaction -like 'active*' }) { - Write-Host (" (* = 走压缩路径,该轮 cache miss 是预期)") -ForegroundColor DarkGray + Write-Host (" (* = took compression path, cache miss this round is expected)") -ForegroundColor DarkGray } } Write-Host '' } - # 全局健康度判断 + # Global health check $allReadPct = ($Records | Measure-Object ReadPct -Average).Average - Write-Host '----------- 全局健康度 -----------' -ForegroundColor Cyan + Write-Host '----------- Global Health -----------' -ForegroundColor Cyan if ($allReadPct -ge 50) { - Write-Host ("✅ 全局平均命中率 {0:F1}% - 优秀" -f $allReadPct) -ForegroundColor Green + Write-Host ("✅ Global avg hit rate {0:F1}% - excellent" -f $allReadPct) -ForegroundColor Green } elseif ($allReadPct -ge 20) { - Write-Host ("⚠️ 全局平均命中率 {0:F1}% - 正常,但有提升空间" -f $allReadPct) -ForegroundColor Yellow + Write-Host ("⚠️ Global avg hit rate {0:F1}% - normal, but room for improvement" -f $allReadPct) -ForegroundColor Yellow } else { - Write-Host ("❌ 全局平均命中率 {0:F1}% - 偏低,可能有 prefix 不稳定问题" -f $allReadPct) -ForegroundColor Red - Write-Host ' 排查方向: 检查 system prompt 是否含每请求都变的字段,MCP tools 顺序是否稳定' + Write-Host ("❌ Global avg hit rate {0:F1}% - low, possible prefix instability" -f $allReadPct) -ForegroundColor Red + Write-Host ' Troubleshoot: Check if system prompt contains fields that vary per request, check if MCP tools order is stable' } if ($compactionActive.Count -gt 0) { $nonCompactionRecords = @($Records | Where-Object { $_.Compaction -notlike 'active*' }) if ($nonCompactionRecords.Count -gt 0) { $nonCompactionAvg = ($nonCompactionRecords | Measure-Object ReadPct -Average).Average - Write-Host ("ℹ️ 排除压缩轮后平均命中率 {0:F1}% (n={1})" -f $nonCompactionAvg, $nonCompactionRecords.Count) -ForegroundColor DarkCyan + Write-Host ("ℹ️ Avg hit rate excluding compression rounds {0:F1}% (n={1})" -f $nonCompactionAvg, $nonCompactionRecords.Count) -ForegroundColor DarkCyan } } } -# ---------- 4. 主流程 ---------- +# ---------- 4. Main process ---------- $logFile = Resolve-ZapLog -Override $LogPath -Write-Host "日志路径: $logFile" -ForegroundColor DarkGray +Write-Host "Log path: $logFile" -ForegroundColor DarkGray if ($Watch) { - Write-Host '进入 watch 模式,Ctrl+C 退出。新增的 [byop-cache] 行将实时输出:' -ForegroundColor Cyan + Write-Host 'Entering watch mode, Ctrl+C to exit. New [byop-cache] lines will be output in real-time:' -ForegroundColor Cyan Get-Content -LiteralPath $logFile -Wait -Tail 0 | ForEach-Object { $rec = Parse-CacheLine $_ if ($rec) { @@ -219,7 +218,7 @@ if ($Watch) { return } -# 静态分析(一次性扫描) +# Static analysis (one-time scan) $records = New-Object System.Collections.ArrayList Get-Content -LiteralPath $logFile -ReadCount 1000 | ForEach-Object { foreach ($line in $_) { @@ -232,7 +231,7 @@ if ($Tail -gt 0 -and $records.Count -gt $Tail) { $records = [System.Collections.ArrayList]::new( $records.GetRange($records.Count - $Tail, $Tail) ) - Write-Host "(只统计最近 $Tail 条)" -ForegroundColor DarkGray + Write-Host "(Only counting recent $Tail records)" -ForegroundColor DarkGray } Format-Summary -Records $records diff --git a/script/deploy_remote_server b/script/deploy_remote_server index 2d6c43c933..0f4126ffca 100755 --- a/script/deploy_remote_server +++ b/script/deploy_remote_server @@ -110,7 +110,7 @@ case "$PROFILE_MODE" in esac FEATURES="release_bundle,crash_reporting,standalone,agent_mode_debug" -WARP_BIN="warp" +ZAPLEX_BIN="warp" BINARY_NAME="oz-local" REMOTE_DIR=".warp-local/remote-server" @@ -124,10 +124,10 @@ case "$CARGO_PROFILE" in OUTPUT_DIR="$CARGO_TARGET_DIR/$TARGET/$CARGO_PROFILE" ;; esac -BUILT_BINARY="$OUTPUT_DIR/$WARP_BIN" +BUILT_BINARY="$OUTPUT_DIR/$ZAPLEX_BIN" echo "==> Building Oz CLI for $TARGET (profile=$CARGO_PROFILE)" -echo " Binary: $WARP_BIN -> $BINARY_NAME" +echo " Binary: $ZAPLEX_BIN -> $BINARY_NAME" echo " Features: $FEATURES" echo "" @@ -137,7 +137,7 @@ CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \ CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C symbol-mangling-version=v0" \ cargo build \ -p warp \ - --bin "$WARP_BIN" \ + --bin "$ZAPLEX_BIN" \ --target "$TARGET" \ --profile "$CARGO_PROFILE" \ --features "$FEATURES" diff --git a/script/linux/bundle b/script/linux/bundle index a5296b895d..2dfd148283 100755 --- a/script/linux/bundle +++ b/script/linux/bundle @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Builds a Zap binary and bundles it up for distribution. +# Builds a Zaplex binary and bundles it up for distribution. set -e @@ -170,16 +170,16 @@ mkdir -p "$OUT_DIR" # APP_NAME here must match the value used in Rust as the # application name; see app/src/channel.rs. # -# WARP_BIN is the name of the binary produced by cargo; +# ZAPLEX_BIN is the name of the binary produced by cargo; # BINARY_NAME is the desired name of the binary in the final package. if [[ $RELEASE_CHANNEL = "local" ]]; then - WARP_BIN="warp" + ZAPLEX_BIN="warp" BINARY_NAME="warp-local" APP_NAME="WarpLocal" FEATURES="$FEATURES,agent_mode_debug" export HANDLE_MARKDOWN=1 elif [[ $RELEASE_CHANNEL = "dev" ]]; then - WARP_BIN="dev" + ZAPLEX_BIN="dev" BINARY_NAME="warp-dev" APP_NAME="WarpDev" FEATURES="$FEATURES,agent_mode_debug" @@ -187,26 +187,26 @@ elif [[ $RELEASE_CHANNEL = "dev" ]]; then FEATURES="$FEATURES,jemalloc_pprof" export HANDLE_MARKDOWN=1 elif [[ $RELEASE_CHANNEL = "preview" ]]; then - WARP_BIN="preview" + ZAPLEX_BIN="preview" BINARY_NAME="warp-preview" APP_NAME="WarpPreview" FEATURES="$FEATURES,preview_channel" elif [[ $RELEASE_CHANNEL = "stable" ]]; then - WARP_BIN="stable" + ZAPLEX_BIN="stable" BINARY_NAME="warp" - APP_NAME="Zap" + APP_NAME="Zaplex" elif [[ $RELEASE_CHANNEL = "oss" ]]; then - WARP_BIN="zap-oss" - BINARY_NAME="zap-oss" - APP_NAME="Zap" - # OSS channel 使用本地 crash reporting,不启用 release 默认特性集合。 + ZAPLEX_BIN="zaplex" + BINARY_NAME="zaplex" + APP_NAME="Zaplex" + # OSS channel uses local crash reporting, does not enable release default feature set. FEATURES="release_bundle" fi # Artifact-specific binary naming if [[ "$ARTIFACT" == "cli" ]]; then # For CLI artifacts, use oz instead of warp as the binary name. The OSS - # channel is an exception: its CLI command name is `zap-oss`, matching + # channel is an exception: its CLI command name is `zaplex`, matching # `Channel::cli_command_name` in the Rust source. if [[ $RELEASE_CHANNEL != "oss" ]]; then BINARY_NAME="${BINARY_NAME/warp/oz}" @@ -221,15 +221,15 @@ elif [[ "$ARTIFACT" == "app" ]]; then fi if [[ $RELEASE_CHANNEL = "oss" ]]; then - # OSS(Zap) bundle id 与运行时 app id (`dev.zap.Zap`, 见 - # `app/src/bin/oss.rs` / Cargo bundle metadata) 对齐, 否则 Linux 桌面 - # (尤其是 Wayland) 会按 desktop-file ID 而非 StartupWMClass 关联窗口, - # 导致 icon 找不到. - BUNDLE_ID="dev.zap.Zap" + # OSS (Zaplex) bundle id aligns with runtime app id (`dev.zaplex.Zaplex`, see + # `app/src/bin/oss.rs` / Cargo bundle metadata), otherwise Linux desktop + # (especially Wayland) will associate windows by desktop-file ID instead of StartupWMClass, + # causing icons to not be found. + BUNDLE_ID="dev.zaplex.Zaplex" else BUNDLE_ID="dev.warp.$APP_NAME" fi -EXECUTABLE_PATH="$CARGO_TARGET_OUTPUT_DIR/$WARP_BIN" +EXECUTABLE_PATH="$CARGO_TARGET_OUTPUT_DIR/$ZAPLEX_BIN" DEBUG_EXECUTABLE_PATH="$EXECUTABLE_PATH.debug" # Note that this variable must be set (and exported!) before we compile the @@ -250,23 +250,23 @@ fi # then exit. We use this script to invoke `cargo check` to ensure that we are # using the same feature flags and profile that we would be using in production. if [[ "$CHECK_ONLY" == "true" ]]; then - cargo check -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" "${CARGO_TARGET_ARG[@]}" + cargo check -p warp --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" "${CARGO_TARGET_ARG[@]}" exit 0 fi # Build the binary. # -# `USE_ZIGBUILD=true` 时改用 `cargo zigbuild`:zig 自带完整 C/C++ 交叉工具链, -# musl 目标下不需要手工准备 `musl-gcc`/`musl-g++`,也不需要给 cc-rs 装 -# `-{gcc,g++}` 符号链接。CI 的静态 musl CLI build 走这条路。 -# 本地不设置该变量时维持原有 `cargo build` 行为(host glibc 路径不受影响)。 +# When `USE_ZIGBUILD=true`, use `cargo zigbuild` instead: zig includes complete C/C++ cross-compilation toolchain, +# no need to manually prepare `musl-gcc`/`musl-g++` for musl targets, no need to set up +# `-{gcc,g++}` symlinks for cc-rs. CI's static musl CLI build takes this path. +# When this variable is not set locally, maintain the existing `cargo build` behavior (host glibc path unaffected). if [[ "$BUILD" == "true" ]]; then - echo "Building and bundling Zap for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID" + echo "Building and bundling Zaplex for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID" if [[ "${USE_ZIGBUILD:-false}" == "true" ]]; then echo "Using cargo zigbuild (zig-backed cross toolchain)" - cargo zigbuild -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" "${CARGO_TARGET_ARG[@]}" + cargo zigbuild -p warp --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" "${CARGO_TARGET_ARG[@]}" else - cargo build -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" "${CARGO_TARGET_ARG[@]}" + cargo build -p warp --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" "${CARGO_TARGET_ARG[@]}" fi echo "Making debug copy of '$EXECUTABLE_PATH' at '$DEBUG_EXECUTABLE_PATH'" cp "$EXECUTABLE_PATH" "$DEBUG_EXECUTABLE_PATH" diff --git a/script/linux/bundle_appimage b/script/linux/bundle_appimage index 7eee4b9ed2..a035abc41a 100755 --- a/script/linux/bundle_appimage +++ b/script/linux/bundle_appimage @@ -85,9 +85,9 @@ rm -rf "$APP_DIR" # deploys the binary, desktop file, and icons by default. cd "$OUT_DIR" echo "Running linuxdeploy to create the AppImage" -export WARP_BINARY_NAME="$BINARY_NAME" -export WARP_OPT_DIR="$OPT_DIR" -export WARP_BUNDLED_RESOURCES_DIR="${STAGE_DIR}${OPT_DIR}/resources" +export ZAPLEX_BINARY_NAME="$BINARY_NAME" +export ZAPLEX_OPT_DIR="$OPT_DIR" +export ZAPLEX_BUNDLED_RESOURCES_DIR="${STAGE_DIR}${OPT_DIR}/resources" export PATH="$WORKSPACE_ROOT_DIR/script/linux:$PATH" linuxdeploy \ --appdir "$APP_DIR" \ diff --git a/script/linux/linuxdeploy-plugin-warp b/script/linux/linuxdeploy-plugin-warp index f641cf13b5..babf849781 100755 --- a/script/linux/linuxdeploy-plugin-warp +++ b/script/linux/linuxdeploy-plugin-warp @@ -5,7 +5,7 @@ # # By default, linuxdeploy places the executable at usr/bin/ inside the AppDir. # This plugin moves it to a directory tree that matches the deb/rpm/arch -# package install layout (controlled by WARP_OPT_DIR), creates a symlink from +# package install layout (controlled by ZAPLEX_OPT_DIR), creates a symlink from # usr/bin/ to the new location, and copies bundled resources alongside the # binary. # @@ -13,13 +13,13 @@ # as installing a .deb or .rpm package. # # Required environment variables: -# WARP_BINARY_NAME: Name of the binary (e.g. "zap-oss"). -# WARP_OPT_DIR: Absolute /opt-rooted directory for the +# ZAPLEX_BINARY_NAME: Name of the binary (e.g. "zap-oss"). +# ZAPLEX_OPT_DIR: Absolute /opt-rooted directory for the # install layout inside the AppDir (e.g. # "/opt/zap" for the Zap OSS channel, or # "/opt/warpdotdev/warp-terminal-dev" for # upstream-style internal channels). -# WARP_BUNDLED_RESOURCES_DIR: Path to the staged resources directory to copy. +# ZAPLEX_BUNDLED_RESOURCES_DIR: Path to the staged resources directory to copy. set -e @@ -44,44 +44,44 @@ if [ -z "$APPDIR" ]; then exit 1 fi -for var in WARP_BINARY_NAME WARP_OPT_DIR WARP_BUNDLED_RESOURCES_DIR; do +for var in ZAPLEX_BINARY_NAME ZAPLEX_OPT_DIR ZAPLEX_BUNDLED_RESOURCES_DIR; do if [ -z "${!var}" ]; then echo "ERROR: $var environment variable is not set" >&2 exit 1 fi done -if [ ! -d "$WARP_BUNDLED_RESOURCES_DIR" ]; then - echo "ERROR: Bundled resources directory does not exist: $WARP_BUNDLED_RESOURCES_DIR" >&2 +if [ ! -d "$ZAPLEX_BUNDLED_RESOURCES_DIR" ]; then + echo "ERROR: Bundled resources directory does not exist: $ZAPLEX_BUNDLED_RESOURCES_DIR" >&2 exit 1 fi -SRC_BIN="$APPDIR/usr/bin/$WARP_BINARY_NAME" +SRC_BIN="$APPDIR/usr/bin/$ZAPLEX_BINARY_NAME" if [ ! -f "$SRC_BIN" ]; then echo "ERROR: Expected binary not found at $SRC_BIN" >&2 exit 1 fi -# Create the /opt install directory inside the AppDir. WARP_OPT_DIR is an +# Create the /opt install directory inside the AppDir. ZAPLEX_OPT_DIR is an # absolute path on the target machine (e.g. "/opt/zap"); prepend the AppDir # to nest it inside the AppImage. -OPT_DIR="$APPDIR$WARP_OPT_DIR" +OPT_DIR="$APPDIR$ZAPLEX_OPT_DIR" mkdir -p "$OPT_DIR" # Move the binary from usr/bin/ to the OPT_DIR. -echo "Relocating binary to $OPT_DIR/$WARP_BINARY_NAME" -mv "$SRC_BIN" "$OPT_DIR/$WARP_BINARY_NAME" +echo "Relocating binary to $OPT_DIR/$ZAPLEX_BINARY_NAME" +mv "$SRC_BIN" "$OPT_DIR/$ZAPLEX_BINARY_NAME" # Create a relative symlink so usr/bin/ still resolves to the binary. # usr/bin/ 的父目录是 usr/bin/,固定需要 ../../ 才能回到 AppDir 根, -# 然后拼上去掉前导 / 的 WARP_OPT_DIR 与二进制文件名。 -echo "Creating symlink at usr/bin/$WARP_BINARY_NAME" -ln -s "../..${WARP_OPT_DIR}/$WARP_BINARY_NAME" "$SRC_BIN" +# 然后拼上去掉前导 / 的 ZAPLEX_OPT_DIR 与二进制文件名。 +echo "Creating symlink at usr/bin/$ZAPLEX_BINARY_NAME" +ln -s "../..${ZAPLEX_OPT_DIR}/$ZAPLEX_BINARY_NAME" "$SRC_BIN" # Copy bundled resources alongside the binary. DEST_RESOURCES="$OPT_DIR/resources" echo "Copying bundled resources to $DEST_RESOURCES" mkdir -p "$DEST_RESOURCES" -cp -R "$WARP_BUNDLED_RESOURCES_DIR/." "$DEST_RESOURCES/" +cp -R "$ZAPLEX_BUNDLED_RESOURCES_DIR/." "$DEST_RESOURCES/" echo "Successfully restructured AppDir for Zap" diff --git a/script/macos/bundle b/script/macos/bundle index 9a5219b24c..59fd12e34d 100755 --- a/script/macos/bundle +++ b/script/macos/bundle @@ -25,11 +25,11 @@ # Once the stapling is done, the app can be shared. # # Three passwords are read from GCP Secret Manager (or from env if --read-passwords-from-env is set) if you are codesigning. -# 1) WARP_NOTARIZATION_PASSWORD: This is an "app-specific password" that +# 1) ZAPLEX_NOTARIZATION_PASSWORD: This is an "app-specific password" that # is tied to the zach@warp.dev account. See https://support.apple.com/en-us/HT204397 -# 2) WARP_DEVELOPER_ID_CERT_PASSWORD: This is a password tied to the private key +# 2) ZAPLEX_DEVELOPER_ID_CERT_PASSWORD: This is a password tied to the private key # of our cert - it is needed to use the cert to sign our binary. -# 3) WARP_CODESIGN_KEYCHAIN_PASSWORD: This is an arbitrary password only used +# 3) ZAPLEX_CODESIGN_KEYCHAIN_PASSWORD: This is an arbitrary password only used # in the lifetime of this app for creating the keychain used to sign. Can # be anything. # @@ -80,7 +80,7 @@ cleanup_dmg_files() { find "$target_dir" -name "rw.*.dmg" -type f -delete fi # Also check if any volumes are mounted and unmount them - hdiutil info | grep "/Volumes/Zap.*" | awk '{print $1}' | while read -r disk; do + hdiutil info | grep "/Volumes/Zaplex.*" | awk '{print $1}' | while read -r disk; do echo "Unmounting disk image: $disk" hdiutil detach "$disk" -force || true done @@ -146,7 +146,7 @@ while (( "$#" )); do SELFSIGN=false shift ;; - # Sign with a local Apple Development cert instead of the official Zap cert. + # Sign with a local Apple Development cert instead of the official Zaplex cert. # Useful for local debug builds when you don't have access to the company signing key. # Falls back to ad-hoc signing if no Apple Development cert is found. --selfsign) @@ -279,43 +279,44 @@ if [[ "$CARGO_PROFILE" == "dev" ]]; then fi if [[ $RELEASE_CHANNEL = "local" ]]; then - WARP_BIN="warp" + ZAPLEX_BIN="warp" BUNDLE_ID="dev.warp.Zap-Local" - WARP_APP_NAME="WarpLocal" - WARP_SCHEME_NAME="warplocal" + ZAPLEX_APP_NAME="WarpLocal" + ZAPLEX_SCHEME_NAME="warplocal" FEATURES="$FEATURES,agent_mode_debug" - # 本地构建可覆盖 bundled framework 版本,供 app/build.rs 在 cargo bundle 期间读取。 + # Local builds can override bundled framework version for app/build.rs to read during cargo bundle. export FRAMEWORK_OVERRIDE="dev" elif [[ $RELEASE_CHANNEL = "dev" ]]; then - WARP_BIN="dev" + ZAPLEX_BIN="dev" BUNDLE_ID="dev.warp.Zap-Dev" - WARP_APP_NAME="WarpDev" - WARP_SCHEME_NAME="warpdev" + ZAPLEX_APP_NAME="WarpDev" + ZAPLEX_SCHEME_NAME="warpdev" FEATURES="$FEATURES,agent_mode_debug" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking" - # Dev 构建可覆盖 bundled framework 版本,供 app/build.rs 在 cargo bundle 期间读取。 + # Dev builds can override bundled framework version for app/build.rs to read during cargo bundle. export FRAMEWORK_OVERRIDE="dev" export HANDLE_MARKDOWN=1 elif [[ $RELEASE_CHANNEL = "preview" ]]; then - WARP_BIN="preview" + ZAPLEX_BIN="preview" BUNDLE_ID="dev.warp.Zap-Preview" - WARP_APP_NAME="WarpPreview" - WARP_SCHEME_NAME="warppreview" + ZAPLEX_APP_NAME="WarpPreview" + ZAPLEX_SCHEME_NAME="warppreview" FEATURES="$FEATURES,preview_channel" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking" elif [[ $RELEASE_CHANNEL = "stable" ]]; then - WARP_BIN="stable" + ZAPLEX_BIN="stable" BUNDLE_ID="dev.warp.Zap-Stable" - WARP_APP_NAME="Zap" - WARP_SCHEME_NAME="warp" + ZAPLEX_APP_NAME="Zaplex" + ZAPLEX_SCHEME_NAME="warp" elif [[ $RELEASE_CHANNEL = "oss" ]]; then - WARP_BIN="zap-oss" - BUNDLE_ID="dev.zap.Zap" - WARP_APP_NAME="Zap" - WARP_SCHEME_NAME="zap" - # autoupdate 走 GitHub Release(zerx-lab/warp),仅下载 DMG 到 Downloads,不自动覆盖 .app。 + ZAPLEX_BIN="zaplex" + BUNDLE_ID="dev.zaplex.Zaplex" + ZAPLEX_APP_NAME="Zaplex" + ZAPLEX_SCHEME_NAME="zaplex" + # autoupdate via GitHub Release (zerx-lab/warp): only downloads the DMG to + # Downloads, does not auto-overwrite the .app. FEATURES="release_bundle,extern_plist,autoupdate" fi @@ -367,24 +368,24 @@ fi # then exit. We use this script to invoke `cargo check` to ensure that we are # using the same feature flags and profile that we would be using in production. if [[ "$CHECK_ONLY" == "true" ]]; then - cargo check --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES" + cargo check --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES" if [[ $UNIVERSAL_BINARY = true && "$ARTIFACT" != "cli" ]]; then - cargo check --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$ADDITIONAL_TARGET" --features "$FEATURES" + cargo check --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --target "$ADDITIONAL_TARGET" --features "$FEATURES" fi exit 0 fi -DMG_DIR="$BUNDLE_DIR/dmg/$WARP_BIN" -DMG_NAME="$WARP_APP_NAME.dmg" +DMG_DIR="$BUNDLE_DIR/dmg/$ZAPLEX_BIN" +DMG_NAME="$ZAPLEX_APP_NAME.dmg" if [[ -z "$FINAL_DMG_NAME" && -n "$DMG_NAME_SUFFIX" ]]; then - FINAL_DMG_NAME="$WARP_APP_NAME-$DMG_NAME_SUFFIX.dmg" + FINAL_DMG_NAME="$ZAPLEX_APP_NAME-$DMG_NAME_SUFFIX.dmg" else - FINAL_DMG_NAME="$WARP_APP_NAME.dmg" + FINAL_DMG_NAME="$ZAPLEX_APP_NAME.dmg" fi # First clean up and prep the outdir mkdir -p "$OUT_DIR" -rm -R "$OUT_DIR/$WARP_APP_NAME.app" || echo "No old app to remove" +rm -R "$OUT_DIR/$ZAPLEX_APP_NAME.app" || echo "No old app to remove" rm -R "$OUT_DIR/$FINAL_DMG_NAME" || echo "No old dmg to remove" ########################### @@ -399,14 +400,14 @@ if [[ "$ARTIFACT" == "app" ]]; then pushd app > /dev/null echo "Building and bundling $DEFAULT_TARGET for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID with profile $CARGO_PROFILE" - cargo bundle --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES" + cargo bundle --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES" popd > /dev/null echo "Adding rpath to support mac frameworks" - install_name_tool -add_rpath "@executable_path/../Frameworks" "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS/$WARP_BIN" + install_name_tool -add_rpath "@executable_path/../Frameworks" "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/MacOS/$ZAPLEX_BIN" - export WARP_SCHEME_NAME - export WARP_PLIST_PATH="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Info.plist" + export ZAPLEX_SCHEME_NAME + export ZAPLEX_PLIST_PATH="$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/Info.plist" ./script/update_plist if [[ $REGISTER_SERVICES = true ]]; then @@ -416,7 +417,7 @@ if [[ "$ARTIFACT" == "app" ]]; then NSMenuItem default - New $WARP_APP_NAME Tab Here + New $ZAPLEX_APP_NAME Tab Here NSMessage openTab @@ -435,7 +436,7 @@ if [[ "$ARTIFACT" == "app" ]]; then NSMenuItem default - New $WARP_APP_NAME Window Here + New $ZAPLEX_APP_NAME Window Here NSMessage openWindow @@ -450,25 +451,25 @@ if [[ "$ARTIFACT" == "app" ]]; then public.plain-text - " "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist + " "$BUNDLE_DIR"/$ZAPLEX_APP_NAME.app/Contents/Info.plist fi - # Temporary key that ChatGPT Desktop can use to determine if the latest version of Zap supports the ChatGPT integration. + # Temporary key that ChatGPT Desktop can use to determine if the latest version of Zaplex supports the ChatGPT integration. # Once support has been rolled out for a sufficient amount of time we (and ChatGPT) can remove this. - plutil -insert SUPPORTS_CHAT_GPT_WORK_WITH_APPS -bool true "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist + plutil -insert SUPPORTS_CHAT_GPT_WORK_WITH_APPS -bool true "$BUNDLE_DIR"/$ZAPLEX_APP_NAME.app/Contents/Info.plist # Add LSBackgroundOnly key and set it to false since we need a UI app - plutil -insert LSBackgroundOnly -bool false "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist + plutil -insert LSBackgroundOnly -bool false "$BUNDLE_DIR"/$ZAPLEX_APP_NAME.app/Contents/Info.plist # Add SMAuthorizedClients for macOS 13+ (Ventura) to support login item functionality - plutil -insert SMAuthorizedClients -xml "$BUNDLE_ID" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist + plutil -insert SMAuthorizedClients -xml "$BUNDLE_ID" "$BUNDLE_DIR"/$ZAPLEX_APP_NAME.app/Contents/Info.plist if [[ $UNIVERSAL_BINARY = true ]]; then if [[ $BUILD_BINARY = true ]]; then echo "Building $ADDITIONAL_TARGET to include in universal binary" pushd app > /dev/null - cargo build --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$ADDITIONAL_TARGET" --features "$FEATURES" + cargo build --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --target "$ADDITIONAL_TARGET" --features "$FEATURES" popd > /dev/null else echo "Skipping build of $ADDITIONAL_TARGET due to --skip-build flag" @@ -477,13 +478,13 @@ if [[ "$ARTIFACT" == "app" ]]; then tmp_bundle_dir=$(mktemp -d -t ci-XXXXXXXXXX) echo "Adding rpath to both binaries for universal executable" - install_name_tool -add_rpath "@executable_path/../Frameworks" "target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" - install_name_tool -add_rpath "@executable_path/../Frameworks" "target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" + install_name_tool -add_rpath "@executable_path/../Frameworks" "target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN" + install_name_tool -add_rpath "@executable_path/../Frameworks" "target/$ARM_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN" echo "Building universal binary using lipo." - lipo -create -output "$tmp_bundle_dir/$WARP_BIN" \ - "target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" \ - "target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" + lipo -create -output "$tmp_bundle_dir/$ZAPLEX_BIN" \ + "target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN" \ + "target/$ARM_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN" # Compute the real absolute path to the dSYM file, factoring in symlinks. # Starting in Rust 1.56, the generated dSYMs are symlinks to files within the target/dep directory. For example, @@ -491,35 +492,35 @@ if [[ "$ARTIFACT" == "app" ]]; then # would be located at dev.dSYM/Contents/Resources/DWARF/dev-080cf7e291fb066d.dSYM where dev.dSYM is symlink to # deps/dev-080cf7e291fb066d.dSYM. To fix this, parse out the actual name of the directory that the dSYM is # symlinked to, since this is also the name of file that contains the debug symbols within the dSYM. - INTEL_DSYM_NAME=$(basename "$(realpath target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM)" .dSYM) - INTEL_DSYM_PATH="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM/Contents/Resources/DWARF/$INTEL_DSYM_NAME" - ARM_DSYM_NAME=$(basename "$(realpath target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM)" .dSYM) - ARM_DSYM_PATH="target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM/Contents/Resources/DWARF/$ARM_DSYM_NAME" + INTEL_DSYM_NAME=$(basename "$(realpath target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM)" .dSYM) + INTEL_DSYM_PATH="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM/Contents/Resources/DWARF/$INTEL_DSYM_NAME" + ARM_DSYM_NAME=$(basename "$(realpath target/$ARM_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM)" .dSYM) + ARM_DSYM_PATH="target/$ARM_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM/Contents/Resources/DWARF/$ARM_DSYM_NAME" if [[ -e "$INTEL_DSYM_PATH" && -e "$ARM_DSYM_PATH" ]]; then - echo "Building universal binary .dSYM using lipo and storing in $OUT_DIR/$WARP_BIN.dSYM" + echo "Building universal binary .dSYM using lipo and storing in $OUT_DIR/$ZAPLEX_BIN.dSYM" # Use lipo to merge the .dSYM files into a single universal .dSYM file. # It expects the base name for the file (with the .dSYM suffix removed). - lipo -create -output "$OUT_DIR/$WARP_BIN.dSYM" "${INTEL_DSYM_PATH}" "${ARM_DSYM_PATH}" + lipo -create -output "$OUT_DIR/$ZAPLEX_BIN.dSYM" "${INTEL_DSYM_PATH}" "${ARM_DSYM_PATH}" fi echo "Storing result in $BUNDLE_DIR, replacing $DEFAULT_TARGET binary with fat binary." - mv "$tmp_bundle_dir/$WARP_BIN" "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS" - elif [[ -n "$TARGET_ARCH" && -e "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" ]]; then - echo "Copying .dSYM into $OUT_DIR/$WARP_BIN.dSYM" - cp -HR "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" "$OUT_DIR/" + mv "$tmp_bundle_dir/$ZAPLEX_BIN" "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/MacOS" + elif [[ -n "$TARGET_ARCH" && -e "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM" ]]; then + echo "Copying .dSYM into $OUT_DIR/$ZAPLEX_BIN.dSYM" + cp -HR "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM" "$OUT_DIR/" fi # Note that the dock tile plugin is pre-built for both arm64 and x86_64 so we don't need to run lipo on it. echo "Creating PlugIns directory and copying pre-built DockTilePlugin..." - mkdir -p "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/PlugIns" - cp -R "$DOCK_TILE_PLUGIN_DIR" "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/PlugIns/" + mkdir -p "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/PlugIns" + cp -R "$DOCK_TILE_PLUGIN_DIR" "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/PlugIns/" echo "Updating plist with dock tile plugin entries" - plutil -insert NSDockTilePlugIn -string "ZapDockTilePlugin.docktileplugin" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist - plutil -insert MainAppBundleIdentifier -string "$BUNDLE_ID" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/PlugIns/ZapDockTilePlugin.docktileplugin/Contents/Info.plist + plutil -insert NSDockTilePlugIn -string "ZapDockTilePlugin.docktileplugin" "$BUNDLE_DIR"/$ZAPLEX_APP_NAME.app/Contents/Info.plist + plutil -insert MainAppBundleIdentifier -string "$BUNDLE_ID" "$BUNDLE_DIR"/$ZAPLEX_APP_NAME.app/Contents/PlugIns/ZapDockTilePlugin.docktileplugin/Contents/Info.plist - BUNDLED_RESOURCES_DIR="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Resources" + BUNDLED_RESOURCES_DIR="$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/Resources" echo "Preparing bundled resources..." # Only forward --target to the schema generator when the build target is # runnable on the host; otherwise `cargo run` would try to execute a @@ -531,9 +532,9 @@ if [[ "$ARTIFACT" == "app" ]]; then fi "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE" "$FEATURES" "$SCHEMA_CARGO_TARGET" - "$WORKSPACE_ROOT_DIR/script/compile_icon" "$RELEASE_CHANNEL" "$BUNDLE_DIR/$WARP_APP_NAME.app" + "$WORKSPACE_ROOT_DIR/script/compile_icon" "$RELEASE_CHANNEL" "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app" - HELPERS_DIR="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Helpers" + HELPERS_DIR="$BUNDLE_DIR/$ZAPLEX_APP_NAME.app/Contents/Helpers" if [[ ",$FEATURES," =~ ",heap_usage_tracking," ]]; then echo "Bundling pprof..." "$WORKSPACE_ROOT_DIR/script/prepare_bundled_pprof" "$HELPERS_DIR" @@ -544,7 +545,7 @@ if [[ "$ARTIFACT" == "app" ]]; then if [[ $RELEASE_CHANNEL = "stable" ]]; then CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/oz" elif [[ $RELEASE_CHANNEL = "oss" ]]; then - CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/zap-oss" + CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/zaplex" else CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/oz-$RELEASE_CHANNEL" fi @@ -555,17 +556,17 @@ if [[ "$ARTIFACT" == "app" ]]; then cat > "$CLI_SCRIPT_PATH" << 'EOF' #!/bin/bash script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -exec -a "$0" "$script_dir/../../MacOS/WARP_BIN_PLACEHOLDER" "$@" +exec -a "$0" "$script_dir/../../MacOS/ZAPLEX_BIN_PLACEHOLDER" "$@" EOF # Replace the placeholder with the actual binary name - sed -i '' "s/WARP_BIN_PLACEHOLDER/$WARP_BIN/" "$CLI_SCRIPT_PATH" + sed -i '' "s/ZAPLEX_BIN_PLACEHOLDER/$ZAPLEX_BIN/" "$CLI_SCRIPT_PATH" # Make the script executable chmod +x "$CLI_SCRIPT_PATH" # Store the built artifact locations for GitHub Actions outputs. - BINARY_PATH="target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" + BINARY_PATH="target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN" DMG_PATH="$OUT_DIR/$FINAL_DMG_NAME" elif [[ "$ARTIFACT" == "cli" ]]; then if [[ $BUILD_BINARY == true ]]; then @@ -573,30 +574,30 @@ elif [[ "$ARTIFACT" == "cli" ]]; then # Apple's codesigning tools will detect Info.plist files in the same directory as an executable. # This breaks the code signature, so we must use a different location for the file. mkdir -p "$BUNDLE_DIR" - export WARP_PLIST_PATH="$BUNDLE_DIR/cli-info.plist" - cp app/assets/resources/mac/CLI-Info.plist "$WARP_PLIST_PATH" + export ZAPLEX_PLIST_PATH="$BUNDLE_DIR/cli-info.plist" + cp app/assets/resources/mac/CLI-Info.plist "$ZAPLEX_PLIST_PATH" - export WARP_PLIST_NO_FILE_TYPES=true + export ZAPLEX_PLIST_NO_FILE_TYPES=true ./script/update_plist - plutil -insert CFBundleIdentifier -string "$BUNDLE_ID" "$WARP_PLIST_PATH" - plutil -insert CFBundleName -string "$WARP_BIN" "$WARP_PLIST_PATH" - plutil -insert CFBundleExecutable -string "$WARP_BIN" "$WARP_PLIST_PATH" + plutil -insert CFBundleIdentifier -string "$BUNDLE_ID" "$ZAPLEX_PLIST_PATH" + plutil -insert CFBundleName -string "$ZAPLEX_BIN" "$ZAPLEX_PLIST_PATH" + plutil -insert CFBundleExecutable -string "$ZAPLEX_BIN" "$ZAPLEX_PLIST_PATH" - export "INFO_PLIST_PATH=$(realpath "$WARP_PLIST_PATH")" + export "INFO_PLIST_PATH=$(realpath "$ZAPLEX_PLIST_PATH")" pushd app > /dev/null echo "Building $DEFAULT_TARGET for channel $RELEASE_CHANNEL with profile $CARGO_PROFILE" - cargo build --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES" + cargo build --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES" popd > /dev/null else echo "Skipping binary build due to --skip-build flag" fi - echo "Copying binary into $OUT_DIR/$WARP_BIN" - cp "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" "$OUT_DIR/$WARP_BIN" + echo "Copying binary into $OUT_DIR/$ZAPLEX_BIN" + cp "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN" "$OUT_DIR/$ZAPLEX_BIN" - if [[ -n "$TARGET_ARCH" && -e "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" ]]; then - echo "Copying .dSYM into $OUT_DIR/$WARP_BIN.dSYM" - cp -HR "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" "$OUT_DIR/" + if [[ -n "$TARGET_ARCH" && -e "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM" ]]; then + echo "Copying .dSYM into $OUT_DIR/$ZAPLEX_BIN.dSYM" + cp -HR "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$ZAPLEX_BIN.dSYM" "$OUT_DIR/" fi echo "Preparing CLI resources directory" @@ -613,7 +614,7 @@ elif [[ "$ARTIFACT" == "cli" ]]; then # Set the primary binary path to output. - BINARY_PATH="$OUT_DIR/$WARP_BIN" + BINARY_PATH="$OUT_DIR/$ZAPLEX_BIN" else echo "Unsupported artifact: $ARTIFACT" >&2 exit 1 @@ -629,25 +630,25 @@ if [[ $READ_PASSWORDS_FROM_ENV != true ]]; then fi if [[ $CODESIGN = true ]]; then - # TODO - does this need to change per user? Seems like it's tied to the WARP_NOTARIZATION_PASSWORD password. + # TODO - does this need to change per user? Seems like it's tied to the ZAPLEX_NOTARIZATION_PASSWORD password. APPLE_TEAM_ID="2BBY89MBSN" CODESIGN_KEYCHAIN_NAME="warp-codesign-keychain" echo "Starting codesigning..." if [[ $READ_PASSWORDS_FROM_ENV = true ]]; then - if [ -z "$WARP_NOTARIZATION_PASSWORD" ] ; then - echo "WARP_NOTARIZATION_PASSWORD must be set for code signing" + if [ -z "$ZAPLEX_NOTARIZATION_PASSWORD" ] ; then + echo "ZAPLEX_NOTARIZATION_PASSWORD must be set for code signing" exit 1 fi - if [ -z "$WARP_DEVELOPER_ID_CERT_PASSWORD" ] ; then - echo "WARP_DEVELOPER_ID_CERT_PASSWORD must be set for code signing" + if [ -z "$ZAPLEX_DEVELOPER_ID_CERT_PASSWORD" ] ; then + echo "ZAPLEX_DEVELOPER_ID_CERT_PASSWORD must be set for code signing" exit 1 fi - if [ -z "$WARP_CODESIGN_KEYCHAIN_PASSWORD" ] ; then - echo "WARP_CODESIGN_KEYCHAIN_PASSWORD must be set for code signing" + if [ -z "$ZAPLEX_CODESIGN_KEYCHAIN_PASSWORD" ] ; then + echo "ZAPLEX_CODESIGN_KEYCHAIN_PASSWORD must be set for code signing" exit 1 fi fi @@ -655,14 +656,14 @@ if [[ $CODESIGN = true ]]; then security delete-keychain $CODESIGN_KEYCHAIN_NAME || echo "No existing keychain to clean up". echo "Creating $CODESIGN_KEYCHAIN_NAME keychain." - security create-keychain -p "$WARP_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME + security create-keychain -p "$ZAPLEX_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME security list-keychains -s $CODESIGN_KEYCHAIN_NAME security set-keychain-settings -t 3600 -u $CODESIGN_KEYCHAIN_NAME echo "Unlocking keychain and setting cert." - security unlock-keychain -p "$WARP_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME - security import <(echo "$WARP_DEVELOPER_ID_CERT" | base64 -d) -f pkcs12 -P "$WARP_DEVELOPER_ID_CERT_PASSWORD" -k $CODESIGN_KEYCHAIN_NAME -T /usr/bin/codesign - security set-key-partition-list -S "apple-tool:,apple:" -s -k "$WARP_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME + security unlock-keychain -p "$ZAPLEX_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME + security import <(echo "$ZAPLEX_DEVELOPER_ID_CERT" | base64 -d) -f pkcs12 -P "$ZAPLEX_DEVELOPER_ID_CERT_PASSWORD" -k $CODESIGN_KEYCHAIN_NAME -T /usr/bin/codesign + security set-key-partition-list -S "apple-tool:,apple:" -s -k "$ZAPLEX_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME fi ############################## @@ -678,31 +679,31 @@ if [[ $SELFSIGN = true ]]; then echo "Found Apple Development certificate" fi if [[ "$ARTIFACT" == app ]]; then - echo "Self-signing $BUNDLE_DIR/$WARP_APP_NAME.app with ${SIGNING_CERT}..." - codesign --force --deep --options runtime --sign "$SIGNING_CERT" "$BUNDLE_DIR/$WARP_APP_NAME.app" --entitlements script/Debug-Entitlements.plist + echo "Self-signing $BUNDLE_DIR/$ZAPLEX_APP_NAME.app with ${SIGNING_CERT}..." + codesign --force --deep --options runtime --sign "$SIGNING_CERT" "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app" --entitlements script/Debug-Entitlements.plist elif [[ "$ARTIFACT" == cli ]]; then - echo "Self-signing $OUT_DIR/$WARP_BIN with ${SIGNING_CERT}..." - codesign --force --options runtime --sign "$SIGNING_CERT" "$OUT_DIR/$WARP_BIN" --entitlements script/Debug-Entitlements.plist + echo "Self-signing $OUT_DIR/$ZAPLEX_BIN with ${SIGNING_CERT}..." + codesign --force --options runtime --sign "$SIGNING_CERT" "$OUT_DIR/$ZAPLEX_BIN" --entitlements script/Debug-Entitlements.plist fi elif [[ $CODESIGN = true ]]; then if [[ "$ARTIFACT" == app ]]; then - echo "Codesigning $BUNDLE_DIR/$WARP_APP_NAME.app..." + echo "Codesigning $BUNDLE_DIR/$ZAPLEX_APP_NAME.app..." # Use --deep so we sign bundled frameworks as well - codesign --deep -f -o runtime --timestamp -s "$APPLE_TEAM_ID" "$BUNDLE_DIR/$WARP_APP_NAME.app" --entitlements script/Entitlements.plist + codesign --deep -f -o runtime --timestamp -s "$APPLE_TEAM_ID" "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app" --entitlements script/Entitlements.plist elif [[ "$ARTIFACT" == cli ]]; then - echo "Codesigning $OUT_DIR/$WARP_BIN..." - codesign -f -o runtime --timestamp -s "$APPLE_TEAM_ID" "$OUT_DIR/$WARP_BIN" --entitlements script/Entitlements.plist + echo "Codesigning $OUT_DIR/$ZAPLEX_BIN..." + codesign -f -o runtime --timestamp -s "$APPLE_TEAM_ID" "$OUT_DIR/$ZAPLEX_BIN" --entitlements script/Entitlements.plist # Create the .zip for notarization in a separate location - otherwise, Apple's codesigning # tools decide that it's a sealed resource that belongs to the binary and needs to also be signed. - NOTARIZATION_ARTIFACT="$BUNDLE_DIR/${WARP_BIN}_notarize.zip" + NOTARIZATION_ARTIFACT="$BUNDLE_DIR/${ZAPLEX_BIN}_notarize.zip" if [[ -e "$NOTARIZATION_ARTIFACT" ]]; then echo "Removing old notarization artifact..." rm "$NOTARIZATION_ARTIFACT" fi # Create a .zip archive to notarize. - ditto -c -k "$OUT_DIR/$WARP_BIN" "$NOTARIZATION_ARTIFACT" + ditto -c -k "$OUT_DIR/$ZAPLEX_BIN" "$NOTARIZATION_ARTIFACT" # It's not possible to staple notarization tickets to standalone binaries: # https://developer.apple.com/documentation/security/customizing-the-notarization-workflow?language=objc#Staple-the-ticket-to-your-distribution @@ -721,15 +722,15 @@ if [[ "$ARTIFACT" = app ]]; then local source_folder="$1" local args=( - --volname Zap + --volname "$ZAPLEX_APP_NAME" # For --no-internet-enable, see https://github.com/create-dmg/create-dmg/issues/179 --no-internet-enable - --background app/assets/resources/mac/warp_install_image.png + --background app/assets/resources/mac/zaplex_install_image.png --icon-size 128 --window-size 700 500 --format UDZO --app-drop-link 550 250 - --icon "$WARP_APP_NAME.app" 150 250 + --icon "$ZAPLEX_APP_NAME.app" 150 250 # macOS 26.4 Beta has issues with mounting HFS+ DMGs, so we're using APFS instead. # APFS has been supported as a DMG filesystem since macOS 10.13, and we target 10.14 # as our minimum version. @@ -758,7 +759,7 @@ if [[ "$ARTIFACT" = app ]]; then fi echo "Creating $DMG_DIR" mkdir -p "$DMG_DIR" - cp -R "$BUNDLE_DIR/$WARP_APP_NAME.app" "$DMG_DIR" + cp -R "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app" "$DMG_DIR" create_warp_dmg "$DMG_DIR" @@ -784,7 +785,7 @@ fi if [[ $CODESIGN = true ]]; then echo "Uploading $NOTARIZATION_ARTIFACT to Apple for notarization..." - xcrun notarytool submit "$NOTARIZATION_ARTIFACT" --apple-id "$WARP_NOTARIZATION_APPLE_ID" --password "$WARP_NOTARIZATION_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait + xcrun notarytool submit "$NOTARIZATION_ARTIFACT" --apple-id "$ZAPLEX_NOTARIZATION_APPLE_ID" --password "$ZAPLEX_NOTARIZATION_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait if [[ $STAPLE_TICKET = true ]]; then echo "Attempting to staple the notarization ticket to $NOTARIZATION_ARTIFACT..." @@ -801,7 +802,7 @@ if [[ $CODESIGN = true ]]; then if [[ "$ARTIFACT" = app ]]; then xcrun stapler validate "$DMG_DIR/$DMG_NAME" elif [[ "$ARTIFACT" = cli ]]; then - spctl -a -t open --context context:primary-signature -vv "$OUT_DIR/$WARP_BIN" + spctl -a -t open --context context:primary-signature -vv "$OUT_DIR/$ZAPLEX_BIN" fi fi @@ -812,7 +813,7 @@ fi if [[ "$ARTIFACT" = app ]]; then echo "Copying dmg and app to $OUT_DIR" - cp -R "$BUNDLE_DIR/$WARP_APP_NAME.app" "$OUT_DIR" + cp -R "$BUNDLE_DIR/$ZAPLEX_APP_NAME.app" "$OUT_DIR" cp "$DMG_DIR/$DMG_NAME" "$OUT_DIR/$FINAL_DMG_NAME" fi diff --git a/script/macos/run b/script/macos/run index cd6c61e9cb..1040cc28b6 100755 --- a/script/macos/run +++ b/script/macos/run @@ -7,8 +7,8 @@ # This script is invoked by `./script/run`, which handles cross-platform # setup (install_channel_config, binary name detection, feature-to-env-var # mapping). The following env vars are expected to be set by the caller: -# WARP_BIN_NAME — "warp" (internal local build) or "zap-oss" -# WARP_CHANNEL — "local" or "oss" +# ZAPLEX_BIN_NAME — "warp" (internal local build) or "zap-oss" +# ZAPLEX_CHANNEL — "local" or "oss" # FEATURES — comma-separated cargo features (already normalized) # Must be called from the root directory of the warp repo. @@ -17,16 +17,16 @@ set -e REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" cd "${REPO_ROOT}" -: "${WARP_BIN_NAME:?WARP_BIN_NAME must be set (invoke via ./script/run)}" -: "${WARP_CHANNEL:?WARP_CHANNEL must be set (invoke via ./script/run)}" +: "${ZAPLEX_BIN_NAME:?ZAPLEX_BIN_NAME must be set (invoke via ./script/run)}" +: "${ZAPLEX_CHANNEL:?ZAPLEX_CHANNEL must be set (invoke via ./script/run)}" : "${FEATURES:?FEATURES must be set (invoke via ./script/run)}" -if [ "$WARP_CHANNEL" = "local" ]; then - WARP_APP_PATH="target/debug/bundle/osx/WarpLocal.app" - WARP_SCHEME_NAME="warplocal" +if [ "$ZAPLEX_CHANNEL" = "local" ]; then + ZAPLEX_APP_PATH="target/debug/bundle/osx/WarpLocal.app" + ZAPLEX_SCHEME_NAME="warplocal" else - WARP_APP_PATH="target/debug/bundle/osx/Zap.app" - WARP_SCHEME_NAME="zap" + ZAPLEX_APP_PATH="target/debug/bundle/osx/Zaplex.app" + ZAPLEX_SCHEME_NAME="zaplex" fi DONT_OPEN=false # Launches the binary with "open", meaning the Zap process is @@ -39,7 +39,7 @@ OPEN_WITH_LAUNCHD=false # Arguments to pass directly Zap (specified after --) # This is not supported when opening with launchd, as passing CLI arguments to # an application doesn't make sense. -WARP_ARGS=() +ZAPLEX_ARGS=() # Export this variable so it can be referenced by app/build.rs later when # running `cargo bundle`. @@ -50,7 +50,7 @@ while (( "$#" )); do case "$1" in --) shift - WARP_ARGS=("$@") + ZAPLEX_ARGS=("$@") break ;; --dont-open) @@ -59,10 +59,10 @@ while (( "$#" )); do ;; --release) echo "Detected release build, pointing at release bundle under target/release/bundle" - if [ "$WARP_CHANNEL" = "local" ]; then - WARP_APP_PATH="target/release/bundle/osx/WarpLocal.app" + if [ "$ZAPLEX_CHANNEL" = "local" ]; then + ZAPLEX_APP_PATH="target/release/bundle/osx/WarpLocal.app" else - WARP_APP_PATH="target/release/bundle/osx/Zap.app" + ZAPLEX_APP_PATH="target/release/bundle/osx/Zaplex.app" fi PARAMS="$PARAMS $1" shift @@ -70,10 +70,10 @@ while (( "$#" )); do --profile) PROFILE="$2" shift 2 - if [ "$WARP_CHANNEL" = "local" ]; then - WARP_APP_PATH="target/${PROFILE}/bundle/osx/WarpLocal.app" + if [ "$ZAPLEX_CHANNEL" = "local" ]; then + ZAPLEX_APP_PATH="target/${PROFILE}/bundle/osx/WarpLocal.app" else - WARP_APP_PATH="target/${PROFILE}/bundle/osx/Zap.app" + ZAPLEX_APP_PATH="target/${PROFILE}/bundle/osx/Zaplex.app" fi PARAMS="$PARAMS --profile $PROFILE" ;; @@ -92,19 +92,19 @@ while (( "$#" )); do esac done -rm -rf "$WARP_APP_PATH" +rm -rf "$ZAPLEX_APP_PATH" pushd app > /dev/null -echo "Bundling app (bin: $WARP_BIN_NAME)..." -cargo bundle --bin "$WARP_BIN_NAME" --features "$FEATURES" $PARAMS +echo "Bundling app (bin: $ZAPLEX_BIN_NAME)..." +cargo bundle --bin "$ZAPLEX_BIN_NAME" --features "$FEATURES" $PARAMS echo "Successfully bundled..." popd > /dev/null echo "Adding rpath to support mac frameworks" -install_name_tool -add_rpath "@executable_path/../Frameworks" "$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME" +install_name_tool -add_rpath "@executable_path/../Frameworks" "$ZAPLEX_APP_PATH/Contents/MacOS/$ZAPLEX_BIN_NAME" -export WARP_SCHEME_NAME -export WARP_PLIST_PATH="$WARP_APP_PATH/Contents/Info.plist" +export ZAPLEX_SCHEME_NAME +export ZAPLEX_PLIST_PATH="$ZAPLEX_APP_PATH/Contents/Info.plist" ./script/update_plist echo "Preparing bundled resources..." @@ -114,27 +114,27 @@ fi # Pass FEATURES through (and an empty cargo profile, since this script does # not configure one) so that the schema generation reuses the same compiled # artifacts as the cargo bundle invocation above. -NO_LICENSES=1 "${REPO_ROOT}/script/prepare_bundled_resources" "$WARP_APP_PATH/Contents/Resources" "$WARP_CHANNEL" "" "$FEATURES" +NO_LICENSES=1 "${REPO_ROOT}/script/prepare_bundled_resources" "$ZAPLEX_APP_PATH/Contents/Resources" "$ZAPLEX_CHANNEL" "" "$FEATURES" -"${REPO_ROOT}/script/compile_icon" "$WARP_CHANNEL" "$WARP_APP_PATH" +"${REPO_ROOT}/script/compile_icon" "$ZAPLEX_CHANNEL" "$ZAPLEX_APP_PATH" if [[ ",$FEATURES," =~ ",heap_usage_tracking," ]]; then echo "Bundling pprof..." - HELPERS_DIR="$WARP_APP_PATH/Contents/Helpers" + HELPERS_DIR="$ZAPLEX_APP_PATH/Contents/Helpers" "${REPO_ROOT}/script/prepare_bundled_pprof" "$HELPERS_DIR" fi echo "Codesigning app..." SIGNING_CERT="$(security find-identity -p codesigning -v | grep "Apple Development" | awk '{print $2}' | head -1)" -codesign --force --deep --options runtime --sign "${SIGNING_CERT:--}" "$WARP_APP_PATH" --entitlements script/Debug-Entitlements.plist +codesign --force --deep --options runtime --sign "${SIGNING_CERT:--}" "$ZAPLEX_APP_PATH" --entitlements script/Debug-Entitlements.plist if [ "$DONT_OPEN" = false ] ; then if [ "$OPEN_WITH_LAUNCHD" = true ]; then echo "Launching with MacOS application launcher" - PATH="" /usr/bin/open "./$WARP_APP_PATH" + PATH="" /usr/bin/open "./$ZAPLEX_APP_PATH" tail -f ~/Library/Logs/warp_local.log else - echo "Opening app at ./$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME" - "./$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME" "${WARP_ARGS[@]}" + echo "Opening app at ./$ZAPLEX_APP_PATH/Contents/MacOS/$ZAPLEX_BIN_NAME" + "./$ZAPLEX_APP_PATH/Contents/MacOS/$ZAPLEX_BIN_NAME" "${ZAPLEX_ARGS[@]}" fi fi diff --git a/script/run b/script/run index 89f05f204e..5d421b193f 100755 --- a/script/run +++ b/script/run @@ -24,11 +24,11 @@ FEATURES="gui" # If warp_channel_config is on PATH, build the Local channel binary; otherwise build the OSS channel. if command -v warp-channel-config &>/dev/null; then - WARP_BIN_NAME="warp" - WARP_CHANNEL="local" + ZAPLEX_BIN_NAME="warp" + ZAPLEX_CHANNEL="local" else - WARP_BIN_NAME="zap-oss" - WARP_CHANNEL="oss" + ZAPLEX_BIN_NAME="zaplex" + ZAPLEX_CHANNEL="oss" fi # Shared argument parsing. macOS-specific flags (--dont-open, @@ -36,13 +36,13 @@ fi # silently ignored on other platforms. MAC_ARGS=() CARGO_PARAMS=() -WARP_ARGS=() +ZAPLEX_ARGS=() while (( "$#" )); do case "$1" in --) shift - WARP_ARGS=("$@") + ZAPLEX_ARGS=("$@") break ;; --features) @@ -56,7 +56,7 @@ while (( "$#" )); do ;; --host-id) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - export WARP_CLOUD_MODE_DEFAULT_HOST="$2" + export ZAPLEX_CLOUD_MODE_DEFAULT_HOST="$2" shift 2 else echo "Error: Argument for $1 is missing" >&2 @@ -105,21 +105,21 @@ for mapping in \ done export FEATURES -export WARP_BIN_NAME -export WARP_CHANNEL +export ZAPLEX_BIN_NAME +export ZAPLEX_CHANNEL if [[ "$OS_TYPE" = "Darwin" ]]; then - if [[ ${#WARP_ARGS[@]} -gt 0 ]]; then - exec ./script/macos/run "${MAC_ARGS[@]}" -- "${WARP_ARGS[@]}" + if [[ ${#ZAPLEX_ARGS[@]} -gt 0 ]]; then + exec ./script/macos/run "${MAC_ARGS[@]}" -- "${ZAPLEX_ARGS[@]}" else exec ./script/macos/run "${MAC_ARGS[@]}" fi elif [[ "$OS_TYPE" = "Linux" ]] || [[ "$OS_TYPE" =~ ^(MINGW64_NT|MSYS_NT) ]]; then - echo "Running cargo run --bin $WARP_BIN_NAME --features \"$FEATURES\" ${CARGO_PARAMS[*]}" - if [[ ${#WARP_ARGS[@]} -gt 0 ]]; then - cargo run --bin "$WARP_BIN_NAME" --features "$FEATURES" "${CARGO_PARAMS[@]}" -- "${WARP_ARGS[@]}" + echo "Running cargo run --bin $ZAPLEX_BIN_NAME --features \"$FEATURES\" ${CARGO_PARAMS[*]}" + if [[ ${#ZAPLEX_ARGS[@]} -gt 0 ]]; then + cargo run --bin "$ZAPLEX_BIN_NAME" --features "$FEATURES" "${CARGO_PARAMS[@]}" -- "${ZAPLEX_ARGS[@]}" else - cargo run --bin "$WARP_BIN_NAME" --features "$FEATURES" "${CARGO_PARAMS[@]}" + cargo run --bin "$ZAPLEX_BIN_NAME" --features "$FEATURES" "${CARGO_PARAMS[@]}" fi else echo "No run script defined for the current platform ($OS_TYPE)!" >&2 diff --git a/script/setup-merge-drivers.sh b/script/setup-merge-drivers.sh index fd1f13ed0d..ff099019e1 100644 --- a/script/setup-merge-drivers.sh +++ b/script/setup-merge-drivers.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# 注册 openWarp 自定义合并驱动 + 启用 rerere。 -# 第一次 clone 后跑一次,后续合并上游(merge / cherry-pick / rebase)就会: -# 1. .gitattributes 中标了 merge=zap-ours 的路径自动保留本地版本 -# 2. rerere 记录每次冲突解析,下次相同冲突自动复用 +# Register openWarp custom merge driver + enable rerere. +# Run once after first clone, subsequent merges with upstream (merge / cherry-pick / rebase) will: +# 1. Paths marked with merge=zap-ours in .gitattributes automatically keep local version +# 2. rerere records each conflict resolution, next identical conflict auto-reuses solution set -euo pipefail git config merge.zap-ours.name "Always keep openWarp version (custom driver)" diff --git a/script/update_plist b/script/update_plist index f24afe191f..cc3e5f24e9 100755 --- a/script/update_plist +++ b/script/update_plist @@ -11,14 +11,14 @@ if [[ ! -d "script" ]]; then exit 1 fi -if [[ -z "$WARP_PLIST_PATH" ]]; then - echo 'Must set $WARP_PLIST_PATH' +if [[ -z "$ZAPLEX_PLIST_PATH" ]]; then + echo 'Must set $ZAPLEX_PLIST_PATH' exit 1 fi if [[ -n "$GIT_RELEASE_TAG" ]]; then echo "Updating plist Warp version to $GIT_RELEASE_TAG" - plutil -insert WarpVersion -string "$GIT_RELEASE_TAG" "$WARP_PLIST_PATH" + plutil -insert WarpVersion -string "$GIT_RELEASE_TAG" "$ZAPLEX_PLIST_PATH" # We convert our version format to be period-separated integers only to align better with # Apple's guidance on values for `CFBundleVersion` / `CFBundleShortVersionString`. @@ -37,25 +37,25 @@ if [[ -n "$GIT_RELEASE_TAG" ]]; then FORMATTED_VERSION="${MAJOR}.${YEAR}.${MONTH}.${DAY}.${HOUR}.${MINUTE}.${BUILD}" echo "Updating plist version to $FORMATTED_VERSION" - plutil -replace CFBundleShortVersionString -string "$FORMATTED_VERSION" "$WARP_PLIST_PATH" - plutil -replace CFBundleVersion -string "$FORMATTED_VERSION" "$WARP_PLIST_PATH" + plutil -replace CFBundleShortVersionString -string "$FORMATTED_VERSION" "$ZAPLEX_PLIST_PATH" + plutil -replace CFBundleVersion -string "$FORMATTED_VERSION" "$ZAPLEX_PLIST_PATH" else echo "Warning: GIT_RELEASE_TAG '$GIT_RELEASE_TAG' does not match expected format vN.YYYY.MM.DD.HH.MM.channel_NN" fi fi -if [[ -n "$WARP_SCHEME_NAME" ]]; then +if [[ -n "$ZAPLEX_SCHEME_NAME" ]]; then echo "Updating plist to support a custom url scheme" # Update the plist that cargo bundle creates to add support for custom URL schemes. Unfortunately, cargo bundle does not support # setting arbitrary plist fields, so we must do this after the fact. - plutil -insert CFBundleURLTypes -xml "CFBundleURLNameCustom AppCFBundleURLSchemes$WARP_SCHEME_NAME" "$WARP_PLIST_PATH" + plutil -insert CFBundleURLTypes -xml "CFBundleURLNameCustom AppCFBundleURLSchemes$ZAPLEX_SCHEME_NAME" "$ZAPLEX_PLIST_PATH" fi echo "Updating plist with supported localizations" -plutil -insert CFBundleLocalizations -xml "enjazh-CN" "$WARP_PLIST_PATH" 2>/dev/null || \ - plutil -replace CFBundleLocalizations -xml "enjazh-CN" "$WARP_PLIST_PATH" +plutil -insert CFBundleLocalizations -xml "enjazh-CN" "$ZAPLEX_PLIST_PATH" 2>/dev/null || \ + plutil -replace CFBundleLocalizations -xml "enjazh-CN" "$ZAPLEX_PLIST_PATH" -if [[ -z "$WARP_PLIST_NO_FILE_TYPES" ]]; then +if [[ -z "$ZAPLEX_PLIST_NO_FILE_TYPES" ]]; then echo "Updating plist with supported file types" plutil -insert CFBundleDocumentTypes -xml " @@ -249,7 +249,7 @@ if [[ -z "$WARP_PLIST_NO_FILE_TYPES" ]]; then public.data - " "$WARP_PLIST_PATH" + " "$ZAPLEX_PLIST_PATH" fi # Unfortunately, there isn't a single standard UTI (what goes in LSItemContentTypes) for Markdown @@ -258,13 +258,13 @@ fi # https://blog.smittytone.net/2021/01/19/how-tools-try-to-own-markdown/ echo "Updating plist with permissions descriptions" -plutil -insert NSAppleEventsUsageDescription -string "A program in Warp wants to use AppleScript." "$WARP_PLIST_PATH" -plutil -insert NSCameraUsageDescription -string "A program in Warp wants to use the camera." "$WARP_PLIST_PATH" -plutil -insert NSMicrophoneUsageDescription -string "A program in Warp wants to use your microphone." "$WARP_PLIST_PATH" -plutil -insert NSContactsUsageDescription -string "A program in Warp wants to use your contacts." "$WARP_PLIST_PATH" -plutil -insert NSCalendarsUsageDescription -string "A program in Warp wants to use your calendar." "$WARP_PLIST_PATH" -plutil -insert NSLocationUsageDescription -string "A program in Warp wants to use your location information." "$WARP_PLIST_PATH" -plutil -insert NSPhotoLibraryUsageDescription -string "A program in Warp wants to use your photo library." "$WARP_PLIST_PATH" +plutil -insert NSAppleEventsUsageDescription -string "A program in Warp wants to use AppleScript." "$ZAPLEX_PLIST_PATH" +plutil -insert NSCameraUsageDescription -string "A program in Warp wants to use the camera." "$ZAPLEX_PLIST_PATH" +plutil -insert NSMicrophoneUsageDescription -string "A program in Warp wants to use your microphone." "$ZAPLEX_PLIST_PATH" +plutil -insert NSContactsUsageDescription -string "A program in Warp wants to use your contacts." "$ZAPLEX_PLIST_PATH" +plutil -insert NSCalendarsUsageDescription -string "A program in Warp wants to use your calendar." "$ZAPLEX_PLIST_PATH" +plutil -insert NSLocationUsageDescription -string "A program in Warp wants to use your location information." "$ZAPLEX_PLIST_PATH" +plutil -insert NSPhotoLibraryUsageDescription -string "A program in Warp wants to use your photo library." "$ZAPLEX_PLIST_PATH" echo "Disabling use of Liquid Glass on macOS Tahoe" -plutil -insert UIDesignRequiresCompatibility -bool true "$WARP_PLIST_PATH" +plutil -insert UIDesignRequiresCompatibility -bool true "$ZAPLEX_PLIST_PATH" diff --git a/script/wasm/bundle b/script/wasm/bundle index bd29172c0c..cbc267f0d9 100755 --- a/script/wasm/bundle +++ b/script/wasm/bundle @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Builds a Zap binary and bundles it up for distribution. +# Builds a Zaplex binary and bundles it up for distribution. set -e @@ -114,20 +114,20 @@ mkdir -p "$EXTRAS_DIR" # Update parameters based on the target release channel. # -# WARP_BIN is the name of the binary produced by cargo. +# ZAPLEX_BIN is the name of the binary produced by cargo. # N.B. The bundled outputs will always be warp.js and warp_bg.wasm. if [[ $RELEASE_CHANNEL = "local" ]]; then - WARP_BIN="warp" + ZAPLEX_BIN="warp" FEATURES="$FEATURES,remote_tty" elif [[ $RELEASE_CHANNEL = "dev" ]]; then - WARP_BIN="dev" + ZAPLEX_BIN="dev" elif [[ $RELEASE_CHANNEL = "preview" ]]; then - WARP_BIN="preview" + ZAPLEX_BIN="preview" FEATURES="$FEATURES,preview_channel" elif [[ $RELEASE_CHANNEL = "stable" ]]; then - WARP_BIN="stable" + ZAPLEX_BIN="stable" elif [[ $RELEASE_CHANNEL = "oss" ]]; then - WARP_BIN="zap-oss" + ZAPLEX_BIN="zaplex" fi if [ -n "${FEATURES_OVERRIDE+x}" ]; then @@ -138,23 +138,23 @@ fi # then exit. We use this script to invoke `cargo check` to ensure that we are # using the same feature flags and profile that we would be using in production. if [[ "$CHECK_ONLY" == "true" ]]; then - ASSET_TARGET_DIR="$ASSET_TARGET_DIR" cargo check --target wasm32-unknown-unknown --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" + ASSET_TARGET_DIR="$ASSET_TARGET_DIR" cargo check --target wasm32-unknown-unknown --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" exit 0 fi # Build the wasm binary. -echo "Building and bundling Zap for channel $RELEASE_CHANNEL" -ASSET_TARGET_DIR="$ASSET_TARGET_DIR" cargo build --target wasm32-unknown-unknown --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" +echo "Building and bundling Zaplex for channel $RELEASE_CHANNEL" +ASSET_TARGET_DIR="$ASSET_TARGET_DIR" cargo build --target wasm32-unknown-unknown --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" # Generate linked JS and wasm files that can be run in the browser. -echo "Running wasm-bindgen on $CARGO_TARGET_OUTPUT_DIR/$WARP_BIN.wasm" -wasm-bindgen --target web --out-dir "$OUT_DIR" --out-name "warp" --keep-debug --no-typescript "$CARGO_TARGET_OUTPUT_DIR/$WARP_BIN.wasm" +echo "Running wasm-bindgen on $CARGO_TARGET_OUTPUT_DIR/$ZAPLEX_BIN.wasm" +wasm-bindgen --target web --out-dir "$OUT_DIR" --out-name "warp" --keep-debug --no-typescript "$CARGO_TARGET_OUTPUT_DIR/$ZAPLEX_BIN.wasm" WASM_BINARY_OUT="${OUT_DIR}/warp_bg.wasm" WASM_BINARY_DEBUG="${EXTRAS_DIR}/warp_bg.debug.wasm" if [[ "$NO_SPLIT" != "true" ]]; then - # 使用 wasm-split 从 wasm binary 中拆分 debug information。 + # Use wasm-split to extract debug information from the wasm binary. # # Binary releases of wasm-split can be found here: # https://github.com/getsentry/symbolicator/releases diff --git a/script/windows/bootstrap.ps1 b/script/windows/bootstrap.ps1 index 48d6f7d08e..758395955b 100644 --- a/script/windows/bootstrap.ps1 +++ b/script/windows/bootstrap.ps1 @@ -49,15 +49,15 @@ winget install jqlang.jq # CMake is needed to build native dependencies. winget install -e --id Kitware.CMake -# Strawberry Perl 用于从源码编译 OpenSSL。zap_sftp → ssh2(openssl-on-win32)→ -# openssl-sys 的 vendored 构建会调用 perl 运行 OpenSSL 的 Configure 脚本。 -# 必须用原生 Windows perl(Strawberry),Git for Windows 自带的 cygwin perl 不适用于 MSVC 构建。 +# Strawberry Perl is used for compiling OpenSSL from source. zap_sftp → ssh2(openssl-on-win32) → +# openssl-sys's vendored build calls perl to run OpenSSL's Configure script. +# Must use native Windows perl (Strawberry), Git for Windows's bundled cygwin perl is not suitable for MSVC builds. winget install -e --id StrawberryPerl.StrawberryPerl ` --accept-package-agreements --accept-source-agreements -# protoc(Protocol Buffers 编译器)用于 proto 依赖(如 warp_multi_agent_api)的 build.rs 代码生成。 -# 固定到与 script/linux/install_build_deps 相同的版本,保证跨平台代码生成一致; -# prost-build 要求 protoc >= 3.15(proto3 optional 字段)。winget 的 Google.Protobuf 版本过新,故直接取官方发行版 zip。 +# protoc (Protocol Buffers compiler) is used for build.rs code generation for proto dependencies (e.g. warp_multi_agent_api). +# Fixed to the same version as script/linux/install_build_deps to ensure consistent code generation across platforms; +# prost-build requires protoc >= 3.15 (proto3 optional field). winget's Google.Protobuf version is too new, so get official release zip directly. $protocVersion = '25.1' $protocDir = "$env:LOCALAPPDATA\protoc" $protocExe = "$protocDir\bin\protoc.exe" @@ -67,7 +67,7 @@ if (-not (Test-Path $protocExe)) { Expand-Archive -Path $protocZip -DestinationPath $protocDir -Force Remove-Item $protocZip } -# prost-build 优先读取 PROTOC 环境变量(见构建错误提示),指向固定版本二进制最稳妥。 +# prost-build prioritizes reading PROTOC environment variable (see build error message), pointing to fixed version binary is safest. [Environment]::SetEnvironmentVariable('PROTOC', $protocExe, 'User') # We use InnoSetup to build our release bundle installer. diff --git a/script/windows/bundle.ps1 b/script/windows/bundle.ps1 index 8572c7173f..7b97884c4d 100644 --- a/script/windows/bundle.ps1 +++ b/script/windows/bundle.ps1 @@ -16,9 +16,9 @@ Param ( [String]$RELEASE_TAG = '', [String]$FEATURES = 'release_bundle,crash_reporting,gui', - # Builds only the Zap binary, skips the installer. + # Builds only the Zaplex binary, skips the installer. [Switch]$SKIP_BUILD_INSTALLER = $False, - # Builds only the installer, skips the Zap binary. Use this if the Zap + # Builds only the installer, skips the Zaplex binary. Use this if the Zaplex # binary has already been built. [Switch]$SKIP_BUILD_BINARY = $False, @@ -86,45 +86,45 @@ $BUNDLE_ID = "dev.warp.$app_name" # APP_NAME here must match the value used in Rust as the # application name; see app/src/channel.rs. # -# WARP_BIN is the name of the binary produced by cargo; +# ZAPLEX_BIN is the name of the binary produced by cargo; # BINARY_NAME is the desired name of the binary in the final package. if ("$CHANNEL" -eq 'local') { - $WARP_BIN = 'warp' + $ZAPLEX_BIN = 'warp' $BINARY_NAME = 'warp.exe' $APP_NAME = 'WarpLocal' $FEATURES = "$FEATURES,nld_improvements" } elseif ("$CHANNEL" -eq 'dev') { - $WARP_BIN = 'dev' + $ZAPLEX_BIN = 'dev' $BINARY_NAME = 'dev.exe' $APP_NAME = 'WarpDev' $FEATURES = "$FEATURES,agent_mode_debug,nld_improvements" } elseif ("$CHANNEL" -eq 'preview') { - $WARP_BIN = 'preview' + $ZAPLEX_BIN = 'preview' $BINARY_NAME = 'preview.exe' $APP_NAME = 'WarpPreview' $FEATURES = "$FEATURES,preview_channel,nld_improvements" } elseif ("$CHANNEL" -eq 'stable') { - $WARP_BIN = 'stable' + $ZAPLEX_BIN = 'stable' $BINARY_NAME = 'warp.exe' - $APP_NAME = 'Zap' + $APP_NAME = 'Zaplex' # TODO(vorporeal): Remove this once we get tests passing with this default enabled. $FEATURES = "$FEATURES,nld_improvements" } elseif ("$CHANNEL" -eq 'oss') { - $WARP_BIN = 'zap-oss' - $BINARY_NAME = 'zap-oss.exe' - $APP_NAME = 'Zap' - # OSS channel 使用本地 crash reporting,不启用 release 默认特性集合。 - # autoupdate 走 GitHub Release(zerx-lab/warp),仅下载到 Downloads,不调 Inno Setup。 + $ZAPLEX_BIN = 'zaplex' + $BINARY_NAME = 'zaplex.exe' + $APP_NAME = 'Zaplex' + # OSS channel uses local crash reporting, does not enable release default feature set. + # autoupdate goes through GitHub Release (zerx-lab/warp), only downloads to Downloads, does not call Inno Setup. $FEATURES = 'release_bundle,gui,nld_improvements,autoupdate' } $BINARY_PATH = "$CARGO_TARGET_OUTPUT_DIR\$BINARY_NAME" -# AUMID(Windows AppUserModel ID)—— 必须与进程端 `ChannelState::app_id()` 生成的完全一致, -# 否则 Windows ToastNotificationManager 会在 Start Menu 快捷方式 / 进程 AUMID 不匹配时 -# 静默吞掉 toast。OSS(Zap)在 `app/src/bin/oss.rs` 里是 `dev.zap.Zap`, -# 其他官方 channel 是 `dev.warp.`。 +# AUMID (Windows AppUserModel ID) — must match exactly with what the process side `ChannelState::app_id()` generates, +# otherwise Windows ToastNotificationManager will silently drop toasts when Start Menu shortcut / process AUMID doesn't match. +# OSS (Zaplex) in `app/src/bin/oss.rs` is `dev.zaplex.Zaplex`, +# other official channels are `dev.warp.`. if ("$CHANNEL" -eq 'oss') { - $AUMID = "dev.zap.$APP_NAME" + $AUMID = "dev.zaplex.$APP_NAME" } else { $AUMID = "dev.warp.$APP_NAME" } @@ -132,7 +132,7 @@ $BUNDLE_ID = $AUMID $INSTALLER_OUTPUT_DIR = "$WINDOWS_INSTALLER_DIR\Output" $INSTALLER_NAME = "$($APP_NAME)$($FILE_ENDING)" $INSTALLER_PATH = "$($INSTALLER_OUTPUT_DIR)\$($INSTALLER_NAME).exe" -$PDB_PATH = "$CARGO_TARGET_OUTPUT_DIR\$WARP_BIN.pdb" +$PDB_PATH = "$CARGO_TARGET_OUTPUT_DIR\$ZAPLEX_BIN.pdb" # The CARGO_FULL_PROFILE environment variable is read by the `cargo` build # script (`app/build.rs`) to determine where to place `conpty.dll`. @@ -146,28 +146,28 @@ if ($DEBUG_BUILD) { # then exit. We use this script to invoke `cargo check` to ensure that we are # using the same feature flags and profile that we would be using in production. if ($CHECK_ONLY) { - cargo check -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" --target $PLATFORM_TARGET + cargo check -p warp --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" --target $PLATFORM_TARGET if (-Not $?) { - Write-Error "Failed to verify Zap $WARP_BIN compilation with profile $CARGO_PROFILE" + Write-Error "Failed to verify Zaplex $ZAPLEX_BIN compilation with profile $CARGO_PROFILE" exit 1 } exit 0 } if (-Not $SKIP_BUILD_BINARY) { - Write-Output "Building Zap for channel $CHANNEL and bundle id $BUNDLE_ID" + Write-Output "Building Zaplex for channel $CHANNEL and bundle id $BUNDLE_ID" $env:CARGO_BIN_NAME = $CHANNEL - $env:WARP_APP_NAME = $APP_NAME - cargo build -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" --target $PLATFORM_TARGET + $env:ZAPLEX_APP_NAME = $APP_NAME + cargo build -p warp --profile "$CARGO_PROFILE" --bin "$ZAPLEX_BIN" --features "$FEATURES" --target $PLATFORM_TARGET if (-Not $?) { - Write-Error "Failed to build Zap $WARP_BIN binary with profile $CARGO_PROFILE" + Write-Error "Failed to build Zaplex $ZAPLEX_BIN binary with profile $CARGO_PROFILE" exit 1 } # If we desire an executable name different from the cargo bin, rename it. - if ("$WARP_BIN.exe" -ne $BINARY_NAME) { - $binarySource = "$CARGO_TARGET_OUTPUT_DIR\$WARP_BIN.exe" - Write-Output "Renaming executable $WARP_BIN.exe to $BINARY_NAME" + if ("$ZAPLEX_BIN.exe" -ne $BINARY_NAME) { + $binarySource = "$CARGO_TARGET_OUTPUT_DIR\$ZAPLEX_BIN.exe" + Write-Output "Renaming executable $ZAPLEX_BIN.exe to $BINARY_NAME" Move-Item -Path "$binarySource" -Destination "$BINARY_PATH" -Force } } @@ -211,12 +211,12 @@ if (-Not $?) { exit 1 } -Write-Output 'Building Zap installer' -# Inno Setup `AppId` 决定注册表 Uninstall 条目与升级跟踪键。OSS 下固定为 `zap-oss`, -# 避免留在默认的 `warp-terminal-oss` 上。其他 channel 走 .iss 里的默认 -# `warp-terminal-{ReleaseChannel}`。 +Write-Output 'Building Zaplex installer' +# Inno Setup `AppId` determines the registry Uninstall entry and upgrade tracking key. Under OSS, fixed to `zaplex`, +# avoiding the default `warp-terminal-oss`. Other channels use the default in .iss +# `warp-terminal-{ReleaseChannel}`. if ("$CHANNEL" -eq 'oss') { - $INNO_APP_ID = 'zap-oss' + $INNO_APP_ID = 'zaplex' } else { $INNO_APP_ID = "warp-terminal-$CHANNEL" } diff --git a/script/windows/windows-installer.iss b/script/windows/windows-installer.iss index 7c8b528f37..619ea85adb 100644 --- a/script/windows/windows-installer.iss +++ b/script/windows/windows-installer.iss @@ -17,7 +17,7 @@ #define ReleaseChannel "dev" #endif #ifndef AppUserModelId - ; 默认跟随官方 channel 的 `dev.warp.*` 命名;OSS 在 bundle.ps1 里会覆盖为 `dev.zap.Zap`。 + ; Defaults to the official-channel `dev.warp.*` naming; for OSS, bundle.ps1 overrides it with `dev.zaplex.Zaplex`. #define AppUserModelId "dev.warp." + MyAppName #endif #ifndef TargetProfileDir @@ -40,7 +40,7 @@ [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) -; bundle.ps1 会为 OSS 传入 `InnoAppId=zap-oss`,其他 channel 走默认的 `warp-terminal-{ReleaseChannel}`。 +; For OSS, bundle.ps1 passes `InnoAppId=zaplex`; other channels fall back to the default `warp-terminal-{ReleaseChannel}`. #ifndef InnoAppId #define InnoAppId "warp-terminal-" + ReleaseChannel #endif @@ -239,7 +239,7 @@ begin #if ReleaseChannel == "stable" CmdScriptName := 'oz.cmd' #elif ReleaseChannel == "oss" - CmdScriptName := 'zap-oss.cmd'; + CmdScriptName := 'zaplex.cmd'; #else CmdScriptName := 'oz-{#ReleaseChannel}.cmd'; #endif @@ -247,7 +247,7 @@ begin { Create the helper CMD script } CmdScriptPath := BinDir + '\' + CmdScriptName; CmdScriptContent := '@echo off' + #13#10 + - 'set "WARP_CLI_MODE=1"' + #13#10 + + 'set "ZAPLEX_CLI_MODE=1"' + #13#10 + '"' + ExpandConstant('{app}\{#MyAppExeName}') + '" %*' + #13#10; SaveStringToFile(CmdScriptPath, CmdScriptContent, False); diff --git a/specs/APP-3619/PRODUCT.md b/specs/APP-3619/PRODUCT.md index 1e5ad3d747..c2f4a91d0c 100644 --- a/specs/APP-3619/PRODUCT.md +++ b/specs/APP-3619/PRODUCT.md @@ -13,7 +13,7 @@ When the Warp notification plugin can't be auto-installed (SSH session, or a pre ## Chip Visibility Fix (Remote Sessions) -`should_show_install_plugin_button` hides the chip when `manager.is_installed()` returns true. But `is_installed()` reads the **local** filesystem (`~/.claude/plugins/installed_plugins.json`), not the remote machine's. In any remote session (warpified SSH, legacy SSH, Docker via SSH) where Claude Code runs on the remote, this check is wrong: +`should_show_install_plugin_button` hides the chip when `manager.is_installed()` returns true. But `is_installed()` reads the **local** filesystem (`~/.claude/plugins/installed_plugins.json`), not the remote machine's. In any remote session (zaplexified SSH, legacy SSH, Docker via SSH) where Claude Code runs on the remote, this check is wrong: - Plugin installed locally but not on remote → chip hidden, user stuck with no instructions @@ -114,4 +114,4 @@ Failure state is not persisted. A new terminal session starts fresh in Mode 1 (a - **User installs plugin manually mid-session (without using the chip):** The listener will connect on next `SessionStart` event, chip disappears automatically. - **User clicks chip in Mode 2 then installs manually:** Modal stays open until dismissed. Chip disappears on next render once listener is present. - **Multiple terminal tabs with same agent:** Each tab has its own `AgentInputFooter` with independent failure tracking. This is correct — one tab's failure shouldn't affect another. -- **Warpified SSH (tmux wrapper):** Even though the local filesystem is accessible via tmux, the agent runs on the remote machine. The `is_remote` flag is set for all SSH sessions (warpified or legacy), so Mode 2 applies to all remote sessions. +- **Zaplexified SSH (tmux wrapper):** Even though the local filesystem is accessible via tmux, the agent runs on the remote machine. The `is_remote` flag is set for all SSH sessions (zaplexified or legacy), so Mode 2 applies to all remote sessions. diff --git a/specs/APP-3619/TECH.md b/specs/APP-3619/TECH.md index b6d3f8b240..08d8ce7cce 100644 --- a/specs/APP-3619/TECH.md +++ b/specs/APP-3619/TECH.md @@ -76,9 +76,9 @@ Follows the existing `OpenAutoReloadModal` pattern (`view.rs:18411`, `workspace/ ## 5. Remote Session Detection -`CLIAgentSession` has an `is_remote: bool` field set at session creation from `TerminalView::active_session_is_local()`. This uses `SessionType::WarpifiedRemote` and `IsLegacySSHSession` — the same logic as the SSH host chip (`context_chips/builtins.rs:76`). +`CLIAgentSession` has an `is_remote: bool` field set at session creation from `TerminalView::active_session_is_local()`. This uses `SessionType::ZaplexifiedRemote` and `IsLegacySSHSession` — the same logic as the SSH host chip (`context_chips/builtins.rs:76`). -This avoids relying on `terminal_model.is_ssh_block()` (which only tracks the pre-warpification login phase) or `is_warpified_ssh()` (which misses legacy SSH). The `is_remote` flag is threaded through `set_session` and `register_listener` at all call sites in `terminal/view.rs`. +This avoids relying on `terminal_model.is_ssh_block()` (which only tracks the pre-zaplexification login phase) or `is_zaplexified_ssh()` (which misses legacy SSH). The `is_remote` flag is threaded through `set_session` and `register_listener` at all call sites in `terminal/view.rs`. ## 6. Two-Mode Chip diff --git a/specs/APP-3787/TECH.md b/specs/APP-3787/TECH.md index 6eab1463b2..475a8da6be 100644 --- a/specs/APP-3787/TECH.md +++ b/specs/APP-3787/TECH.md @@ -31,7 +31,7 @@ This spec covers three pieces: - `app/src/terminal/view.rs (11022-11199)` — `TerminalView::handle_session_bootstrapped()` reacts to the event ### Session and SSH types -- `app/src/terminal/model/session.rs (691-699)` — `SessionType::Local` / `SessionType::WarpifiedRemote` +- `app/src/terminal/model/session.rs (691-699)` — `SessionType::Local` / `SessionType::ZaplexifiedRemote` - `app/src/terminal/model/session.rs (426-451)` — `SessionInfo` struct with `hostname`, `user`, `session_type`, `spawning_session_id` - `app/src/terminal/model/terminal_model.rs (632-647)` — `SubshellInitializationInfo` with `ssh_connection_info: Option` - `app/src/terminal/ssh/util.rs (86-89)` — `InteractiveSshCommand { host, port }` diff --git a/specs/APP-3790/TECH-remote-apply-diff.md b/specs/APP-3790/TECH-remote-apply-diff.md index b7bdef766b..692a65bbe4 100644 --- a/specs/APP-3790/TECH-remote-apply-diff.md +++ b/specs/APP-3790/TECH-remote-apply-diff.md @@ -29,7 +29,7 @@ When an AI agent runs in an SSH session, the `ApplyFileDiffs` tool is disabled b **CodeDiffView save/delete/create**: `DiffSessionType` already exists with `Local` and `Remote(HostId)` variants. `set_candidate_diffs` routes to `register_file` (local) or `register_remote_file` (remote). `FileModel` has `FileBackend::Remote` that dispatches save/delete through `RemoteServerClient`. However, `RequestFileEditsExecutor` never sets `diff_session_type` — it defaults to `Local`. -**Agent tool gating**: `get_supported_tools` excludes `ApplyFileDiffs`, `ReadFiles`, and `SearchCodebase` when `session_type` is `WarpifiedRemote`. There is no field on `SessionContext` to indicate whether a `RemoteServerClient` is available. +**Agent tool gating**: `get_supported_tools` excludes `ApplyFileDiffs`, `ReadFiles`, and `SearchCodebase` when `session_type` is `ZaplexifiedRemote`. There is no field on `SessionContext` to indicate whether a `RemoteServerClient` is available. **Post-accept context**: After diffs are accepted, `execute` re-reads files from disk via `read_local_file_context` and sends updated content to the LLM. This would require a network round-trip for remote sessions. @@ -146,7 +146,7 @@ Session type is modeled as two distinct enums to separate immutable bootstrap-ti ```rust pub enum BootstrapSessionType { Local, - WarpifiedRemote, + ZaplexifiedRemote, } ``` @@ -155,7 +155,7 @@ pub enum BootstrapSessionType { ```rust pub enum SessionType { Local, - WarpifiedRemote { host_id: Option }, + ZaplexifiedRemote { host_id: Option }, } ``` @@ -176,10 +176,10 @@ match session_context.session_type() { api::ToolType::SearchCodebase, ]); } - Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => { + Some(SessionType::ZaplexifiedRemote { host_id: Some(_) }) => { supported_tools.push(api::ToolType::ApplyFileDiffs); } - Some(SessionType::WarpifiedRemote { host_id: None }) => { + Some(SessionType::ZaplexifiedRemote { host_id: None }) => { // Feature flag off or not yet connected — no remote tools. } } diff --git a/specs/APP-3790/TECH-remote-read-files.md b/specs/APP-3790/TECH-remote-read-files.md index 78c2165426..9e76584e5c 100644 --- a/specs/APP-3790/TECH-remote-read-files.md +++ b/specs/APP-3790/TECH-remote-read-files.md @@ -2,7 +2,7 @@ ## Problem -The `ReadFiles` agent tool is disabled for remote SSH sessions. `get_supported_tools` skips `ToolType::ReadFiles` when `SessionType::WarpifiedRemote`, because the underlying `read_local_file_context` reads files via `async_fs`, `FileModel::read_text_file`, and local image processing — all local-only APIs. +The `ReadFiles` agent tool is disabled for remote SSH sessions. `get_supported_tools` skips `ToolType::ReadFiles` when `SessionType::ZaplexifiedRemote`, because the underlying `read_local_file_context` reads files via `async_fs`, `FileModel::read_text_file`, and local image processing — all local-only APIs. The remote server already runs on the host machine with full filesystem access and has access to the same dependencies (`warp_files`, `warp_util`, `mime_guess`). Rather than building a degraded client-side approximation, we push the file-reading logic to the server so the ReadFiles tool has full feature parity with local: line-range extraction, binary/image support, metadata, and size limits. @@ -36,7 +36,7 @@ The remote server already runs on the host machine with full filesystem access a **Current `ReadFile` proto** (`remote_server.proto`): `ReadFile { path }` → `ReadFileSuccess { content, exists }`. The server handler just calls `tokio::fs::read_to_string` — no metadata, no line ranges, no size limits, no binary support. -**Tool gating**: `get_supported_tools` excludes `ReadFiles` for `WarpifiedRemote` sessions. `get_supported_cli_agent_tools` also excludes it. +**Tool gating**: `get_supported_tools` excludes `ReadFiles` for `ZaplexifiedRemote` sessions. `get_supported_cli_agent_tools` also excludes it. ## Proposed Changes @@ -135,8 +135,8 @@ Follows the same pattern as `write_file` / `delete_file` — sends request, awai In the `execute` method, after resolving cwd/shell, check `active_session.session_type(ctx)`: - **Local / None**: call `read_local_file_context` as today (unchanged). -- **WarpifiedRemote with host_id**: resolve `RemoteServerClient` via `RemoteServerManager::client_for_host`, call `client.read_file_context(...)`, convert `ReadFileContextResponse` → `ReadFileContextResult` (mapping proto `FileContextProto` → `FileContext`, `FailedFileRead` → `missing_files`). -- **WarpifiedRemote without host_id**: fall through to the local `read_local_file_context` path. +- **ZaplexifiedRemote with host_id**: resolve `RemoteServerClient` via `RemoteServerManager::client_for_host`, call `client.read_file_context(...)`, convert `ReadFileContextResponse` → `ReadFileContextResult` (mapping proto `FileContextProto` → `FileContext`, `FailedFileRead` → `missing_files`). +- **ZaplexifiedRemote without host_id**: fall through to the local `read_local_file_context` path. The remote client lookup uses a unified code path with no `cfg` gating — `RemoteServerManager` and `RemoteServerClient` compile on all targets including WASM. On WASM, `client_for_host` returns `None` (since `connect_session` is a no-op), so the local path is used automatically. @@ -146,7 +146,7 @@ The `read_remote_file` adapter currently uses the old `ReadFile`/`ReadFileSucces ### 7. Enable `ReadFiles` in `get_supported_tools` for remote sessions -In `get_supported_tools` (impl.rs:179), add `api::ToolType::ReadFiles` alongside `ApplyFileDiffs` for the `WarpifiedRemote { host_id: Some(_) }` arm. +In `get_supported_tools` (impl.rs:179), add `api::ToolType::ReadFiles` alongside `ApplyFileDiffs` for the `ZaplexifiedRemote { host_id: Some(_) }` arm. Also in `get_supported_cli_agent_tools` (impl.rs:234), enable `ReadFiles` for remote sessions with a connected host. @@ -161,7 +161,7 @@ sequenceDiagram participant Shared as read_single_file_context LLM->>Executor: ReadFiles action (file locations) - Note over Executor: session_type is WarpifiedRemote with host_id + Note over Executor: session_type is ZaplexifiedRemote with host_id Executor->>Client: read_file_context(files, max_bytes) Client->>Server: ReadFileContextRequest { files, max_file_bytes, max_batch_bytes } diff --git a/specs/APP-3791/TECH.md b/specs/APP-3791/TECH.md index a25c0ea4cd..a5efbc94b6 100644 --- a/specs/APP-3791/TECH.md +++ b/specs/APP-3791/TECH.md @@ -175,7 +175,7 @@ The `SshRemoteServer` branch is added as the **first** check in `new_command_exe 1. SshRemoteServer + IsLegacySSHSession::Yes → RemoteServerCommandExecutor [NEW] 2. SSHTmuxWrapper + tmux_control_mode → TmuxCommandExecutor 3. SessionType::Local (various) → LocalCommandExecutor / MSYS2 / WSL -4. WarpifiedRemote + legacy SSH + !InBandForSSH → RemoteCommandExecutor +4. ZaplexifiedRemote + legacy SSH + !InBandForSSH → RemoteCommandExecutor 5. default → InBandCommandExecutor / NoOp ``` diff --git a/specs/APP-3801/TECH.md b/specs/APP-3801/TECH.md index a2909a8cdb..69c1ae50d2 100644 --- a/specs/APP-3801/TECH.md +++ b/specs/APP-3801/TECH.md @@ -275,7 +275,7 @@ Why rejected: - **Leaks via `ps`.** On any shared-user remote host, argv is world-readable. Disqualifying on security grounds before even considering the daemon. - **No refresh channel; stale at spawn.** Firebase ID tokens expire in ~1 hour. A startup-only credential cannot be updated once baked into argv: even if a later tab on the same daemon has a fresher token, there is no protocol path for it to land on the already-spawned daemon. The runtime `Authenticate` message this spec introduces is the only way to ship a rotation-capable credential. -### 4.2 Environment variable (`WARP_REMOTE_AUTH_TOKEN`) +### 4.2 Environment variable (`ZAPLEX_REMOTE_AUTH_TOKEN`) Why rejected: diff --git a/specs/APP-3872/TECH.md b/specs/APP-3872/TECH.md index 06803e4ba6..0bd20062a8 100644 --- a/specs/APP-3872/TECH.md +++ b/specs/APP-3872/TECH.md @@ -37,7 +37,7 @@ The recording API is exposed through `TestStep` helpers: This means annotated recording is opt-in at the test level. A test author does not need to change how clicks, drags, or keystrokes are authored once recording has started. ### Environment-Based Debugging API -The existing environment variable `WARP_INTEGRATION_TEST_VIDEO` remains available as a convenience override for automatic recording during test runs. This is useful for debugging, but the primary product-facing API remains the explicit step-based recording controls. +The existing environment variable `ZAPLEX_INTEGRATION_TEST_VIDEO` remains available as a convenience override for automatic recording during test runs. This is useful for debugging, but the primary product-facing API remains the explicit step-based recording controls. ## High-Level Architecture diff --git a/specs/APP-4069/TECH.md b/specs/APP-4069/TECH.md index 292ad55301..56f8d68cf1 100644 --- a/specs/APP-4069/TECH.md +++ b/specs/APP-4069/TECH.md @@ -1,12 +1,12 @@ # APP-4069 — SSH Initialization UX Linear: [APP-4069 — Initialization UX](https://linear.app/warpdotdev/issue/APP-4069/initialization-ux) ## 1. Problem -When a user SSHes into a remote host, we want to introduce a choice block for users to choose between (1) installing and connecting to the remote server (2) falling back to the existing warpify behaviour. +When a user SSHes into a remote host, we want to introduce a choice block for users to choose between (1) installing and connecting to the remote server (2) falling back to the existing zaplexify behaviour. -To do so, we'll need to block the current bootstrap and connect server flow. Today, the client has a race where: `PtyController` writes the legacy bootstrap script to the PTY synchronously on `InitShell`, while `TerminalView` in parallel kicks off an async background task in `RemoteServerManager` that checks for the remote-server binary, installs it if missing, and initializes the server. Because the bootstrap is written before the check completes, we cannot defer or cancel warpification based on the check result — by the time we know whether the remote server is available, the legacy warpification has already taken effect. +To do so, we'll need to block the current bootstrap and connect server flow. Today, the client has a race where: `PtyController` writes the legacy bootstrap script to the PTY synchronously on `InitShell`, while `TerminalView` in parallel kicks off an async background task in `RemoteServerManager` that checks for the remote-server binary, installs it if missing, and initializes the server. Because the bootstrap is written before the check completes, we cannot defer or cancel zaplexification based on the check result — by the time we know whether the remote server is available, the legacy zaplexification has already taken effect. This spec resolves the race by deferring the bootstrap write under the control of the remote-server setup outcome, and introduces a **two-option choice block** that appears only when the binary is missing: -- **Yes, install** — flush the bootstrap, install the binary on the remote, launch + handshake the remote server. Session is fully warpified via the remote-server path. -- **No, skip** — flush the stashed bootstrap (so the shell is properly initialized) but do not call `connect_session`. The session falls back to ControlMaster warpification without engaging the remote-server path. +- **Yes, install** — flush the bootstrap, install the binary on the remote, launch + handshake the remote server. Session is fully zaplexified via the remote-server path. +- **No, skip** — flush the stashed bootstrap (so the shell is properly initialized) but do not call `connect_session`. The session falls back to ControlMaster zaplexification without engaging the remote-server path. This spec covers the following two sections: - **Part 1 — Blocking and wiring.** A new per-pane `RemoteServerController` owns the state machine that defers the bootstrap, checks the binary via `RemoteServerManager`, and flushes at the right moment. @@ -32,7 +32,7 @@ Two subscribers react synchronously to `ModelEvent::Handler(AnsiHandlerEvent::In - `TerminalView` (in `view.rs:10813–10836`) spawns `RemoteServerManager::connect_session`, which runs check + install + launch + handshake in a single background task. Because both subscribers fire on the same tick but the view's work is async, the bootstrap is already written by the time the check result is known. This is the core race. `RemoteServerManager::connect_session` today is monolithic: it emits `SetupStateChanged(Checking)` → runs the check → on "not installed" emits `SetupStateChanged(Installing)` and runs the install → on success emits `SetupReady` and proceeds to launch + handshake. There is no way to observe the binary-presence result without also triggering the install. -`ModelEventDispatcher` already has a stash-and-wait gate that waits for both `Bootstrapped` (from the remote shell sourcing the bootstrap script) and `RemoteServerReady` (forwarded today from `RemoteServerManager::SetupReady`) before calling `complete_bootstrapped_session`. The gate logic itself is unchanged, but its success-signal source moves: today `SetupReady` fires after the install decision but before `start_remote_server` and `client.initialize()` have run, so it is optimistic — launch or handshake can still fail after the gate has already resolved with `ready=true` (because `Bootstrapped` typically arrives while the handshake is still in flight), at which point the session has been committed to the warpified path against a manager that has no connected client. §4.2.1 sources the gate's success signal from `SessionConnected` (emitted only after handshake succeeds at `manager.rs:535`) to fix this. +`ModelEventDispatcher` already has a stash-and-wait gate that waits for both `Bootstrapped` (from the remote shell sourcing the bootstrap script) and `RemoteServerReady` (forwarded today from `RemoteServerManager::SetupReady`) before calling `complete_bootstrapped_session`. The gate logic itself is unchanged, but its success-signal source moves: today `SetupReady` fires after the install decision but before `start_remote_server` and `client.initialize()` have run, so it is optimistic — launch or handshake can still fail after the gate has already resolved with `ready=true` (because `Bootstrapped` typically arrives while the handshake is still in flight), at which point the session has been committed to the zaplexified path against a manager that has no connected client. §4.2.1 sources the gate's success signal from `SessionConnected` (emitted only after handshake succeeds at `manager.rs:535`) to fix this. ## 4. Proposed changes ### Part 1: Blocking and wiring #### 4.1 `RemoteServerController` — per-pane orchestrator diff --git a/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md b/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md index 711840d0eb..2d930ff23f 100644 --- a/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md +++ b/specs/Advait-M/cli-agent-rich-input-shell-commands/TECH.md @@ -126,7 +126,7 @@ let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx) if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) { ``` -Note this should NOT be allowed if we're in a Warpified remote host, where we cannot run in-band generators. +Note this should NOT be allowed if we're in a Zaplexified remote host, where we cannot run in-band generators. ### 8. Placeholder text diff --git a/specs/QUALITY-408/TECH.md b/specs/QUALITY-408/TECH.md index 7ac5dad2d9..4571c1b3c0 100644 --- a/specs/QUALITY-408/TECH.md +++ b/specs/QUALITY-408/TECH.md @@ -6,7 +6,7 @@ On macOS, `macos_config_dir_name()` returns `.warp` for both Stable and Preview ## Relevant Code -- `crates/warp_core/src/paths.rs:42-51` — `macos_config_dir_name()` maps channels to directory names. The `Channel::Preview` arm currently returns `WARP_CONFIG_DIR` (`.warp`). +- `crates/warp_core/src/paths.rs:42-51` — `macos_config_dir_name()` maps channels to directory names. The `Channel::Preview` arm currently returns `ZAPLEX_CONFIG_DIR` (`.warp`). - `crates/warp_core/src/paths.rs:57-67` — `data_dir()` uses `macos_config_dir_name()` on macOS. - `crates/warp_core/src/paths.rs:71-83` — `config_local_dir()` also uses `macos_config_dir_name()` on macOS. On macOS, `data_dir()` and `config_local_dir()` return the same path. - `app/src/warp_data_directory_watcher.rs:28-46` — `ensure_warp_watch_roots_exist()` creates the data and config directories at startup. @@ -21,8 +21,8 @@ The `macos_config_dir_name()` function in `paths.rs` determines the macOS config ```rust fn macos_config_dir_name() -> String { match ChannelState::channel() { - Channel::Stable | Channel::Preview => WARP_CONFIG_DIR.to_owned(), - Channel::Dev => format!("{WARP_CONFIG_DIR}-dev"), + Channel::Stable | Channel::Preview => ZAPLEX_CONFIG_DIR.to_owned(), + Channel::Dev => format!("{ZAPLEX_CONFIG_DIR}-dev"), // ... } } @@ -32,7 +32,7 @@ Both `data_dir()` and `config_local_dir()` use this on macOS, so all config path The SQLite database is **not** affected — it is stored under `secure_state_dir()` (App Group container) or `state_dir()` (`~/Library/Application Support/dev.warp.Warp-Preview/`), both of which already use the bundle ID and are channel-specific. -The `WARP_CONFIG_DIR` constant (`.warp`) is also used for per-repository project configs (e.g., `./.warp/workflows`). This is unrelated to the home-directory config and must not change. +The `ZAPLEX_CONFIG_DIR` constant (`.warp`) is also used for per-repository project configs (e.g., `./.warp/workflows`). This is unrelated to the home-directory config and must not change. ## Proposed Changes @@ -43,11 +43,11 @@ Change the Preview arm to return `.warp-preview`: ```rust fn macos_config_dir_name() -> String { match ChannelState::channel() { - Channel::Stable => WARP_CONFIG_DIR.to_owned(), - Channel::Preview => format!("{WARP_CONFIG_DIR}-preview"), - Channel::Dev => format!("{WARP_CONFIG_DIR}-dev"), - Channel::Integration => format!("{WARP_CONFIG_DIR}-integration"), - Channel::Local => format!("{WARP_CONFIG_DIR}-local"), + Channel::Stable => ZAPLEX_CONFIG_DIR.to_owned(), + Channel::Preview => format!("{ZAPLEX_CONFIG_DIR}-preview"), + Channel::Dev => format!("{ZAPLEX_CONFIG_DIR}-dev"), + Channel::Integration => format!("{ZAPLEX_CONFIG_DIR}-integration"), + Channel::Local => format!("{ZAPLEX_CONFIG_DIR}-local"), } } ``` diff --git a/specs/REMOTE-1254/TECH.md b/specs/REMOTE-1254/TECH.md index 3483a726bd..a3426c792e 100644 --- a/specs/REMOTE-1254/TECH.md +++ b/specs/REMOTE-1254/TECH.md @@ -150,7 +150,7 @@ sequenceDiagram ## 6. Risks and mitigations -**OSC vs protocol session conflict on viewer**: If a viewer somehow receives OSC `SessionStart` events (e.g. warpified SSH where plugin events leak), the local OSC path could clobber the protocol-managed session. Mitigated by an early return in `handle_cli_agent_notification` when `is_shared_session_viewer()` is true — the protocol is the sole source of truth for viewer session lifecycle. +**OSC vs protocol session conflict on viewer**: If a viewer somehow receives OSC `SessionStart` events (e.g. zaplexified SSH where plugin events leak), the local OSC path could clobber the protocol-managed session. Mitigated by an early return in `handle_cli_agent_notification` when `is_shared_session_viewer()` is true — the protocol is the sole source of truth for viewer session lifecycle. **Echo loops**: Handled by the existing `RemoteUpdateGuard` pattern — all new broadcast subscribers check `guard.should_broadcast()`, and all incoming `apply_*` calls run inside an `ActiveRemoteUpdate` scope. diff --git a/specs/alokedesai/APP-3797/TECH.md b/specs/alokedesai/APP-3797/TECH.md index 357474c09e..440ab24fd7 100644 --- a/specs/alokedesai/APP-3797/TECH.md +++ b/specs/alokedesai/APP-3797/TECH.md @@ -77,12 +77,12 @@ The setup runs as an async task, using the existing SSH ControlMaster socket (fr *) echo "unsupported arch: $arch" >&2; exit 2 ;; esac mkdir -p "$HOME/.warp/remote-server" - curl -fSL "$WARP_GET_URL?package=$pkg" -o "$HOME/.warp/remote-server/oz.tar.gz" + curl -fSL "$ZAPLEX_GET_URL?package=$pkg" -o "$HOME/.warp/remote-server/oz.tar.gz" tar -xzf "$HOME/.warp/remote-server/oz.tar.gz" -C "$HOME/.warp/remote-server" chmod +x "$HOME/.warp/remote-server/oz" ``` - `$WARP_GET_URL` is substituted at runtime from the configured server root URL (`SERVER_ROOT_URL` env var or its compiled-in default), pointing at `/download/cli`. Exit code 2 is mapped to `ErrorReason::UnsupportedPlatform`. The script is shipped as a constant `&str` in the install module. Parse `curl` stderr for download progress if available. + `$ZAPLEX_GET_URL` is substituted at runtime from the configured server root URL (`SERVER_ROOT_URL` env var or its compiled-in default), pointing at `/download/cli`. Exit code 2 is mapped to `ErrorReason::UnsupportedPlatform`. The script is shipped as a constant `&str` in the install module. Parse `curl` stderr for download progress if available. 3. **Launch**: `ssh -o ControlPath={socket} placeholder@placeholder '~/.warp/remote-server/oz'` — keep the SSH channel open, forwarding stdin/stdout. 4. **Initialize**: Send a `ClientMessage { request_id, initialize: Initialize {} }` (length-prefixed protobuf) to the process's stdin. Read a `ServerMessage { initialize_response }` from stdout. Timeout after 10 seconds. diff --git a/specs/andy/CODE-1779/PRODUCT.md b/specs/andy/CODE-1779/PRODUCT.md index 7a250bece3..b0c5daaaf4 100644 --- a/specs/andy/CODE-1779/PRODUCT.md +++ b/specs/andy/CODE-1779/PRODUCT.md @@ -33,7 +33,7 @@ When a Warp tab is attached to a Unix-like shell on Windows — WSL, or MSYS2 / 4. Shell-specific escaping (quoting spaces, special characters) applies on top of the transformed path using the active session's shell family — identical to the non-WSL/non-MSYS2 behavior today. -5. When the active session is neither WSL nor MSYS2/Git Bash (local PowerShell, cmd, SSH into a remote host, Warpified remote, etc.), dropped paths are inserted exactly as they are today. No transformation happens. +5. When the active session is neither WSL nor MSYS2/Git Bash (local PowerShell, cmd, SSH into a remote host, Zaplexified remote, etc.), dropped paths are inserted exactly as they are today. No transformation happens. 6. Image auto-attachment (dragging an image file into Agent Mode / an empty buffer, which attaches it as AI image context) continues to use the original Windows-native path for filesystem reads, regardless of WSL / MSYS2 state. Transformed paths would not be readable from the Windows host. @@ -47,7 +47,7 @@ When a Warp tab is attached to a Unix-like shell on Windows — WSL, or MSYS2 / 10. Non-regressions: - Dropping into any non-terminal editor (notebooks, settings, themes, etc.) is unchanged — no path transformation is applied. - - Dropping into a non-WSL, non-MSYS2 terminal session (PowerShell, cmd, SSH, remote Warpified) is unchanged. + - Dropping into a non-WSL, non-MSYS2 terminal session (PowerShell, cmd, SSH, remote Zaplexified) is unchanged. - The terminal-grid long-running-command code paths for both WSL and MSYS2 are unchanged. - Dropping image-only content into the input in Agent Mode still attaches the images; nothing about attachment behavior changes. - Pasting paths via clipboard is out of scope for this ticket and remains unchanged. diff --git a/specs/andy/CODE-1786/PRODUCT.md b/specs/andy/CODE-1786/PRODUCT.md index 7edf2efbf3..c4c82c935a 100644 --- a/specs/andy/CODE-1786/PRODUCT.md +++ b/specs/andy/CODE-1786/PRODUCT.md @@ -14,7 +14,7 @@ On macOS, Warp exposes a **Start Warp at login** toggle in **Settings → Featur 6. **Launch behavior at login.** When Warp launches as a result of a Windows sign-in, it launches silently without stealing focus — no splash screen, no window auto-popped to the foreground. The user's normal activation paths (global hotkey, clicking the tray/taskbar entry, etc.) surface the window on demand. Session restoration follows the existing **Restore previous session** setting. 7. **Per-channel isolation.** Dev, Preview, and Stable installs register under distinct identifiers so that having multiple channels installed does not cause one channel to overwrite another's startup entry, and toggling the setting in one channel does not affect the others. 8. **Dev builds do not register.** Running Warp from `cargo run` or any unbundled/dev-channel binary does not write a startup entry, even if the toggle is on. This mirrors the existing macOS check that skips registration when not running from a real bundle, and prevents developer machines from autostarting every local build. -9. **Integration test builds do not register.** When the `WARP_INTEGRATION` environment variable is set, Warp skips all registration/unregistration work regardless of the toggle state, mirroring the existing macOS guard. +9. **Integration test builds do not register.** When the `ZAPLEX_INTEGRATION` environment variable is set, Warp skips all registration/unregistration work regardless of the toggle state, mirroring the existing macOS guard. 10. **Moving or renaming the install.** If the Warp executable moves (e.g. an update reinstalls to a new path, or a portable install is relocated), the existing registration keeps pointing at the old path until the user toggles **Start Warp at login** off and back on, at which point Warp rewrites the entry against the new path. A stale entry whose target no longer exists is left in place (Windows itself handles the dangling entry). Automatic detection on launch is tracked as a follow-up. 11. **Visibility to the user outside Warp.** The startup entry must be visible and removable in Windows' standard UIs — at minimum, **Settings → Apps → Startup** on Windows 10/11 and **Task Manager → Startup apps**. The entry's display name is "Warp" (or the channel-specific name, e.g. "Warp Preview", "Warp Dev") so users can identify it. 12. **Telemetry.** Toggling the setting emits the same `ToggleLoginItem` features-page telemetry event that the macOS toggle emits today, so rollout can be measured consistently across platforms. diff --git a/specs/andy/CODE-1786/TECH.md b/specs/andy/CODE-1786/TECH.md index 33490ab203..0ea8d1abf4 100644 --- a/specs/andy/CODE-1786/TECH.md +++ b/specs/andy/CODE-1786/TECH.md @@ -6,7 +6,7 @@ The macOS "Start Warp at login" feature is already plumbed end-to-end. This tick Relevant code today: - `app/src/terminal/general_settings.rs:36-55` — `add_app_as_login_item` (`LoginItem`) and `app_added_as_login_item` (`AppAddedAsLoginItem`), both gated on `SupportedPlatforms::MAC` with defaults `true` / `false` respectively. The second setting is the "already registered" bookkeeping that prevents clobbering a manual unregister. - `app/src/lib.rs:2229-2239` — startup wiring. Subscribes to `GeneralSettingsChangedEvent::LoginItem` and calls `maybe_register_app_as_login_item` on change + once at launch. Entire block is `#[cfg(target_os = "macos")]`. -- `app/src/lib.rs:2279-2362` — `maybe_register_app_as_login_item`. macOS-only. Guards on `WARP_INTEGRATION`, skips when bundle identifier is missing or equals `dev.warp.Warp-Local`, runs `SMAppService register/unregisterAndReturnError:` off the UI thread, then writes the result back to `app_added_as_login_item`. +- `app/src/lib.rs:2279-2362` — `maybe_register_app_as_login_item`. macOS-only. Guards on `ZAPLEX_INTEGRATION`, skips when bundle identifier is missing or equals `dev.warp.Warp-Local`, runs `SMAppService register/unregisterAndReturnError:` off the UI thread, then writes the result back to `app_added_as_login_item`. - `app/src/settings_view/features_page.rs` — toggle UI: - action enum: `FeaturesPageAction::ToggleLoginItem` (~l618) - telemetry: `ToggleLoginItem` branch (~l975-978) @@ -24,7 +24,7 @@ Create `app/src/login_item/mod.rs` (or reuse an existing "platform adapters" spo ```rust path=null start=null // app/src/login_item/mod.rs pub fn maybe_register_app_as_login_item(ctx: &mut AppContext) { - if std::env::var("WARP_INTEGRATION").is_ok() { + if std::env::var("ZAPLEX_INTEGRATION").is_ok() { log::debug!("Not registering as a login item in integration tests"); return; } @@ -99,7 +99,7 @@ Unit tests (Windows, gated with `#[cfg(target_os = "windows")]`): Because the real `Software\Microsoft\Windows\CurrentVersion\Run` hive is shared user state that should not be mutated by tests, the registry helpers should accept an injectable subkey path (e.g. `register_with_subkey(subkey, …)`) and the tests should drive them against `Software\Warp\TestRun\` under HKCU, cleaning up on drop. Follow the `crates/warpui/src/windowing/winit/windows/registry.rs` style for the read-side helper for consistency. Cross-platform tests (run on every OS): - **Invariant 1/13 (platform gate).** Assert `LoginItem::supported_platforms().matches_current_platform()` is true on mac+windows, false on wasm. This is already covered structurally by `SupportedPlatforms::OR`; a small regression test in `general_settings`/`features_page` confirms the intent stays stable. -- **Invariant 9 (integration test guard).** With `WARP_INTEGRATION` set, calling `maybe_register_app_as_login_item` does no registry I/O. Can be asserted by calling with a mock subkey provider that panics if invoked. +- **Invariant 9 (integration test guard).** With `ZAPLEX_INTEGRATION` set, calling `maybe_register_app_as_login_item` does no registry I/O. Can be asserted by calling with a mock subkey provider that panics if invoked. Manual validation (Windows): 1. **Invariants 1, 2, 11.** Install a release-bundle Windows build, verify **Features → General** shows *Start Warp at login*, toggle default is on, and `HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Warp` exists with the installed exe path. Confirm the entry is visible in **Settings → Apps → Startup** and **Task Manager → Startup apps** with the display name "Warp". 2. **Invariant 3–4.** Toggle off; confirm the registry value is gone and Windows UIs no longer show the entry. Toggle back on; confirm value is rewritten. diff --git a/specs/gh-110-ssh-config-import/PRODUCT.md b/specs/gh-110-ssh-config-import/PRODUCT.md index ced6fd8084..4ec8d9b42e 100644 --- a/specs/gh-110-ssh-config-import/PRODUCT.md +++ b/specs/gh-110-ssh-config-import/PRODUCT.md @@ -16,7 +16,7 @@ Two consequences: re-enter every host/user/port/key by hand before the SSH Manager UI is useful to them. 2. **Inconsistency across SSH entry paths.** Zap has two SSH paths: - - Path ① — typing `ssh prodbox` in any terminal tab triggers Warpification + - Path ① — typing `ssh prodbox` in any terminal tab triggers Zaplexification and `exec`s the system `ssh`, which transparently reads `~/.ssh/config` and resolves the `prodbox` alias. - Path ② — the SSH Manager panel completely ignores the same file. diff --git a/specs/network-log-in-app/TECH.md b/specs/network-log-in-app/TECH.md index 798b258abb..6c186670b2 100644 --- a/specs/network-log-in-app/TECH.md +++ b/specs/network-log-in-app/TECH.md @@ -15,7 +15,7 @@ Companion to `PRODUCT.md` in this directory; refer there for user-visible behavi - Add `NetworkLogModel` in `app/src/server/network_logging.rs`: singleton entity with a `VecDeque` capped at `NETWORK_LOGGING_MAX_ITEMS = 50` (matches current file-rotation threshold). `push(item, ctx)` pops the front when over capacity; `snapshot_text(&self) -> String` joins items with `\n`. - `pub(crate)` on `NetworkLogItem`; keep its `Display` impl and the existing timestamp + `{:?}` request/response format so pane output matches what `warp_network.log` used to contain. - Rewrite `init(..)` to take the `ModelContext`. Keep the bounded channel and client hooks unchanged. Replace the disk-writing background task with `ctx.spawn_stream_local(rx, |_, item, ctx| NetworkLogModel::handle(ctx).update(ctx, |m, ctx| m.push(item, ctx)), |_, _| {})`. This mirrors the existing `event_receiver` pattern in `ServerApiProvider::new`. -- Delete `truncate_and_restart_log`, `log_file_path()`, the `WARP_LOGS_DIR` branch, and the `local_fs` cfg split. Remove dead `warp_core::paths` / `PathBuf` imports. +- Delete `truncate_and_restart_log`, `log_file_path()`, the `ZAPLEX_LOGS_DIR` branch, and the `local_fs` cfg split. Remove dead `warp_core::paths` / `PathBuf` imports. - Register the singleton in `app/src/lib.rs::initialize_app` (before `ServerApiProvider`): `ctx.add_singleton_model(|_| NetworkLogModel::default())`. ### View, pane, and manager - `app/src/server/network_log_view.rs`: `NetworkLogView` owns a `ViewHandle` and a `ModelHandle` titled "Network log". On `new`, build `CodeEditorView` with `buffer: None` + default `CodeEditorRenderOptions`, seed with `NetworkLogModel::as_ref(ctx).snapshot_text()` via `editor.reset(InitialBufferState::plain_text(text), ctx)`, and call `set_interaction_state(InteractionState::ReadOnly, ctx)`. No subscription to the model — single snapshot per open.