From d89a0e2df35f2ddc014d4beaeb3c43dee22227cc Mon Sep 17 00:00:00 2001 From: sirily11 <32106111+sirily11@users.noreply.github.com> Date: Mon, 25 May 2026 13:32:30 +0800 Subject: [PATCH 1/2] fix: briefing new thread redirect issue --- Packages/Sources/DiffView/DiffLine.swift | 4 +- Packages/Sources/DiffView/DiffView.swift | 167 +- .../RxCodeChatKit/ChangeDiffView.swift | 42 +- .../Sources/RxCodeChatKit/FileDiffView.swift | 82 +- .../Sources/RxCodeChatKit/InputBarView.swift | 15 +- .../Sources/RxCodeChatKit/MessageBubble.swift | 1 + .../MobileSheetPresentationModifier.swift | 14 + .../Resources/Localizable.xcstrings | 7204 +++++++++-------- .../RxCodeChatKit/SlashCommandBar.swift | 2 + .../RxCodeChatKit/ToolResultView.swift | 4 +- .../DiffViewTests/DiffNavigationTests.swift | 30 + .../DiffViewTests/GutterLayoutTests.swift | 14 + RxCode.xcodeproj/project.pbxproj | 22 +- RxCode/App/AppState+CrossProject.swift | 44 +- RxCode/App/AppState+Lifecycle.swift | 25 + RxCode/Views/Sidebar/BriefingView.swift | 1 + RxCodeMobile/Resources/Localizable.xcstrings | 6 + .../State/MobileAppState+Inbound.swift | 4 + .../State/MobileAppState+Intents.swift | 52 +- RxCodeMobile/State/MobileAppState+Sync.swift | 5 +- RxCodeMobile/State/MobileAppState.swift | 3 + .../Views/MobileBriefingDetailView.swift | 1 + RxCodeMobile/Views/MobileChatView.swift | 27 +- RxCodeMobile/Views/MobileMCPServersView.swift | 2 + RxCodeMobile/Views/MobileQuestionSheet.swift | 3 +- .../Views/MobileRunProfileEditorView.swift | 1 + RxCodeMobile/Views/MobileSettingsView.swift | 1 + .../MobileSheetPresentationModifier.swift | 29 + .../Views/MobileSkillMarketView.swift | 1 + RxCodeMobile/Views/NewThreadSheet.swift | 4 +- RxCodeMobile/Views/OnboardingView.swift | 1 + .../Views/PermissionApprovalSheet.swift | 2 +- RxCodeMobile/Views/ProjectsSidebar.swift | 1 + RxCodeMobile/Views/QueuedMessagesSheet.swift | 3 +- RxCodeMobile/Views/RenameThreadSheet.swift | 3 +- RxCodeMobile/Views/RootView.swift | 2 + RxCodeMobile/Views/SessionsList.swift | 2 + RxCodeMobile/Views/SyncLoadingView.swift | 34 +- RxCodeMobile/Views/ThreadChangesSheet.swift | 110 +- RxCodeMobile/Views/ThreadTodoSheet.swift | 3 +- .../Mock/MockRelayServer.swift | 78 +- .../iPadNavigationUITests.swift | 1 + .../iPhoneNavigationUITests.swift | 1 + .../LocalAIProviderAcceptanceTests.swift | 29 + 44 files changed, 4382 insertions(+), 3698 deletions(-) create mode 100644 Packages/Sources/RxCodeChatKit/MobileSheetPresentationModifier.swift create mode 100644 Packages/Tests/DiffViewTests/DiffNavigationTests.swift create mode 100644 RxCodeMobile/Views/MobileSheetPresentationModifier.swift diff --git a/Packages/Sources/DiffView/DiffLine.swift b/Packages/Sources/DiffView/DiffLine.swift index a59446a4..ce19f7cc 100644 --- a/Packages/Sources/DiffView/DiffLine.swift +++ b/Packages/Sources/DiffView/DiffLine.swift @@ -6,8 +6,8 @@ import SwiftUI /// two-column gutter. Either gutter column may be `nil` — added rows have no /// pre-edit number, removed rows have no post-edit number, hunk/meta rows have /// neither. -public struct DiffLine: Hashable, Sendable { - public enum Kind: Hashable, Sendable { +public nonisolated struct DiffLine: Hashable, Sendable { + public nonisolated enum Kind: Hashable, Sendable { case added case removed case context diff --git a/Packages/Sources/DiffView/DiffView.swift b/Packages/Sources/DiffView/DiffView.swift index 3dc0e7f9..73c0880e 100644 --- a/Packages/Sources/DiffView/DiffView.swift +++ b/Packages/Sources/DiffView/DiffView.swift @@ -25,28 +25,99 @@ public struct DiffView: View { case scroll } + public enum ChangeNavigationDirection: Equatable, Sendable { + case previous + case next + } + + public struct ChangeNavigationRequest: Equatable, Sendable { + public let direction: ChangeNavigationDirection + public let token: Int + + public init(direction: ChangeNavigationDirection, token: Int) { + self.direction = direction + self.token = token + } + } + + public struct ChangeNavigationState: Equatable, Sendable { + public let changeCount: Int + public let currentIndex: Int? + + public init(changeCount: Int, currentIndex: Int?) { + self.changeCount = changeCount + self.currentIndex = currentIndex + } + + public static let empty = ChangeNavigationState(changeCount: 0, currentIndex: nil) + } + private let lines: [DiffLine] private let showsBackground: Bool private let display: LineDisplay private let language: String? + private let navigationRequest: ChangeNavigationRequest? + private let onNavigationStateChange: ((ChangeNavigationState) -> Void)? + + @State private var selectedChangeBlockIndex: Int? + @State private var verticalScrollPosition: String? public init( lines: [DiffLine], showsBackground: Bool = true, display: LineDisplay = .wrap, - language: String? = nil + language: String? = nil, + navigationRequest: ChangeNavigationRequest? = nil, + onNavigationStateChange: ((ChangeNavigationState) -> Void)? = nil ) { self.lines = lines self.showsBackground = showsBackground self.display = display self.language = language + self.navigationRequest = navigationRequest + self.onNavigationStateChange = onNavigationStateChange } public var body: some View { let layout = GutterLayout(lines: lines) - content(layout: layout) - .background(showsBackground ? ClaudeTheme.codeBackground : Color.clear) - .textSelection(.enabled) + let changeBlocks = Self.changeBlockOffsets(in: lines) + ScrollViewReader { proxy in + content(layout: layout) + .background(showsBackground ? ClaudeTheme.codeBackground : Color.clear) + .textSelection(.enabled) + .onAppear { + publishNavigationState(changeBlocks: changeBlocks) + } + .onChange(of: lines) { _, newLines in + selectedChangeBlockIndex = nil + verticalScrollPosition = nil + publishNavigationState(changeBlocks: Self.changeBlockOffsets(in: newLines)) + } + .onChange(of: navigationRequest) { _, request in + guard let request else { return } + navigateChanges( + direction: request.direction, + changeBlocks: changeBlocks, + proxy: proxy + ) + } + } + } + + public nonisolated static func changeBlockOffsets(in lines: [DiffLine]) -> [Int] { + var offsets: [Int] = [] + var previousWasChange = false + for (offset, line) in lines.enumerated() { + let isChange = switch line.kind { + case .added, .removed: true + case .context, .hunk, .meta: false + } + if isChange, !previousWasChange { + offsets.append(offset) + } + previousWasChange = isChange + } + return offsets } @ViewBuilder @@ -55,24 +126,33 @@ public struct DiffView: View { case .wrap: ScrollView(.vertical) { LazyVStack(alignment: .leading, spacing: 0) { - ForEach(Array(lines.enumerated()), id: \.offset) { _, line in + ForEach(Array(lines.enumerated()), id: \.offset) { offset, line in DiffRow(line: line, layout: layout, wraps: true, language: language) + .id(rowID(for: offset)) } } + .scrollTargetLayout() .frame(maxWidth: .infinity, alignment: .leading) } + .scrollPosition(id: $verticalScrollPosition, anchor: .top) case .scroll: GeometryReader { proxy in let viewportWidth = max(0, proxy.size.width - layout.width) + let bodyWidth = max( + viewportWidth, + Self.horizontalScrollBodyWidth(for: lines, layout: layout) + ) ScrollView(.vertical) { HStack(alignment: .top, spacing: 0) { // Sticky gutter column — sits outside the horizontal // scroll so line numbers stay anchored on the left. LazyVStack(alignment: .leading, spacing: 0) { - ForEach(Array(lines.enumerated()), id: \.offset) { _, line in + ForEach(Array(lines.enumerated()), id: \.offset) { offset, line in DiffRowGutter(line: line, layout: layout) + .id(rowID(for: offset)) } } + .scrollTargetLayout() // Horizontally scrollable body. Constrain the viewport // to the visible remainder; otherwise the vertical // scroll layout can offer an effectively unbounded @@ -86,17 +166,90 @@ public struct DiffView: View { wraps: false, language: language ) + .frame(width: bodyWidth, alignment: .leading) } } - .frame(minWidth: viewportWidth, alignment: .leading) + .frame(width: bodyWidth, alignment: .leading) } .frame(width: viewportWidth, alignment: .leading) } .frame(width: proxy.size.width, alignment: .leading) } + .scrollPosition(id: $verticalScrollPosition, anchor: .top) } } } + + private func navigateChanges( + direction: ChangeNavigationDirection, + changeBlocks: [Int], + proxy: ScrollViewProxy + ) { + guard !changeBlocks.isEmpty else { + selectedChangeBlockIndex = nil + publishNavigationState(changeBlocks: changeBlocks) + return + } + + let nextIndex: Int + switch direction { + case .previous: + nextIndex = ((selectedChangeBlockIndex ?? changeBlocks.count) - 1 + changeBlocks.count) % changeBlocks.count + case .next: + nextIndex = ((selectedChangeBlockIndex ?? -1) + 1) % changeBlocks.count + } + + selectedChangeBlockIndex = nextIndex + let targetID = rowID(for: changeBlocks[nextIndex]) + verticalScrollPosition = targetID + withAnimation(.easeInOut(duration: 0.18)) { + proxy.scrollTo(targetID, anchor: .top) + } + publishNavigationState(changeBlocks: changeBlocks) + } + + private func publishNavigationState(changeBlocks: [Int]) { + let current = selectedChangeBlockIndex.flatMap { index in + changeBlocks.indices.contains(index) ? index : nil + } + onNavigationStateChange?(.init(changeCount: changeBlocks.count, currentIndex: current)) + } + + private func rowID(for offset: Int) -> String { + "diff-line-\(offset)" + } + + static func horizontalScrollBodyWidth(for lines: [DiffLine], layout: GutterLayout) -> CGFloat { + let maxColumns = lines.map { horizontalColumnCount(for: $0, layout: layout) }.max() ?? 0 + let estimatedMonospaceWidth = ClaudeTheme.messageSize(12) * 0.72 + return ceil(CGFloat(maxColumns) * estimatedMonospaceWidth) + 12 + } + + private static func horizontalColumnCount(for line: DiffLine, layout: GutterLayout) -> Int { + let prefixColumns: Int + let body: String + switch line.kind { + case .added: + prefixColumns = 2 + body = line.text.hasPrefix("+") ? String(line.text.dropFirst()) : line.text + case .removed: + prefixColumns = 2 + body = line.text.hasPrefix("-") ? String(line.text.dropFirst()) : line.text + case .context: + prefixColumns = 2 + body = line.text.hasPrefix(" ") ? String(line.text.dropFirst()) : line.text + case .hunk, .meta: + prefixColumns = layout.columnCount > 0 ? 2 : 0 + body = line.text + } + return prefixColumns + visualColumnCount(body) + } + + private static func visualColumnCount(_ text: String) -> Int { + text.reduce(0) { count, character in + count + (character == "\t" ? 4 : 1) + } + } } // MARK: - Gutter Layout diff --git a/Packages/Sources/RxCodeChatKit/ChangeDiffView.swift b/Packages/Sources/RxCodeChatKit/ChangeDiffView.swift index bf6839b9..88ad6532 100644 --- a/Packages/Sources/RxCodeChatKit/ChangeDiffView.swift +++ b/Packages/Sources/RxCodeChatKit/ChangeDiffView.swift @@ -15,28 +15,55 @@ public struct ChangeDiffView: View { private let lines: [DiffLine] private let display: DiffView.LineDisplay private let language: String? + private let navigationRequest: DiffView.ChangeNavigationRequest? + private let onNavigationStateChange: ((DiffView.ChangeNavigationState) -> Void)? /// Renders a raw unified diff, e.g. `git diff` output. - public init(unifiedDiff: String, display: DiffView.LineDisplay = .wrap, language: String? = nil) { + public init( + unifiedDiff: String, + display: DiffView.LineDisplay = .wrap, + language: String? = nil, + navigationRequest: DiffView.ChangeNavigationRequest? = nil, + onNavigationStateChange: ((DiffView.ChangeNavigationState) -> Void)? = nil + ) { self.lines = DiffComputation.parseUnifiedDiff(unifiedDiff) self.display = display self.language = language + self.navigationRequest = navigationRequest + self.onNavigationStateChange = onNavigationStateChange } /// Renders old/new replacement pairs as a removed-then-added diff. - public init(hunks: [PreviewFile.EditHunk], display: DiffView.LineDisplay = .wrap, language: String? = nil) { + public init( + hunks: [PreviewFile.EditHunk], + display: DiffView.LineDisplay = .wrap, + language: String? = nil, + navigationRequest: DiffView.ChangeNavigationRequest? = nil, + onNavigationStateChange: ((DiffView.ChangeNavigationState) -> Void)? = nil + ) { self.lines = DiffComputation.buildEditDiffLines(from: hunks) self.display = display self.language = language + self.navigationRequest = navigationRequest + self.onNavigationStateChange = onNavigationStateChange } /// Renders the diff between a pre-edit snapshot and a post-edit snapshot. /// Preferred when both snapshots are available — gives an exact, thread- /// isolated diff with no dependence on hunk anchoring or disk state. - public init(original: String, modified: String, display: DiffView.LineDisplay = .wrap, language: String? = nil) { + public init( + original: String, + modified: String, + display: DiffView.LineDisplay = .wrap, + language: String? = nil, + navigationRequest: DiffView.ChangeNavigationRequest? = nil, + onNavigationStateChange: ((DiffView.ChangeNavigationState) -> Void)? = nil + ) { self.lines = DiffComputation.buildSnapshotDiffLines(original: original, current: modified) self.display = display self.language = language + self.navigationRequest = navigationRequest + self.onNavigationStateChange = onNavigationStateChange } /// `+/-` counts derived from the same snapshot diff that @@ -57,6 +84,13 @@ public struct ChangeDiffView: View { } public var body: some View { - DiffView(lines: lines, showsBackground: false, display: display, language: language) + DiffView( + lines: lines, + showsBackground: false, + display: display, + language: language, + navigationRequest: navigationRequest, + onNavigationStateChange: onNavigationStateChange + ) } } diff --git a/Packages/Sources/RxCodeChatKit/FileDiffView.swift b/Packages/Sources/RxCodeChatKit/FileDiffView.swift index 39dc4fb4..af4fb516 100644 --- a/Packages/Sources/RxCodeChatKit/FileDiffView.swift +++ b/Packages/Sources/RxCodeChatKit/FileDiffView.swift @@ -22,6 +22,9 @@ public struct FileDiffView: View { @State private var isLoading = true @State private var isCopied = false @State private var diffDisplay: DiffView.LineDisplay = .wrap + @State private var navigationToken = 0 + @State private var navigationDirection: DiffView.ChangeNavigationDirection? + @State private var navigationState = DiffView.ChangeNavigationState.empty public init( filePath: String, @@ -46,6 +49,10 @@ public struct FileDiffView: View { header ClaudeThemeDivider() contentArea + if !isLoading, !diffLines.isEmpty { + ClaudeThemeDivider() + changeNavigationBar + } } .background(ClaudeTheme.background) .background { @@ -151,16 +158,89 @@ public struct FileDiffView: View { DiffView( lines: diffLines, display: diffDisplay, - language: SyntaxHighlighter.language(forFilename: fileName) + language: SyntaxHighlighter.language(forFilename: fileName), + navigationRequest: navigationRequest, + onNavigationStateChange: { navigationState = $0 } ) .frame(maxWidth: .infinity, maxHeight: .infinity) } } + // MARK: - Change Navigation + + private var changeNavigationBar: some View { + HStack(spacing: 8) { + Button { + requestChangeNavigation(.previous) + } label: { + Image(systemName: "arrow.up") + .font(.system(size: ClaudeTheme.messageSize(12), weight: .medium)) + .foregroundStyle(ClaudeTheme.textSecondary) + .frame(width: 26, height: 24) + } + .buttonStyle(.borderless) + .focusable(false) + .disabled(navigationState.changeCount == 0) + .help("Previous change") + .accessibilityLabel("Previous change") + + Button { + requestChangeNavigation(.next) + } label: { + Image(systemName: "arrow.down") + .font(.system(size: ClaudeTheme.messageSize(12), weight: .medium)) + .foregroundStyle(ClaudeTheme.textSecondary) + .frame(width: 26, height: 24) + } + .buttonStyle(.borderless) + .focusable(false) + .disabled(navigationState.changeCount == 0) + .help("Next change") + .accessibilityLabel("Next change") + + changePositionPill + + Spacer() + } + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(ClaudeTheme.surfacePrimary) + } + + private var navigationRequest: DiffView.ChangeNavigationRequest? { + guard let navigationDirection else { return nil } + return DiffView.ChangeNavigationRequest(direction: navigationDirection, token: navigationToken) + } + + private var changePositionText: String { + guard navigationState.changeCount > 0 else { return "No changes" } + guard let currentIndex = navigationState.currentIndex else { + return "\(navigationState.changeCount) changes" + } + return "\(currentIndex + 1) of \(navigationState.changeCount)" + } + + private var changePositionPill: some View { + Text(changePositionText) + .font(.system(size: ClaudeTheme.messageSize(11), weight: .medium)) + .foregroundStyle(ClaudeTheme.textTertiary) + .monospacedDigit() + .lineLimit(1) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background(ClaudeTheme.surfaceSecondary, in: Capsule()) + } + + private func requestChangeNavigation(_ direction: DiffView.ChangeNavigationDirection) { + navigationDirection = direction + navigationToken += 1 + } + // MARK: - Diff Sources private func loadDiff() async { isLoading = true + navigationState = .empty defer { isLoading = false } // Snapshot-pair path: both originalContent and modifiedContent were diff --git a/Packages/Sources/RxCodeChatKit/InputBarView.swift b/Packages/Sources/RxCodeChatKit/InputBarView.swift index c453f635..3ab8811e 100644 --- a/Packages/Sources/RxCodeChatKit/InputBarView.swift +++ b/Packages/Sources/RxCodeChatKit/InputBarView.swift @@ -86,9 +86,18 @@ struct InputBarView: View { .padding(.horizontal, 8) .padding(.top, 0) .padding(.bottom, 12) - .sheet(item: $slashDetailCommand) { cmd in CommandDetailSheet(command: cmd) } - .sheet(item: $textPreviewAttachment) { attachment in TextPreviewSheet(attachment: attachment) } - .sheet(item: $imagePreviewAttachment) { attachment in ImagePreviewSheet(attachment: attachment) } + .sheet(item: $slashDetailCommand) { cmd in + CommandDetailSheet(command: cmd) + .mobileSheetPresentation() + } + .sheet(item: $textPreviewAttachment) { attachment in + TextPreviewSheet(attachment: attachment) + .mobileSheetPresentation() + } + .sheet(item: $imagePreviewAttachment) { attachment in + ImagePreviewSheet(attachment: attachment) + .mobileSheetPresentation() + } .onDrop(of: [.fileURL, .image], isTargeted: $isDragOver) { providers in processItemProviders(providers) return true diff --git a/Packages/Sources/RxCodeChatKit/MessageBubble.swift b/Packages/Sources/RxCodeChatKit/MessageBubble.swift index b1197031..2badeb5b 100644 --- a/Packages/Sources/RxCodeChatKit/MessageBubble.swift +++ b/Packages/Sources/RxCodeChatKit/MessageBubble.swift @@ -138,6 +138,7 @@ struct MessageBubble: View { set: { previewImagePath = $0?.path } )) { item in MessageImagePreviewSheet(path: item.path) { previewImagePath = nil } + .mobileSheetPresentation() } } diff --git a/Packages/Sources/RxCodeChatKit/MobileSheetPresentationModifier.swift b/Packages/Sources/RxCodeChatKit/MobileSheetPresentationModifier.swift new file mode 100644 index 00000000..ec6f8684 --- /dev/null +++ b/Packages/Sources/RxCodeChatKit/MobileSheetPresentationModifier.swift @@ -0,0 +1,14 @@ +import SwiftUI + +extension View { + @ViewBuilder + func mobileSheetPresentation() -> some View { +#if os(iOS) + self + .presentationDragIndicator(.hidden) + .interactiveDismissDisabled(true) +#else + self +#endif + } +} diff --git a/Packages/Sources/RxCodeChatKit/Resources/Localizable.xcstrings b/Packages/Sources/RxCodeChatKit/Resources/Localizable.xcstrings index 8ca9a95a..3d96fd5c 100644 --- a/Packages/Sources/RxCodeChatKit/Resources/Localizable.xcstrings +++ b/Packages/Sources/RxCodeChatKit/Resources/Localizable.xcstrings @@ -1,5682 +1,5694 @@ { - "sourceLanguage": "en", - "strings": { - "": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "" + "sourceLanguage" : "en", + "strings" : { + "" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "" } } } }, - " · ": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": " · " + " · " : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : " · " } } } }, - "--": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "--" + "--" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "--" } } } }, - "·": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "·" + "·" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "·" } } } }, - "(no output)": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "(无输出)" + "(no output)" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "(无输出)" } } } }, - "/": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "/" + "/" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "/" } } } }, - "%@ — ": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ — " + "%@ — " : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ — " } } } }, - "%@ %@": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%1$@ %2$@" + "%@ %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%1$@ %2$@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$@" } } } }, - "%@ in progress": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 进行中" + "%@ in progress" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 进行中" } } } }, - "%lld": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld" + "%lld" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld" } } } }, - "%lld commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%lld commands" + "%lld commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%lld commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "%lld개 커맨드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld개 커맨드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个命令" } } } }, - "%lld messages queued": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 条消息已排队" + "%lld messages queued" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 条消息已排队" } } } }, - "%lld shortcuts": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%lld shortcuts" + "%lld shortcuts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%lld shortcuts" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "%lld개 단축키" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld개 단축키" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个快捷方式" } } } }, - "%lld tools executed": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%lld tools executed" + "%lld tools executed" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%lld tools executed" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "%lld개 도구 실행됨" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld개 도구 실행됨" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已执行 %lld 个工具" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已执行 %lld 个工具" } } } }, - "%lld/%lld": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%1$lld/%2$lld" + "%lld/%lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld/%2$lld" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%1$lld/%2$lld" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld/%2$lld" } } } }, - "%lld%%": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld%%" + "%lld%%" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld%%" } } } }, - "+%d more pending": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "+%d more pending" + "+%d more pending" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "+%d more pending" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "还有 %d 个待处理" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "还有 %d 个待处理" } } } }, - "$": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "$" + "$" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "$" } } } }, - "5-hour rate limit": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "5-hour rate limit" + "5-hour rate limit" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "5-hour rate limit" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "5시간 사용량 한도" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "5시간 사용량 한도" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "5 小时用量限制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "5 小时用量限制" } } } }, - "5h": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "5h" + "5h" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "5h" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "5시간" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "5시간" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "5小时" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "5小时" } } } }, - "7-day rate limit": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "7-day rate limit" + "7-day rate limit" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "7-day rate limit" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "7일 사용량 한도" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "7일 사용량 한도" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "7 天用量限制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 天用量限制" } } } }, - "7d": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "7d" + "7d" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "7d" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "7일" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "7일" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "7天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "7天" } } } }, - "A command with this name already exists.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "A command with this name already exists." + "A command with this name already exists." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "A command with this name already exists." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "이미 존재하는 커맨드 이름입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이미 존재하는 커맨드 이름입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已存在同名命令。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已存在同名命令。" } } } }, - "Accept + Auto": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Accept + Auto" + "Accept + Auto" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Accept + Auto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "接受 + 自动" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "接受 + 自动" } } } }, - "Accept Ask": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Accept Ask" + "Accept Ask" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Accept Ask" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "接受并询问" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "接受并询问" } } } }, - "Accept Edits": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "接受编辑" + "Accept Edits" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "接受编辑" } } } }, - "accepts input": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "accepts input" + "accepts input" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "accepts input" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "입력 허용" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "입력 허용" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "接受输入" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "接受输入" } } } }, - "Accepts Input": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Accepts Input" + "Accepts Input" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Accepts Input" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "입력 허용" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "입력 허용" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "接受输入" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "接受输入" } } } }, - "acceptsInput: true lets users type additional text after the command.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "acceptsInput: true lets users type additional text after the command." + "acceptsInput: true lets users type additional text after the command." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "acceptsInput: true lets users type additional text after the command." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "acceptsInput: true이면 커맨드 뒤에 추가 텍스트를 입력할 수 있습니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "acceptsInput: true이면 커맨드 뒤에 추가 텍스트를 입력할 수 있습니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "acceptsInput:设为 true 后,用户可以在命令后输入额外文本。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "acceptsInput:设为 true 后,用户可以在命令后输入额外文本。" } } } }, - "Add": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Add" + "Add" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "추가" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "추가" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加" } } } }, - "Add — attach file or toggle plan mode": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加 — 附加文件或切换计划模式" + "Add — attach file or toggle plan mode" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加 — 附加文件或切换计划模式" } } } }, - "Add First Shortcut": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Add First Shortcut" + "Add First Shortcut" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add First Shortcut" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "첫 단축키 추가" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "첫 단축키 추가" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加第一个快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加第一个快捷方式" } } } }, - "Add New Command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Add New Command" + "Add New Command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add New Command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 커맨드 추가" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "새 커맨드 추가" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加新命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加新命令" } } } }, - "Add New Shortcut": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Add New Shortcut" + "Add New Shortcut" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add New Shortcut" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 단축키 추가" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "새 단축키 추가" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加新快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加新快捷方式" } } } }, - "Add working directory to session": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Add working directory to session" + "Add working directory to session" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Add working directory to session" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "세션에 작업 디렉토리 추가" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "세션에 작업 디렉토리 추가" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将工作目录添加到会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将工作目录添加到会话" } } } }, - "Agent": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "智能体" + "Agent" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "智能体" } } } }, - "Agent asked %d questions": { - "comment": "Inline summary when the assistant invokes AskUserQuestion with multiple questions. %d is the number of questions.", - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "智能体提出了 %d 个问题" + "Agent asked %d questions" : { + "comment" : "Inline summary when the assistant invokes AskUserQuestion with multiple questions. %d is the number of questions.", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "智能体提出了 %d 个问题" } } } }, - "Agent asked a question": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "智能体提出了一个问题" + "Agent asked a question" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "智能体提出了一个问题" } } } }, - "agentProvider: agent this command applies to (claudeCode, codex, acp). Use null or omit it for all agents.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "agentProvider: agent this command applies to (claudeCode, codex, acp). Use null or omit it for all agents." + "agentProvider: agent this command applies to (claudeCode, codex, acp). Use null or omit it for all agents." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "agentProvider: agent this command applies to (claudeCode, codex, acp). Use null or omit it for all agents." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "agentProvider:此命令适用的代理(claudeCode、codex、acp)。设为 null 或省略表示适用于所有代理。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "agentProvider:此命令适用的代理(claudeCode、codex、acp)。设为 null 或省略表示适用于所有代理。" } } } }, - "All Agents": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "所有智能体" + "All Agents" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有智能体" } } } }, - "All Agents (Global)": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "所有智能体(全局)" + "All Agents (Global)" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有智能体(全局)" } } } }, - "All modified default commands will be restored to their original state.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "All modified default commands will be restored to their original state." + "All modified default commands will be restored to their original state." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "All modified default commands will be restored to their original state." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "수정한 모든 기본 커맨드가 원래 상태로 복원됩니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "수정한 모든 기본 커맨드가 원래 상태로 복원됩니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "所有修改过的默认命令都会恢复到原始状态。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有修改过的默认命令都会恢复到原始状态。" } } } }, - "All properties example:": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "All properties example:" + "All properties example:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "All properties example:" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "모든 속성 예시:" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "모든 속성 예시:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "完整属性示例:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "完整属性示例:" } } } }, - "Allows additional text to be entered after the command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Allows additional text to be entered after the command" + "Allows additional text to be entered after the command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Allows additional text to be entered after the command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 뒤에 추가 텍스트를 입력할 수 있습니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 뒤에 추가 텍스트를 입력할 수 있습니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "允许在命令后输入额外文本" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "允许在命令后输入额外文本" } } } }, - "Analyze security vulnerabilities": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Analyze security vulnerabilities" + "Analyze security vulnerabilities" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Analyze security vulnerabilities" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "현재 브랜치 변경사항 보안 분석" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 브랜치 변경사항 보안 분석" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "分析安全漏洞" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分析安全漏洞" } } } }, - "Answered": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已回答" + "Answered" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已回答" } } } }, - "Assistant: %@": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "助手:%@" + "Assistant: %@" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "助手:%@" } } } }, - "Attach file…": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Attach file…" + "Attach file…" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Attach file…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "附加文件…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "附加文件…" } } } }, - "Auto-fix PR CI failures and review comments": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Auto-fix PR CI failures and review comments" + "Auto-fix PR CI failures and review comments" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Auto-fix PR CI failures and review comments" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "PR CI 실패 및 리뷰 댓글 자동 수정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "PR CI 실패 및 리뷰 댓글 자동 수정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "自动修复 PR CI 失败和评审评论" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动修复 PR CI 失败和评审评论" } } } }, - "Bash": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Bash" + "Bash" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bash" } } } }, - "Built-in command names are ignored on import and are never exported.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Built-in command names are ignored on import and are never exported." + "Built-in command names are ignored on import and are never exported." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Built-in command names are ignored on import and are never exported." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "기본 명령어 이름은 가져올 때 무시되며 내보내기에 포함되지 않습니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기본 명령어 이름은 가져올 때 무시되며 내보내기에 포함되지 않습니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "内置命令名称在导入时会被忽略,也不会被导出。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "内置命令名称在导入时会被忽略,也不会被导出。" } } } }, - "Built-in commands are Claude Code only. Custom commands can target one agent or all.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "内置命令仅适用于 Claude Code。自定义命令可以针对一个智能体或全部智能体。" + "Built-in commands are Claude Code only. Custom commands can target one agent or all." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "内置命令仅适用于 Claude Code。自定义命令可以针对一个智能体或全部智能体。" } } } }, - "Cache creation": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Cache creation" + "Cache creation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cache creation" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "캐시 생성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "캐시 생성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "创建缓存" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建缓存" } } } }, - "Cache read": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Cache read" + "Cache read" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cache read" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "캐시 읽기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "캐시 읽기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "读取缓存" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "读取缓存" } } } }, - "Cancel": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Cancel" + "Cancel" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cancel" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "취소" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "취소" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "取消" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "取消" } } } }, - "Change color theme": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Change color theme" + "Change color theme" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Change color theme" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "색상 테마 변경" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "색상 테마 변경" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "更改颜色主题" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更改颜色主题" } } } }, - "Client: %@": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "客户端:%@" + "Client: %@" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端:%@" } } } }, - "Close": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Close" + "Close" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Close" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "닫기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "닫기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" } } } }, - "Close — you can review later from the banner": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Close — you can review later from the banner" + "Close — you can review later from the banner" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Close — you can review later from the banner" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭 — 之后可从横幅继续查看" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭 — 之后可从横幅继续查看" } } } }, - "Collapse": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Collapse" + "Collapse" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Collapse" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "접기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "접기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "折叠" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "折叠" } } } }, - "Combine and send all queued messages as a single turn": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将所有排队消息合并为一轮发送" + "Combine and send all queued messages as a single turn" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将所有排队消息合并为一轮发送" } } } }, - "Command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Command" + "Command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令" } } } }, - "Command name (e.g. my-command)": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Command name (e.g. my-command)" + "Command name (e.g. my-command)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Command name (e.g. my-command)" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 이름 (예: my-command)" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 이름 (예: my-command)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "命令名称(例如 my-command)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令名称(例如 my-command)" } } } }, - "Commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Commands" + "Commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令" } } } }, - "Commands requiring TUI will run in the inline terminal": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Commands requiring TUI will run in the inline terminal" + "Commands requiring TUI will run in the inline terminal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Commands requiring TUI will run in the inline terminal" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "TUI가 필요한 커맨드는 인라인 터미널에서 실행됩니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "TUI가 필요한 커맨드는 인라인 터미널에서 실행됩니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "需要 TUI 的命令会在内联终端中运行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要 TUI 的命令会在内联终端中运行" } } } }, - "Compact conversation (focus instructions allowed)": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Compact conversation (focus instructions allowed)" + "Compact conversation (focus instructions allowed)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Compact conversation (focus instructions allowed)" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "대화 압축 (선택적 포커스 지침 허용)" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "대화 압축 (선택적 포커스 지침 허용)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "压缩对话(允许保留重点说明)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "压缩对话(允许保留重点说明)" } } } }, - "Completed": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Completed" + "Completed" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Completed" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "완료됨" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "완료됨" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已完成" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已完成" } } } }, - "Configure Claude in Chrome": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Configure Claude in Chrome" + "Configure Claude in Chrome" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Configure Claude in Chrome" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Chrome에서 Claude 설정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chrome에서 Claude 설정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 Chrome 中配置 Claude" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 Chrome 中配置 Claude" } } } }, - "Configure extra usage": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Configure extra usage" + "Configure extra usage" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Configure extra usage" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "속도 제한 초과 시 추가 사용량 구성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "속도 제한 초과 시 추가 사용량 구성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置额外用量" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置额外用量" } } } }, - "Configure keybindings": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Configure keybindings" + "Configure keybindings" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Configure keybindings" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "키바인딩 구성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "키바인딩 구성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置键位绑定" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置键位绑定" } } } }, - "Configure remote environment": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Configure remote environment" + "Configure remote environment" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Configure remote environment" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "원격 환경 구성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "원격 환경 구성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置远程环境" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置远程环境" } } } }, - "Configure status line": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Configure status line" + "Configure status line" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Configure status line" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "상태 표시줄 구성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "상태 표시줄 구성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置状态行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置状态行" } } } }, - "Configure terminal keybindings": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Configure terminal keybindings" + "Configure terminal keybindings" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Configure terminal keybindings" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널 키바인딩 구성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "터미널 키바인딩 구성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置终端键位绑定" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置终端键位绑定" } } } }, - "Connect GitHub account to Claude Code on the web": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Connect GitHub account to Claude Code on the web" + "Connect GitHub account to Claude Code on the web" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Connect GitHub account to Claude Code on the web" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "GitHub 계정을 Claude Code 웹에 연결" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "GitHub 계정을 Claude Code 웹에 연결" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将 GitHub 账号连接到网页版 Claude Code" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将 GitHub 账号连接到网页版 Claude Code" } } } }, - "context": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "context" + "context" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "context" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "컨텍스트" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "컨텍스트" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上下文" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上下文" } } } }, - "Context window": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Context window" + "Context window" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Context window" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "컨텍스트 윈도우" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "컨텍스트 윈도우" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上下文窗口" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上下文窗口" } } } }, - "Continue in Desktop app": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Continue in Desktop app" + "Continue in Desktop app" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Continue in Desktop app" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "데스크톱 앱에서 세션 계속" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "데스크톱 앱에서 세션 계속" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在桌面应用中继续" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在桌面应用中继续" } } } }, - "Copied": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Copied" + "Copied" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copied" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "복사됨" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "복사됨" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已复制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已复制" } } } }, - "Copy": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Copy" + "Copy" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "복사" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "복사" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制" } } } }, - "Copy Conversation": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Copy Conversation" + "Copy Conversation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy Conversation" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "대화 복사" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "대화 복사" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制对话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制对话" } } } }, - "Copy last response to clipboard": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Copy last response to clipboard" + "Copy last response to clipboard" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy last response to clipboard" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "마지막 응답을 클립보드에 복사" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "마지막 응답을 클립보드에 복사" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将最后一条响应复制到剪贴板" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将最后一条响应复制到剪贴板" } } } }, - "Copy output": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制输出" + "Copy output" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制输出" } } } }, - "Copy plan": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Copy plan" + "Copy plan" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy plan" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制计划" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制计划" } } } }, - "Cost": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Cost" + "Cost" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cost" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "비용" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "비용" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "费用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "费用" } } } }, - "Create a branch of current conversation": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Create a branch of current conversation" + "Create a branch of current conversation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Create a branch of current conversation" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "현재 대화의 브랜치 생성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 대화의 브랜치 생성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从当前对话创建分支" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从当前对话创建分支" } } } }, - "Create new file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Create new file" + "Create new file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Create new file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 파일 생성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "새 파일 생성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "创建新文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建新文件" } } } }, - "Currently at %lld%%": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Currently at %lld%%" + "Currently at %lld%%" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Currently at %lld%%" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "현재 %lld%%" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 %lld%%" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "当前为 %lld%%" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前为 %lld%%" } } } }, - "Daily usage and session history visualization": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Daily usage and session history visualization" + "Daily usage and session history visualization" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Daily usage and session history visualization" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "일일 사용량, 세션 기록, 연속 기록 시각화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "일일 사용량, 세션 기록, 연속 기록 시각화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "每日用量和会话历史可视化" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每日用量和会话历史可视化" } } } }, - "Decided": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Decided" + "Decided" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Decided" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已决定" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已决定" } } } }, - "Deep multi-agent cloud code review": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Deep multi-agent cloud code review" + "Deep multi-agent cloud code review" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Deep multi-agent cloud code review" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "다중 에이전트 심층 클라우드 코드 리뷰" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "다중 에이전트 심층 클라우드 코드 리뷰" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "深度多智能体云端代码评审" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "深度多智能体云端代码评审" } } } }, - "default": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "default" + "default" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "default" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "기본" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기본" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "默认" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认" } } } }, - "Delete": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Delete" + "Delete" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Delete" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "삭제" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "삭제" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "删除" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除" } } } }, - "Description": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Description" + "Description" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Description" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "설명" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "설명" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "描述" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "描述" } } } }, - "description: short text shown in the command picker.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "description: short text shown in the command picker." + "description: short text shown in the command picker." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "description: short text shown in the command picker." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "description: 커맨드 선택기에 표시되는 짧은 설명입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "description: 커맨드 선택기에 표시되는 짧은 설명입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "description:显示在命令选择器中的简短文本。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "description:显示在命令选择器中的简短文本。" } } } }, - "Detail Description (optional)": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Detail Description (optional)" + "Detail Description (optional)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Detail Description (optional)" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "상세 설명 (선택사항)" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "상세 설명 (선택사항)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "详细描述(可选)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "详细描述(可选)" } } } }, - "detailDescription: optional longer text shown in command details. Use null or omit it when empty.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "detailDescription: optional longer text shown in command details. Use null or omit it when empty." + "detailDescription: optional longer text shown in command details. Use null or omit it when empty." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "detailDescription: optional longer text shown in command details. Use null or omit it when empty." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "detailDescription: 커맨드 상세에 표시되는 선택적 긴 설명입니다. 비어 있으면 null을 쓰거나 생략하세요." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "detailDescription: 커맨드 상세에 표시되는 선택적 긴 설명입니다. 비어 있으면 null을 쓰거나 생략하세요." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "detailDescription:显示在命令详情中的可选长文本。为空时使用 null 或省略。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "detailDescription:显示在命令详情中的可选长文本。为空时使用 null 或省略。" } } } }, - "Diagnose installation/configuration": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Diagnose installation/configuration" + "Diagnose installation/configuration" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Diagnose installation/configuration" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "설치 및 설정 진단" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "설치 및 설정 진단" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "诊断安装/配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "诊断安装/配置" } } } }, - "Diff": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Diff" + "Diff" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Diff" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Diff" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diff" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "差异" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "差异" } } } }, - "Diff viewer for uncommitted changes": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Diff viewer for uncommitted changes" + "Diff viewer for uncommitted changes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Diff viewer for uncommitted changes" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "미커밋 변경사항 diff 뷰어" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "미커밋 변경사항 diff 뷰어" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未提交更改的差异查看器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未提交更改的差异查看器" } } } }, - "Disable": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Disable" + "Disable" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Disable" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "비활성화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "비활성화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "停用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停用" } } } }, - "Draft a plan in an ultraplan session": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Draft a plan in an ultraplan session" + "Draft a plan in an ultraplan session" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Draft a plan in an ultraplan session" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Ultraplan 세션에서 계획 작성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ultraplan 세션에서 계획 작성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 ultraplan 会话中起草计划" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 ultraplan 会话中起草计划" } } } }, - "Drafting plan…": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "正在起草计划…" + "Drafting plan…" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在起草计划…" } } } }, - "Duration": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Duration" + "Duration" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Duration" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "소요 시간" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "소요 시간" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "持续时间" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "持续时间" } } } }, - "Each custom command supports these properties:": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Each custom command supports these properties:" + "Each custom command supports these properties:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Each custom command supports these properties:" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "각 커스텀 커맨드는 다음 속성을 지원합니다:" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "각 커스텀 커맨드는 다음 속성을 지원합니다:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "每个自定义命令支持这些属性:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每个自定义命令支持这些属性:" } } } }, - "Each shortcut supports these properties:": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Each shortcut supports these properties:" + "Each shortcut supports these properties:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Each shortcut supports these properties:" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "각 단축키는 다음 속성을 지원합니다:" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "각 단축키는 다음 속성을 지원합니다:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "每个快捷方式支持这些属性:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "每个快捷方式支持这些属性:" } } } }, - "Edit CLAUDE.md memory file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit CLAUDE.md memory file" + "Edit CLAUDE.md memory file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit CLAUDE.md memory file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "CLAUDE.md 메모리 파일 편집" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "CLAUDE.md 메모리 파일 편집" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑 CLAUDE.md 记忆文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑 CLAUDE.md 记忆文件" } } } }, - "Edit Command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit Command" + "Edit Command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit Command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 편집" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 편집" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑命令" } } } }, - "Edit file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit file" + "Edit file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "파일 편집" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "파일 편집" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑文件" } } } }, - "Edit message...": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit message..." + "Edit message..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit message..." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "메시지 편집..." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "메시지 편집..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑消息..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑消息..." } } } }, - "Edit multiple locations": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit multiple locations" + "Edit multiple locations" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit multiple locations" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "여러 위치 편집" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "여러 위치 편집" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑多个位置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑多个位置" } } } }, - "Edit notebook": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit notebook" + "Edit notebook" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit notebook" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "노트북 편집" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "노트북 편집" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑笔记本" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑笔记本" } } } }, - "Edit Shortcut": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit Shortcut" + "Edit Shortcut" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit Shortcut" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키 편집" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키 편집" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑快捷方式" } } } }, - "Edit the array below to add, remove, or change custom slash commands.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit the array below to add, remove, or change custom slash commands." + "Edit the array below to add, remove, or change custom slash commands." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit the array below to add, remove, or change custom slash commands." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "아래 배열을 편집해서 커스텀 슬래시 커맨드를 추가, 삭제, 변경하세요." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "아래 배열을 편집해서 커스텀 슬래시 커맨드를 추가, 삭제, 변경하세요." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑下面的数组以添加、移除或更改自定义斜杠命令。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑下面的数组以添加、移除或更改自定义斜杠命令。" } } } }, - "Edit the array below to add, remove, or change shortcuts.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Edit the array below to add, remove, or change shortcuts." + "Edit the array below to add, remove, or change shortcuts." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Edit the array below to add, remove, or change shortcuts." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "아래 배열을 편집해서 단축키를 추가, 삭제, 변경하세요." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "아래 배열을 편집해서 단축키를 추가, 삭제, 변경하세요." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑下面的数组以添加、移除或更改快捷方式。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑下面的数组以添加、移除或更改快捷方式。" } } } }, - "Enable": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Enable" + "Enable" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Enable" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "활성화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "활성화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用" } } } }, - "Enable debug logging and diagnose issues": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Enable debug logging and diagnose issues" + "Enable debug logging and diagnose issues" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Enable debug logging and diagnose issues" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "디버그 로그 활성화 및 문제 진단" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "디버그 로그 활성화 및 문제 진단" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启用调试日志并诊断问题" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用调试日志并诊断问题" } } } }, - "Enter plan mode": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Enter plan mode" + "Enter plan mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Enter plan mode" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "플랜 모드 진입" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "플랜 모드 진입" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "进入计划模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进入计划模式" } } } }, - "error": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "error" + "error" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "error" } } } }, - "Error occurred": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Error occurred" + "Error occurred" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Error occurred" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "오류 발생" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "오류 발생" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "发生错误" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发生错误" } } } }, - "Execution Mode": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Execution Mode" + "Execution Mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Execution Mode" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "실행 모드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "실행 모드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "执行模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "执行模式" } } } }, - "Exit CLI": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Exit CLI" + "Exit CLI" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Exit CLI" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "CLI 종료" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "CLI 종료" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "退出 CLI" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出 CLI" } } } }, - "Export": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Export" + "Export" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "내보내기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "내보내기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出" } } } }, - "Export conversation as text": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Export conversation as text" + "Export conversation as text" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export conversation as text" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "현재 대화를 텍스트로 내보내기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 대화를 텍스트로 내보내기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将对话导出为文本" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将对话导出为文本" } } } }, - "Export custom commands to a JSON file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Export custom commands to a JSON file" + "Export custom commands to a JSON file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export custom commands to a JSON file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커스텀 커맨드를 JSON 파일로 내보냅니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커스텀 커맨드를 JSON 파일로 내보냅니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将自定义命令导出为 JSON 文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将自定义命令导出为 JSON 文件" } } } }, - "Export Shortcuts": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Export Shortcuts" + "Export Shortcuts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export Shortcuts" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키 내보내기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키 내보내기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导出快捷按钮" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出快捷按钮" } } } }, - "Export shortcuts to a JSON file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Export shortcuts to a JSON file" + "Export shortcuts to a JSON file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export shortcuts to a JSON file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키를 JSON 파일로 내보냅니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키를 JSON 파일로 내보냅니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将快捷方式导出为 JSON 文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将快捷方式导出为 JSON 文件" } } } }, - "Export Slash Commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Export Slash Commands" + "Export Slash Commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Export Slash Commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "슬래시 커맨드 내보내기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "슬래시 커맨드 내보내기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导出斜杠命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出斜杠命令" } } } }, - "Files": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "文件" + "Files" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文件" } } } }, - "Find files": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Find files" + "Find files" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Find files" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "파일 찾기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "파일 찾기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查找文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查找文件" } } } }, - "Generate a one-line session summary": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Generate a one-line session summary" + "Generate a one-line session summary" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Generate a one-line session summary" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "세션 한 줄 요약 생성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "세션 한 줄 요약 생성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "生成单行会话摘要" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "生成单行会话摘要" } } } }, - "Generate a team onboarding guide": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Generate a team onboarding guide" + "Generate a team onboarding guide" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Generate a team onboarding guide" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "팀 온보딩 가이드 생성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "팀 온보딩 가이드 생성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "生成团队入门指南" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "生成团队入门指南" } } } }, - "How can I help you?": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "How can I help you?" + "How can I help you?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "How can I help you?" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "무엇을 도와드릴까요?" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "무엇을 도와드릴까요?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "有什么可以帮你?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "有什么可以帮你?" } } } }, - "id: optional unique identifier (UUID). Auto-generated on import if omitted.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "id: optional unique identifier (UUID). Auto-generated on import if omitted." + "id: optional unique identifier (UUID). Auto-generated on import if omitted." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "id: optional unique identifier (UUID). Auto-generated on import if omitted." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "id: 선택적 고유 식별자 (UUID). 가져올 때 생략하면 자동 생성됩니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "id: 선택적 고유 식별자 (UUID). 가져올 때 생략하면 자동 생성됩니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "id:可选的唯一标识符(UUID)。导入时省略则自动生成。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "id:可选的唯一标识符(UUID)。导入时省略则自动生成。" } } } }, - "Image not available": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "图像不可用" + "Image not available" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图像不可用" } } } }, - "Image unavailable": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "图像不可用" + "Image unavailable" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图像不可用" } } } }, - "Import": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import" + "Import" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "가져오기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "가져오기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入" } } } }, - "Import commands from a JSON file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import commands from a JSON file" + "Import commands from a JSON file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import commands from a JSON file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "JSON 파일에서 커맨드를 가져옵니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON 파일에서 커맨드를 가져옵니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从 JSON 文件导入命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从 JSON 文件导入命令" } } } }, - "Import Failed": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import Failed" + "Import Failed" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import Failed" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "가져오기 실패" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "가져오기 실패" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入失败" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入失败" } } } }, - "Import Shortcuts": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import Shortcuts" + "Import Shortcuts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import Shortcuts" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키 가져오기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키 가져오기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入快捷按钮" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入快捷按钮" } } } }, - "Import shortcuts from a JSON file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import shortcuts from a JSON file" + "Import shortcuts from a JSON file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import shortcuts from a JSON file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "JSON 파일에서 단축키를 가져옵니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON 파일에서 단축키를 가져옵니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从 JSON 文件导入快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从 JSON 文件导入快捷方式" } } } }, - "Import Slash Commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import Slash Commands" + "Import Slash Commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import Slash Commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "슬래시 커맨드 가져오기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "슬래시 커맨드 가져오기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入斜杠命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入斜杠命令" } } } }, - "Import Succeeded": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Import Succeeded" + "Import Succeeded" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Import Succeeded" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "가져오기 성공" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "가져오기 성공" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入成功" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入成功" } } } }, - "Imported %lld custom commands.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Imported %lld custom commands." + "Imported %lld custom commands." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Imported %lld custom commands." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "%lld개 커스텀 커맨드를 가져왔습니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld개 커스텀 커맨드를 가져왔습니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已导入 %lld 个自定义命令。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已导入 %lld 个自定义命令。" } } } }, - "Initialize project with CLAUDE.md": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Initialize project with CLAUDE.md" + "Initialize project with CLAUDE.md" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Initialize project with CLAUDE.md" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "CLAUDE.md로 프로젝트 초기화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "CLAUDE.md로 프로젝트 초기화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用 CLAUDE.md 初始化项目" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 CLAUDE.md 初始化项目" } } } }, - "Input tokens": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Input tokens" + "Input tokens" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Input tokens" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "입력 토큰" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "입력 토큰" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "输入 token" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输入 token" } } } }, - "Install Slack app": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Install Slack app" + "Install Slack app" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Install Slack app" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Slack 앱 설치" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Slack 앱 설치" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安装 Slack 应用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装 Slack 应用" } } } }, - "Interactive (Terminal)": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Interactive (Terminal)" + "Interactive (Terminal)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Interactive (Terminal)" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "인터랙티브 (터미널)" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "인터랙티브 (터미널)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "交互式(终端)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "交互式(终端)" } } } }, - "Interactive feature lessons with demos": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Interactive feature lessons with demos" + "Interactive feature lessons with demos" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Interactive feature lessons with demos" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "애니메이션 데모와 함께하는 기능 학습" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "애니메이션 데모와 함께하는 기능 학습" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "带演示的交互式功能课程" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "带演示的交互式功能课程" } } } }, - "Interactive terminal": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Interactive terminal" + "Interactive terminal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Interactive terminal" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "인터랙티브 터미널" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "인터랙티브 터미널" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "交互式终端" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "交互式终端" } } } }, - "Interrupted": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Interrupted" + "Interrupted" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Interrupted" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "중단됨" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "중단됨" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已中断" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已中断" } } } }, - "Invalid JSON format.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Invalid JSON format." + "Invalid JSON format." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Invalid JSON format." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "유효하지 않은 JSON 형식입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "유효하지 않은 JSON 형식입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "JSON 格式无效。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON 格式无效。" } } } }, - "isInteractive: true runs the command in the interactive terminal.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "isInteractive: true runs the command in the interactive terminal." + "isInteractive: true runs the command in the interactive terminal." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "isInteractive: true runs the command in the interactive terminal." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "isInteractive: true이면 인터랙티브 터미널에서 실행됩니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "isInteractive: true이면 인터랙티브 터미널에서 실행됩니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "isInteractive:设为 true 后,命令会在交互式终端中运行。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "isInteractive:设为 true 后,命令会在交互式终端中运行。" } } } }, - "isTerminalCommand: true runs the message as a terminal command instead of sending it to chat.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "isTerminalCommand: true runs the message as a terminal command instead of sending it to chat." + "isTerminalCommand: true runs the message as a terminal command instead of sending it to chat." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "isTerminalCommand: true runs the message as a terminal command instead of sending it to chat." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "isTerminalCommand: true이면 채팅 대신 터미널 커맨드로 실행됩니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "isTerminalCommand: true이면 채팅 대신 터미널 커맨드로 실행됩니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "isTerminalCommand:设为 true 后,会把消息作为终端命令运行,而不是发送到聊天。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "isTerminalCommand:设为 true 后,会把消息作为终端命令运行,而不是发送到聊天。" } } } }, - "json": {}, - "List available skills": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "List available skills" + "json" : { + + }, + "List available skills" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "List available skills" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사용 가능한 스킬 목록" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사용 가능한 스킬 목록" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "列出可用技能" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "列出可用技能" } } } }, - "Load Claude API reference for current project": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Load Claude API reference for current project" + "Load Claude API reference for current project" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Load Claude API reference for current project" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "현재 프로젝트용 Claude API 레퍼런스 로드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 프로젝트용 Claude API 레퍼런스 로드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "为当前项目加载 Claude API 参考" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "为当前项目加载 Claude API 参考" } } } }, - "Log in to Anthropic account": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Log in to Anthropic account" + "Log in to Anthropic account" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Log in to Anthropic account" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Anthropic 계정 로그인" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anthropic 계정 로그인" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "登录 Anthropic 账号" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "登录 Anthropic 账号" } } } }, - "Log out of Anthropic account": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Log out of Anthropic account" + "Log out of Anthropic account" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Log out of Anthropic account" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Anthropic 계정 로그아웃" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anthropic 계정 로그아웃" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "退出 Anthropic 账号" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出 Anthropic 账号" } } } }, - "Manage agent configuration": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage agent configuration" + "Manage agent configuration" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage agent configuration" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "에이전트 구성 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "에이전트 구성 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理智能体配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理智能体配置" } } } }, - "Manage background tasks": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage background tasks" + "Manage background tasks" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage background tasks" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "백그라운드 작업 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "백그라운드 작업 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理后台任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理后台任务" } } } }, - "Manage cloud scheduled tasks": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage cloud scheduled tasks" + "Manage cloud scheduled tasks" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage cloud scheduled tasks" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "클라우드 예약 작업 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "클라우드 예약 작업 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理云端计划任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理云端计划任务" } } } }, - "Manage Commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage Commands" + "Manage Commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage Commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理命令" } } } }, - "Manage IDE integration": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage IDE integration" + "Manage IDE integration" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage IDE integration" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "IDE 통합 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "IDE 통합 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理 IDE 集成" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理 IDE 集成" } } } }, - "Manage MCP servers": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage MCP servers" + "Manage MCP servers" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage MCP servers" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "MCP 서버 연결 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP 서버 연결 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理 MCP 服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理 MCP 服务器" } } } }, - "Manage permissions": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage permissions" + "Manage permissions" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage permissions" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "권한 규칙 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "권한 규칙 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理权限" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理权限" } } } }, - "Manage plugins": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Manage plugins" + "Manage plugins" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage plugins" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "플러그인 관리" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "플러그인 관리" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理插件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理插件" } } } }, - "Message": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Message" + "Message" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Message" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "메시지" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "메시지" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "消息" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "消息" } } } }, - "Message sent to Claude or command run in terminal": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Message sent to Claude or command run in terminal" + "Message sent to Claude or command run in terminal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Message sent to Claude or command run in terminal" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Claude에 전송할 메시지 또는 터미널에서 실행할 커맨드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Claude에 전송할 메시지 또는 터미널에서 실행할 커맨드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "发送给 Claude 的消息或在终端中运行的命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送给 Claude 的消息或在终端中运行的命令" } } } }, - "message: text sent to Claude, or command run in the terminal.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "message: text sent to Claude, or command run in the terminal." + "message: text sent to Claude, or command run in the terminal." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "message: text sent to Claude, or command run in the terminal." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "message: Claude에 전송할 텍스트 또는 터미널에서 실행할 커맨드입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "message: Claude에 전송할 텍스트 또는 터미널에서 실행할 커맨드입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "message:发送给 Claude 的文本,或在终端中运行的命令。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "message:发送给 Claude 的文本,或在终端中运行的命令。" } } } }, - "Mobile app QR code": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Mobile app QR code" + "Mobile app QR code" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Mobile app QR code" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "모바일 앱 QR 코드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "모바일 앱 QR 코드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移动应用二维码" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移动应用二维码" } } } }, - "Name": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Name" + "Name" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Name" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "이름" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이름" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "名称" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名称" } } } }, - "Name shown on the button": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Name shown on the button" + "Name shown on the button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Name shown on the button" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "버튼에 표시될 이름" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "버튼에 표시될 이름" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按钮上显示的名称" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按钮上显示的名称" } } } }, - "name: command name without the leading slash.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "name: command name without the leading slash." + "name: command name without the leading slash." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "name: command name without the leading slash." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "name: 앞의 /를 제외한 커맨드 이름입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "name: 앞의 /를 제외한 커맨드 이름입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "name:不含开头斜杠的命令名称。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "name:不含开头斜杠的命令名称。" } } } }, - "name: label shown on the shortcut button.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "name: label shown on the shortcut button." + "name: label shown on the shortcut button." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "name: label shown on the shortcut button." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "name: 단축키 버튼에 표시되는 이름입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "name: 단축키 버튼에 표시되는 이름입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "name:显示在快捷方式按钮上的标签。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "name:显示在快捷方式按钮上的标签。" } } } }, - "New Command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "New Command" + "New Command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "New Command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 커맨드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "새 커맨드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "新建命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建命令" } } } }, - "New Shortcut": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "New Shortcut" + "New Shortcut" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "New Shortcut" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 단축키" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "새 단축키" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "新建快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建快捷方式" } } } }, - "No changes": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "No changes" + "Next change" : { + + }, + "No changes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "No changes" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "변경사항 없음" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "변경사항 없음" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "没有变更" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有变更" } } } }, - "No results found": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "No results found" + "No results found" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "No results found" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "검색 결과 없음" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "검색 결과 없음" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未找到结果" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未找到结果" } } } }, - "No shortcuts": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "No shortcuts" + "No shortcuts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "No shortcuts" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키 없음" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키 없음" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "没有快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有快捷方式" } } } }, - "No todos": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "No todos" + "No todos" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "No todos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "没有待办" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有待办" } } } }, - "ok": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "ok" + "ok" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "ok" } } } }, - "OK": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "OK" + "OK" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "OK" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "확인" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "확인" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "确定" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确定" } } } }, - "Open in browser": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Open in browser" + "Open in browser" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Open in browser" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "브라우저에서 열기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "브라우저에서 열기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在浏览器中打开" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在浏览器中打开" } } } }, - "Open the plan to accept or reject": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开计划以接受或拒绝" + "Open the plan to accept or reject" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开计划以接受或拒绝" } } } }, - "Open the plan to review the decision": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开计划以查看决策" + "Open the plan to review the decision" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开计划以查看决策" } } } }, - "Optional longer description shown in command details": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Optional longer description shown in command details" + "Optional longer description shown in command details" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Optional longer description shown in command details" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 상세에 표시되는 선택적 긴 설명" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 상세에 표시되는 선택적 긴 설명" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示在命令详情中的可选长描述" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示在命令详情中的可选长描述" } } } }, - "Orchestrate large-scale parallel changes": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Orchestrate large-scale parallel changes" + "Orchestrate large-scale parallel changes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Orchestrate large-scale parallel changes" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "대규모 병렬 변경 오케스트레이션" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "대규모 병렬 변경 오케스트레이션" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编排大规模并行更改" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编排大规模并行更改" } } } }, - "Order Claude Code stickers": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Order Claude Code stickers" + "Order Claude Code stickers" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Order Claude Code stickers" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Claude Code 스티커 주문" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Claude Code 스티커 주문" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "订购 Claude Code 贴纸" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "订购 Claude Code 贴纸" } } } }, - "Output tokens": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Output tokens" + "Output tokens" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Output tokens" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "출력 토큰" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "출력 토큰" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "输出 token" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输出 token" } } } }, - "Plan": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Plan" + "Plan" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Plan" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "계획" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "계획" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "计划" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计划" } } } }, - "Plan content unavailable.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Plan content unavailable." + "Plan content unavailable." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Plan content unavailable." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "计划内容不可用。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计划内容不可用。" } } } }, - "Plan is still drafting…": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "计划仍在起草…" + "Plan is still drafting…" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计划仍在起草…" } } } }, - "Plan mode": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Plan mode" + "Plan mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Plan mode" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "计划模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计划模式" } } } }, - "Plan mode is on — Add menu": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "计划模式已开启 — 添加菜单" + "Plan mode is on — Add menu" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计划模式已开启 — 添加菜单" } } } }, - "Plan ready": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "计划已就绪" + "Plan ready" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "计划已就绪" } } } }, - "Plan usage limits and rate limits": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Plan usage limits and rate limits" + "Plan usage limits and rate limits" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Plan usage limits and rate limits" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "요금제 사용 한도 및 속도 제한" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "요금제 사용 한도 및 속도 제한" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "规划用量限制和速率限制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "规划用量限制和速率限制" } } } }, - "Portion of the current conversation's context window in use. When it fills, older messages may be summarized or dropped.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Portion of the current conversation's context window in use. When it fills, older messages may be summarized or dropped." + "Portion of the current conversation's context window in use. When it fills, older messages may be summarized or dropped." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Portion of the current conversation's context window in use. When it fills, older messages may be summarized or dropped." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "현재 대화의 컨텍스트 윈도우 중 사용 중인 비율입니다. 가득 차면 이전 메시지가 요약되거나 삭제될 수 있습니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 대화의 컨텍스트 윈도우 중 사용 중인 비율입니다. 가득 차면 이전 메시지가 요약되거나 삭제될 수 있습니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "当前对话上下文窗口的已用比例。填满后,较早消息可能会被总结或丢弃。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前对话上下文窗口的已用比例。填满后,较早消息可能会被总结或丢弃。" } } } }, - "Privacy settings (Pro/Max)": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Privacy settings (Pro/Max)" + "Previous change" : { + + }, + "Privacy settings (Pro/Max)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Privacy settings (Pro/Max)" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "개인정보 설정 (Pro/Max)" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "개인정보 설정 (Pro/Max)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "隐私设置(Pro/Max)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐私设置(Pro/Max)" } } } }, - "Pull a web session into this terminal": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Pull a web session into this terminal" + "Pull a web session into this terminal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Pull a web session into this terminal" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "웹 세션을 터미널로 가져오기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "웹 세션을 터미널로 가져오기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将网页会话拉取到此终端" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将网页会话拉取到此终端" } } } }, - "Read file": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Read file" + "Read file" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Read file" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "파일 읽기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "파일 읽기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "读取文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "读取文件" } } } }, - "Reduce permission prompts by scanning transcripts": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reduce permission prompts by scanning transcripts" + "Reduce permission prompts by scanning transcripts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reduce permission prompts by scanning transcripts" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "대화 기록 분석으로 권한 요청 횟수 줄이기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "대화 기록 분석으로 권한 요청 횟수 줄이기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "通过扫描转录记录减少权限提示" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过扫描转录记录减少权限提示" } } } }, - "Register frequently used messages as shortcuts": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Register frequently used messages as shortcuts" + "Register frequently used messages as shortcuts" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Register frequently used messages as shortcuts" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "자주 사용하는 메시지를 단축키로 등록하세요" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "자주 사용하는 메시지를 단축키로 등록하세요" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将常用消息注册为快捷方式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将常用消息注册为快捷方式" } } } }, - "Reject": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reject" + "Reject" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reject" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "拒绝" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "拒绝" } } } }, - "Reject with Reason": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reject with Reason" + "Reject with Reason" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reject with Reason" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "拒绝并说明原因" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "拒绝并说明原因" } } } }, - "Reload plugins": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reload plugins" + "Reload plugins" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reload plugins" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "플러그인 다시 로드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "플러그인 다시 로드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新加载插件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新加载插件" } } } }, - "Remote control from claude.ai": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Remote control from claude.ai" + "Remote control from claude.ai" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Remote control from claude.ai" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "claude.ai에서 원격 제어" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "claude.ai에서 원격 제어" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "来自 claude.ai 的远程控制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "来自 claude.ai 的远程控制" } } } }, - "Remove": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移除" + "Remove" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除" } } } }, - "Rename session": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Rename session" + "Rename session" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Rename session" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "세션 이름 변경" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "세션 이름 변경" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重命名会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重命名会话" } } } }, - "Repeat execution (e.g. /loop 5m /foo)": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Repeat execution (e.g. /loop 5m /foo)" + "Repeat execution (e.g. /loop 5m /foo)" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Repeat execution (e.g. /loop 5m /foo)" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "반복 실행 (예: /loop 5m /foo)" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "반복 실행 (예: /loop 5m /foo)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重复执行(例如 /loop 5m /foo)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重复执行(例如 /loop 5m /foo)" } } } }, - "Reset": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reset" + "Reset" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reset" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "초기화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "초기화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置" } } } }, - "Reset Default Commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reset Default Commands" + "Reset Default Commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reset Default Commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "기본 커맨드 초기화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기본 커맨드 초기화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重置默认命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置默认命令" } } } }, - "Reset Defaults": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Reset Defaults" + "Reset Defaults" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Reset Defaults" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "기본값 초기화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기본값 초기화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重置默认值" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置默认值" } } } }, - "Resets in %@": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Resets in %@" + "Resets in %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Resets in %@" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "%@ 후 리셋" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 후 리셋" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 后重置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 后重置" } } } }, - "Response in progress": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "响应进行中" + "Response in progress" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "响应进行中" } } } }, - "Restore Default": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Restore Default" + "Restore Default" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Restore Default" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "기본값 복원" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기본값 복원" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "恢复默认" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复默认" } } } }, - "Restore modified default commands to their original state": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Restore modified default commands to their original state" + "Restore modified default commands to their original state" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Restore modified default commands to their original state" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "수정한 기본 커맨드를 원래 상태로 복원합니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "수정한 기본 커맨드를 원래 상태로 복원합니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将修改过的默认命令恢复到原始状态" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将修改过的默认命令恢复到原始状态" } } } }, - "Review": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看" + "Review" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看" } } } }, - "Review a pull request locally": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Review a pull request locally" + "Review a pull request locally" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Review a pull request locally" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "로컬에서 PR 리뷰" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "로컬에서 PR 리뷰" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在本地评审拉取请求" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在本地评审拉取请求" } } } }, - "Review and fix code quality/efficiency of changes": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Review and fix code quality/efficiency of changes" + "Review and fix code quality/efficiency of changes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Review and fix code quality/efficiency of changes" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "변경사항 코드 품질/효율성 검토 및 수정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "변경사항 코드 품질/효율성 검토 및 수정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "评审并修复更改的代码质量/效率问题" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "评审并修复更改的代码质量/效率问题" } } } }, - "Rewind to a previous point": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Rewind to a previous point" + "Rewind to a previous point" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Rewind to a previous point" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "이전 시점으로 되돌리기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이전 시점으로 되돌리기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "回退到之前的位置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "回退到之前的位置" } } } }, - "Run as terminal command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Run as terminal command" + "Run as terminal command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Run as terminal command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널 커맨드로 실행" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "터미널 커맨드로 실행" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "作为终端命令运行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "作为终端命令运行" } } } }, - "Run command": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Run command" + "Run command" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Run command" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 실행" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 실행" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "运行命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行命令" } } } }, - "Running": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Running" + "Running" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Running" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "실행 중" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "실행 중" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "正在运行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在运行" } } } }, - "RxCode shortcut export file.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "RxCode shortcut export file." + "RxCode shortcut export file." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "RxCode shortcut export file." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "RxCode 단축키 내보내기 파일입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "RxCode 단축키 내보내기 파일입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "RxCode 快捷方式导出文件。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "RxCode 快捷方式导出文件。" } } } }, - "RxCode slash command export file.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "RxCode slash command export file." + "RxCode slash command export file." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "RxCode slash command export file." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "RxCode 슬래시 커맨드 내보내기 파일입니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "RxCode 슬래시 커맨드 내보내기 파일입니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "RxCode 斜杠命令导出文件。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "RxCode 斜杠命令导出文件。" } } } }, - "Save": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Save" + "Save" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Save" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "저장" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "저장" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "保存" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" } } } }, - "Search commands...": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Search commands..." + "Search commands..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Search commands..." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 검색..." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 검색..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索命令..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索命令..." } } } }, - "Search in code": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Search in code" + "Search in code" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Search in code" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "코드 검색" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "코드 검색" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在代码中搜索" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在代码中搜索" } } } }, - "Select/change AI model": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Select/change AI model" + "Select/change AI model" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Select/change AI model" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "AI 모델 선택/변경" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "AI 모델 선택/변경" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择/更改 AI 模型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择/更改 AI 模型" } } } }, - "Send": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Send" + "Send" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "전송" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "전송" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "发送" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送" } } } }, - "Send all as one": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "合并发送" + "Send all as one" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "合并发送" } } } }, - "Send feedback": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Send feedback" + "Send feedback" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send feedback" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "发送反馈" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送反馈" } } } }, - "Send now — cancels current response": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "立即发送 — 取消当前响应" + "Send now — cancels current response" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即发送 — 取消当前响应" } } } }, - "Session analysis report": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Session analysis report" + "Session analysis report" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Session analysis report" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "세션 분석 보고서 생성" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "세션 분석 보고서 생성" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "会话分析报告" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "会话分析报告" } } } }, - "Session Usage": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Session Usage" + "Session Usage" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Session Usage" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "세션 사용량" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "세션 사용량" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "会话用量" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "会话用量" } } } }, - "Set model effort level": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Set model effort level" + "Set model effort level" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Set model effort level" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "모델 effort 수준 설정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "모델 effort 수준 설정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置模型推理强度" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置模型推理强度" } } } }, - "Set prompt bar color": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Set prompt bar color" + "Set prompt bar color" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Set prompt bar color" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "프롬프트 바 색상 설정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "프롬프트 바 색상 설정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置提示栏颜色" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置提示栏颜色" } } } }, - "Set terminal UI renderer": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Set terminal UI renderer" + "Set terminal UI renderer" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Set terminal UI renderer" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널 UI 렌더러 설정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "터미널 UI 렌더러 설정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置终端 UI 渲染器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置终端 UI 渲染器" } } } }, - "Set up GitHub Actions app": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Set up GitHub Actions app" + "Set up GitHub Actions app" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Set up GitHub Actions app" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "GitHub Actions 앱 설정" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "GitHub Actions 앱 설정" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置 GitHub Actions 应用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置 GitHub Actions 应用" } } } }, - "Settings interface": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Settings interface" + "Settings interface" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Settings interface" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "설정 인터페이스" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "설정 인터페이스" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置界面" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置界面" } } } }, - "Share free 1-week passes": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Share free 1-week passes" + "Share free 1-week passes" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Share free 1-week passes" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "1주 무료 이용권 공유" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "1주 무료 이용권 공유" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "分享免费 1 周通行证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享免费 1 周通行证" } } } }, - "Short description": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Short description" + "Short description" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Short description" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "짧은 설명" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "짧은 설명" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "简短描述" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "简短描述" } } } }, - "Short description shown in the command picker": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Short description shown in the command picker" + "Short description shown in the command picker" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Short description shown in the command picker" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "커맨드 선택기에 표시되는 짧은 설명" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "커맨드 선택기에 표시되는 짧은 설명" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示在命令选择器中的简短描述" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示在命令选择器中的简短描述" } } } }, - "Shortcut button label": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Shortcut button label" + "Shortcut button label" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Shortcut button label" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키 버튼 이름" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키 버튼 이름" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "快捷方式按钮标签" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷方式按钮标签" } } } }, - "Shortcut Manager": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Shortcut Manager" + "Shortcut Manager" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Shortcut Manager" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단축키 관리자" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "단축키 관리자" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "快捷方式管理器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷方式管理器" } } } }, - "Shortcuts": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "快捷按钮" + "Shortcuts" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷按钮" } } } }, - "Show help": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Show help" + "Show help" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Show help" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "도움말 보기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "도움말 보기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示帮助" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示帮助" } } } }, - "Show less": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Show less" + "Show less" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Show less" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "접기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "접기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "收起" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收起" } } } }, - "Show more": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Show more" + "Show more" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Show more" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "더 보기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "더 보기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示更多" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示更多" } } } }, - "Side question not added to conversation": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Side question not added to conversation" + "Side question not added to conversation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Side question not added to conversation" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "대화에 추가되지 않는 부가 질문" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "대화에 추가되지 않는 부가 질문" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "旁路问题未添加到对话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "旁路问题未添加到对话" } } } }, - "Skill": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Skill" + "Skill" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Skill" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "스킬" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "스킬" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "技能" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "技能" } } } }, - "Slash Command Manager": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Slash Command Manager" + "Slash Command Manager" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Slash Command Manager" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "슬래시 커맨드 관리자" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "슬래시 커맨드 관리자" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "斜杠命令管理器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "斜杠命令管理器" } } } }, - "Slash Commands": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Slash Commands" + "Slash Commands" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Slash Commands" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "슬래시 커맨드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "슬래시 커맨드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "斜杠命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "斜杠命令" } } } }, - "Start a new conversation": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Start a new conversation" + "Start a new conversation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Start a new conversation" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 대화 시작" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "새 대화 시작" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "开始新对话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开始新对话" } } } }, - "Start in plan mode": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "以计划模式开始" + "Start in plan mode" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "以计划模式开始" } } } }, - "Subagent": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Subagent" + "Subagent" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Subagent" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "서브에이전트" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "서브에이전트" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "子智能体" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "子智能体" } } } }, - "Submit feedback/bug report": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Submit feedback/bug report" + "Submit feedback/bug report" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Submit feedback/bug report" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "피드백/버그 보고" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "피드백/버그 보고" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "提交反馈/错误报告" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "提交反馈/错误报告" } } } }, - "Switch to horizontal scroll": {}, - "Switch to wrap": {}, - "Tell Claude what to change": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Tell Claude what to change" + "Switch to horizontal scroll" : { + + }, + "Switch to wrap" : { + + }, + "Tell Claude what to change" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Tell Claude what to change" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "告诉 Claude 需要修改什么" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "告诉 Claude 需要修改什么" } } } }, - "terminal": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "terminal" + "terminal" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "terminal" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "터미널" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "终端" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终端" } } } }, - "The leading slash is optional while editing names; RxCode saves names without it.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "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." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The leading slash is optional while editing names; RxCode saves names without it." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "이름을 편집할 때 앞의 /는 선택사항입니다. RxCode는 / 없이 저장합니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이름을 편집할 때 앞의 /는 선택사항입니다. RxCode는 / 없이 저장합니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑名称时开头的斜杠可选;RxCode 保存时不包含它。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑名称时开头的斜杠可选;RxCode 保存时不包含它。" } } } }, - "This command will run in the terminal when the button is clicked": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "This command will run in the terminal when the button is clicked" + "This command will run in the terminal when the button is clicked" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "This command will run in the terminal when the button is clicked" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "버튼을 클릭하면 이 커맨드가 터미널에서 실행됩니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "버튼을 클릭하면 이 커맨드가 터미널에서 실행됩니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "点击按钮时,此命令会在终端中运行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击按钮时,此命令会在终端中运行" } } } }, - "This message will be sent when the button is clicked": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "This message will be sent when the button is clicked" + "This message will be sent when the button is clicked" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "This message will be sent when the button is clicked" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "버튼을 클릭하면 이 메시지가 전송됩니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "버튼을 클릭하면 이 메시지가 전송됩니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "点击按钮时会发送此消息" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击按钮时会发送此消息" } } } }, - "Todos": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Todos" + "Todos" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Todos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "待办" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "待办" } } } }, - "Toggle fast mode": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Toggle fast mode" + "Toggle fast mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Toggle fast mode" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "빠른 모드 켜기/끄기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "빠른 모드 켜기/끄기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换快速模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换快速模式" } } } }, - "Toggle sandbox mode": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Toggle sandbox mode" + "Toggle sandbox mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Toggle sandbox mode" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "샌드박스 모드 전환" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "샌드박스 모드 전환" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换沙盒模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换沙盒模式" } } } }, - "Toggle voice dictation": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Toggle voice dictation" + "Toggle voice dictation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Toggle voice dictation" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "음성 받아쓰기 전환" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "음성 받아쓰기 전환" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换语音听写" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换语音听写" } } } }, - "tok": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "tok" + "tok" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "tok" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "토큰" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "토큰" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "tok" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "tok" } } } }, - "Token usage statistics": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Token usage statistics" + "Token usage statistics" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Token usage statistics" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "토큰 사용 통계" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "토큰 사용 통계" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Token 用量统计" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Token 用量统计" } } } }, - "Tracks usage against Anthropic's rolling 5-hour limit. Resets gradually as older requests age out.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Tracks usage against Anthropic's rolling 5-hour limit. Resets gradually as older requests age out." + "Tracks usage against Anthropic's rolling 5-hour limit. Resets gradually as older requests age out." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Tracks usage against Anthropic's rolling 5-hour limit. Resets gradually as older requests age out." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Anthropic의 5시간 롤링 한도 대비 사용량을 추적합니다. 이전 요청이 시간이 지남에 따라 점진적으로 리셋됩니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anthropic의 5시간 롤링 한도 대비 사용량을 추적합니다. 이전 요청이 시간이 지남에 따라 점진적으로 리셋됩니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "跟踪 Anthropic 5 小时滚动限制的用量。旧请求过期后会逐步恢复。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "跟踪 Anthropic 5 小时滚动限制的用量。旧请求过期后会逐步恢复。" } } } }, - "Tracks usage against Anthropic's rolling 7-day limit. Resets gradually as older requests age out.": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Tracks usage against Anthropic's rolling 7-day limit. Resets gradually as older requests age out." + "Tracks usage against Anthropic's rolling 7-day limit. Resets gradually as older requests age out." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Tracks usage against Anthropic's rolling 7-day limit. Resets gradually as older requests age out." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Anthropic의 7일 롤링 한도 대비 사용량을 추적합니다. 이전 요청이 시간이 지남에 따라 점진적으로 리셋됩니다." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anthropic의 7일 롤링 한도 대비 사용량을 추적합니다. 이전 요청이 시간이 지남에 따라 점진적으로 리셋됩니다." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "跟踪 Anthropic 7 天滚动限制的用量。旧请求过期后会逐步恢复。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "跟踪 Anthropic 7 天滚动限制的用量。旧请求过期后会逐步恢复。" } } } }, - "Tracks usage against Codex's rolling 5-hour limit. Resets gradually as older requests age out.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "跟踪 Codex 滚动 5 小时限制下的用量。较早请求过期后会逐步重置。" + "Tracks usage against Codex's rolling 5-hour limit. Resets gradually as older requests age out." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "跟踪 Codex 滚动 5 小时限制下的用量。较早请求过期后会逐步重置。" } } } }, - "Tracks usage against Codex's rolling 7-day limit. Resets gradually as older requests age out.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "跟踪 Codex 滚动 7 天限制下的用量。较早请求过期后会逐步重置。" + "Tracks usage against Codex's rolling 7-day limit. Resets gradually as older requests age out." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "跟踪 Codex 滚动 7 天限制下的用量。较早请求过期后会逐步重置。" } } } }, - "Try a different keyword": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Try a different keyword" + "Try a different keyword" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Try a different keyword" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "다른 키워드로 검색해 보세요" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "다른 키워드로 검색해 보세요" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "尝试其他关键词" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "尝试其他关键词" } } } }, - "Turn off plan mode": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Turn off plan mode" + "Turn off plan mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Turn off plan mode" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "플랜 모드 끄기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "플랜 모드 끄기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭计划模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭计划模式" } } } }, - "Turns": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Turns" + "Turns" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Turns" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "대화 횟수" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "대화 횟수" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "轮次" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "轮次" } } } }, - "Type a message...": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Type a message..." + "Type a message..." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Type a message..." } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "메시지를 입력하세요..." + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "메시지를 입력하세요..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "输入消息..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输入消息..." } } } }, - "Upgrade plan": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Upgrade plan" + "Upgrade plan" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Upgrade plan" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "상위 요금제로 업그레이드" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "상위 요금제로 업그레이드" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "升级套餐" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "升级套餐" } } } }, - "Usage": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Usage" + "Usage" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Usage" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사용량" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사용량" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "用量" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用量" } } } }, - "Usage:": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Usage:" + "Usage:" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Usage:" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사용법:" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사용법:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "用法:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用法:" } } } }, - "Use the Add menu or Shift-Tab to ask the agent for a read-only plan before edits begin.": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在开始编辑前,使用“添加”菜单或 Shift-Tab 请求智能体提供只读计划。" + "Use the Add menu or Shift-Tab to ask the agent for a read-only plan before edits begin." : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在开始编辑前,使用“添加”菜单或 Shift-Tab 请求智能体提供只读计划。" } } } }, - "Version, model, and account status": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Version, model, and account status" + "Version, model, and account status" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Version, model, and account status" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "버전, 모델, 계정 상태" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "버전, 모델, 계정 상태" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "版本、模型和账号状态" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本、模型和账号状态" } } } }, - "View": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看" + "View" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看" } } } }, - "View changelog": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "View changelog" + "View changelog" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "View changelog" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "변경 로그 보기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "변경 로그 보기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看更新日志" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看更新日志" } } } }, - "View hook configuration": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "View hook configuration" + "View hook configuration" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "View hook configuration" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "훅 구성 보기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "훅 구성 보기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看 hook 配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看 hook 配置" } } } }, - "View Result": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "View Result" + "View Result" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "View Result" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "결과 보기" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "결과 보기" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看结果" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看结果" } } } }, - "Visualize context usage": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Visualize context usage" + "Visualize context usage" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Visualize context usage" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "컨텍스트 사용량 시각화" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "컨텍스트 사용량 시각화" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "可视化上下文用量" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可视化上下文用量" } } } }, - "Waiting for answer": { - "localizations": { - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "等待回答" + "Waiting for answer" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "等待回答" } } } }, - "What should we build in %@?": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "What should we build in %@?" + "What should we build in %@?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "What should we build in %@?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "我们要在 %@ 中构建什么?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "我们要在 %@ 中构建什么?" } } } }, - "What would you like changed?": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "What would you like changed?" + "What would you like changed?" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "What would you like changed?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "你希望修改什么?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "你希望修改什么?" } } } }, - "When enabled, the command runs in the terminal instead of chat": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "When enabled, the command runs in the terminal instead of chat" + "When enabled, the command runs in the terminal instead of chat" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "When enabled, the command runs in the terminal instead of chat" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "활성화하면 커맨드가 채팅 대신 터미널에서 실행됩니다" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "활성화하면 커맨드가 채팅 대신 터미널에서 실행됩니다" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启用后,命令会在终端中运行,而不是发送到聊天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用后,命令会在终端中运行,而不是发送到聊天" } } } }, - "Write heap snapshot for memory diagnostics": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "Write heap snapshot for memory diagnostics" + "Write heap snapshot for memory diagnostics" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Write heap snapshot for memory diagnostics" } }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "메모리 진단을 위한 힙 스냅샷 저장" + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "메모리 진단을 위한 힙 스냅샷 저장" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "写入堆快照以进行内存诊断" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "写入堆快照以进行内存诊断" } } } } }, - "version": "1.1" -} + "version" : "1.1" +} \ No newline at end of file diff --git a/Packages/Sources/RxCodeChatKit/SlashCommandBar.swift b/Packages/Sources/RxCodeChatKit/SlashCommandBar.swift index 6df62530..2ed58988 100644 --- a/Packages/Sources/RxCodeChatKit/SlashCommandBar.swift +++ b/Packages/Sources/RxCodeChatKit/SlashCommandBar.swift @@ -531,6 +531,7 @@ struct SlashCommandPopup: View { .shadow(color: ClaudeTheme.shadowColor, radius: 12, y: -4) .sheet(item: $detailCommand) { cmd in CommandDetailSheet(command: cmd) + .mobileSheetPresentation() } } } @@ -697,6 +698,7 @@ struct CommandMenuButton: View { .popover(isPresented: $showUsagePopover, arrowEdge: .top) { UsagePopoverView() } .sheet(isPresented: $showCommandManager) { SlashCommandManagerView() + .mobileSheetPresentation() } } diff --git a/Packages/Sources/RxCodeChatKit/ToolResultView.swift b/Packages/Sources/RxCodeChatKit/ToolResultView.swift index dbca3827..8ccd00a6 100644 --- a/Packages/Sources/RxCodeChatKit/ToolResultView.swift +++ b/Packages/Sources/RxCodeChatKit/ToolResultView.swift @@ -266,6 +266,7 @@ struct ToolResultView: View { } .sheet(isPresented: $showBashSheet) { BashTerminalSheet(toolCall: toolCall) + .mobileSheetPresentation() } .sheet(isPresented: $showDetailSheet) { ToolCallDetailSheet(toolCall: toolCall) @@ -778,7 +779,8 @@ private extension View { #if os(iOS) self .presentationDetents([.large]) - .presentationDragIndicator(.visible) + .presentationDragIndicator(.hidden) + .interactiveDismissDisabled(true) #else self #endif diff --git a/Packages/Tests/DiffViewTests/DiffNavigationTests.swift b/Packages/Tests/DiffViewTests/DiffNavigationTests.swift new file mode 100644 index 00000000..43c85630 --- /dev/null +++ b/Packages/Tests/DiffViewTests/DiffNavigationTests.swift @@ -0,0 +1,30 @@ +import Testing +@testable import DiffView + +@Suite("Diff navigation") +struct DiffNavigationTests { + @Test("contiguous add and remove rows form one change block") + func contiguousChangedRowsAreOneBlock() { + let lines = [ + DiffLine(text: " a", kind: .context, oldLineNumber: 1, newLineNumber: 1), + DiffLine(text: "-b", kind: .removed, oldLineNumber: 2), + DiffLine(text: "+B", kind: .added, newLineNumber: 2), + DiffLine(text: " c", kind: .context, oldLineNumber: 3, newLineNumber: 3), + DiffLine(text: "+d", kind: .added, newLineNumber: 4), + ] + + #expect(DiffView.changeBlockOffsets(in: lines) == [1, 4]) + } + + @Test("hunk headers do not count as change targets") + func hunkHeadersAreSkipped() { + let lines = [ + DiffLine(text: "@@ -1,2 +1,2 @@", kind: .hunk), + DiffLine(text: " a", kind: .context, oldLineNumber: 1, newLineNumber: 1), + DiffLine(text: "-b", kind: .removed, oldLineNumber: 2), + DiffLine(text: "+B", kind: .added, newLineNumber: 2), + ] + + #expect(DiffView.changeBlockOffsets(in: lines) == [2]) + } +} diff --git a/Packages/Tests/DiffViewTests/GutterLayoutTests.swift b/Packages/Tests/DiffViewTests/GutterLayoutTests.swift index afd97c0b..06004fff 100644 --- a/Packages/Tests/DiffViewTests/GutterLayoutTests.swift +++ b/Packages/Tests/DiffViewTests/GutterLayoutTests.swift @@ -68,4 +68,18 @@ struct GutterLayoutTests { #expect(layout.columnCount == 0) } + + @Test("horizontal scroll body expands for long right-side content") + func horizontalScrollBodyWidthExpandsForLongContent() { + let lines = [ + DiffLine( + text: "+let value = \"this is a long line that should require horizontal scrolling on mobile\"", + kind: .added, + newLineNumber: 1 + ), + ] + let layout = GutterLayout(lines: lines) + + #expect(DiffView.horizontalScrollBodyWidth(for: lines, layout: layout) > 320) + } } diff --git a/RxCode.xcodeproj/project.pbxproj b/RxCode.xcodeproj/project.pbxproj index 616dd7a6..28db4bbe 100644 --- a/RxCode.xcodeproj/project.pbxproj +++ b/RxCode.xcodeproj/project.pbxproj @@ -297,11 +297,11 @@ 4381E755142272EB2DAA9C96 /* AppStateProjectSwitchTests.swift */, DFA0CCC02FB4CC01005991E1 /* PlanDecisionTests.swift */, DFA0CCC12FB4CC01005991E1 /* PlanCardViewTests.swift */, - DFA0CCD52FB4CC02005991E1 /* HistoryListArchiveFilterTests.swift */, - 5C2222222FCB200000000001 /* BriefingThreadRowTests.swift */, - 5C3333332FCB400000000001 /* ThreadStoreThreadSummaryTests.swift */, - E62000002FCB000100000001 /* MemoryIntentTests.swift */, - ); + DFA0CCD52FB4CC02005991E1 /* HistoryListArchiveFilterTests.swift */, + 5C2222222FCB200000000001 /* BriefingThreadRowTests.swift */, + 5C3333332FCB400000000001 /* ThreadStoreThreadSummaryTests.swift */, + E62000002FCB000100000001 /* MemoryIntentTests.swift */, + ); path = RxCodeTests; sourceTree = ""; }; @@ -711,12 +711,12 @@ 101010112FCB100000000002 /* BranchBriefingPromptContextTests.swift in Sources */, 33993F0F87CF4DB09F2813A8 /* AppStateProjectSwitchTests.swift in Sources */, DFA0CCD12FB4CC01005991E1 /* PlanDecisionTests.swift in Sources */, - DFA0CCD22FB4CC01005991E1 /* PlanCardViewTests.swift in Sources */, - DFA0CCE12FB4CC02005991E1 /* HistoryListArchiveFilterTests.swift in Sources */, - 5C2222222FCB200000000002 /* BriefingThreadRowTests.swift in Sources */, - 5C3333332FCB400000000002 /* ThreadStoreThreadSummaryTests.swift in Sources */, - E62000012FCB000100000001 /* MemoryIntentTests.swift in Sources */, - ); + DFA0CCD22FB4CC01005991E1 /* PlanCardViewTests.swift in Sources */, + DFA0CCE12FB4CC02005991E1 /* HistoryListArchiveFilterTests.swift in Sources */, + 5C2222222FCB200000000002 /* BriefingThreadRowTests.swift in Sources */, + 5C3333332FCB400000000002 /* ThreadStoreThreadSummaryTests.swift in Sources */, + E62000012FCB000100000001 /* MemoryIntentTests.swift in Sources */, + ); runOnlyForDeploymentPostprocessing = 0; }; DF22D8222FBE025C00E3ABFD /* Sources */ = { diff --git a/RxCode/App/AppState+CrossProject.swift b/RxCode/App/AppState+CrossProject.swift index e314cfc6..a84f6796 100644 --- a/RxCode/App/AppState+CrossProject.swift +++ b/RxCode/App/AppState+CrossProject.swift @@ -697,10 +697,52 @@ extension AppState { await finalizeAgentStream(agentProvider: agentProvider, streamId: streamId) if sessionKey != resultEvent.sessionId { - if let state = sessionStates.removeValue(forKey: sessionKey) { + let previousSessionKey = sessionKey + let wasForeground = (window.currentSessionId ?? window.newSessionKey) == previousSessionKey + if let state = sessionStates.removeValue(forKey: previousSessionKey) { sessionStates[resultEvent.sessionId] = state } + renameDraftState(from: previousSessionKey, to: resultEvent.sessionId, in: window) + sessionIdRedirect[previousSessionKey] = resultEvent.sessionId sessionKey = resultEvent.sessionId + if wasForeground { + window.currentSessionId = resultEvent.sessionId + } + + let expectedPlaceholder = "pending-\(streamId.uuidString)" + if window.pendingPlaceholderIds.contains(expectedPlaceholder) { + if let idx = allSessionSummaries.firstIndex(where: { $0.id == expectedPlaceholder }) { + let old = allSessionSummaries[idx] + let replacement = ChatSession( + id: resultEvent.sessionId, + projectId: old.projectId, + title: old.title, + messages: [], + createdAt: old.createdAt, + updatedAt: old.createdAt, + isPinned: old.isPinned, + agentProvider: old.agentProvider, + model: old.model, + effort: old.effort, + permissionMode: old.permissionMode, + origin: old.origin, + worktreePath: old.worktreePath, + worktreeBranch: old.worktreeBranch, + isArchived: old.isArchived, + archivedAt: old.archivedAt + ) + allSessionSummaries.removeAll { $0.id == expectedPlaceholder || $0.id == resultEvent.sessionId } + allSessionSummaries.insert(replacement.summary, at: 0) + threadStore.renameId(from: expectedPlaceholder, to: resultEvent.sessionId) + threadStore.upsert(replacement.summary, cliSessionId: resultEvent.sessionId) + } else { + allSessionSummaries.removeAll { $0.id == expectedPlaceholder } + threadStore.delete(id: expectedPlaceholder) + } + window.removePendingPlaceholder(expectedPlaceholder) + } + + broadcastMobileSessionRedirect(from: previousSessionKey, to: resultEvent.sessionId) } // A background completion is "finished, unread". Setting the diff --git a/RxCode/App/AppState+Lifecycle.swift b/RxCode/App/AppState+Lifecycle.swift index 1d6daf83..bf2df82d 100644 --- a/RxCode/App/AppState+Lifecycle.swift +++ b/RxCode/App/AppState+Lifecycle.swift @@ -164,6 +164,8 @@ extension AppState { customRepos = await persistence.loadCustomRepos() marketplaceCustomSources = await marketplace.customSources() + seedUITestBriefingIfRequested() + // Sidebar threads are now sourced from the local SwiftData store. // CLI session files are no longer surfaced in the sidebar list — the // CLI is still the transcript backend (replay on thread open), but @@ -372,6 +374,29 @@ extension AppState { window.isInitialized = true } + func seedUITestBriefingIfRequested() { + guard ProcessInfo.processInfo.environment["RXCODE_UI_TEST_SEED_BRIEFING"] == "1", + let project = projects.first else { + return + } + + let branch = "main" + threadStore.upsertThreadSummary( + sessionId: "rxcode-ui-test-seeded-briefing-thread", + projectId: project.id, + branch: branch, + title: "UI Test Seed Thread", + summary: "Seeded thread summary for the briefing new-thread acceptance path." + ) + threadStore.upsertBranchBriefing( + projectId: project.id, + branch: branch, + briefing: "Seeded briefing for the local UI acceptance test." + ) + threadSummaryRevision &+= 1 + branchBriefingRevision &+= 1 + } + // MARK: - ChatBridge Setup /// Configures a `ChatBridge`'s action handlers and starts an observation loop that keeps diff --git a/RxCode/Views/Sidebar/BriefingView.swift b/RxCode/Views/Sidebar/BriefingView.swift index 9010a926..8ac8c880 100644 --- a/RxCode/Views/Sidebar/BriefingView.swift +++ b/RxCode/Views/Sidebar/BriefingView.swift @@ -545,6 +545,7 @@ struct BriefingView: View { .menuStyle(.borderlessButton) .menuIndicator(.hidden) .fixedSize() + .accessibilityIdentifier("briefing-group-actions-button") .help("Actions for \(project.name)") } diff --git a/RxCodeMobile/Resources/Localizable.xcstrings b/RxCodeMobile/Resources/Localizable.xcstrings index 498d20ed..47027b65 100644 --- a/RxCodeMobile/Resources/Localizable.xcstrings +++ b/RxCodeMobile/Resources/Localizable.xcstrings @@ -2853,6 +2853,9 @@ } } } + }, + "Next change" : { + }, "No agents found." : { "localizations" : { @@ -3444,6 +3447,9 @@ } } } + }, + "Previous change" : { + }, "Privacy Report" : { diff --git a/RxCodeMobile/State/MobileAppState+Inbound.swift b/RxCodeMobile/State/MobileAppState+Inbound.swift index 2431e5cd..f8d483f7 100644 --- a/RxCodeMobile/State/MobileAppState+Inbound.swift +++ b/RxCodeMobile/State/MobileAppState+Inbound.swift @@ -345,6 +345,10 @@ extension MobileAppState { func applySessionUpdate(_ update: SessionUpdatePayload) { if let previous = update.previousSessionID, previous != update.sessionID { + sessionIDRedirects[previous] = update.sessionID + for (stale, target) in sessionIDRedirects where target == previous { + sessionIDRedirects[stale] = update.sessionID + } if let carried = messagesBySession.removeValue(forKey: previous) { if let existing = messagesBySession[update.sessionID], !existing.isEmpty { // The new session id already accumulated live messages diff --git a/RxCodeMobile/State/MobileAppState+Intents.swift b/RxCodeMobile/State/MobileAppState+Intents.swift index da07db19..e9ad6d72 100644 --- a/RxCodeMobile/State/MobileAppState+Intents.swift +++ b/RxCodeMobile/State/MobileAppState+Intents.swift @@ -8,54 +8,78 @@ import SwiftUI import UIKit import os.log extension MobileAppState { + func resolveSessionID(_ sessionID: String) -> String { + var current = sessionID + var seen: Set = [] + while let next = sessionIDRedirects[current], !seen.contains(next) { + seen.insert(current) + current = next + } + return current + } + + func messages(sessionID: String) -> [ChatMessage] { + messagesBySession[resolveSessionID(sessionID)] ?? [] + } + + func sessionSummary(sessionID: String) -> SessionSummary? { + let resolved = resolveSessionID(sessionID) + return sessions.first { $0.id == resolved } + } + + func threadSummary(sessionID: String) -> MobileThreadSummary? { + let resolved = resolveSessionID(sessionID) + return threadSummaries.first { $0.sessionId == resolved } + } + func sendUserMessage(_ text: String, sessionID: String) async { guard isPaired else { return } - let payload = UserMessagePayload(sessionID: sessionID, text: text) + let payload = UserMessagePayload(sessionID: resolveSessionID(sessionID), text: text) try? await client.send(.userMessage(payload), toHex: pairedDesktopPubkey) } func cancelStream(sessionID: String) async { guard isPaired else { return } - let payload = CancelStreamPayload(sessionID: sessionID) + let payload = CancelStreamPayload(sessionID: resolveSessionID(sessionID)) try? await client.send(.cancelStream(payload), toHex: pairedDesktopPubkey) } func removeQueuedMessage(sessionID: String, queuedID: UUID) async { guard isPaired else { return } - let payload = RemoveQueuedMessagePayload(sessionID: sessionID, queuedMessageID: queuedID) + let payload = RemoveQueuedMessagePayload(sessionID: resolveSessionID(sessionID), queuedMessageID: queuedID) try? await client.send(.removeQueuedMessage(payload), toHex: pairedDesktopPubkey) } /// True iff the desktop reports the given session as actively streaming. func isSessionStreaming(_ sessionID: String) -> Bool { - sessions.first(where: { $0.id == sessionID })?.isStreaming ?? false + sessions.first(where: { $0.id == resolveSessionID(sessionID) })?.isStreaming ?? false } /// True iff the desktop reports the given session as currently producing /// reasoning/thinking tokens. func isSessionThinking(_ sessionID: String) -> Bool { - thinkingSessions.contains(sessionID) + thinkingSessions.contains(resolveSessionID(sessionID)) } /// Whether the given session has messages older than the loaded window. func hasMoreMessages(sessionID: String) -> Bool { - sessionsWithMoreMessages.contains(sessionID) + sessionsWithMoreMessages.contains(resolveSessionID(sessionID)) } /// Whether an older page is currently being fetched for the given session. func isLoadingMoreMessages(sessionID: String) -> Bool { - loadingMoreSessions.contains(sessionID) + loadingMoreSessions.contains(resolveSessionID(sessionID)) } /// Whether the initial message window for a newly opened thread is still /// being refreshed from the desktop. func isLoadingThreadMessages(sessionID: String) -> Bool { - loadingThreadMessageSessions.contains(sessionID) + loadingThreadMessageSessions.contains(resolveSessionID(sessionID)) } /// Mirror of the desktop's per-session queue, surfaced via `SessionSummary`. func queuedMessages(sessionID: String) -> [QueuedUserMessage] { - sessions.first(where: { $0.id == sessionID })?.queuedMessages ?? [] + sessions.first(where: { $0.id == resolveSessionID(sessionID) })?.queuedMessages ?? [] } /// Ask the desktop to create a new thread. The per-thread agent config @@ -125,7 +149,7 @@ extension MobileAppState { /// uses. Superseded plans (re-emitted within the same turn) are dropped so /// only actionable cards surface. func pendingPlans(sessionID: String) -> [PendingPlan] { - let messages = messagesBySession[sessionID] ?? [] + let messages = messagesBySession[resolveSessionID(sessionID)] ?? [] var plans: [PendingPlan] = [] for message in messages { for block in message.blocks { @@ -160,7 +184,7 @@ extension MobileAppState { guard isPaired else { return } let payload = PlanDecisionPayload( toolUseID: toolUseID, - sessionID: sessionID, + sessionID: resolveSessionID(sessionID), decision: action ) try? await client.send(.planDecision(payload), toHex: pairedDesktopPubkey) @@ -171,6 +195,7 @@ extension MobileAppState { /// Rename a thread. The local title is updated optimistically; the desktop /// confirms via the next snapshot / session update. func renameThread(sessionID: String, title: String) async { + let sessionID = resolveSessionID(sessionID) let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } replaceSession(sessionID: sessionID) { current in @@ -193,6 +218,7 @@ extension MobileAppState { /// Archive a thread. Optimistically flips `isArchived` so the row drops out /// of the active list right away. func archiveThread(sessionID: String) async { + let sessionID = resolveSessionID(sessionID) loadingThreadMessageSessions.remove(sessionID) replaceSession(sessionID: sessionID) { current in SessionSummary( @@ -213,6 +239,7 @@ extension MobileAppState { /// Delete a thread. Optimistically drops it from local state. func deleteThread(sessionID: String) async { + let sessionID = resolveSessionID(sessionID) sessions.removeAll { $0.id == sessionID } messagesBySession.removeValue(forKey: sessionID) sessionsWithMoreMessages.remove(sessionID) @@ -223,6 +250,7 @@ extension MobileAppState { } func replaceSession(sessionID: String, _ transform: (SessionSummary) -> SessionSummary) { + let sessionID = resolveSessionID(sessionID) guard let index = sessions.firstIndex(where: { $0.id == sessionID }) else { return } sessions[index] = transform(sessions[index]) } @@ -232,6 +260,7 @@ extension MobileAppState { action: ThreadActionRequestPayload.Action, newTitle: String? = nil ) async { + let sessionID = resolveSessionID(sessionID) guard isPaired else { logger.error("[ThreadAction] not paired — dropping action=\(action.rawValue, privacy: .public)") return @@ -295,6 +324,7 @@ extension MobileAppState { } func subscribe(to sessionID: String?) async { + let sessionID = sessionID.map(resolveSessionID) activeSessionID = sessionID guard isPaired else { return } if let sessionID { diff --git a/RxCodeMobile/State/MobileAppState+Sync.swift b/RxCodeMobile/State/MobileAppState+Sync.swift index 6e7ea5eb..1bdbcd7b 100644 --- a/RxCodeMobile/State/MobileAppState+Sync.swift +++ b/RxCodeMobile/State/MobileAppState+Sync.swift @@ -18,6 +18,7 @@ extension MobileAppState { /// settles. @discardableResult func loadMoreMessages(sessionID: String) async -> Bool { + let sessionID = resolveSessionID(sessionID) guard isPaired, sessionsWithMoreMessages.contains(sessionID), !loadingMoreSessions.contains(sessionID), @@ -71,6 +72,7 @@ extension MobileAppState { /// `threadChanges` via the `threadChangesResult` payload. func requestThreadChanges(sessionID: String) async { guard isPaired else { return } + let sessionID = resolveSessionID(sessionID) let requestID = UUID() pendingThreadChangesID = requestID isLoadingThreadChanges = true @@ -101,7 +103,8 @@ extension MobileAppState { /// Pending `AskUserQuestion` calls for one session, in the order the desktop /// queued them. func pendingQuestions(sessionID: String) -> [PendingQuestionPayload] { - pendingQuestions.filter { $0.sessionID == sessionID } + let sessionID = resolveSessionID(sessionID) + return pendingQuestions.filter { $0.sessionID == sessionID } } /// Submit the user's answers for one `AskUserQuestion` call to the desktop. diff --git a/RxCodeMobile/State/MobileAppState.swift b/RxCodeMobile/State/MobileAppState.swift index 031786de..47d82108 100644 --- a/RxCodeMobile/State/MobileAppState.swift +++ b/RxCodeMobile/State/MobileAppState.swift @@ -144,6 +144,9 @@ final class MobileAppState: ObservableObject { /// Last branch op error message, surfaced once and cleared by the UI. @Published var lastBranchOpError: String? @Published var messagesBySession: [String: [ChatMessage]] = [:] + /// Maps stale mobile-visible session ids, such as a temporary + /// pending-mobile id, to the authoritative desktop session id. + @Published var sessionIDRedirects: [String: String] = [:] /// Sessions the desktop currently reports as producing reasoning/thinking /// tokens. Drives the "Thinking…" label in the streaming indicator. @Published var thinkingSessions: Set = [] diff --git a/RxCodeMobile/Views/MobileBriefingDetailView.swift b/RxCodeMobile/Views/MobileBriefingDetailView.swift index f1bdaba1..23b97e13 100644 --- a/RxCodeMobile/Views/MobileBriefingDetailView.swift +++ b/RxCodeMobile/Views/MobileBriefingDetailView.swift @@ -47,6 +47,7 @@ struct MobileBriefingDetailView: View { onOpenSession(newSessionID) } .environmentObject(state) + .mobileSheetPresentation() } .refreshable { await state.refreshSnapshot() diff --git a/RxCodeMobile/Views/MobileChatView.swift b/RxCodeMobile/Views/MobileChatView.swift index 1387168d..c275033d 100644 --- a/RxCodeMobile/Views/MobileChatView.swift +++ b/RxCodeMobile/Views/MobileChatView.swift @@ -137,6 +137,11 @@ struct MobileChatView: View { .task(id: sessionID) { await runThreadLoadingGate() } + .task(id: resolvedSessionID) { + if !MobileDraftSessionID.isDraft(resolvedSessionID) { + await state.subscribe(to: resolvedSessionID) + } + } .onChange(of: isThreadLoadingMessages) { _, isLoading in handleThreadLoadingChange(isLoading) } @@ -167,13 +172,13 @@ struct MobileChatView: View { await state.refreshFromDesktop(reason: "open_run_profiles") } } + .mobileSheetPresentation() } } .sheet(isPresented: $showingChanges) { ThreadChangesSheet(sessionID: sessionID) .environmentObject(state) - .presentationDetents([.large]) - .presentationDragIndicator(.visible) + .mobileSheetPresentation([.large]) } .fullScreenCover(isPresented: $showingBrowser) { NavigationStack { @@ -220,8 +225,7 @@ struct MobileChatView: View { }, onClose: { presentedPlan = nil } ) - .presentationDetents([.large]) - .presentationDragIndicator(.visible) + .mobileSheetPresentation([.large]) } .onChange(of: sessionPlans) { _, plans in // The plan resolved (decided here or on another device) or @@ -553,11 +557,11 @@ struct MobileChatView: View { } private var messages: [ChatMessage] { - state.messagesBySession[sessionID] ?? [] + state.messages(sessionID: sessionID) } private var currentProjectID: UUID? { - state.sessions.first(where: { $0.id == sessionID })?.projectId + state.sessionSummary(sessionID: sessionID)?.projectId } private var browserLaunchURL: URL? { @@ -571,7 +575,7 @@ struct MobileChatView: View { } private var title: String { - state.sessions.first(where: { $0.id == sessionID })?.title ?? "Thread" + state.sessionSummary(sessionID: sessionID)?.title ?? "Thread" } /// Live todos from synced messages when available, otherwise from the @@ -579,14 +583,18 @@ struct MobileChatView: View { /// snapshots rather than `TodoWrite` message tool calls. private var todos: [TodoItem]? { TodoExtractor.latest(in: messages) - ?? state.sessions.first(where: { $0.id == sessionID })?.todos + ?? state.sessionSummary(sessionID: sessionID)?.todos } /// The desktop-generated summary for this thread, if one exists. Summaries /// are produced once a thread finishes a turn, so live threads may not have /// one yet. private var threadSummary: MobileThreadSummary? { - state.threadSummaries.first { $0.sessionId == sessionID } + state.threadSummary(sessionID: sessionID) + } + + private var resolvedSessionID: String { + state.resolveSessionID(sessionID) } private var isStreaming: Bool { @@ -607,6 +615,7 @@ struct MobileChatView: View { private var shouldShowThreadLoading: Bool { !MobileDraftSessionID.isDraft(sessionID) + && messages.isEmpty && isThreadLoadingOverlayVisible } diff --git a/RxCodeMobile/Views/MobileMCPServersView.swift b/RxCodeMobile/Views/MobileMCPServersView.swift index fa9e7f96..f7390997 100644 --- a/RxCodeMobile/Views/MobileMCPServersView.swift +++ b/RxCodeMobile/Views/MobileMCPServersView.swift @@ -48,10 +48,12 @@ struct MobileMCPServersView: View { .sheet(isPresented: $showingAdd) { MobileMCPServerFormView(existing: nil) .environmentObject(state) + .mobileSheetPresentation() } .sheet(item: $editing) { server in MobileMCPServerFormView(existing: server) .environmentObject(state) + .mobileSheetPresentation() } .alert( "Remove MCP server?", diff --git a/RxCodeMobile/Views/MobileQuestionSheet.swift b/RxCodeMobile/Views/MobileQuestionSheet.swift index 37303920..ad0a7939 100644 --- a/RxCodeMobile/Views/MobileQuestionSheet.swift +++ b/RxCodeMobile/Views/MobileQuestionSheet.swift @@ -78,8 +78,7 @@ struct MobileQuestionSheet: View { bottomActionBar } .background(ClaudeTheme.background) - .presentationDetents([.large]) - .presentationDragIndicator(.visible) + .mobileSheetPresentation([.large]) } } diff --git a/RxCodeMobile/Views/MobileRunProfileEditorView.swift b/RxCodeMobile/Views/MobileRunProfileEditorView.swift index 4e75300b..41435a0b 100644 --- a/RxCodeMobile/Views/MobileRunProfileEditorView.swift +++ b/RxCodeMobile/Views/MobileRunProfileEditorView.swift @@ -85,6 +85,7 @@ struct MobileRunProfileEditorView: View { } .sheet(item: $activePicker) { pick in folderPickerSheet(for: pick) + .mobileSheetPresentation() } } diff --git a/RxCodeMobile/Views/MobileSettingsView.swift b/RxCodeMobile/Views/MobileSettingsView.swift index 5e6fd5f2..84b4c145 100644 --- a/RxCodeMobile/Views/MobileSettingsView.swift +++ b/RxCodeMobile/Views/MobileSettingsView.swift @@ -68,6 +68,7 @@ struct MobileSettingsView: View { .navigationTitle("Pair New Mac") .navigationBarTitleDisplayMode(.inline) } + .mobileSheetPresentation() } .alert( "Remove pairing?", diff --git a/RxCodeMobile/Views/MobileSheetPresentationModifier.swift b/RxCodeMobile/Views/MobileSheetPresentationModifier.swift new file mode 100644 index 00000000..3bb7e926 --- /dev/null +++ b/RxCodeMobile/Views/MobileSheetPresentationModifier.swift @@ -0,0 +1,29 @@ +import SwiftUI + +private struct MobileSheetPresentationModifier: ViewModifier { + let detents: Set? + + @ViewBuilder + func body(content: Content) -> some View { + if let detents { + content + .presentationDetents(detents) + .presentationDragIndicator(.hidden) + .interactiveDismissDisabled(true) + } else { + content + .presentationDragIndicator(.hidden) + .interactiveDismissDisabled(true) + } + } +} + +extension View { + func mobileSheetPresentation() -> some View { + modifier(MobileSheetPresentationModifier(detents: nil)) + } + + func mobileSheetPresentation(_ detents: Set) -> some View { + modifier(MobileSheetPresentationModifier(detents: detents)) + } +} diff --git a/RxCodeMobile/Views/MobileSkillMarketView.swift b/RxCodeMobile/Views/MobileSkillMarketView.swift index 705d7378..8b20071a 100644 --- a/RxCodeMobile/Views/MobileSkillMarketView.swift +++ b/RxCodeMobile/Views/MobileSkillMarketView.swift @@ -67,6 +67,7 @@ struct MobileSkillMarketView: View { .sheet(isPresented: $showingGitSourceSheet) { MobileSkillGitSourceSheet() .environmentObject(state) + .mobileSheetPresentation() } } diff --git a/RxCodeMobile/Views/NewThreadSheet.swift b/RxCodeMobile/Views/NewThreadSheet.swift index 389738f9..aaec7e60 100644 --- a/RxCodeMobile/Views/NewThreadSheet.swift +++ b/RxCodeMobile/Views/NewThreadSheet.swift @@ -73,7 +73,7 @@ struct NewThreadSheet: View { } } } - .interactiveDismissDisabled(true) + .mobileSheetPresentation() .onAppear { seedConfigIfNeeded() DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { @@ -600,7 +600,7 @@ struct CreateBranchSheet: View { DispatchQueue.main.async { isFocused = true } } } - .presentationDetents([.medium]) + .mobileSheetPresentation([.medium]) } private func submit() { diff --git a/RxCodeMobile/Views/OnboardingView.swift b/RxCodeMobile/Views/OnboardingView.swift index 0bb3b6d3..b6a0a20d 100644 --- a/RxCodeMobile/Views/OnboardingView.swift +++ b/RxCodeMobile/Views/OnboardingView.swift @@ -110,6 +110,7 @@ struct OnboardingView: View { } } .preferredColorScheme(.dark) + .mobileSheetPresentation() } .photosPicker( isPresented: $photoPickerShown, diff --git a/RxCodeMobile/Views/PermissionApprovalSheet.swift b/RxCodeMobile/Views/PermissionApprovalSheet.swift index 72498ab1..bbbdc901 100644 --- a/RxCodeMobile/Views/PermissionApprovalSheet.swift +++ b/RxCodeMobile/Views/PermissionApprovalSheet.swift @@ -68,6 +68,6 @@ struct PermissionApprovalSheet: View { } } .padding(20) - .presentationDetents([.medium, .large]) + .mobileSheetPresentation([.medium, .large]) } } diff --git a/RxCodeMobile/Views/ProjectsSidebar.swift b/RxCodeMobile/Views/ProjectsSidebar.swift index be24a2d3..497ef18d 100644 --- a/RxCodeMobile/Views/ProjectsSidebar.swift +++ b/RxCodeMobile/Views/ProjectsSidebar.swift @@ -32,6 +32,7 @@ struct ProjectsSidebar: View { .sheet(isPresented: $showingRemoteFolderPicker) { RemoteFolderPickerView() .environmentObject(state) + .mobileSheetPresentation() } .refreshable { await state.refreshSnapshot() diff --git a/RxCodeMobile/Views/QueuedMessagesSheet.swift b/RxCodeMobile/Views/QueuedMessagesSheet.swift index 7e430cdd..ca5a5ace 100644 --- a/RxCodeMobile/Views/QueuedMessagesSheet.swift +++ b/RxCodeMobile/Views/QueuedMessagesSheet.swift @@ -50,7 +50,6 @@ struct QueuedMessagesSheet: View { } } } - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.visible) + .mobileSheetPresentation([.medium, .large]) } } diff --git a/RxCodeMobile/Views/RenameThreadSheet.swift b/RxCodeMobile/Views/RenameThreadSheet.swift index 31604420..ce874367 100644 --- a/RxCodeMobile/Views/RenameThreadSheet.swift +++ b/RxCodeMobile/Views/RenameThreadSheet.swift @@ -57,8 +57,7 @@ struct RenameThreadSheet: View { } } } - .presentationDetents([.medium]) - .presentationDragIndicator(.visible) + .mobileSheetPresentation([.medium]) } private func commit() { diff --git a/RxCodeMobile/Views/RootView.swift b/RxCodeMobile/Views/RootView.swift index 5aaf0209..651457c9 100644 --- a/RxCodeMobile/Views/RootView.swift +++ b/RxCodeMobile/Views/RootView.swift @@ -53,6 +53,7 @@ struct RootView: View { .navigationTitle("Pair New Mac") .navigationBarTitleDisplayMode(.inline) } + .mobileSheetPresentation() } .mobileDismissesKeyboardOnScroll() } @@ -239,6 +240,7 @@ struct RootView: View { .sheet(isPresented: $showSettings) { MobileSettingsView() .environmentObject(state) + .mobileSheetPresentation() } .onChange(of: selectedProject) { _, newValue in if newValue != nil { diff --git a/RxCodeMobile/Views/SessionsList.swift b/RxCodeMobile/Views/SessionsList.swift index 68d47a1b..3dbede9a 100644 --- a/RxCodeMobile/Views/SessionsList.swift +++ b/RxCodeMobile/Views/SessionsList.swift @@ -44,6 +44,7 @@ struct SessionsList: View { selected = newSessionID } .environmentObject(state) + .mobileSheetPresentation() } .searchable( text: $searchText, @@ -314,6 +315,7 @@ struct MobileRunProfilesView: View { MobileRunProfileEditorView(profile: profile, projectID: projectID) .environmentObject(state) } + .mobileSheetPresentation() } .alert("Run Profile Error", isPresented: Binding( get: { state.lastRunProfileError != nil }, diff --git a/RxCodeMobile/Views/SyncLoadingView.swift b/RxCodeMobile/Views/SyncLoadingView.swift index c7d8a882..ea14a088 100644 --- a/RxCodeMobile/Views/SyncLoadingView.swift +++ b/RxCodeMobile/Views/SyncLoadingView.swift @@ -72,6 +72,11 @@ struct SyncLoadingView: View { .padding(.top, 8) .opacity(appeared ? 1 : 0) + pairNewDesktopButton + .opacity(appeared ? 1 : 0) + .offset(y: appeared ? 0 : 16) + .padding(.top, 4) + Spacer() Spacer() } @@ -157,22 +162,27 @@ struct SyncLoadingView: View { } .buttonStyle(.plain) - if let onPairNewDesktop { - Button { - onPairNewDesktop() - } label: { - Label("Pair New Mac", systemImage: "plus.circle") - .font(.body.weight(.semibold)) - .padding(.horizontal, 20) - .padding(.vertical, 10) - } - .buttonStyle(.glass) - .foregroundStyle(.primary) - } + pairNewDesktopButton } .padding(.top, 8) } + @ViewBuilder + private var pairNewDesktopButton: some View { + if let onPairNewDesktop { + Button { + onPairNewDesktop() + } label: { + Label("Pair New Mac", systemImage: "plus.circle") + .font(.body.weight(.semibold)) + .padding(.horizontal, 20) + .padding(.vertical, 10) + } + .buttonStyle(.glass) + .foregroundStyle(.primary) + } + } + @ViewBuilder private var desktopPickerView: some View { if pairedDesktops.count > 1 { diff --git a/RxCodeMobile/Views/ThreadChangesSheet.swift b/RxCodeMobile/Views/ThreadChangesSheet.swift index 74746c57..851159de 100644 --- a/RxCodeMobile/Views/ThreadChangesSheet.swift +++ b/RxCodeMobile/Views/ThreadChangesSheet.swift @@ -153,21 +153,24 @@ struct ThreadChangesSheet: View { // capture race on a large file) fall through so the user still sees // the change as full-file diff or hunks. if let modified = edit.modifiedContent, - let original = edit.originalContent, - original != modified { + let original = edit.originalContent, + original != modified + { return .snapshot(original: original, modified: modified) } if let modified = edit.modifiedContent, - edit.originalContent == nil, - !modified.isEmpty { + edit.originalContent == nil, + !modified.isEmpty + { return .snapshot(original: "", modified: modified) } if let fullFileDiff = edit.fullFileDiff, !fullFileDiff.isEmpty { return .unified(fullFileDiff) } - return .hunks(edit.hunks.map { - PreviewFile.EditHunk(oldString: $0.oldString, newString: $0.newString) - }) + return .hunks( + edit.hunks.map { + PreviewFile.EditHunk(oldString: $0.oldString, newString: $0.newString) + }) } /// Sidebar `+/-` count. Prefers a fresh snapshot-pair diff when both @@ -176,7 +179,8 @@ struct ThreadChangesSheet: View { /// the snapshot pair collapsed to zero (race-loss on the original capture). private func turnStat(_ edit: SyncFileEdit) -> (added: Int, removed: Int) { if let modified = edit.modifiedContent { - let stat = ChangeDiffView.snapshotStat(original: edit.originalContent ?? "", modified: modified) + let stat = ChangeDiffView.snapshotStat( + original: edit.originalContent ?? "", modified: modified) if stat.added > 0 || stat.removed > 0 { return stat } @@ -328,6 +332,9 @@ struct ThreadChangeDetailView: View { let truncated: Bool @State private var diffDisplay: DiffView.LineDisplay = .wrap + @State private var navigationToken = 0 + @State private var navigationDirection: DiffView.ChangeNavigationDirection? + @State private var navigationState = DiffView.ChangeNavigationState.empty var body: some View { VStack(alignment: .leading, spacing: 8) { @@ -359,13 +366,36 @@ struct ThreadChangeDetailView: View { Button { diffDisplay = (diffDisplay == .wrap) ? .scroll : .wrap } label: { - Image(systemName: diffDisplay == .wrap - ? "arrow.left.and.right" - : "text.alignleft") + Image( + systemName: diffDisplay == .wrap + ? "arrow.left.and.right" + : "text.alignleft") } - .accessibilityLabel(diffDisplay == .wrap - ? "Switch to horizontal scroll" - : "Switch to wrap") + .accessibilityLabel( + diffDisplay == .wrap + ? "Switch to horizontal scroll" + : "Switch to wrap") + } + ToolbarItemGroup(placement: .bottomBar) { + Button { + requestChangeNavigation(.previous) + } label: { + Image(systemName: "arrow.up") + } + .disabled(navigationState.changeCount == 0) + .accessibilityLabel("Previous change") + + Button { + requestChangeNavigation(.next) + } label: { + Image(systemName: "arrow.down") + } + .disabled(navigationState.changeCount == 0) + .accessibilityLabel("Next change") + + Spacer() + + changePositionPill } } } @@ -378,23 +408,69 @@ struct ThreadChangeDetailView: View { if text.isEmpty { emptyDiff } else { - ChangeDiffView(unifiedDiff: text, display: diffDisplay, language: language) + ChangeDiffView( + unifiedDiff: text, + display: diffDisplay, + language: language, + navigationRequest: navigationRequest, + onNavigationStateChange: { navigationState = $0 } + ) } case .hunks(let hunks): if hunks.isEmpty { emptyDiff } else { - ChangeDiffView(hunks: hunks, display: diffDisplay, language: language) + ChangeDiffView( + hunks: hunks, + display: diffDisplay, + language: language, + navigationRequest: navigationRequest, + onNavigationStateChange: { navigationState = $0 } + ) } case .snapshot(let original, let modified): if original == modified { emptyDiff } else { - ChangeDiffView(original: original, modified: modified, display: diffDisplay, language: language) + ChangeDiffView( + original: original, + modified: modified, + display: diffDisplay, + language: language, + navigationRequest: navigationRequest, + onNavigationStateChange: { navigationState = $0 } + ) } } } + private var navigationRequest: DiffView.ChangeNavigationRequest? { + guard let navigationDirection else { return nil } + return DiffView.ChangeNavigationRequest( + direction: navigationDirection, token: navigationToken) + } + + private var changePositionText: String { + guard navigationState.changeCount > 0 else { return "No changes" } + guard let currentIndex = navigationState.currentIndex else { + return "\(navigationState.changeCount)" + } + return "\(currentIndex + 1) of \(navigationState.changeCount)" + } + + private var changePositionPill: some View { + Text(changePositionText) + .font(.caption.weight(.medium)) + .monospacedDigit() + .lineLimit(1) + .fixedSize(horizontal: true, vertical: true) + } + + private func requestChangeNavigation(_ direction: DiffView.ChangeNavigationDirection) { + navigationDirection = direction + navigationToken += 1 + } + private var emptyDiff: some View { Text("No diff content available.") .font(.callout) diff --git a/RxCodeMobile/Views/ThreadTodoSheet.swift b/RxCodeMobile/Views/ThreadTodoSheet.swift index b2eaa599..b91f014c 100644 --- a/RxCodeMobile/Views/ThreadTodoSheet.swift +++ b/RxCodeMobile/Views/ThreadTodoSheet.swift @@ -88,8 +88,7 @@ struct ThreadTodoSheet: View { } } } - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.visible) + .mobileSheetPresentation([.medium, .large]) } // MARK: Title header diff --git a/RxCodeMobileUITests/Mock/MockRelayServer.swift b/RxCodeMobileUITests/Mock/MockRelayServer.swift index 67a44ab9..0accb602 100644 --- a/RxCodeMobileUITests/Mock/MockRelayServer.swift +++ b/RxCodeMobileUITests/Mock/MockRelayServer.swift @@ -187,8 +187,16 @@ nonisolated final class MockRelayServer: @unchecked Sendable { hasMore: false )), on: connection) case .newSessionRequest(let request): - let sessionID = registerNewSession(for: request) - send(.snapshot(currentSnapshot(activeSessionID: sessionID)), on: connection) + let created = registerNewSession(for: request) + send(.snapshot(currentSnapshot(activeSessionID: created.pendingID)), on: connection) + queue.asyncAfter(deadline: .now() + 1.0) { [weak self, weak connection] in + guard let self, let connection else { return } + self.completeNewSessionRedirect( + from: created.pendingID, + to: created.sessionID, + on: connection + ) + } default: // pairRequest, apnsToken, userMessage, ping, … — decrypt and ignore. break @@ -223,13 +231,15 @@ nonisolated final class MockRelayServer: @unchecked Sendable { ) } - /// Materializes a fresh session + thread summary + canned transcript for a - /// `newSessionRequest`, mirroring what the desktop would persist before - /// pushing a snapshot back. Returns the new session ID so the snapshot - /// reply can mark it as active. - private func registerNewSession(for request: NewSessionRequestPayload) -> String { + /// Materializes a fresh pending session + thread summary + canned transcript + /// for a `newSessionRequest`. The real desktop first exposes a temporary + /// pending-mobile id, then later redirects it to the agent's real session id + /// after the stream result arrives; the mock mirrors that so navigation + /// regressions around stale ids are covered. + private func registerNewSession(for request: NewSessionRequestPayload) -> (pendingID: String, sessionID: String) { newSessionCounter += 1 let sessionID = "sess-new-\(newSessionCounter)" + let pendingID = "pending-mobile-ui-\(newSessionCounter)" let now = Date(timeIntervalSince1970: 1_716_000_000) let title = request.initialText? .trimmingCharacters(in: .whitespacesAndNewlines) @@ -243,7 +253,7 @@ nonisolated final class MockRelayServer: @unchecked Sendable { .first(where: { $0.projectId == request.projectID })?.branch ?? "main" let session = SessionSummary( - id: sessionID, + id: pendingID, projectId: request.projectID, title: title, updatedAt: now, @@ -251,7 +261,7 @@ nonisolated final class MockRelayServer: @unchecked Sendable { isArchived: false ) let summary = MobileThreadSummary( - sessionId: sessionID, + sessionId: pendingID, projectId: request.projectID, branch: branch, title: title, @@ -271,8 +281,54 @@ nonisolated final class MockRelayServer: @unchecked Sendable { dynamicSessions.append(session) dynamicThreadSummaries.append(summary) - dynamicMessagesBySession[sessionID] = transcript - return sessionID + dynamicMessagesBySession[pendingID] = transcript + return (pendingID, sessionID) + } + + /// Completes the pending-mobile -> real-session transition that desktop + /// sends after the agent returns its authoritative id. + private func completeNewSessionRedirect(from pendingID: String, to sessionID: String, on connection: NWConnection) { + guard let pendingSession = dynamicSessions.first(where: { $0.id == pendingID }) else { return } + let finalSession = SessionSummary( + id: sessionID, + projectId: pendingSession.projectId, + title: pendingSession.title, + updatedAt: pendingSession.updatedAt, + isPinned: pendingSession.isPinned, + isArchived: pendingSession.isArchived, + isStreaming: false, + attention: pendingSession.attention, + progress: pendingSession.progress, + todos: pendingSession.todos, + queuedMessages: pendingSession.queuedMessages, + hasUncheckedCompletion: pendingSession.hasUncheckedCompletion + ) + + let pendingSummary = dynamicThreadSummaries.first { $0.sessionId == pendingID } + let finalThreadSummary = MobileThreadSummary( + sessionId: sessionID, + projectId: pendingSummary?.projectId ?? pendingSession.projectId, + branch: pendingSummary?.branch ?? "main", + title: pendingSummary?.title ?? pendingSession.title, + summary: pendingSummary?.summary ?? "", + updatedAt: pendingSummary?.updatedAt ?? pendingSession.updatedAt + ) + + dynamicSessions.removeAll { $0.id == pendingID || $0.id == sessionID } + dynamicSessions.append(finalSession) + dynamicThreadSummaries.removeAll { $0.sessionId == pendingID || $0.sessionId == sessionID } + dynamicThreadSummaries.append(finalThreadSummary) + dynamicMessagesBySession[sessionID] = dynamicMessagesBySession.removeValue(forKey: pendingID) + + send(.sessionUpdate(SessionUpdatePayload( + sessionID: sessionID, + kind: .statusChanged, + message: nil, + isStreaming: false, + summary: finalSession, + previousSessionID: pendingID + )), on: connection) + send(.snapshot(currentSnapshot(activeSessionID: sessionID)), on: connection) } /// Seal `payload` for the mobile peer and send it as a binary WS message. diff --git a/RxCodeMobileUITests/iPadNavigationUITests.swift b/RxCodeMobileUITests/iPadNavigationUITests.swift index 4e660a6b..71a86f13 100644 --- a/RxCodeMobileUITests/iPadNavigationUITests.swift +++ b/RxCodeMobileUITests/iPadNavigationUITests.swift @@ -48,6 +48,7 @@ final class iPadNavigationUITests: XCTestCase { // navigate us into the projects split. Thread.sleep(forTimeInterval: 2.0) r.assertExists(r.chatScreen, "chat screen remains after snapshot settles") + r.assertMessagesShown() r.assertExists(r.briefingListScreen, "briefing list column still visible after settle") } diff --git a/RxCodeMobileUITests/iPhoneNavigationUITests.swift b/RxCodeMobileUITests/iPhoneNavigationUITests.swift index d8dffa0b..07727442 100644 --- a/RxCodeMobileUITests/iPhoneNavigationUITests.swift +++ b/RxCodeMobileUITests/iPhoneNavigationUITests.swift @@ -45,6 +45,7 @@ final class iPhoneNavigationUITests: XCTestCase { // Sustain the assertion a bit so a delayed snapshot can't pop us off. Thread.sleep(forTimeInterval: 2.0) r.assertExists(r.chatScreen, "chat screen remains after snapshot settles") + r.assertMessagesShown() } /// Case 3: Projects tab → project → thread list → thread → messages → back → diff --git a/RxCodeUITests/LocalAIProviderAcceptanceTests.swift b/RxCodeUITests/LocalAIProviderAcceptanceTests.swift index 41b83431..3ea56bbc 100644 --- a/RxCodeUITests/LocalAIProviderAcceptanceTests.swift +++ b/RxCodeUITests/LocalAIProviderAcceptanceTests.swift @@ -60,6 +60,7 @@ final class LocalAIProviderAcceptanceTests: XCTestCase { XCTAssertTrue(app.buttons["run-profile-run-button"].waitForExistence(timeout: 10)) runProfileSmoke() + sendBriefingNewThreadTurn(provider: provider) sendPlanModeTurn(provider: provider) sendIntegratedAITurn(provider: provider) } @@ -76,6 +77,7 @@ final class LocalAIProviderAcceptanceTests: XCTestCase { app.launchEnvironment = [ "RXCODE_APP_SUPPORT_DIR": fixture.appSupportURL.path, "RXCODE_UI_TESTING": "1", + "RXCODE_UI_TEST_SEED_BRIEFING": "1", "RXCODE_LOCAL_MCP_SERVER": fixture.mcpServerPath, ] app.launch() @@ -89,6 +91,33 @@ final class LocalAIProviderAcceptanceTests: XCTestCase { XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS %@", "RXCODE_UI_TEST_RUN_PROFILE")).element.waitForExistence(timeout: 15)) } + private func sendBriefingNewThreadTurn(provider: Provider) { + XCTAssertTrue(app.staticTexts["Briefings"].waitForExistence(timeout: 10)) + + let actionsButton = app.buttons["briefing-group-actions-button"] + XCTAssertTrue(actionsButton.waitForExistence(timeout: 10)) + actionsButton.click() + + let startNewChatMenuItem = app.menuItems["Start New Chat"] + if startNewChatMenuItem.waitForExistence(timeout: 2) { + startNewChatMenuItem.click() + } else { + let startNewChatButton = app.buttons["Start New Chat"] + XCTAssertTrue(startNewChatButton.waitForExistence(timeout: 5)) + startNewChatButton.click() + } + + let marker = "RXCODE_UI_TEST_BRIEFING_DONE" + let input = focusInput() + input.typeText("Start a new thread from the briefing view. Reply with \(marker) and no extra text.") + app.buttons["chat-send-button"].click() + + XCTAssertTrue( + app.staticTexts.containing(NSPredicate(format: "label CONTAINS %@", marker)).element.waitForExistence(timeout: 120), + "Expected the assistant response to remain visible in thread detail for \(provider.rawValue)" + ) + } + private func sendPlanModeTurn(provider: Provider) { let input = focusInput() app.typeKey(.tab, modifierFlags: [.shift]) From e85ca0393988253abb0bafe17659f3c7b0b9497f Mon Sep 17 00:00:00 2001 From: sirily11 <32106111+sirily11@users.noreply.github.com> Date: Mon, 25 May 2026 14:15:11 +0800 Subject: [PATCH 2/2] feat: add open file in file view mode --- Packages/Sources/DiffView/DiffView.swift | 62 ++++++++-- .../RxCodeCore/Utilities/LocalFileLink.swift | 84 +++++++++++++ .../Protocol/Payload+Sessions.swift | 45 +++++++ .../Sources/RxCodeSync/Protocol/Payload.swift | 10 ++ RxCode/App/AppState+MobileSnapshots.swift | 80 +++++++++++++ RxCode/App/AppState+MobileSync.swift | 14 +++ RxCode/App/RxCodeApp.swift | 33 +++-- RxCode/Resources/Localizable.xcstrings | 3 + .../MobileSyncService+EventDispatch.swift | 7 ++ RxCode/Services/MobileSyncService.swift | 1 + RxCode/Views/Sidebar/FileInspectorView.swift | 27 +++++ RxCodeMobile/Resources/Localizable.xcstrings | 12 ++ .../State/MobileAppState+Inbound.swift | 6 + RxCodeMobile/State/MobileAppState+Sync.swift | 36 ++++++ RxCodeMobile/State/MobileAppState.swift | 3 + RxCodeMobile/Views/MobileChatView.swift | 15 +++ .../Views/MobileRemoteFilePreviewSheet.swift | 113 ++++++++++++++++++ 17 files changed, 529 insertions(+), 22 deletions(-) create mode 100644 Packages/Sources/RxCodeCore/Utilities/LocalFileLink.swift create mode 100644 RxCodeMobile/Views/MobileRemoteFilePreviewSheet.swift diff --git a/Packages/Sources/DiffView/DiffView.swift b/Packages/Sources/DiffView/DiffView.swift index 73c0880e..46377d88 100644 --- a/Packages/Sources/DiffView/DiffView.swift +++ b/Packages/Sources/DiffView/DiffView.swift @@ -54,6 +54,7 @@ public struct DiffView: View { private let lines: [DiffLine] private let showsBackground: Bool + private let showsDiffMarkers: Bool private let display: LineDisplay private let language: String? private let navigationRequest: ChangeNavigationRequest? @@ -65,6 +66,7 @@ public struct DiffView: View { public init( lines: [DiffLine], showsBackground: Bool = true, + showsDiffMarkers: Bool = true, display: LineDisplay = .wrap, language: String? = nil, navigationRequest: ChangeNavigationRequest? = nil, @@ -72,6 +74,7 @@ public struct DiffView: View { ) { self.lines = lines self.showsBackground = showsBackground + self.showsDiffMarkers = showsDiffMarkers self.display = display self.language = language self.navigationRequest = navigationRequest @@ -127,7 +130,13 @@ public struct DiffView: View { ScrollView(.vertical) { LazyVStack(alignment: .leading, spacing: 0) { ForEach(Array(lines.enumerated()), id: \.offset) { offset, line in - DiffRow(line: line, layout: layout, wraps: true, language: language) + DiffRow( + line: line, + layout: layout, + showsDiffMarkers: showsDiffMarkers, + wraps: true, + language: language + ) .id(rowID(for: offset)) } } @@ -140,7 +149,11 @@ public struct DiffView: View { let viewportWidth = max(0, proxy.size.width - layout.width) let bodyWidth = max( viewportWidth, - Self.horizontalScrollBodyWidth(for: lines, layout: layout) + Self.horizontalScrollBodyWidth( + for: lines, + layout: layout, + showsDiffMarkers: showsDiffMarkers + ) ) ScrollView(.vertical) { HStack(alignment: .top, spacing: 0) { @@ -163,6 +176,7 @@ public struct DiffView: View { DiffRowBody( line: line, layout: layout, + showsDiffMarkers: showsDiffMarkers, wraps: false, language: language ) @@ -219,27 +233,41 @@ public struct DiffView: View { "diff-line-\(offset)" } - static func horizontalScrollBodyWidth(for lines: [DiffLine], layout: GutterLayout) -> CGFloat { - let maxColumns = lines.map { horizontalColumnCount(for: $0, layout: layout) }.max() ?? 0 + static func horizontalScrollBodyWidth( + for lines: [DiffLine], + layout: GutterLayout, + showsDiffMarkers: Bool = true + ) -> CGFloat { + let maxColumns = lines.map { + horizontalColumnCount( + for: $0, + layout: layout, + showsDiffMarkers: showsDiffMarkers + ) + }.max() ?? 0 let estimatedMonospaceWidth = ClaudeTheme.messageSize(12) * 0.72 return ceil(CGFloat(maxColumns) * estimatedMonospaceWidth) + 12 } - private static func horizontalColumnCount(for line: DiffLine, layout: GutterLayout) -> Int { + private static func horizontalColumnCount( + for line: DiffLine, + layout: GutterLayout, + showsDiffMarkers: Bool + ) -> Int { let prefixColumns: Int let body: String switch line.kind { case .added: - prefixColumns = 2 + prefixColumns = showsDiffMarkers ? 2 : 0 body = line.text.hasPrefix("+") ? String(line.text.dropFirst()) : line.text case .removed: - prefixColumns = 2 + prefixColumns = showsDiffMarkers ? 2 : 0 body = line.text.hasPrefix("-") ? String(line.text.dropFirst()) : line.text case .context: - prefixColumns = 2 + prefixColumns = showsDiffMarkers ? 2 : 0 body = line.text.hasPrefix(" ") ? String(line.text.dropFirst()) : line.text case .hunk, .meta: - prefixColumns = layout.columnCount > 0 ? 2 : 0 + prefixColumns = showsDiffMarkers && layout.columnCount > 0 ? 2 : 0 body = line.text } return prefixColumns + visualColumnCount(body) @@ -299,13 +327,20 @@ struct GutterLayout { private struct DiffRow: View { let line: DiffLine let layout: GutterLayout + let showsDiffMarkers: Bool let wraps: Bool let language: String? var body: some View { HStack(alignment: .firstTextBaseline, spacing: 0) { DiffRowGutter(line: line, layout: layout) - DiffRowBody(line: line, layout: layout, wraps: wraps, language: language) + DiffRowBody( + line: line, + layout: layout, + showsDiffMarkers: showsDiffMarkers, + wraps: wraps, + language: language + ) Spacer(minLength: 0) } .frame(maxWidth: .infinity, alignment: .leading) @@ -357,6 +392,7 @@ enum DiffMetrics { private struct DiffRowBody: View { let line: DiffLine let layout: GutterLayout + let showsDiffMarkers: Bool let wraps: Bool /// File-extension hint (e.g. "swift", "ts") used to syntax-highlight the /// body text. `nil` skips highlighting and falls back to a flat color. @@ -398,6 +434,10 @@ private struct DiffRowBody: View { /// flush with the `+` / `-` itself, instead of sitting in a phantom column /// past the marker like they do when marker and body are separate views. private var combinedText: Text { + guard showsDiffMarkers else { + return bodyTextView + } + let prefix: Text switch line.kind { case .added: @@ -455,6 +495,8 @@ private struct DiffRowBody: View { } private var rowBackground: Color { + guard showsDiffMarkers else { return .clear } + switch line.kind { case .added: return ClaudeTheme.statusSuccess.opacity(0.12) case .removed: return ClaudeTheme.statusError.opacity(0.12) diff --git a/Packages/Sources/RxCodeCore/Utilities/LocalFileLink.swift b/Packages/Sources/RxCodeCore/Utilities/LocalFileLink.swift new file mode 100644 index 00000000..69e9d187 --- /dev/null +++ b/Packages/Sources/RxCodeCore/Utilities/LocalFileLink.swift @@ -0,0 +1,84 @@ +import Foundation + +public struct LocalFileLink: Sendable, Equatable, Identifiable { + public var id: String { line.map { "\(path):\($0)" } ?? path } + + public let path: String + public let line: Int? + public let column: Int? + + public init(path: String, line: Int? = nil, column: Int? = nil) { + self.path = path + self.line = line + self.column = column + } + + public static func parse(_ url: URL) -> LocalFileLink? { + let raw = localPathString(from: url) + return raw.flatMap(parsePathString) + } + + public static func parsePathString(_ value: String) -> LocalFileLink? { + let decoded = value.removingPercentEncoding ?? value + let trimmed = decoded.trimmingCharacters(in: .whitespacesAndNewlines) + guard trimmed.hasPrefix("/") else { return nil } + + let parsed = stripLocationSuffix(from: trimmed) + guard parsed.path.hasPrefix("/") else { return nil } + + let standardized = URL(fileURLWithPath: parsed.path) + .standardizedFileURL + .path + return LocalFileLink(path: standardized, line: parsed.line, column: parsed.column) + } + + private static func localPathString(from url: URL) -> String? { + if url.isFileURL { + return url.path + } + + if let scheme = url.scheme?.lowercased(), + scheme == "http" || scheme == "https", + let host = url.host?.lowercased(), + host == "users" { + return "/Users" + url.path + } + + if url.scheme == nil || url.scheme?.isEmpty == true { + return url.relativeString.isEmpty ? url.absoluteString : url.relativeString + } + + let absolute = url.absoluteString + if absolute.hasPrefix("/") { + return absolute + } + + return nil + } + + private static func stripLocationSuffix(from value: String) -> (path: String, line: Int?, column: Int?) { + let firstPass = stripTrailingNumber(from: value) + guard let lastNumber = firstPass.number else { + return (value, nil, nil) + } + + let secondPass = stripTrailingNumber(from: firstPass.path) + if let lineNumber = secondPass.number { + return (secondPass.path, lineNumber, lastNumber) + } + return (firstPass.path, lastNumber, nil) + } + + private static func stripTrailingNumber(from value: String) -> (path: String, number: Int?) { + guard let colon = value.lastIndex(of: ":") else { + return (value, nil) + } + + let suffix = value[value.index(after: colon)...] + guard !suffix.isEmpty, suffix.allSatisfy(\.isNumber), let number = Int(suffix) else { + return (value, nil) + } + + return (String(value[.. Bool { + let standardizedPath = URL(fileURLWithPath: path).standardizedFileURL.path + return projectPaths.contains { projectPath in + let root = URL(fileURLWithPath: projectPath).standardizedFileURL.path + return standardizedPath == root || standardizedPath.hasPrefix(root + "/") + } + } + + nonisolated private static func readMobilePreviewFile(path: String) throws -> (content: String, truncated: Bool) { + let attributes = try FileManager.default.attributesOfItem(atPath: path) + let size = (attributes[.size] as? Int) ?? 0 + let byteLimit = 1_500_000 + let truncated = size > byteLimit + + guard let handle = try? FileHandle(forReadingFrom: URL(fileURLWithPath: path)) else { + throw MobileRemoteFileReadError.unreadable + } + defer { try? handle.close() } + + let data = try handle.read(upToCount: min(size, byteLimit)) ?? Data() + guard let content = String(data: data, encoding: .utf8) else { + throw MobileRemoteFileReadError.binary + } + return (content, truncated) + } + /// Strips per-file `originalContent` / `modifiedContent` once the running /// total of attached snapshots would push the encoded reply past the /// relay's 10 MiB envelope cap. Order is preserved so earlier files in the @@ -973,3 +1039,17 @@ extension AppState { } } + +private enum MobileRemoteFileReadError: LocalizedError { + case unreadable + case binary + + var errorDescription: String? { + switch self { + case .unreadable: + return "File could not be opened." + case .binary: + return "Binary file — preview not available." + } + } +} diff --git a/RxCode/App/AppState+MobileSync.swift b/RxCode/App/AppState+MobileSync.swift index 02261ac2..0ff1d110 100644 --- a/RxCode/App/AppState+MobileSync.swift +++ b/RxCode/App/AppState+MobileSync.swift @@ -148,6 +148,20 @@ extension AppState { } mobileSyncObservers.append(threadChangesObserver) + let remoteFileObserver = center.addObserver( + forName: .mobileSyncRemoteFileRequested, + object: nil, + queue: nil + ) { [weak self] notification in + guard let fromHex = notification.userInfo?["from"] as? String, + let request = notification.userInfo?["payload"] as? RemoteFileRequestPayload + else { return } + Task { @MainActor [weak self] in + await self?.handleMobileRemoteFileRequest(request, fromHex: fromHex) + } + } + mobileSyncObservers.append(remoteFileObserver) + let branchOpObserver = center.addObserver( forName: .mobileSyncBranchOpRequested, object: nil, diff --git a/RxCode/App/RxCodeApp.swift b/RxCode/App/RxCodeApp.swift index 8802b149..34107aa6 100644 --- a/RxCode/App/RxCodeApp.swift +++ b/RxCode/App/RxCodeApp.swift @@ -475,12 +475,7 @@ struct MainWindowRoot: View { .environment(windowState) .environment(chatBridge) .environment(\.openURL, OpenURLAction { url in - var finalURL = url - if url.scheme == nil || url.scheme!.isEmpty { - finalURL = URL(string: "https://\(url.absoluteString)") ?? url - } - NSWorkspace.shared.open(finalURL) - return .handled + openMarkdownLink(url, in: windowState) }) .transition(.opacity) } else { @@ -502,6 +497,25 @@ struct MainWindowRoot: View { } } +@MainActor +private func openMarkdownLink(_ url: URL, in windowState: WindowState) -> OpenURLAction.Result { + if let fileLink = LocalFileLink.parse(url) { + let fileName = URL(fileURLWithPath: fileLink.path).lastPathComponent + windowState.inspectorFile = PreviewFile( + path: fileLink.path, + name: fileName.isEmpty ? fileLink.path : fileName + ) + return .handled + } + + var finalURL = url + if url.scheme == nil || url.scheme!.isEmpty { + finalURL = URL(string: "https://\(url.absoluteString)") ?? url + } + NSWorkspace.shared.open(finalURL) + return .handled +} + // MARK: - Settings Window Root struct SettingsWindowRoot: View { @@ -531,12 +545,7 @@ struct ProjectWindowRoot: View { .environment(windowState) .environment(chatBridge) .environment(\.openURL, OpenURLAction { url in - var finalURL = url - if url.scheme == nil || url.scheme!.isEmpty { - finalURL = URL(string: "https://\(url.absoluteString)") ?? url - } - NSWorkspace.shared.open(finalURL) - return .handled + openMarkdownLink(url, in: windowState) }) .transition(.opacity) } else { diff --git a/RxCode/Resources/Localizable.xcstrings b/RxCode/Resources/Localizable.xcstrings index 7ab788fc..fbc66155 100644 --- a/RxCode/Resources/Localizable.xcstrings +++ b/RxCode/Resources/Localizable.xcstrings @@ -4709,6 +4709,9 @@ } } } + }, + "Open in Editor" : { + }, "Open in External Editor" : { "localizations" : { diff --git a/RxCode/Services/MobileSyncService+EventDispatch.swift b/RxCode/Services/MobileSyncService+EventDispatch.swift index c1e384ab..007a41bc 100644 --- a/RxCode/Services/MobileSyncService+EventDispatch.swift +++ b/RxCode/Services/MobileSyncService+EventDispatch.swift @@ -247,6 +247,13 @@ extension MobileSyncService { object: nil, userInfo: ["from": inbound.fromHex, "payload": req] ) + case .remoteFileRequest(let req): + guard acceptPairedOnlyPayload(from: inbound.fromHex, type: "remote_file_request") else { return } + NotificationCenter.default.post( + name: .mobileSyncRemoteFileRequested, + object: nil, + userInfo: ["from": inbound.fromHex, "payload": req] + ) case .subscribeSession(let sub): guard acceptPairedOnlyPayload(from: inbound.fromHex, type: "subscribe_session") else { return } subscribedSessions[inbound.fromHex] = sub.sessionID ?? "" diff --git a/RxCode/Services/MobileSyncService.swift b/RxCode/Services/MobileSyncService.swift index 5befb8b4..3b369db9 100644 --- a/RxCode/Services/MobileSyncService.swift +++ b/RxCode/Services/MobileSyncService.swift @@ -972,6 +972,7 @@ extension Notification.Name { static let mobileSyncLoadMoreMessagesRequested = Notification.Name("mobileSync.loadMoreMessagesRequested") static let mobileSyncSearchRequested = Notification.Name("mobileSync.searchRequested") static let mobileSyncThreadChangesRequested = Notification.Name("mobileSync.threadChangesRequested") + static let mobileSyncRemoteFileRequested = Notification.Name("mobileSync.remoteFileRequested") static let mobileSyncSettingsUpdateReceived = Notification.Name("mobileSync.settingsUpdateReceived") static let mobileSyncPermissionResponse = Notification.Name("mobileSync.permissionResponse") static let mobileSyncQuestionAnswerReceived = Notification.Name("mobileSync.questionAnswerReceived") diff --git a/RxCode/Views/Sidebar/FileInspectorView.swift b/RxCode/Views/Sidebar/FileInspectorView.swift index 158675c8..7f6b83e8 100644 --- a/RxCode/Views/Sidebar/FileInspectorView.swift +++ b/RxCode/Views/Sidebar/FileInspectorView.swift @@ -124,6 +124,33 @@ struct FileInspectorView: View { .help(isCopied ? "Copied" : "Copy") } + Menu { + let editors = ExternalEditorService.shared.detectedEditors() + if editors.isEmpty { + Text("No editors detected") + } else { + ForEach(editors) { editor in + Button { + ExternalEditorService.shared.open(editor, path: filePath) + } label: { + Label(editor.displayName, systemImage: editor.systemSymbol) + } + } + } + Divider() + Button { + ExternalEditorService.shared.revealInFinder(filePath) + } label: { + Label("Reveal in Finder", systemImage: "folder") + } + } label: { + Image(systemName: "arrow.up.forward.app") + .font(.system(size: ClaudeTheme.size(12))) + .foregroundStyle(ClaudeTheme.textSecondary) + } + .menuStyle(.borderlessButton) + .help("Open in Editor") + Button { windowState.inspectorFile = nil } label: { Image(systemName: "xmark") .font(.system(size: ClaudeTheme.size(11), weight: .medium)) diff --git a/RxCodeMobile/Resources/Localizable.xcstrings b/RxCodeMobile/Resources/Localizable.xcstrings index 47027b65..fbf43398 100644 --- a/RxCodeMobile/Resources/Localizable.xcstrings +++ b/RxCodeMobile/Resources/Localizable.xcstrings @@ -1246,6 +1246,9 @@ } } } + }, + "Couldn't Load File" : { + }, "Couldn't load the selected image." : { "localizations" : { @@ -2007,6 +2010,9 @@ } } } + }, + "Failed to request file: %@" : { + }, "Failed to request MCP servers: %@" : { "localizations" : { @@ -2154,6 +2160,9 @@ } } } + }, + "File preview truncated." : { + }, "Filter Skills" : { "localizations" : { @@ -3188,6 +3197,9 @@ } } } + }, + "Not connected to your Mac." : { + }, "Nothing to Show" : { "localizations" : { diff --git a/RxCodeMobile/State/MobileAppState+Inbound.swift b/RxCodeMobile/State/MobileAppState+Inbound.swift index f8d483f7..08ccc1e9 100644 --- a/RxCodeMobile/State/MobileAppState+Inbound.swift +++ b/RxCodeMobile/State/MobileAppState+Inbound.swift @@ -144,6 +144,12 @@ extension MobileAppState { pendingThreadChangesID = nil isLoadingThreadChanges = false threadChanges = result + case .remoteFileResult(let result): + guard acceptsActiveDesktopPayload(from: inbound.fromHex, type: "remote_file_result") else { return } + guard let pending = pendingRemoteFileID, result.clientRequestID == pending else { return } + pendingRemoteFileID = nil + isLoadingRemoteFile = false + remoteFileResult = result case .branchOpResult(let result): guard acceptsActiveDesktopPayload(from: inbound.fromHex, type: "branch_op_result") else { return } inFlightBranchOps.remove(result.clientRequestID) diff --git a/RxCodeMobile/State/MobileAppState+Sync.swift b/RxCodeMobile/State/MobileAppState+Sync.swift index 1bdbcd7b..eba697a3 100644 --- a/RxCodeMobile/State/MobileAppState+Sync.swift +++ b/RxCodeMobile/State/MobileAppState+Sync.swift @@ -87,6 +87,42 @@ extension MobileAppState { } } + func requestRemoteFile(path: String, line: Int?) async { + guard isPaired else { + remoteFileResult = RemoteFileResultPayload( + clientRequestID: UUID(), + path: path, + name: URL(fileURLWithPath: path).lastPathComponent, + line: line, + ok: false, + errorMessage: String(localized: "Not connected to your Mac.") + ) + return + } + + let requestID = UUID() + pendingRemoteFileID = requestID + remoteFileResult = nil + isLoadingRemoteFile = true + let payload = RemoteFileRequestPayload(clientRequestID: requestID, path: path, line: line) + do { + try await client.send(.remoteFileRequest(payload), toHex: pairedDesktopPubkey) + } catch { + if pendingRemoteFileID == requestID { + pendingRemoteFileID = nil + isLoadingRemoteFile = false + remoteFileResult = RemoteFileResultPayload( + clientRequestID: requestID, + path: path, + name: URL(fileURLWithPath: path).lastPathComponent, + line: line, + ok: false, + errorMessage: String(localized: "Failed to request file: \(error.localizedDescription)") + ) + } + } + } + func respondToPermission(allow: Bool, denyReason: String? = nil) async { guard let pending = pendingPermission else { return } let payload = PermissionResponsePayload( diff --git a/RxCodeMobile/State/MobileAppState.swift b/RxCodeMobile/State/MobileAppState.swift index 47d82108..ce6396b6 100644 --- a/RxCodeMobile/State/MobileAppState.swift +++ b/RxCodeMobile/State/MobileAppState.swift @@ -190,6 +190,9 @@ final class MobileAppState: ObservableObject { @Published var threadChanges: ThreadChangesResultPayload? @Published var isLoadingThreadChanges: Bool = false var pendingThreadChangesID: UUID? + @Published var remoteFileResult: RemoteFileResultPayload? + @Published var isLoadingRemoteFile: Bool = false + var pendingRemoteFileID: UUID? @Published var remoteFolderRoot: RemoteFolderNode? @Published var remoteFolderIsLoading = false diff --git a/RxCodeMobile/Views/MobileChatView.swift b/RxCodeMobile/Views/MobileChatView.swift index c275033d..59bf3c13 100644 --- a/RxCodeMobile/Views/MobileChatView.swift +++ b/RxCodeMobile/Views/MobileChatView.swift @@ -36,6 +36,9 @@ struct MobileChatView: View { @State private var presentedQuestion: PendingQuestionPayload? /// The plan whose review sheet is currently presented, if any. @State private var presentedPlan: PendingPlan? + /// Local file link tapped in rendered markdown. Mobile fetches the content + /// from the paired desktop before presenting it. + @State private var presentedFileLink: LocalFileLink? /// The message that sat at the top before an older page was requested. The /// viewport is restored to it after the page is prepended so the content /// the user was reading doesn't jump. @@ -180,6 +183,11 @@ struct MobileChatView: View { .environmentObject(state) .mobileSheetPresentation([.large]) } + .sheet(item: $presentedFileLink) { link in + MobileRemoteFilePreviewSheet(link: link) + .environmentObject(state) + .mobileSheetPresentation([.large]) + } .fullScreenCover(isPresented: $showingBrowser) { NavigationStack { MobileInAppBrowserView( @@ -514,6 +522,13 @@ struct MobileChatView: View { } } .accessibilityIdentifier("chat-screen") + .environment(\.openURL, OpenURLAction { url in + if let link = LocalFileLink.parse(url) { + presentedFileLink = link + return .handled + } + return .systemAction + }) } private var mobileTranscriptItems: [ChatTranscriptListItem] { diff --git a/RxCodeMobile/Views/MobileRemoteFilePreviewSheet.swift b/RxCodeMobile/Views/MobileRemoteFilePreviewSheet.swift new file mode 100644 index 00000000..2dc17920 --- /dev/null +++ b/RxCodeMobile/Views/MobileRemoteFilePreviewSheet.swift @@ -0,0 +1,113 @@ +import RxCodeChatKit +import RxCodeCore +import RxCodeSync +import SwiftUI + +struct MobileRemoteFilePreviewSheet: View { + @EnvironmentObject private var state: MobileAppState + @Environment(\.dismiss) private var dismiss + let link: LocalFileLink + @State private var lineDisplay: DiffView.LineDisplay = .wrap + + var body: some View { + NavigationStack { + content + .navigationTitle(fileName) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItemGroup(placement: .topBarTrailing) { + Button { + lineDisplay = (lineDisplay == .wrap) ? .scroll : .wrap + } label: { + Image( + systemName: lineDisplay == .wrap + ? "arrow.left.and.right" + : "text.alignleft" + ) + } + .accessibilityLabel( + lineDisplay == .wrap + ? "Switch to horizontal scroll" + : "Switch to wrap" + ) + + Button("Done") { dismiss() } + } + } + } + .task(id: link.id) { + await state.requestRemoteFile(path: link.path, line: link.line) + } + } + + @ViewBuilder + private var content: some View { + if state.isLoadingRemoteFile && state.remoteFileResult == nil { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else if let result = state.remoteFileResult, result.path == link.path { + if result.ok, let text = result.content { + codeView(text, truncated: result.truncated) + } else { + errorView(result.errorMessage ?? "Could not load this file.") + } + } else { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + + private func codeView(_ text: String, truncated: Bool) -> some View { + VStack(spacing: 0) { + if truncated { + HStack(spacing: 6) { + Image(systemName: "exclamationmark.triangle") + Text("File preview truncated.") + Spacer(minLength: 0) + } + .font(.footnote) + .foregroundStyle(.secondary) + .padding(.horizontal, 12) + .padding(.vertical, 8) + Divider() + } + + DiffView( + lines: codeLines(in: text), + showsDiffMarkers: false, + display: lineDisplay, + language: SyntaxHighlighter.language(forFilename: link.path) + ) + } + } + + private func errorView(_ message: String) -> some View { + ContentUnavailableView { + Label("Couldn't Load File", systemImage: "doc.text.magnifyingglass") + } description: { + Text(message) + } actions: { + Button("Retry") { + Task { await state.requestRemoteFile(path: link.path, line: link.line) } + } + } + } + + private var fileName: String { + URL(fileURLWithPath: link.path).lastPathComponent + } + + private func codeLines(in text: String) -> [DiffLine] { + var lines = text.components(separatedBy: "\n") + if lines.last == "" { lines.removeLast() } + let content = lines.isEmpty ? [""] : lines + return content.enumerated().map { index, line in + DiffLine( + text: line, + kind: .context, + oldLineNumber: nil, + newLineNumber: index + 1 + ) + } + } +}