diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..0d195065 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,174 @@ +name: Build & Release + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - main + +jobs: + build: + name: Build macOS App + runs-on: self-hosted + permissions: + contents: write + packages: write + actions: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Cache Xcode derived data + uses: irgaly/xcode-cache@v1 + with: + key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.ref_name }} + restore-keys: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.ref_name }} + + - name: Install create-dmg + run: npm install --global create-dmg + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Install Python dependencies + run: pip install markdown + + - name: Import code signing certificate + uses: apple-actions/import-codesign-certs@v6 + with: + p12-file-base64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + p12-password: ${{ secrets.P12_PASSWORD }} + + - name: Bump version (release only) + if: github.event_name == 'release' + run: | + # Strip leading 'v' from release tag (v1.2.3 -> 1.2.3) — Apple's + # MARKETING_VERSION must be numeric. + RELEASE_VERSION="${GITHUB_REF_NAME#v}" + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_ENV" + ./scripts/ci/update-version.sh "${RELEASE_VERSION}" + + - name: Build archive + run: | + gem install xcpretty + # CURRENT_PROJECT_VERSION is overridden per-build so every CI build + # has a unique build number (github.run_number), without committing + # churn to project.pbxproj. + set -o pipefail && xcodebuild -destination platform=macOS \ + -project RxCode.xcodeproj \ + -scheme RxCode \ + -configuration Release \ + -archivePath output/output.xcarchive \ + -allowProvisioningUpdates \ + CODE_SIGN_IDENTITY="${{ secrets.SIGNING_CERTIFICATE_NAME }}" \ + CODE_SIGN_STYLE=Manual \ + OTHER_CODE_SIGN_FLAGS="--options=runtime --timestamp" \ + CURRENT_PROJECT_VERSION=${{ github.run_number }} \ + archive | xcpretty + + - name: Sign Sparkle + run: ./scripts/ci/sign-sparkle.sh + env: + SIGNING_CERTIFICATE_NAME: ${{ secrets.SIGNING_CERTIFICATE_NAME }} + + - name: Notarize + run: ./scripts/ci/notary.sh + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PWD: ${{ secrets.APPLE_ID_PWD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + + - name: Generate appcast + run: ./scripts/ci/generate-appcast.sh + env: + SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }} + VERSION: ${{ github.ref_name }} + BUILD_NUMBER: ${{ github.run_number }} + RELEASE_NOTE: ${{ github.event.release.body }} + + # Upload artifact for Pull Requests (1 day retention) + - name: Upload artifact for PR + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v7 + with: + name: RxCode-PR-${{ github.event.pull_request.number }} + path: RxCode.dmg + retention-days: 1 + + # Upload to release for release events + - name: Upload DMG to Release + if: github.event_name == 'release' + uses: softprops/action-gh-release@v3 + with: + files: RxCode.dmg + token: ${{ secrets.GITHUB_TOKEN }} + + # Upload appcast as an artifact for the deploy job + - name: Upload appcast for deploy job + if: github.event_name == 'release' + uses: actions/upload-artifact@v7 + with: + name: appcast-${{ github.sha }} + path: appcast.xml + retention-days: 1 + + - name: Upload release notes for deploy job + if: github.event_name == 'release' + uses: actions/upload-artifact@v7 + with: + name: release_notes-${{ github.sha }} + path: release_notes.html + retention-days: 1 + + deploy: + name: Deploy to GitHub Pages + needs: build + if: github.event_name == 'release' + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Download appcast from build job + uses: actions/download-artifact@v8 + with: + name: appcast-${{ github.sha }} + + - name: Download release notes from build job + uses: actions/download-artifact@v8 + with: + name: release_notes-${{ github.sha }} + + - name: Prepare pages directory + run: | + mkdir -p pages + cp appcast.xml pages/ + cp release_notes.html pages/ + # Pin custom domain — without this, deploy-pages strips the CNAME + # GitHub wrote when the domain was set in Settings → Pages. + echo "update.code.rxlab.app" > pages/CNAME + + - name: Setup Pages + uses: actions/configure-pages@v6 + + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v5 + with: + path: "pages" + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/create-release.yaml b/.github/workflows/create-release.yaml new file mode 100644 index 00000000..3ea12171 --- /dev/null +++ b/.github/workflows/create-release.yaml @@ -0,0 +1,44 @@ +name: Create Release +on: + workflow_dispatch: + push: + +jobs: + create-release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: "22" + - name: Setup Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Dry Run Release + uses: cycjimmy/semantic-release-action@v6 + if: github.event_name == 'push' + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + with: + dry_run: true + branches: | + [ + '+([0-9])?(.{+([0-9]),x}).x', + 'main', + {name: '*', prerelease: true} + ] + extra_plugins: | + @semantic-release/exec + - name: Create Release + uses: cycjimmy/semantic-release-action@v6 + if: github.event_name == 'workflow_dispatch' + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + with: + branch: main + extra_plugins: | + @semantic-release/exec diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..df1dc22b --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,66 @@ +name: Tests + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + +jobs: + test-packages: + name: swift test (Packages) + runs-on: self-hosted + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Install xcpretty + run: gem install xcpretty + + - name: Swift version + run: swift --version + + - name: Run package tests + working-directory: Packages + run: | + set -o pipefail + swift test 2>&1 | xcpretty + + build-smoke: + name: xcodebuild build (Release) + runs-on: self-hosted + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Cache Xcode derived data + uses: irgaly/xcode-cache@v1 + with: + key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.ref_name }} + restore-keys: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.ref_name }} + + - name: Install xcpretty + run: gem install xcpretty + + - name: Build (no signing) + run: | + set -o pipefail && xcodebuild \ + -project RxCode.xcodeproj \ + -scheme RxCode \ + -configuration Release \ + -destination platform=macOS \ + CODE_SIGNING_ALLOWED=NO \ + build | xcpretty diff --git a/.gitignore b/.gitignore index f86e65b9..e46d89e9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,12 @@ scripts/.sparkle_private_key # Sparkle CLI tools (reinstallable via setup_sparkle.sh) scripts/sparkle_tools/ + +# CI build outputs +output/ +test-results/ +RxCode.dmg +sparkle.key +release_notes.html +release_notes.md + diff --git a/.releaserc b/.releaserc new file mode 100644 index 00000000..5bc96d2c --- /dev/null +++ b/.releaserc @@ -0,0 +1,3 @@ +{ + plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator', '@semantic-release/github'] +} diff --git a/AGENTS.md b/AGENTS.md index 7e4dc9f6..c14caabb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t ## Project Overview -Clarc is a native macOS desktop client for the Codex CLI. Written in Swift + SwiftUI with two external dependencies: SwiftTerm (terminal emulation) and Sparkle (auto-update). +RxCode is a native macOS desktop client for the Codex CLI. Written in Swift + SwiftUI with two external dependencies: SwiftTerm (terminal emulation) and Sparkle (auto-update). ## Writing Rules @@ -14,18 +14,18 @@ Clarc is a native macOS desktop client for the Codex CLI. Written in Swift + Swi ```bash # Open in Xcode (build/run with Cmd+R) -open Clarc.xcodeproj +open RxCode.xcodeproj # CLI build -xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Debug build +xcodebuild -project RxCode.xcodeproj -scheme RxCode -configuration Debug build # Release build -xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Release build +xcodebuild -project RxCode.xcodeproj -scheme RxCode -configuration Release build ``` - Minimum deployment target: macOS 15.0+ - No test suite (UI app) -- Bundle ID: `com.idealapp.Clarc` +- Bundle ID: `com.idealapp.RxCode` - External dependencies: SwiftTerm (terminal emulation), Sparkle (auto-update) ## Architecture @@ -33,7 +33,7 @@ xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Release build ### Core Patterns - **Observable AppState** (`App/AppState.swift`): `@MainActor @Observable` single state container. Manages all app state including projects, sessions, chat, and permission approvals. -- **App entry point** (`App/ClarcApp.swift`): Defines WindowGroup (main), WindowGroup(id: "project-window") for dedicated per-project windows, Settings window, and Command menu (theme, update). +- **App entry point** (`App/RxCodeApp.swift`): Defines WindowGroup (main), WindowGroup(id: "project-window") for dedicated per-project windows, Settings window, and Command menu (theme, update). - **Actor-based services**: All services are implemented as `actor` for concurrency safety. Isolated without locks. - **SwiftUI only**: No Storyboards or XIBs. 100% declarative UI. @@ -43,8 +43,8 @@ The codebase is split into two Swift packages under `Packages/`: | Package | Role | | -------------- | ---------------------------------------------------------------- | -| `ClarcCore` | Shared models, theme, utilities — no UI dependencies | -| `ClarcChatKit` | Chat UI components (ChatView, MessageBubble, InputBarView, etc.) | +| `RxCodeCore` | Shared models, theme, utilities — no UI dependencies | +| `RxCodeChatKit` | Chat UI components (ChatView, MessageBubble, InputBarView, etc.) | ### Service Layer (`Services/`) @@ -53,7 +53,7 @@ The codebase is split into two Swift packages under `Packages/`: | `ClaudeService` | Spawns Codex CLI as a subprocess, parses stdout NDJSON stream, buffers text deltas at 50ms intervals | | `PermissionServer` | Network framework-based local HTTP server (ports 19836–19846). Receives CLI PreToolUse hook requests and holds the connection until UI approval | | `GitHubService` | OAuth Device Flow authentication, Keychain token storage, SSH key generation/registration, repo cloning | -| `PersistenceService` | JSON file-based persistence at `~/Library/Application Support/Clarc/`. Per-project/session directory structure | +| `PersistenceService` | JSON file-based persistence at `~/Library/Application Support/RxCode/`. Per-project/session directory structure | | `MarketplaceService` | Parallel fetch of plugin catalog from 4 Anthropic GitHub repos, 5-minute cache | | `RateLimitService` | Anthropic usage API polling, OAuth token refresh, usage tracking | | `UpdateService` | Sparkle-based auto-update manager. Starts updater on launch; exposes `checkForUpdates()` for menu-initiated checks | diff --git a/CLAUDE.md b/CLAUDE.md index be903774..5830ca99 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Clarc is a native macOS desktop client for the Claude Code CLI. Written in Swift + SwiftUI with two external dependencies: SwiftTerm (terminal emulation) and Sparkle (auto-update). +RxCode is a native macOS desktop client for the Claude Code CLI. Written in Swift + SwiftUI with two external dependencies: SwiftTerm (terminal emulation) and Sparkle (auto-update). ## Writing Rules @@ -14,18 +14,18 @@ Clarc is a native macOS desktop client for the Claude Code CLI. Written in Swift ```bash # Open in Xcode (build/run with Cmd+R) -open Clarc.xcodeproj +open RxCode.xcodeproj # CLI build -xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Debug build +xcodebuild -project RxCode.xcodeproj -scheme RxCode -configuration Debug build # Release build -xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Release build +xcodebuild -project RxCode.xcodeproj -scheme RxCode -configuration Release build ``` - Minimum deployment target: macOS 15.0+ - No test suite (UI app) -- Bundle ID: `com.idealapp.Clarc` +- Bundle ID: `com.idealapp.RxCode` - External dependencies: SwiftTerm (terminal emulation), Sparkle (auto-update) ## Architecture @@ -33,7 +33,7 @@ xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Release build ### Core Patterns - **Observable AppState** (`App/AppState.swift`): `@MainActor @Observable` single state container. Manages all app state including projects, sessions, chat, and permission approvals. -- **App entry point** (`App/ClarcApp.swift`): Defines WindowGroup (main), WindowGroup(id: "project-window") for dedicated per-project windows, Settings window, and Command menu (theme, update). +- **App entry point** (`App/RxCodeApp.swift`): Defines WindowGroup (main), WindowGroup(id: "project-window") for dedicated per-project windows, Settings window, and Command menu (theme, update). - **Actor-based services**: All services are implemented as `actor` for concurrency safety. Isolated without locks. - **SwiftUI only**: No Storyboards or XIBs. 100% declarative UI. @@ -43,8 +43,8 @@ The codebase is split into two Swift packages under `Packages/`: | Package | Role | |---------|------| -| `ClarcCore` | Shared models, theme, utilities — no UI dependencies | -| `ClarcChatKit` | Chat UI components (ChatView, MessageBubble, InputBarView, etc.) | +| `RxCodeCore` | Shared models, theme, utilities — no UI dependencies | +| `RxCodeChatKit` | Chat UI components (ChatView, MessageBubble, InputBarView, etc.) | ### Service Layer (`Services/`) @@ -53,7 +53,7 @@ The codebase is split into two Swift packages under `Packages/`: | `ClaudeService` | Spawns Claude CLI as a subprocess, parses stdout NDJSON stream, buffers text deltas at 50ms intervals | | `PermissionServer` | Network framework-based local HTTP server (ports 19836–19846). Receives CLI PreToolUse hook requests and holds the connection until UI approval | | `GitHubService` | OAuth Device Flow authentication, Keychain token storage, SSH key generation/registration, repo cloning | -| `PersistenceService` | JSON file-based persistence at `~/Library/Application Support/Clarc/`. Per-project/session directory structure | +| `PersistenceService` | JSON file-based persistence at `~/Library/Application Support/RxCode/`. Per-project/session directory structure | | `MarketplaceService` | Parallel fetch of plugin catalog from 4 Anthropic GitHub repos, 5-minute cache | | `RateLimitService` | Anthropic usage API polling, OAuth token refresh, usage tracking | | `UpdateService` | Sparkle-based auto-update manager. Starts updater on launch; exposes `checkForUpdates()` for menu-initiated checks | diff --git a/Clarc/Views/LoadingView.swift b/Clarc/Views/LoadingView.swift deleted file mode 100644 index 0d275ca5..00000000 --- a/Clarc/Views/LoadingView.swift +++ /dev/null @@ -1,31 +0,0 @@ -import SwiftUI -import ClarcCore - -/// Splash shown while `AppState.initialize()` is loading projects, sessions, -/// and warming services. Replaced by the main view as soon as -/// `AppState.isInitialized` flips. -struct LoadingView: View { - @State private var pulse = false - - var body: some View { - VStack(spacing: 20) { - Image(systemName: "sparkle") - .font(.system(size: ClaudeTheme.size(56), weight: .light)) - .foregroundStyle(ClaudeTheme.accent) - .scaleEffect(pulse ? 1.08 : 0.94) - .opacity(pulse ? 1.0 : 0.7) - .animation(.easeInOut(duration: 1.1).repeatForever(autoreverses: true), value: pulse) - - Text("Clarc") - .font(.system(size: ClaudeTheme.size(28), weight: .semibold)) - .foregroundStyle(ClaudeTheme.textPrimary) - - ProgressView() - .controlSize(.small) - .padding(.top, 4) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(ClaudeTheme.background.ignoresSafeArea()) - .onAppear { pulse = true } - } -} diff --git a/Packages/Package.swift b/Packages/Package.swift index fb457032..2801c55d 100644 --- a/Packages/Package.swift +++ b/Packages/Package.swift @@ -2,25 +2,25 @@ import PackageDescription let package = Package( - name: "ClarcPackages", + name: "RxCodePackages", defaultLocalization: "en", platforms: [.macOS(.v15)], products: [ - .library(name: "ClarcCore", targets: ["ClarcCore"]), - .library(name: "ClarcChatKit", targets: ["ClarcChatKit"]), + .library(name: "RxCodeCore", targets: ["RxCodeCore"]), + .library(name: "RxCodeChatKit", targets: ["RxCodeChatKit"]), ], dependencies: [ .package(url: "https://github.com/nalexn/ViewInspector", from: "0.10.0"), ], targets: [ .target( - name: "ClarcCore", - path: "Sources/ClarcCore" + name: "RxCodeCore", + path: "Sources/RxCodeCore" ), .target( - name: "ClarcChatKit", - dependencies: ["ClarcCore"], - path: "Sources/ClarcChatKit", + name: "RxCodeChatKit", + dependencies: ["RxCodeCore"], + path: "Sources/RxCodeChatKit", resources: [ .process("Resources"), ], @@ -29,18 +29,18 @@ let package = Package( ] ), .testTarget( - name: "ClarcCoreTests", - dependencies: ["ClarcCore"], - path: "Tests/ClarcCoreTests" + name: "RxCodeCoreTests", + dependencies: ["RxCodeCore"], + path: "Tests/RxCodeCoreTests" ), .testTarget( - name: "ClarcChatKitTests", + name: "RxCodeChatKitTests", dependencies: [ - "ClarcChatKit", - "ClarcCore", + "RxCodeChatKit", + "RxCodeCore", .product(name: "ViewInspector", package: "ViewInspector"), ], - path: "Tests/ClarcChatKitTests" + path: "Tests/RxCodeChatKitTests" ), ] ) diff --git a/Packages/Sources/ClarcCore/Utilities/AppSupport.swift b/Packages/Sources/ClarcCore/Utilities/AppSupport.swift deleted file mode 100644 index 37dc6a66..00000000 --- a/Packages/Sources/ClarcCore/Utilities/AppSupport.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -/// Application Support directory scoped by the running bundle identifier. -/// Production (`com.idealapp.Clarc`) keeps the historical `Clarc` directory. -/// Any other bundle id (e.g. a dev build at `com.idealapp.Clarc.dev`) maps to -/// `Clarc.` so alternate builds never share state with production. -public enum AppSupport { - public static let bundleScopedURL: URL = { - let root = FileManager.default - .urls(for: .applicationSupportDirectory, in: .userDomainMask) - .first! - return root.appendingPathComponent(directoryName, isDirectory: true) - }() - - private static var directoryName: String { - let bundleID = Bundle.main.bundleIdentifier ?? "com.idealapp.Clarc" - if bundleID == "com.idealapp.Clarc" { return "Clarc" } - let suffix = bundleID.split(separator: ".").last.map(String.init) ?? "dev" - return "Clarc.\(suffix)" - } -} diff --git a/Packages/Sources/ClarcChatKit/AskUserQuestionView.swift b/Packages/Sources/RxCodeChatKit/AskUserQuestionView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/AskUserQuestionView.swift rename to Packages/Sources/RxCodeChatKit/AskUserQuestionView.swift index ccb9914e..e34e0e74 100644 --- a/Packages/Sources/ClarcChatKit/AskUserQuestionView.swift +++ b/Packages/Sources/RxCodeChatKit/AskUserQuestionView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Minimal historical bubble for a Claude Code `AskUserQuestion` tool call. /// Shows only "Agent asked N questions" plus whether the user has answered. diff --git a/Packages/Sources/ClarcChatKit/AttachmentPreviewItem.swift b/Packages/Sources/RxCodeChatKit/AttachmentPreviewItem.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/AttachmentPreviewItem.swift rename to Packages/Sources/RxCodeChatKit/AttachmentPreviewItem.swift index 7d54eac1..e049b997 100644 --- a/Packages/Sources/ClarcChatKit/AttachmentPreviewItem.swift +++ b/Packages/Sources/RxCodeChatKit/AttachmentPreviewItem.swift @@ -1,6 +1,6 @@ import SwiftUI import LinkPresentation -import ClarcCore +import RxCodeCore /// Attachment preview item — tall rectangular card /// Shows an X button overlay on mouse hover diff --git a/Packages/Sources/ClarcChatKit/AutoScrollAnchor.swift b/Packages/Sources/RxCodeChatKit/AutoScrollAnchor.swift similarity index 100% rename from Packages/Sources/ClarcChatKit/AutoScrollAnchor.swift rename to Packages/Sources/RxCodeChatKit/AutoScrollAnchor.swift diff --git a/Packages/Sources/ClarcChatKit/BashTerminalSheet.swift b/Packages/Sources/RxCodeChatKit/BashTerminalSheet.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/BashTerminalSheet.swift rename to Packages/Sources/RxCodeChatKit/BashTerminalSheet.swift index f5733d62..e766143f 100644 --- a/Packages/Sources/ClarcChatKit/BashTerminalSheet.swift +++ b/Packages/Sources/RxCodeChatKit/BashTerminalSheet.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Terminal-styled sheet that surfaces the output of a completed `Bash` tool call. /// Mirrors the look of the interactive terminal popup (dark background, monospaced diff --git a/Packages/Sources/ClarcChatKit/BubbleStyle.swift b/Packages/Sources/RxCodeChatKit/BubbleStyle.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/BubbleStyle.swift rename to Packages/Sources/RxCodeChatKit/BubbleStyle.swift index 7c30eb3b..3289fa47 100644 --- a/Packages/Sources/ClarcChatKit/BubbleStyle.swift +++ b/Packages/Sources/RxCodeChatKit/BubbleStyle.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - Bubble Variant diff --git a/Packages/Sources/ClarcChatKit/ChatBridge.swift b/Packages/Sources/RxCodeChatKit/ChatBridge.swift similarity index 97% rename from Packages/Sources/ClarcChatKit/ChatBridge.swift rename to Packages/Sources/RxCodeChatKit/ChatBridge.swift index f88449df..7bad2239 100644 --- a/Packages/Sources/ClarcChatKit/ChatBridge.swift +++ b/Packages/Sources/RxCodeChatKit/ChatBridge.swift @@ -1,7 +1,7 @@ import Foundation -import ClarcCore +import RxCodeCore -/// Per-window observable bridge between chat views (ClarcChatKit) and the app-layer services. +/// Per-window observable bridge between chat views (RxCodeChatKit) and the app-layer services. /// /// The app target creates one `ChatBridge` per window, sets up action handlers, and keeps the /// streaming state properties updated. Chat views consume this object via the SwiftUI environment. diff --git a/Packages/Sources/ClarcChatKit/ChatShortcut.swift b/Packages/Sources/RxCodeChatKit/ChatShortcut.swift similarity index 97% rename from Packages/Sources/ClarcChatKit/ChatShortcut.swift rename to Packages/Sources/RxCodeChatKit/ChatShortcut.swift index 775a0ea6..3b546e9e 100644 --- a/Packages/Sources/ClarcChatKit/ChatShortcut.swift +++ b/Packages/Sources/RxCodeChatKit/ChatShortcut.swift @@ -1,6 +1,6 @@ import Foundation import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - Notifications @@ -65,7 +65,7 @@ public enum ChatShortcutRegistry { private static var storeURL: URL { let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! return appSupport - .appendingPathComponent("Clarc") + .appendingPathComponent("RxCode") .appendingPathComponent("shortcuts.json") } @@ -125,7 +125,7 @@ public enum ChatShortcutRegistry { private static func exportCommentHeader() -> String { let commentLines = [ - String(localized: "Clarc shortcut export file.", bundle: .module), + String(localized: "RxCode shortcut export file.", bundle: .module), String(localized: "Usage:", bundle: .module), String(localized: "Edit the array below to add, remove, or change shortcuts.", bundle: .module), String(localized: "Each shortcut supports these properties:", bundle: .module), diff --git a/Packages/Sources/ClarcChatKit/ChatView.swift b/Packages/Sources/RxCodeChatKit/ChatView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/ChatView.swift rename to Packages/Sources/RxCodeChatKit/ChatView.swift index 2d6d7be3..711fc6fb 100644 --- a/Packages/Sources/ClarcChatKit/ChatView.swift +++ b/Packages/Sources/RxCodeChatKit/ChatView.swift @@ -1,6 +1,6 @@ import Combine import SwiftUI -import ClarcCore +import RxCodeCore public struct ChatView: View { @Environment(WindowState.self) private var windowState diff --git a/Packages/Sources/ClarcChatKit/FileDiffView.swift b/Packages/Sources/RxCodeChatKit/FileDiffView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/FileDiffView.swift rename to Packages/Sources/RxCodeChatKit/FileDiffView.swift index 7f2aaf51..30bc8e81 100644 --- a/Packages/Sources/ClarcChatKit/FileDiffView.swift +++ b/Packages/Sources/RxCodeChatKit/FileDiffView.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore public struct FileDiffView: View { public let filePath: String @@ -337,7 +337,7 @@ private struct DiffTextRenderer: NSViewRepresentable { private func registerThemeObserver() { guard themeObserver == nil else { return } themeObserver = NotificationCenter.default.addObserver( - forName: .clarcThemeDidChange, + forName: .rxcodeThemeDidChange, object: nil, queue: .main ) { [weak self] _ in diff --git a/Packages/Sources/ClarcChatKit/IMETextView.swift b/Packages/Sources/RxCodeChatKit/IMETextView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/IMETextView.swift rename to Packages/Sources/RxCodeChatKit/IMETextView.swift index 95094785..bb360027 100644 --- a/Packages/Sources/ClarcChatKit/IMETextView.swift +++ b/Packages/Sources/RxCodeChatKit/IMETextView.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore // SwiftUI's TextEditor (PlatformTextView) overrides insertText to enforce binding sync, which // races and discards composing Hangul on commit. Hosting an NSTextView directly lets us own the diff --git a/Packages/Sources/ClarcChatKit/ImagePreviewSheet.swift b/Packages/Sources/RxCodeChatKit/ImagePreviewSheet.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/ImagePreviewSheet.swift rename to Packages/Sources/RxCodeChatKit/ImagePreviewSheet.swift index a1428a63..04a2a7de 100644 --- a/Packages/Sources/ClarcChatKit/ImagePreviewSheet.swift +++ b/Packages/Sources/RxCodeChatKit/ImagePreviewSheet.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore /// Detail preview sheet for image attachments struct ImagePreviewSheet: View { diff --git a/Packages/Sources/ClarcChatKit/InputBarView.swift b/Packages/Sources/RxCodeChatKit/InputBarView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/InputBarView.swift rename to Packages/Sources/RxCodeChatKit/InputBarView.swift index 0fcf6fbb..d62756e6 100644 --- a/Packages/Sources/ClarcChatKit/InputBarView.swift +++ b/Packages/Sources/RxCodeChatKit/InputBarView.swift @@ -1,6 +1,6 @@ import SwiftUI import UniformTypeIdentifiers -import ClarcCore +import RxCodeCore struct InputBarView: View { @Environment(ChatBridge.self) private var chatBridge diff --git a/Packages/Sources/ClarcChatKit/MarkdownView.swift b/Packages/Sources/RxCodeChatKit/MarkdownView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/MarkdownView.swift rename to Packages/Sources/RxCodeChatKit/MarkdownView.swift index 67fa1460..d6738f3f 100644 --- a/Packages/Sources/ClarcChatKit/MarkdownView.swift +++ b/Packages/Sources/RxCodeChatKit/MarkdownView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - Render Group Cache @@ -16,7 +16,7 @@ private final class RenderGroupCache: @unchecked Sendable { init() { cache.countLimit = 200 - NotificationCenter.default.addObserver(forName: .clarcThemeDidChange, object: nil, queue: .main) { [weak self] _ in + NotificationCenter.default.addObserver(forName: .rxcodeThemeDidChange, object: nil, queue: .main) { [weak self] _ in self?.cache.removeAllObjects() } } diff --git a/Packages/Sources/ClarcChatKit/MessageBubble.swift b/Packages/Sources/RxCodeChatKit/MessageBubble.swift similarity index 98% rename from Packages/Sources/ClarcChatKit/MessageBubble.swift rename to Packages/Sources/RxCodeChatKit/MessageBubble.swift index 59be941f..5e59a831 100644 --- a/Packages/Sources/ClarcChatKit/MessageBubble.swift +++ b/Packages/Sources/RxCodeChatKit/MessageBubble.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore struct MessageBubble: View { @Environment(ChatBridge.self) private var chatBridge @@ -232,10 +232,10 @@ struct MessageBubble: View { .lineLimit(isLong && !isLongTextExpanded ? 5 : nil) .fixedSize(horizontal: false, vertical: true) .environment(\.openURL, OpenURLAction { url in - // Intercept the synthetic `clarc-image://` link + // Intercept the synthetic `rxcode-image://` link // emitted by chipifiedAttributedString — open the matching // image in the preview sheet rather than the system browser. - guard url.scheme == "clarc-image", + guard url.scheme == "rxcode-image", let index = Int(url.host ?? ""), let path = imagePath(forChipIndex: index) else { return .systemAction @@ -533,7 +533,7 @@ struct MessageBubble: View { /// with a rounded background there via `ChipLayoutManager`; this mirrors that /// treatment in the sent user bubble. /// - /// Each chip also gets a `clarc-image://` link attribute; an + /// Each chip also gets a `rxcode-image://` link attribute; an /// `environment(\.openURL, ...)` handler on the Text intercepts the tap and /// opens the corresponding image in `MessageImagePreviewSheet`. The index /// matches `WindowState.imageIndex(for:)` — 1-based, image-only. @@ -550,7 +550,7 @@ struct MessageBubble: View { attr[range].backgroundColor = ClaudeTheme.accent.opacity(0.22) attr[range].foregroundColor = ClaudeTheme.accent attr[range].font = .system(size: ClaudeTheme.messageSize(13), weight: .medium) - attr[range].link = URL(string: "clarc-image://\(index)") + attr[range].link = URL(string: "rxcode-image://\(index)") attr[range].underlineStyle = nil } return attr diff --git a/Packages/Sources/ClarcChatKit/MessageListView.swift b/Packages/Sources/RxCodeChatKit/MessageListView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/MessageListView.swift rename to Packages/Sources/RxCodeChatKit/MessageListView.swift index f8a5ba04..a529634d 100644 --- a/Packages/Sources/ClarcChatKit/MessageListView.swift +++ b/Packages/Sources/RxCodeChatKit/MessageListView.swift @@ -1,6 +1,6 @@ import SwiftUI import Combine -import ClarcCore +import RxCodeCore import os /// Message scroll area — extracted from ChatView to isolate @Observable dependencies on `messages`. diff --git a/Packages/Sources/ClarcChatKit/PlanCardView.swift b/Packages/Sources/RxCodeChatKit/PlanCardView.swift similarity index 97% rename from Packages/Sources/ClarcChatKit/PlanCardView.swift rename to Packages/Sources/RxCodeChatKit/PlanCardView.swift index ebe25252..f46a8230 100644 --- a/Packages/Sources/ClarcChatKit/PlanCardView.swift +++ b/Packages/Sources/RxCodeChatKit/PlanCardView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore import AppKit /// Interactive UI for a Claude `ExitPlanMode` tool call, plus a fallback for plan @@ -34,12 +34,28 @@ struct PlanCardView: View { return userDecisionPrefixes.contains { result.hasPrefix($0) } } - @State private var isExpanded: Bool = true + @State private var isExpanded: Bool @State private var showFullSheet: Bool = false @State private var feedbackText: String = "" @State private var isComposingFeedback: Bool = false @State private var isResolving: Bool = false + init( + toolCall: ToolCall, + planMarkdown: String, + isMessageStreaming: Bool, + externalPlanMarkdown: String? = nil + ) { + self.toolCall = toolCall + self.planMarkdown = planMarkdown + self.isMessageStreaming = isMessageStreaming + self.externalPlanMarkdown = externalPlanMarkdown + // Start collapsed when the plan is already decided so reloaded history + // doesn't re-display the full plan body. The .onChange below handles + // collapsing for live decisions. + self._isExpanded = State(initialValue: !Self.isPlanDecided(toolCall)) + } + /// Extract the plan markdown for a tool call, or nil if this isn't a plan-bearing call. /// Routed from `MessageBubble` — when nil, the generic `ToolResultView` is used instead. static func planMarkdown(from toolCall: ToolCall) -> String? { diff --git a/Packages/Sources/ClarcChatKit/Resources/en.lproj/Localizable.strings b/Packages/Sources/RxCodeChatKit/Resources/en.lproj/Localizable.strings similarity index 91% rename from Packages/Sources/ClarcChatKit/Resources/en.lproj/Localizable.strings rename to Packages/Sources/RxCodeChatKit/Resources/en.lproj/Localizable.strings index ca19dcef..9efbd569 100644 --- a/Packages/Sources/ClarcChatKit/Resources/en.lproj/Localizable.strings +++ b/Packages/Sources/RxCodeChatKit/Resources/en.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* English — base localization for ClarcChatKit */ +/* English — base localization for RxCodeChatKit */ // MARK: - InputBarView "Attach file" = "Attach file"; @@ -15,10 +15,10 @@ "A command with this name already exists." = "A command with this name already exists."; "Restore modified default commands to their original state" = "Restore modified default commands to their original state"; "All modified default commands will be restored to their original state." = "All modified default commands will be restored to their original state."; -"Clarc slash command export file." = "Clarc slash command export file."; +"RxCode slash command export file." = "RxCode slash command export file."; "Usage:" = "Usage:"; "Edit the array below to add, remove, or change custom slash commands." = "Edit the array below to add, remove, or change custom slash commands."; -"The leading slash is optional while editing names; Clarc saves names without it." = "The leading slash is optional while editing names; Clarc saves names without it."; +"The leading slash is optional while editing names; RxCode saves names without it." = "The leading slash is optional while editing names; RxCode saves names without it."; "Built-in command names are ignored on import and are never exported." = "Built-in command names are ignored on import and are never exported."; "Each custom command supports these properties:" = "Each custom command supports these properties:"; "name: command name without the leading slash." = "name: command name without the leading slash."; @@ -29,7 +29,7 @@ "All properties example:" = "All properties example:"; "Short description shown in the command picker" = "Short description shown in the command picker"; "Optional longer description shown in command details" = "Optional longer description shown in command details"; -"Clarc shortcut export file." = "Clarc shortcut export file."; +"RxCode shortcut export file." = "RxCode shortcut export file."; "Edit the array below to add, remove, or change shortcuts." = "Edit the array below to add, remove, or change shortcuts."; "Each shortcut supports these properties:" = "Each shortcut supports these properties:"; "id: optional unique identifier (UUID). Auto-generated on import if omitted." = "id: optional unique identifier (UUID). Auto-generated on import if omitted."; diff --git a/Packages/Sources/ClarcChatKit/Resources/ko.lproj/Localizable.strings b/Packages/Sources/RxCodeChatKit/Resources/ko.lproj/Localizable.strings similarity index 97% rename from Packages/Sources/ClarcChatKit/Resources/ko.lproj/Localizable.strings rename to Packages/Sources/RxCodeChatKit/Resources/ko.lproj/Localizable.strings index 4ab11465..e996c0db 100644 --- a/Packages/Sources/ClarcChatKit/Resources/ko.lproj/Localizable.strings +++ b/Packages/Sources/RxCodeChatKit/Resources/ko.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Korean localization — ClarcChatKit */ +/* Korean localization — RxCodeChatKit */ // MARK: - InputBarView "Attach file" = "파일 첨부"; @@ -58,10 +58,10 @@ "Invalid JSON format." = "유효하지 않은 JSON 형식입니다."; "Imported %lld custom commands." = "%lld개 커스텀 커맨드를 가져왔습니다."; "A command with this name already exists." = "이미 존재하는 커맨드 이름입니다."; -"Clarc slash command export file." = "Clarc 슬래시 커맨드 내보내기 파일입니다."; +"RxCode slash command export file." = "RxCode 슬래시 커맨드 내보내기 파일입니다."; "Usage:" = "사용법:"; "Edit the array below to add, remove, or change custom slash commands." = "아래 배열을 편집해서 커스텀 슬래시 커맨드를 추가, 삭제, 변경하세요."; -"The leading slash is optional while editing names; Clarc saves names without it." = "이름을 편집할 때 앞의 /는 선택사항입니다. Clarc는 / 없이 저장합니다."; +"The leading slash is optional while editing names; RxCode saves names without it." = "이름을 편집할 때 앞의 /는 선택사항입니다. RxCode는 / 없이 저장합니다."; "Built-in command names are ignored on import and are never exported." = "기본 명령어 이름은 가져올 때 무시되며 내보내기에 포함되지 않습니다."; "Each custom command supports these properties:" = "각 커스텀 커맨드는 다음 속성을 지원합니다:"; "name: command name without the leading slash." = "name: 앞의 /를 제외한 커맨드 이름입니다."; @@ -72,7 +72,7 @@ "All properties example:" = "모든 속성 예시:"; "Short description shown in the command picker" = "커맨드 선택기에 표시되는 짧은 설명"; "Optional longer description shown in command details" = "커맨드 상세에 표시되는 선택적 긴 설명"; -"Clarc shortcut export file." = "Clarc 단축키 내보내기 파일입니다."; +"RxCode shortcut export file." = "RxCode 단축키 내보내기 파일입니다."; "Edit the array below to add, remove, or change shortcuts." = "아래 배열을 편집해서 단축키를 추가, 삭제, 변경하세요."; "Each shortcut supports these properties:" = "각 단축키는 다음 속성을 지원합니다:"; "id: optional unique identifier (UUID). Auto-generated on import if omitted." = "id: 선택적 고유 식별자 (UUID). 가져올 때 생략하면 자동 생성됩니다."; diff --git a/Packages/Sources/ClarcChatKit/ShortcutManagerView.swift b/Packages/Sources/RxCodeChatKit/ShortcutManagerView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/ShortcutManagerView.swift rename to Packages/Sources/RxCodeChatKit/ShortcutManagerView.swift index 5ff6141f..5d210772 100644 --- a/Packages/Sources/ClarcChatKit/ShortcutManagerView.swift +++ b/Packages/Sources/RxCodeChatKit/ShortcutManagerView.swift @@ -1,6 +1,6 @@ import SwiftUI import UniformTypeIdentifiers -import ClarcCore +import RxCodeCore // MARK: - Shortcut Manager View diff --git a/Packages/Sources/ClarcChatKit/SlashCommandBar.swift b/Packages/Sources/RxCodeChatKit/SlashCommandBar.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/SlashCommandBar.swift rename to Packages/Sources/RxCodeChatKit/SlashCommandBar.swift index eba31eeb..9a07db32 100644 --- a/Packages/Sources/ClarcChatKit/SlashCommandBar.swift +++ b/Packages/Sources/RxCodeChatKit/SlashCommandBar.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - Slash Command Data @@ -231,7 +231,7 @@ public enum SlashCommandRegistry { private static var storeURL: URL { let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! return appSupport - .appendingPathComponent("Clarc") + .appendingPathComponent("RxCode") .appendingPathComponent("custom_commands.json") } @@ -383,10 +383,10 @@ public enum SlashCommandRegistry { private static func exportCommentHeader() -> String { let commentLines = [ - String(localized: "Clarc slash command export file.", bundle: .module), + String(localized: "RxCode slash command export file.", bundle: .module), String(localized: "Usage:", bundle: .module), String(localized: "Edit the array below to add, remove, or change custom slash commands.", bundle: .module), - String(localized: "The leading slash is optional while editing names; Clarc saves names without it.", bundle: .module), + String(localized: "The leading slash is optional while editing names; RxCode saves names without it.", bundle: .module), String(localized: "Built-in command names are ignored on import and are never exported.", bundle: .module), String(localized: "Each custom command supports these properties:", bundle: .module), String(localized: "name: command name without the leading slash.", bundle: .module), diff --git a/Packages/Sources/ClarcChatKit/SlashCommandManagerView.swift b/Packages/Sources/RxCodeChatKit/SlashCommandManagerView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/SlashCommandManagerView.swift rename to Packages/Sources/RxCodeChatKit/SlashCommandManagerView.swift index 6f84699f..1dd8031d 100644 --- a/Packages/Sources/ClarcChatKit/SlashCommandManagerView.swift +++ b/Packages/Sources/RxCodeChatKit/SlashCommandManagerView.swift @@ -1,6 +1,6 @@ import SwiftUI import UniformTypeIdentifiers -import ClarcCore +import RxCodeCore // MARK: - Slash Command Manager View diff --git a/Packages/Sources/ClarcChatKit/StatusLineView.swift b/Packages/Sources/RxCodeChatKit/StatusLineView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/StatusLineView.swift rename to Packages/Sources/RxCodeChatKit/StatusLineView.swift index 0bbc6889..0faab4a7 100644 --- a/Packages/Sources/ClarcChatKit/StatusLineView.swift +++ b/Packages/Sources/RxCodeChatKit/StatusLineView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct StatusLineView: View { @Environment(ChatBridge.self) private var chatBridge diff --git a/Packages/Sources/ClarcChatKit/TextPreviewSheet.swift b/Packages/Sources/RxCodeChatKit/TextPreviewSheet.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/TextPreviewSheet.swift rename to Packages/Sources/RxCodeChatKit/TextPreviewSheet.swift index 8449babb..12fd15af 100644 --- a/Packages/Sources/ClarcChatKit/TextPreviewSheet.swift +++ b/Packages/Sources/RxCodeChatKit/TextPreviewSheet.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Detail preview sheet for text attachments struct TextPreviewSheet: View { diff --git a/Packages/Sources/ClarcChatKit/TodoProgressView.swift b/Packages/Sources/RxCodeChatKit/TodoProgressView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/TodoProgressView.swift rename to Packages/Sources/RxCodeChatKit/TodoProgressView.swift index 5cec52bf..5db799f5 100644 --- a/Packages/Sources/ClarcChatKit/TodoProgressView.swift +++ b/Packages/Sources/RxCodeChatKit/TodoProgressView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - Todo Progress Pill diff --git a/Packages/Sources/ClarcChatKit/ToolResultView.swift b/Packages/Sources/RxCodeChatKit/ToolResultView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/ToolResultView.swift rename to Packages/Sources/RxCodeChatKit/ToolResultView.swift index f5259400..2f2d7515 100644 --- a/Packages/Sources/ClarcChatKit/ToolResultView.swift +++ b/Packages/Sources/RxCodeChatKit/ToolResultView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct ToolResultView: View { let toolCall: ToolCall diff --git a/Packages/Sources/ClarcChatKit/TypingDotsView.swift b/Packages/Sources/RxCodeChatKit/TypingDotsView.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/TypingDotsView.swift rename to Packages/Sources/RxCodeChatKit/TypingDotsView.swift index 07bb93f1..008831b9 100644 --- a/Packages/Sources/ClarcChatKit/TypingDotsView.swift +++ b/Packages/Sources/RxCodeChatKit/TypingDotsView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Animated pulse-ring indicator with gradient rings that ripple outward. struct PulseRingView: View { diff --git a/Packages/Sources/ClarcChatKit/WebPreviewButton.swift b/Packages/Sources/RxCodeChatKit/WebPreviewButton.swift similarity index 99% rename from Packages/Sources/ClarcChatKit/WebPreviewButton.swift rename to Packages/Sources/RxCodeChatKit/WebPreviewButton.swift index 35a88ad4..735b8755 100644 --- a/Packages/Sources/ClarcChatKit/WebPreviewButton.swift +++ b/Packages/Sources/RxCodeChatKit/WebPreviewButton.swift @@ -1,6 +1,6 @@ import SwiftUI import WebKit -import ClarcCore +import RxCodeCore /// Detects localhost URLs and shows a "View Result" button that, /// when clicked, provides an in-app web view preview. diff --git a/Packages/Sources/ClarcCore/CLISession/CLILineToBlocksMapper.swift b/Packages/Sources/RxCodeCore/CLISession/CLILineToBlocksMapper.swift similarity index 97% rename from Packages/Sources/ClarcCore/CLISession/CLILineToBlocksMapper.swift rename to Packages/Sources/RxCodeCore/CLISession/CLILineToBlocksMapper.swift index 49b874e2..677988a4 100644 --- a/Packages/Sources/ClarcCore/CLISession/CLILineToBlocksMapper.swift +++ b/Packages/Sources/RxCodeCore/CLISession/CLILineToBlocksMapper.swift @@ -1,6 +1,6 @@ import Foundation -/// Converts a stream of decoded jsonl lines into the `[ChatMessage]` shape Clarc +/// Converts a stream of decoded jsonl lines into the `[ChatMessage]` shape RxCode /// renders. Sidechain (subagent) lines and meta caveats are skipped; tool_result /// user-lines fold back into the matching assistant `ToolCall.result`. public enum CLILineToBlocksMapper { @@ -127,7 +127,7 @@ public enum CLILineToBlocksMapper { guard !blocks.isEmpty else { return } // If the previous assistant turn ended with an unresolved tool_use that - // we kept, keep them as separate ChatMessages — Clarc renders one bubble + // we kept, keep them as separate ChatMessages — RxCode renders one bubble // per ChatMessage and the live-stream code does the same. messages.append(ChatMessage( role: .assistant, diff --git a/Packages/Sources/ClarcCore/CLISession/CLISessionRecord.swift b/Packages/Sources/RxCodeCore/CLISession/CLISessionRecord.swift similarity index 100% rename from Packages/Sources/ClarcCore/CLISession/CLISessionRecord.swift rename to Packages/Sources/RxCodeCore/CLISession/CLISessionRecord.swift diff --git a/Packages/Sources/ClarcCore/CLISession/CLISessionStore.swift b/Packages/Sources/RxCodeCore/CLISession/CLISessionStore.swift similarity index 99% rename from Packages/Sources/ClarcCore/CLISession/CLISessionStore.swift rename to Packages/Sources/RxCodeCore/CLISession/CLISessionStore.swift index 4eb97ac4..ecb0f11e 100644 --- a/Packages/Sources/ClarcCore/CLISession/CLISessionStore.swift +++ b/Packages/Sources/RxCodeCore/CLISession/CLISessionStore.swift @@ -59,7 +59,7 @@ public actor CLISessionStore { // MARK: - Discovery - /// Resolve the on-disk projects directory for a Clarc-known cwd. Prefers + /// Resolve the on-disk projects directory for a RxCode-known cwd. Prefers /// the cwd-index (built by reading jsonl content, so it sees through the /// lossy slash/dot encoding) and falls back to forward encoding when no /// index entry exists yet. @@ -296,7 +296,7 @@ public actor CLISessionStore { } /// Read the last ~16KB of a jsonl and return the largest `timestamp` field - /// found. Survives Clarc-side rewrites (PickerExposer, LegacyMigrator) that + /// found. Survives RxCode-side rewrites (PickerExposer, LegacyMigrator) that /// would otherwise bump file mtime to "now". Returns nil for files whose /// tail contains no timestamped lines (e.g. only metadata) — caller falls /// back to mtime. @@ -343,7 +343,7 @@ public actor CLISessionStore { /// Stream-parse the full jsonl into a `ChatSession`. Memory footprint is /// roughly proportional to rendered content (thinking blocks dropped at /// decode time). `projectId` is supplied by the caller because the session - /// jsonl itself doesn't carry the Clarc project UUID. + /// jsonl itself doesn't carry the RxCode project UUID. public func loadFullSession( sid: String, cwd: String, diff --git a/Packages/Sources/ClarcCore/CLISession/LegacyMigrator.swift b/Packages/Sources/RxCodeCore/CLISession/LegacyMigrator.swift similarity index 98% rename from Packages/Sources/ClarcCore/CLISession/LegacyMigrator.swift rename to Packages/Sources/RxCodeCore/CLISession/LegacyMigrator.swift index 3bce0e9d..f4f9b33b 100644 --- a/Packages/Sources/ClarcCore/CLISession/LegacyMigrator.swift +++ b/Packages/Sources/RxCodeCore/CLISession/LegacyMigrator.swift @@ -1,6 +1,6 @@ import Foundation -/// Converts legacy Clarc-owned ChatSession JSON files into CLI-compatible JSONL +/// Converts legacy RxCode-owned ChatSession JSON files into CLI-compatible JSONL /// so Claude's `--resume` can pick them up from ~/.claude/projects/{enc(cwd)}/. public enum LegacyMigrator { diff --git a/Packages/Sources/ClarcCore/CLISession/PickerExposer.swift b/Packages/Sources/RxCodeCore/CLISession/PickerExposer.swift similarity index 94% rename from Packages/Sources/ClarcCore/CLISession/PickerExposer.swift rename to Packages/Sources/RxCodeCore/CLISession/PickerExposer.swift index 4e25bdc7..cd9d3454 100644 --- a/Packages/Sources/ClarcCore/CLISession/PickerExposer.swift +++ b/Packages/Sources/RxCodeCore/CLISession/PickerExposer.swift @@ -1,13 +1,13 @@ import Foundation import os -/// Rewrites Claude Code session jsonl files so that Clarc-spawned sessions +/// Rewrites Claude Code session jsonl files so that RxCode-spawned sessions /// appear in the interactive `claude --resume` picker. /// /// The CLI tags every line with `"entrypoint":"sdk-cli"` and prepends /// `type: queue-operation` envelope lines whenever it is launched in print -/// mode (`-p`), which is how Clarc spawns it. The picker filters those -/// sessions out, so without this normalization Clarc's history is invisible +/// mode (`-p`), which is how RxCode spawns it. The picker filters those +/// sessions out, so without this normalization RxCode's history is invisible /// to anyone running `claude --resume` outside the app. Rewriting the file /// to look like a regular interactive session lets the picker pick it up. /// diff --git a/Packages/Sources/ClarcCore/CLISession/SessionMetaStore.swift b/Packages/Sources/RxCodeCore/CLISession/SessionMetaStore.swift similarity index 96% rename from Packages/Sources/ClarcCore/CLISession/SessionMetaStore.swift rename to Packages/Sources/RxCodeCore/CLISession/SessionMetaStore.swift index 65d74e18..32850628 100644 --- a/Packages/Sources/ClarcCore/CLISession/SessionMetaStore.swift +++ b/Packages/Sources/RxCodeCore/CLISession/SessionMetaStore.swift @@ -1,9 +1,9 @@ import Foundation import os -/// Sidecar persistence for the Clarc-only fields that don't live in the CLI's +/// Sidecar persistence for the RxCode-only fields that don't live in the CLI's /// jsonl: title, isPinned, model, effort, permissionMode. One file per session -/// id at `~/Library/Application Support/Clarc/session-meta/{sid}.json`. +/// id at `~/Library/Application Support/RxCode/session-meta/{sid}.json`. public actor SessionMetaStore { public struct Meta: Codable, Sendable { @@ -56,7 +56,7 @@ public actor SessionMetaStore { private let baseURL: URL private let logger = Logger(subsystem: "com.claudework", category: "SessionMetaStore") - /// In-memory cache. The sidecar directory is owned exclusively by Clarc + /// In-memory cache. The sidecar directory is owned exclusively by RxCode /// (CLI doesn't touch it), so the cache is authoritative once populated. private var cache: [String: Meta] = [:] diff --git a/Packages/Sources/ClarcCore/Models/AskUserQuestion.swift b/Packages/Sources/RxCodeCore/Models/AskUserQuestion.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/AskUserQuestion.swift rename to Packages/Sources/RxCodeCore/Models/AskUserQuestion.swift diff --git a/Packages/Sources/ClarcCore/Models/Attachment.swift b/Packages/Sources/RxCodeCore/Models/Attachment.swift similarity index 98% rename from Packages/Sources/ClarcCore/Models/Attachment.swift rename to Packages/Sources/RxCodeCore/Models/Attachment.swift index fbfb0d4a..546fbfe6 100644 --- a/Packages/Sources/ClarcCore/Models/Attachment.swift +++ b/Packages/Sources/RxCodeCore/Models/Attachment.swift @@ -151,7 +151,7 @@ public enum AttachmentFactory { guard attachment.type == .image, attachment.path.isEmpty, let data = attachment.imageData else { return attachment } let url = attachmentsDirectory - .appendingPathComponent("clarc-img-\(UUID().uuidString.prefix(8)).png") + .appendingPathComponent("rxcode-img-\(UUID().uuidString.prefix(8)).png") guard (try? data.write(to: url)) != nil else { return attachment } return Attachment(id: attachment.id, type: .image, name: attachment.name, path: url.path, fileSize: Int64(data.count), imageData: data) diff --git a/Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift b/Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift rename to Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift diff --git a/Packages/Sources/ClarcCore/Models/ChatMessage.swift b/Packages/Sources/RxCodeCore/Models/ChatMessage.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/ChatMessage.swift rename to Packages/Sources/RxCodeCore/Models/ChatMessage.swift diff --git a/Packages/Sources/ClarcCore/Models/ChatSession.swift b/Packages/Sources/RxCodeCore/Models/ChatSession.swift similarity index 99% rename from Packages/Sources/ClarcCore/Models/ChatSession.swift rename to Packages/Sources/RxCodeCore/Models/ChatSession.swift index eb163040..0986b3dd 100644 --- a/Packages/Sources/ClarcCore/Models/ChatSession.swift +++ b/Packages/Sources/RxCodeCore/Models/ChatSession.swift @@ -69,7 +69,7 @@ public struct ChatSession: Identifiable, Codable, Sendable { model = try container.decodeIfPresent(String.self, forKey: .model) effort = try container.decodeIfPresent(String.self, forKey: .effort) permissionMode = try container.decodeIfPresent(PermissionMode.self, forKey: .permissionMode) - origin = try container.decodeIfPresent(SessionOrigin.self, forKey: .origin) ?? .legacyClarc + origin = try container.decodeIfPresent(SessionOrigin.self, forKey: .origin) ?? .legacyRxCode worktreePath = try container.decodeIfPresent(String.self, forKey: .worktreePath) worktreeBranch = try container.decodeIfPresent(String.self, forKey: .worktreeBranch) isArchived = try container.decodeIfPresent(Bool.self, forKey: .isArchived) ?? false @@ -139,7 +139,7 @@ public struct ChatSession: Identifiable, Codable, Sendable { model = try container.decodeIfPresent(String.self, forKey: .model) effort = try container.decodeIfPresent(String.self, forKey: .effort) permissionMode = try container.decodeIfPresent(PermissionMode.self, forKey: .permissionMode) - origin = try container.decodeIfPresent(SessionOrigin.self, forKey: .origin) ?? .legacyClarc + origin = try container.decodeIfPresent(SessionOrigin.self, forKey: .origin) ?? .legacyRxCode worktreePath = try container.decodeIfPresent(String.self, forKey: .worktreePath) worktreeBranch = try container.decodeIfPresent(String.self, forKey: .worktreeBranch) isArchived = try container.decodeIfPresent(Bool.self, forKey: .isArchived) ?? false diff --git a/Packages/Sources/ClarcCore/Models/ChatSessionStats.swift b/Packages/Sources/RxCodeCore/Models/ChatSessionStats.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/ChatSessionStats.swift rename to Packages/Sources/RxCodeCore/Models/ChatSessionStats.swift diff --git a/Packages/Sources/ClarcCore/Models/ChatThread.swift b/Packages/Sources/RxCodeCore/Models/ChatThread.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/ChatThread.swift rename to Packages/Sources/RxCodeCore/Models/ChatThread.swift diff --git a/Packages/Sources/ClarcCore/Models/GitHubModels.swift b/Packages/Sources/RxCodeCore/Models/GitHubModels.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/GitHubModels.swift rename to Packages/Sources/RxCodeCore/Models/GitHubModels.swift diff --git a/Packages/Sources/ClarcCore/Models/InteractiveTerminalState.swift b/Packages/Sources/RxCodeCore/Models/InteractiveTerminalState.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/InteractiveTerminalState.swift rename to Packages/Sources/RxCodeCore/Models/InteractiveTerminalState.swift diff --git a/Packages/Sources/ClarcCore/Models/JSONValue.swift b/Packages/Sources/RxCodeCore/Models/JSONValue.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/JSONValue.swift rename to Packages/Sources/RxCodeCore/Models/JSONValue.swift diff --git a/Packages/Sources/ClarcCore/Models/MCPServer.swift b/Packages/Sources/RxCodeCore/Models/MCPServer.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/MCPServer.swift rename to Packages/Sources/RxCodeCore/Models/MCPServer.swift diff --git a/Packages/Sources/ClarcCore/Models/MarketplacePlugin.swift b/Packages/Sources/RxCodeCore/Models/MarketplacePlugin.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/MarketplacePlugin.swift rename to Packages/Sources/RxCodeCore/Models/MarketplacePlugin.swift diff --git a/Packages/Sources/ClarcCore/Models/PermissionMode.swift b/Packages/Sources/RxCodeCore/Models/PermissionMode.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/PermissionMode.swift rename to Packages/Sources/RxCodeCore/Models/PermissionMode.swift diff --git a/Packages/Sources/ClarcCore/Models/PermissionRequest.swift b/Packages/Sources/RxCodeCore/Models/PermissionRequest.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/PermissionRequest.swift rename to Packages/Sources/RxCodeCore/Models/PermissionRequest.swift diff --git a/Packages/Sources/ClarcCore/Models/PreviewFile.swift b/Packages/Sources/RxCodeCore/Models/PreviewFile.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/PreviewFile.swift rename to Packages/Sources/RxCodeCore/Models/PreviewFile.swift diff --git a/Packages/Sources/ClarcCore/Models/Project.swift b/Packages/Sources/RxCodeCore/Models/Project.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/Project.swift rename to Packages/Sources/RxCodeCore/Models/Project.swift diff --git a/Packages/Sources/ClarcCore/Models/QueuedMessageRecord.swift b/Packages/Sources/RxCodeCore/Models/QueuedMessageRecord.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/QueuedMessageRecord.swift rename to Packages/Sources/RxCodeCore/Models/QueuedMessageRecord.swift diff --git a/Packages/Sources/ClarcCore/Models/RateLimitUsage.swift b/Packages/Sources/RxCodeCore/Models/RateLimitUsage.swift similarity index 92% rename from Packages/Sources/ClarcCore/Models/RateLimitUsage.swift rename to Packages/Sources/RxCodeCore/Models/RateLimitUsage.swift index c7735a21..ff093da9 100644 --- a/Packages/Sources/ClarcCore/Models/RateLimitUsage.swift +++ b/Packages/Sources/RxCodeCore/Models/RateLimitUsage.swift @@ -1,6 +1,6 @@ import Foundation -/// Rate limit usage data passed through ChatBridge to avoid direct RateLimitService dependency in ClarcChatKit. +/// Rate limit usage data passed through ChatBridge to avoid direct RateLimitService dependency in RxCodeChatKit. public struct RateLimitUsage: Sendable { public let fiveHourPercent: Double public let sevenDayPercent: Double diff --git a/Packages/Sources/ClarcCore/Models/SessionOrigin.swift b/Packages/Sources/RxCodeCore/Models/SessionOrigin.swift similarity index 60% rename from Packages/Sources/ClarcCore/Models/SessionOrigin.swift rename to Packages/Sources/RxCodeCore/Models/SessionOrigin.swift index 1b10980d..8c54a683 100644 --- a/Packages/Sources/ClarcCore/Models/SessionOrigin.swift +++ b/Packages/Sources/RxCodeCore/Models/SessionOrigin.swift @@ -2,11 +2,11 @@ import Foundation /// Where the session's message content lives on disk. public enum SessionOrigin: String, Codable, Sendable { - /// Legacy Clarc-owned JSON at `~/Library/Application Support/Clarc/sessions/{projectId}/{sid}.json`. + /// Legacy RxCode-owned JSON at `~/Library/Application Support/RxCode/sessions/{projectId}/{sid}.json`. /// Read-only going forward; will not appear in the CLI's `~/.claude/projects/...` directory. - case legacyClarc + case legacyRxCode /// Backed by Claude Code CLI's `~/.claude/projects/{enc(cwd)}/{sid}.jsonl`. - /// Source of truth is the CLI; Clarc keeps Clarc-only metadata in a sidecar. + /// Source of truth is the CLI; RxCode keeps RxCode-only metadata in a sidecar. case cliBacked } diff --git a/Packages/Sources/ClarcCore/Models/SessionRowReconciler.swift b/Packages/Sources/RxCodeCore/Models/SessionRowReconciler.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/SessionRowReconciler.swift rename to Packages/Sources/RxCodeCore/Models/SessionRowReconciler.swift diff --git a/Packages/Sources/ClarcCore/Models/StreamEvent.swift b/Packages/Sources/RxCodeCore/Models/StreamEvent.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/StreamEvent.swift rename to Packages/Sources/RxCodeCore/Models/StreamEvent.swift diff --git a/Packages/Sources/ClarcCore/Models/StreamEventDecoding.swift b/Packages/Sources/RxCodeCore/Models/StreamEventDecoding.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/StreamEventDecoding.swift rename to Packages/Sources/RxCodeCore/Models/StreamEventDecoding.swift diff --git a/Packages/Sources/ClarcCore/Models/ThreadFileEdit.swift b/Packages/Sources/RxCodeCore/Models/ThreadFileEdit.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/ThreadFileEdit.swift rename to Packages/Sources/RxCodeCore/Models/ThreadFileEdit.swift diff --git a/Packages/Sources/ClarcCore/Models/TodoItem.swift b/Packages/Sources/RxCodeCore/Models/TodoItem.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/TodoItem.swift rename to Packages/Sources/RxCodeCore/Models/TodoItem.swift diff --git a/Packages/Sources/ClarcCore/Models/TodoSnapshot.swift b/Packages/Sources/RxCodeCore/Models/TodoSnapshot.swift similarity index 100% rename from Packages/Sources/ClarcCore/Models/TodoSnapshot.swift rename to Packages/Sources/RxCodeCore/Models/TodoSnapshot.swift diff --git a/Packages/Sources/ClarcCore/Theme/AppTheme.swift b/Packages/Sources/RxCodeCore/Theme/AppTheme.swift similarity index 99% rename from Packages/Sources/ClarcCore/Theme/AppTheme.swift rename to Packages/Sources/RxCodeCore/Theme/AppTheme.swift index 1b35a3c2..befca8e0 100644 --- a/Packages/Sources/ClarcCore/Theme/AppTheme.swift +++ b/Packages/Sources/RxCodeCore/Theme/AppTheme.swift @@ -312,7 +312,7 @@ public enum AppTheme: String, CaseIterable, Identifiable { // MARK: - Theme Store public extension Notification.Name { - static let clarcThemeDidChange = Notification.Name("com.clarc.themeDidChange") + static let rxcodeThemeDidChange = Notification.Name("com.rxcode.themeDidChange") } @MainActor @@ -323,7 +323,7 @@ public final class ThemeStore { public var current: AppTheme = .claude { didSet { colors = current.colors - NotificationCenter.default.post(name: .clarcThemeDidChange, object: nil) + NotificationCenter.default.post(name: .rxcodeThemeDidChange, object: nil) } } public var colors: ThemeColors = .claude diff --git a/Packages/Sources/ClarcCore/Theme/ClaudeTheme.swift b/Packages/Sources/RxCodeCore/Theme/ClaudeTheme.swift similarity index 100% rename from Packages/Sources/ClarcCore/Theme/ClaudeTheme.swift rename to Packages/Sources/RxCodeCore/Theme/ClaudeTheme.swift diff --git a/Packages/Sources/RxCodeCore/Utilities/AppSupport.swift b/Packages/Sources/RxCodeCore/Utilities/AppSupport.swift new file mode 100644 index 00000000..ec66a95e --- /dev/null +++ b/Packages/Sources/RxCodeCore/Utilities/AppSupport.swift @@ -0,0 +1,41 @@ +import Foundation + +/// Application Support directory scoped by the running bundle identifier. +/// Production (`com.idealapp.RxCode`) keeps the `RxCode` directory. +/// Any other bundle id (e.g. a dev build at `com.idealapp.RxCode.dev`) maps to +/// `RxCode.` so alternate builds never share state with production. +public enum AppSupport { + public static let bundleScopedURL: URL = { + let root = FileManager.default + .urls(for: .applicationSupportDirectory, in: .userDomainMask) + .first! + let target = root.appendingPathComponent(directoryName, isDirectory: true) + migrateLegacyClarcDirectoryIfNeeded(root: root, target: target) + return target + }() + + private static var directoryName: String { + let bundleID = Bundle.main.bundleIdentifier ?? "com.idealapp.RxCode" + if bundleID == "com.idealapp.RxCode" { return "RxCode" } + let suffix = bundleID.split(separator: ".").last.map(String.init) ?? "dev" + return "RxCode.\(suffix)" + } + + private static var legacyDirectoryName: String { + let bundleID = Bundle.main.bundleIdentifier ?? "com.idealapp.RxCode" + if bundleID == "com.idealapp.RxCode" { return "Clarc" } + let suffix = bundleID.split(separator: ".").last.map(String.init) ?? "dev" + return "Clarc.\(suffix)" + } + + // One-shot migration: rename the pre-rebrand "Clarc" Application Support + // directory to "RxCode" so existing users keep their projects, sessions, + // and settings after upgrading. + private static func migrateLegacyClarcDirectoryIfNeeded(root: URL, target: URL) { + let fm = FileManager.default + guard !fm.fileExists(atPath: target.path) else { return } + let legacy = root.appendingPathComponent(legacyDirectoryName, isDirectory: true) + guard fm.fileExists(atPath: legacy.path) else { return } + try? fm.moveItem(at: legacy, to: target) + } +} diff --git a/Packages/Sources/ClarcCore/Utilities/ClipboardHelper.swift b/Packages/Sources/RxCodeCore/Utilities/ClipboardHelper.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/ClipboardHelper.swift rename to Packages/Sources/RxCodeCore/Utilities/ClipboardHelper.swift diff --git a/Packages/Sources/ClarcCore/Utilities/DirectoryWatcher.swift b/Packages/Sources/RxCodeCore/Utilities/DirectoryWatcher.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/DirectoryWatcher.swift rename to Packages/Sources/RxCodeCore/Utilities/DirectoryWatcher.swift diff --git a/Packages/Sources/ClarcCore/Utilities/DurationFormatting.swift b/Packages/Sources/RxCodeCore/Utilities/DurationFormatting.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/DurationFormatting.swift rename to Packages/Sources/RxCodeCore/Utilities/DurationFormatting.swift diff --git a/Packages/Sources/ClarcCore/Utilities/GitHelper.swift b/Packages/Sources/RxCodeCore/Utilities/GitHelper.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/GitHelper.swift rename to Packages/Sources/RxCodeCore/Utilities/GitHelper.swift diff --git a/Packages/Sources/ClarcCore/Utilities/GitStatusParsing.swift b/Packages/Sources/RxCodeCore/Utilities/GitStatusParsing.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/GitStatusParsing.swift rename to Packages/Sources/RxCodeCore/Utilities/GitStatusParsing.swift diff --git a/Packages/Sources/ClarcCore/Utilities/GitURLHelpers.swift b/Packages/Sources/RxCodeCore/Utilities/GitURLHelpers.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/GitURLHelpers.swift rename to Packages/Sources/RxCodeCore/Utilities/GitURLHelpers.swift diff --git a/Packages/Sources/ClarcCore/Utilities/GitWorktreeService.swift b/Packages/Sources/RxCodeCore/Utilities/GitWorktreeService.swift similarity index 99% rename from Packages/Sources/ClarcCore/Utilities/GitWorktreeService.swift rename to Packages/Sources/RxCodeCore/Utilities/GitWorktreeService.swift index 7a4edbe1..b02325f4 100644 --- a/Packages/Sources/ClarcCore/Utilities/GitWorktreeService.swift +++ b/Packages/Sources/RxCodeCore/Utilities/GitWorktreeService.swift @@ -35,7 +35,7 @@ public actor GitWorktreeService { let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: NSHomeDirectory() + "/Library/Application Support") let dir = appSupport - .appendingPathComponent("Clarc", isDirectory: true) + .appendingPathComponent("RxCode", isDirectory: true) .appendingPathComponent("worktrees", isDirectory: true) .appendingPathComponent(baseRepo.lastPathComponent, isDirectory: true) return dir diff --git a/Packages/Sources/ClarcCore/Utilities/PathEncoding.swift b/Packages/Sources/RxCodeCore/Utilities/PathEncoding.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/PathEncoding.swift rename to Packages/Sources/RxCodeCore/Utilities/PathEncoding.swift diff --git a/Packages/Sources/ClarcCore/Utilities/SyntaxHighlighter.swift b/Packages/Sources/RxCodeCore/Utilities/SyntaxHighlighter.swift similarity index 100% rename from Packages/Sources/ClarcCore/Utilities/SyntaxHighlighter.swift rename to Packages/Sources/RxCodeCore/Utilities/SyntaxHighlighter.swift diff --git a/Packages/Sources/ClarcCore/WindowState.swift b/Packages/Sources/RxCodeCore/WindowState.swift similarity index 100% rename from Packages/Sources/ClarcCore/WindowState.swift rename to Packages/Sources/RxCodeCore/WindowState.swift diff --git a/Packages/Tests/ClarcChatKitTests/AutoScrollAnchorTests.swift b/Packages/Tests/RxCodeChatKitTests/AutoScrollAnchorTests.swift similarity index 99% rename from Packages/Tests/ClarcChatKitTests/AutoScrollAnchorTests.swift rename to Packages/Tests/RxCodeChatKitTests/AutoScrollAnchorTests.swift index e005159a..18495678 100644 --- a/Packages/Tests/ClarcChatKitTests/AutoScrollAnchorTests.swift +++ b/Packages/Tests/RxCodeChatKitTests/AutoScrollAnchorTests.swift @@ -1,6 +1,6 @@ import Testing import CoreGraphics -@testable import ClarcChatKit +@testable import RxCodeChatKit /// `AutoScrollAnchor` decides whether content-growth events should pull the /// scroll view back to the bottom and whether a given geometry change should diff --git a/Packages/Tests/ClarcChatKitTests/ChatBridgePlanStateTests.swift b/Packages/Tests/RxCodeChatKitTests/ChatBridgePlanStateTests.swift similarity index 99% rename from Packages/Tests/ClarcChatKitTests/ChatBridgePlanStateTests.swift rename to Packages/Tests/RxCodeChatKitTests/ChatBridgePlanStateTests.swift index 86c5ad04..e05b38c5 100644 --- a/Packages/Tests/ClarcChatKitTests/ChatBridgePlanStateTests.swift +++ b/Packages/Tests/RxCodeChatKitTests/ChatBridgePlanStateTests.swift @@ -1,6 +1,6 @@ import Testing -import ClarcCore -@testable import ClarcChatKit +import RxCodeCore +@testable import RxCodeChatKit @MainActor @Suite("ChatBridge plan-state helpers") diff --git a/Packages/Tests/ClarcChatKitTests/MessageBubbleTests.swift b/Packages/Tests/RxCodeChatKitTests/MessageBubbleTests.swift similarity index 96% rename from Packages/Tests/ClarcChatKitTests/MessageBubbleTests.swift rename to Packages/Tests/RxCodeChatKitTests/MessageBubbleTests.swift index 2824083e..9ec6235a 100644 --- a/Packages/Tests/ClarcChatKitTests/MessageBubbleTests.swift +++ b/Packages/Tests/RxCodeChatKitTests/MessageBubbleTests.swift @@ -1,8 +1,8 @@ import Testing import SwiftUI import ViewInspector -import ClarcCore -@testable import ClarcChatKit +import RxCodeCore +@testable import RxCodeChatKit /// ViewInspector tests for `MessageBubble`. These exercise the user-message rendering /// path that surfaces inline `[Attached image: /path]` markers from CLI-history content. @@ -30,7 +30,7 @@ struct MessageBubbleTests { private func writeTempImage() -> String { // Create a 1x1 PNG so NSImage(contentsOfFile:) succeeds in inlineAttachedImages. - let path = NSTemporaryDirectory() + "clarc-test-\(UUID().uuidString).png" + let path = NSTemporaryDirectory() + "rxcode-test-\(UUID().uuidString).png" let image = NSImage(size: NSSize(width: 1, height: 1)) image.lockFocus() NSColor.red.setFill() diff --git a/Packages/Tests/ClarcCoreTests/ChatSessionTitleTests.swift b/Packages/Tests/RxCodeCoreTests/ChatSessionTitleTests.swift similarity index 90% rename from Packages/Tests/ClarcCoreTests/ChatSessionTitleTests.swift rename to Packages/Tests/RxCodeCoreTests/ChatSessionTitleTests.swift index 2586bba1..d605eb28 100644 --- a/Packages/Tests/ClarcCoreTests/ChatSessionTitleTests.swift +++ b/Packages/Tests/RxCodeCoreTests/ChatSessionTitleTests.swift @@ -1,12 +1,12 @@ import Testing -@testable import ClarcCore +@testable import RxCodeCore @Suite("ChatSession.stripAttachmentMarkers") struct StripAttachmentMarkersTests { @Test("Strips [Attached image: ...] block") func stripsAttachedImage() { - let input = "[Attached image: /var/folders/nl/clarc-img-3624AF0E.png] fix the bug" + let input = "[Attached image: /var/folders/nl/rxcode-img-3624AF0E.png] fix the bug" #expect(ChatSession.stripAttachmentMarkers(from: input) == "fix the bug") } @@ -42,7 +42,7 @@ struct StripAttachmentMarkersTests { @Test("Strips both [Attached image] and [ImageN] in same message") func stripsCombined() { - let input = "[Attached image: /var/folders/nl/clarc-img.png]\n\n[Image1] remove the status bar's project location" + let input = "[Attached image: /var/folders/nl/rxcode-img.png]\n\n[Image1] remove the status bar's project location" #expect(ChatSession.stripAttachmentMarkers(from: input) == "remove the status bar's project location") } diff --git a/Packages/Tests/ClarcCoreTests/GitStatusParsingTests.swift b/Packages/Tests/RxCodeCoreTests/GitStatusParsingTests.swift similarity index 99% rename from Packages/Tests/ClarcCoreTests/GitStatusParsingTests.swift rename to Packages/Tests/RxCodeCoreTests/GitStatusParsingTests.swift index ef5cb639..61e993da 100644 --- a/Packages/Tests/ClarcCoreTests/GitStatusParsingTests.swift +++ b/Packages/Tests/RxCodeCoreTests/GitStatusParsingTests.swift @@ -1,5 +1,5 @@ import Testing -@testable import ClarcCore +@testable import RxCodeCore // parseGitStatusPorcelain reads character at index 1 (the Y/worktree column). // Staged-only changes (X=' ', Y=' ') fall through to the default case → counted as modified. diff --git a/Packages/Tests/ClarcCoreTests/GitURLHelpersTests.swift b/Packages/Tests/RxCodeCoreTests/GitURLHelpersTests.swift similarity index 97% rename from Packages/Tests/ClarcCoreTests/GitURLHelpersTests.swift rename to Packages/Tests/RxCodeCoreTests/GitURLHelpersTests.swift index 98843a45..d4b10d15 100644 --- a/Packages/Tests/ClarcCoreTests/GitURLHelpersTests.swift +++ b/Packages/Tests/RxCodeCoreTests/GitURLHelpersTests.swift @@ -1,5 +1,5 @@ import Testing -@testable import ClarcCore +@testable import RxCodeCore @Suite("parseGitHubOwnerRepo") struct GitURLHelpersTests { diff --git a/Packages/Tests/ClarcCoreTests/SessionRowReconcilerTests.swift b/Packages/Tests/RxCodeCoreTests/SessionRowReconcilerTests.swift similarity index 97% rename from Packages/Tests/ClarcCoreTests/SessionRowReconcilerTests.swift rename to Packages/Tests/RxCodeCoreTests/SessionRowReconcilerTests.swift index 7ffa1655..6af88e08 100644 --- a/Packages/Tests/ClarcCoreTests/SessionRowReconcilerTests.swift +++ b/Packages/Tests/RxCodeCoreTests/SessionRowReconcilerTests.swift @@ -1,5 +1,5 @@ import Testing -@testable import ClarcCore +@testable import RxCodeCore @Suite("SessionRowReconciler") struct SessionRowReconcilerTests { @@ -116,7 +116,7 @@ struct ChatSessionPlaceholderTitleTests { @Test("Strips [ImageN] tokens and [Attached image] markers from the prefix") func stripsImageTokensAndAttachedImage() { - let input = "[Attached image: /var/folders/nl/clarc-img.png]\n\n[Image1] remove the status bar" + let input = "[Attached image: /var/folders/nl/rxcode-img.png]\n\n[Image1] remove the status bar" #expect(ChatSession.placeholderTitle(from: input) == "remove the status bar") } diff --git a/README.md b/README.md index 0f0561ec..c296f0ff 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Clarc +# RxCode -**The terminal was for the few. Clarc is for everyone.** +**The terminal was for the few. RxCode is for everyone.** -Clarc is a lightweight native macOS desktop client for Claude Code. It brings the CLI agent workflow into a project-centric GUI with streaming chat, repository switching, file browsing, Git status, permissions, terminal access, and per-project notes. +RxCode is a lightweight native macOS desktop client for Claude Code. It brings the CLI agent workflow into a project-centric GUI with streaming chat, repository switching, file browsing, Git status, permissions, terminal access, and per-project notes. ![Platform](https://img.shields.io/badge/platform-macOS%2015.0%2B-blue) ![Swift](https://img.shields.io/badge/Swift-6.x-orange) @@ -13,15 +13,15 @@ Clarc is a lightweight native macOS desktop client for Claude Code. It brings th ## Screenshots -![Clarc Screenshot](docs/screenshot.png) +![RxCode Screenshot](docs/screenshot.png) --- -## Why Clarc? +## Why RxCode? The terminal is a wall. For most people who aren't developers, it's a closed door — install a CLI, generate SSH keys, approve every tool call without a real preview of what it's about to do. None of that is hard for engineers; all of it is hard for everyone else. The terminal was for the few, and it still is. -Clarc was built so my non-developer coworkers could use Claude Code without learning a shell first. It doesn't reinvent the agent. It spawns the real `claude` CLI underneath, so your `CLAUDE.md`, skills, MCP servers, and slash commands keep working as-is. What sits on top is a native Mac app: +RxCode was built so my non-developer coworkers could use Claude Code without learning a shell first. It doesn't reinvent the agent. It spawns the real `claude` CLI underneath, so your `CLAUDE.md`, skills, MCP servers, and slash commands keep working as-is. What sits on top is a native Mac app: - Approval modals that surface the actual diff before any tool runs, with risk-aware Allow / Allow Session / Deny options. - Per-project windows you can run in parallel — switch tabs, double-click to spin off a window, keep streams alive in the background. @@ -35,12 +35,12 @@ Same engine, no terminal required. ## Key Features vs. Claude Desktop -| Clarc feature | Why it matters | +| RxCode feature | Why it matters | |---------------|----------------| | **Native macOS app** | Built with SwiftUI, not Electron. The current v1.2.0 release is about 5.6 MB to download and about 13 MB unpacked, without bundling a browser runtime. | | **Project-centric workspace** | Register multiple local repositories, switch between them from project tabs, or open a project in its own window for parallel sessions. In-progress streams keep running in the background while you switch. | | **Custom slash commands** | Add, edit, disable, import, and export custom slash commands. Built-in commands can be edited locally, while JSON import/export stays custom-only. | -| **Shortcut buttons** | Create quick buttons for prompts or terminal commands you run repeatedly. Terminal-command shortcuts can launch directly into Clarc's interactive terminal popup. | +| **Shortcut buttons** | Create quick buttons for prompts or terminal commands you run repeatedly. Terminal-command shortcuts can launch directly into RxCode's interactive terminal popup. | | **Built-in file explorer with Git status** | Browse and search project files, toggle hidden files, preview or edit files, inspect Git status, and switch branches from the sidebar. | | **Rich-text memo pad per project** | Keep project-specific notes in the inspector panel with headings, lists, checkboxes, links, and Markdown copy/paste support. | | **Embedded terminal** | Use a SwiftTerm-based terminal in the inspector, plus interactive terminal popups for commands such as `/config`, `/permissions`, and `/model`. | @@ -73,7 +73,7 @@ Same engine, no terminal required. | **Skill Marketplace** | Browse and install official Anthropic plugins, refreshed with a 5-minute cache. | | **Themes and Font Controls** | Six accent themes plus independent font size controls for the interface and message area. | | **Focus Mode** | Optional focused chat layout that can be enabled from Settings. | -| **Notifications** | Optional system notifications with response previews while Clarc is in the background. | +| **Notifications** | Optional system notifications with response previews while RxCode is in the background. | | **Localization** | Full English and Korean UI. | | **User Guide** | Built-in in-app help guide accessible from the toolbar and Settings. | | **Auto-update** | Sparkle-based update checking on launch, with manual checks from the app menu. | @@ -90,41 +90,41 @@ Same engine, no terminal required. ## Installation -1. Download the latest `Clarc-x.y.z.zip` from the [Releases](https://github.com/ttnear/Clarc/releases) page. -2. Unzip and move `Clarc.app` to your `Applications` folder. -3. Launch `Clarc.app`. +1. Download the latest `RxCode-x.y.z.zip` from the [Releases](https://github.com/ttnear/RxCode/releases) page. +2. Unzip and move `RxCode.app` to your `Applications` folder. +3. Launch `RxCode.app`. ### First Launch on macOS 15 (Sequoia) macOS Sequoia blocks the first launch of any downloaded app, even notarized ones, and routes approval through System Settings instead of the old right-click -> Open flow. -When you see **"Apple could not verify 'Clarc.app' is free of malware..."**: +When you see **"Apple could not verify 'RxCode.app' is free of malware..."**: 1. Click **Done** on the dialog. 2. Open **System Settings -> Privacy & Security**. -3. Scroll to the Security section and click **Open Anyway** next to `Clarc.app`. +3. Scroll to the Security section and click **Open Anyway** next to `RxCode.app`. 4. Confirm with your password or Touch ID. -After this one-time approval, Clarc launches normally. The app is signed with a Developer ID certificate and notarized by Apple. This prompt is standard macOS behavior, not a security warning specific to Clarc. +After this one-time approval, RxCode launches normally. The app is signed with a Developer ID certificate and notarized by Apple. This prompt is standard macOS behavior, not a security warning specific to RxCode. --- ## Build from Source ```bash -open Clarc.xcodeproj +open RxCode.xcodeproj ``` For a CLI build: ```bash -xcodebuild -project Clarc.xcodeproj -scheme Clarc -configuration Debug build +xcodebuild -project RxCode.xcodeproj -scheme RxCode -configuration Debug build ``` For tests: ```bash -xcodebuild test -project Clarc.xcodeproj -scheme Clarc -destination 'platform=macOS' +xcodebuild test -project RxCode.xcodeproj -scheme RxCode -destination 'platform=macOS' swift test --package-path Packages ``` @@ -136,10 +136,10 @@ For a signed release ZIP, use the release scripts under `scripts/`. | Path | Purpose | |------|---------| -| `Clarc/` | macOS app target: app entry point, views, services, resources, and integrations. | -| `Packages/Sources/ClarcCore/` | Shared models, theme, utilities, Git helpers, and pure core logic. | -| `Packages/Sources/ClarcChatKit/` | Reusable chat UI, message rendering, input bar, slash commands, shortcuts, diffs, and status line. | -| `ClarcTests/` | App-level XCTest coverage. | +| `RxCode/` | macOS app target: app entry point, views, services, resources, and integrations. | +| `Packages/Sources/RxCodeCore/` | Shared models, theme, utilities, Git helpers, and pure core logic. | +| `Packages/Sources/RxCodeChatKit/` | Reusable chat UI, message rendering, input bar, slash commands, shortcuts, diffs, and status line. | +| `RxCodeTests/` | App-level XCTest coverage. | | `Packages/Tests/` | Swift Testing coverage for core utilities. | | `release_notes/` | Human-readable release notes used for publishing. | | `scripts/` | Build, notarization, Sparkle signing, and release automation. | diff --git a/Clarc.xcodeproj/project.pbxproj b/RxCode.xcodeproj/project.pbxproj similarity index 85% rename from Clarc.xcodeproj/project.pbxproj rename to RxCode.xcodeproj/project.pbxproj index db1d765c..a14e10a3 100644 --- a/Clarc.xcodeproj/project.pbxproj +++ b/RxCode.xcodeproj/project.pbxproj @@ -9,16 +9,16 @@ /* Begin PBXBuildFile section */ 33993F0F87CF4DB09F2813A8 /* AppStateProjectSwitchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4381E755142272EB2DAA9C96 /* AppStateProjectSwitchTests.swift */; }; 92E180F9B311F3C72D5DE6B7 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9993BB72A5307039A88B729 /* Cocoa.framework */; }; - DCA8CF4A05C959C4A6EB391F /* ClarcCore in Frameworks */ = {isa = PBXBuildFile; productRef = CCDF9594876099576D4FD46E /* ClarcCore */; }; + DCA8CF4A05C959C4A6EB391F /* RxCodeCore in Frameworks */ = {isa = PBXBuildFile; productRef = CCDF9594876099576D4FD46E /* RxCodeCore */; }; DF06CCD12FB4CAB5005991E1 /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = DF06CCD02FB4CAB5005991E1 /* ViewInspector */; }; DFA0CCD12FB4CC01005991E1 /* PlanDecisionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA0CCC02FB4CC01005991E1 /* PlanDecisionTests.swift */; }; DFA0CCD22FB4CC01005991E1 /* PlanCardViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA0CCC12FB4CC01005991E1 /* PlanCardViewTests.swift */; }; DFA0CCD32FB4CC01005991E1 /* InputBarDisableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA0CCC22FB4CC01005991E1 /* InputBarDisableTests.swift */; }; - DFA0CCD42FB4CC01005991E1 /* ClarcChatKit in Frameworks */ = {isa = PBXBuildFile; productRef = DFA0CCC32FB4CC01005991E1 /* ClarcChatKit */; }; + DFA0CCD42FB4CC01005991E1 /* RxCodeChatKit in Frameworks */ = {isa = PBXBuildFile; productRef = DFA0CCC32FB4CC01005991E1 /* RxCodeChatKit */; }; E6821AC12F7CEE7200829FC9 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = E6A001012F8A000100000001 /* SwiftTerm */; }; E6C001022F9B000100000001 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E6C001012F9B000100000001 /* Sparkle */; }; - E6D001032FA0000100000001 /* ClarcCore in Frameworks */ = {isa = PBXBuildFile; productRef = E6D001012FA0000100000001 /* ClarcCore */; }; - E6D001042FA0000100000001 /* ClarcChatKit in Frameworks */ = {isa = PBXBuildFile; productRef = E6D001022FA0000100000001 /* ClarcChatKit */; }; + E6D001032FA0000100000001 /* RxCodeCore in Frameworks */ = {isa = PBXBuildFile; productRef = E6D001012FA0000100000001 /* RxCodeCore */; }; + E6D001042FA0000100000001 /* RxCodeChatKit in Frameworks */ = {isa = PBXBuildFile; productRef = E6D001022FA0000100000001 /* RxCodeChatKit */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -27,24 +27,24 @@ containerPortal = E67335302F7356F600FD26C7 /* Project object */; proxyType = 1; remoteGlobalIDString = E67335372F7356F600FD26C7; - remoteInfo = Clarc; + remoteInfo = RxCode; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 4381E755142272EB2DAA9C96 /* AppStateProjectSwitchTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AppStateProjectSwitchTests.swift; sourceTree = ""; }; - 7321B5E8B81AAB1A2DC0593B /* ClarcTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClarcTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7321B5E8B81AAB1A2DC0593B /* RxCodeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxCodeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A9993BB72A5307039A88B729 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.0.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; DFA0CCC02FB4CC01005991E1 /* PlanDecisionTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PlanDecisionTests.swift; sourceTree = ""; }; DFA0CCC12FB4CC01005991E1 /* PlanCardViewTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PlanCardViewTests.swift; sourceTree = ""; }; DFA0CCC22FB4CC01005991E1 /* InputBarDisableTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InputBarDisableTests.swift; sourceTree = ""; }; - E67335382F7356F600FD26C7 /* Clarc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Clarc.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E67335382F7356F600FD26C7 /* RxCode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxCode.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - E673353A2F7356F600FD26C7 /* Clarc */ = { + E673353A2F7356F600FD26C7 /* RxCode */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = Clarc; + path = RxCode; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ @@ -55,8 +55,8 @@ buildActionMask = 2147483647; files = ( 92E180F9B311F3C72D5DE6B7 /* Cocoa.framework in Frameworks */, - DCA8CF4A05C959C4A6EB391F /* ClarcCore in Frameworks */, - DFA0CCD42FB4CC01005991E1 /* ClarcChatKit in Frameworks */, + DCA8CF4A05C959C4A6EB391F /* RxCodeCore in Frameworks */, + DFA0CCD42FB4CC01005991E1 /* RxCodeChatKit in Frameworks */, DF06CCD12FB4CAB5005991E1 /* ViewInspector in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -67,15 +67,15 @@ files = ( E6821AC12F7CEE7200829FC9 /* SwiftTerm in Frameworks */, E6C001022F9B000100000001 /* Sparkle in Frameworks */, - E6D001032FA0000100000001 /* ClarcCore in Frameworks */, - E6D001042FA0000100000001 /* ClarcChatKit in Frameworks */, + E6D001032FA0000100000001 /* RxCodeCore in Frameworks */, + E6D001042FA0000100000001 /* RxCodeChatKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1525FE6BFB6F06A3F00B92D3 /* ClarcTests */ = { + 1525FE6BFB6F06A3F00B92D3 /* RxCodeTests */ = { isa = PBXGroup; children = ( 4381E755142272EB2DAA9C96 /* AppStateProjectSwitchTests.swift */, @@ -83,7 +83,7 @@ DFA0CCC12FB4CC01005991E1 /* PlanCardViewTests.swift */, DFA0CCC22FB4CC01005991E1 /* InputBarDisableTests.swift */, ); - path = ClarcTests; + path = RxCodeTests; sourceTree = ""; }; 50F6C20C7EE8F07B95128612 /* OS X */ = { @@ -105,18 +105,18 @@ E673352F2F7356F600FD26C7 = { isa = PBXGroup; children = ( - E673353A2F7356F600FD26C7 /* Clarc */, + E673353A2F7356F600FD26C7 /* RxCode */, E67335392F7356F600FD26C7 /* Products */, 609E8EE085862BD7D5B4012F /* Frameworks */, - 1525FE6BFB6F06A3F00B92D3 /* ClarcTests */, + 1525FE6BFB6F06A3F00B92D3 /* RxCodeTests */, ); sourceTree = ""; }; E67335392F7356F600FD26C7 /* Products */ = { isa = PBXGroup; children = ( - E67335382F7356F600FD26C7 /* Clarc.app */, - 7321B5E8B81AAB1A2DC0593B /* ClarcTests.xctest */, + E67335382F7356F600FD26C7 /* RxCode.app */, + 7321B5E8B81AAB1A2DC0593B /* RxCodeTests.xctest */, ); name = Products; sourceTree = ""; @@ -124,9 +124,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 5D74FE7B782850D23F8D9BF7 /* ClarcTests */ = { + 5D74FE7B782850D23F8D9BF7 /* RxCodeTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 50524686CA1336CE22A6C2C3 /* Build configuration list for PBXNativeTarget "ClarcTests" */; + buildConfigurationList = 50524686CA1336CE22A6C2C3 /* Build configuration list for PBXNativeTarget "RxCodeTests" */; buildPhases = ( AEA1371373E1E06A1A8F340A /* Sources */, 023150BBA1A1771608C2FCEB /* Frameworks */, @@ -137,19 +137,19 @@ dependencies = ( BC9DD9CE1DAA92D5D395C09C /* PBXTargetDependency */, ); - name = ClarcTests; + name = RxCodeTests; packageProductDependencies = ( - CCDF9594876099576D4FD46E /* ClarcCore */, - DFA0CCC32FB4CC01005991E1 /* ClarcChatKit */, + CCDF9594876099576D4FD46E /* RxCodeCore */, + DFA0CCC32FB4CC01005991E1 /* RxCodeChatKit */, DF06CCD02FB4CAB5005991E1 /* ViewInspector */, ); - productName = ClarcTests; - productReference = 7321B5E8B81AAB1A2DC0593B /* ClarcTests.xctest */; + productName = RxCodeTests; + productReference = 7321B5E8B81AAB1A2DC0593B /* RxCodeTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - E67335372F7356F600FD26C7 /* Clarc */ = { + E67335372F7356F600FD26C7 /* RxCode */ = { isa = PBXNativeTarget; - buildConfigurationList = E67335432F7356F700FD26C7 /* Build configuration list for PBXNativeTarget "Clarc" */; + buildConfigurationList = E67335432F7356F700FD26C7 /* Build configuration list for PBXNativeTarget "RxCode" */; buildPhases = ( E67335342F7356F600FD26C7 /* Sources */, E67335352F7356F600FD26C7 /* Frameworks */, @@ -160,17 +160,17 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - E673353A2F7356F600FD26C7 /* Clarc */, + E673353A2F7356F600FD26C7 /* RxCode */, ); - name = Clarc; + name = RxCode; packageProductDependencies = ( E6A001012F8A000100000001 /* SwiftTerm */, E6C001012F9B000100000001 /* Sparkle */, - E6D001012FA0000100000001 /* ClarcCore */, - E6D001022FA0000100000001 /* ClarcChatKit */, + E6D001012FA0000100000001 /* RxCodeCore */, + E6D001022FA0000100000001 /* RxCodeChatKit */, ); - productName = Clarc; - productReference = E67335382F7356F600FD26C7 /* Clarc.app */; + productName = RxCode; + productReference = E67335382F7356F600FD26C7 /* RxCode.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -188,7 +188,7 @@ }; }; }; - buildConfigurationList = E67335332F7356F600FD26C7 /* Build configuration list for PBXProject "Clarc" */; + buildConfigurationList = E67335332F7356F600FD26C7 /* Build configuration list for PBXProject "RxCode" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -209,8 +209,8 @@ projectDirPath = ""; projectRoot = ""; targets = ( - E67335372F7356F600FD26C7 /* Clarc */, - 5D74FE7B782850D23F8D9BF7 /* ClarcTests */, + E67335372F7356F600FD26C7 /* RxCode */, + 5D74FE7B782850D23F8D9BF7 /* RxCodeTests */, ); }; /* End PBXProject section */ @@ -256,8 +256,8 @@ /* Begin PBXTargetDependency section */ BC9DD9CE1DAA92D5D395C09C /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = Clarc; - target = E67335372F7356F600FD26C7 /* Clarc */; + name = RxCode; + target = E67335372F7356F600FD26C7 /* RxCode */; targetProxy = 35C1B17CDEF83F212F648418 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -269,10 +269,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 26.2; - PRODUCT_NAME = ClarcTests; + PRODUCT_NAME = RxCodeTests; SDKROOT = macosx; SWIFT_VERSION = 6.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clarc.app/Contents/MacOS/Clarc"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxCode.app/Contents/MacOS/RxCode"; }; name = Debug; }; @@ -282,10 +282,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 26.2; - PRODUCT_NAME = ClarcTests; + PRODUCT_NAME = RxCodeTests; SDKROOT = macosx; SWIFT_VERSION = 6.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clarc.app/Contents/MacOS/Clarc"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxCode.app/Contents/MacOS/RxCode"; }; name = Release; }; @@ -423,7 +423,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = Clarc/Info.plist; + INFOPLIST_FILE = RxCode/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -431,7 +431,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.2.5; - PRODUCT_BUNDLE_IDENTIFIER = com.idealapp.Clarc; + PRODUCT_BUNDLE_IDENTIFIER = com.idealapp.RxCode; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -457,7 +457,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = NO; - INFOPLIST_FILE = Clarc/Info.plist; + INFOPLIST_FILE = RxCode/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -465,7 +465,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 1.2.5; - PRODUCT_BUNDLE_IDENTIFIER = com.idealapp.Clarc; + PRODUCT_BUNDLE_IDENTIFIER = com.idealapp.RxCode; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -480,7 +480,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 50524686CA1336CE22A6C2C3 /* Build configuration list for PBXNativeTarget "ClarcTests" */ = { + 50524686CA1336CE22A6C2C3 /* Build configuration list for PBXNativeTarget "RxCodeTests" */ = { isa = XCConfigurationList; buildConfigurations = ( AD9E27433C08ABBEF721A8A6 /* Release */, @@ -489,7 +489,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - E67335332F7356F600FD26C7 /* Build configuration list for PBXProject "Clarc" */ = { + E67335332F7356F600FD26C7 /* Build configuration list for PBXProject "RxCode" */ = { isa = XCConfigurationList; buildConfigurations = ( E67335412F7356F700FD26C7 /* Debug */, @@ -498,7 +498,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - E67335432F7356F700FD26C7 /* Build configuration list for PBXNativeTarget "Clarc" */ = { + E67335432F7356F700FD26C7 /* Build configuration list for PBXNativeTarget "RxCode" */ = { isa = XCConfigurationList; buildConfigurations = ( E67335442F7356F700FD26C7 /* Debug */, @@ -544,18 +544,18 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - CCDF9594876099576D4FD46E /* ClarcCore */ = { + CCDF9594876099576D4FD46E /* RxCodeCore */ = { isa = XCSwiftPackageProductDependency; - productName = ClarcCore; + productName = RxCodeCore; }; DF06CCD02FB4CAB5005991E1 /* ViewInspector */ = { isa = XCSwiftPackageProductDependency; package = DF06CCCF2FB4CAB5005991E1 /* XCRemoteSwiftPackageReference "ViewInspector" */; productName = ViewInspector; }; - DFA0CCC32FB4CC01005991E1 /* ClarcChatKit */ = { + DFA0CCC32FB4CC01005991E1 /* RxCodeChatKit */ = { isa = XCSwiftPackageProductDependency; - productName = ClarcChatKit; + productName = RxCodeChatKit; }; E6A001012F8A000100000001 /* SwiftTerm */ = { isa = XCSwiftPackageProductDependency; @@ -567,15 +567,15 @@ package = E6C001002F9B000100000001 /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; - E6D001012FA0000100000001 /* ClarcCore */ = { + E6D001012FA0000100000001 /* RxCodeCore */ = { isa = XCSwiftPackageProductDependency; package = E6D001002FA0000100000001 /* XCLocalSwiftPackageReference "Packages" */; - productName = ClarcCore; + productName = RxCodeCore; }; - E6D001022FA0000100000001 /* ClarcChatKit */ = { + E6D001022FA0000100000001 /* RxCodeChatKit */ = { isa = XCSwiftPackageProductDependency; package = E6D001002FA0000100000001 /* XCLocalSwiftPackageReference "Packages" */; - productName = ClarcChatKit; + productName = RxCodeChatKit; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Clarc.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RxCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Clarc.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to RxCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Clarc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/RxCode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 100% rename from Clarc.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to RxCode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Clarc.xcodeproj/xcshareddata/xcschemes/Clarc.xcscheme b/RxCode.xcodeproj/xcshareddata/xcschemes/RxCode.xcscheme similarity index 82% rename from Clarc.xcodeproj/xcshareddata/xcschemes/Clarc.xcscheme rename to RxCode.xcodeproj/xcshareddata/xcschemes/RxCode.xcscheme index f9e986a0..8a8f85ad 100644 --- a/Clarc.xcodeproj/xcshareddata/xcschemes/Clarc.xcscheme +++ b/RxCode.xcodeproj/xcshareddata/xcschemes/RxCode.xcscheme @@ -16,9 +16,9 @@ + BuildableName = "RxCode.app" + BlueprintName = "RxCode" + ReferencedContainer = "container:RxCode.xcodeproj"> @@ -35,9 +35,9 @@ + BuildableName = "RxCodeTests.xctest" + BlueprintName = "RxCodeTests" + ReferencedContainer = "container:RxCode.xcodeproj"> @@ -58,9 +58,9 @@ + BuildableName = "RxCode.app" + BlueprintName = "RxCode" + ReferencedContainer = "container:RxCode.xcodeproj"> @@ -75,9 +75,9 @@ + BuildableName = "RxCode.app" + BlueprintName = "RxCode" + ReferencedContainer = "container:RxCode.xcodeproj"> diff --git a/Clarc/App/AppState.swift b/RxCode/App/AppState.swift similarity index 99% rename from Clarc/App/AppState.swift rename to RxCode/App/AppState.swift index 714f85f0..353fd274 100644 --- a/Clarc/App/AppState.swift +++ b/RxCode/App/AppState.swift @@ -1,8 +1,8 @@ import Foundation -import ClarcCore +import RxCodeCore import SwiftUI import os -import ClarcChatKit +import RxCodeChatKit // MARK: - Per-Session Stream State @@ -822,6 +822,12 @@ final class AppState { await self?.refreshAndProbeAllMCPServers() } + // Warm the rate-limit usage so the menu-bar label has data before the + // popover is opened. RateLimitService caches for 5 minutes internally. + Task { [weak self] in + await self?.refreshRateLimitUsage() + } + // Recurring probe so disconnected MCP servers surface promptly even // when the user isn't actively interacting with the Settings tab. startMCPPeriodicProbe() @@ -1514,8 +1520,12 @@ final class AppState { sessionKey = sid startFlushTimer(for: sid) - // If this is the foreground session, also update window.currentSessionId - let isFg = (window.currentSessionId ?? window.newSessionKey) == previousSessionKey || window.currentSessionId == nil + // If this is the foreground session, also update window.currentSessionId. + // Do NOT treat `currentSessionId == nil` (the new-thread page) as foreground + // for an arbitrary streaming session — that caused the UI to auto-navigate + // to a previously-detached thread whenever its CLI advanced its session_id + // (e.g. pending→real on first system event, or compact_boundary swap). + let isFg = (window.currentSessionId ?? window.newSessionKey) == previousSessionKey if isFg { window.currentSessionId = sid } } @@ -2540,8 +2550,8 @@ final class AppState { func cloneAndAddProject(_ repo: GitHubRepo, in window: WindowState) async throws { let home = FileManager.default.homeDirectoryForCurrentUser.path - let clonePath = "\(home)/Clarc/\(repo.name)" - let parentDir = "\(home)/Clarc" + let clonePath = "\(home)/RxCode/\(repo.name)" + let parentDir = "\(home)/RxCode" let fm = FileManager.default if !fm.fileExists(atPath: parentDir) { try fm.createDirectory(atPath: parentDir, withIntermediateDirectories: true) @@ -2783,7 +2793,7 @@ final class AppState { var updated: ChatSession = switch summary.origin { case .cliBacked: summary.makeSession() - case .legacyClarc: + case .legacyRxCode: persistence.loadLegacySessionSync(projectId: session.projectId, sessionId: session.id) ?? session } mutate(&updated) diff --git a/Clarc/App/ClarcApp.swift b/RxCode/App/RxCodeApp.swift similarity index 87% rename from Clarc/App/ClarcApp.swift rename to RxCode/App/RxCodeApp.swift index 97bca9a4..8bd5db22 100644 --- a/Clarc/App/ClarcApp.swift +++ b/RxCode/App/RxCodeApp.swift @@ -1,6 +1,6 @@ import SwiftUI -import ClarcCore -import ClarcChatKit +import RxCodeCore +import RxCodeChatKit // MARK: - FocusedValues @@ -31,7 +31,7 @@ struct TerminalWindowValue: Codable, Hashable { // MARK: - App @main -struct ClarcApp: App { +struct RxCodeApp: App { @State private var appState = AppState() @FocusedValue(\.startNewChat) private var startNewChat @AppStorage("showMenuBarExtra") private var showMenuBarExtra: Bool = true @@ -104,26 +104,66 @@ private struct MenuBarLabel: View { var body: some View { let inProgress = appState.inProgressSessionCount let unchecked = appState.uncheckedFinishedSessionCount + let totalJobs = inProgress + unchecked + let fiveHour = appState.latestRateLimitUsage?.fiveHourPercent - if inProgress == 0 && unchecked == 0 { - Image(systemName: "bubble.left.and.bubble.right") + if let image = Self.renderLabelImage(fiveHour: fiveHour, inProgress: inProgress, totalJobs: totalJobs) { + Image(nsImage: image) } else { - // SwiftUI renders MenuBarExtra labels as text + image. Compose a - // single attributed-ish string by joining counts with separators. - HStack(spacing: 4) { - if inProgress > 0 { - Image(systemName: "circle.dotted") - Text("\(inProgress)") - } - if inProgress > 0 && unchecked > 0 { - Text("·") + Image(systemName: "message") + } + } + + @MainActor + private static func renderLabelImage(fiveHour: Double?, inProgress: Int, totalJobs: Int) -> NSImage? { + let content = MenuBarLabelContent( + fiveHourText: fiveHour.map { "\(formatPercent($0))%" } ?? "—%", + jobsText: inProgress > 0 ? "\(inProgress) Job\(inProgress == 1 ? "" : "s")" : nil + ) + let renderer = ImageRenderer(content: content) + renderer.scale = NSScreen.main?.backingScaleFactor ?? 2 + guard let cgImage = renderer.cgImage else { return nil } + let size = NSSize(width: CGFloat(cgImage.width) / renderer.scale, + height: CGFloat(cgImage.height) / renderer.scale) + let image = NSImage(cgImage: cgImage, size: size) + image.isTemplate = true + return image + } + + private static func formatPercent(_ value: Double) -> String { + if value > 0 && value < 1 { + return String(format: "%.1f", value) + } + return "\(Int(value.rounded()))" + } +} + +private struct MenuBarLabelContent: View { + let fiveHourText: String + let jobsText: String? + + var body: some View { + HStack(spacing: 6) { + Image(systemName: "message") + .font(.system(size: 11, weight: .medium)) + + VStack(alignment: .trailing, spacing: -1) { + HStack(alignment: .bottom, spacing: 1) { + Text(fiveHourText) + .font(.system(size: 11, weight: .semibold)) + .monospacedDigit() + Text("5h") + .font(.system(size: 7, weight: .medium)) } - if unchecked > 0 { - Image(systemName: "checkmark.circle") - Text("\(unchecked)") + if let jobsText { + Text(jobsText) + .font(.system(size: 8, weight: .semibold)) + .monospacedDigit() } } } + .padding(.vertical, 1) + .foregroundStyle(.black) } } @@ -224,7 +264,7 @@ private struct MenuBarContentView: View { private var footer: some View { HStack { - Button("Open Clarc") { + Button("Open RxCode") { NSApp.activate(ignoringOtherApps: true) } .buttonStyle(.borderless) diff --git a/Clarc/Assets.xcassets/AccentColor.colorset/Contents.json b/RxCode/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Clarc/Assets.xcassets/AccentColor.colorset/Contents.json rename to RxCode/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/1024-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/1024-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/1024-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/1024-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/128-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/128-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/128-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/128-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/16-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/16-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/16-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/16-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/256-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/256-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/256-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/256-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/32-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/32-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/32-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/32-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/512-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/512-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/512-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/512-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/64-mac.png b/RxCode/Assets.xcassets/AppIcon.appiconset/64-mac.png similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/64-mac.png rename to RxCode/Assets.xcassets/AppIcon.appiconset/64-mac.png diff --git a/Clarc/Assets.xcassets/AppIcon.appiconset/Contents.json b/RxCode/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Clarc/Assets.xcassets/AppIcon.appiconset/Contents.json rename to RxCode/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Clarc/Assets.xcassets/Contents.json b/RxCode/Assets.xcassets/Contents.json similarity index 100% rename from Clarc/Assets.xcassets/Contents.json rename to RxCode/Assets.xcassets/Contents.json diff --git a/Clarc/Assets.xcassets/GitHubMark.imageset/Contents.json b/RxCode/Assets.xcassets/GitHubMark.imageset/Contents.json similarity index 100% rename from Clarc/Assets.xcassets/GitHubMark.imageset/Contents.json rename to RxCode/Assets.xcassets/GitHubMark.imageset/Contents.json diff --git a/Clarc/Assets.xcassets/GitHubMark.imageset/github-mark.pdf b/RxCode/Assets.xcassets/GitHubMark.imageset/github-mark.pdf similarity index 100% rename from Clarc/Assets.xcassets/GitHubMark.imageset/github-mark.pdf rename to RxCode/Assets.xcassets/GitHubMark.imageset/github-mark.pdf diff --git a/Clarc/Info.plist b/RxCode/Info.plist similarity index 93% rename from Clarc/Info.plist rename to RxCode/Info.plist index 259a7826..8e6a374f 100644 --- a/Clarc/Info.plist +++ b/RxCode/Info.plist @@ -27,7 +27,7 @@ NSHumanReadableCopyright SUFeedURL - https://raw.githubusercontent.com/ttnear/Clarc/main/appcast.xml + https://update.code.rxlab.app/appcast.xml SUPublicEDKey o1+OjC4eJrHnvmfwRxGPhH8OQFD21WYlxkFndh+4J0M= diff --git a/Clarc/Resources/en.lproj/Localizable.strings b/RxCode/Resources/en.lproj/Localizable.strings similarity index 93% rename from Clarc/Resources/en.lproj/Localizable.strings rename to RxCode/Resources/en.lproj/Localizable.strings index 0f8aca23..29adfbaa 100644 --- a/Clarc/Resources/en.lproj/Localizable.strings +++ b/RxCode/Resources/en.lproj/Localizable.strings @@ -1,6 +1,6 @@ /* English — base localization for main app */ -// MARK: - ClarcApp +// MARK: - RxCodeApp "New Chat" = "New Chat"; "Check for Updates..." = "Check for Updates..."; "Theme" = "Theme"; @@ -33,7 +33,7 @@ "font.size.hint" = "Adjust interface and message font sizes independently."; "Notifications" = "Notifications"; "Notify when response completes" = "Notify when response completes"; -"Sends a system notification while Clarc is in the background." = "Sends a system notification while Clarc is in the background."; +"Sends a system notification while RxCode is in the background." = "Sends a system notification while RxCode is in the background."; "Open Source" = "Open Source"; "Response complete" = "Response complete"; @@ -107,7 +107,7 @@ "Projects" = "Projects"; "Rename Project" = "Rename Project"; "Delete Project" = "Delete Project"; -"This will remove the project from Clarc. The files on disk will not be deleted." = "This will remove the project from Clarc. The files on disk will not be deleted."; +"This will remove the project from RxCode. The files on disk will not be deleted." = "This will remove the project from RxCode. The files on disk will not be deleted."; "Project name" = "Project name"; // MARK: - HistoryListView @@ -189,7 +189,7 @@ "Install command:" = "Install command:"; "Check Again" = "Check Again"; "Get Started" = "Get Started"; -"Share session history with the terminal CLI in ~/.claude/projects/. Turn off to keep Clarc sessions separate. You can change this later in Settings." = "Share session history with the terminal CLI in ~/.claude/projects/. Turn off to keep Clarc sessions separate. You can change this later in Settings."; +"Share session history with the terminal CLI in ~/.claude/projects/. Turn off to keep RxCode sessions separate. You can change this later in Settings." = "Share session history with the terminal CLI in ~/.claude/projects/. Turn off to keep RxCode sessions separate. You can change this later in Settings."; // MARK: - GitHubLoginView "Connect GitHub to fetch your repo list and add projects with a single click." = "Connect GitHub to fetch your repo list and add projects with a single click."; @@ -236,7 +236,7 @@ // MARK: - UserManualView // Topic titles -"About Clarc" = "About Clarc"; +"About RxCode" = "About RxCode"; "Status Line" = "Status Line"; "Project Management" = "Project Management"; "Chat Basics" = "Chat Basics"; @@ -249,7 +249,7 @@ "Permission Requests" = "Permission Requests"; // Overview -"What is Clarc?" = "What is Clarc?"; +"What is RxCode?" = "What is RxCode?"; "A native macOS desktop client for the Claude Code CLI. Use all Claude Code features via a polished GUI without needing a terminal." = "A native macOS desktop client for the Claude Code CLI. Use all Claude Code features via a polished GUI without needing a terminal."; "Main Layout" = "Main Layout"; "The left sidebar has History and Files tabs. Project tabs appear at the top of the chat area — click to switch projects. The right inspector panel contains the Terminal and Memo tabs." = "The left sidebar has History and Files tabs. Project tabs appear at the top of the chat area — click to switch projects. The right inspector panel contains the Terminal and Memo tabs."; @@ -346,7 +346,7 @@ "Attaching Files" = "Attaching Files"; "Click the clip icon to the left of the input field, or drag and drop files onto the input field. When dragging, the input field border highlights in the accent color to show the drop zone." = "Click the clip icon to the left of the input field, or drag and drop files onto the input field. When dragging, the input field border highlights in the accent color to show the drop zone."; "Clipboard Detection" = "Clipboard Detection"; -"Pasting (⌘V) is smart — Clarc detects what's in the clipboard and handles it automatically." = "Pasting (⌘V) is smart — Clarc detects what's in the clipboard and handles it automatically."; +"Pasting (⌘V) is smart — RxCode detects what's in the clipboard and handles it automatically." = "Pasting (⌘V) is smart — RxCode detects what's in the clipboard and handles it automatically."; "Image data (PNG/TIFF) → attached as an image" = "Image data (PNG/TIFF) → attached as an image"; "File path → attached as a file" = "File path → attached as a file"; "URL → attached as a URL reference" = "URL → attached as a URL reference"; @@ -384,12 +384,12 @@ "The exit code is shown at the bottom — \"exit 0\" means the command completed successfully." = "The exit code is shown at the bottom — \"exit 0\" means the command completed successfully."; // GitHub Integration -"Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to Clarc with one click." = "Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to Clarc with one click."; +"Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to RxCode with one click." = "Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to RxCode with one click."; "Adding a Repository" = "Adding a Repository"; -"Search for a repository by name, then click Add. Clarc clones it automatically and opens it as a new project." = "Search for a repository by name, then click Add. Clarc clones it automatically and opens it as a new project."; +"Search for a repository by name, then click Add. RxCode clones it automatically and opens it as a new project." = "Search for a repository by name, then click Add. RxCode clones it automatically and opens it as a new project."; "Private repository" = "Private repository"; "Public repository" = "Public repository"; -"Already added to Clarc" = "Already added to Clarc"; +"Already added to RxCode" = "Already added to RxCode"; // Skill Marketplace "Click the brain icon (🧠) in the toolbar to browse the MCP plugin catalog published on Anthropic's GitHub. Plugins can be filtered by category or searched by name, description, or author." = "Click the brain icon (🧠) in the toolbar to browse the MCP plugin catalog published on Anthropic's GitHub. Plugins can be filtered by category or searched by name, description, or author."; @@ -467,17 +467,17 @@ // Settings & Themes topic "Settings & Themes" = "Settings & Themes"; "Opening Settings" = "Opening Settings"; -"Choose Clarc → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into three tabs: General, Slash Commands, and Shortcuts." = "Choose Clarc → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into three tabs: General, Slash Commands, and Shortcuts."; +"Choose RxCode → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into three tabs: General, Slash Commands, and Shortcuts." = "Choose RxCode → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into three tabs: General, Slash Commands, and Shortcuts."; "General Tab" = "General Tab"; "The General tab configures session defaults. Changes apply to newly created sessions — existing sessions keep their current values." = "The General tab configures session defaults. Changes apply to newly created sessions — existing sessions keep their current values."; "Accent color palette (Terracotta, Ocean, Forest, Lavender, Midnight, Amber)" = "Accent color palette (Terracotta, Ocean, Forest, Lavender, Midnight, Amber)"; "Claude model used when starting a new session" = "Claude model used when starting a new session"; "Permission mode applied to new sessions" = "Permission mode applied to new sessions"; "Reasoning effort applied to new sessions" = "Reasoning effort applied to new sessions"; -"Show a system notification when a response completes while Clarc is in the background" = "Show a system notification when a response completes while Clarc is in the background"; +"Show a system notification when a response completes while RxCode is in the background" = "Show a system notification when a response completes while RxCode is in the background"; "Model, permission mode, and effort can also be overridden per session from the toolbar — the chosen value sticks to that session." = "Model, permission mode, and effort can also be overridden per session from the toolbar — the chosen value sticks to that session."; "Themes" = "Themes"; -"Clarc ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose." = "Clarc ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose."; +"RxCode ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose." = "RxCode ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose."; "Terracotta" = "Terracotta"; "Ocean" = "Ocean"; "Forest" = "Forest"; @@ -493,4 +493,4 @@ "Slash Commands & Shortcuts Tabs" = "Slash Commands & Shortcuts Tabs"; "The other two Settings tabs manage per-project slash commands and shortcut buttons. Slash command JSON import/export includes custom commands only; shortcut JSON import/export backs up the shortcut set." = "The other two Settings tabs manage per-project slash commands and shortcut buttons. Slash command JSON import/export includes custom commands only; shortcut JSON import/export backs up the shortcut set."; "Checking for Updates" = "Checking for Updates"; -"Open Clarc → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch." = "Open Clarc → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch."; +"Open RxCode → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch." = "Open RxCode → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch."; diff --git a/Clarc/Resources/ko.lproj/Localizable.strings b/RxCode/Resources/ko.lproj/Localizable.strings similarity index 93% rename from Clarc/Resources/ko.lproj/Localizable.strings rename to RxCode/Resources/ko.lproj/Localizable.strings index e1d88698..b3d77466 100644 --- a/Clarc/Resources/ko.lproj/Localizable.strings +++ b/RxCode/Resources/ko.lproj/Localizable.strings @@ -1,6 +1,6 @@ /* Korean localization — main app */ -// MARK: - ClarcApp +// MARK: - RxCodeApp "New Chat" = "새 채팅"; "Check for Updates..." = "업데이트 확인..."; "Theme" = "테마"; @@ -33,7 +33,7 @@ "font.size.hint" = "인터페이스와 메시지 폰트 크기를 각각 조절합니다."; "Notifications" = "알림"; "Notify when response completes" = "응답 완료 시 알림"; -"Sends a system notification while Clarc is in the background." = "Clarc가 백그라운드에 있을 때 시스템 알림을 보냅니다."; +"Sends a system notification while RxCode is in the background." = "RxCode가 백그라운드에 있을 때 시스템 알림을 보냅니다."; "Open Source" = "오픈 소스"; "Response complete" = "응답 완료"; @@ -107,7 +107,7 @@ "Projects" = "프로젝트"; "Rename Project" = "프로젝트 이름 변경"; "Delete Project" = "프로젝트 삭제"; -"This will remove the project from Clarc. The files on disk will not be deleted." = "Clarc에서 프로젝트가 제거됩니다. 디스크의 파일은 삭제되지 않습니다."; +"This will remove the project from RxCode. The files on disk will not be deleted." = "RxCode에서 프로젝트가 제거됩니다. 디스크의 파일은 삭제되지 않습니다."; "Project name" = "프로젝트 이름"; // MARK: - HistoryListView @@ -189,7 +189,7 @@ "Install command:" = "설치 커맨드:"; "Check Again" = "다시 확인"; "Get Started" = "시작하기"; -"Share session history with the terminal CLI in ~/.claude/projects/. Turn off to keep Clarc sessions separate. You can change this later in Settings." = "~/.claude/projects/의 터미널 CLI와 세션 기록을 공유합니다. 끄면 Clarc 세션을 분리해서 사용합니다. 이후 설정에서 변경할 수 있습니다."; +"Share session history with the terminal CLI in ~/.claude/projects/. Turn off to keep RxCode sessions separate. You can change this later in Settings." = "~/.claude/projects/의 터미널 CLI와 세션 기록을 공유합니다. 끄면 RxCode 세션을 분리해서 사용합니다. 이후 설정에서 변경할 수 있습니다."; // MARK: - GitHubLoginView "Connect GitHub to fetch your repo list and add projects with a single click." = "GitHub를 연결하면 저장소 목록을 가져오고 한 번의 클릭으로 프로젝트를 추가할 수 있습니다."; @@ -236,7 +236,7 @@ // MARK: - UserManualView // Topic titles -"About Clarc" = "Clarc 소개"; +"About RxCode" = "RxCode 소개"; "Project Management" = "프로젝트 관리"; "Chat Basics" = "채팅 기본"; "Keyboard Shortcuts" = "단축키"; @@ -249,7 +249,7 @@ "Status Line" = "상태 표시줄"; // Overview -"What is Clarc?" = "Clarc란?"; +"What is RxCode?" = "RxCode란?"; "A native macOS desktop client for the Claude Code CLI. Use all Claude Code features via a polished GUI without needing a terminal." = "Claude Code CLI를 위한 네이티브 macOS 데스크톱 클라이언트입니다. 터미널 없이 세련된 GUI로 Claude Code의 모든 기능을 사용할 수 있습니다."; "Main Layout" = "기본 레이아웃"; "The left sidebar has History and Files tabs. Project tabs appear at the top of the chat area — click to switch projects. The right inspector panel contains the Terminal and Memo tabs." = "왼쪽 사이드바에는 기록 및 파일 탭이 있습니다. 채팅 영역 상단에 프로젝트 탭이 표시되며 클릭하여 전환할 수 있습니다. 오른쪽 인스펙터 패널에는 터미널과 메모 탭이 있습니다."; @@ -346,7 +346,7 @@ "Attaching Files" = "파일 첨부"; "Click the clip icon to the left of the input field, or drag and drop files onto the input field. When dragging, the input field border highlights in the accent color to show the drop zone." = "입력창 왼쪽의 클립 아이콘을 클릭하거나 파일을 입력창으로 드래그하세요. 드래그 중에는 입력창 테두리가 강조 색상으로 표시됩니다."; "Clipboard Detection" = "클립보드 감지"; -"Pasting (⌘V) is smart — Clarc detects what's in the clipboard and handles it automatically." = "붙여넣기(⌘V)가 스마트합니다 — Clarc가 클립보드 내용을 자동으로 감지하여 처리합니다."; +"Pasting (⌘V) is smart — RxCode detects what's in the clipboard and handles it automatically." = "붙여넣기(⌘V)가 스마트합니다 — RxCode가 클립보드 내용을 자동으로 감지하여 처리합니다."; "Image data (PNG/TIFF) → attached as an image" = "이미지 데이터(PNG/TIFF) → 이미지로 첨부"; "File path → attached as a file" = "파일 경로 → 파일로 첨부"; "URL → attached as a URL reference" = "URL → URL 참조로 첨부"; @@ -384,12 +384,12 @@ "The exit code is shown at the bottom — \"exit 0\" means the command completed successfully." = "하단에 종료 코드가 표시됩니다 — \"exit 0\"은 커맨드가 성공적으로 완료되었음을 의미합니다."; // GitHub Integration -"Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to Clarc with one click." = "사이드바 상단의 GitHub 마크 버튼을 클릭하여 GitHub 패널을 열어보세요. GitHub 계정을 연결하면 저장소 목록이 표시되고 한 번의 클릭으로 Clarc에 추가할 수 있습니다."; +"Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to RxCode with one click." = "사이드바 상단의 GitHub 마크 버튼을 클릭하여 GitHub 패널을 열어보세요. GitHub 계정을 연결하면 저장소 목록이 표시되고 한 번의 클릭으로 RxCode에 추가할 수 있습니다."; "Adding a Repository" = "저장소 추가"; -"Search for a repository by name, then click Add. Clarc clones it automatically and opens it as a new project." = "이름으로 저장소를 검색한 후 추가 버튼을 클릭하세요. Clarc가 자동으로 클론하고 새 프로젝트로 엽니다."; +"Search for a repository by name, then click Add. RxCode clones it automatically and opens it as a new project." = "이름으로 저장소를 검색한 후 추가 버튼을 클릭하세요. RxCode가 자동으로 클론하고 새 프로젝트로 엽니다."; "Private repository" = "비공개 저장소"; "Public repository" = "공개 저장소"; -"Already added to Clarc" = "이미 Clarc에 추가됨"; +"Already added to RxCode" = "이미 RxCode에 추가됨"; // Skill Marketplace "Click the brain icon (🧠) in the toolbar to browse the MCP plugin catalog published on Anthropic's GitHub. Plugins can be filtered by category or searched by name, description, or author." = "툴바의 🧠 아이콘을 클릭하면 Anthropic GitHub에 게시된 MCP 플러그인 카탈로그를 탐색할 수 있습니다. 카테고리별 필터링 또는 이름, 설명, 작성자로 검색이 가능합니다."; @@ -478,7 +478,7 @@ // Settings & Themes topic "Settings & Themes" = "설정 및 테마"; "Opening Settings" = "설정 열기"; -"Choose Clarc → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into four tabs: General, Message, Slash Commands, and Shortcuts." = "메뉴 막대에서 Clarc → 설정을 선택하거나 ⌘,를 눌러 설정 창을 엽니다. 설정은 일반, 메시지, 슬래시 커맨드, 단축키 네 개의 탭으로 구성되어 있습니다."; +"Choose RxCode → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into four tabs: General, Message, Slash Commands, and Shortcuts." = "메뉴 막대에서 RxCode → 설정을 선택하거나 ⌘,를 눌러 설정 창을 엽니다. 설정은 일반, 메시지, 슬래시 커맨드, 단축키 네 개의 탭으로 구성되어 있습니다."; "General Tab" = "일반 탭"; "The General tab configures session defaults. Changes apply to newly created sessions — existing sessions keep their current values." = "일반 탭에서 세션 기본값을 설정합니다. 변경사항은 새로 생성되는 세션에 적용되며, 기존 세션은 현재 값을 유지합니다."; "Accent color palette (Terracotta, Ocean, Forest, Lavender, Midnight, Amber)" = "강조 색상 팔레트 (Terracotta, Ocean, Forest, Lavender, Midnight, Amber)"; @@ -487,7 +487,7 @@ "Claude model used when starting a new session" = "새 세션 시작 시 사용할 Claude 모델"; "Permission mode applied to new sessions" = "새 세션에 적용될 권한 모드"; "Reasoning effort applied to new sessions" = "새 세션에 적용될 추론 작업량"; -"Show a system notification when a response completes while Clarc is in the background" = "Clarc가 백그라운드에 있을 때 응답이 완료되면 시스템 알림 표시"; +"Show a system notification when a response completes while RxCode is in the background" = "RxCode가 백그라운드에 있을 때 응답이 완료되면 시스템 알림 표시"; "Model, permission mode, and effort can also be overridden per session from the toolbar — the chosen value sticks to that session." = "모델, 권한 모드, 작업량은 툴바에서 세션별로도 변경할 수 있으며, 선택한 값은 해당 세션에만 적용됩니다."; "Message Tab" = "메시지 탭"; "The Message tab controls chat display and attachment behavior." = "메시지 탭에서 채팅 표시 및 첨부 파일 동작을 설정합니다."; @@ -497,7 +497,7 @@ "Automatically show a preview chip when image data is pasted" = "이미지 데이터 붙여넣기 시 자동으로 미리보기 칩을 표시합니다."; "Automatically convert long text (200+ characters) into an attachment chip when pasted" = "긴 텍스트(200자 이상) 붙여넣기 시 자동으로 첨부 파일 칩으로 변환합니다."; "Themes" = "테마"; -"Clarc ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose." = "Clarc는 6가지 강조 색상 테마를 제공합니다. 테마 선택기에서 바로 각 테마를 미리볼 수 있으며, 선택과 동시에 앱 전체 색상이 즉시 바뀝니다."; +"RxCode ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose." = "RxCode는 6가지 강조 색상 테마를 제공합니다. 테마 선택기에서 바로 각 테마를 미리볼 수 있으며, 선택과 동시에 앱 전체 색상이 즉시 바뀝니다."; "Terracotta" = "테라코타"; "Ocean" = "오션"; "Forest" = "포레스트"; @@ -513,4 +513,4 @@ "Slash Commands & Shortcuts Tabs" = "슬래시 커맨드 및 단축키 탭"; "The Slash Commands tab manages built-in and custom commands — edit, enable/disable, add, or delete. JSON import/export covers custom commands only. The Shortcuts tab manages shortcut buttons with JSON import/export support." = "슬래시 커맨드 탭에서는 기본 및 커스텀 커맨드를 관리합니다 — 편집, 활성화/비활성화, 추가, 삭제가 가능합니다. JSON 가져오기/내보내기는 커스텀 커맨드만 지원합니다. 단축키 탭에서는 단축 버튼을 관리하며 JSON 가져오기/내보내기를 지원합니다."; "Checking for Updates" = "업데이트 확인"; -"Open Clarc → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch." = "Clarc → 업데이트 확인…을 통해 Sparkle 업데이트 확인을 수동으로 실행할 수 있습니다. 앱 시작 시에도 자동으로 확인됩니다."; +"Open RxCode → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch." = "RxCode → 업데이트 확인…을 통해 Sparkle 업데이트 확인을 수동으로 실행할 수 있습니다. 앱 시작 시에도 자동으로 확인됩니다."; diff --git a/Clarc/Services/BashSafety.swift b/RxCode/Services/BashSafety.swift similarity index 100% rename from Clarc/Services/BashSafety.swift rename to RxCode/Services/BashSafety.swift diff --git a/Clarc/Services/ClaudeService.swift b/RxCode/Services/ClaudeService.swift similarity index 88% rename from Clarc/Services/ClaudeService.swift rename to RxCode/Services/ClaudeService.swift index 923d2ebf..6e33aaa2 100644 --- a/Clarc/Services/ClaudeService.swift +++ b/RxCode/Services/ClaudeService.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import os // MARK: - ClaudeService @@ -305,7 +305,7 @@ actor ClaudeService { /// call so it doesn't inherit user-level MCP servers. Returns nil on I/O failure; /// caller falls back to the default config. private func writeEmptyMCPConfig() -> String? { - let dir = FileManager.default.temporaryDirectory.appendingPathComponent("Clarc", isDirectory: true) + let dir = FileManager.default.temporaryDirectory.appendingPathComponent("RxCode", isDirectory: true) let path = dir.appendingPathComponent("empty-mcp.json") do { try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) @@ -449,42 +449,89 @@ actor ClaudeService { /// children die alongside the parent. Escalate to SIGKILL after 5 seconds. func cancel(streamId: UUID) { guard let pgid = streamPGIDs[streamId] else { return } + let escapees = Self.descendantPids(of: pgid) - logger.info("Sending SIGINT to claude pgid \(pgid) (stream=\(streamId))") + logger.info("Sending SIGINT to claude pgid \(pgid) escapees=\(escapees) (stream=\(streamId))") killpg(pgid, SIGINT) + for pid in escapees { kill(pid, SIGINT) } let log = logger - Task { + Task.detached { try? await Task.sleep(nanoseconds: 5_000_000_000) - // killpg on a fully-dead group returns ESRCH — harmless. Send unconditionally - // to cover any subagent that ignored SIGINT. - if killpg(pgid, SIGKILL) == 0 { - log.warning("Process group \(pgid) still alive after 5 s — sent SIGKILL") - } + // killpg/kill on a fully-dead target returns ESRCH — harmless. Send unconditionally + // to cover any subagent that ignored SIGINT or escaped the process group. + killpg(pgid, SIGKILL) + for pid in escapees { kill(pid, SIGKILL) } + log.debug("Cancel SIGKILL pgid=\(pgid) escapees=\(escapees)") } } /// Thread-finished sweep. Called after the `result` event (and from the exit /// handler) to guarantee no subagent process outlives the parent CLI. /// - /// The parent `claude` will already be on its way out by the time we call this - /// (`closeStdin` was just invoked), so SIGTERM to the group is a no-op race - /// for the leader and a real kill for any subagent that the parent failed to - /// reap on exit. SIGKILL escalation 2 s later handles the rare case where a - /// subagent ignores SIGTERM. + /// MCP servers and Node children spawned `detached: true` may escape the + /// parent's process group (via `setsid`/`setpgid`), so `killpg` alone is not + /// enough. Snapshot the descendant tree before signaling and SIGKILL each + /// escapee individually as the safety net — running in a detached Task so + /// actor contention can't delay the escalation. func finalize(streamId: UUID) { guard let pgid = streamPGIDs[streamId] else { return } + let escapees = Self.descendantPids(of: pgid) - logger.info("Finalizing stream — killpg(SIGTERM) pgid=\(pgid) stream=\(streamId)") + logger.info("Finalizing stream — pgid=\(pgid) escapees=\(escapees) stream=\(streamId)") killpg(pgid, SIGTERM) + for pid in escapees { kill(pid, SIGTERM) } let log = logger - Task { - try? await Task.sleep(nanoseconds: 2_000_000_000) - if killpg(pgid, SIGKILL) == 0 { - log.debug("Finalize SIGKILL pgid=\(pgid) reaped stragglers") - } + Task.detached { + try? await Task.sleep(nanoseconds: 1_500_000_000) + killpg(pgid, SIGKILL) + for pid in escapees { kill(pid, SIGKILL) } + log.debug("Finalize SIGKILL pgid=\(pgid) escapees=\(escapees)") + } + } + + /// Walk the process tree rooted at `root` via `ps -Ao pid,ppid` and return + /// every descendant pid (not including `root`). Used to find subagents that + /// escaped the original process group so they can be signaled directly. + /// + /// Must be called *before* the root dies — once the root exits, its children + /// are reparented to launchd (ppid=1) and the link back to the original + /// process is lost. + private static func descendantPids(of root: pid_t) -> [pid_t] { + let proc = Process() + proc.executableURL = URL(fileURLWithPath: "/bin/ps") + proc.arguments = ["-Ao", "pid,ppid"] + let pipe = Pipe() + proc.standardOutput = pipe + proc.standardError = FileHandle.nullDevice + do { + try proc.run() + proc.waitUntilExit() + } catch { + return [] } + let data = pipe.fileHandleForReading.readDataToEndOfFile() + guard let text = String(data: data, encoding: .utf8) else { return [] } + + var childrenByParent: [pid_t: [pid_t]] = [:] + for line in text.split(separator: "\n").dropFirst() { + let parts = line.split(whereSeparator: { $0 == " " || $0 == "\t" }) + guard parts.count >= 2, + let pid = pid_t(parts[0]), + let ppid = pid_t(parts[1]) else { continue } + childrenByParent[ppid, default: []].append(pid) + } + + var result: [pid_t] = [] + var queue: [pid_t] = [root] + while !queue.isEmpty { + let next = queue.removeFirst() + guard let children = childrenByParent[next] else { continue } + result.append(contentsOf: children) + queue.append(contentsOf: children) + } + return result } // MARK: - Private Helpers @@ -827,15 +874,33 @@ actor ClaudeService { // MARK: - Cleanup - /// Tear down any resources held by the service. + /// Tear down any resources held by the service. Called on app termination + /// so spawned `claude` CLIs (and any subagent or MCP server children they + /// hold open) don't outlive the host app and linger in Activity Monitor. func cleanup() { inactivityTimer?.cancel() inactivityTimer = nil - // killpg sweeps the parent CLI + every subagent in its process group. + // Snapshot every descendant tree before signaling so escaped subagents + // (MCP servers detached via setsid) still get the SIGKILL pass. + var allEscapees: [pid_t] = [] + for (_, pgid) in streamPGIDs { + allEscapees.append(contentsOf: Self.descendantPids(of: pgid)) + } + for (_, pgid) in streamPGIDs { killpg(pgid, SIGTERM) } + for pid in allEscapees { kill(pid, SIGTERM) } + + // Synchronous SIGKILL escalation — the host process is exiting, so a + // detached Task wouldn't have time to fire. + usleep(200_000) + for (_, pgid) in streamPGIDs { + killpg(pgid, SIGKILL) + } + for pid in allEscapees { kill(pid, SIGKILL) } + streamPGIDs.removeAll() for (_, handle) in stdinHandles { try? handle.close() diff --git a/Clarc/Services/GitHubService.swift b/RxCode/Services/GitHubService.swift similarity index 99% rename from Clarc/Services/GitHubService.swift rename to RxCode/Services/GitHubService.swift index af2340f9..6a3ea9cc 100644 --- a/Clarc/Services/GitHubService.swift +++ b/RxCode/Services/GitHubService.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import os import Security @@ -232,7 +232,7 @@ actor GitHubService { func registerSSHKey(_ publicKey: String) async throws { let body = try JSONSerialization.data( withJSONObject: [ - "title": "Clarc (\(Host.current().localizedName ?? "Mac"))", + "title": "RxCode (\(Host.current().localizedName ?? "Mac"))", "key": publicKey ] ) diff --git a/Clarc/Services/MCPService.swift b/RxCode/Services/MCPService.swift similarity index 99% rename from Clarc/Services/MCPService.swift rename to RxCode/Services/MCPService.swift index 54149e08..3a8f86da 100644 --- a/Clarc/Services/MCPService.swift +++ b/RxCode/Services/MCPService.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import os // MARK: - MCPService @@ -592,7 +592,7 @@ actor MCPService { "protocolVersion": "2025-06-18", "capabilities": [String: Any](), "clientInfo": [ - "name": "Clarc", + "name": "RxCode", "version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0" ] ] diff --git a/Clarc/Services/MarketplaceService.swift b/RxCode/Services/MarketplaceService.swift similarity index 99% rename from Clarc/Services/MarketplaceService.swift rename to RxCode/Services/MarketplaceService.swift index 7d54e3ab..d3810dc2 100644 --- a/Clarc/Services/MarketplaceService.swift +++ b/RxCode/Services/MarketplaceService.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import os /// Fetches the marketplace catalog from Anthropic's GitHub repositories diff --git a/Clarc/Services/NotificationService.swift b/RxCode/Services/NotificationService.swift similarity index 98% rename from Clarc/Services/NotificationService.swift rename to RxCode/Services/NotificationService.swift index a116195d..10f99e39 100644 --- a/Clarc/Services/NotificationService.swift +++ b/RxCode/Services/NotificationService.swift @@ -9,7 +9,7 @@ import os.log final class NotificationService: NSObject { static let shared = NotificationService() - private let logger = Logger(subsystem: "com.idealapp.Clarc", category: "Notification") + private let logger = Logger(subsystem: "com.idealapp.RxCode", category: "Notification") private var didRequestAuthorization = false /// Invoked on the main actor when the user clicks a notification. diff --git a/Clarc/Services/PermissionServer.swift b/RxCode/Services/PermissionServer.swift similarity index 99% rename from Clarc/Services/PermissionServer.swift rename to RxCode/Services/PermissionServer.swift index 6d3029fd..ac55bca8 100644 --- a/Clarc/Services/PermissionServer.swift +++ b/RxCode/Services/PermissionServer.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import Network import os @@ -477,7 +477,7 @@ actor PermissionServer { private static var bashAllowlistURL: URL { let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! - return appSupport.appendingPathComponent("Clarc").appendingPathComponent("bash_allowlist.json") + return appSupport.appendingPathComponent("RxCode").appendingPathComponent("bash_allowlist.json") } private func loadBashAllowlistIfNeeded() async { diff --git a/Clarc/Services/PersistenceService.swift b/RxCode/Services/PersistenceService.swift similarity index 96% rename from Clarc/Services/PersistenceService.swift rename to RxCode/Services/PersistenceService.swift index 35164bc8..7da519ac 100644 --- a/Clarc/Services/PersistenceService.swift +++ b/RxCode/Services/PersistenceService.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import os actor PersistenceService { @@ -32,7 +32,7 @@ actor PersistenceService { func saveSession(_ session: ChatSession, persistTitle: Bool = false) async throws { switch session.origin { case .cliBacked: - // CLI owns the message log (jsonl). We only persist the Clarc-only + // CLI owns the message log (jsonl). We only persist the RxCode-only // sidecar — title/pin/model/effort/permissionMode. // // Title rule: the sidecar holds *user-renamed* titles only. Auto @@ -58,7 +58,7 @@ actor PersistenceService { ) ) - case .legacyClarc: + case .legacyRxCode: let dir = baseURL .appendingPathComponent("sessions") .appendingPathComponent(session.projectId.uuidString) @@ -88,15 +88,15 @@ actor PersistenceService { .filter { $0.pathExtension == "json" } .compactMap { url -> ChatSession.Summary? in guard var summary = decode(ChatSession.Summary.self, from: url) else { return nil } - // Files saved before this change may decode as `.legacyClarc` + // Files saved before this change may decode as `.legacyRxCode` // already (default), but be defensive. - if summary.origin != .legacyClarc { + if summary.origin != .legacyRxCode { summary = ChatSession.Summary( id: summary.id, projectId: summary.projectId, title: summary.title, createdAt: summary.createdAt, updatedAt: summary.updatedAt, isPinned: summary.isPinned, model: summary.model, effort: summary.effort, permissionMode: summary.permissionMode, - origin: .legacyClarc + origin: .legacyRxCode ) } return summary @@ -144,11 +144,11 @@ actor PersistenceService { } else { logger.warning("Skipping CLI jsonl delete for \(sessionId, privacy: .public): cwd unavailable") } - // Pre-cli-sync builds wrote a Clarc-side json with the same sid. If + // Pre-cli-sync builds wrote a RxCode-side json with the same sid. If // it survives, the merge in AppState falls back to it after the // jsonl is gone and the entry resurrects on the next reload. try removeLegacySessionFile(projectId: projectId, sessionId: sessionId) - case .legacyClarc: + case .legacyRxCode: try removeLegacySessionFile(projectId: projectId, sessionId: sessionId) } } @@ -166,7 +166,7 @@ actor PersistenceService { /// Loads the full message history. Routes by `origin`: /// - `.cliBacked` → CLI jsonl - /// - `.legacyClarc` → Clarc's per-project json + /// - `.legacyRxCode` → RxCode's per-project json func loadFullSession(summary: ChatSession.Summary, cwd: String) async -> ChatSession? { switch summary.origin { case .cliBacked: @@ -175,7 +175,7 @@ actor PersistenceService { cwd: cwd, projectId: summary.projectId ) - case .legacyClarc: + case .legacyRxCode: return loadLegacySessionSync(projectId: summary.projectId, sessionId: summary.id) } } diff --git a/Clarc/Services/RateLimitService.swift b/RxCode/Services/RateLimitService.swift similarity index 99% rename from Clarc/Services/RateLimitService.swift rename to RxCode/Services/RateLimitService.swift index 9914fa86..c4aa3b5f 100644 --- a/Clarc/Services/RateLimitService.swift +++ b/RxCode/Services/RateLimitService.swift @@ -1,5 +1,5 @@ import Foundation -import ClarcCore +import RxCodeCore import os actor RateLimitService { diff --git a/Clarc/Services/ThreadStore.swift b/RxCode/Services/ThreadStore.swift similarity index 99% rename from Clarc/Services/ThreadStore.swift rename to RxCode/Services/ThreadStore.swift index e28a4830..50ff2ce7 100644 --- a/Clarc/Services/ThreadStore.swift +++ b/RxCode/Services/ThreadStore.swift @@ -1,6 +1,6 @@ import Foundation import SwiftData -import ClarcCore +import RxCodeCore import os /// MainActor-scoped wrapper around the SwiftData store for `ChatThread`. @@ -37,7 +37,7 @@ final class ThreadStore { let fm = FileManager.default let appSupport = (try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)) ?? URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Application Support") - let dir = appSupport.appendingPathComponent("Clarc", isDirectory: true) + let dir = appSupport.appendingPathComponent("RxCode", isDirectory: true) try? fm.createDirectory(at: dir, withIntermediateDirectories: true) return dir.appendingPathComponent("threads.store") } diff --git a/Clarc/Services/UpdateService.swift b/RxCode/Services/UpdateService.swift similarity index 100% rename from Clarc/Services/UpdateService.swift rename to RxCode/Services/UpdateService.swift diff --git a/Clarc/Utilities/KeychainHelper.swift b/RxCode/Utilities/KeychainHelper.swift similarity index 100% rename from Clarc/Utilities/KeychainHelper.swift rename to RxCode/Utilities/KeychainHelper.swift diff --git a/Clarc/Utilities/SSHKeyManager.swift b/RxCode/Utilities/SSHKeyManager.swift similarity index 97% rename from Clarc/Utilities/SSHKeyManager.swift rename to RxCode/Utilities/SSHKeyManager.swift index 3885a80e..ec88aa89 100644 --- a/Clarc/Utilities/SSHKeyManager.swift +++ b/RxCode/Utilities/SSHKeyManager.swift @@ -1,7 +1,7 @@ import Foundation import os -/// Manages an ed25519 SSH key dedicated to Clarc for GitHub access. +/// Manages an ed25519 SSH key dedicated to RxCode for GitHub access. /// /// The key pair lives at `~/.ssh/claudework_ed25519` (private) and /// `~/.ssh/claudework_ed25519.pub` (public). All shell operations use @@ -66,7 +66,7 @@ actor SSHKeyManager { } /// Add a `Host github.com` entry to `~/.ssh/config` that points at the - /// Clarc key, but only if such an entry is not already present. + /// RxCode key, but only if such an entry is not already present. func configureSSHConfig() async throws { let configPath = "\(sshDirectory)/config" @@ -84,7 +84,7 @@ actor SSHKeyManager { let entry = """ - # Clarc — GitHub access + # RxCode — GitHub access Host github.com-claudework HostName github.com User git @@ -113,7 +113,7 @@ actor SSHKeyManager { } } - logger.info("Added Clarc SSH config entry.") + logger.info("Added RxCode SSH config entry.") } /// Run `ssh-keyscan github.com` and append the result to `~/.ssh/known_hosts`. diff --git a/Clarc/Views/Chat/BranchPickerChip.swift b/RxCode/Views/Chat/BranchPickerChip.swift similarity index 97% rename from Clarc/Views/Chat/BranchPickerChip.swift rename to RxCode/Views/Chat/BranchPickerChip.swift index 1a7e4dec..cf02d5b5 100644 --- a/Clarc/Views/Chat/BranchPickerChip.swift +++ b/RxCode/Views/Chat/BranchPickerChip.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - BranchPickerChip @@ -98,7 +98,7 @@ struct CreateBranchSheet: View { let currentBranch: String? let onCreated: () -> Void - @State private var branchText: String = "clarc/" + @State private var branchText: String = "rxcode/" @State private var isCreating = false @State private var errorMessage: String? @FocusState private var isFocused: Bool @@ -107,7 +107,7 @@ struct CreateBranchSheet: View { let trimmed = branchText.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { return "Branch name is required." } if trimmed.hasSuffix("/") { return "Branch name cannot end with “/”." } - if trimmed == "clarc/" { return "Add a branch name after the prefix." } + if trimmed == "rxcode/" { return "Add a branch name after the prefix." } return nil } @@ -142,7 +142,7 @@ struct CreateBranchSheet: View { } } - TextField("clarc/feature-name", text: $branchText) + TextField("rxcode/feature-name", text: $branchText) .textFieldStyle(.plain) .font(.system(size: ClaudeTheme.size(14), design: .monospaced)) .padding(.horizontal, 10) diff --git a/Clarc/Views/Chat/GitHubSheet.swift b/RxCode/Views/Chat/GitHubSheet.swift similarity index 99% rename from Clarc/Views/Chat/GitHubSheet.swift rename to RxCode/Views/Chat/GitHubSheet.swift index c650ee65..2d72df51 100644 --- a/Clarc/Views/Chat/GitHubSheet.swift +++ b/RxCode/Views/Chat/GitHubSheet.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct GitHubSheet: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/Chat/RecentChatsSuggestionList.swift b/RxCode/Views/Chat/RecentChatsSuggestionList.swift similarity index 99% rename from Clarc/Views/Chat/RecentChatsSuggestionList.swift rename to RxCode/Views/Chat/RecentChatsSuggestionList.swift index 569e2218..3d0e616a 100644 --- a/Clarc/Views/Chat/RecentChatsSuggestionList.swift +++ b/RxCode/Views/Chat/RecentChatsSuggestionList.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Shown below the input box on empty state so the user can quickly resume a recent chat. struct RecentChatsSuggestionList: View { diff --git a/Clarc/Views/Chat/SkillMarketView.swift b/RxCode/Views/Chat/SkillMarketView.swift similarity index 99% rename from Clarc/Views/Chat/SkillMarketView.swift rename to RxCode/Views/Chat/SkillMarketView.swift index e3b55427..7ba9b5c6 100644 --- a/Clarc/Views/Chat/SkillMarketView.swift +++ b/RxCode/Views/Chat/SkillMarketView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Skill marketplace panel — displayed as an overlay or embedded in a settings tab. struct SkillMarketView: View { diff --git a/Clarc/Views/Inspector/InspectorContentView.swift b/RxCode/Views/Inspector/InspectorContentView.swift similarity index 99% rename from Clarc/Views/Inspector/InspectorContentView.swift rename to RxCode/Views/Inspector/InspectorContentView.swift index b7221bd0..749499f3 100644 --- a/Clarc/Views/Inspector/InspectorContentView.swift +++ b/RxCode/Views/Inspector/InspectorContentView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - InspectorContentView diff --git a/Clarc/Views/Inspector/RightInspectorPanel.swift b/RxCode/Views/Inspector/RightInspectorPanel.swift similarity index 99% rename from Clarc/Views/Inspector/RightInspectorPanel.swift rename to RxCode/Views/Inspector/RightInspectorPanel.swift index 69f81e45..96c72ea5 100644 --- a/Clarc/Views/Inspector/RightInspectorPanel.swift +++ b/RxCode/Views/Inspector/RightInspectorPanel.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - RightInspectorPanel diff --git a/Clarc/Views/InspectorMemoPanel.swift b/RxCode/Views/InspectorMemoPanel.swift similarity index 99% rename from Clarc/Views/InspectorMemoPanel.swift rename to RxCode/Views/InspectorMemoPanel.swift index 501e7552..604cf8de 100644 --- a/Clarc/Views/InspectorMemoPanel.swift +++ b/RxCode/Views/InspectorMemoPanel.swift @@ -1,9 +1,9 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore -private let memoArchiveKey = "clarc.memoAttrData" -private let checkboxStateKey = NSAttributedString.Key("clarc.checkboxState") +private let memoArchiveKey = "rxcode.memoAttrData" +private let checkboxStateKey = NSAttributedString.Key("rxcode.checkboxState") private let defaultMemoFont: NSFont = .systemFont(ofSize: 14) private let defaultMemoParagraphStyle: NSParagraphStyle = { diff --git a/RxCode/Views/LoadingView.swift b/RxCode/Views/LoadingView.swift new file mode 100644 index 00000000..5a1ff523 --- /dev/null +++ b/RxCode/Views/LoadingView.swift @@ -0,0 +1,104 @@ +import SwiftUI +import AppKit +import RxCodeCore + +/// Splash shown while `AppState.initialize()` is loading projects, sessions, +/// and warming services. Replaced by the main view as soon as +/// `AppState.isInitialized` flips. +struct LoadingView: View { + @State private var pulse = false + + var body: some View { + VStack(spacing: 20) { + Image(systemName: "sparkle") + .font(.system(size: ClaudeTheme.size(56), weight: .light)) + .foregroundStyle(ClaudeTheme.accent) + .scaleEffect(pulse ? 1.08 : 0.94) + .opacity(pulse ? 1.0 : 0.7) + .animation(.easeInOut(duration: 1.1).repeatForever(autoreverses: true), value: pulse) + + Text("RxCode") + .font(.system(size: ClaudeTheme.size(28), weight: .semibold)) + .foregroundStyle(ClaudeTheme.textPrimary) + + ProgressView() + .controlSize(.small) + .padding(.top, 4) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(ClaudeTheme.background.ignoresSafeArea()) + .toolbarBackground(.hidden, for: .windowToolbar) + .background(TitleBarHider()) + .onAppear { pulse = true } + } +} + +/// Hides the hosting NSWindow's title bar while the loading view is visible, +/// then restores the original chrome when it disappears. +private struct TitleBarHider: NSViewRepresentable { + final class Coordinator { + weak var window: NSWindow? + var savedStyleMask: NSWindow.StyleMask? + var savedTitleVisibility: NSWindow.TitleVisibility? + var savedTitlebarAppearsTransparent: Bool? + var savedStandardButtonHidden: [NSWindow.ButtonType: Bool] = [:] + + func apply(to window: NSWindow) { + guard self.window !== window else { return } + restore() + self.window = window + savedStyleMask = window.styleMask + savedTitleVisibility = window.titleVisibility + savedTitlebarAppearsTransparent = window.titlebarAppearsTransparent + for type in [NSWindow.ButtonType.closeButton, .miniaturizeButton, .zoomButton] { + savedStandardButtonHidden[type] = window.standardWindowButton(type)?.isHidden ?? false + } + + window.styleMask.insert(.fullSizeContentView) + window.titleVisibility = .hidden + window.titlebarAppearsTransparent = true + for type in [NSWindow.ButtonType.closeButton, .miniaturizeButton, .zoomButton] { + window.standardWindowButton(type)?.isHidden = true + } + } + + func restore() { + guard let window else { return } + if let mask = savedStyleMask { window.styleMask = mask } + if let vis = savedTitleVisibility { window.titleVisibility = vis } + if let transparent = savedTitlebarAppearsTransparent { window.titlebarAppearsTransparent = transparent } + for (type, hidden) in savedStandardButtonHidden { + window.standardWindowButton(type)?.isHidden = hidden + } + self.window = nil + savedStyleMask = nil + savedTitleVisibility = nil + savedTitlebarAppearsTransparent = nil + savedStandardButtonHidden.removeAll() + } + + deinit { restore() } + } + + func makeCoordinator() -> Coordinator { Coordinator() } + + func makeNSView(context: Context) -> NSView { + let view = NSView() + DispatchQueue.main.async { [weak view] in + guard let window = view?.window else { return } + context.coordinator.apply(to: window) + } + return view + } + + func updateNSView(_ nsView: NSView, context: Context) { + DispatchQueue.main.async { [weak nsView] in + guard let window = nsView?.window else { return } + context.coordinator.apply(to: window) + } + } + + static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) { + coordinator.restore() + } +} diff --git a/Clarc/Views/MainView.swift b/RxCode/Views/MainView.swift similarity index 95% rename from Clarc/Views/MainView.swift rename to RxCode/Views/MainView.swift index dfbdf222..b7008576 100644 --- a/Clarc/Views/MainView.swift +++ b/RxCode/Views/MainView.swift @@ -1,5 +1,6 @@ -import ClarcChatKit -import ClarcCore +import AppKit +import RxCodeChatKit +import RxCodeCore import SwiftUI import UniformTypeIdentifiers @@ -31,6 +32,15 @@ struct MainView: View { } } + private var navigationTitleText: String { + if let id = windowState.currentSessionId, + let title = appState.allSessionSummaries.first(where: { $0.id == id })?.title, + !title.isEmpty { + return title + } + return windowState.selectedProject?.name ?? "" + } + var body: some View { if !appState.onboardingCompleted { OnboardingView() @@ -77,7 +87,9 @@ struct MainView: View { .onAppear { windowState.focusMode = appState.focusMode } - .toolbar(removing: .title) + .navigationTitle(navigationTitleText) + .toolbarBackground(.hidden, for: .windowToolbar) + .background(UnifiedTitleBarAccessor()) .toolbar { if columnVisibility != .detailOnly { ToolbarItem(placement: .navigation) { @@ -124,7 +136,7 @@ struct MainView: View { VStack(spacing: 0) { ProjectTreeView() } - .background(ClaudeTheme.sidebarBackground) + .background(ClaudeTheme.sidebarBackground.ignoresSafeArea()) .navigationSplitViewColumnWidth(min: 220, ideal: 280, max: 360) .sheet(isPresented: $showGitHubSheet) { GitHubSheet() @@ -216,13 +228,37 @@ struct MainView: View { struct DetailToolbar: View { var body: some View { Color.clear - .toolbarBackground(.hidden, for: .windowToolbar) .toolbar { - ClarcToolbarContent() + RxCodeToolbarContent() } } } +// MARK: - Unified Title Bar + +/// Makes the hosting NSWindow's titlebar transparent and lets content extend +/// under it, so sidebar/detail backgrounds reach the top of the window +/// instead of leaving a separate full-width colored title-bar strip. +struct UnifiedTitleBarAccessor: NSViewRepresentable { + func makeNSView(context: Context) -> NSView { + let view = NSView() + DispatchQueue.main.async { [weak view] in + guard let window = view?.window else { return } + window.styleMask.insert(.fullSizeContentView) + window.titlebarAppearsTransparent = true + } + return view + } + + func updateNSView(_ nsView: NSView, context: Context) { + DispatchQueue.main.async { [weak nsView] in + guard let window = nsView?.window else { return } + window.styleMask.insert(.fullSizeContentView) + window.titlebarAppearsTransparent = true + } + } +} + // MARK: - Project Tab Button (isolated — isSelected passed as value, body reads no @Observable properties) struct ProjectTabButton: View { diff --git a/Clarc/Views/Onboarding/GitHubLoginView.swift b/RxCode/Views/Onboarding/GitHubLoginView.swift similarity index 99% rename from Clarc/Views/Onboarding/GitHubLoginView.swift rename to RxCode/Views/Onboarding/GitHubLoginView.swift index b0d9f0e0..cb38059c 100644 --- a/Clarc/Views/Onboarding/GitHubLoginView.swift +++ b/RxCode/Views/Onboarding/GitHubLoginView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct GitHubLoginView: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/Onboarding/OnboardingView.swift b/RxCode/Views/Onboarding/OnboardingView.swift similarity index 99% rename from Clarc/Views/Onboarding/OnboardingView.swift rename to RxCode/Views/Onboarding/OnboardingView.swift index 69e3619f..2d30fc1c 100644 --- a/Clarc/Views/Onboarding/OnboardingView.swift +++ b/RxCode/Views/Onboarding/OnboardingView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct OnboardingView: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/Permission/PermissionModal.swift b/RxCode/Views/Permission/PermissionModal.swift similarity index 99% rename from Clarc/Views/Permission/PermissionModal.swift rename to RxCode/Views/Permission/PermissionModal.swift index 590a7d01..2c59266c 100644 --- a/Clarc/Views/Permission/PermissionModal.swift +++ b/RxCode/Views/Permission/PermissionModal.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct PermissionModal: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/Permission/PermissionQueueBanner.swift b/RxCode/Views/Permission/PermissionQueueBanner.swift similarity index 99% rename from Clarc/Views/Permission/PermissionQueueBanner.swift rename to RxCode/Views/Permission/PermissionQueueBanner.swift index b1db2713..36fba23a 100644 --- a/Clarc/Views/Permission/PermissionQueueBanner.swift +++ b/RxCode/Views/Permission/PermissionQueueBanner.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Compact pill displayed directly above the chat input bar whenever the CLI /// has pending permission requests. Tapping it surfaces the full diff --git a/Clarc/Views/Permission/QuestionSheetView.swift b/RxCode/Views/Permission/QuestionSheetView.swift similarity index 99% rename from Clarc/Views/Permission/QuestionSheetView.swift rename to RxCode/Views/Permission/QuestionSheetView.swift index e4be0605..0d89f42d 100644 --- a/Clarc/Views/Permission/QuestionSheetView.swift +++ b/RxCode/Views/Permission/QuestionSheetView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Sheet UI for an `AskUserQuestion` tool call. Displays every question in the payload /// behind a navigator of pills, allows the user to answer each (single-select, diff --git a/Clarc/Views/ProjectWindowView.swift b/RxCode/Views/ProjectWindowView.swift similarity index 89% rename from Clarc/Views/ProjectWindowView.swift rename to RxCode/Views/ProjectWindowView.swift index 964e7766..dbe6138f 100644 --- a/Clarc/Views/ProjectWindowView.swift +++ b/RxCode/Views/ProjectWindowView.swift @@ -1,6 +1,6 @@ import SwiftUI -import ClarcCore -import ClarcChatKit +import RxCodeCore +import RxCodeChatKit /// Dedicated project window — shares AppState, WindowState.isProjectWindow = true struct ProjectWindowView: View { @@ -9,6 +9,15 @@ struct ProjectWindowView: View { @State private var inspectorStarted = false @State private var columnVisibility: NavigationSplitViewVisibility = .all + private var navigationTitleText: String { + if let id = windowState.currentSessionId, + let title = appState.allSessionSummaries.first(where: { $0.id == id })?.title, + !title.isEmpty { + return title + } + return windowState.selectedProject?.name ?? "" + } + var body: some View { Group { if windowState.isInitialized { @@ -26,7 +35,7 @@ struct ProjectWindowView: View { .hidden() } .id(appState.themeRevision) - .toolbar(removing: .title) + .navigationTitle(navigationTitleText) .onChange(of: windowState.showInspector) { _, isShowing in if isShowing, !inspectorStarted { inspectorStarted = true } } @@ -79,7 +88,7 @@ struct ProjectWindowView: View { VStack(spacing: 0) { ProjectTreeView() } - .background(ClaudeTheme.sidebarBackground) + .background(ClaudeTheme.sidebarBackground.ignoresSafeArea()) .navigationSplitViewColumnWidth(min: 220, ideal: 280, max: 360) } @@ -135,10 +144,11 @@ struct ProjectWindowView: View { .background(ClaudeTheme.background) } } - .toolbarBackground(.visible, for: .windowToolbar) + .toolbarBackground(.hidden, for: .windowToolbar) .toolbar { - ClarcToolbarContent() + RxCodeToolbarContent() } + .background(UnifiedTitleBarAccessor()) .focusedValue(\.startNewChat) { appState.startNewChat(in: windowState) } diff --git a/Clarc/Views/Settings/MCPServerEditorSheet.swift b/RxCode/Views/Settings/MCPServerEditorSheet.swift similarity index 99% rename from Clarc/Views/Settings/MCPServerEditorSheet.swift rename to RxCode/Views/Settings/MCPServerEditorSheet.swift index 74d0432c..1ed3c773 100644 --- a/Clarc/Views/Settings/MCPServerEditorSheet.swift +++ b/RxCode/Views/Settings/MCPServerEditorSheet.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct MCPServerEditorSheet: View { /// Returns true on success (sheet dismisses), false on failure (stays open). diff --git a/Clarc/Views/Settings/MCPSettingsTab.swift b/RxCode/Views/Settings/MCPSettingsTab.swift similarity index 99% rename from Clarc/Views/Settings/MCPSettingsTab.swift rename to RxCode/Views/Settings/MCPSettingsTab.swift index 7702e282..dd47ea55 100644 --- a/Clarc/Views/Settings/MCPSettingsTab.swift +++ b/RxCode/Views/Settings/MCPSettingsTab.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct MCPSettingsTab: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/SettingsView.swift b/RxCode/Views/SettingsView.swift similarity index 99% rename from Clarc/Views/SettingsView.swift rename to RxCode/Views/SettingsView.swift index 41326f2d..25ebfcd2 100644 --- a/Clarc/Views/SettingsView.swift +++ b/RxCode/Views/SettingsView.swift @@ -1,6 +1,6 @@ import SwiftUI -import ClarcCore -import ClarcChatKit +import RxCodeCore +import RxCodeChatKit // MARK: - Settings Sheet @@ -196,7 +196,7 @@ struct GeneralSettingsTab: View { toggleSection( title: "Notifications", label: "Notify when response completes", - detail: "Sends a system notification while Clarc is in the background.", + detail: "Sends a system notification while RxCode is in the background.", isOn: appState ) } @@ -295,7 +295,7 @@ struct GeneralSettingsTab: View { // MARK: - Source Code Section private var sourceCodeSection: some View { - Link(destination: URL(string: "https://github.com/ttnear/Clarc")!) { + Link(destination: URL(string: "https://github.com/ttnear/RxCode")!) { HStack(spacing: 10) { Image(systemName: "chevron.left.forwardslash.chevron.right") .font(.system(size: ClaudeTheme.size(14))) @@ -304,7 +304,7 @@ struct GeneralSettingsTab: View { Text("Open Source") .font(.system(size: ClaudeTheme.size(13))) .foregroundStyle(.primary) - Text(verbatim: "github.com/ttnear/Clarc") + Text(verbatim: "github.com/ttnear/RxCode") .font(.system(size: ClaudeTheme.size(11))) .foregroundStyle(.secondary) } diff --git a/Clarc/Views/Sidebar/FileInspectorView.swift b/RxCode/Views/Sidebar/FileInspectorView.swift similarity index 99% rename from Clarc/Views/Sidebar/FileInspectorView.swift rename to RxCode/Views/Sidebar/FileInspectorView.swift index 39e1bcb3..158675c8 100644 --- a/Clarc/Views/Sidebar/FileInspectorView.swift +++ b/RxCode/Views/Sidebar/FileInspectorView.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore /// Inspector panel for file preview with syntax highlighting struct FileInspectorView: View { @@ -381,7 +381,7 @@ private struct CodeTextRenderer: NSViewRepresentable { private func registerThemeObserver() { guard themeObserver == nil else { return } themeObserver = NotificationCenter.default.addObserver( - forName: .clarcThemeDidChange, + forName: .rxcodeThemeDidChange, object: nil, queue: .main ) { [weak self] _ in diff --git a/Clarc/Views/Sidebar/FileTreeView.swift b/RxCode/Views/Sidebar/FileTreeView.swift similarity index 99% rename from Clarc/Views/Sidebar/FileTreeView.swift rename to RxCode/Views/Sidebar/FileTreeView.swift index b1088926..a984acd8 100644 --- a/Clarc/Views/Sidebar/FileTreeView.swift +++ b/RxCode/Views/Sidebar/FileTreeView.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore /// View that displays the project folder structure as a tree. struct FileTreeView: View { @@ -482,6 +482,6 @@ struct FileNode: Identifiable, Sendable { } #Preview { - FileTreeView(projectPath: "/Users/jmlee/workspace/Clarc", searchTrigger: .constant(false)) + FileTreeView(projectPath: "/Users/jmlee/workspace/RxCode", searchTrigger: .constant(false)) .frame(width: 280, height: 400) } diff --git a/Clarc/Views/Sidebar/GitHubRepoListView.swift b/RxCode/Views/Sidebar/GitHubRepoListView.swift similarity index 99% rename from Clarc/Views/Sidebar/GitHubRepoListView.swift rename to RxCode/Views/Sidebar/GitHubRepoListView.swift index 133e8a62..c9535cbd 100644 --- a/Clarc/Views/Sidebar/GitHubRepoListView.swift +++ b/RxCode/Views/Sidebar/GitHubRepoListView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct GitHubRepoListView: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/Sidebar/GitStatusView.swift b/RxCode/Views/Sidebar/GitStatusView.swift similarity index 99% rename from Clarc/Views/Sidebar/GitStatusView.swift rename to RxCode/Views/Sidebar/GitStatusView.swift index dec22a99..c216d1c2 100644 --- a/Clarc/Views/Sidebar/GitStatusView.swift +++ b/RxCode/Views/Sidebar/GitStatusView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// View that visually shows the Git status of the project. struct GitStatusView: View { @@ -388,7 +388,7 @@ private func gitCheckoutRemote(branch: RemoteBranch, localBranches: [String], at #Preview { VStack(spacing: 0) { - GitStatusView(projectPath: "/Users/jmlee/workspace/Clarc") + GitStatusView(projectPath: "/Users/jmlee/workspace/RxCode") } .frame(width: 400) } diff --git a/Clarc/Views/Sidebar/HistoryListView.swift b/RxCode/Views/Sidebar/HistoryListView.swift similarity index 99% rename from Clarc/Views/Sidebar/HistoryListView.swift rename to RxCode/Views/Sidebar/HistoryListView.swift index bcab746b..f7129e68 100644 --- a/Clarc/Views/Sidebar/HistoryListView.swift +++ b/RxCode/Views/Sidebar/HistoryListView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct HistoryListView: View { @Environment(AppState.self) private var appState diff --git a/Clarc/Views/Sidebar/MarkdownPreviewView.swift b/RxCode/Views/Sidebar/MarkdownPreviewView.swift similarity index 99% rename from Clarc/Views/Sidebar/MarkdownPreviewView.swift rename to RxCode/Views/Sidebar/MarkdownPreviewView.swift index aeed41cc..f17dfbed 100644 --- a/Clarc/Views/Sidebar/MarkdownPreviewView.swift +++ b/RxCode/Views/Sidebar/MarkdownPreviewView.swift @@ -43,7 +43,7 @@ struct MarkdownPreviewView: NSViewRepresentable {
- + @@ -223,7 +223,7 @@ struct MarkdownPreviewView: NSViewRepresentable { return s; } - document.getElementById('root').innerHTML = parseBlocks(__CLARC_MD__); + document.getElementById('root').innerHTML = parseBlocks(__RXCODE_MD__); })(); """# } diff --git a/Clarc/Views/Sidebar/ProjectChatRow.swift b/RxCode/Views/Sidebar/ProjectChatRow.swift similarity index 93% rename from Clarc/Views/Sidebar/ProjectChatRow.swift rename to RxCode/Views/Sidebar/ProjectChatRow.swift index ae47f9e1..5c96be07 100644 --- a/Clarc/Views/Sidebar/ProjectChatRow.swift +++ b/RxCode/Views/Sidebar/ProjectChatRow.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - ChatStatus @@ -60,6 +60,7 @@ struct ProjectChatRow: View { let onSelect: () -> Void let onRename: () -> Void let onTogglePin: () -> Void + let onToggleArchive: () -> Void let onDelete: () -> Void @State private var isHovered = false @@ -139,6 +140,13 @@ struct ProjectChatRow: View { Label("Pin", systemImage: "pin") } } + Button { onToggleArchive() } label: { + if summary.isArchived { + Label("Unarchive", systemImage: "tray.and.arrow.up") + } else { + Label("Archive", systemImage: "archivebox") + } + } Divider() Button(role: .destructive) { onDelete() } label: { Label("Delete", systemImage: "trash") diff --git a/Clarc/Views/Sidebar/ProjectListView.swift b/RxCode/Views/Sidebar/ProjectListView.swift similarity index 97% rename from Clarc/Views/Sidebar/ProjectListView.swift rename to RxCode/Views/Sidebar/ProjectListView.swift index 3dd1c6aa..b80ea143 100644 --- a/Clarc/Views/Sidebar/ProjectListView.swift +++ b/RxCode/Views/Sidebar/ProjectListView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore import UniformTypeIdentifiers struct ProjectListView: View { @@ -53,7 +53,7 @@ struct ProjectListView: View { projectToDelete = nil } } message: { - Text("This will remove the project from Clarc. The files on disk will not be deleted.") + Text("This will remove the project from RxCode. The files on disk will not be deleted.") } .sheet(item: $projectToRename) { project in RenameProjectSheet(name: $renameText) { diff --git a/Clarc/Views/Sidebar/ProjectTreeView.swift b/RxCode/Views/Sidebar/ProjectTreeView.swift similarity index 96% rename from Clarc/Views/Sidebar/ProjectTreeView.swift rename to RxCode/Views/Sidebar/ProjectTreeView.swift index cf9d8393..a95a3187 100644 --- a/Clarc/Views/Sidebar/ProjectTreeView.swift +++ b/RxCode/Views/Sidebar/ProjectTreeView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore // MARK: - ProjectTreeView @@ -63,7 +63,7 @@ struct ProjectTreeView: View { } Button("Cancel", role: .cancel) { deleteProject = nil } } message: { - Text("This will remove the project from Clarc. The files on disk will not be deleted.") + Text("This will remove the project from RxCode. The files on disk will not be deleted.") } .alert("Rename Session", isPresented: Binding( get: { renameSession != nil }, @@ -399,6 +399,16 @@ private struct ProjectChatsList: View { onTogglePin: { Task { await appState.togglePinSession(summary.makeSession()) } }, + onToggleArchive: { + let session = summary.makeSession() + Task { + if summary.isArchived { + await appState.unarchiveSession(session, in: windowState) + } else { + await appState.archiveSession(session, in: windowState) + } + } + }, onDelete: { onDeleteSession(summary.makeSession()) } diff --git a/Clarc/Views/Sidebar/TypewriterTitleText.swift b/RxCode/Views/Sidebar/TypewriterTitleText.swift similarity index 99% rename from Clarc/Views/Sidebar/TypewriterTitleText.swift rename to RxCode/Views/Sidebar/TypewriterTitleText.swift index 17c50060..5d6a4896 100644 --- a/Clarc/Views/Sidebar/TypewriterTitleText.swift +++ b/RxCode/Views/Sidebar/TypewriterTitleText.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore /// Animates a text swap by erasing the current value character-by-character /// (cursor moving right→left) and then typing the new value left→right. diff --git a/Clarc/Views/Terminal/TerminalView.swift b/RxCode/Views/Terminal/TerminalView.swift similarity index 99% rename from Clarc/Views/Terminal/TerminalView.swift rename to RxCode/Views/Terminal/TerminalView.swift index 12c5e5ae..5aa3a6e8 100644 --- a/Clarc/Views/Terminal/TerminalView.swift +++ b/RxCode/Views/Terminal/TerminalView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore import SwiftTerm // MARK: - TerminalProcess diff --git a/Clarc/Views/Toolbar/ExternalEditorMenu.swift b/RxCode/Views/Toolbar/ExternalEditorMenu.swift similarity index 99% rename from Clarc/Views/Toolbar/ExternalEditorMenu.swift rename to RxCode/Views/Toolbar/ExternalEditorMenu.swift index 191ea10d..bc4323cb 100644 --- a/Clarc/Views/Toolbar/ExternalEditorMenu.swift +++ b/RxCode/Views/Toolbar/ExternalEditorMenu.swift @@ -1,6 +1,6 @@ import SwiftUI import AppKit -import ClarcCore +import RxCodeCore // MARK: - ExternalEditor diff --git a/Clarc/Views/Toolbar/ClarcToolbar.swift b/RxCode/Views/Toolbar/RxCodeToolbar.swift similarity index 97% rename from Clarc/Views/Toolbar/ClarcToolbar.swift rename to RxCode/Views/Toolbar/RxCodeToolbar.swift index dae5124b..9c538fa9 100644 --- a/Clarc/Views/Toolbar/ClarcToolbar.swift +++ b/RxCode/Views/Toolbar/RxCodeToolbar.swift @@ -1,15 +1,15 @@ import AppKit -import ClarcCore +import RxCodeCore import SwiftUI -// MARK: - ClarcToolbar +// MARK: - RxCodeToolbar /// Shared trailing toolbar group: New Chat, Open in Editor, Terminal, /// Memo, Inspector toggle, Settings. /// /// Wrapped in an isolated struct so toolbar reads do not trigger NSToolbar /// re-layout when `selectedProject` changes. -struct ClarcToolbarContent: ToolbarContent { +struct RxCodeToolbarContent: ToolbarContent { @Environment(AppState.self) private var appState @Environment(WindowState.self) private var windowState @Environment(\.openSettings) private var openSettings diff --git a/Clarc/Views/Toolbar/TodoProgressToolbarItem.swift b/RxCode/Views/Toolbar/TodoProgressToolbarItem.swift similarity index 97% rename from Clarc/Views/Toolbar/TodoProgressToolbarItem.swift rename to RxCode/Views/Toolbar/TodoProgressToolbarItem.swift index 993d66a4..57e17c42 100644 --- a/Clarc/Views/Toolbar/TodoProgressToolbarItem.swift +++ b/RxCode/Views/Toolbar/TodoProgressToolbarItem.swift @@ -1,6 +1,6 @@ import SwiftUI -import ClarcCore -import ClarcChatKit +import RxCodeCore +import RxCodeChatKit /// Toolbar pill rendering the current session's TodoWrite progress. /// diff --git a/Clarc/Views/UserManualView.swift b/RxCode/Views/UserManualView.swift similarity index 97% rename from Clarc/Views/UserManualView.swift rename to RxCode/Views/UserManualView.swift index d26b3338..8e708fa7 100644 --- a/Clarc/Views/UserManualView.swift +++ b/RxCode/Views/UserManualView.swift @@ -1,5 +1,5 @@ import SwiftUI -import ClarcCore +import RxCodeCore struct UserManualView: View { @Environment(\.dismiss) private var dismiss @@ -184,7 +184,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { var title: String { switch self { - case .overview: "About Clarc" + case .overview: "About RxCode" case .projects: "Project Management" case .chat: "Chat Basics" case .shortcuts: "Keyboard Shortcuts" @@ -223,7 +223,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { case .overview: [ ManualSection( - title: "What is Clarc?", + title: "What is RxCode?", body: "A native macOS desktop client for the Claude Code CLI. Use all Claude Code features via a polished GUI without needing a terminal." ), ManualSection( @@ -429,7 +429,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { ), ManualSection( title: "Clipboard Detection", - body: "Pasting (⌘V) is smart — Clarc detects what's in the clipboard and handles it automatically.", + body: "Pasting (⌘V) is smart — RxCode detects what's in the clipboard and handles it automatically.", items: [ KeyValueItem(key: "image", value: "Image data (PNG/TIFF) → attached as an image", symbolName: "photo", symbolColor: .blue), KeyValueItem(key: "file", value: "File path → attached as a file", symbolName: "doc", symbolColor: .secondary), @@ -522,15 +522,15 @@ enum ManualTopic: String, CaseIterable, Identifiable { [ ManualSection( title: "GitHub Integration", - body: "Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to Clarc with one click." + body: "Click the GitHub Mark button at the top of the sidebar to open the GitHub panel. After connecting your GitHub account, your repositories are listed and can be added to RxCode with one click." ), ManualSection( title: "Adding a Repository", - body: "Search for a repository by name, then click Add. Clarc clones it automatically and opens it as a new project.", + body: "Search for a repository by name, then click Add. RxCode clones it automatically and opens it as a new project.", items: [ KeyValueItem(key: "lock", value: "Private repository", symbolName: "lock", symbolColor: .secondary), KeyValueItem(key: "globe", value: "Public repository", symbolName: "globe", symbolColor: .secondary), - KeyValueItem(key: "checkmark", value: "Already added to Clarc", symbolName: "checkmark.circle.fill", symbolColor: .green), + KeyValueItem(key: "checkmark", value: "Already added to RxCode", symbolName: "checkmark.circle.fill", symbolColor: .green), ] ), ] @@ -617,7 +617,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { [ ManualSection( title: "Opening Settings", - body: "Choose Clarc → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into four tabs: General, Message, Slash Commands, and Shortcuts." + body: "Choose RxCode → Settings from the menu bar or press ⌘, to open the Settings window. Settings are organized into four tabs: General, Message, Slash Commands, and Shortcuts." ), ManualSection( title: "General Tab", @@ -629,7 +629,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { KeyValueItem(key: "Default Model", value: "Claude model used when starting a new session"), KeyValueItem(key: "Default Permission Mode", value: "Permission mode applied to new sessions"), KeyValueItem(key: "Default Effort Level", value: "Reasoning effort applied to new sessions"), - KeyValueItem(key: "Notifications", value: "Show a system notification when a response completes while Clarc is in the background"), + KeyValueItem(key: "Notifications", value: "Show a system notification when a response completes while RxCode is in the background"), ], note: "Model, permission mode, and effort can also be overridden per session from the toolbar — the chosen value sticks to that session." ), @@ -646,7 +646,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { ), ManualSection( title: "Themes", - body: "Clarc ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose.", + body: "RxCode ships with six accent color themes. Preview each one directly from the theme picker — the whole app recolors live as you choose.", items: [ KeyValueItem(key: "Terracotta", value: "Claude default — warm orange-red"), KeyValueItem(key: "Ocean", value: "Cool blue"), @@ -662,7 +662,7 @@ enum ManualTopic: String, CaseIterable, Identifiable { ), ManualSection( title: "Checking for Updates", - body: "Open Clarc → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch." + body: "Open RxCode → Check for Updates… to run Sparkle's update check manually. Updates are also checked automatically on launch." ), ] } diff --git a/ClarcTests/AppStateProjectSwitchTests.swift b/RxCodeTests/AppStateProjectSwitchTests.swift similarity index 98% rename from ClarcTests/AppStateProjectSwitchTests.swift rename to RxCodeTests/AppStateProjectSwitchTests.swift index f749bf99..3e030c1e 100644 --- a/ClarcTests/AppStateProjectSwitchTests.swift +++ b/RxCodeTests/AppStateProjectSwitchTests.swift @@ -1,6 +1,6 @@ import XCTest -import ClarcCore -@testable import Clarc +import RxCodeCore +@testable import RxCode @MainActor final class AppStateProjectSwitchTests: XCTestCase { diff --git a/ClarcTests/InputBarDisableTests.swift b/RxCodeTests/InputBarDisableTests.swift similarity index 98% rename from ClarcTests/InputBarDisableTests.swift rename to RxCodeTests/InputBarDisableTests.swift index 3620f90a..dfa7a774 100644 --- a/ClarcTests/InputBarDisableTests.swift +++ b/RxCodeTests/InputBarDisableTests.swift @@ -1,6 +1,6 @@ import XCTest -import ClarcCore -@testable import ClarcChatKit +import RxCodeCore +@testable import RxCodeChatKit @MainActor final class InputBarDisableTests: XCTestCase { diff --git a/ClarcTests/PlanCardViewTests.swift b/RxCodeTests/PlanCardViewTests.swift similarity index 98% rename from ClarcTests/PlanCardViewTests.swift rename to RxCodeTests/PlanCardViewTests.swift index 51714c89..72c9bed7 100644 --- a/ClarcTests/PlanCardViewTests.swift +++ b/RxCodeTests/PlanCardViewTests.swift @@ -1,8 +1,8 @@ import XCTest import SwiftUI import ViewInspector -import ClarcCore -@testable import ClarcChatKit +import RxCodeCore +@testable import RxCodeChatKit @MainActor final class PlanCardViewTests: XCTestCase { diff --git a/ClarcTests/PlanDecisionTests.swift b/RxCodeTests/PlanDecisionTests.swift similarity index 98% rename from ClarcTests/PlanDecisionTests.swift rename to RxCodeTests/PlanDecisionTests.swift index dfe3e944..afe45453 100644 --- a/ClarcTests/PlanDecisionTests.swift +++ b/RxCodeTests/PlanDecisionTests.swift @@ -1,6 +1,6 @@ import XCTest -import ClarcCore -@testable import Clarc +import RxCodeCore +@testable import RxCode @MainActor final class PlanDecisionTests: XCTestCase { diff --git a/appcast.xml b/appcast.xml index f5c6a81a..8dc0730e 100644 --- a/appcast.xml +++ b/appcast.xml @@ -1,78 +1,78 @@ - Clarc - Clarc Updates + RxCode + RxCode Updates en - Clarc v1.1.9 + RxCode v1.1.9 15 1.1.9 15.0 Thu, 24 Apr 2026 00:00:00 +0000 - Clarc v1.1.8 + RxCode v1.1.8 14 1.1.8 15.0 Tue, 22 Apr 2026 00:00:00 +0000 - Clarc v1.1.4 + RxCode v1.1.4 9 1.1.4 15.0 Sun, 20 Apr 2026 00:00:00 +0000 - Clarc v1.1.3 + RxCode v1.1.3 8 1.1.3 15.0 Sun, 20 Apr 2026 00:00:00 +0000 - Clarc v1.1.2 + RxCode v1.1.2 7 1.1.2 15.0 Sun, 20 Apr 2026 00:00:00 +0000 - Clarc v1.1.0 - https://github.com/ttnear/Clarc/releases/tag/v1.1.0 + RxCode v1.1.0 + https://github.com/ttnear/RxCode/releases/tag/v1.1.0 Fri, 17 Apr 2026 07:56:20 +0000 15.0 - Clarc v1.0.2 - https://github.com/ttnear/Clarc/releases/tag/v1.0.2 + RxCode v1.0.2 + https://github.com/ttnear/RxCode/releases/tag/v1.0.2 Wed, 15 Apr 2026 07:57:18 +0000 15.0 - Clarc v1.0.1 - https://github.com/ttnear/Clarc/releases/tag/v1.0.1 + RxCode v1.0.1 + https://github.com/ttnear/RxCode/releases/tag/v1.0.1 Wed, 15 Apr 2026 06:56:38 +0000 15.0 - Clarc v1.0.0 - https://github.com/ttnear/Clarc/releases/tag/v1.0.0 + RxCode v1.0.0 + https://github.com/ttnear/RxCode/releases/tag/v1.0.0 Tue, 15 Apr 2026 00:00:00 +0000 15.0 - Clarc v1.1.1 + RxCode v1.1.1 6 1.1.1 15.0 Fri, 17 Apr 2026 12:50:10 +0000 - Clarc v1.1.6 + RxCode v1.1.6 12 1.1.6 15.0 Sun, 20 Apr 2026 00:00:00 +0000 - Clarc v1.1.5 + RxCode v1.1.5 11 1.1.5 15.0 Mon, 20 Apr 2026 06:47:18 +0000 - Clarc v1.1.7 + RxCode v1.1.7 13 1.1.7 15.0 Tue, 21 Apr 2026 09:08:19 +0000 - Clarc v1.2.0 + RxCode v1.2.0 16 1.2.0 15.0 @@ -194,13 +194,13 @@ ]]> - Clarc v1.2.1 + RxCode v1.2.1 18 1.2.1 15.0 @@ -223,13 +223,13 @@ ]]> - Clarc v1.2.3 + RxCode v1.2.3 21 1.2.3 15.0 @@ -237,7 +237,7 @@ New
    -
  • Sessions started in Clarc now appear in the claude --resume picker — switch freely between Clarc and the terminal CLI.
  • +
  • Sessions started in RxCode now appear in the claude --resume picker — switch freely between RxCode and the terminal CLI.
  • Legacy sessions are automatically migrated to the new format.

Improved

@@ -254,14 +254,14 @@ ]]>
- Clarc v1.2.2 + RxCode v1.2.2 20 1.2.2 15.0 @@ -279,13 +279,13 @@ ]]> - Clarc v1.2.4 + RxCode v1.2.4 23 1.2.4 15.0 @@ -297,13 +297,13 @@ ]]> - Clarc v1.2.5 + RxCode v1.2.5 24 1.2.5 15.0 @@ -316,7 +316,7 @@ ]]> diff --git a/bin/generate_appcast b/bin/generate_appcast new file mode 100755 index 00000000..c6e2e0c7 Binary files /dev/null and b/bin/generate_appcast differ diff --git a/docs/superpowers/plans/2026-04-27-attachment-auto-preview-settings.md b/docs/superpowers/plans/2026-04-27-attachment-auto-preview-settings.md index 9097461a..15f11d92 100644 --- a/docs/superpowers/plans/2026-04-27-attachment-auto-preview-settings.md +++ b/docs/superpowers/plans/2026-04-27-attachment-auto-preview-settings.md @@ -4,7 +4,7 @@ **Goal:** Add four independently toggleable settings (URL, file path, image, long text) that control whether pasted content is auto-converted to an attachment preview chip in the chat input. -**Architecture:** `AttachmentAutoPreviewSettings` struct lives in `ClarcCore`. `AppState` owns and persists it to UserDefaults. The existing `startBridgeObservation` loop pushes the value from `AppState` into each `ChatBridge`. `InputBarView` reads from `chatBridge.autoPreviewSettings` and skips attachment creation for disabled types. +**Architecture:** `AttachmentAutoPreviewSettings` struct lives in `RxCodeCore`. `AppState` owns and persists it to UserDefaults. The existing `startBridgeObservation` loop pushes the value from `AppState` into each `ChatBridge`. `InputBarView` reads from `chatBridge.autoPreviewSettings` and skips attachment creation for disabled types. **Tech Stack:** Swift, SwiftUI (`@Observable`, `@Bindable`), `UserDefaults` + `JSONEncoder`/`JSONDecoder`, `NSPasteboard` @@ -14,24 +14,24 @@ | File | Action | Responsibility | |---|---|---| -| `Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift` | Create | Settings model — four Bool fields, Codable | -| `Packages/Sources/ClarcChatKit/ChatBridge.swift` | Modify | Add `autoPreviewSettings` property for InputBarView to read | -| `Clarc/App/AppState.swift` | Modify | Add `autoPreviewSettings` property with UserDefaults persistence | -| `Clarc/App/AppState.swift` (`startBridgeObservation`) | Modify | Push settings into bridge inside observation loop | -| `Packages/Sources/ClarcChatKit/InputBarView.swift` | Modify | Guard each attachment-creation path with the matching setting | -| `Clarc/Views/SettingsView.swift` | Modify | Add four Toggles in a new section inside `ChatSettingsTab` | +| `Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift` | Create | Settings model — four Bool fields, Codable | +| `Packages/Sources/RxCodeChatKit/ChatBridge.swift` | Modify | Add `autoPreviewSettings` property for InputBarView to read | +| `RxCode/App/AppState.swift` | Modify | Add `autoPreviewSettings` property with UserDefaults persistence | +| `RxCode/App/AppState.swift` (`startBridgeObservation`) | Modify | Push settings into bridge inside observation loop | +| `Packages/Sources/RxCodeChatKit/InputBarView.swift` | Modify | Guard each attachment-creation path with the matching setting | +| `RxCode/Views/SettingsView.swift` | Modify | Add four Toggles in a new section inside `ChatSettingsTab` | --- ## Task 1: Create AttachmentAutoPreviewSettings model **Files:** -- Create: `Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift` +- Create: `Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift` - [ ] **Step 1: Create the file** ```swift -// Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift +// Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift import Foundation public struct AttachmentAutoPreviewSettings: Codable, Sendable { @@ -44,10 +44,10 @@ public struct AttachmentAutoPreviewSettings: Codable, Sendable { } ``` -- [ ] **Step 2: Build ClarcCore to verify** +- [ ] **Step 2: Build RxCodeCore to verify** ```bash -cd /Users/jmlee/workspace/Clarc/Packages && swift build --target ClarcCore 2>&1 | tail -5 +cd /Users/jmlee/workspace/RxCode/Packages && swift build --target RxCodeCore 2>&1 | tail -5 ``` Expected: `Build complete!` @@ -55,7 +55,7 @@ Expected: `Build complete!` - [ ] **Step 3: Commit** ```bash -git add Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift +git add Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift git commit -m "feat(core): add AttachmentAutoPreviewSettings model" ``` @@ -64,11 +64,11 @@ git commit -m "feat(core): add AttachmentAutoPreviewSettings model" ## Task 2: Add autoPreviewSettings to ChatBridge **Files:** -- Modify: `Packages/Sources/ClarcChatKit/ChatBridge.swift` +- Modify: `Packages/Sources/RxCodeChatKit/ChatBridge.swift` - [ ] **Step 1: Add property to ChatBridge** -Open `Packages/Sources/ClarcChatKit/ChatBridge.swift`. After the last `public var` in the "Streaming State" section (after `sessionStats`), add: +Open `Packages/Sources/RxCodeChatKit/ChatBridge.swift`. After the last `public var` in the "Streaming State" section (after `sessionStats`), add: ```swift public var autoPreviewSettings: AttachmentAutoPreviewSettings = AttachmentAutoPreviewSettings() @@ -88,10 +88,10 @@ public var sessionStats: ChatSessionStats = ChatSessionStats() public var autoPreviewSettings: AttachmentAutoPreviewSettings = AttachmentAutoPreviewSettings() ``` -- [ ] **Step 2: Build ClarcChatKit to verify** +- [ ] **Step 2: Build RxCodeChatKit to verify** ```bash -cd /Users/jmlee/workspace/Clarc/Packages && swift build --target ClarcChatKit 2>&1 | tail -5 +cd /Users/jmlee/workspace/RxCode/Packages && swift build --target RxCodeChatKit 2>&1 | tail -5 ``` Expected: `Build complete!` @@ -99,7 +99,7 @@ Expected: `Build complete!` - [ ] **Step 3: Commit** ```bash -git add Packages/Sources/ClarcChatKit/ChatBridge.swift +git add Packages/Sources/RxCodeChatKit/ChatBridge.swift git commit -m "feat(chatkit): add autoPreviewSettings to ChatBridge" ``` @@ -108,11 +108,11 @@ git commit -m "feat(chatkit): add autoPreviewSettings to ChatBridge" ## Task 3: Add autoPreviewSettings to AppState **Files:** -- Modify: `Clarc/App/AppState.swift` +- Modify: `RxCode/App/AppState.swift` - [ ] **Step 1: Add property to AppState** -Open `Clarc/App/AppState.swift`. After the `focusMode` property (around line 199), add a new MARK and property: +Open `RxCode/App/AppState.swift`. After the `focusMode` property (around line 199), add a new MARK and property: ```swift // MARK: - Attachment Auto-Preview Settings @@ -135,7 +135,7 @@ var autoPreviewSettings: AttachmentAutoPreviewSettings = { - [ ] **Step 2: Build to verify** ```bash -xcodebuild -project /Users/jmlee/workspace/Clarc/Clarc.xcodeproj -scheme Clarc -configuration Debug build 2>&1 | grep -E "error:|Build complete|BUILD SUCCEEDED|BUILD FAILED" | tail -5 +xcodebuild -project /Users/jmlee/workspace/RxCode/RxCode.xcodeproj -scheme RxCode -configuration Debug build 2>&1 | grep -E "error:|Build complete|BUILD SUCCEEDED|BUILD FAILED" | tail -5 ``` Expected: `BUILD SUCCEEDED` @@ -143,7 +143,7 @@ Expected: `BUILD SUCCEEDED` - [ ] **Step 3: Commit** ```bash -git add Clarc/App/AppState.swift +git add RxCode/App/AppState.swift git commit -m "feat(app): add autoPreviewSettings to AppState with UserDefaults persistence" ``` @@ -152,11 +152,11 @@ git commit -m "feat(app): add autoPreviewSettings to AppState with UserDefaults ## Task 4: Sync settings into ChatBridge via observation loop **Files:** -- Modify: `Clarc/App/AppState.swift` (the `startBridgeObservation` function, around line 489) +- Modify: `RxCode/App/AppState.swift` (the `startBridgeObservation` function, around line 489) - [ ] **Step 1: Add sync line inside withObservationTracking** -Open `Clarc/App/AppState.swift`. In `startBridgeObservation`, inside the `withObservationTracking` closure, add one line after `bridge.sessionStats = ...`: +Open `RxCode/App/AppState.swift`. In `startBridgeObservation`, inside the `withObservationTracking` closure, add one line after `bridge.sessionStats = ...`: ```swift bridge.autoPreviewSettings = self.autoPreviewSettings @@ -190,7 +190,7 @@ withObservationTracking { - [ ] **Step 2: Build to verify** ```bash -xcodebuild -project /Users/jmlee/workspace/Clarc/Clarc.xcodeproj -scheme Clarc -configuration Debug build 2>&1 | grep -E "error:|BUILD SUCCEEDED|BUILD FAILED" | tail -5 +xcodebuild -project /Users/jmlee/workspace/RxCode/RxCode.xcodeproj -scheme RxCode -configuration Debug build 2>&1 | grep -E "error:|BUILD SUCCEEDED|BUILD FAILED" | tail -5 ``` Expected: `BUILD SUCCEEDED` @@ -198,7 +198,7 @@ Expected: `BUILD SUCCEEDED` - [ ] **Step 3: Commit** ```bash -git add Clarc/App/AppState.swift +git add RxCode/App/AppState.swift git commit -m "feat(app): sync autoPreviewSettings into ChatBridge via observation loop" ``` @@ -207,7 +207,7 @@ git commit -m "feat(app): sync autoPreviewSettings into ChatBridge via observati ## Task 5: Guard attachment creation in InputBarView **Files:** -- Modify: `Packages/Sources/ClarcChatKit/InputBarView.swift` +- Modify: `Packages/Sources/RxCodeChatKit/InputBarView.swift` ### Step-by-step changes @@ -355,10 +355,10 @@ if chatBridge.autoPreviewSettings.longText, } ``` -- [ ] **Step 6: Build ClarcChatKit to verify** +- [ ] **Step 6: Build RxCodeChatKit to verify** ```bash -cd /Users/jmlee/workspace/Clarc/Packages && swift build --target ClarcChatKit 2>&1 | tail -5 +cd /Users/jmlee/workspace/RxCode/Packages && swift build --target RxCodeChatKit 2>&1 | tail -5 ``` Expected: `Build complete!` @@ -366,7 +366,7 @@ Expected: `Build complete!` - [ ] **Step 7: Commit** ```bash -git add Packages/Sources/ClarcChatKit/InputBarView.swift +git add Packages/Sources/RxCodeChatKit/InputBarView.swift git commit -m "feat(chatkit): guard attachment creation with autoPreviewSettings flags" ``` @@ -375,11 +375,11 @@ git commit -m "feat(chatkit): guard attachment creation with autoPreviewSettings ## Task 6: Add settings UI in ChatSettingsTab **Files:** -- Modify: `Clarc/Views/SettingsView.swift` +- Modify: `RxCode/Views/SettingsView.swift` - [ ] **Step 1: Add autoPreviewSection helper** -Open `Clarc/Views/SettingsView.swift`. Inside `ChatSettingsTab`, after `focusModeSection` (around line 443, before `effortDisplayName`), add a new helper: +Open `RxCode/Views/SettingsView.swift`. Inside `ChatSettingsTab`, after `focusModeSection` (around line 443, before `effortDisplayName`), add a new helper: ```swift // MARK: - Auto-Preview Attachments Section @@ -451,7 +451,7 @@ var body: some View { - [ ] **Step 3: Build to verify** ```bash -xcodebuild -project /Users/jmlee/workspace/Clarc/Clarc.xcodeproj -scheme Clarc -configuration Debug build 2>&1 | grep -E "error:|BUILD SUCCEEDED|BUILD FAILED" | tail -5 +xcodebuild -project /Users/jmlee/workspace/RxCode/RxCode.xcodeproj -scheme RxCode -configuration Debug build 2>&1 | grep -E "error:|BUILD SUCCEEDED|BUILD FAILED" | tail -5 ``` Expected: `BUILD SUCCEEDED` @@ -459,7 +459,7 @@ Expected: `BUILD SUCCEEDED` - [ ] **Step 4: Commit** ```bash -git add Clarc/Views/SettingsView.swift +git add RxCode/Views/SettingsView.swift git commit -m "feat(settings): add auto-preview attachment toggles in Message tab" ``` diff --git a/docs/superpowers/specs/2026-04-27-attachment-auto-preview-settings-design.md b/docs/superpowers/specs/2026-04-27-attachment-auto-preview-settings-design.md index b6edd4d4..91d2177d 100644 --- a/docs/superpowers/specs/2026-04-27-attachment-auto-preview-settings-design.md +++ b/docs/superpowers/specs/2026-04-27-attachment-auto-preview-settings-design.md @@ -1,11 +1,11 @@ # Attachment Auto-Preview Settings **Date:** 2026-04-27 -**Issue:** https://github.com/ttnear/Clarc/issues/4 +**Issue:** https://github.com/ttnear/RxCode/issues/4 ## Problem -When a user pastes a URL, file path, image, or long text into the chat input, Clarc automatically converts it into an attachment preview chip. There is no way to disable this behavior per content type. Users who want a URL to remain as plain text in the message have no option to do so. +When a user pastes a URL, file path, image, or long text into the chat input, RxCode automatically converts it into an attachment preview chip. There is no way to disable this behavior per content type. Users who want a URL to remain as plain text in the message have no option to do so. ## Goal @@ -23,7 +23,7 @@ Four independently toggleable content types: ### Data Model -New file: `Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift` +New file: `Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift` ```swift struct AttachmentAutoPreviewSettings: Codable { @@ -56,7 +56,7 @@ No `@AppStorage` wrapper needed since the value is `Codable` (not a primitive). ### InputBarView -`Packages/Sources/ClarcChatKit/InputBarView.swift` +`Packages/Sources/RxCodeChatKit/InputBarView.swift` In `attachmentFromPastedText()` and `handlePasteKey()`, check settings before creating each attachment type: @@ -115,9 +115,9 @@ User toggles in SettingsView | File | Change | |---|---| -| `Packages/Sources/ClarcCore/Models/AttachmentAutoPreviewSettings.swift` | New file — settings model | +| `Packages/Sources/RxCodeCore/Models/AttachmentAutoPreviewSettings.swift` | New file — settings model | | `App/AppState.swift` | Add `autoPreviewSettings` property with UserDefaults load/save | -| `Packages/Sources/ClarcChatKit/InputBarView.swift` | Guard attachment creation with settings flags | +| `Packages/Sources/RxCodeChatKit/InputBarView.swift` | Guard attachment creation with settings flags | | `Views/SettingsView.swift` | Add toggles section in Message tab | ## Out of Scope diff --git a/scripts/build_zip.sh b/scripts/build_zip.sh index 18dc0562..809b6b29 100755 --- a/scripts/build_zip.sh +++ b/scripts/build_zip.sh @@ -2,7 +2,7 @@ set -e # ───────────────────────────────────────────── -# Clarc ZIP 배포 빌드 스크립트 (노터라이제이션 포함) +# RxCode ZIP 배포 빌드 스크립트 (노터라이제이션 포함) # # 사용법: ./scripts/build_zip.sh [버전] # 예시: ./scripts/build_zip.sh 1.2.0 @@ -24,18 +24,18 @@ else exit 1 fi -SCHEME="Clarc" -PROJECT="Clarc.xcodeproj" +SCHEME="RxCode" +PROJECT="RxCode.xcodeproj" EXPORT_OPTIONS="scripts/ExportOptions.plist" BUILD_DIR="build" VERSION=${1:-"1.0.0"} -ARCHIVE_PATH="$BUILD_DIR/Clarc.xcarchive" +ARCHIVE_PATH="$BUILD_DIR/RxCode.xcarchive" EXPORT_PATH="$BUILD_DIR/export" -APP_PATH="$EXPORT_PATH/Clarc.app" -ZIP_NAME="Clarc-${VERSION}.zip" +APP_PATH="$EXPORT_PATH/RxCode.app" +ZIP_NAME="RxCode-${VERSION}.zip" -echo "▶ Clarc v${VERSION} 빌드 시작" +echo "▶ RxCode v${VERSION} 빌드 시작" echo "" # 이전 빌드 정리 @@ -80,7 +80,7 @@ echo "" # ── 3. 노터라이제이션 ──────────────────────── echo "🔐 노터라이제이션 제출 중 (수 분 소요)..." -NOTARIZE_ZIP="$BUILD_DIR/Clarc-notarize.zip" +NOTARIZE_ZIP="$BUILD_DIR/RxCode-notarize.zip" ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_ZIP" xcrun notarytool submit "$NOTARIZE_ZIP" \ diff --git a/scripts/ci/convert-markdown.py b/scripts/ci/convert-markdown.py new file mode 100644 index 00000000..f4937ede --- /dev/null +++ b/scripts/ci/convert-markdown.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import markdown +import sys +import os + +def convert_markdown_to_html(markdown_file, output_html_file): + """ + Convert markdown content to HTML with dark mode support + + Args: + markdown_file (str): Path to the markdown file + output_html_file (str): Path to output HTML file + """ + # Read markdown content + with open(markdown_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Convert markdown to HTML + html_content = markdown.markdown(content, extensions=['fenced_code', 'tables', 'nl2br']) + + # Create HTML document with dark mode support + html_document = f""" + + + + + Release Notes + + + + {html_content} + +""" + + # Write HTML to file + with open(output_html_file, 'w', encoding='utf-8') as f: + f.write(html_document) + + print(f"Converted {markdown_file} to {output_html_file}") + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Usage: python convert-markdown.py ") + print("Example: python convert-markdown.py release_notes.md release_notes.html") + sys.exit(1) + + markdown_file = sys.argv[1] + output_html_file = sys.argv[2] + + convert_markdown_to_html(markdown_file, output_html_file) diff --git a/scripts/ci/generate-appcast.sh b/scripts/ci/generate-appcast.sh new file mode 100755 index 00000000..1e19cb5a --- /dev/null +++ b/scripts/ci/generate-appcast.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +# Write SPARKLE_KEY to sparkle.key +echo "$SPARKLE_KEY" > sparkle.key + +echo "Generating appcast for version: $VERSION" + +# Create a temporary release notes file if release notes exist +if [ -n "$RELEASE_NOTE" ]; then + echo "$RELEASE_NOTE" > release_notes.md + # Convert markdown to HTML + python3 scripts/ci/convert-markdown.py release_notes.md release_notes.html +else + echo "No release notes provided" +fi + +# Generate appcast.xml with optional release notes +./bin/generate_appcast ./ \ + --ed-key-file sparkle.key \ + --link https://github.com/rxtech-lab/Clarc/releases \ + --download-url-prefix https://github.com/rxtech-lab/Clarc/releases/download/${VERSION}/ + +if [ -f "release_notes.html" ]; then + python3 scripts/ci/update-xml.py appcast.xml release_notes.html ${BUILD_NUMBER} +fi diff --git a/scripts/ci/notary.sh b/scripts/ci/notary.sh new file mode 100755 index 00000000..81fa457d --- /dev/null +++ b/scripts/ci/notary.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +APP_NAME="./output/output.xcarchive/Products/Applications/RxCode.app" +DMG_NAME="RxCode.dmg" + +# Remove existing DMG if it exists +if [ -f "$DMG_NAME" ]; then + echo "Removing existing DMG file" + rm "$DMG_NAME" +fi + +# Create DMG +create-dmg --overwrite "$APP_NAME" && mv *.dmg "$DMG_NAME" + +echo "DMG created: $DMG_NAME" + +# Notarize the app +xcrun notarytool submit ./$DMG_NAME --verbose --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_ID_PWD" --wait + +# Staple the ticket +xcrun stapler staple $DMG_NAME + +echo "All operations completed successfully!" diff --git a/scripts/ci/sign-sparkle.sh b/scripts/ci/sign-sparkle.sh new file mode 100755 index 00000000..d9de6be4 --- /dev/null +++ b/scripts/ci/sign-sparkle.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +APP_PATH="output/output.xcarchive/Products/Applications/RxCode.app" + +if [ -z "${SIGNING_CERTIFICATE_NAME}" ]; then + echo "Error: SIGNING_CERTIFICATE_NAME is not set" + exit 1 +fi + +# Sign the main Sparkle framework binary first +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle" + +# Sign Sparkle components +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app" +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate" +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc" +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc" + +# Sign the Sparkle framework as a whole +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/Frameworks/Sparkle.framework" + +# Re-sign the main app binary +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH/Contents/MacOS/RxCode" + +# Re-sign the main app to ensure everything is properly signed +codesign --force --options runtime --timestamp --sign "${SIGNING_CERTIFICATE_NAME}" "$APP_PATH" + +echo "Signing completed successfully" diff --git a/scripts/ci/update-version.sh b/scripts/ci/update-version.sh new file mode 100755 index 00000000..1d8bb330 --- /dev/null +++ b/scripts/ci/update-version.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +VERSION="$1" + +if [ -z "$VERSION" ]; then + echo "Usage: $0 " + echo "Example: $0 1.2.3" + exit 1 +fi + +PROJECT_FILE="RxCode.xcodeproj/project.pbxproj" + +if [ ! -f "$PROJECT_FILE" ]; then + echo "Error: $PROJECT_FILE not found" + exit 1 +fi + +echo "Updating MARKETING_VERSION to $VERSION in $PROJECT_FILE" + +# Create a temporary file +TMP_FILE=$(mktemp) + +# Use awk to update only the RxCode main target (matched by PRODUCT_BUNDLE_IDENTIFIER) +awk -v new_version="$VERSION" ' +/MARKETING_VERSION = / { + # Store the current line + marketing_line = $0 + # Read the next line + getline + # Check if this is the main app target (not Tests or UITests) + if ($0 ~ /PRODUCT_BUNDLE_IDENTIFIER = com\.idealapp\.RxCode;$/) { + # Update MARKETING_VERSION + gsub(/MARKETING_VERSION = [^;]+;/, "MARKETING_VERSION = " new_version ";", marketing_line) + print marketing_line + print + } else { + # Keep both lines unchanged + print marketing_line + print + } + next +} +{ print } +' "$PROJECT_FILE" > "$TMP_FILE" + +# Replace the original file +mv "$TMP_FILE" "$PROJECT_FILE" + +echo "Successfully updated MARKETING_VERSION to $VERSION" diff --git a/scripts/ci/update-xml.py b/scripts/ci/update-xml.py new file mode 100644 index 00000000..5982b6bc --- /dev/null +++ b/scripts/ci/update-xml.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +import xml.etree.ElementTree as ET +import os +import sys + +def add_release_notes_link_and_update_version(xml_file, notes_path="./release_notes.html", build_number=None): + """ + Add a sparkle:releaseNotesLink element to each item in the appcast.xml file + if it doesn't already have one and update the sparkle:version with the build number. + + Args: + xml_file (str): Path to the appcast.xml file + notes_path (str): Path to the release notes HTML file + build_number (str): Build number to use for sparkle:version + """ + # Register the namespace + ET.register_namespace('sparkle', 'http://www.andymatuschak.org/xml-namespaces/sparkle') + + # Parse the XML file + tree = ET.parse(xml_file) + root = tree.getroot() + + # Define the namespace map + namespaces = {'sparkle': 'http://www.andymatuschak.org/xml-namespaces/sparkle'} + + # Flag to track if any changes were made + changes_made = False + + # Find all items + channel = root.find('channel') + if channel is not None: + for item in channel.findall('item'): + # Check if the item already has a releaseNotesLink + release_notes_link = item.find('.//sparkle:releaseNotesLink', namespaces) + + if release_notes_link is None: + # Create new releaseNotesLink element + release_notes_link = ET.Element('{http://www.andymatuschak.org/xml-namespaces/sparkle}releaseNotesLink') + release_notes_link.text = notes_path + + # Add it after title and pubDate + title = item.find('title') + pubDate = item.find('pubDate') + + # Find the insertion point - after pubDate if it exists, otherwise after title + if pubDate is not None: + # Find index of pubDate and insert after it + index = list(item).index(pubDate) + 1 + elif title is not None: + # Find index of title and insert after it + index = list(item).index(title) + 1 + else: + # Insert at the beginning if neither exists + index = 0 + + item.insert(index, release_notes_link) + changes_made = True + + # Update sparkle:version if build_number is provided + if build_number: + # Find the sparkle:version element + version_element = item.find('.//sparkle:version', namespaces) + + if version_element is not None: + # Update the existing sparkle:version element + old_version = version_element.text + version_element.text = build_number + print(f"Updated sparkle:version from {old_version} to {build_number}") + changes_made = True + else: + # Create a new sparkle:version element if it doesn't exist + version_element = ET.Element('{http://www.andymatuschak.org/xml-namespaces/sparkle}version') + version_element.text = build_number + + # Insert it after the release notes link if it exists + if release_notes_link is not None and release_notes_link in list(item): + index = list(item).index(release_notes_link) + 1 + else: + # Otherwise insert after title or pubDate + pubDate = item.find('pubDate') + title = item.find('title') + if pubDate is not None: + index = list(item).index(pubDate) + 1 + elif title is not None: + index = list(item).index(title) + 1 + else: + index = 0 + + item.insert(index, version_element) + print(f"Added new sparkle:version with value {build_number}") + changes_made = True + + if changes_made: + # Write the modified XML to the file directly without creating a backup + tree.write(xml_file, encoding='utf-8', xml_declaration=True) + print(f"Updated {xml_file} with changes") + else: + print("No changes made to the XML file.") + +if __name__ == "__main__": + if len(sys.argv) > 1: + xml_file = sys.argv[1] + notes_path = sys.argv[2] if len(sys.argv) > 2 else "./release_notes.html" + build_number = sys.argv[3] if len(sys.argv) > 3 else None + add_release_notes_link_and_update_version(xml_file, notes_path, build_number) + else: + print("Usage: python update-xml.py [path_to_release_notes] [build_number]") + print("Example: python update-xml.py ./appcast.xml ./release_notes.html 1.2.3") + sys.exit(1) diff --git a/scripts/release.sh b/scripts/release.sh index 65f8294c..c9c42d21 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -2,7 +2,7 @@ set -e # ───────────────────────────────────────────── -# Clarc release script +# RxCode release script # # Usage: ./scripts/release.sh [notes-file] # Example: ./scripts/release.sh 1.0.1 @@ -29,7 +29,7 @@ if [ -z "$VERSION" ]; then fi TAG="v${VERSION}" -ZIP="build/Clarc-${VERSION}.zip" +ZIP="build/RxCode-${VERSION}.zip" META_FILE="build/.sparkle_meta" NOTES_FILE_ARG=${2:-""} @@ -47,11 +47,11 @@ else HAS_NOTES=0 fi -echo "▶ Starting Clarc ${TAG} release" +echo "▶ Starting RxCode ${TAG} release" echo "" # ── 1. Bump version in pbxproj ─────────────── -PBXPROJ="Clarc.xcodeproj/project.pbxproj" +PBXPROJ="RxCode.xcodeproj/project.pbxproj" APPCAST="appcast.xml" CURRENT_BUILD=$(grep -m1 "CURRENT_PROJECT_VERSION = " "$PBXPROJ" | sed 's/.*= \([0-9]*\);/\1/') @@ -99,7 +99,7 @@ if [ -f "$META_FILE" ]; then DESC_BLOCK="" if [ "$HAS_NOTES" = "1" ]; then - DESC_FILE="$(mktemp -t clarc_desc).html" + DESC_FILE="$(mktemp -t rxcode_desc).html" python3 - "$NOTES_FILE" "$DESC_FILE" <<'PYEOF' import re, sys src = open(sys.argv[1], encoding='utf-8').read() @@ -153,7 +153,7 @@ ${DESC_HTML} fi NEW_ITEM=" - Clarc ${TAG} + RxCode ${TAG} ${BUILD_NUMBER} ${VERSION} 15.0 @@ -166,7 +166,7 @@ ${DESC_HTML} " # Insert the new item right before
- NEW_ITEM_FILE="$(mktemp -t clarc_item).xml" + NEW_ITEM_FILE="$(mktemp -t rxcode_item).xml" printf '%s' "$NEW_ITEM" > "$NEW_ITEM_FILE" python3 - "$NEW_ITEM_FILE" <<'PYEOF' import sys @@ -207,16 +207,16 @@ echo "" echo "🚀 Creating GitHub Release..." if [ "$HAS_NOTES" = "1" ]; then gh release create "$TAG" "$ZIP" \ - --title "Clarc ${TAG}" \ + --title "RxCode ${TAG}" \ --notes-file "$NOTES_FILE" else gh release create "$TAG" "$ZIP" \ - --title "Clarc ${TAG}" \ - --notes "## Clarc ${TAG} + --title "RxCode ${TAG}" \ + --notes "## RxCode ${TAG} ### Installation -1. Download \`Clarc-${VERSION}.zip\` -2. Unzip and move \`Clarc.app\` to \`/Applications\` +1. Download \`RxCode-${VERSION}.zip\` +2. Unzip and move \`RxCode.app\` to \`/Applications\` 3. On first launch, right-click → Open > Existing users will receive this via the in-app auto-updater." diff --git a/scripts/setup_sparkle.sh b/scripts/setup_sparkle.sh index fdbe7b40..aaab65ac 100755 --- a/scripts/setup_sparkle.sh +++ b/scripts/setup_sparkle.sh @@ -15,7 +15,7 @@ set -e SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)" TOOLS_DIR="$SCRIPTS_DIR/sparkle_tools" KEY_FILE="$SCRIPTS_DIR/.sparkle_private_key" -PBXPROJ="$(dirname "$SCRIPTS_DIR")/Clarc.xcodeproj/project.pbxproj" +PBXPROJ="$(dirname "$SCRIPTS_DIR")/RxCode.xcodeproj/project.pbxproj" SPARKLE_VERSION="2.9.1" SPARKLE_URL="https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz" @@ -80,7 +80,7 @@ echo "✅ 설정 완료" echo "" echo "📌 다음 단계: Xcode에서 SUPublicEDKey 설정" echo "" -echo " 1. Xcode → Clarc 타겟 → Build Settings → Info.plist Values" +echo " 1. Xcode → RxCode 타겟 → Build Settings → Info.plist Values" echo " 2. 아래 키/값을 추가:" echo "" echo " 키: SUPublicEDKey" @@ -97,7 +97,7 @@ if grep -q "INFOPLIST_KEY_SUPublicEDKey" "$PBXPROJ" 2>/dev/null; then echo "✓ SUPublicEDKey가 이미 pbxproj에 있습니다." else echo "🔧 pbxproj에 SUPublicEDKey 자동 추가 중..." - sed -i '' "s|INFOPLIST_KEY_SUFeedURL = \"https://raw.githubusercontent.com/ttnear/Clarc/main/appcast.xml\";|INFOPLIST_KEY_SUFeedURL = \"https://raw.githubusercontent.com/ttnear/Clarc/main/appcast.xml\";\n\t\t\t\tINFOPLIST_KEY_SUPublicEDKey = \"${PUBLIC_KEY}\";|g" "$PBXPROJ" + sed -i '' "s|INFOPLIST_KEY_SUFeedURL = \"https://raw.githubusercontent.com/ttnear/RxCode/main/appcast.xml\";|INFOPLIST_KEY_SUFeedURL = \"https://raw.githubusercontent.com/ttnear/RxCode/main/appcast.xml\";\n\t\t\t\tINFOPLIST_KEY_SUPublicEDKey = \"${PUBLIC_KEY}\";|g" "$PBXPROJ" echo "✓ SUPublicEDKey가 pbxproj에 추가되었습니다." fi