From 6e6baa3571aa8beaf09b32ccf7263281382f9e4d Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sat, 26 Sep 2026 22:40:57 -0400 Subject: [PATCH 01/23] Replace the focus double blink with one short pulse Focus navigation, focus-history jumps, and notification dismissal all play FocusFlashPattern: two blinks over 0.9 s. On the pane-focus hot path that repeats every time. One 0.4 s pulse still marks where focus landed. Co-Authored-By: Claude Opus 5.5 --- Sources/Panels/Panel.swift | 10 ++++++---- cmuxTests/WindowAndDragTests.swift | 31 +++++++++++------------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index c6c036f3ac4c..eb5516515288 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -246,11 +246,13 @@ struct FocusFlashSegment: Equatable { let curve: FocusFlashCurve } +/// One short pulse: enough to say where focus or attention landed without +/// replaying a blink every time someone moves between panes. enum FocusFlashPattern { - static let values: [Double] = [0, 1, 0, 1, 0] - static let keyTimes: [Double] = [0, 0.25, 0.5, 0.75, 1] - static let duration: TimeInterval = 0.9 - static let curves: [FocusFlashCurve] = [.easeOut, .easeIn, .easeOut, .easeIn] + static let values: [Double] = [0, 1, 0] + static let keyTimes: [Double] = [0, 0.3, 1] + static let duration: TimeInterval = 0.4 + static let curves: [FocusFlashCurve] = [.easeOut, .easeIn] static let ringInset: Double = Double(PanelOverlayRingMetrics.inset) static let ringCornerRadius: Double = Double(PanelOverlayRingMetrics.cornerRadius) diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index 1db5547ff4bd..133839236416 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -711,38 +711,29 @@ final class TerminalDefaultFileOpenRequestTests: XCTestCase { final class FocusFlashPatternTests: XCTestCase { - func testFocusFlashPatternMatchesTerminalDoublePulseShape() { - XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0, 1, 0]) - XCTAssertEqual(FocusFlashPattern.keyTimes, [0, 0.25, 0.5, 0.75, 1]) - XCTAssertEqual(FocusFlashPattern.duration, 0.9, accuracy: 0.0001) - XCTAssertEqual(FocusFlashPattern.curves, [.easeOut, .easeIn, .easeOut, .easeIn]) + func testFocusFlashPatternIsOneShortPulse() { + XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0]) + XCTAssertEqual(FocusFlashPattern.keyTimes, [0, 0.3, 1]) + XCTAssertEqual(FocusFlashPattern.duration, 0.4, accuracy: 0.0001) + XCTAssertEqual(FocusFlashPattern.curves, [.easeOut, .easeIn]) XCTAssertEqual(FocusFlashPattern.ringInset, Double(PanelOverlayRingMetrics.inset), accuracy: 0.0001) XCTAssertEqual(FocusFlashPattern.ringCornerRadius, Double(PanelOverlayRingMetrics.cornerRadius), accuracy: 0.0001) } - func testFocusFlashPatternSegmentsCoverFullDoublePulseTimeline() { + func testFocusFlashPatternSegmentsCoverThePulse() { let segments = FocusFlashPattern.segments - XCTAssertEqual(segments.count, 4) + XCTAssertEqual(segments.count, 2) XCTAssertEqual(segments[0].delay, 0.0, accuracy: 0.0001) - XCTAssertEqual(segments[0].duration, 0.225, accuracy: 0.0001) + XCTAssertEqual(segments[0].duration, 0.12, accuracy: 0.0001) XCTAssertEqual(segments[0].targetOpacity, 1, accuracy: 0.0001) XCTAssertEqual(segments[0].curve, .easeOut) - XCTAssertEqual(segments[1].delay, 0.225, accuracy: 0.0001) - XCTAssertEqual(segments[1].duration, 0.225, accuracy: 0.0001) + XCTAssertEqual(segments[1].delay, 0.12, accuracy: 0.0001) + XCTAssertEqual(segments[1].duration, 0.28, accuracy: 0.0001) XCTAssertEqual(segments[1].targetOpacity, 0, accuracy: 0.0001) XCTAssertEqual(segments[1].curve, .easeIn) - - XCTAssertEqual(segments[2].delay, 0.45, accuracy: 0.0001) - XCTAssertEqual(segments[2].duration, 0.225, accuracy: 0.0001) - XCTAssertEqual(segments[2].targetOpacity, 1, accuracy: 0.0001) - XCTAssertEqual(segments[2].curve, .easeOut) - - XCTAssertEqual(segments[3].delay, 0.675, accuracy: 0.0001) - XCTAssertEqual(segments[3].duration, 0.225, accuracy: 0.0001) - XCTAssertEqual(segments[3].targetOpacity, 0, accuracy: 0.0001) - XCTAssertEqual(segments[3].curve, .easeIn) + XCTAssertEqual(FocusFlashPattern.opacity(at: 0.4), 0, accuracy: 0.0001) } } From 3d08f3b9e89dfb262be41a1da6c28f6c6c60c32f Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 02:38:06 -0400 Subject: [PATCH 02/23] Don't flash a pane when typing is what dismissed its notification Typing into a terminal dismisses its notification or unread mark and used to play the dismiss flash on the pane under the user's cursor. The ring clearing is the acknowledgement there. Clicks, jumps and workspace selection still flash, since they move the user's eye to a pane. Co-Authored-By: Claude Opus 5.5 --- .../NotificationDismissalContext.swift | 7 +++++++ .../NotificationDismissalModel.swift | 2 +- .../NotificationDismissalModelTests.swift | 13 +++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift index fe79e58b024f..94d0440932d6 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift @@ -22,6 +22,13 @@ public enum NotificationDismissalContext: Sendable { } } + /// Whether a dismissal in this context flashes the pane. Typing means the + /// user is already working in that pane, so the cleared ring is the + /// acknowledgement; a flash under their cursor only adds noise. + public var flashesOnDismiss: Bool { + self != .terminalInteraction + } + /// Whether this context may clear a manually-set unread indicator. public var canDismissManualUnreadIndicator: Bool { self == .terminalInteraction diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift index 2d72bc1d0354..dc88707ed4a2 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift @@ -222,7 +222,7 @@ public final class NotificationDismissalModel: NotificationDismissing { host.storeClearFocusedReadIndicator(workspaceId: workspaceId, surfaceId: surfaceId) } } - if let targetPanelId { + if let targetPanelId, context.flashesOnDismiss { if hasUnreadNotification || hasFocusedIndicator { host.workspaceTriggerNotificationDismissFlash(workspaceId: workspaceId, panelId: targetPanelId) } else if didDismissUnreadIndicator { diff --git a/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift b/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift index 3c58e218330f..62fba7cdca4e 100644 --- a/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift +++ b/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift @@ -230,16 +230,25 @@ struct NotificationDismissalModelTests { #expect(!model.dismissNotificationOnDirectInteraction(workspaceId: workspaceId, surfaceId: panelId)) #expect(host.log.isEmpty) - // Terminal interaction clears it and triggers the indicator flash. + // Terminal interaction clears it without flashing the pane being typed in. #expect(model.dismissNotificationOnTerminalInteraction(workspaceId: workspaceId, surfaceId: panelId)) let prefix = String(panelId.uuidString.prefix(4)) #expect(host.log == [ "panelClearManualUnread", "storeClearManualUnread", "storeClearManualUnread:\(prefix)", - "clearFocusedRead:\(prefix)", "unreadIndicatorFlash", + "clearFocusedRead:\(prefix)", ]) } + @Test func typingDismissesAnUnreadNotificationWithoutFlashing() { + let (model, host, workspaceId, panelId) = makeModel() + host.unreadNotificationSurfaces = [panelId] + + #expect(model.dismissNotificationOnTerminalInteraction(workspaceId: workspaceId, surfaceId: panelId)) + let prefix = String(panelId.uuidString.prefix(4)) + #expect(host.log == ["markRead:\(prefix)", "clearFocusedRead:\(prefix)"]) + } + @Test func restoredUnreadNotClearedByPlainActiveFocus() { let (model, host, workspaceId, panelId) = makeModel() host.restoredPanelUnread = [panelId] From 391819a2d662e56f6a3bd005463983c1045b5278 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 05:00:23 -0400 Subject: [PATCH 03/23] Add a Double Blink setting for the pane flash `notifications.paneFlashDoubleBlink` (off by default) brings back the 0.9 s double blink for anyone who prefers it; the default stays one short pulse. FocusFlashPattern becomes a value with `pulse` and `doubleBlink` shapes, and every flash reads `FocusFlashPattern.current` when it starts. The setting sits under Pane Flash in Settings, in the command palette, cmux.json, the schema, and settings search. Co-Authored-By: Claude Opus 5.5 --- .../CmuxConfigSchema.generated.swift | 355 +++++++++--------- .../Keys/NotificationsCatalogSection.swift | 7 + .../CuratedSettingEntry+Default.swift | 1 + .../CmuxSettingsUI/Sections/AppSection.swift | 17 +- .../SettingsRowAnchorResolutionTests.swift | 1 + Resources/Localizable.xcstrings | 195 ++++++++++ ...CmuxSettingsFileStore+SupportedPaths.swift | 1 + Sources/CmuxSettingsJSONPathSupport.swift | 1 + .../CommandPaletteSettingsToggle.swift | 11 + Sources/GhosttyTerminalView.swift | 9 +- ...rdShortcutSettingsFileStore+Template.swift | 1 + Sources/Panels/BrowserPanelView.swift | 4 +- Sources/Panels/CustomSidebarPanelView.swift | 6 +- Sources/Panels/FilePreviewPanel.swift | 4 +- Sources/Panels/MarkdownPanelView.swift | 4 +- Sources/Panels/Panel.swift | 42 ++- Sources/Panels/TerminalPanel.swift | 2 +- Sources/RightSidebarToolPanel.swift | 4 +- Sources/SettingsSearchAliases.swift | 1 + Sources/SettingsSearchIndex.swift | 2 + .../TerminalNotificationStoreSettings.swift | 7 + Sources/TmuxWorkspacePaneOverlayView.swift | 8 +- cmuxTests/WindowAndDragTests.swift | 34 +- skills/cmux-settings/references/all-keys.md | 1 + web/data/cmux.schema.json | 6 + 25 files changed, 507 insertions(+), 217 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index 51b6193307ff..ad43fbd3dcd7 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -174,183 +174,184 @@ enum CmuxEmbeddedConfigSchema { "ZmF1bHRzIHRvIHRydWUuIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJub3RpZmljYXRpb25zIjogewogICAgICAidGl0bGUiOiAibm90aWZpY2F0aW9ucyIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJOb3RpZmljYXRpb24gYmVoYXZpb3IgZnJvbSBTZXR0aW5ncyA+IE5vdGlmaWNhdGlvbnMuIiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICJwcm9wZXJ0aWVzIjogewog", "ICAgICAgICJkb2NrQmFkZ2UiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IHRoZSB1bnJlYWQgY291bnQgaW4gdGhlIERvY2sgdGlsZS4iCiAgICAgICAgfSwKICAgICAgICAic2hvd0luTWVudUJhciI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAg", "ImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgdGhlIG1lbnUgYmFyIGV4dHJhLiIKICAgICAgICB9LAogICAgICAgICJ1bnJlYWRQYW5lUmluZyI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkhpZ2hsaWdodCBwYW5lcyB3aXRoIHVucmVhZCBub3RpZmljYXRpb25zLiIKICAgICAgICB9LAogICAgICAgICJwYW5lRmxh", - "c2giOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJGbGFzaCB0aGUgZm9jdXNlZCBwYW5lIHdoZW4gcmVxdWVzdGVkLiIKICAgICAgICB9LAogICAgICAgICJwYW5lRmxhc2hDb2xvciI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXhPck51bGwiLAogICAgICAgICAg", - "ImRlZmF1bHQiOiBudWxsLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5ub3RpZmljYXRpb25zLnBhbmVGbGFzaENvbG9yIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJPdmVycmlkZSB0aGUgcGFuZSBmbGFzaCBhbmQgdW5yZWFkIHJpbmcgY29sb3IuIE51bGwga2VlcHMgdGhlIGJ1aWx0LWluIGJsdWUuIgogICAgICAgIH0sCiAgICAgICAgInN1cHByZXNzT25seUZvY3VzZWRTdXJmYWNlIjogewogICAgICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAgICAg", - "InR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5ub3RpZmljYXRpb25zLnN1cHByZXNzT25seUZvY3VzZWRTdXJmYWNlIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJXaGVuIGVuYWJsZWQsIGEgbm90aWZpY2F0aW9uIGJhbm5lciBpcyBhdXRvLXdpdGhkcmF3biBvbmx5IHdoZW4gaXRzIHN1cmZhY2UgaXMgdGhlIGV4YWN0IGZvY3VzZWQgc3VyZmFjZS4gQSBiYW5uZXIgZGVsaXZlcmVkIGZvciBhIG5vbi1m", - "b2N1c2VkIHN1cmZhY2UgaW4gdGhlIGN1cnJlbnRseSB2aXNpYmxlIHdvcmtzcGFjZSBzdGF5cyB1cCB1bnRpbCB5b3UgZm9jdXMgdGhhdCBzdXJmYWNlIChvciBjbGljay9kaXNtaXNzIGl0KSwgaW5zdGVhZCBvZiBiZWluZyByZXRyYWN0ZWQgd2hlbiB0aGUgd29ya3NwYWNlIGJlY29tZXMgdmlzaWJsZS4gT2ZmIHByZXNlcnZlcyB0aGUgbGVnYWN5IHdvcmtzcGFjZS12aXNpYmlsaXR5IHdpdGhkcmF3LiIKICAgICAgICB9LAogICAgICAgICJhZ2VudFBlcm1pc3Npb25Qcm9tcHQiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3Bl", - "cyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubm90aWZpY2F0aW9ucy5hZ2VudFBlcm1pc3Npb25Qcm9tcHQiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk5vdGlmeSB3aGVuIGFuIGFnZW50IChlLmcuIENsYXVkZSBDb2RlKSBpcyBibG9ja2VkIHdhaXRpbmcgZm9yIHlvdXIgcGVybWlzc2lvbiB0byBydW4gYSB0b29sLiBPbiBieSBkZWZhdWx0LCBzaW5jZSB0", - "aGlzIGlzIHRoZSBhbGVydCB5b3UgbXVzdCBhY3Qgb24gdG8gdW5ibG9jayB0aGUgYWdlbnQuIgogICAgICAgIH0sCiAgICAgICAgImFnZW50VHVybkNvbXBsZXRlIjogewogICAgICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWyJ3aGVuSWRsZSIsICJhbHdheXMiLCAibmV2ZXIiXSwKICAgICAgICAgICJkZWZhdWx0IjogIndoZW5JZGxlIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubm90", - "aWZpY2F0aW9ucy5hZ2VudFR1cm5Db21wbGV0ZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV2hlbiB0byBub3RpZnkgdGhhdCBhbiBhZ2VudCBmaW5pc2hlZCBhIHR1cm4uIHdoZW5JZGxlIChkZWZhdWx0KSBzdXBwcmVzc2VzIHRoZSBub3RpZmljYXRpb24gd2hpbGUgdGhlIGFnZW50IHN0aWxsIGhhcyBhIHJ1bm5pbmcgYmFja2dyb3VuZCB0YXNrIG9yIGEgcGVuZGluZyBzY2hlZHVsZWQgd2FrZXVwLCBzbyB5b3UgYXJlIHBpbmdlZCBvbmNlIHdvcmsgdHJ1bHkgZHJhaW5zLiBhbHdheXMgbm90aWZpZXMgb24gZXZlcnkg", - "dHVybiBlbmQ7IG5ldmVyIGRpc2FibGVzIGl0LiIKICAgICAgICB9LAogICAgICAgICJhZ2VudElkbGVSZW1pbmRlciI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5ub3RpZmljYXRpb25zLmFnZW50SWRsZVJlbWluZGVyIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJOb3RpZnkgd2hlbiBhbiBhZ2VudCBoYXMg", - "YmVlbiBpZGxlIHdhaXRpbmcgZm9yIHlvdXIgaW5wdXQgKGFib3V0IDYwcyBhZnRlciBhIHR1cm4gZW5kcykuIFN1cHByZXNzZWQgd2hpbGUgYmFja2dyb3VuZCB3b3JrIGZyb20gdGhlIGxhc3QgdHVybiBpcyBzdGlsbCBwZW5kaW5nLCBzbyBhIHJ1bm5pbmcgYnVpbGQgb3Igd2F0Y2hlciBkb2VzIG5vdCB0cmlnZ2VyIGEgZmFsc2Ugd2FpdGluZyBhbGVydC4iCiAgICAgICAgfSwKICAgICAgICAic291bmQiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJzdHJpbmci", - "LAogICAgICAgICAgImVudW0iOiBbCiAgICAgICAgICAgICJkZWZhdWx0IiwKICAgICAgICAgICAgIkJhc3NvIiwKICAgICAgICAgICAgIkJsb3ciLAogICAgICAgICAgICAiQm90dGxlIiwKICAgICAgICAgICAgIkZyb2ciLAogICAgICAgICAgICAiRnVuayIsCiAgICAgICAgICAgICJHbGFzcyIsCiAgICAgICAgICAgICJIZXJvIiwKICAgICAgICAgICAgIk1vcnNlIiwKICAgICAgICAgICAgIlBpbmciLAogICAgICAgICAgICAiUG9wIiwKICAgICAgICAgICAgIlB1cnIiLAogICAgICAgICAgICAiU29zdW1pIiwKICAgICAgICAg", - "ICAgIlN1Ym1hcmluZSIsCiAgICAgICAgICAgICJUaW5rIiwKICAgICAgICAgICAgImN1c3RvbV9maWxlIiwKICAgICAgICAgICAgIm5vbmUiCiAgICAgICAgICBdLAogICAgICAgICAgImRlZmF1bHQiOiAiZGVmYXVsdCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiTm90aWZpY2F0aW9uIHNvdW5kIHByZXNldC4iCiAgICAgICAgfSwKICAgICAgICAiY3VzdG9tU291bmRGaWxlUGF0aCI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAg", - "ICAiZGVmYXVsdCI6ICIiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkxvY2FsIHBhdGggdG8gdGhlIGN1c3RvbSBub3RpZmljYXRpb24gc291bmQgZmlsZS4iCiAgICAgICAgfSwKICAgICAgICAic291bmRPdmVycmlkZXMiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImRlZmF1bHQiOiB7fSwKICAgICAgICAgICJtYXhQcm9wZXJ0aWVzIjogMjU2LAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0", - "aW9ucy5ub3RpZmljYXRpb25zLnNvdW5kT3ZlcnJpZGVzIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTcGFyc2UgcGVyLWFnZW50IGFuZCBwZXItYWxlcnQtdHlwZSBub3RpZmljYXRpb24gc291bmQgb3ZlcnJpZGVzLiBNaXNzaW5nIGNlbGxzIHVzZSBub3RpZmljYXRpb25zLnNvdW5kLiIsCiAgICAgICAgICAicHJvcGVydHlOYW1lcyI6IHsKICAgICAgICAgICAgImFsbE9mIjogWwogICAgICAgICAgICAgIHsgInBhdHRlcm4iOiAiXltBLVphLXowLTkuXy1dKyQiIH0sCiAgICAgICAgICAgICAgeyAibWF4TGVuZ3RoIjog", - "NjQgfSwKICAgICAgICAgICAgICB7ICJub3QiOiB7ICJlbnVtIjogWyIuIiwgIi4uIl0gfSB9CiAgICAgICAgICAgIF0KICAgICAgICAgIH0sCiAgICAgICAgICAicGF0dGVyblByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJeW0EtWmEtejAtOS5fLV0rJCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgICAgICJtYXhQcm9wZXJ0aWVzIjogMywKICAgICAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAg", - "ICAgICAgICJ0dXJuRG9uZSI6IHsgIiRyZWYiOiAiIy8kZGVmcy9ub3RpZmljYXRpb25Tb3VuZE92ZXJyaWRlIiB9LAogICAgICAgICAgICAgICAgIm5lZWRzSW5wdXQiOiB7ICIkcmVmIjogIiMvJGRlZnMvbm90aWZpY2F0aW9uU291bmRPdmVycmlkZSIgfSwKICAgICAgICAgICAgICAgICJlcnJvclN0YWxsZWQiOiB7ICIkcmVmIjogIiMvJGRlZnMvbm90aWZpY2F0aW9uU291bmRPdmVycmlkZSIgfQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGll", - "cyI6IGZhbHNlCiAgICAgICAgfSwKICAgICAgICAiY29tbWFuZCI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZGVmYXVsdCI6ICIiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk9wdGlvbmFsIHNoZWxsIGNvbW1hbmQgdG8gcnVuIGFsb25nc2lkZSBub3RpZmljYXRpb24gZGVsaXZlcnkuIgogICAgICAgIH0sCiAgICAgICAgImhvb2tzTW9kZSI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51", - "bSI6IFsiYXBwZW5kIiwgInJlcGxhY2UiXSwKICAgICAgICAgICJkZWZhdWx0IjogImFwcGVuZCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQ29udHJvbHMgd2hldGhlciBwcm9qZWN0LWxvY2FsIG5vdGlmaWNhdGlvbiBob29rcyBhcHBlbmQgdG8gaW5oZXJpdGVkIGhvb2tzIG9yIHJlcGxhY2UgdGhlbS4iCiAgICAgICAgfSwKICAgICAgICAiaG9va3MiOiB7CiAgICAgICAgICAidHlwZSI6ICJhcnJheSIsCiAgICAgICAgICAiZGVmYXVsdCI6IFtdLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkNvbXBvc2FibGUgc2hl", - "bGwgaG9va3MgdGhhdCByZWNlaXZlIG5vdGlmaWNhdGlvbiBwb2xpY3kgSlNPTiBvbiBzdGRpbiBhbmQgcmV0dXJuIHVwZGF0ZWQgcG9saWN5IEpTT04gb24gc3Rkb3V0LiIsCiAgICAgICAgICAiaXRlbXMiOiB7CiAgICAgICAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAgICAgICAicmVxdWlyZWQiOiBbImlkIiwgImNvbW1hbmQiXSwKICAgICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICAgImlkIjogewogICAgICAgICAgICAg", - "ICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICAgICAgICJtaW5MZW5ndGgiOiAxLAogICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlN0YWJsZSBob29rIGlkZW50aWZpZXIgdXNlZCBpbiBsb2dzIGFuZCBmYWlsdXJlIGFsZXJ0cy4iCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAiY29tbWFuZCI6IHsKICAgICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgICAibWluTGVuZ3RoIjogMSwKICAgICAgICAgICAgICAgICJwYXR0ZXJuIjogIi4qXFxTLioiLAogICAgICAgICAg", - "ICAgICAgImRlc2NyaXB0aW9uIjogIlNoZWxsIGNvbW1hbmQgcnVuIHdpdGggbm90aWZpY2F0aW9uIHBvbGljeSBKU09OIG9uIHN0ZGluLiIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJ0aW1lb3V0U2Vjb25kcyI6IHsKICAgICAgICAgICAgICAgICJ0eXBlIjogIm51bWJlciIsCiAgICAgICAgICAgICAgICAiZXhjbHVzaXZlTWluaW11bSI6IDAsCiAgICAgICAgICAgICAgICAiZGVmYXVsdCI6IDIwLAogICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIk1heGltdW0gdGltZSBjbXV4IHdhaXRzIGZvciB0aGlz", - "IGhvb2sgYmVmb3JlIGZhbGxpbmcgYmFjayB0byBkZWZhdWx0IG5vdGlmaWNhdGlvbiBiZWhhdmlvci4iCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAiZW5hYmxlZCI6IHsKICAgICAgICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIkRpc2FibGUgdGhpcyBob29rIHdpdGhvdXQgcmVtb3ZpbmcgaXQuIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAg", - "ICAgfQogICAgfSwKICAgICJzaWRlYmFyIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJzaWRlYmFyIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlNpZGViYXIgY29udGVudCBhbmQgbWV0YWRhdGEgdmlzaWJpbGl0eSBmcm9tIFNldHRpbmdzID4gU2lkZWJhci4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImhpZGVBbGxEZXRhaWxzIjogewogICAgICAgICAg", - "InR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkhpZGUgYWxsIHBlci13b3Jrc3BhY2UgZGV0YWlsIHJvd3MuIgogICAgICAgIH0sCiAgICAgICAgIndyYXBXb3Jrc3BhY2VUaXRsZXMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogZmFsc2UsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQWxsb3cgd29ya3NwYWNlIHRpdGxlcyBpbiB0aGUgc2lkZWJhciB0byB3cmFwIHRvIG11bHRpcGxlIGxpbmVz", - "IGluc3RlYWQgb2YgdHJ1bmNhdGluZyBhZnRlciBvbmUgbGluZS4iCiAgICAgICAgfSwKICAgICAgICAic2hvd1dvcmtzcGFjZURlc2NyaXB0aW9uIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBjdXN0b20gd29ya3NwYWNlIGRlc2NyaXB0aW9ucyBpbiB0aGUgc2lkZWJhci4iCiAgICAgICAgfSwKICAgICAgICAid29ya3NwYWNlRGVzY3JpcHRpb25Db2xvciI6IHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMv", - "Y29sb3JIZXhPck51bGwiLAogICAgICAgICAgImRlZmF1bHQiOiBudWxsLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk92ZXJyaWRlIHRoZSB3b3Jrc3BhY2UgZGVzY3JpcHRpb24gdGV4dCBjb2xvciBpbiB0aGUgc2lkZWJhci4iCiAgICAgICAgfSwKICAgICAgICAiYmV0YSI6IHsKICAgICAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFeHBlcmltZW50YWwgc2lkZWJhciBmZWF0dXJlcy4iLAogICAgICAgICAg", - "InByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJ3b3Jrc3BhY2VUb2RvcyI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJXb3Jrc3BhY2UgdG9kb3MgY2hlY2tsaXN0IHByZXNlbnRhdGlvbi4iLAogICAgICAgICAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICAgICAgICAgImNvbnRyb2xzIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAg", - "ICAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIldvcmtzcGFjZSB0b2RvIGVkaXRpbmcgY29udHJvbHMuIiwKICAgICAgICAgICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgImVuYWJsZWQiOiB7CiAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICAgICAgICAgICAgICJkZWZhdWx0IjogZmFsc2UsCiAgICAgICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hv", - "dyBBZGQgQ2hlY2tsaXN0IEl0ZW0gYW5kIHdvcmtzcGFjZSBzdGF0dXMgY29udHJvbHMgb24gd29ya3NwYWNlIHJvd3MuIFdoZW4gZmFsc2UsIHRoZSB0b2RvIHN1bW1hcnkgc3RheXMgcmVhZC1vbmx5LiIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiY2hlY2tsaXN0U3R5bGUiOiB7CiAgICAgICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgICAgICJlbnVtIjogWyJwb3BvdmVyIiwgImlubGluZSJdLAog", - "ICAgICAgICAgICAgICAgICAiZGVmYXVsdCI6ICJwb3BvdmVyIiwKICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIkhvdyBhIHJvdydzIGNoZWNrbGlzdCBvcGVucyBmcm9tIGl0cyBzdW1tYXJ5IGxpbmU6IGFuIGFuY2hvcmVkIHBvcG92ZXIgb3IgaW5saW5lIGV4cGFuc2lvbi4iCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAiYnJhbmNoTGF5b3V0IjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAg", - "ICJlbnVtIjogWyJ2ZXJ0aWNhbCIsICJpbmxpbmUiXSwKICAgICAgICAgICJkZWZhdWx0IjogInZlcnRpY2FsIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IGdpdCBicmFuY2ggZGV0YWlscyBzdGFja2VkIHZlcnRpY2FsbHkgb3IgaW5saW5lLiIKICAgICAgICB9LAogICAgICAgICJzdGFja0JyYW5jaERpcmVjdG9yeSI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJSZW5kZXIgdGhlIGdpdCBicmFuY2ggYW5k", - "IHdvcmtpbmcgZGlyZWN0b3J5IG9uIHNlcGFyYXRlIGxpbmVzIGluc3RlYWQgb2Ygc2hhcmluZyBvbmUgbGluZS4iCiAgICAgICAgfSwKICAgICAgICAicGF0aExhc3RTZWdtZW50T25seSI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJUcnVuY2F0ZSBzaWRlYmFyIHBhdGhzIGZyb20gdGhlIHN0YXJ0LCBzaG93aW5nIGFzIG11Y2ggb2YgdGhlIHRyYWlsaW5nIHBhdGggYXMgZml0cyB3aXRoIGEgbGVhZGluZyDigKYvLiBX", - "aGVuIGZhbHNlLCBmdWxsIHBhdGhzIGFyZSBhYmJyZXZpYXRlZCB3aXRoIH4vLiIKICAgICAgICB9LAogICAgICAgICJzaG93Tm90aWZpY2F0aW9uTWVzc2FnZSI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgdGhlIGxhdGVzdCBub3RpZmljYXRpb24gdGV4dCBpbiB0aGUgc2lkZWJhci4iCiAgICAgICAgfSwKICAgICAgICAibm90aWZpY2F0aW9uTWVzc2FnZUxpbmVMaW1pdCI6IHsKICAgICAgICAgICJ0eXBlIjog", - "ImludGVnZXIiLAogICAgICAgICAgIm1pbmltdW0iOiAxLAogICAgICAgICAgIm1heGltdW0iOiA1MCwKICAgICAgICAgICJkZWZhdWx0IjogMTIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiTWF4aW11bSBsaW5lcyBzaG93biBmb3IgdGhlIGxhdGVzdCBub3RpZmljYXRpb24gYmVsb3cgZWFjaCB3b3Jrc3BhY2UgdGl0bGUuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuc2lkZWJhci5ub3RpZmljYXRpb25NZXNzYWdlTGluZUxpbWl0IgogICAgICAgIH0sCiAgICAgICAgInNob3dC", - "cmFuY2hEaXJlY3RvcnkiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IHRoZSB3b3Jrc3BhY2Ugd29ya2luZyBkaXJlY3RvcnkuIgogICAgICAgIH0sCiAgICAgICAgInNob3dQdWxsUmVxdWVzdHMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IHB1bGwgcmVxdWVzdCBtZXRhZGF0YSBpbiB0aGUgc2lk", - "ZWJhci4iCiAgICAgICAgfSwKICAgICAgICAid2F0Y2hHaXRTdGF0dXMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJXYXRjaCByZXBvc2l0b3J5IGZpbGVzIGZvciBzaWRlYmFyIGJyYW5jaCBhbmQgcHVsbCByZXF1ZXN0IG1ldGFkYXRhIHdpdGhvdXQgcG9sbGluZyBnaXQuIgogICAgICAgIH0sCiAgICAgICAgIm1ha2VQdWxsUmVxdWVzdHNDbGlja2FibGUiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAg", - "ICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJBbGxvdyBzaWRlYmFyIHB1bGwgcmVxdWVzdCBtZXRhZGF0YSB0byBvcGVuIGxpbmtzIHdoZW4gY2xpY2tlZC4iCiAgICAgICAgfSwKICAgICAgICAib3BlblB1bGxSZXF1ZXN0TGlua3NJbkNtdXhCcm93c2VyIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3BlbiBzaWRlYmFyIHB1bGwgcmVxdWVzdCBsaW5rcyBpbiB0aGUgZW1iZWRk", - "ZWQgY211eCBicm93c2VyLiIKICAgICAgICB9LAogICAgICAgICJvcGVuUG9ydExpbmtzSW5DbXV4QnJvd3NlciI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk9wZW4gc2lkZWJhciBwb3J0IGxpbmtzIGluIHRoZSBlbWJlZGRlZCBjbXV4IGJyb3dzZXIuIgogICAgICAgIH0sCiAgICAgICAgInNob3dTU0giOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAg", - "ICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IFNTSCBjb25uZWN0aW9uIGRldGFpbHMuIgogICAgICAgIH0sCiAgICAgICAgInNob3dQb3J0cyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgbGlzdGVuaW5nIHBvcnRzLiIKICAgICAgICB9LAogICAgICAgICJzaG93TG9nIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24i", - "OiAiU2hvdyByZWNlbnQgbG9nIHNuaXBwZXRzLiIKICAgICAgICB9LAogICAgICAgICJzaG93UHJvZ3Jlc3MiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IHByb2dyZXNzIGluZGljYXRvcnMuIgogICAgICAgIH0sCiAgICAgICAgInNob3dBZ2VudEFjdGl2aXR5IjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAi", - "U2hvdyB0aGUgbG9hZGluZyBzcGlubmVyIG9uIHdvcmtzcGFjZXMgd2l0aCBydW5uaW5nIGNvZGluZyBhZ2VudHMgb3IgYWN0aXZlIGxvYWRlcnMuIgogICAgICAgIH0sCiAgICAgICAgImxvYWRpbmdTcGlubmVyUG9zaXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbImxlYWRpbmciLCAidHJhaWxpbmciXSwKICAgICAgICAgICJkZWZhdWx0IjogImxlYWRpbmciLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIldoaWNoIHNpZGUgb2YgdGhlIHdvcmtzcGFjZSByb3cgdGhlIGxv", - "YWRpbmcgc3Bpbm5lciBhcHBlYXJzIG9uOiBsZWFkaW5nIChsZWZ0LCBzaGFyaW5nIHRoZSB1bnJlYWQtYmFkZ2Ugc2xvdCkgb3IgdHJhaWxpbmcgKHJpZ2h0KS4iCiAgICAgICAgfSwKICAgICAgICAibm90aWZpY2F0aW9uQmFkZ2VQb3NpdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsibGVhZGluZyIsICJ0cmFpbGluZyJdLAogICAgICAgICAgImRlZmF1bHQiOiAibGVhZGluZyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV2hpY2ggc2lkZSBvZiB0aGUgd29ya3NwYWNl", - "IHJvdyB0aGUgdW5yZWFkIG5vdGlmaWNhdGlvbiBiYWRnZSBhcHBlYXJzIG9uOiBsZWFkaW5nIChsZWZ0KSBvciB0cmFpbGluZyAocmlnaHQpLiIKICAgICAgICB9LAogICAgICAgICJzaG93Q3VzdG9tTWV0YWRhdGEiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IGN1c3RvbSBtZXRhZGF0YSBwaWxscy4iCiAgICAgICAgfSwKICAgICAgICAicmlnaHRNYXhXaWR0aCI6IHsKICAgICAgICAgICJ0eXBlIjogIm51bWJl", - "ciIsCiAgICAgICAgICAiZXhjbHVzaXZlTWluaW11bSI6IDAsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLnNpZGViYXIucmlnaHRNYXhXaWR0aCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiTWF4aW11bSB3aWR0aCBpbiBwb2ludHMgZm9yIHRoZSByaWdodCBzaWRlYmFyLiBXaGVuIG9taXR0ZWQsIHRoZSBidWlsdC1pbiBkeW5hbWljIGNhcCBhcHBsaWVzLiIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAid29ya3NwYWNlQ29sb3JzIjogewogICAgICAieC1jbXV4LXNjb3Bl", - "cyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJ3b3Jrc3BhY2VDb2xvcnMiLAogICAgICAiZGVzY3JpcHRpb24iOiAiV29ya3NwYWNlIHRhYiBhbmQgYmFkZ2UgY29sb3JzIGZyb20gU2V0dGluZ3MgPiBXb3Jrc3BhY2UgQ29sb3JzLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAiaW5kaWNhdG9yU3R5bGUiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbCiAg", - "ICAgICAgICAgICJsZWZ0UmFpbCIsCiAgICAgICAgICAgICJzb2xpZEZpbGwiLAogICAgICAgICAgICAicmFpbCIsCiAgICAgICAgICAgICJib3JkZXIiLAogICAgICAgICAgICAid2FzaCIsCiAgICAgICAgICAgICJsaWZ0IiwKICAgICAgICAgICAgInR5cG9ncmFwaHkiLAogICAgICAgICAgICAid2FzaFJhaWwiLAogICAgICAgICAgICAiYmx1ZVdhc2hDb2xvclJhaWwiCiAgICAgICAgICBdLAogICAgICAgICAgImRlZmF1bHQiOiAibGVmdFJhaWwiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkFjdGl2ZSB3b3Jrc3BhY2Ug", - "aW5kaWNhdG9yIHN0eWxlLiBMZWdhY3kgYWxpYXNlcyBhcmUgYWNjZXB0ZWQgYW5kIG5vcm1hbGl6ZWQuIgogICAgICAgIH0sCiAgICAgICAgInNlbGVjdGlvbkNvbG9yIjogewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9jb2xvckhleE9yTnVsbCIsCiAgICAgICAgICAiZGVmYXVsdCI6IG51bGwsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3ZlcnJpZGUgdGhlIHNlbGVjdGVkIHdvcmtzcGFjZSBiYWNrZ3JvdW5kIGNvbG9yLiIKICAgICAgICB9LAogICAgICAgICJub3RpZmljYXRpb25CYWRnZUNvbG9yIjogewogICAg", - "ICAgICAgIiRyZWYiOiAiIy8kZGVmcy9jb2xvckhleE9yTnVsbCIsCiAgICAgICAgICAiZGVmYXVsdCI6IG51bGwsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3ZlcnJpZGUgdGhlIHVucmVhZCBub3RpZmljYXRpb24gYmFkZ2UgY29sb3IuIgogICAgICAgIH0sCiAgICAgICAgImNvbG9ycyI6IHsKICAgICAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRnVsbCBuYW1lZCB3b3Jrc3BhY2UgY29sb3IgcGFsZXR0ZS4gSW5jbHVkZSBidWlsdC1pbiBlbnRyaWVzIHlvdSB3YW50IHRvIGtl", - "ZXAsIHJlbW92ZSBrZXlzIHRvIHJlbW92ZSBjb2xvcnMsIGFuZCBhZGQgbW9yZSBuYW1lZCBlbnRyaWVzIHRvIGV4dGVuZCB0aGUgcGlja2VyLiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHsKICAgICAgICAgICAgIlJlZCI6ICIjQzAzOTJCIiwKICAgICAgICAgICAgIkNyaW1zb24iOiAiIzkyMkIyMSIsCiAgICAgICAgICAgICJPcmFuZ2UiOiAiI0EwNDAwMCIsCiAgICAgICAgICAgICJBbWJlciI6ICIjN0Q2NjA4IiwKICAgICAgICAgICAgIk9saXZlIjogIiM0QTVDMTgiLAogICAgICAgICAgICAiR3JlZW4iOiAiIzE5NkYzRCIs", - "CiAgICAgICAgICAgICJUZWFsIjogIiMwMDZCNkIiLAogICAgICAgICAgICAiQXF1YSI6ICIjMEU2QjhDIiwKICAgICAgICAgICAgIkJsdWUiOiAiIzE1NjVDMCIsCiAgICAgICAgICAgICJOYXZ5IjogIiMxQTUyNzYiLAogICAgICAgICAgICAiSW5kaWdvIjogIiMyODM1OTMiLAogICAgICAgICAgICAiUHVycGxlIjogIiM2QTFCOUEiLAogICAgICAgICAgICAiTWFnZW50YSI6ICIjQUQxNDU3IiwKICAgICAgICAgICAgIlJvc2UiOiAiIzg4MEU0RiIsCiAgICAgICAgICAgICJCcm93biI6ICIjN0IzRjAwIiwKICAgICAgICAgICAg", - "IkNoYXJjb2FsIjogIiMzRTRCNUUiCiAgICAgICAgICB9LAogICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogewogICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4IgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInBhbGV0dGVPdmVycmlkZXMiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkxlZ2FjeSB3b3Jrc3BhY2UgY29sb3Igb3ZlcnJpZGVzIGZvciBidWlsdC1pbiBwYWxldHRlIG5hbWVzLiBQcmVmZXIgd29ya3NwYWNlQ29sb3Jz", - "LmNvbG9ycyBmb3IgbmV3IGNvbmZpZ3MuIiwKICAgICAgICAgICJkZWZhdWx0Ijoge30sCiAgICAgICAgICAiZGVwcmVjYXRlZCI6IHRydWUsCiAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXgiCiAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAiY3VzdG9tQ29sb3JzIjogewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkxlZ2FjeSBsaXN0IG9mIGN1c3RvbSB3b3Jrc3BhY2UgY29sb3JzLiBQ", - "cmVmZXIgd29ya3NwYWNlQ29sb3JzLmNvbG9ycyBmb3IgbmV3IGNvbmZpZ3MuIiwKICAgICAgICAgICJkZWZhdWx0IjogW10sCiAgICAgICAgICAiZGVwcmVjYXRlZCI6IHRydWUsCiAgICAgICAgICAiaXRlbXMiOiB7CiAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXgiCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgInNpZGViYXJBcHBlYXJhbmNlIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJzaWRlYmFyQXBwZWFyYW5jZSIsCiAg", - "ICAgICJkZXNjcmlwdGlvbiI6ICJTaWRlYmFyIHRpbnQgc2V0dGluZ3MgZnJvbSBTZXR0aW5ncyA+IFNpZGViYXIgQXBwZWFyYW5jZS4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIm1hdGNoVGVybWluYWxCYWNrZ3JvdW5kIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlVzZSB0aGUgdGVybWluYWwg", - "YmFja2dyb3VuZCBpbnN0ZWFkIG9mIHRoZSBzaWRlYmFyIHRpbnQuIgogICAgICAgIH0sCiAgICAgICAgInRpbnRDb2xvciI6IHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXgiLAogICAgICAgICAgImRlZmF1bHQiOiAiIzAwMDAwMCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQmFzZSBzaWRlYmFyIHRpbnQgY29sb3IgdXNlZCB3aGVuIGxpZ2h0L2Rhcmsgb3ZlcnJpZGVzIGFyZSBub3Qgc2V0LiIKICAgICAgICB9LAogICAgICAgICJsaWdodE1vZGVUaW50Q29sb3IiOiB7CiAgICAgICAgICAiJHJlZiI6", - "ICIjLyRkZWZzL2NvbG9ySGV4T3JOdWxsIiwKICAgICAgICAgICJkZWZhdWx0IjogbnVsbCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaWRlYmFyIHRpbnQgb3ZlcnJpZGUgZm9yIGxpZ2h0IGFwcGVhcmFuY2UuIgogICAgICAgIH0sCiAgICAgICAgImRhcmtNb2RlVGludENvbG9yIjogewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9jb2xvckhleE9yTnVsbCIsCiAgICAgICAgICAiZGVmYXVsdCI6IG51bGwsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2lkZWJhciB0aW50IG92ZXJyaWRlIGZvciBkYXJrIGFwcGVh", - "cmFuY2UuIgogICAgICAgIH0sCiAgICAgICAgInRpbnRPcGFjaXR5IjogewogICAgICAgICAgInR5cGUiOiAibnVtYmVyIiwKICAgICAgICAgICJtaW5pbXVtIjogMCwKICAgICAgICAgICJtYXhpbXVtIjogMSwKICAgICAgICAgICJkZWZhdWx0IjogMC4xOCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaWRlYmFyIHRpbnQgb3BhY2l0eSBmcm9tIDAgdG8gMS4gTm90ZTogdGhpcyBvbmx5IGNvbnRyb2xzIHRoZSBzaWRlYmFyIHRpbnQsIG5vdCB0ZXJtaW5hbC93aW5kb3cgdHJhbnNwYXJlbmN5LiBGb3IgdGVybWluYWwgYmFj", - "a2dyb3VuZCB0cmFuc3BhcmVuY3kgb3IgYmx1ciwgc2V0IGBiYWNrZ3JvdW5kLW9wYWNpdHlgIGFuZCBgYmFja2dyb3VuZC1ibHVyYCBpbiBgfi8uY29uZmlnL2dob3N0dHkvY29uZmlnYCBhbmQgcnVuIGBjbXV4IHJlbG9hZC1jb25maWdgLiIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAiYXV0b21hdGlvbiI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAiYXV0b21hdGlvbiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJTb2NrZXQgY29udHJvbCBhbmQgYXV0b21hdGlvbiBz", - "ZXR0aW5ncyBmcm9tIFNldHRpbmdzID4gQXV0b21hdGlvbi4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgInNvY2tldENvbnRyb2xNb2RlIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWwogICAgICAgICAgICAib2ZmIiwKICAgICAgICAgICAgImNtdXhPbmx5IiwKICAgICAgICAgICAgImF1dG9tYXRpb24iLAogICAgICAgICAgICAicGFzc3dvcmQiLAogICAgICAg", - "ICAgICAiYWxsb3dBbGwiLAogICAgICAgICAgICAib3BlbkFjY2VzcyIsCiAgICAgICAgICAgICJmdWxsT3BlbkFjY2VzcyIsCiAgICAgICAgICAgICJub3RpZmljYXRpb25zIiwKICAgICAgICAgICAgImZ1bGwiCiAgICAgICAgICBdLAogICAgICAgICAgImRlZmF1bHQiOiAiY211eE9ubHkiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNvY2tldCBjb250cm9sIG1vZGUuIExlZ2FjeSBhbGlhc2VzIGFyZSBhY2NlcHRlZCBhbmQgbm9ybWFsaXplZC4iCiAgICAgICAgfSwKICAgICAgICAic29ja2V0UGFzc3dvcmQiOiB7CiAg", - "ICAgICAgICAib25lT2YiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAidHlwZSI6ICJudWxsIgogICAgICAgICAgICB9CiAgICAgICAgICBdLAogICAgICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJQYXNzd29yZCBmb3IgcGFzc3dvcmQtbW9kZSBzb2NrZXQgYWNjZXNzLiBVc2UgbnVsbCBvciBhbiBlbXB0eSBzdHJpbmcgdG8gY2xlYXIgaXQuIgogICAgICAgIH0sCiAgICAg", - "ICAgImNsYXVkZUNvZGVJbnRlZ3JhdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkVuYWJsZSBjbXV4IGludGVncmF0aW9uIGhvb2tzIGZvciBDbGF1ZGUgQ29kZS4iCiAgICAgICAgfSwKICAgICAgICAiY29kZXhJbnRlZ3JhdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkVuYWJsZSBjbXV4IGludGVn", - "cmF0aW9uIGhvb2tzIGZvciBDb2RleC4gV2hlbiBkaXNhYmxlZCwgY211eCBubyBsb25nZXIgd3JhcHMgdGhlIGNvZGV4IGNvbW1hbmQgYnV0IHN0aWxsIHRyYWNrcyBsaXZlIENvZGV4IHNlc3Npb25zIGl0IGNhbiBvYnNlcnZlLiIKICAgICAgICB9LAogICAgICAgICJjbGF1ZGVCaW5hcnlQYXRoIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJkZWZhdWx0IjogIiIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQ3VzdG9tIHBhdGggdG8gdGhlIGNsYXVkZSBiaW5hcnkuIgogICAgICAgIH0sCiAg", - "ICAgICAgIndvcmtzcGFjZUF1dG9OYW1pbmciOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogZmFsc2UsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmF1dG9tYXRpb24ud29ya3NwYWNlQXV0b05hbWluZyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3B0LWluIEFJIGF1dG8tbmFtaW5nIG9mIHdvcmtzcGFjZXMgYW5kIHRhYnMgZnJvbSBhZ2VudCBjb252ZXJzYXRpb24gY29udGVudC4gV2hlbiBlbmFibGVkLCBjbXV4IHN1bW1hcml6", - "ZXMgc3VwcG9ydGVkIGFnZW50IHNlc3Npb25zIGludG8gc2hvcnQgdGl0bGVzIHVzaW5nIGVhY2ggYWdlbnQncyBvd24gYmluYXJ5OyBtYW51YWwgcmVuYW1lcyBhbHdheXMgd2luLiIKICAgICAgICB9LAogICAgICAgICJhdXRvTmFtaW5nQWdlbnQiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiYXV0byIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmF1dG9tYXRpb24uYXV0b05hbWluZ0FnZW50IiwKICAgICAgICAgICJkZXNjcmlwdGlv", - "biI6ICJXaGljaCBhZ2VudCBnZW5lcmF0ZXMgYXV0by1uYW1lcyBmb3IgZXZlcnkgc2Vzc2lvbi4gXCJhdXRvXCIgKGRlZmF1bHQpIG5hbWVzIGVhY2ggc2Vzc2lvbiB3aXRoIGl0cyBvd24gYWdlbnQ7IGFueSBhZ2VudCBzbHVnIChjbGF1ZGUsIGNvZGV4LCBncm9rLCBvcGVuY29kZSwgcGksIG9tcCwg4oCmKSBvdmVycmlkZXMgbmFtaW5nIGZvciBhbGwgc2Vzc2lvbnMsIGV2ZW4gb3RoZXIgYWdlbnRzJyBzZXNzaW9ucy4gVW5kcml2ZWFibGUgb3IgdW5pbnN0YWxsZWQgYWdlbnRzIGZhbGwgYmFjayB0byB0aGUgc2Vzc2lvbidz", - "IG93biBhZ2VudCwgc28gbmFtaW5nIG5ldmVyIGJyZWFrcy4iCiAgICAgICAgfSwKICAgICAgICAicmlwZ3JlcEJpbmFyeVBhdGgiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDdXN0b20gcGF0aCB0byB0aGUgcmlwZ3JlcCAocmcpIGJpbmFyeSB1c2VkIGJ5IHByb2plY3Qgc2VhcmNoLiIKICAgICAgICB9LAogICAgICAgICJzdXBwcmVzc1N1YmFnZW50Tm90aWZpY2F0aW9ucyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xl", - "YW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5hdXRvbWF0aW9uLnN1cHByZXNzU3ViYWdlbnROb3RpZmljYXRpb25zIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTdXBwcmVzcyB2aXNpYmxlIGNvbXBsZXRpb24gbm90aWZpY2F0aW9ucyBhbmQgc3RhdHVzIG11dGF0aW9ucyBmcm9tIG5lc3RlZCBDb2RleCBvciBDbGF1ZGUgY2hpbGQgYWdlbnRzIHdoaWxlIGtlZXBpbmcgdGhlaXIgZXZlbnRzIGluIEZlZWQgdGVsZW1ldHJ5LiIK", - "ICAgICAgICB9LAogICAgICAgICJhbXBJbnRlZ3JhdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5hdXRvbWF0aW9uLmFtcEludGVncmF0aW9uIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbmFibGUgY211eCBpbnRlZ3JhdGlvbiBob29rcyBmb3IgQW1wLiBXaGVuIGRpc2FibGVkLCB0aGUgYnVuZGxlZCBwbHVnaW4gc3RheXMgaW5hY3RpdmUgd2l0aG91dCBuZWVkaW5n", - "IHRvIGJlIHJlbW92ZWQuIgogICAgICAgIH0sCiAgICAgICAgImN1cnNvckludGVncmF0aW9uIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRW5hYmxlIGNtdXggaW50ZWdyYXRpb24gaG9va3MgZm9yIEN1cnNvci4iCiAgICAgICAgfSwKICAgICAgICAiZ2VtaW5pSW50ZWdyYXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlw", - "dGlvbiI6ICJFbmFibGUgY211eCBpbnRlZ3JhdGlvbiBob29rcyBmb3IgR2VtaW5pLiIKICAgICAgICB9LAogICAgICAgICJraXJvSW50ZWdyYXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbmFibGUgY211eCBpbnRlZ3JhdGlvbiBob29rcyBmb3IgS2lybyBDTEkuIgogICAgICAgIH0sCiAgICAgICAgImtpcm9Ob3RpZmljYXRpb25MZXZlbCI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAg", - "ICAiZW51bSI6IFsKICAgICAgICAgICAgIm1pbmltYWwiLAogICAgICAgICAgICAic3RhbmRhcmQiLAogICAgICAgICAgICAidmVyYm9zZSIKICAgICAgICAgIF0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJzdGFuZGFyZCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQ29udHJvbHMgaG93IG1hbnkgS2lybyB0b29sIGV2ZW50cyBhcHBlYXIgaW4gRmVlZC4iCiAgICAgICAgfSwKICAgICAgICAicG9ydEJhc2UiOiB7CiAgICAgICAgICAidHlwZSI6ICJpbnRlZ2VyIiwKICAgICAgICAgICJtaW5pbXVtIjogMSwKICAgICAgICAg", - "ICJkZWZhdWx0IjogOTEwMCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTdGFydGluZyB2YWx1ZSBmb3Igd29ya3NwYWNlIENNVVhfUE9SVCBhc3NpZ25tZW50cy4iCiAgICAgICAgfSwKICAgICAgICAicG9ydFJhbmdlIjogewogICAgICAgICAgInR5cGUiOiAiaW50ZWdlciIsCiAgICAgICAgICAibWluaW11bSI6IDEsCiAgICAgICAgICAiZGVmYXVsdCI6IDEwLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk51bWJlciBvZiBwb3J0cyByZXNlcnZlZCBwZXIgd29ya3NwYWNlLiIKICAgICAgICB9CiAgICAgIH0KICAgIH0s", - "CiAgICAiYnJvd3NlciI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAiYnJvd3NlciIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJFbWJlZGRlZCBicm93c2VyIHNldHRpbmdzIGZyb20gU2V0dGluZ3MgPiBCcm93c2VyLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAiZGVmYXVsdFNlYXJjaEVuZ2luZSI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAg", - "ICAgICAgICAiZW51bSI6IFsiZ29vZ2xlIiwgImR1Y2tkdWNrZ28iLCAiYmluZyIsICJrYWdpIiwgInN0YXJ0cGFnZSIsICJicmF2ZSIsICJwZXJwbGV4aXR5IiwgImV4YSIsICJ5YWhvbyIsICJlY29zaWEiLCAicXdhbnQiLCAibW9qZWVrIiwgIndpa2lwZWRpYSIsICJnaXRodWIiLCAiYmFpZHUiLCAieWFuZGV4IiwgImN1c3RvbSJdLAogICAgICAgICAgImRlZmF1bHQiOiAiZ29vZ2xlIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuYnJvd3Nlci5kZWZhdWx0U2VhcmNoRW5naW5lIiwK", - "ICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEZWZhdWx0IHNlYXJjaCBlbmdpbmUgZm9yIG5vbi1VUkwgYnJvd3NlciBhZGRyZXNzIGJhciBxdWVyaWVzLiBVc2UgY3VzdG9tIHdpdGggY3VzdG9tU2VhcmNoRW5naW5lVVJMVGVtcGxhdGUgZm9yIGFyYml0cmFyeSBwcm92aWRlcnMuIgogICAgICAgIH0sCiAgICAgICAgImN1c3RvbVNlYXJjaEVuZ2luZU5hbWUiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNj", - "cmlwdGlvbnMuYnJvd3Nlci5jdXN0b21TZWFyY2hFbmdpbmVOYW1lIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEaXNwbGF5IG5hbWUgdXNlZCB3aGVuIGRlZmF1bHRTZWFyY2hFbmdpbmUgaXMgY3VzdG9tLiIKICAgICAgICB9LAogICAgICAgICJjdXN0b21TZWFyY2hFbmdpbmVVUkxUZW1wbGF0ZSI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZGVmYXVsdCI6ICJodHRwczovL3d3dy5nb29nbGUuY29tL3NlYXJjaD9xPXtxdWVyeX0iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNj", - "aGVtYURlc2NyaXB0aW9ucy5icm93c2VyLmN1c3RvbVNlYXJjaEVuZ2luZVVSTFRlbXBsYXRlIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTZWFyY2ggVVJMIHVzZWQgd2hlbiBkZWZhdWx0U2VhcmNoRW5naW5lIGlzIGN1c3RvbS4gSW5jbHVkZSB7cXVlcnl9IG9yICVzIGZvciB0aGUgZW5jb2RlZCBxdWVyeS4gSWYgb21pdHRlZCwgY211eCBhcHBlbmRzIHE9IHRvIHRoZSBVUkwuIgogICAgICAgIH0sCiAgICAgICAgInNob3dTZWFyY2hTdWdnZXN0aW9ucyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAg", - "ICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgb21uaWJhciBzZWFyY2ggc3VnZ2VzdGlvbnMuIgogICAgICAgIH0sCiAgICAgICAgInRoZW1lIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWyJzeXN0ZW0iLCAibGlnaHQiLCAiZGFyayJdLAogICAgICAgICAgImRlZmF1bHQiOiAic3lzdGVtIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbWJlZGRlZCBicm93c2VyIHRoZW1lLiIKICAgICAgICB9LAogICAgICAgICJkZWZhdWx0Wm9v", - "bUxldmVsIjogewogICAgICAgICAgInR5cGUiOiAibnVtYmVyIiwKICAgICAgICAgICJtaW5pbXVtIjogMC4yNSwKICAgICAgICAgICJtYXhpbXVtIjogNSwKICAgICAgICAgICJkZWZhdWx0IjogMSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuYnJvd3Nlci5kZWZhdWx0Wm9vbUxldmVsIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEZWZhdWx0IHBhZ2Ugem9vbSBmYWN0b3IgYXBwbGllZCB0byBuZXdseSBvcGVuZWQgYnJvd3NlciBwYWdlcyAoMSA9IDEwMCUpLiBab29tIEluLCBa", - "b29tIE91dCwgYW5kIEFjdHVhbCBTaXplIHN0aWxsIGFkanVzdCBlYWNoIHBhZ2UuIgogICAgICAgIH0sCiAgICAgICAgImRpc2NhcmRIaWRkZW5XZWJWaWV3cyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkFsbG93IGhpZGRlbiBicm93c2VyIHRhYnMgdG8gcmVsZWFzZSBwYWdlIG1lbW9yeSBhbmQgcmVzdG9yZSB3aGVuIHNob3duIGFnYWluLiIKICAgICAgICB9LAogICAgICAgICJoaWRkZW5XZWJWaWV3RGlzY2FyZERl", - "bGF5U2Vjb25kcyI6IHsKICAgICAgICAgICJ0eXBlIjogIm51bWJlciIsCiAgICAgICAgICAibWluaW11bSI6IDAsCiAgICAgICAgICAibWF4aW11bSI6IDM2MDAsCiAgICAgICAgICAiZGVmYXVsdCI6IDMwMCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTZWNvbmRzIGEgYnJvd3NlciB0YWIgbXVzdCBzdGF5IGhpZGRlbiBiZWZvcmUgY211eCBmcmVlcyBpdHMgcGFnZSBtZW1vcnkuIgogICAgICAgIH0sCiAgICAgICAgImFza1doZXJlVG9TYXZlRG93bmxvYWRzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAg", - "ICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5icm93c2VyLmFza1doZXJlVG9TYXZlRG93bmxvYWRzIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IGEgc2F2ZSBwYW5lbCBmb3IgYnJvd3NlciBkb3dubG9hZHMgaW5zdGVhZCBvZiBzYXZpbmcgZGlyZWN0bHkgdG8gRG93bmxvYWRzLiIKICAgICAgICB9LAogICAgICAgICJvcGVuVGVybWluYWxMaW5rc0luQ211eEJyb3dzZXIiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwK", - "ICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJPcGVuIGNsaWNrZWQgdGVybWluYWwgbGlua3MgaW4gdGhlIGVtYmVkZGVkIGJyb3dzZXIuIgogICAgICAgIH0sCiAgICAgICAgImludGVyY2VwdFRlcm1pbmFsT3BlbkNvbW1hbmRJbkNtdXhCcm93c2VyIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiSW50ZXJjZXB0IHRlcm1pbmFsIG9wZW4gaHR0cChzKSBjb21tYW5kcyBhbmQg", - "cm91dGUgdGhlbSB0aHJvdWdoIHRoZSBlbWJlZGRlZCBicm93c2VyLiIKICAgICAgICB9LAogICAgICAgICJob3N0c1RvT3BlbkluRW1iZWRkZWRCcm93c2VyIjogewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgIml0ZW1zIjogewogICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICB9LAogICAgICAgICAgImRlZmF1bHQiOiBbXSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJBbGxvd2xpc3Qgb2YgaG9zdHMgdGhhdCBzaG91bGQgc3RheSBpbnNpZGUgdGhlIGVtYmVkZGVkIGJyb3dzZXIu", - "IgogICAgICAgIH0sCiAgICAgICAgInVybHNUb0Fsd2F5c09wZW5FeHRlcm5hbGx5IjogewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgIml0ZW1zIjogewogICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICB9LAogICAgICAgICAgImRlZmF1bHQiOiBbXSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJSdWxlcyB0aGF0IGFsd2F5cyBvcGVuIG1hdGNoaW5nIFVSTHMgaW4gdGhlIHN5c3RlbSBicm93c2VyLiIKICAgICAgICB9LAogICAgICAgICJpbnNlY3VyZUh0dHBIb3N0c0FsbG93ZWRJ", - "bkVtYmVkZGVkQnJvd3NlciI6IHsKICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICJpdGVtcyI6IHsKICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgfSwKICAgICAgICAgICJkZWZhdWx0IjogWwogICAgICAgICAgICAibG9jYWxob3N0IiwKICAgICAgICAgICAgIioubG9jYWxob3N0IiwKICAgICAgICAgICAgIjEyNy4wLjAuMSIsCiAgICAgICAgICAgICI6OjEiLAogICAgICAgICAgICAiMC4wLjAuMCIsCiAgICAgICAgICAgICIqLmxvY2FsdGVzdC5tZSIKICAgICAgICAgIF0sCiAgICAg", - "ICAgICAiZGVzY3JpcHRpb24iOiAiSFRUUCBob3N0cyBhbGxvd2VkIGluIHRoZSBlbWJlZGRlZCBicm93c2VyIHdpdGhvdXQgYSB3YXJuaW5nIHByb21wdC4iCiAgICAgICAgfSwKICAgICAgICAidXJsQWxsb3dsaXN0IjogewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgIml0ZW1zIjogewogICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciCiAgICAgICAgICB9LAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5icm93c2VyLnVybEFsbG93bGlzdCIsCiAgICAgICAgICAi", - "ZGVmYXVsdCI6IFsKICAgICAgICAgICAgImxvY2FsaG9zdCIsCiAgICAgICAgICAgICIqLmxvY2FsaG9zdCIsCiAgICAgICAgICAgICIxMjcuMC4wLjEiLAogICAgICAgICAgICAiOjoxIiwKICAgICAgICAgICAgIjAuMC4wLjAiLAogICAgICAgICAgICAiKi5sb2NhbHRlc3QubWUiCiAgICAgICAgICBdLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkhvc3Qgb3IgVVJMIHBhdHRlcm5zIHRoYXQgcmVzdHJpY3QgZW1iZWRkZWQtYnJvd3NlciBuYXZpZ2F0aW9uLiBUaGUgU2V0dGluZ3MgVUkgc3VnZ2VzdHMgbG9jYWwgZGV2ZWxv", - "cG1lbnQgb3JpZ2luczsgc2F2aW5nIGEgbGlzdCBvcHRzIGludG8gdGhlIG9wdGlvbmFsIHJlc3RyaWN0aW9uLiBSZW1vdmUgZW50cmllcyB0byBibG9jayB0aGVtLCBvciBsZWF2ZSB0aGUgdXNlciB2YWx1ZSBlbXB0eSB0byBkaXNhYmxlIGl0IHdoZW4gbm8gbWFuYWdlZCBwb2xpY3kgYXBwbGllcy4iCiAgICAgICAgfSwKICAgICAgICAic2hvd0ltcG9ydEhpbnRPbkJsYW5rVGFicyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9u", - "IjogIlNob3cgdGhlIGJyb3dzZXIgaW1wb3J0IGhpbnQgb24gYmxhbmsgdGFicy4iCiAgICAgICAgfSwKICAgICAgICAicmVhY3RHcmFiVmVyc2lvbiI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZGVmYXVsdCI6ICIwLjEuMjkiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlBpbm5lZCByZWFjdC1ncmFiIHZlcnNpb24gZm9yIHRoZSBicm93c2VyIHRvb2xiYXIgaGVscGVyLiIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAibW9iaWxlIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsi", - "Z2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJtb2JpbGUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTWFjLXNpZGUgc2V0dGluZ3MgZm9yIGNtdXggb24gaU9TLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAiYXJ0aWZhY3RGb2xkZXJBY2Nlc3MiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbInN1YnRyZWUiLCAib25lTGV2ZWwiXSwKICAgICAgICAgICJkZWZhdWx0", - "IjogInN1YnRyZWUiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkNvbnRyb2xzIHdoZXRoZXIgYSByZWZlcmVuY2VkIG9yIHRlcm1pbmFsLXZpc2libGUgZGlyZWN0b3J5IGF1dGhvcml6ZXMgaXRzIGZ1bGwgY2Fub25pY2FsIHN1YnRyZWUgb3Igb25seSBpbW1lZGlhdGUgY2hpbGRyZW4uIgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJtYXJrZG93biI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAibWFya2Rvd24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiQnVpbHQt", - "aW4gbWFya2Rvd24gdmlld2VyIHNldHRpbmdzLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAiZm9udFNpemUiOiB7CiAgICAgICAgICAidHlwZSI6ICJpbnRlZ2VyIiwKICAgICAgICAgICJtaW5pbXVtIjogOCwKICAgICAgICAgICJtYXhpbXVtIjogOTYsCiAgICAgICAgICAiZGVmYXVsdCI6IDE1LAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkRlZmF1bHQgYm9keSBmb250IHNpemUsIGluIHBvaW50cywg", - "Zm9yIG5ld2x5IG9wZW5lZCBtYXJrZG93biB2aWV3ZXJzLiBab29tIGEgdmlld2VyIGxpdmUgd2l0aCBDbWQtKyAvIENtZC0tIC8gQ21kLTAuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubWFya2Rvd24uZm9udFNpemUiCiAgICAgICAgfSwKICAgICAgICAiZm9udEZhbWlseSI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZGVmYXVsdCI6ICIiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkRlZmF1bHQgYm9keSBmb250IGZhbWlseSBmb3IgbmV3bHkg", - "b3BlbmVkIG1hcmtkb3duIHZpZXdlcnMuIExlYXZlIGVtcHR5IGZvciB0aGUgc3lzdGVtIG1hcmtkb3duIGZvbnQgc3RhY2suIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubWFya2Rvd24uZm9udEZhbWlseSIKICAgICAgICB9LAogICAgICAgICJtYXhXaWR0aCI6IHsKICAgICAgICAgICJ0eXBlIjogImludGVnZXIiLAogICAgICAgICAgIm1pbmltdW0iOiAzMjAsCiAgICAgICAgICAibWF4aW11bSI6IDI0MDAsCiAgICAgICAgICAiZGVmYXVsdCI6IDk4MCwKICAgICAgICAgICJkZXNj", - "cmlwdGlvbiI6ICJEZWZhdWx0IG1heGltdW0gcmVhZGluZyBjb2x1bW4gd2lkdGgsIGluIENTUyBwaXhlbHMsIGZvciBuZXdseSBvcGVuZWQgbWFya2Rvd24gdmlld2Vycy4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5tYXJrZG93bi5tYXhXaWR0aCIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAiY2FudmFzIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJjYW52YXMiLAogICAgICAiZGVzY3JpcHRpb24iOiAiRnJlZWZvcm0g", - "Y2FudmFzIHdvcmtzcGFjZSBsYXlvdXQgc2V0dGluZ3MuIiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICJwYW5lR2FwIjogewogICAgICAgICAgInR5cGUiOiAiaW50ZWdlciIsCiAgICAgICAgICAibWluaW11bSI6IDAsCiAgICAgICAgICAibWF4aW11bSI6IDY0LAogICAgICAgICAgImRlZmF1bHQiOiAxNiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTcGFjaW5nIGJldHdlZW4gcGFuZXMgaW4gdGhlIGNh", - "bnZhcyBsYXlvdXQsIGluIHBvaW50cy4gU25hcHBpbmcsIHRpZHksIGFuZCBuZXctcGFuZSBwbGFjZW1lbnQgYWxsIHVzZSB0aGlzIG9uZSBnYXAuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuY2FudmFzLnBhbmVHYXAiCiAgICAgICAgfSwKICAgICAgICAic25hcHBpbmdFbmFibGVkIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU25hcCBwYW5lIGRyYWdzIGFuZCByZXNpemVz", - "IHRvIG5laWdoYm9yIGVkZ2VzIGFuZCB0aGUgcGFuZSBnYXAuIEhvbGQgQ29tbWFuZCB0byBzdXNwZW5kIHNuYXBwaW5nIGZvciBvbmUgZ2VzdHVyZS4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5jYW52YXMuc25hcHBpbmdFbmFibGVkIgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJmaWxlRWRpdG9yIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJmaWxlRWRpdG9yIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkJ1aWx0LWlu", - "IGZpbGUgZWRpdG9yIHNldHRpbmdzLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAid29yZFdyYXAiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogZmFsc2UsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV3JhcCBsb25nIGxpbmVzIGF0IHRoZSBlZGl0b3IncyByaWdodCBlZGdlIGluc3RlYWQgb2Ygc2Nyb2xsaW5nIGhvcml6b250YWxseS4iLAogICAgICAg", - "ICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5maWxlRWRpdG9yLndvcmRXcmFwIgogICAgICAgIH0sCiAgICAgICAgInN5bnRheEhpZ2hsaWdodGluZyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkNvbG9yIHNvdXJjZSB0b2tlbnMgaW4gdGhlIGJ1aWx0LWluIGZpbGUgZWRpdG9yLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmZpbGVFZGl0b3Iuc3lu", - "dGF4SGlnaGxpZ2h0aW5nIgogICAgICAgIH0sCiAgICAgICAgImxpbmVOdW1iZXJzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBhIGxpbmUtbnVtYmVyIGd1dHRlciBpbiB0aGUgYnVpbHQtaW4gZmlsZSBlZGl0b3IuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuZmlsZUVkaXRvci5saW5lTnVtYmVycyIKICAgICAgICB9LAogICAgICAgICJpbmRlbnRHdWlkZXMiOiB7", - "CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEcmF3IHZlcnRpY2FsIGluZGVudCBndWlkZXMgaW4gdGhlIGJ1aWx0LWluIGZpbGUgZWRpdG9yLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmZpbGVFZGl0b3IuaW5kZW50R3VpZGVzIgogICAgICAgIH0sCiAgICAgICAgImN1cnJlbnRMaW5lSGlnaGxpZ2h0IjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAi", - "ZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiSGlnaGxpZ2h0IHRoZSBjYXJldCdzIGxpbmUgd2hlbiB0aGUgc2VsZWN0aW9uIGlzIGVtcHR5LiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmZpbGVFZGl0b3IuY3VycmVudExpbmVIaWdobGlnaHQiCiAgICAgICAgfSwKICAgICAgICAidGFiV2lkdGgiOiB7CiAgICAgICAgICAidHlwZSI6ICJpbnRlZ2VyIiwKICAgICAgICAgICJkZWZhdWx0IjogNCwKICAgICAgICAgICJtaW5pbXVtIjogMSwKICAgICAgICAg", - "ICJtYXhpbXVtIjogOCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDb2x1bW5zIHBlciB0YWIgc3RvcCBmb3IgaW5kZW50IGd1aWRlcy4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5maWxlRWRpdG9yLnRhYldpZHRoIgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJmaWxlRXhwbG9yZXIiOiB7CiAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgInRpdGxlIjogImZpbGVFeHBsb3JlciIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJSaWdodC1zaWRlYmFy", - "IGZpbGUgZXhwbG9yZXIgKGZpbGUgdHJlZSkgc2V0dGluZ3MuIiwKICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5maWxlRXhwbG9yZXIuZGVzY3JpcHRpb24iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImRvdWJsZUNsaWNrQWN0aW9uIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWyJwcmV2aWV3IiwgImRlZmF1bHRFZGl0b3IiLCAi", - "cHJlZmVycmVkRWRpdG9yIl0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJwcmV2aWV3IiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJXaGF0IGRvdWJsZS1jbGlja2luZyAob3IgcHJlc3NpbmcgUmV0dXJuIG9uIGEgc2VhcmNoIHJlc3VsdCBmb3IpIGEgZmlsZSBpbiB0aGUgZmlsZSBleHBsb3JlciBkb2VzLiBgcHJldmlld2Agb3BlbnMgdGhlIGJ1aWx0LWluIGNtdXggZmlsZSBwcmV2aWV3ICh0aGUgZGVmYXVsdCBhbmQgaGlzdG9yaWNhbCBiZWhhdmlvcikuIGBkZWZhdWx0RWRpdG9yYCBvcGVucyB3aXRoIHRoZSBtYWNPUyBk", - "ZWZhdWx0IGFwcCBmb3IgdGhlIGZpbGUgdHlwZS4gYHByZWZlcnJlZEVkaXRvcmAgb3BlbnMgd2l0aCB0aGUgYGFwcC5wcmVmZXJyZWRFZGl0b3JgIGNvbW1hbmQsIGZhbGxpbmcgYmFjayB0byB0aGUgZGVmYXVsdCBhcHAgd2hlbiBub25lIGlzIHNldC4gT25seSBhcHBsaWVzIHRvIGZpbGVzOyBkaXJlY3RvcmllcyBhbHdheXMgZXhwYW5kL2NvbGxhcHNlLCBhbmQgbm9uLWxvY2FsIChyZW1vdGUpIGZpbGUgZXhwbG9yZXJzIGFsd2F5cyBvcGVuIHRoZSBjbXV4IHByZXZpZXcuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6", - "ICJzY2hlbWFEZXNjcmlwdGlvbnMuZmlsZUV4cGxvcmVyLmRvdWJsZUNsaWNrQWN0aW9uIgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJkaWZmVmlld2VyIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJkaWZmVmlld2VyIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkJ1aWx0LWluIGRpZmYgdmlld2VyIHNldHRpbmdzLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsK", - "ICAgICAgICAiZGVmYXVsdExheW91dCI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsidW5pZmllZCIsICJzcGxpdCJdLAogICAgICAgICAgImRlZmF1bHQiOiAidW5pZmllZCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRGVmYXVsdCBsYXlvdXQgZm9yIG5ld2x5IG9wZW5lZCBkaWZmIHZpZXdlcnMuIFRoZSB0b29sYmFyIGxheW91dCB0b2dnbGUgcGVyc2lzdHMgdGhlIHVzZXIncyBsYXN0IGNob2ljZTsgYW4gZXhwbGljaXQgYGNtdXggZGlmZiAtLWxheW91dGAgb3B0aW9uIG92", - "ZXJyaWRlcyBib3RoLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmRpZmZWaWV3ZXIuZGVmYXVsdExheW91dCIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAicmlnaHRTaWRlYmFyIjogewogICAgICAidGl0bGUiOiAicmlnaHRTaWRlYmFyIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkxlZ2FjeSBhbmQgZXh0ZW5zaW9uLW93bmVkIHJpZ2h0LXNpZGViYXIgY29uZmlndXJhdGlvbiBwcmVzZXJ2ZWQgaW4gY211eC5qc29uLiIsCiAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hl", - "bWFEZXNjcmlwdGlvbnMucmlnaHRTaWRlYmFyIiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogdHJ1ZQogICAgfSwKICAgICJzaG9ydGN1dHMiOiB7CiAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgInRpdGxlIjogInNob3J0Y3V0cyIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJLZXlib2FyZCBzaG9ydGN1dCBzZXR0aW5ncyBmcm9tIFNldHRpbmdzID4gS2V5Ym9hcmQgU2hvcnRjdXRzLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRp", - "dGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAic2hvd01vZGlmaWVySG9sZEhpbnRzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBzaG9ydGN1dC1oaW50IGNoaXBzIHdoaWxlIGhvbGRpbmcgQ21kIG9yIENvbnRyb2wuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuc2hvcnRjdXRzLnNob3dNb2RpZmllckhvbGRIaW50", - "cyIKICAgICAgICB9LAogICAgICAgICJiaW5kaW5ncyI6IHsKICAgICAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICAgICAiZGVmYXVsdCI6IHt9LAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3J0Y3V0IG92ZXJyaWRlcyBrZXllZCBieSBjbXV4IGFjdGlvbiBpZC4gVXNlIGEgc3RyaW5nIGZvciBhIHNpbmdsZSBzaG9ydGN1dCwgYW4gYXJyYXkgZm9yIGEgY2hvcmQsIG51bGwsIGFuIGVtcHR5IHN0cmluZywgbm9uZSwgY2xlYXIsIHVuYm91bmQsIG9yIGRpc2FibGVkIHRvIHVuYmluZC4iLAogICAgICAgICAgInBy", - "b3BlcnR5TmFtZXMiOiB7CiAgICAgICAgICAgICJlbnVtIjogWwogICAgICAgICAgICAgICJvcGVuU2V0dGluZ3MiLAogICAgICAgICAgICAgICJvcGVuVGVhbVBpY2tlciIsCiAgICAgICAgICAgICAgInJlbG9hZENvbmZpZ3VyYXRpb24iLAogICAgICAgICAgICAgICJzaG93SGlkZUFsbFdpbmRvd3MiLAogICAgICAgICAgICAgICJnbG9iYWxTZWFyY2giLAogICAgICAgICAgICAgICJuZXdXaW5kb3ciLAogICAgICAgICAgICAgICJjbG9zZVdpbmRvdyIsCiAgICAgICAgICAgICAgInRvZ2dsZUZ1bGxTY3JlZW4iLAogICAgICAg", - "ICAgICAgICJxdWl0IiwKICAgICAgICAgICAgICAidG9nZ2xlU2lkZWJhciIsCiAgICAgICAgICAgICAgIm5ld1RhYiIsCiAgICAgICAgICAgICAgIm5ld0Jyb3dzZXJXb3Jrc3BhY2UiLAogICAgICAgICAgICAgICJuZXdDbG91ZFdvcmtzcGFjZSIsCiAgICAgICAgICAgICAgIm5ld0Nsb3VkTWFjaGluZSIsCiAgICAgICAgICAgICAgInNhdmVMYXlvdXRUZW1wbGF0ZSIsCiAgICAgICAgICAgICAgIm9wZW5Gb2xkZXIiLAogICAgICAgICAgICAgICJyZW9wZW5QcmV2aW91c1Nlc3Npb24iLAogICAgICAgICAgICAgICJnb1RvV29y", - "a3NwYWNlIiwKICAgICAgICAgICAgICAiY29tbWFuZFBhbGV0dGUiLAogICAgICAgICAgICAgICJjb21tYW5kUGFsZXR0ZU5leHQiLAogICAgICAgICAgICAgICJjb21tYW5kUGFsZXR0ZVByZXZpb3VzIiwKICAgICAgICAgICAgICAic2VuZEZlZWRiYWNrIiwKICAgICAgICAgICAgICAic2hvd05vdGlmaWNhdGlvbnMiLAogICAgICAgICAgICAgICJqdW1wVG9VbnJlYWQiLAogICAgICAgICAgICAgICJ0b2dnbGVVbnJlYWQiLAogICAgICAgICAgICAgICJtYXJrT2xkZXN0VW5yZWFkQW5kSnVtcE5leHQiLAogICAgICAgICAgICAg", - "ICJtYXJrQWxsTm90aWZpY2F0aW9uc1JlYWQiLAogICAgICAgICAgICAgICJjbGVhckFsbE5vdGlmaWNhdGlvbnMiLAogICAgICAgICAgICAgICJmb2N1c1JpZ2h0U2lkZWJhciIsCiAgICAgICAgICAgICAgInN3aXRjaFJpZ2h0U2lkZWJhclRvRmlsZXMiLAogICAgICAgICAgICAgICJzd2l0Y2hSaWdodFNpZGViYXJUb0ZpbmQiLAogICAgICAgICAgICAgICJzd2l0Y2hSaWdodFNpZGViYXJUb1Nlc3Npb25zIiwKICAgICAgICAgICAgICAic3dpdGNoUmlnaHRTaWRlYmFyVG9GZWVkIiwKICAgICAgICAgICAgICAic3dpdGNoUmln", - "aHRTaWRlYmFyVG9Eb2NrIiwKICAgICAgICAgICAgICAic3dpdGNoUmlnaHRTaWRlYmFyVG9NYWNoaW5lcyIsCiAgICAgICAgICAgICAgInRyaWdnZXJGbGFzaCIsCiAgICAgICAgICAgICAgIm5leHRTdXJmYWNlIiwKICAgICAgICAgICAgICAicHJldlN1cmZhY2UiLAogICAgICAgICAgICAgICJtb3ZlU3VyZmFjZUxlZnQiLAogICAgICAgICAgICAgICJtb3ZlU3VyZmFjZVJpZ2h0IiwKICAgICAgICAgICAgICAibW92ZVN1cmZhY2VUb1ByZXZpb3VzUGFuZSIsCiAgICAgICAgICAgICAgIm1vdmVTdXJmYWNlVG9OZXh0UGFuZSIs", - "CiAgICAgICAgICAgICAgIm1vdmVTdXJmYWNlVG9QYW5lTGVmdCIsCiAgICAgICAgICAgICAgIm1vdmVTdXJmYWNlVG9QYW5lUmlnaHQiLAogICAgICAgICAgICAgICJtb3ZlU3VyZmFjZVRvUGFuZVVwIiwKICAgICAgICAgICAgICAibW92ZVN1cmZhY2VUb1BhbmVEb3duIiwKICAgICAgICAgICAgICAic2VsZWN0U3VyZmFjZUJ5TnVtYmVyIiwKICAgICAgICAgICAgICAibmV4dFNpZGViYXJUYWIiLAogICAgICAgICAgICAgICJwcmV2U2lkZWJhclRhYiIsCiAgICAgICAgICAgICAgIm5leHRTaWRlYmFyVGFiSW5Hcm91cCIsCiAg", - "ICAgICAgICAgICAgInByZXZTaWRlYmFyVGFiSW5Hcm91cCIsCiAgICAgICAgICAgICAgIm1vdmVXb3Jrc3BhY2VVcCIsCiAgICAgICAgICAgICAgIm1vdmVXb3Jrc3BhY2VEb3duIiwKICAgICAgICAgICAgICAiZm9jdXNIaXN0b3J5QmFjayIsCiAgICAgICAgICAgICAgImZvY3VzSGlzdG9yeUZvcndhcmQiLAogICAgICAgICAgICAgICJmb2N1c0hpc3RvcnlMYXN0IiwKICAgICAgICAgICAgICAic2VsZWN0V29ya3NwYWNlQnlOdW1iZXIiLAogICAgICAgICAgICAgICJyZW5hbWVUYWIiLAogICAgICAgICAgICAgICJyZW5hbWVX", - "b3Jrc3BhY2UiLAogICAgICAgICAgICAgICJlZGl0V29ya3NwYWNlRGVzY3JpcHRpb24iLAogICAgICAgICAgICAgICJtYXJrV29ya3NwYWNlRG9uZSIsCiAgICAgICAgICAgICAgImN5Y2xlV29ya3NwYWNlU3RhdHVzIiwKICAgICAgICAgICAgICAidG9nZ2xlQ2hlY2tsaXN0SXRlbUNvbXBsZXRlIiwKICAgICAgICAgICAgICAiY2xvc2VUYWIiLAogICAgICAgICAgICAgICJjbG9zZU90aGVyVGFic0luUGFuZSIsCiAgICAgICAgICAgICAgImNsb3NlV29ya3NwYWNlIiwKICAgICAgICAgICAgICAibmV3V29ya3NwYWNlR3JvdXAi", - "LAogICAgICAgICAgICAgICJncm91cFNlbGVjdGVkV29ya3NwYWNlcyIsCiAgICAgICAgICAgICAgInRvZ2dsZUZvY3VzZWRXb3Jrc3BhY2VHcm91cENvbGxhcHNlZCIsCiAgICAgICAgICAgICAgInJlb3BlbkNsb3NlZFdvcmtzcGFjZSIsCiAgICAgICAgICAgICAgInJlb3BlbkNsb3NlZEJyb3dzZXJQYW5lbCIsCiAgICAgICAgICAgICAgIm5ld1N1cmZhY2UiLAogICAgICAgICAgICAgICJ0b2dnbGVUZXJtaW5hbENvcHlNb2RlIiwKICAgICAgICAgICAgICAiZm9jdXNUZXh0Qm94SW5wdXQiLAogICAgICAgICAgICAgICJjeWNs", - "ZVRleHRCb3hTdWJtaXRBY3Rpb24iLAogICAgICAgICAgICAgICJhdHRhY2hUZXh0Qm94RmlsZSIsCiAgICAgICAgICAgICAgInNlbmRDdHJsRlRvVGVybWluYWwiLAogICAgICAgICAgICAgICJjbGVhclNjcmVlbktlZXBTY3JvbGxiYWNrIiwKICAgICAgICAgICAgICAic2ltdWxhdG9ySG9tZSIsCiAgICAgICAgICAgICAgInNpbXVsYXRvclJvdGF0ZUxlZnQiLAogICAgICAgICAgICAgICJzaW11bGF0b3JSb3RhdGVSaWdodCIsCiAgICAgICAgICAgICAgInNpbXVsYXRvclRvZ2dsZUFwcGVhcmFuY2UiLAogICAgICAgICAgICAg", - "ICJzaW11bGF0b3JUb2dnbGVTb2Z0d2FyZUtleWJvYXJkIiwKICAgICAgICAgICAgICAiZm9jdXNMZWZ0IiwKICAgICAgICAgICAgICAiZm9jdXNSaWdodCIsCiAgICAgICAgICAgICAgImZvY3VzVXAiLAogICAgICAgICAgICAgICJmb2N1c0Rvd24iLAogICAgICAgICAgICAgICJmb2N1c1ByZXZpb3VzUGFuZSIsCiAgICAgICAgICAgICAgImZvY3VzTmV4dFBhbmUiLAogICAgICAgICAgICAgICJzcGxpdFJpZ2h0IiwKICAgICAgICAgICAgICAic3BsaXREb3duIiwKICAgICAgICAgICAgICAidG9nZ2xlU3BsaXRab29tIiwKICAg", - "ICAgICAgICAgICAiaW5jcmVhc2VXb3Jrc3BhY2VUZXJtaW5hbEZvbnRTaXplIiwKICAgICAgICAgICAgICAiZGVjcmVhc2VXb3Jrc3BhY2VUZXJtaW5hbEZvbnRTaXplIiwKICAgICAgICAgICAgICAicmVzZXRXb3Jrc3BhY2VUZXJtaW5hbEZvbnRTaXplIiwKICAgICAgICAgICAgICAiZXF1YWxpemVTcGxpdHMiLAogICAgICAgICAgICAgICJyZXNpemUtcGFuZS1sZWZ0IiwKICAgICAgICAgICAgICAicmVzaXplLXBhbmUtcmlnaHQiLAogICAgICAgICAgICAgICJyZXNpemUtcGFuZS11cCIsCiAgICAgICAgICAgICAgInJlc2l6", - "ZS1wYW5lLWRvd24iLAogICAgICAgICAgICAgICJzcGxpdEJyb3dzZXJSaWdodCIsCiAgICAgICAgICAgICAgInNwbGl0QnJvd3NlckRvd24iLAogICAgICAgICAgICAgICJ0b2dnbGVDYW52YXNMYXlvdXQiLAogICAgICAgICAgICAgICJjYW52YXNSZXZlYWxGb2N1c2VkUGFuZSIsCiAgICAgICAgICAgICAgImNhbnZhc092ZXJ2aWV3IiwKICAgICAgICAgICAgICAiY2FudmFzWm9vbUluIiwKICAgICAgICAgICAgICAiY2FudmFzWm9vbU91dCIsCiAgICAgICAgICAgICAgImNhbnZhc1pvb21SZXNldCIsCiAgICAgICAgICAgICAg", - "ImNhbnZhc1RpZHkiLAogICAgICAgICAgICAgICJjYW52YXNBbGlnbkxlZnQiLAogICAgICAgICAgICAgICJjYW52YXNBbGlnblJpZ2h0IiwKICAgICAgICAgICAgICAiY2FudmFzQWxpZ25Ub3AiLAogICAgICAgICAgICAgICJjYW52YXNBbGlnbkJvdHRvbSIsCiAgICAgICAgICAgICAgImNhbnZhc0VxdWFsaXplV2lkdGhzIiwKICAgICAgICAgICAgICAiY2FudmFzRXF1YWxpemVIZWlnaHRzIiwKICAgICAgICAgICAgICAiY2FudmFzRGlzdHJpYnV0ZUhvcml6b250YWxseSIsCiAgICAgICAgICAgICAgImNhbnZhc0Rpc3RyaWJ1", - "dGVWZXJ0aWNhbGx5IiwKICAgICAgICAgICAgICAidG9nZ2xlRmlsZUV4cGxvcmVyIiwKICAgICAgICAgICAgICAiZmlsZUV4cGxvcmVyT3BlblNlbGVjdGlvbiIsCiAgICAgICAgICAgICAgImZpbGVFeHBsb3Jlck9wZW5TZWxlY3Rpb25GaW5kZXJBbGlhcyIsCiAgICAgICAgICAgICAgInNhdmVGaWxlUHJldmlldyIsCiAgICAgICAgICAgICAgInRvZ2dsZUZpbGVFZGl0b3JXb3JkV3JhcCIsCiAgICAgICAgICAgICAgIm9wZW5Ccm93c2VyIiwKICAgICAgICAgICAgICAiZm9jdXNCcm93c2VyQWRkcmVzc0JhciIsCiAgICAgICAg", - "ICAgICAgImJyb3dzZXJCYWNrIiwKICAgICAgICAgICAgICAiYnJvd3NlckZvcndhcmQiLAogICAgICAgICAgICAgICJicm93c2VyUmVsb2FkIiwKICAgICAgICAgICAgICAiYnJvd3NlckhhcmRSZWxvYWQiLAogICAgICAgICAgICAgICJicm93c2VyWm9vbUluIiwKICAgICAgICAgICAgICAiYnJvd3Nlclpvb21PdXQiLAogICAgICAgICAgICAgICJicm93c2VyWm9vbVJlc2V0IiwKICAgICAgICAgICAgICAibWFya2Rvd25ab29tSW4iLAogICAgICAgICAgICAgICJtYXJrZG93blpvb21PdXQiLAogICAgICAgICAgICAgICJtYXJr", - "ZG93blpvb21SZXNldCIsCiAgICAgICAgICAgICAgImZpbmQiLAogICAgICAgICAgICAgICJmaW5kSW5EaXJlY3RvcnkiLAogICAgICAgICAgICAgICJmaW5kTmV4dCIsCiAgICAgICAgICAgICAgImZpbmRQcmV2aW91cyIsCiAgICAgICAgICAgICAgImhpZGVGaW5kIiwKICAgICAgICAgICAgICAidXNlU2VsZWN0aW9uRm9yRmluZCIsCiAgICAgICAgICAgICAgInRvZ2dsZUJyb3dzZXJEZXZlbG9wZXJUb29scyIsCiAgICAgICAgICAgICAgInNob3dCcm93c2VySmF2YVNjcmlwdENvbnNvbGUiLAogICAgICAgICAgICAgICJ0b2dn", - "bGVCcm93c2VyRm9jdXNNb2RlIiwKICAgICAgICAgICAgICAidG9nZ2xlQnJvd3NlckRlc2lnbk1vZGUiLAogICAgICAgICAgICAgICJ0b2dnbGVSZWFjdEdyYWIiLAogICAgICAgICAgICAgICJvcGVuRGlmZlZpZXdlciIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxEb3duIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbFVwIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbEhhbGZQYWdlRG93biIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxIYWxmUGFnZVVwIiwKICAgICAgICAg", - "ICAgICAiZGlmZlZpZXdlclNjcm9sbERvd25FbWFjcyIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxVcEVtYWNzIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbFRvQm90dG9tIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbFRvVG9wIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlck9wZW5GaWxlU2VhcmNoIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlck5leHRGaWxlIiwKICAgICAgICAgICAgICAiZGlmZlZpZXdlclByZXZpb3VzRmlsZSIKICAgICAgICAgICAgXQogICAgICAgICAgfSwK", - "ICAgICAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICAgICAiZmlsZUV4cGxvcmVyT3BlblNlbGVjdGlvbiI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3NpbmdsZUJhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZmlsZUV4cGxvcmVyT3BlblNlbGVjdGlvbkZpbmRlckFsaWFzIjogewogICAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvc2luZ2xlQmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiCiAgICAgICAg", - "ICAgIH0sCiAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsRG93biI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbFVwIjogewogICAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsSGFsZlBhZ2VEb3duIjogewog", - "ICAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsSGFsZlBhZ2VVcCI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbERvd25FbWFjcyI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0", - "cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbFVwRW1hY3MiOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxUb0JvdHRvbSI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAog", - "ICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbFRvVG9wIjogewogICAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkaWZmVmlld2VyT3BlbkZpbGVTZWFyY2giOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRpZmZWaWV3ZXJOZXh0RmlsZSI6IHsKICAgICAgICAgICAg", - "ICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclByZXZpb3VzRmlsZSI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogewogICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3Nob3J0Y3V0QmluZGluZ051bGxhYmxl", - "IgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgIndoZW4iOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImRlZmF1bHQiOiB7fSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJPcHRpb25hbCBwZXItYWN0aW9uIGNvbnRleHQgcHJlZGljYXRlcyAoVlMgQ29kZS1zdHlsZSBgd2hlbmAgY2xhdXNlcyksIGtleWVkIGJ5IGNtdXggYWN0aW9uIGlkLiBFYWNoIHZhbHVlIGlzIGEgYm9vbGVhbiBleHByZXNzaW9uIG92ZXIgY29udGV4dCBrZXlzIGNvbWJpbmVkIHdpdGggISwgJiYsIHx8LCBhbmQg", - "cGFyZW50aGVzZXMuIEJvb2xlYW4ga2V5czogc2lkZWJhckZvY3VzLCBicm93c2VyRm9jdXMsIG1hcmtkb3duRm9jdXMsIGZpbGVQcmV2aWV3VGV4dEVkaXRvckZvY3VzLCBzaW11bGF0b3JGb2N1cywgdGVybWluYWxGb2N1cywgY29tbWFuZFBhbGV0dGVWaXNpYmxlLCB0ZXJtaW5hbEZpbmRWaXNpYmxlLCB3b3Jrc3BhY2VDYW52YXNMYXlvdXQuIFR5cGVkIGtleXMgc3VwcG9ydCBjb21wYXJpc29uczogdGhlIHN0cmluZyBzaWRlYmFyTW9kZSAoZmlsZXMsIGZpbmQsIHNlc3Npb25zLCBmZWVkLCBvciBkb2NrKSBhbmQgdGhlIGlu", - "dGVnZXJzIHBhbmVDb3VudCBhbmQgd29ya3NwYWNlQ291bnQuIENvbXBhcmlzb24gb3BlcmF0b3JzIGFyZSA9PSwgIT0sID1+IChyZWdleCksIDwsIDw9LCA+LCA+PSwgYW5kIGBpbiBbYSwgYl1gOyBhbiB1bmtub3duIG9yIGFic2VudCBrZXkgcmVhZHMgYXMgZmFsc2UuIFRoZSBib29sZWFuIGxpdGVyYWxzIHRydWUgYW5kIGZhbHNlIGFyZSBhbHNvIGFjY2VwdGVkOyBga2V5ID09IGZhbHNlYCBpcyB0aGUgc2FtZSBhcyBgIWtleWAuIFRoZSBhY3Rpb24ncyBzaG9ydGN1dCBvbmx5IGZpcmVzIChhbmQgb25seSBjb25mbGljdHMg", - "d2l0aCBvdGhlciBzaG9ydGN1dHMpIHdoZW4gdGhlIGNsYXVzZSBob2xkcy4gRXhhbXBsZXM6IHsgXCJzZWxlY3RXb3Jrc3BhY2VCeU51bWJlclwiOiBcIiFzaWRlYmFyRm9jdXNcIiB9IHNlbGVjdHMgd29ya3NwYWNlcyB3aXRoIEN0cmwrMeKAkzkgZXZlcnl3aGVyZSBleGNlcHQgd2hlbiB0aGUgcmlnaHQgc2lkZWJhciBpcyBmb2N1c2VkOyB7IFwic2VsZWN0U3VyZmFjZUJ5TnVtYmVyXCI6IFwic2lkZWJhck1vZGUgPT0gJ2ZpbmQnICYmIHBhbmVDb3VudCA+IDFcIiB9IHNjb3BlcyBhIGJpbmRpbmcgdG8gdGhlIEZpbmQgc2lk", - "ZWJhciB3aGVuIHRoZSB3b3Jrc3BhY2UgaGFzIG11bHRpcGxlIHBhbmVzLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLnNob3J0Y3V0cy53aGVuIiwKICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IHsKICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfQogIH0sCiAgIiRkZWZzIjogewogICAgImNvbG9ySGV4IjogewogICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAicGF0dGVybiI6ICJeI1swLTlBLUZh", - "LWZdezZ9JCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJVcHBlcmNhc2Ugb3IgbG93ZXJjYXNlIDYtZGlnaXQgaGV4IGNvbG9yLiIKICAgIH0sCiAgICAiY29sb3JIZXhPck51bGwiOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4IgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAibnVsbCIKICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICAibm90aWZpY2F0aW9uU291bmRPdmVycmlkZSI6IHsKICAgICAgIm9uZU9mIjogWwogICAgICAg", - "IHsKICAgICAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICJyZXF1aXJlZCI6IFsic291bmQiXSwKICAgICAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICAgICAic291bmQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICAgICAiZW51bSI6IFsiZGVmYXVsdCIsICJCYXNzbyIsICJCbG93IiwgIkJvdHRsZSIsICJGcm9nIiwgIkZ1bmsiLCAiR2xhc3MiLCAiSGVybyIsICJNb3JzZSIsICJQaW5nIiwgIlBv", - "cCIsICJQdXJyIiwgIlNvc3VtaSIsICJTdWJtYXJpbmUiLCAiVGluayIsICJub25lIl0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IGZhbHNlLAogICAgICAgICAgInJlcXVpcmVkIjogWyJzb3VuZCIsICJjdXN0b21Tb3VuZEZpbGVQYXRoIl0sCiAgICAgICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAgICAgInNvdW5kIjogeyAiY29uc3QiOiAiY3VzdG9tX2ZpbGUiIH0sCiAg", - "ICAgICAgICAgICJjdXN0b21Tb3VuZEZpbGVQYXRoIjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgIm1pbkxlbmd0aCI6IDEsCiAgICAgICAgICAgICAgInBhdHRlcm4iOiAiLipcXFMuKiIKICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgXSwKICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5ub3RpZmljYXRpb25zLnNvdW5kT3ZlcnJpZGUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiQSBub3RpZmljYXRpb24gc291bmQgb3ZlcnJp", - "ZGUuIGBkZWZhdWx0YCB1c2VzIG5vdGlmaWNhdGlvbnMuc291bmQsIGBub25lYCBkaXNhYmxlcyBwbGF5YmFjaywgYW5kIHRoZSByZW1haW5pbmcgdmFsdWVzIHNlbGVjdCBhIGJ1aWx0LWluIG9yIGN1c3RvbSBzb3VuZC4iCiAgICB9LAogICAgInNob3J0Y3V0QmluZGluZyI6IHsKICAgICAgIm9uZU9mIjogWwogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvdW5ib3VuZFNob3J0Y3V0QmluZGluZyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVW5iaW5kIHRoaXMgc2hvcnRjdXQuIEFjY2VwdGVkIHZhbHVl", - "cyBhcmUgYW4gZW1wdHkgc3RyaW5nLCBub25lLCBjbGVhciwgdW5ib3VuZCwgb3IgZGlzYWJsZWQuIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9zaG9ydGN1dEZpcnN0U3Ryb2tlIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaW5nbGUtc3Ryb2tlIHNob3J0Y3V0LCBmb3IgZXhhbXBsZSBjbWQrbiBvciBjbWQrc3BhY2UuIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgIm1pbkl0ZW1zIjogMSwKICAgICAgICAgICJtYXhJ", - "dGVtcyI6IDIsCiAgICAgICAgICAicHJlZml4SXRlbXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3Nob3J0Y3V0Rmlyc3RTdHJva2UiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3Nob3J0Y3V0U3Ryb2tlIgogICAgICAgICAgICB9CiAgICAgICAgICBdLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkNob3JkZWQgc2hvcnRjdXQuIEV4YW1wbGU6IFtcImN0cmwrYlwiLCBcImNcIl0uIgogICAgICAgIH0KICAgICAgXQog", - "ICAgfSwKICAgICJzaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSI6IHsKICAgICAgIm9uZU9mIjogWwogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvc2hvcnRjdXRCaW5kaW5nIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAibnVsbCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVW5iaW5kIHRoaXMgc2hvcnRjdXQuIgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJzaW5nbGVCYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmciOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAg", - "ICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3VuYm91bmRTaG9ydGN1dEJpbmRpbmciLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlVuYmluZCB0aGlzIHNob3J0Y3V0LiBBY2NlcHRlZCB2YWx1ZXMgYXJlIGFuIGVtcHR5IHN0cmluZywgbm9uZSwgY2xlYXIsIHVuYm91bmQsIG9yIGRpc2FibGVkLiIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRGaXJzdFN0cm9rZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2luZ2xlLXN0cm9r", - "ZSBzaG9ydGN1dC4gQmFyZSBmaXJzdCBzdHJva2VzIGFyZSBhY2NlcHRlZCBmb3IgdGhpcyBhY3Rpb24uIgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJzaW5nbGVCYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSI6IHsKICAgICAgIm9uZU9mIjogWwogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvc2luZ2xlQmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAibnVsbCIsCiAgICAgICAgICAiZGVzY3JpcHRp", - "b24iOiAiVW5iaW5kIHRoaXMgc2hvcnRjdXQuIgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJiYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmciOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3VuYm91bmRTaG9ydGN1dEJpbmRpbmciLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlVuYmluZCB0aGlzIHNob3J0Y3V0LiBBY2NlcHRlZCB2YWx1ZXMgYXJlIGFuIGVtcHR5IHN0cmluZywgbm9uZSwgY2xlYXIsIHVuYm91bmQsIG9yIGRpc2FibGVkLiIKICAgICAg", - "ICB9LAogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRGaXJzdFN0cm9rZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2luZ2xlLXN0cm9rZSBzaG9ydGN1dC4gQmFyZSBmaXJzdCBzdHJva2VzIGFyZSBhY2NlcHRlZCBmb3IgdGhpcyBhY3Rpb24uIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgIm1pbkl0ZW1zIjogMSwKICAgICAgICAgICJtYXhJdGVtcyI6IDIsCiAgICAgICAgICAicHJlZml4SXRlbXMi", - "OiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0Rmlyc3RTdHJva2UiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3Nob3J0Y3V0U3Ryb2tlIgogICAgICAgICAgICB9CiAgICAgICAgICBdLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkNob3JkZWQgc2hvcnRjdXQuIEJhcmUgZmlyc3Qgc3Ryb2tlcyBhcmUgYWNjZXB0ZWQgZm9yIHRoaXMgYWN0aW9uLiBFeGFtcGxlOiBbXCJnXCIsIFwi", - "Z1wiXS4iCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImJhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIjogewogICAgICAib25lT2YiOiBbCiAgICAgICAgewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmciCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAidHlwZSI6ICJudWxsIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJVbmJpbmQgdGhpcyBzaG9ydGN1dC4iCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgInVuYm91bmRT", - "aG9ydGN1dEJpbmRpbmciOiB7CiAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICJlbnVtIjogWyIiLCAibm9uZSIsICJjbGVhciIsICJ1bmJvdW5kIiwgImRpc2FibGVkIl0KICAgIH0sCiAgICAic2hvcnRjdXRGaXJzdFN0cm9rZSI6IHsKICAgICAgImFsbE9mIjogWwogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvc2hvcnRjdXRTdHJva2UiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAiYW55T2YiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAicGF0dGVybiI6ICJeKD86KD86W2ND", - "XVttTV1bZERdfFtjQ11bb09dW21NXVttTV1bYUFdW25OXVtkRF18W3NTXVtoSF1baUldW2ZGXVt0VF18W29PXVtwUF1bdFRdfFtvT11bcFBdW3RUXVtpSV1bb09dW25OXXxbYUFdW2xMXVt0VF18W2NDXVt0VF1bclJdW2xMXXxbY0NdW29PXVtuTl1bdFRdW3JSXVtvT11bbExdfFtjQ11bdFRdW2xMXXzijJh84oenfOKMpXzijIMpXFwrKSsiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAicGF0dGVybiI6ICJeKD86IHxbc1NdW3BQXVthQV1bY0NdW2VFXXxbc1NdW3BQXVthQV1bY0NdW2VFXVtiQl1b", - "YUFdW3JSXXw8W3NTXVtwUF1bYUFdW2NDXVtlRV0+KSQiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9CiAgICAgIF0sCiAgICAgICJkZXNjcmlwdGlvbiI6ICJGaXJzdCBvciBvbmx5IHNob3J0Y3V0IHN0cm9rZS4gTXVzdCBpbmNsdWRlIGEgbW9kaWZpZXIgdW5sZXNzIHRoZSBrZXkgaXMgU3BhY2UuIgogICAgfSwKICAgICJiYXJlRmlyc3RTdHJva2VTaG9ydGN1dEZpcnN0U3Ryb2tlIjogewogICAgICAiYWxsT2YiOiBbCiAgICAgICAgewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9zaG9ydGN1dFN0cm9r", - "ZSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJhbnlPZiI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICJwYXR0ZXJuIjogIl4oPzooPzpbY0NdW21NXVtkRF18W2NDXVtvT11bbU1dW21NXVthQV1bbk5dW2REXXxbc1NdW2hIXVtpSV1bZkZdW3RUXXxbb09dW3BQXVt0VF18W29PXVtwUF1bdFRdW2lJXVtvT11bbk5dfFthQV1bbExdW3RUXXxbY0NdW3RUXVtyUl1bbExdfFtjQ11bb09dW25OXVt0VF1bclJdW29PXVtsTF18W2NDXVt0VF1bbExdfOKMmHzih6d84oylfOKMgylcXCspKyIKICAgICAgICAgICAg", - "fSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJwYXR0ZXJuIjogIl4oPzogfFtzU11bcFBdW2FBXVtjQ11bZUVdfFtzU11bcFBdW2FBXVtjQ11bZUVdW2JCXVthQV1bclJdfDxbc1NdW3BQXVthQV1bY0NdW2VFXT4pJCIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJwYXR0ZXJuIjogIl4oPzpbclJdW2VFXVt0VF1bdVVdW3JSXVtuTl18W2VFXVtuTl1bdFRdW2VFXVtyUl184oapKSQiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAicGF0dGVybiI6ICJeKD86", - "W2xMXVtlRV1bZkZdW3RUXXxbYUFdW3JSXVtyUl1bb09dW3dXXVtsTF1bZUVdW2ZGXVt0VF18W2xMXVtlRV1bZkZdW3RUXVthQV1bclJdW3JSXVtvT11bd1ddfOKGkHxbclJdW2lJXVtnR11baEhdW3RUXXxbYUFdW3JSXVtyUl1bb09dW3dXXVtyUl1baUldW2dHXVtoSF1bdFRdfFtyUl1baUldW2dHXVtoSF1bdFRdW2FBXVtyUl1bclJdW29PXVt3V1184oaSfFt1VV1bcFBdfFthQV1bclJdW3JSXVtvT11bd1ddW3VVXVtwUF18W3VVXVtwUF1bYUFdW3JSXVtyUl1bb09dW3dXXXzihpF8W2REXVtvT11bd1ddW25OXXxbYUFdW3JSXVty", - "Ul1bb09dW3dXXVtkRF1bb09dW3dXXVtuTl18W2REXVtvT11bd1ddW25OXVthQV1bclJdW3JSXVtvT11bd1ddfOKGkykkIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInBhdHRlcm4iOiAiXig/OltBLVphLXowLTldfFssLi9cXFxcOydgPVxcW1xcXS1dfFtjQ11bb09dW21NXVttTV1bYUFdfFtwUF1bZUVdW3JSXVtpSV1bb09dW2REXXxbZERdW29PXVt0VF18W3NTXVtsTF1bYUFdW3NTXVtoSF18W2JCXVthQV1bY0NdW2tLXVtzU11bbExdW2FBXVtzU11baEhdfFtzU11bZUVdW21NXVtpSV1bY0Nd", - "W29PXVtsTF1bb09dW25OXXxbcVFdW3VVXVtvT11bdFRdW2VFXXxbYUFdW3BQXVtvT11bc1NdW3RUXVtyUl1bb09dW3BQXVtoSF1bZUVdfFtiQl1bYUFdW2NDXVtrS11bdFRdW2lJXVtjQ11ba0tdfFtnR11bclJdW2FBXVt2Vl1bZUVdfFttTV1baUldW25OXVt1VV1bc1NdfFtoSF1beVldW3BQXVtoSF1bZUVdW25OXXxbcFBdW2xMXVt1VV1bc1NdfFtlRV1bcVFdW3VVXVthQV1bbExdW3NTXXxbbExdW2VFXVtmRl1bdFRdW2JCXVtyUl1bYUFdW2NDXVtrS11bZUVdW3RUXXxbb09dW3BQXVtlRV1bbk5dW2JCXVtyUl1bYUFdW2NDXVtr", - "S11bZUVdW3RUXXxbclJdW2lJXVtnR11baEhdW3RUXVtiQl1bclJdW2FBXVtjQ11ba0tdW2VFXVt0VF18W2NDXVtsTF1bb09dW3NTXVtlRV1bYkJdW3JSXVthQV1bY0NdW2tLXVtlRV1bdFRdKSQiCiAgICAgICAgICAgIH0KICAgICAgICAgIF0KICAgICAgICB9CiAgICAgIF0sCiAgICAgICJkZXNjcmlwdGlvbiI6ICJGaXJzdCBvciBvbmx5IHNob3J0Y3V0IHN0cm9rZS4gQmFyZSBwcmludGFibGUga2V5cyBhcmUgYWNjZXB0ZWQgZm9yIGFjdGlvbnMgdGhhdCBvcHQgaW50byBiYXJlIGZpcnN0IHN0cm9rZXMuIgogICAgfSwKICAg", - "ICJzaG9ydGN1dFN0cm9rZSI6IHsKICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgInBhdHRlcm4iOiAiXig/Oig/OltjQ11bbU1dW2REXXxbY0NdW29PXVttTV1bbU1dW2FBXVtuTl1bZERdfFtzU11baEhdW2lJXVtmRl1bdFRdfFtvT11bcFBdW3RUXXxbb09dW3BQXVt0VF1baUldW29PXVtuTl18W2FBXVtsTF1bdFRdfFtjQ11bdFRdW3JSXVtsTF18W2NDXVtvT11bbk5dW3RUXVtyUl1bb09dW2xMXXxbY0NdW3RUXVtsTF184oyYfOKHp3zijKV84oyDKVxcKykqKD86IHxbQS1aYS16MC05XXxbbExdW2VFXVtmRl1bdFRdfFth", - "QV1bclJdW3JSXVtvT11bd1ddW2xMXVtlRV1bZkZdW3RUXXxbbExdW2VFXVtmRl1bdFRdW2FBXVtyUl1bclJdW29PXVt3V1184oaQfFtyUl1baUldW2dHXVtoSF1bdFRdfFthQV1bclJdW3JSXVtvT11bd1ddW3JSXVtpSV1bZ0ddW2hIXVt0VF18W3JSXVtpSV1bZ0ddW2hIXVt0VF1bYUFdW3JSXVtyUl1bb09dW3dXXXzihpJ8W3VVXVtwUF18W2FBXVtyUl1bclJdW29PXVt3V11bdVVdW3BQXXxbdVVdW3BQXVthQV1bclJdW3JSXVtvT11bd1ddfOKGkXxbZERdW29PXVt3V11bbk5dfFthQV1bclJdW3JSXVtvT11bd1ddW2REXVtvT11b", - "d1ddW25OXXxbZERdW29PXVt3V11bbk5dW2FBXVtyUl1bclJdW29PXVt3V1184oaTfFt0VF1bYUFdW2JCXXxbclJdW2VFXVt0VF1bdVVdW3JSXVtuTl18W2VFXVtuTl1bdFRdW2VFXVtyUl184oapfFtzU11bcFBdW2FBXVtjQ11bZUVdfFtzU11bcFBdW2FBXVtjQ11bZUVdW2JCXVthQV1bclJdfDxbc1NdW3BQXVthQV1bY0NdW2VFXT58WywuL1xcXFw7J2A9XFxbXFxdLV18W2NDXVtvT11bbU1dW21NXVthQV18W3BQXVtlRV1bclJdW2lJXVtvT11bZERdfFtkRF1bb09dW3RUXXxbc1NdW2xMXVthQV1bc1NdW2hIXXxbYkJdW2FBXVtj", - "Q11ba0tdW3NTXVtsTF1bYUFdW3NTXVtoSF18W3NTXVtlRV1bbU1dW2lJXVtjQ11bb09dW2xMXVtvT11bbk5dfFtxUV1bdVVdW29PXVt0VF1bZUVdfFthQV1bcFBdW29PXVtzU11bdFRdW3JSXVtvT11bcFBdW2hIXVtlRV18W2JCXVthQV1bY0NdW2tLXVt0VF1baUldW2NDXVtrS118W2dHXVtyUl1bYUFdW3ZWXVtlRV18W21NXVtpSV1bbk5dW3VVXVtzU118W2hIXVt5WV1bcFBdW2hIXVtlRV1bbk5dfFtwUF1bbExdW3VVXVtzU118W2VFXVtxUV1bdVVdW2FBXVtsTF1bc1NdfFtsTF1bZUVdW2ZGXVt0VF1bYkJdW3JSXVthQV1bY0Nd", - "W2tLXVtlRV1bdFRdfFtvT11bcFBdW2VFXVtuTl1bYkJdW3JSXVthQV1bY0NdW2tLXVtlRV1bdFRdfFtyUl1baUldW2dHXVtoSF1bdFRdW2JCXVtyUl1bYUFdW2NDXVtrS11bZUVdW3RUXXxbY0NdW2xMXVtvT11bc1NdW2VFXVtiQl1bclJdW2FBXVtjQ11ba0tdW2VFXVt0VF18W2ZGXSg/OlsxLTldfDFbMC05XXwyMCl8W3ZWXVtvT11bbExdW3VVXVttTV1bZUVdW3VVXVtwUF18W21NXVtlRV1bZERdW2lJXVthQV1bdlZdW29PXVtsTF1bdVVdW21NXVtlRV1bdVVdW3BQXXxbbU1dW2VFXVtkRF1baUldW2FBXVxcLlt2Vl1bb09dW2xM", - "XVt1VV1bbU1dW2VFXVt1VV1bcFBdfFt2Vl1bb09dW2xMXVt1VV1bbU1dW2VFXVtkRF1bb09dW3dXXVtuTl18W21NXVtlRV1bZERdW2lJXVthQV1bdlZdW29PXVtsTF1bdVVdW21NXVtlRV1bZERdW29PXVt3V11bbk5dfFttTV1bZUVdW2REXVtpSV1bYUFdXFwuW3ZWXVtvT11bbExdW3VVXVttTV1bZUVdW2REXVtvT11bd1ddW25OXXxbYkJdW3JSXVtpSV1bZ0ddW2hIXVt0VF1bbk5dW2VFXVtzU11bc1NdW3VVXVtwUF18W21NXVtlRV1bZERdW2lJXVthQV1bYkJdW3JSXVtpSV1bZ0ddW2hIXVt0VF1bbk5dW2VFXVtzU11bc1NdW3VV", - "XVtwUF18W21NXVtlRV1bZERdW2lJXVthQV1cXC5bYkJdW3JSXVtpSV1bZ0ddW2hIXVt0VF1bbk5dW2VFXVtzU11bc1NdW3VVXVtwUF18W2JCXVtyUl1baUldW2dHXVtoSF1bdFRdW25OXVtlRV1bc1NdW3NTXVtkRF1bb09dW3dXXVtuTl18W21NXVtlRV1bZERdW2lJXVthQV1bYkJdW3JSXVtpSV1bZ0ddW2hIXVt0VF1bbk5dW2VFXVtzU11bc1NdW2REXVtvT11bd1ddW25OXXxbbU1dW2VFXVtkRF1baUldW2FBXVxcLltiQl1bclJdW2lJXVtnR11baEhdW3RUXVtuTl1bZUVdW3NTXVtzU11bZERdW29PXVt3V11bbk5dfFttTV1bdVVd", - "W3RUXVtlRV18W21NXVtlRV1bZERdW2lJXVthQV1bbU1dW3VVXVt0VF1bZUVdfFttTV1bZUVdW2REXVtpSV1bYUFdXFwuW21NXVt1VV1bdFRdW2VFXXxbcFBdW2xMXVthQV1beVldW3BQXVthQV1bdVVdW3NTXVtlRV18W21NXVtlRV1bZERdW2lJXVthQV1bcFBdW2xMXVthQV1beVldW3BQXVthQV1bdVVdW3NTXVtlRV18W21NXVtlRV1bZERdW2lJXVthQV1cXC5bcFBdW2xMXVthQV1beVldW3BQXVthQV1bdVVdW3NTXVtlRV18W25OXVtlRV1beFhdW3RUXVt0VF1bclJdW2FBXVtjQ11ba0tdfFttTV1bZUVdW2REXVtpSV1bYUFdW25O", - "XVtlRV1beFhdW3RUXXxbbU1dW2VFXVtkRF1baUldW2FBXVxcLltuTl1bZUVdW3hYXVt0VF18W21NXVtlRV1bZERdW2lJXVthQV1cXC5bbk5dW2VFXVt4WF1bdFRdW3RUXVtyUl1bYUFdW2NDXVtrS118W3BQXVtyUl1bZUVdW3ZWXVtpSV1bb09dW3VVXVtzU11bdFRdW3JSXVthQV1bY0NdW2tLXXxbbU1dW2VFXVtkRF1baUldW2FBXVtwUF1bclJdW2VFXVt2Vl1baUldW29PXVt1VV1bc1NdfFttTV1bZUVdW2REXVtpSV1bYUFdXFwuW3BQXVtyUl1bZUVdW3ZWXVtpSV1bb09dW3VVXVtzU118W21NXVtlRV1bZERdW2lJXVthQV1cXC5b", - "cFBdW3JSXVtlRV1bdlZdW2lJXVtvT11bdVVdW3NTXVt0VF1bclJdW2FBXVtjQ11ba0tdKSQiLAogICAgICAiZGVzY3JpcHRpb24iOiAiT25lIGtleWJvYXJkIHNob3J0Y3V0IHN0cm9rZSB1c2luZyBtb2RpZmllcitrZXkgc3ludGF4LiBTdXBwb3J0ZWQga2V5IG5hbWVzIGluY2x1ZGUgc3BhY2UsIFNwYWNlLCA8c3BhY2U+LCBhbmQgPFNwYWNlPi4iCiAgICB9CiAgfQp9Cg==", + "c2giOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJGbGFzaCB0aGUgZm9jdXNlZCBwYW5lIHdoZW4gcmVxdWVzdGVkLiIKICAgICAgICB9LAogICAgICAgICJwYW5lRmxhc2hEb3VibGVCbGluayI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQi", + "OiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJCbGluayB0aGUgcGFuZSBmbGFzaCB0d2ljZSBpbnN0ZWFkIG9mIG9uZSBzaG9ydCBwdWxzZS4iCiAgICAgICAgfSwKICAgICAgICAicGFuZUZsYXNoQ29sb3IiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4T3JOdWxsIiwKICAgICAgICAgICJkZWZhdWx0IjogbnVsbCwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubm90aWZpY2F0aW9u", + "cy5wYW5lRmxhc2hDb2xvciIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3ZlcnJpZGUgdGhlIHBhbmUgZmxhc2ggYW5kIHVucmVhZCByaW5nIGNvbG9yLiBOdWxsIGtlZXBzIHRoZSBidWlsdC1pbiBibHVlLiIKICAgICAgICB9LAogICAgICAgICJzdXBwcmVzc09ubHlGb2N1c2VkU3VyZmFjZSI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJz", + "Y2hlbWFEZXNjcmlwdGlvbnMubm90aWZpY2F0aW9ucy5zdXBwcmVzc09ubHlGb2N1c2VkU3VyZmFjZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV2hlbiBlbmFibGVkLCBhIG5vdGlmaWNhdGlvbiBiYW5uZXIgaXMgYXV0by13aXRoZHJhd24gb25seSB3aGVuIGl0cyBzdXJmYWNlIGlzIHRoZSBleGFjdCBmb2N1c2VkIHN1cmZhY2UuIEEgYmFubmVyIGRlbGl2ZXJlZCBmb3IgYSBub24tZm9jdXNlZCBzdXJmYWNlIGluIHRoZSBjdXJyZW50bHkgdmlzaWJsZSB3b3Jrc3BhY2Ugc3RheXMgdXAgdW50aWwgeW91IGZvY3VzIHRo", + "YXQgc3VyZmFjZSAob3IgY2xpY2svZGlzbWlzcyBpdCksIGluc3RlYWQgb2YgYmVpbmcgcmV0cmFjdGVkIHdoZW4gdGhlIHdvcmtzcGFjZSBiZWNvbWVzIHZpc2libGUuIE9mZiBwcmVzZXJ2ZXMgdGhlIGxlZ2FjeSB3b3Jrc3BhY2UtdmlzaWJpbGl0eSB3aXRoZHJhdy4iCiAgICAgICAgfSwKICAgICAgICAiYWdlbnRQZXJtaXNzaW9uUHJvbXB0IjogewogICAgICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAg", + "ICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLm5vdGlmaWNhdGlvbnMuYWdlbnRQZXJtaXNzaW9uUHJvbXB0IiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJOb3RpZnkgd2hlbiBhbiBhZ2VudCAoZS5nLiBDbGF1ZGUgQ29kZSkgaXMgYmxvY2tlZCB3YWl0aW5nIGZvciB5b3VyIHBlcm1pc3Npb24gdG8gcnVuIGEgdG9vbC4gT24gYnkgZGVmYXVsdCwgc2luY2UgdGhpcyBpcyB0aGUgYWxlcnQgeW91IG11c3QgYWN0IG9uIHRvIHVuYmxvY2sgdGhlIGFnZW50LiIKICAgICAgICB9LAogICAgICAgICJh", + "Z2VudFR1cm5Db21wbGV0ZSI6IHsKICAgICAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsid2hlbklkbGUiLCAiYWx3YXlzIiwgIm5ldmVyIl0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJ3aGVuSWRsZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLm5vdGlmaWNhdGlvbnMuYWdlbnRUdXJuQ29tcGxldGUiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIldoZW4gdG8gbm90aWZ5IHRoYXQg", + "YW4gYWdlbnQgZmluaXNoZWQgYSB0dXJuLiB3aGVuSWRsZSAoZGVmYXVsdCkgc3VwcHJlc3NlcyB0aGUgbm90aWZpY2F0aW9uIHdoaWxlIHRoZSBhZ2VudCBzdGlsbCBoYXMgYSBydW5uaW5nIGJhY2tncm91bmQgdGFzayBvciBhIHBlbmRpbmcgc2NoZWR1bGVkIHdha2V1cCwgc28geW91IGFyZSBwaW5nZWQgb25jZSB3b3JrIHRydWx5IGRyYWlucy4gYWx3YXlzIG5vdGlmaWVzIG9uIGV2ZXJ5IHR1cm4gZW5kOyBuZXZlciBkaXNhYmxlcyBpdC4iCiAgICAgICAgfSwKICAgICAgICAiYWdlbnRJZGxlUmVtaW5kZXIiOiB7CiAgICAg", + "ICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubm90aWZpY2F0aW9ucy5hZ2VudElkbGVSZW1pbmRlciIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiTm90aWZ5IHdoZW4gYW4gYWdlbnQgaGFzIGJlZW4gaWRsZSB3YWl0aW5nIGZvciB5b3VyIGlucHV0IChhYm91dCA2MHMgYWZ0ZXIgYSB0dXJuIGVuZHMpLiBTdXBwcmVzc2VkIHdo", + "aWxlIGJhY2tncm91bmQgd29yayBmcm9tIHRoZSBsYXN0IHR1cm4gaXMgc3RpbGwgcGVuZGluZywgc28gYSBydW5uaW5nIGJ1aWxkIG9yIHdhdGNoZXIgZG9lcyBub3QgdHJpZ2dlciBhIGZhbHNlIHdhaXRpbmcgYWxlcnQuIgogICAgICAgIH0sCiAgICAgICAgInNvdW5kIjogewogICAgICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWwogICAgICAgICAgICAiZGVmYXVsdCIsCiAgICAgICAgICAgICJCYXNzbyIsCiAgICAgICAgICAg", + "ICJCbG93IiwKICAgICAgICAgICAgIkJvdHRsZSIsCiAgICAgICAgICAgICJGcm9nIiwKICAgICAgICAgICAgIkZ1bmsiLAogICAgICAgICAgICAiR2xhc3MiLAogICAgICAgICAgICAiSGVybyIsCiAgICAgICAgICAgICJNb3JzZSIsCiAgICAgICAgICAgICJQaW5nIiwKICAgICAgICAgICAgIlBvcCIsCiAgICAgICAgICAgICJQdXJyIiwKICAgICAgICAgICAgIlNvc3VtaSIsCiAgICAgICAgICAgICJTdWJtYXJpbmUiLAogICAgICAgICAgICAiVGluayIsCiAgICAgICAgICAgICJjdXN0b21fZmlsZSIsCiAgICAgICAgICAgICJu", + "b25lIgogICAgICAgICAgXSwKICAgICAgICAgICJkZWZhdWx0IjogImRlZmF1bHQiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk5vdGlmaWNhdGlvbiBzb3VuZCBwcmVzZXQuIgogICAgICAgIH0sCiAgICAgICAgImN1c3RvbVNvdW5kRmlsZVBhdGgiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJMb2NhbCBwYXRoIHRvIHRoZSBjdXN0b20gbm90aWZpY2F0", + "aW9uIHNvdW5kIGZpbGUuIgogICAgICAgIH0sCiAgICAgICAgInNvdW5kT3ZlcnJpZGVzIjogewogICAgICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgICAgICJkZWZhdWx0Ijoge30sCiAgICAgICAgICAibWF4UHJvcGVydGllcyI6IDI1NiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubm90aWZpY2F0aW9ucy5zb3VuZE92ZXJyaWRlcyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU3BhcnNlIHBlci1hZ2Vu", + "dCBhbmQgcGVyLWFsZXJ0LXR5cGUgbm90aWZpY2F0aW9uIHNvdW5kIG92ZXJyaWRlcy4gTWlzc2luZyBjZWxscyB1c2Ugbm90aWZpY2F0aW9ucy5zb3VuZC4iLAogICAgICAgICAgInByb3BlcnR5TmFtZXMiOiB7CiAgICAgICAgICAgICJhbGxPZiI6IFsKICAgICAgICAgICAgICB7ICJwYXR0ZXJuIjogIl5bQS1aYS16MC05Ll8tXSskIiB9LAogICAgICAgICAgICAgIHsgIm1heExlbmd0aCI6IDY0IH0sCiAgICAgICAgICAgICAgeyAibm90IjogeyAiZW51bSI6IFsiLiIsICIuLiJdIH0gfQogICAgICAgICAgICBdCiAgICAgICAg", + "ICB9LAogICAgICAgICAgInBhdHRlcm5Qcm9wZXJ0aWVzIjogewogICAgICAgICAgICAiXltBLVphLXowLTkuXy1dKyQiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgICAgICAgICAibWF4UHJvcGVydGllcyI6IDMsCiAgICAgICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICAgICAidHVybkRvbmUiOiB7ICIkcmVmIjogIiMvJGRlZnMvbm90aWZpY2F0aW9uU291bmRPdmVycmlkZSIgfSwKICAgICAgICAg", + "ICAgICAgICJuZWVkc0lucHV0IjogeyAiJHJlZiI6ICIjLyRkZWZzL25vdGlmaWNhdGlvblNvdW5kT3ZlcnJpZGUiIH0sCiAgICAgICAgICAgICAgICAiZXJyb3JTdGFsbGVkIjogeyAiJHJlZiI6ICIjLyRkZWZzL25vdGlmaWNhdGlvblNvdW5kT3ZlcnJpZGUiIH0KICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZQogICAgICAgIH0sCiAgICAgICAgImNvbW1hbmQiOiB7CiAgICAgICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFs", + "Il0sCiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJPcHRpb25hbCBzaGVsbCBjb21tYW5kIHRvIHJ1biBhbG9uZ3NpZGUgbm90aWZpY2F0aW9uIGRlbGl2ZXJ5LiIKICAgICAgICB9LAogICAgICAgICJob29rc01vZGUiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbImFwcGVuZCIsICJyZXBsYWNlIl0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJhcHBlbmQiLAogICAgICAgICAgImRlc2NyaXB0", + "aW9uIjogIkNvbnRyb2xzIHdoZXRoZXIgcHJvamVjdC1sb2NhbCBub3RpZmljYXRpb24gaG9va3MgYXBwZW5kIHRvIGluaGVyaXRlZCBob29rcyBvciByZXBsYWNlIHRoZW0uIgogICAgICAgIH0sCiAgICAgICAgImhvb2tzIjogewogICAgICAgICAgInR5cGUiOiAiYXJyYXkiLAogICAgICAgICAgImRlZmF1bHQiOiBbXSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDb21wb3NhYmxlIHNoZWxsIGhvb2tzIHRoYXQgcmVjZWl2ZSBub3RpZmljYXRpb24gcG9saWN5IEpTT04gb24gc3RkaW4gYW5kIHJldHVybiB1cGRhdGVkIHBv", + "bGljeSBKU09OIG9uIHN0ZG91dC4iLAogICAgICAgICAgIml0ZW1zIjogewogICAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICAgInJlcXVpcmVkIjogWyJpZCIsICJjb21tYW5kIl0sCiAgICAgICAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICAgICAgICJpZCI6IHsKICAgICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgICAibWluTGVuZ3RoIjogMSwKICAgICAgICAgICAgICAgICJkZXNjcmlw", + "dGlvbiI6ICJTdGFibGUgaG9vayBpZGVudGlmaWVyIHVzZWQgaW4gbG9ncyBhbmQgZmFpbHVyZSBhbGVydHMuIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgImNvbW1hbmQiOiB7CiAgICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgICAgICAgIm1pbkxlbmd0aCI6IDEsCiAgICAgICAgICAgICAgICAicGF0dGVybiI6ICIuKlxcUy4qIiwKICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaGVsbCBjb21tYW5kIHJ1biB3aXRoIG5vdGlmaWNhdGlvbiBwb2xpY3kgSlNPTiBvbiBzdGRp", + "bi4iCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAidGltZW91dFNlY29uZHMiOiB7CiAgICAgICAgICAgICAgICAidHlwZSI6ICJudW1iZXIiLAogICAgICAgICAgICAgICAgImV4Y2x1c2l2ZU1pbmltdW0iOiAwLAogICAgICAgICAgICAgICAgImRlZmF1bHQiOiAyMCwKICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJNYXhpbXVtIHRpbWUgY211eCB3YWl0cyBmb3IgdGhpcyBob29rIGJlZm9yZSBmYWxsaW5nIGJhY2sgdG8gZGVmYXVsdCBub3RpZmljYXRpb24gYmVoYXZpb3IuIgogICAgICAgICAgICAgIH0s", + "CiAgICAgICAgICAgICAgImVuYWJsZWQiOiB7CiAgICAgICAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEaXNhYmxlIHRoaXMgaG9vayB3aXRob3V0IHJlbW92aW5nIGl0LiIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAic2lkZWJhciI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUi", + "OiAic2lkZWJhciIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJTaWRlYmFyIGNvbnRlbnQgYW5kIG1ldGFkYXRhIHZpc2liaWxpdHkgZnJvbSBTZXR0aW5ncyA+IFNpZGViYXIuIiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICJoaWRlQWxsRGV0YWlscyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJIaWRl", + "IGFsbCBwZXItd29ya3NwYWNlIGRldGFpbCByb3dzLiIKICAgICAgICB9LAogICAgICAgICJ3cmFwV29ya3NwYWNlVGl0bGVzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkFsbG93IHdvcmtzcGFjZSB0aXRsZXMgaW4gdGhlIHNpZGViYXIgdG8gd3JhcCB0byBtdWx0aXBsZSBsaW5lcyBpbnN0ZWFkIG9mIHRydW5jYXRpbmcgYWZ0ZXIgb25lIGxpbmUuIgogICAgICAgIH0sCiAgICAgICAgInNob3dXb3Jrc3BhY2VEZXNj", + "cmlwdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgY3VzdG9tIHdvcmtzcGFjZSBkZXNjcmlwdGlvbnMgaW4gdGhlIHNpZGViYXIuIgogICAgICAgIH0sCiAgICAgICAgIndvcmtzcGFjZURlc2NyaXB0aW9uQ29sb3IiOiB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4T3JOdWxsIiwKICAgICAgICAgICJkZWZhdWx0IjogbnVsbCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJPdmVycmlk", + "ZSB0aGUgd29ya3NwYWNlIGRlc2NyaXB0aW9uIHRleHQgY29sb3IgaW4gdGhlIHNpZGViYXIuIgogICAgICAgIH0sCiAgICAgICAgImJldGEiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRXhwZXJpbWVudGFsIHNpZGViYXIgZmVhdHVyZXMuIiwKICAgICAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICAgICAid29ya3NwYWNlVG9kb3MiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAib2JqZWN0", + "IiwKICAgICAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV29ya3NwYWNlIHRvZG9zIGNoZWNrbGlzdCBwcmVzZW50YXRpb24uIiwKICAgICAgICAgICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAgICAgICAgICJjb250cm9scyI6IHsKICAgICAgICAgICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgICAgICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJX", + "b3Jrc3BhY2UgdG9kbyBlZGl0aW5nIGNvbnRyb2xzLiIsCiAgICAgICAgICAgICAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICAgICAgICAgICAgICJlbmFibGVkIjogewogICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAgICAgICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgQWRkIENoZWNrbGlzdCBJdGVtIGFuZCB3b3Jrc3BhY2Ugc3RhdHVzIGNvbnRyb2xzIG9uIHdvcmtzcGFjZSByb3dzLiBXaGVuIGZh", + "bHNlLCB0aGUgdG9kbyBzdW1tYXJ5IHN0YXlzIHJlYWQtb25seS4iCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImNoZWNrbGlzdFN0eWxlIjogewogICAgICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgICAgICAgICAiZW51bSI6IFsicG9wb3ZlciIsICJpbmxpbmUiXSwKICAgICAgICAgICAgICAgICAgImRlZmF1bHQiOiAicG9wb3ZlciIsCiAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJIb3cg", + "YSByb3cncyBjaGVja2xpc3Qgb3BlbnMgZnJvbSBpdHMgc3VtbWFyeSBsaW5lOiBhbiBhbmNob3JlZCBwb3BvdmVyIG9yIGlubGluZSBleHBhbnNpb24uIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgImJyYW5jaExheW91dCI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsidmVydGljYWwiLCAiaW5saW5lIl0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJ2ZXJ0aWNhbCIsCiAgICAgICAgICAi", + "ZGVzY3JpcHRpb24iOiAiU2hvdyBnaXQgYnJhbmNoIGRldGFpbHMgc3RhY2tlZCB2ZXJ0aWNhbGx5IG9yIGlubGluZS4iCiAgICAgICAgfSwKICAgICAgICAic3RhY2tCcmFuY2hEaXJlY3RvcnkiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogZmFsc2UsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiUmVuZGVyIHRoZSBnaXQgYnJhbmNoIGFuZCB3b3JraW5nIGRpcmVjdG9yeSBvbiBzZXBhcmF0ZSBsaW5lcyBpbnN0ZWFkIG9mIHNoYXJpbmcgb25lIGxpbmUuIgogICAgICAgIH0s", + "CiAgICAgICAgInBhdGhMYXN0U2VnbWVudE9ubHkiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogZmFsc2UsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVHJ1bmNhdGUgc2lkZWJhciBwYXRocyBmcm9tIHRoZSBzdGFydCwgc2hvd2luZyBhcyBtdWNoIG9mIHRoZSB0cmFpbGluZyBwYXRoIGFzIGZpdHMgd2l0aCBhIGxlYWRpbmcg4oCmLy4gV2hlbiBmYWxzZSwgZnVsbCBwYXRocyBhcmUgYWJicmV2aWF0ZWQgd2l0aCB+Ly4iCiAgICAgICAgfSwKICAgICAgICAic2hvd05vdGlm", + "aWNhdGlvbk1lc3NhZ2UiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IHRoZSBsYXRlc3Qgbm90aWZpY2F0aW9uIHRleHQgaW4gdGhlIHNpZGViYXIuIgogICAgICAgIH0sCiAgICAgICAgIm5vdGlmaWNhdGlvbk1lc3NhZ2VMaW5lTGltaXQiOiB7CiAgICAgICAgICAidHlwZSI6ICJpbnRlZ2VyIiwKICAgICAgICAgICJtaW5pbXVtIjogMSwKICAgICAgICAgICJtYXhpbXVtIjogNTAsCiAgICAgICAgICAiZGVmYXVs", + "dCI6IDEyLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk1heGltdW0gbGluZXMgc2hvd24gZm9yIHRoZSBsYXRlc3Qgbm90aWZpY2F0aW9uIGJlbG93IGVhY2ggd29ya3NwYWNlIHRpdGxlLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLnNpZGViYXIubm90aWZpY2F0aW9uTWVzc2FnZUxpbmVMaW1pdCIKICAgICAgICB9LAogICAgICAgICJzaG93QnJhbmNoRGlyZWN0b3J5IjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAg", + "ICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyB0aGUgd29ya3NwYWNlIHdvcmtpbmcgZGlyZWN0b3J5LiIKICAgICAgICB9LAogICAgICAgICJzaG93UHVsbFJlcXVlc3RzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBwdWxsIHJlcXVlc3QgbWV0YWRhdGEgaW4gdGhlIHNpZGViYXIuIgogICAgICAgIH0sCiAgICAgICAgIndhdGNoR2l0U3RhdHVzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAg", + "ICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV2F0Y2ggcmVwb3NpdG9yeSBmaWxlcyBmb3Igc2lkZWJhciBicmFuY2ggYW5kIHB1bGwgcmVxdWVzdCBtZXRhZGF0YSB3aXRob3V0IHBvbGxpbmcgZ2l0LiIKICAgICAgICB9LAogICAgICAgICJtYWtlUHVsbFJlcXVlc3RzQ2xpY2thYmxlIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQWxsb3cgc2lkZWJhciBwdWxsIHJlcXVlc3Qg", + "bWV0YWRhdGEgdG8gb3BlbiBsaW5rcyB3aGVuIGNsaWNrZWQuIgogICAgICAgIH0sCiAgICAgICAgIm9wZW5QdWxsUmVxdWVzdExpbmtzSW5DbXV4QnJvd3NlciI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk9wZW4gc2lkZWJhciBwdWxsIHJlcXVlc3QgbGlua3MgaW4gdGhlIGVtYmVkZGVkIGNtdXggYnJvd3Nlci4iCiAgICAgICAgfSwKICAgICAgICAib3BlblBvcnRMaW5rc0luQ211eEJyb3dzZXIiOiB7CiAgICAgICAg", + "ICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJPcGVuIHNpZGViYXIgcG9ydCBsaW5rcyBpbiB0aGUgZW1iZWRkZWQgY211eCBicm93c2VyLiIKICAgICAgICB9LAogICAgICAgICJzaG93U1NIIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBTU0ggY29ubmVjdGlvbiBkZXRhaWxzLiIKICAgICAgICB9LAogICAgICAgICJzaG93UG9y", + "dHMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IGxpc3RlbmluZyBwb3J0cy4iCiAgICAgICAgfSwKICAgICAgICAic2hvd0xvZyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgcmVjZW50IGxvZyBzbmlwcGV0cy4iCiAgICAgICAgfSwKICAgICAgICAic2hvd1Byb2dyZXNzIjogewogICAgICAgICAg", + "InR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBwcm9ncmVzcyBpbmRpY2F0b3JzLiIKICAgICAgICB9LAogICAgICAgICJzaG93QWdlbnRBY3Rpdml0eSI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgdGhlIGxvYWRpbmcgc3Bpbm5lciBvbiB3b3Jrc3BhY2VzIHdpdGggcnVubmluZyBjb2RpbmcgYWdlbnRzIG9yIGFjdGl2ZSBs", + "b2FkZXJzLiIKICAgICAgICB9LAogICAgICAgICJsb2FkaW5nU3Bpbm5lclBvc2l0aW9uIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWyJsZWFkaW5nIiwgInRyYWlsaW5nIl0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJsZWFkaW5nIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJXaGljaCBzaWRlIG9mIHRoZSB3b3Jrc3BhY2Ugcm93IHRoZSBsb2FkaW5nIHNwaW5uZXIgYXBwZWFycyBvbjogbGVhZGluZyAobGVmdCwgc2hhcmluZyB0aGUgdW5yZWFkLWJhZGdlIHNsb3QpIG9yIHRy", + "YWlsaW5nIChyaWdodCkuIgogICAgICAgIH0sCiAgICAgICAgIm5vdGlmaWNhdGlvbkJhZGdlUG9zaXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbImxlYWRpbmciLCAidHJhaWxpbmciXSwKICAgICAgICAgICJkZWZhdWx0IjogImxlYWRpbmciLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIldoaWNoIHNpZGUgb2YgdGhlIHdvcmtzcGFjZSByb3cgdGhlIHVucmVhZCBub3RpZmljYXRpb24gYmFkZ2UgYXBwZWFycyBvbjogbGVhZGluZyAobGVmdCkgb3IgdHJhaWxpbmcgKHJp", + "Z2h0KS4iCiAgICAgICAgfSwKICAgICAgICAic2hvd0N1c3RvbU1ldGFkYXRhIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBjdXN0b20gbWV0YWRhdGEgcGlsbHMuIgogICAgICAgIH0sCiAgICAgICAgInJpZ2h0TWF4V2lkdGgiOiB7CiAgICAgICAgICAidHlwZSI6ICJudW1iZXIiLAogICAgICAgICAgImV4Y2x1c2l2ZU1pbmltdW0iOiAwLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2Ny", + "aXB0aW9ucy5zaWRlYmFyLnJpZ2h0TWF4V2lkdGgiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk1heGltdW0gd2lkdGggaW4gcG9pbnRzIGZvciB0aGUgcmlnaHQgc2lkZWJhci4gV2hlbiBvbWl0dGVkLCB0aGUgYnVpbHQtaW4gZHluYW1pYyBjYXAgYXBwbGllcy4iCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgIndvcmtzcGFjZUNvbG9ycyI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAid29ya3NwYWNlQ29sb3JzIiwKICAgICAgImRlc2NyaXB0aW9uIjogIldvcmtz", + "cGFjZSB0YWIgYW5kIGJhZGdlIGNvbG9ycyBmcm9tIFNldHRpbmdzID4gV29ya3NwYWNlIENvbG9ycy4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImluZGljYXRvclN0eWxlIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWwogICAgICAgICAgICAibGVmdFJhaWwiLAogICAgICAgICAgICAic29saWRGaWxsIiwKICAgICAgICAgICAgInJhaWwiLAogICAgICAgICAg", + "ICAiYm9yZGVyIiwKICAgICAgICAgICAgIndhc2giLAogICAgICAgICAgICAibGlmdCIsCiAgICAgICAgICAgICJ0eXBvZ3JhcGh5IiwKICAgICAgICAgICAgIndhc2hSYWlsIiwKICAgICAgICAgICAgImJsdWVXYXNoQ29sb3JSYWlsIgogICAgICAgICAgXSwKICAgICAgICAgICJkZWZhdWx0IjogImxlZnRSYWlsIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJBY3RpdmUgd29ya3NwYWNlIGluZGljYXRvciBzdHlsZS4gTGVnYWN5IGFsaWFzZXMgYXJlIGFjY2VwdGVkIGFuZCBub3JtYWxpemVkLiIKICAgICAgICB9LAogICAg", + "ICAgICJzZWxlY3Rpb25Db2xvciI6IHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXhPck51bGwiLAogICAgICAgICAgImRlZmF1bHQiOiBudWxsLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk92ZXJyaWRlIHRoZSBzZWxlY3RlZCB3b3Jrc3BhY2UgYmFja2dyb3VuZCBjb2xvci4iCiAgICAgICAgfSwKICAgICAgICAibm90aWZpY2F0aW9uQmFkZ2VDb2xvciI6IHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXhPck51bGwiLAogICAgICAgICAgImRlZmF1bHQiOiBudWxsLAogICAgICAgICAg", + "ImRlc2NyaXB0aW9uIjogIk92ZXJyaWRlIHRoZSB1bnJlYWQgbm90aWZpY2F0aW9uIGJhZGdlIGNvbG9yLiIKICAgICAgICB9LAogICAgICAgICJjb2xvcnMiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkZ1bGwgbmFtZWQgd29ya3NwYWNlIGNvbG9yIHBhbGV0dGUuIEluY2x1ZGUgYnVpbHQtaW4gZW50cmllcyB5b3Ugd2FudCB0byBrZWVwLCByZW1vdmUga2V5cyB0byByZW1vdmUgY29sb3JzLCBhbmQgYWRkIG1vcmUgbmFtZWQgZW50cmllcyB0byBleHRlbmQgdGhlIHBp", + "Y2tlci4iLAogICAgICAgICAgImRlZmF1bHQiOiB7CiAgICAgICAgICAgICJSZWQiOiAiI0MwMzkyQiIsCiAgICAgICAgICAgICJDcmltc29uIjogIiM5MjJCMjEiLAogICAgICAgICAgICAiT3JhbmdlIjogIiNBMDQwMDAiLAogICAgICAgICAgICAiQW1iZXIiOiAiIzdENjYwOCIsCiAgICAgICAgICAgICJPbGl2ZSI6ICIjNEE1QzE4IiwKICAgICAgICAgICAgIkdyZWVuIjogIiMxOTZGM0QiLAogICAgICAgICAgICAiVGVhbCI6ICIjMDA2QjZCIiwKICAgICAgICAgICAgIkFxdWEiOiAiIzBFNkI4QyIsCiAgICAgICAgICAgICJC", + "bHVlIjogIiMxNTY1QzAiLAogICAgICAgICAgICAiTmF2eSI6ICIjMUE1Mjc2IiwKICAgICAgICAgICAgIkluZGlnbyI6ICIjMjgzNTkzIiwKICAgICAgICAgICAgIlB1cnBsZSI6ICIjNkExQjlBIiwKICAgICAgICAgICAgIk1hZ2VudGEiOiAiI0FEMTQ1NyIsCiAgICAgICAgICAgICJSb3NlIjogIiM4ODBFNEYiLAogICAgICAgICAgICAiQnJvd24iOiAiIzdCM0YwMCIsCiAgICAgICAgICAgICJDaGFyY29hbCI6ICIjM0U0QjVFIgogICAgICAgICAgfSwKICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IHsKICAgICAg", + "ICAgICAgIiRyZWYiOiAiIy8kZGVmcy9jb2xvckhleCIKICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgICJwYWxldHRlT3ZlcnJpZGVzIjogewogICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJMZWdhY3kgd29ya3NwYWNlIGNvbG9yIG92ZXJyaWRlcyBmb3IgYnVpbHQtaW4gcGFsZXR0ZSBuYW1lcy4gUHJlZmVyIHdvcmtzcGFjZUNvbG9ycy5jb2xvcnMgZm9yIG5ldyBjb25maWdzLiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHt9LAogICAgICAgICAgImRlcHJlY2F0ZWQiOiB0", + "cnVlLAogICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogewogICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4IgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgImN1c3RvbUNvbG9ycyI6IHsKICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJMZWdhY3kgbGlzdCBvZiBjdXN0b20gd29ya3NwYWNlIGNvbG9ycy4gUHJlZmVyIHdvcmtzcGFjZUNvbG9ycy5jb2xvcnMgZm9yIG5ldyBjb25maWdzLiIsCiAgICAgICAgICAiZGVmYXVsdCI6IFtdLAogICAg", + "ICAgICAgImRlcHJlY2F0ZWQiOiB0cnVlLAogICAgICAgICAgIml0ZW1zIjogewogICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4IgogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJzaWRlYmFyQXBwZWFyYW5jZSI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAic2lkZWJhckFwcGVhcmFuY2UiLAogICAgICAiZGVzY3JpcHRpb24iOiAiU2lkZWJhciB0aW50IHNldHRpbmdzIGZyb20gU2V0dGluZ3MgPiBTaWRlYmFyIEFwcGVhcmFuY2Uu", + "IiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICJtYXRjaFRlcm1pbmFsQmFja2dyb3VuZCI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJVc2UgdGhlIHRlcm1pbmFsIGJhY2tncm91bmQgaW5zdGVhZCBvZiB0aGUgc2lkZWJhciB0aW50LiIKICAgICAgICB9LAogICAgICAgICJ0aW50Q29sb3IiOiB7CiAg", + "ICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2NvbG9ySGV4IiwKICAgICAgICAgICJkZWZhdWx0IjogIiMwMDAwMDAiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkJhc2Ugc2lkZWJhciB0aW50IGNvbG9yIHVzZWQgd2hlbiBsaWdodC9kYXJrIG92ZXJyaWRlcyBhcmUgbm90IHNldC4iCiAgICAgICAgfSwKICAgICAgICAibGlnaHRNb2RlVGludENvbG9yIjogewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9jb2xvckhleE9yTnVsbCIsCiAgICAgICAgICAiZGVmYXVsdCI6IG51bGwsCiAgICAgICAgICAiZGVzY3JpcHRpb24i", + "OiAiU2lkZWJhciB0aW50IG92ZXJyaWRlIGZvciBsaWdodCBhcHBlYXJhbmNlLiIKICAgICAgICB9LAogICAgICAgICJkYXJrTW9kZVRpbnRDb2xvciI6IHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvY29sb3JIZXhPck51bGwiLAogICAgICAgICAgImRlZmF1bHQiOiBudWxsLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNpZGViYXIgdGludCBvdmVycmlkZSBmb3IgZGFyayBhcHBlYXJhbmNlLiIKICAgICAgICB9LAogICAgICAgICJ0aW50T3BhY2l0eSI6IHsKICAgICAgICAgICJ0eXBlIjogIm51bWJlciIsCiAgICAg", + "ICAgICAibWluaW11bSI6IDAsCiAgICAgICAgICAibWF4aW11bSI6IDEsCiAgICAgICAgICAiZGVmYXVsdCI6IDAuMTgsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2lkZWJhciB0aW50IG9wYWNpdHkgZnJvbSAwIHRvIDEuIE5vdGU6IHRoaXMgb25seSBjb250cm9scyB0aGUgc2lkZWJhciB0aW50LCBub3QgdGVybWluYWwvd2luZG93IHRyYW5zcGFyZW5jeS4gRm9yIHRlcm1pbmFsIGJhY2tncm91bmQgdHJhbnNwYXJlbmN5IG9yIGJsdXIsIHNldCBgYmFja2dyb3VuZC1vcGFjaXR5YCBhbmQgYGJhY2tncm91bmQtYmx1cmAg", + "aW4gYH4vLmNvbmZpZy9naG9zdHR5L2NvbmZpZ2AgYW5kIHJ1biBgY211eCByZWxvYWQtY29uZmlnYC4iCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgImF1dG9tYXRpb24iOiB7CiAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgInRpdGxlIjogImF1dG9tYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiU29ja2V0IGNvbnRyb2wgYW5kIGF1dG9tYXRpb24gc2V0dGluZ3MgZnJvbSBTZXR0aW5ncyA+IEF1dG9tYXRpb24uIiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9u", + "YWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICJzb2NrZXRDb250cm9sTW9kZSI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsKICAgICAgICAgICAgIm9mZiIsCiAgICAgICAgICAgICJjbXV4T25seSIsCiAgICAgICAgICAgICJhdXRvbWF0aW9uIiwKICAgICAgICAgICAgInBhc3N3b3JkIiwKICAgICAgICAgICAgImFsbG93QWxsIiwKICAgICAgICAgICAgIm9wZW5BY2Nlc3MiLAogICAgICAgICAgICAiZnVsbE9wZW5BY2Nlc3MiLAogICAg", + "ICAgICAgICAibm90aWZpY2F0aW9ucyIsCiAgICAgICAgICAgICJmdWxsIgogICAgICAgICAgXSwKICAgICAgICAgICJkZWZhdWx0IjogImNtdXhPbmx5IiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTb2NrZXQgY29udHJvbCBtb2RlLiBMZWdhY3kgYWxpYXNlcyBhcmUgYWNjZXB0ZWQgYW5kIG5vcm1hbGl6ZWQuIgogICAgICAgIH0sCiAgICAgICAgInNvY2tldFBhc3N3b3JkIjogewogICAgICAgICAgIm9uZU9mIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgICB9", + "LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInR5cGUiOiAibnVsbCIKICAgICAgICAgICAgfQogICAgICAgICAgXSwKICAgICAgICAgICJkZWZhdWx0IjogIiIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiUGFzc3dvcmQgZm9yIHBhc3N3b3JkLW1vZGUgc29ja2V0IGFjY2Vzcy4gVXNlIG51bGwgb3IgYW4gZW1wdHkgc3RyaW5nIHRvIGNsZWFyIGl0LiIKICAgICAgICB9LAogICAgICAgICJjbGF1ZGVDb2RlSW50ZWdyYXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0", + "IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbmFibGUgY211eCBpbnRlZ3JhdGlvbiBob29rcyBmb3IgQ2xhdWRlIENvZGUuIgogICAgICAgIH0sCiAgICAgICAgImNvZGV4SW50ZWdyYXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJFbmFibGUgY211eCBpbnRlZ3JhdGlvbiBob29rcyBmb3IgQ29kZXguIFdoZW4gZGlzYWJsZWQsIGNtdXggbm8gbG9uZ2VyIHdyYXBzIHRoZSBjb2RleCBjb21tYW5k", + "IGJ1dCBzdGlsbCB0cmFja3MgbGl2ZSBDb2RleCBzZXNzaW9ucyBpdCBjYW4gb2JzZXJ2ZS4iCiAgICAgICAgfSwKICAgICAgICAiY2xhdWRlQmluYXJ5UGF0aCI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZGVmYXVsdCI6ICIiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkN1c3RvbSBwYXRoIHRvIHRoZSBjbGF1ZGUgYmluYXJ5LiIKICAgICAgICB9LAogICAgICAgICJ3b3Jrc3BhY2VBdXRvTmFtaW5nIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVs", + "dCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5hdXRvbWF0aW9uLndvcmtzcGFjZUF1dG9OYW1pbmciLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIk9wdC1pbiBBSSBhdXRvLW5hbWluZyBvZiB3b3Jrc3BhY2VzIGFuZCB0YWJzIGZyb20gYWdlbnQgY29udmVyc2F0aW9uIGNvbnRlbnQuIFdoZW4gZW5hYmxlZCwgY211eCBzdW1tYXJpemVzIHN1cHBvcnRlZCBhZ2VudCBzZXNzaW9ucyBpbnRvIHNob3J0IHRpdGxlcyB1c2luZyBlYWNoIGFnZW50J3Mgb3duIGJpbmFyeTsg", + "bWFudWFsIHJlbmFtZXMgYWx3YXlzIHdpbi4iCiAgICAgICAgfSwKICAgICAgICAiYXV0b05hbWluZ0FnZW50IjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJkZWZhdWx0IjogImF1dG8iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5hdXRvbWF0aW9uLmF1dG9OYW1pbmdBZ2VudCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiV2hpY2ggYWdlbnQgZ2VuZXJhdGVzIGF1dG8tbmFtZXMgZm9yIGV2ZXJ5IHNlc3Npb24uIFwiYXV0b1wiIChkZWZhdWx0KSBu", + "YW1lcyBlYWNoIHNlc3Npb24gd2l0aCBpdHMgb3duIGFnZW50OyBhbnkgYWdlbnQgc2x1ZyAoY2xhdWRlLCBjb2RleCwgZ3Jvaywgb3BlbmNvZGUsIHBpLCBvbXAsIOKApikgb3ZlcnJpZGVzIG5hbWluZyBmb3IgYWxsIHNlc3Npb25zLCBldmVuIG90aGVyIGFnZW50cycgc2Vzc2lvbnMuIFVuZHJpdmVhYmxlIG9yIHVuaW5zdGFsbGVkIGFnZW50cyBmYWxsIGJhY2sgdG8gdGhlIHNlc3Npb24ncyBvd24gYWdlbnQsIHNvIG5hbWluZyBuZXZlciBicmVha3MuIgogICAgICAgIH0sCiAgICAgICAgInJpcGdyZXBCaW5hcnlQYXRoIjog", + "ewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJkZWZhdWx0IjogIiIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQ3VzdG9tIHBhdGggdG8gdGhlIHJpcGdyZXAgKHJnKSBiaW5hcnkgdXNlZCBieSBwcm9qZWN0IHNlYXJjaC4iCiAgICAgICAgfSwKICAgICAgICAic3VwcHJlc3NTdWJhZ2VudE5vdGlmaWNhdGlvbnMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlv", + "bnMuYXV0b21hdGlvbi5zdXBwcmVzc1N1YmFnZW50Tm90aWZpY2F0aW9ucyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU3VwcHJlc3MgdmlzaWJsZSBjb21wbGV0aW9uIG5vdGlmaWNhdGlvbnMgYW5kIHN0YXR1cyBtdXRhdGlvbnMgZnJvbSBuZXN0ZWQgQ29kZXggb3IgQ2xhdWRlIGNoaWxkIGFnZW50cyB3aGlsZSBrZWVwaW5nIHRoZWlyIGV2ZW50cyBpbiBGZWVkIHRlbGVtZXRyeS4iCiAgICAgICAgfSwKICAgICAgICAiYW1wSW50ZWdyYXRpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAg", + "ICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuYXV0b21hdGlvbi5hbXBJbnRlZ3JhdGlvbiIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRW5hYmxlIGNtdXggaW50ZWdyYXRpb24gaG9va3MgZm9yIEFtcC4gV2hlbiBkaXNhYmxlZCwgdGhlIGJ1bmRsZWQgcGx1Z2luIHN0YXlzIGluYWN0aXZlIHdpdGhvdXQgbmVlZGluZyB0byBiZSByZW1vdmVkLiIKICAgICAgICB9LAogICAgICAgICJjdXJzb3JJbnRlZ3JhdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjog", + "ImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkVuYWJsZSBjbXV4IGludGVncmF0aW9uIGhvb2tzIGZvciBDdXJzb3IuIgogICAgICAgIH0sCiAgICAgICAgImdlbWluaUludGVncmF0aW9uIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRW5hYmxlIGNtdXggaW50ZWdyYXRpb24gaG9va3MgZm9yIEdlbWluaS4iCiAgICAgICAgfSwKICAgICAgICAia2lyb0lu", + "dGVncmF0aW9uIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRW5hYmxlIGNtdXggaW50ZWdyYXRpb24gaG9va3MgZm9yIEtpcm8gQ0xJLiIKICAgICAgICB9LAogICAgICAgICJraXJvTm90aWZpY2F0aW9uTGV2ZWwiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbCiAgICAgICAgICAgICJtaW5pbWFsIiwKICAgICAgICAgICAgInN0YW5kYXJkIiwKICAgICAgICAgICAgInZlcmJv", + "c2UiCiAgICAgICAgICBdLAogICAgICAgICAgImRlZmF1bHQiOiAic3RhbmRhcmQiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkNvbnRyb2xzIGhvdyBtYW55IEtpcm8gdG9vbCBldmVudHMgYXBwZWFyIGluIEZlZWQuIgogICAgICAgIH0sCiAgICAgICAgInBvcnRCYXNlIjogewogICAgICAgICAgInR5cGUiOiAiaW50ZWdlciIsCiAgICAgICAgICAibWluaW11bSI6IDEsCiAgICAgICAgICAiZGVmYXVsdCI6IDkxMDAsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU3RhcnRpbmcgdmFsdWUgZm9yIHdvcmtzcGFjZSBDTVVY", + "X1BPUlQgYXNzaWdubWVudHMuIgogICAgICAgIH0sCiAgICAgICAgInBvcnRSYW5nZSI6IHsKICAgICAgICAgICJ0eXBlIjogImludGVnZXIiLAogICAgICAgICAgIm1pbmltdW0iOiAxLAogICAgICAgICAgImRlZmF1bHQiOiAxMCwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJOdW1iZXIgb2YgcG9ydHMgcmVzZXJ2ZWQgcGVyIHdvcmtzcGFjZS4iCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgImJyb3dzZXIiOiB7CiAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgInRpdGxlIjogImJyb3dzZXIi", + "LAogICAgICAiZGVzY3JpcHRpb24iOiAiRW1iZWRkZWQgYnJvd3NlciBzZXR0aW5ncyBmcm9tIFNldHRpbmdzID4gQnJvd3Nlci4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImRlZmF1bHRTZWFyY2hFbmdpbmUiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbImdvb2dsZSIsICJkdWNrZHVja2dvIiwgImJpbmciLCAia2FnaSIsICJzdGFydHBhZ2UiLCAiYnJhdmUi", + "LCAicGVycGxleGl0eSIsICJleGEiLCAieWFob28iLCAiZWNvc2lhIiwgInF3YW50IiwgIm1vamVlayIsICJ3aWtpcGVkaWEiLCAiZ2l0aHViIiwgImJhaWR1IiwgInlhbmRleCIsICJjdXN0b20iXSwKICAgICAgICAgICJkZWZhdWx0IjogImdvb2dsZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmJyb3dzZXIuZGVmYXVsdFNlYXJjaEVuZ2luZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRGVmYXVsdCBzZWFyY2ggZW5naW5lIGZvciBub24tVVJMIGJyb3dzZXIgYWRkcmVzcyBi", + "YXIgcXVlcmllcy4gVXNlIGN1c3RvbSB3aXRoIGN1c3RvbVNlYXJjaEVuZ2luZVVSTFRlbXBsYXRlIGZvciBhcmJpdHJhcnkgcHJvdmlkZXJzLiIKICAgICAgICB9LAogICAgICAgICJjdXN0b21TZWFyY2hFbmdpbmVOYW1lIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJkZWZhdWx0IjogIiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmJyb3dzZXIuY3VzdG9tU2VhcmNoRW5naW5lTmFtZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRGlzcGxheSBu", + "YW1lIHVzZWQgd2hlbiBkZWZhdWx0U2VhcmNoRW5naW5lIGlzIGN1c3RvbS4iCiAgICAgICAgfSwKICAgICAgICAiY3VzdG9tU2VhcmNoRW5naW5lVVJMVGVtcGxhdGUiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9zZWFyY2g/cT17cXVlcnl9IiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuYnJvd3Nlci5jdXN0b21TZWFyY2hFbmdpbmVVUkxUZW1wbGF0ZSIsCiAgICAgICAgICAiZGVzY3JpcHRp", + "b24iOiAiU2VhcmNoIFVSTCB1c2VkIHdoZW4gZGVmYXVsdFNlYXJjaEVuZ2luZSBpcyBjdXN0b20uIEluY2x1ZGUge3F1ZXJ5fSBvciAlcyBmb3IgdGhlIGVuY29kZWQgcXVlcnkuIElmIG9taXR0ZWQsIGNtdXggYXBwZW5kcyBxPSB0byB0aGUgVVJMLiIKICAgICAgICB9LAogICAgICAgICJzaG93U2VhcmNoU3VnZ2VzdGlvbnMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IG9tbmliYXIgc2VhcmNoIHN1Z2dlc3Rp", + "b25zLiIKICAgICAgICB9LAogICAgICAgICJ0aGVtZSI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsic3lzdGVtIiwgImxpZ2h0IiwgImRhcmsiXSwKICAgICAgICAgICJkZWZhdWx0IjogInN5c3RlbSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRW1iZWRkZWQgYnJvd3NlciB0aGVtZS4iCiAgICAgICAgfSwKICAgICAgICAiZGVmYXVsdFpvb21MZXZlbCI6IHsKICAgICAgICAgICJ0eXBlIjogIm51bWJlciIsCiAgICAgICAgICAibWluaW11bSI6IDAuMjUsCiAgICAgICAgICAi", + "bWF4aW11bSI6IDUsCiAgICAgICAgICAiZGVmYXVsdCI6IDEsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmJyb3dzZXIuZGVmYXVsdFpvb21MZXZlbCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRGVmYXVsdCBwYWdlIHpvb20gZmFjdG9yIGFwcGxpZWQgdG8gbmV3bHkgb3BlbmVkIGJyb3dzZXIgcGFnZXMgKDEgPSAxMDAlKS4gWm9vbSBJbiwgWm9vbSBPdXQsIGFuZCBBY3R1YWwgU2l6ZSBzdGlsbCBhZGp1c3QgZWFjaCBwYWdlLiIKICAgICAgICB9LAogICAgICAgICJkaXNjYXJk", + "SGlkZGVuV2ViVmlld3MiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJBbGxvdyBoaWRkZW4gYnJvd3NlciB0YWJzIHRvIHJlbGVhc2UgcGFnZSBtZW1vcnkgYW5kIHJlc3RvcmUgd2hlbiBzaG93biBhZ2Fpbi4iCiAgICAgICAgfSwKICAgICAgICAiaGlkZGVuV2ViVmlld0Rpc2NhcmREZWxheVNlY29uZHMiOiB7CiAgICAgICAgICAidHlwZSI6ICJudW1iZXIiLAogICAgICAgICAgIm1pbmltdW0iOiAwLAogICAgICAgICAg", + "Im1heGltdW0iOiAzNjAwLAogICAgICAgICAgImRlZmF1bHQiOiAzMDAsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2Vjb25kcyBhIGJyb3dzZXIgdGFiIG11c3Qgc3RheSBoaWRkZW4gYmVmb3JlIGNtdXggZnJlZXMgaXRzIHBhZ2UgbWVtb3J5LiIKICAgICAgICB9LAogICAgICAgICJhc2tXaGVyZVRvU2F2ZURvd25sb2FkcyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiBmYWxzZSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuYnJv", + "d3Nlci5hc2tXaGVyZVRvU2F2ZURvd25sb2FkcyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2hvdyBhIHNhdmUgcGFuZWwgZm9yIGJyb3dzZXIgZG93bmxvYWRzIGluc3RlYWQgb2Ygc2F2aW5nIGRpcmVjdGx5IHRvIERvd25sb2Fkcy4iCiAgICAgICAgfSwKICAgICAgICAib3BlblRlcm1pbmFsTGlua3NJbkNtdXhCcm93c2VyIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3BlbiBjbGlja2VkIHRlcm1pbmFsIGxp", + "bmtzIGluIHRoZSBlbWJlZGRlZCBicm93c2VyLiIKICAgICAgICB9LAogICAgICAgICJpbnRlcmNlcHRUZXJtaW5hbE9wZW5Db21tYW5kSW5DbXV4QnJvd3NlciI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkludGVyY2VwdCB0ZXJtaW5hbCBvcGVuIGh0dHAocykgY29tbWFuZHMgYW5kIHJvdXRlIHRoZW0gdGhyb3VnaCB0aGUgZW1iZWRkZWQgYnJvd3Nlci4iCiAgICAgICAgfSwKICAgICAgICAiaG9zdHNUb09wZW5JbkVt", + "YmVkZGVkQnJvd3NlciI6IHsKICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICJpdGVtcyI6IHsKICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgfSwKICAgICAgICAgICJkZWZhdWx0IjogW10sCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQWxsb3dsaXN0IG9mIGhvc3RzIHRoYXQgc2hvdWxkIHN0YXkgaW5zaWRlIHRoZSBlbWJlZGRlZCBicm93c2VyLiIKICAgICAgICB9LAogICAgICAgICJ1cmxzVG9BbHdheXNPcGVuRXh0ZXJuYWxseSI6IHsKICAgICAgICAgICJ0eXBlIjogImFycmF5", + "IiwKICAgICAgICAgICJpdGVtcyI6IHsKICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgfSwKICAgICAgICAgICJkZWZhdWx0IjogW10sCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiUnVsZXMgdGhhdCBhbHdheXMgb3BlbiBtYXRjaGluZyBVUkxzIGluIHRoZSBzeXN0ZW0gYnJvd3Nlci4iCiAgICAgICAgfSwKICAgICAgICAiaW5zZWN1cmVIdHRwSG9zdHNBbGxvd2VkSW5FbWJlZGRlZEJyb3dzZXIiOiB7CiAgICAgICAgICAidHlwZSI6ICJhcnJheSIsCiAgICAgICAgICAiaXRlbXMiOiB7CiAgICAgICAg", + "ICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgIH0sCiAgICAgICAgICAiZGVmYXVsdCI6IFsKICAgICAgICAgICAgImxvY2FsaG9zdCIsCiAgICAgICAgICAgICIqLmxvY2FsaG9zdCIsCiAgICAgICAgICAgICIxMjcuMC4wLjEiLAogICAgICAgICAgICAiOjoxIiwKICAgICAgICAgICAgIjAuMC4wLjAiLAogICAgICAgICAgICAiKi5sb2NhbHRlc3QubWUiCiAgICAgICAgICBdLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkhUVFAgaG9zdHMgYWxsb3dlZCBpbiB0aGUgZW1iZWRkZWQgYnJvd3NlciB3aXRob3V0IGEgd2Fy", + "bmluZyBwcm9tcHQuIgogICAgICAgIH0sCiAgICAgICAgInVybEFsbG93bGlzdCI6IHsKICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICJpdGVtcyI6IHsKICAgICAgICAgICAgInR5cGUiOiAic3RyaW5nIgogICAgICAgICAgfSwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuYnJvd3Nlci51cmxBbGxvd2xpc3QiLAogICAgICAgICAgImRlZmF1bHQiOiBbCiAgICAgICAgICAgICJsb2NhbGhvc3QiLAogICAgICAgICAgICAiKi5sb2NhbGhvc3QiLAogICAgICAgICAgICAi", + "MTI3LjAuMC4xIiwKICAgICAgICAgICAgIjo6MSIsCiAgICAgICAgICAgICIwLjAuMC4wIiwKICAgICAgICAgICAgIioubG9jYWx0ZXN0Lm1lIgogICAgICAgICAgXSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJIb3N0IG9yIFVSTCBwYXR0ZXJucyB0aGF0IHJlc3RyaWN0IGVtYmVkZGVkLWJyb3dzZXIgbmF2aWdhdGlvbi4gVGhlIFNldHRpbmdzIFVJIHN1Z2dlc3RzIGxvY2FsIGRldmVsb3BtZW50IG9yaWdpbnM7IHNhdmluZyBhIGxpc3Qgb3B0cyBpbnRvIHRoZSBvcHRpb25hbCByZXN0cmljdGlvbi4gUmVtb3ZlIGVudHJp", + "ZXMgdG8gYmxvY2sgdGhlbSwgb3IgbGVhdmUgdGhlIHVzZXIgdmFsdWUgZW1wdHkgdG8gZGlzYWJsZSBpdCB3aGVuIG5vIG1hbmFnZWQgcG9saWN5IGFwcGxpZXMuIgogICAgICAgIH0sCiAgICAgICAgInNob3dJbXBvcnRIaW50T25CbGFua1RhYnMiOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG93IHRoZSBicm93c2VyIGltcG9ydCBoaW50IG9uIGJsYW5rIHRhYnMuIgogICAgICAgIH0sCiAgICAgICAgInJlYWN0R3Jh", + "YlZlcnNpb24iOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiMC4xLjI5IiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJQaW5uZWQgcmVhY3QtZ3JhYiB2ZXJzaW9uIGZvciB0aGUgYnJvd3NlciB0b29sYmFyIGhlbHBlci4iCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgIm1vYmlsZSI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAibW9iaWxlIiwKICAgICAgImRlc2NyaXB0aW9uIjogIk1hYy1zaWRlIHNldHRpbmdzIGZv", + "ciBjbXV4IG9uIGlPUy4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImFydGlmYWN0Rm9sZGVyQWNjZXNzIjogewogICAgICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgICAgICJlbnVtIjogWyJzdWJ0cmVlIiwgIm9uZUxldmVsIl0sCiAgICAgICAgICAiZGVmYXVsdCI6ICJzdWJ0cmVlIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDb250cm9scyB3aGV0aGVyIGEgcmVmZXJlbmNlZCBvciB0ZXJt", + "aW5hbC12aXNpYmxlIGRpcmVjdG9yeSBhdXRob3JpemVzIGl0cyBmdWxsIGNhbm9uaWNhbCBzdWJ0cmVlIG9yIG9ubHkgaW1tZWRpYXRlIGNoaWxkcmVuLiIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAibWFya2Rvd24iOiB7CiAgICAgICJ4LWNtdXgtc2NvcGVzIjogWyJnbG9iYWwiXSwKICAgICAgInRpdGxlIjogIm1hcmtkb3duIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkJ1aWx0LWluIG1hcmtkb3duIHZpZXdlciBzZXR0aW5ncy4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3Bl", + "cnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImZvbnRTaXplIjogewogICAgICAgICAgInR5cGUiOiAiaW50ZWdlciIsCiAgICAgICAgICAibWluaW11bSI6IDgsCiAgICAgICAgICAibWF4aW11bSI6IDk2LAogICAgICAgICAgImRlZmF1bHQiOiAxNSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEZWZhdWx0IGJvZHkgZm9udCBzaXplLCBpbiBwb2ludHMsIGZvciBuZXdseSBvcGVuZWQgbWFya2Rvd24gdmlld2Vycy4gWm9vbSBhIHZpZXdlciBsaXZlIHdpdGggQ21kLSsgLyBDbWQtLSAvIENt", + "ZC0wLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLm1hcmtkb3duLmZvbnRTaXplIgogICAgICAgIH0sCiAgICAgICAgImZvbnRGYW1pbHkiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJEZWZhdWx0IGJvZHkgZm9udCBmYW1pbHkgZm9yIG5ld2x5IG9wZW5lZCBtYXJrZG93biB2aWV3ZXJzLiBMZWF2ZSBlbXB0eSBmb3IgdGhlIHN5c3RlbSBtYXJrZG93biBmb250IHN0YWNrLiIsCiAg", + "ICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLm1hcmtkb3duLmZvbnRGYW1pbHkiCiAgICAgICAgfSwKICAgICAgICAibWF4V2lkdGgiOiB7CiAgICAgICAgICAidHlwZSI6ICJpbnRlZ2VyIiwKICAgICAgICAgICJtaW5pbXVtIjogMzIwLAogICAgICAgICAgIm1heGltdW0iOiAyNDAwLAogICAgICAgICAgImRlZmF1bHQiOiA5ODAsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiRGVmYXVsdCBtYXhpbXVtIHJlYWRpbmcgY29sdW1uIHdpZHRoLCBpbiBDU1MgcGl4ZWxzLCBmb3IgbmV3bHkgb3Bl", + "bmVkIG1hcmtkb3duIHZpZXdlcnMuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubWFya2Rvd24ubWF4V2lkdGgiCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgImNhbnZhcyI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAiY2FudmFzIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkZyZWVmb3JtIGNhbnZhcyB3b3Jrc3BhY2UgbGF5b3V0IHNldHRpbmdzLiIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFs", + "UHJvcGVydGllcyI6IGZhbHNlLAogICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAicGFuZUdhcCI6IHsKICAgICAgICAgICJ0eXBlIjogImludGVnZXIiLAogICAgICAgICAgIm1pbmltdW0iOiAwLAogICAgICAgICAgIm1heGltdW0iOiA2NCwKICAgICAgICAgICJkZWZhdWx0IjogMTYsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU3BhY2luZyBiZXR3ZWVuIHBhbmVzIGluIHRoZSBjYW52YXMgbGF5b3V0LCBpbiBwb2ludHMuIFNuYXBwaW5nLCB0aWR5LCBhbmQgbmV3LXBhbmUgcGxhY2VtZW50IGFsbCB1c2UgdGhpcyBv", + "bmUgZ2FwLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmNhbnZhcy5wYW5lR2FwIgogICAgICAgIH0sCiAgICAgICAgInNuYXBwaW5nRW5hYmxlZCI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNuYXAgcGFuZSBkcmFncyBhbmQgcmVzaXplcyB0byBuZWlnaGJvciBlZGdlcyBhbmQgdGhlIHBhbmUgZ2FwLiBIb2xkIENvbW1hbmQgdG8gc3VzcGVuZCBzbmFwcGluZyBmb3Igb25l", + "IGdlc3R1cmUuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuY2FudmFzLnNuYXBwaW5nRW5hYmxlZCIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAiZmlsZUVkaXRvciI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAiZmlsZUVkaXRvciIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJCdWlsdC1pbiBmaWxlIGVkaXRvciBzZXR0aW5ncy4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMi", + "OiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgIndvcmRXcmFwIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IGZhbHNlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIldyYXAgbG9uZyBsaW5lcyBhdCB0aGUgZWRpdG9yJ3MgcmlnaHQgZWRnZSBpbnN0ZWFkIG9mIHNjcm9sbGluZyBob3Jpem9udGFsbHkuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuZmlsZUVkaXRvci53b3JkV3JhcCIKICAgICAgICB9LAogICAg", + "ICAgICJzeW50YXhIaWdobGlnaHRpbmciOiB7CiAgICAgICAgICAidHlwZSI6ICJib29sZWFuIiwKICAgICAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDb2xvciBzb3VyY2UgdG9rZW5zIGluIHRoZSBidWlsdC1pbiBmaWxlIGVkaXRvci4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5maWxlRWRpdG9yLnN5bnRheEhpZ2hsaWdodGluZyIKICAgICAgICB9LAogICAgICAgICJsaW5lTnVtYmVycyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xl", + "YW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgYSBsaW5lLW51bWJlciBndXR0ZXIgaW4gdGhlIGJ1aWx0LWluIGZpbGUgZWRpdG9yLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmZpbGVFZGl0b3IubGluZU51bWJlcnMiCiAgICAgICAgfSwKICAgICAgICAiaW5kZW50R3VpZGVzIjogewogICAgICAgICAgInR5cGUiOiAiYm9vbGVhbiIsCiAgICAgICAgICAiZGVmYXVsdCI6IHRydWUsCiAgICAgICAgICAiZGVzY3JpcHRp", + "b24iOiAiRHJhdyB2ZXJ0aWNhbCBpbmRlbnQgZ3VpZGVzIGluIHRoZSBidWlsdC1pbiBmaWxlIGVkaXRvci4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5maWxlRWRpdG9yLmluZGVudEd1aWRlcyIKICAgICAgICB9LAogICAgICAgICJjdXJyZW50TGluZUhpZ2hsaWdodCI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkhpZ2hsaWdodCB0aGUgY2FyZXQncyBsaW5lIHdoZW4gdGhl", + "IHNlbGVjdGlvbiBpcyBlbXB0eS4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5maWxlRWRpdG9yLmN1cnJlbnRMaW5lSGlnaGxpZ2h0IgogICAgICAgIH0sCiAgICAgICAgInRhYldpZHRoIjogewogICAgICAgICAgInR5cGUiOiAiaW50ZWdlciIsCiAgICAgICAgICAiZGVmYXVsdCI6IDQsCiAgICAgICAgICAibWluaW11bSI6IDEsCiAgICAgICAgICAibWF4aW11bSI6IDgsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiQ29sdW1ucyBwZXIgdGFiIHN0b3AgZm9yIGluZGVudCBndWlk", + "ZXMuIiwKICAgICAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMuZmlsZUVkaXRvci50YWJXaWR0aCIKICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAiZmlsZUV4cGxvcmVyIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJmaWxlRXhwbG9yZXIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiUmlnaHQtc2lkZWJhciBmaWxlIGV4cGxvcmVyIChmaWxlIHRyZWUpIHNldHRpbmdzLiIsCiAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlw", + "dGlvbnMuZmlsZUV4cGxvcmVyLmRlc2NyaXB0aW9uIiwKICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAgICJwcm9wZXJ0aWVzIjogewogICAgICAgICJkb3VibGVDbGlja0FjdGlvbiI6IHsKICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAiZW51bSI6IFsicHJldmlldyIsICJkZWZhdWx0RWRpdG9yIiwgInByZWZlcnJlZEVkaXRvciJdLAogICAgICAgICAgImRlZmF1bHQiOiAicHJldmlldyIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAi", + "V2hhdCBkb3VibGUtY2xpY2tpbmcgKG9yIHByZXNzaW5nIFJldHVybiBvbiBhIHNlYXJjaCByZXN1bHQgZm9yKSBhIGZpbGUgaW4gdGhlIGZpbGUgZXhwbG9yZXIgZG9lcy4gYHByZXZpZXdgIG9wZW5zIHRoZSBidWlsdC1pbiBjbXV4IGZpbGUgcHJldmlldyAodGhlIGRlZmF1bHQgYW5kIGhpc3RvcmljYWwgYmVoYXZpb3IpLiBgZGVmYXVsdEVkaXRvcmAgb3BlbnMgd2l0aCB0aGUgbWFjT1MgZGVmYXVsdCBhcHAgZm9yIHRoZSBmaWxlIHR5cGUuIGBwcmVmZXJyZWRFZGl0b3JgIG9wZW5zIHdpdGggdGhlIGBhcHAucHJlZmVycmVk", + "RWRpdG9yYCBjb21tYW5kLCBmYWxsaW5nIGJhY2sgdG8gdGhlIGRlZmF1bHQgYXBwIHdoZW4gbm9uZSBpcyBzZXQuIE9ubHkgYXBwbGllcyB0byBmaWxlczsgZGlyZWN0b3JpZXMgYWx3YXlzIGV4cGFuZC9jb2xsYXBzZSwgYW5kIG5vbi1sb2NhbCAocmVtb3RlKSBmaWxlIGV4cGxvcmVycyBhbHdheXMgb3BlbiB0aGUgY211eCBwcmV2aWV3LiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLmZpbGVFeHBsb3Jlci5kb3VibGVDbGlja0FjdGlvbiIKICAgICAgICB9CiAgICAgIH0KICAgIH0s", + "CiAgICAiZGlmZlZpZXdlciI6IHsKICAgICAgIngtY211eC1zY29wZXMiOiBbImdsb2JhbCJdLAogICAgICAidGl0bGUiOiAiZGlmZlZpZXdlciIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJCdWlsdC1pbiBkaWZmIHZpZXdlciBzZXR0aW5ncy4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgImRlZmF1bHRMYXlvdXQiOiB7CiAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAgImVudW0iOiBbInVu", + "aWZpZWQiLCAic3BsaXQiXSwKICAgICAgICAgICJkZWZhdWx0IjogInVuaWZpZWQiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIkRlZmF1bHQgbGF5b3V0IGZvciBuZXdseSBvcGVuZWQgZGlmZiB2aWV3ZXJzLiBUaGUgdG9vbGJhciBsYXlvdXQgdG9nZ2xlIHBlcnNpc3RzIHRoZSB1c2VyJ3MgbGFzdCBjaG9pY2U7IGFuIGV4cGxpY2l0IGBjbXV4IGRpZmYgLS1sYXlvdXRgIG9wdGlvbiBvdmVycmlkZXMgYm90aC4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNjaGVtYURlc2NyaXB0aW9ucy5kaWZmVmlld2VyLmRl", + "ZmF1bHRMYXlvdXQiCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgInJpZ2h0U2lkZWJhciI6IHsKICAgICAgInRpdGxlIjogInJpZ2h0U2lkZWJhciIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJMZWdhY3kgYW5kIGV4dGVuc2lvbi1vd25lZCByaWdodC1zaWRlYmFyIGNvbmZpZ3VyYXRpb24gcHJlc2VydmVkIGluIGNtdXguanNvbi4iLAogICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLnJpZ2h0U2lkZWJhciIsCiAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICJhZGRpdGlvbmFsUHJvcGVy", + "dGllcyI6IHRydWUKICAgIH0sCiAgICAic2hvcnRjdXRzIjogewogICAgICAieC1jbXV4LXNjb3BlcyI6IFsiZ2xvYmFsIl0sCiAgICAgICJ0aXRsZSI6ICJzaG9ydGN1dHMiLAogICAgICAiZGVzY3JpcHRpb24iOiAiS2V5Ym9hcmQgc2hvcnRjdXQgc2V0dGluZ3MgZnJvbSBTZXR0aW5ncyA+IEtleWJvYXJkIFNob3J0Y3V0cy4iLAogICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgInNob3dNb2RpZmllckhvbGRIaW50", + "cyI6IHsKICAgICAgICAgICJ0eXBlIjogImJvb2xlYW4iLAogICAgICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNob3cgc2hvcnRjdXQtaGludCBjaGlwcyB3aGlsZSBob2xkaW5nIENtZCBvciBDb250cm9sLiIsCiAgICAgICAgICAiZGVzY3JpcHRpb25LZXkiOiAic2NoZW1hRGVzY3JpcHRpb25zLnNob3J0Y3V0cy5zaG93TW9kaWZpZXJIb2xkSGludHMiCiAgICAgICAgfSwKICAgICAgICAiYmluZGluZ3MiOiB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImRl", + "ZmF1bHQiOiB7fSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJTaG9ydGN1dCBvdmVycmlkZXMga2V5ZWQgYnkgY211eCBhY3Rpb24gaWQuIFVzZSBhIHN0cmluZyBmb3IgYSBzaW5nbGUgc2hvcnRjdXQsIGFuIGFycmF5IGZvciBhIGNob3JkLCBudWxsLCBhbiBlbXB0eSBzdHJpbmcsIG5vbmUsIGNsZWFyLCB1bmJvdW5kLCBvciBkaXNhYmxlZCB0byB1bmJpbmQuIiwKICAgICAgICAgICJwcm9wZXJ0eU5hbWVzIjogewogICAgICAgICAgICAiZW51bSI6IFsKICAgICAgICAgICAgICAib3BlblNldHRpbmdzIiwKICAgICAgICAg", + "ICAgICAib3BlblRlYW1QaWNrZXIiLAogICAgICAgICAgICAgICJyZWxvYWRDb25maWd1cmF0aW9uIiwKICAgICAgICAgICAgICAic2hvd0hpZGVBbGxXaW5kb3dzIiwKICAgICAgICAgICAgICAiZ2xvYmFsU2VhcmNoIiwKICAgICAgICAgICAgICAibmV3V2luZG93IiwKICAgICAgICAgICAgICAiY2xvc2VXaW5kb3ciLAogICAgICAgICAgICAgICJ0b2dnbGVGdWxsU2NyZWVuIiwKICAgICAgICAgICAgICAicXVpdCIsCiAgICAgICAgICAgICAgInRvZ2dsZVNpZGViYXIiLAogICAgICAgICAgICAgICJuZXdUYWIiLAogICAgICAg", + "ICAgICAgICJuZXdCcm93c2VyV29ya3NwYWNlIiwKICAgICAgICAgICAgICAibmV3Q2xvdWRXb3Jrc3BhY2UiLAogICAgICAgICAgICAgICJuZXdDbG91ZE1hY2hpbmUiLAogICAgICAgICAgICAgICJzYXZlTGF5b3V0VGVtcGxhdGUiLAogICAgICAgICAgICAgICJvcGVuRm9sZGVyIiwKICAgICAgICAgICAgICAicmVvcGVuUHJldmlvdXNTZXNzaW9uIiwKICAgICAgICAgICAgICAiZ29Ub1dvcmtzcGFjZSIsCiAgICAgICAgICAgICAgImNvbW1hbmRQYWxldHRlIiwKICAgICAgICAgICAgICAiY29tbWFuZFBhbGV0dGVOZXh0IiwK", + "ICAgICAgICAgICAgICAiY29tbWFuZFBhbGV0dGVQcmV2aW91cyIsCiAgICAgICAgICAgICAgInNlbmRGZWVkYmFjayIsCiAgICAgICAgICAgICAgInNob3dOb3RpZmljYXRpb25zIiwKICAgICAgICAgICAgICAianVtcFRvVW5yZWFkIiwKICAgICAgICAgICAgICAidG9nZ2xlVW5yZWFkIiwKICAgICAgICAgICAgICAibWFya09sZGVzdFVucmVhZEFuZEp1bXBOZXh0IiwKICAgICAgICAgICAgICAibWFya0FsbE5vdGlmaWNhdGlvbnNSZWFkIiwKICAgICAgICAgICAgICAiY2xlYXJBbGxOb3RpZmljYXRpb25zIiwKICAgICAgICAg", + "ICAgICAiZm9jdXNSaWdodFNpZGViYXIiLAogICAgICAgICAgICAgICJzd2l0Y2hSaWdodFNpZGViYXJUb0ZpbGVzIiwKICAgICAgICAgICAgICAic3dpdGNoUmlnaHRTaWRlYmFyVG9GaW5kIiwKICAgICAgICAgICAgICAic3dpdGNoUmlnaHRTaWRlYmFyVG9TZXNzaW9ucyIsCiAgICAgICAgICAgICAgInN3aXRjaFJpZ2h0U2lkZWJhclRvRmVlZCIsCiAgICAgICAgICAgICAgInN3aXRjaFJpZ2h0U2lkZWJhclRvRG9jayIsCiAgICAgICAgICAgICAgInN3aXRjaFJpZ2h0U2lkZWJhclRvTWFjaGluZXMiLAogICAgICAgICAgICAg", + "ICJ0cmlnZ2VyRmxhc2giLAogICAgICAgICAgICAgICJuZXh0U3VyZmFjZSIsCiAgICAgICAgICAgICAgInByZXZTdXJmYWNlIiwKICAgICAgICAgICAgICAibW92ZVN1cmZhY2VMZWZ0IiwKICAgICAgICAgICAgICAibW92ZVN1cmZhY2VSaWdodCIsCiAgICAgICAgICAgICAgIm1vdmVTdXJmYWNlVG9QcmV2aW91c1BhbmUiLAogICAgICAgICAgICAgICJtb3ZlU3VyZmFjZVRvTmV4dFBhbmUiLAogICAgICAgICAgICAgICJtb3ZlU3VyZmFjZVRvUGFuZUxlZnQiLAogICAgICAgICAgICAgICJtb3ZlU3VyZmFjZVRvUGFuZVJpZ2h0", + "IiwKICAgICAgICAgICAgICAibW92ZVN1cmZhY2VUb1BhbmVVcCIsCiAgICAgICAgICAgICAgIm1vdmVTdXJmYWNlVG9QYW5lRG93biIsCiAgICAgICAgICAgICAgInNlbGVjdFN1cmZhY2VCeU51bWJlciIsCiAgICAgICAgICAgICAgIm5leHRTaWRlYmFyVGFiIiwKICAgICAgICAgICAgICAicHJldlNpZGViYXJUYWIiLAogICAgICAgICAgICAgICJuZXh0U2lkZWJhclRhYkluR3JvdXAiLAogICAgICAgICAgICAgICJwcmV2U2lkZWJhclRhYkluR3JvdXAiLAogICAgICAgICAgICAgICJtb3ZlV29ya3NwYWNlVXAiLAogICAgICAg", + "ICAgICAgICJtb3ZlV29ya3NwYWNlRG93biIsCiAgICAgICAgICAgICAgImZvY3VzSGlzdG9yeUJhY2siLAogICAgICAgICAgICAgICJmb2N1c0hpc3RvcnlGb3J3YXJkIiwKICAgICAgICAgICAgICAiZm9jdXNIaXN0b3J5TGFzdCIsCiAgICAgICAgICAgICAgInNlbGVjdFdvcmtzcGFjZUJ5TnVtYmVyIiwKICAgICAgICAgICAgICAicmVuYW1lVGFiIiwKICAgICAgICAgICAgICAicmVuYW1lV29ya3NwYWNlIiwKICAgICAgICAgICAgICAiZWRpdFdvcmtzcGFjZURlc2NyaXB0aW9uIiwKICAgICAgICAgICAgICAibWFya1dvcmtz", + "cGFjZURvbmUiLAogICAgICAgICAgICAgICJjeWNsZVdvcmtzcGFjZVN0YXR1cyIsCiAgICAgICAgICAgICAgInRvZ2dsZUNoZWNrbGlzdEl0ZW1Db21wbGV0ZSIsCiAgICAgICAgICAgICAgImNsb3NlVGFiIiwKICAgICAgICAgICAgICAiY2xvc2VPdGhlclRhYnNJblBhbmUiLAogICAgICAgICAgICAgICJjbG9zZVdvcmtzcGFjZSIsCiAgICAgICAgICAgICAgIm5ld1dvcmtzcGFjZUdyb3VwIiwKICAgICAgICAgICAgICAiZ3JvdXBTZWxlY3RlZFdvcmtzcGFjZXMiLAogICAgICAgICAgICAgICJ0b2dnbGVGb2N1c2VkV29ya3Nw", + "YWNlR3JvdXBDb2xsYXBzZWQiLAogICAgICAgICAgICAgICJyZW9wZW5DbG9zZWRXb3Jrc3BhY2UiLAogICAgICAgICAgICAgICJyZW9wZW5DbG9zZWRCcm93c2VyUGFuZWwiLAogICAgICAgICAgICAgICJuZXdTdXJmYWNlIiwKICAgICAgICAgICAgICAidG9nZ2xlVGVybWluYWxDb3B5TW9kZSIsCiAgICAgICAgICAgICAgImZvY3VzVGV4dEJveElucHV0IiwKICAgICAgICAgICAgICAiY3ljbGVUZXh0Qm94U3VibWl0QWN0aW9uIiwKICAgICAgICAgICAgICAiYXR0YWNoVGV4dEJveEZpbGUiLAogICAgICAgICAgICAgICJzZW5k", + "Q3RybEZUb1Rlcm1pbmFsIiwKICAgICAgICAgICAgICAiY2xlYXJTY3JlZW5LZWVwU2Nyb2xsYmFjayIsCiAgICAgICAgICAgICAgInNpbXVsYXRvckhvbWUiLAogICAgICAgICAgICAgICJzaW11bGF0b3JSb3RhdGVMZWZ0IiwKICAgICAgICAgICAgICAic2ltdWxhdG9yUm90YXRlUmlnaHQiLAogICAgICAgICAgICAgICJzaW11bGF0b3JUb2dnbGVBcHBlYXJhbmNlIiwKICAgICAgICAgICAgICAic2ltdWxhdG9yVG9nZ2xlU29mdHdhcmVLZXlib2FyZCIsCiAgICAgICAgICAgICAgImZvY3VzTGVmdCIsCiAgICAgICAgICAgICAg", + "ImZvY3VzUmlnaHQiLAogICAgICAgICAgICAgICJmb2N1c1VwIiwKICAgICAgICAgICAgICAiZm9jdXNEb3duIiwKICAgICAgICAgICAgICAiZm9jdXNQcmV2aW91c1BhbmUiLAogICAgICAgICAgICAgICJmb2N1c05leHRQYW5lIiwKICAgICAgICAgICAgICAic3BsaXRSaWdodCIsCiAgICAgICAgICAgICAgInNwbGl0RG93biIsCiAgICAgICAgICAgICAgInRvZ2dsZVNwbGl0Wm9vbSIsCiAgICAgICAgICAgICAgImluY3JlYXNlV29ya3NwYWNlVGVybWluYWxGb250U2l6ZSIsCiAgICAgICAgICAgICAgImRlY3JlYXNlV29ya3Nw", + "YWNlVGVybWluYWxGb250U2l6ZSIsCiAgICAgICAgICAgICAgInJlc2V0V29ya3NwYWNlVGVybWluYWxGb250U2l6ZSIsCiAgICAgICAgICAgICAgImVxdWFsaXplU3BsaXRzIiwKICAgICAgICAgICAgICAicmVzaXplLXBhbmUtbGVmdCIsCiAgICAgICAgICAgICAgInJlc2l6ZS1wYW5lLXJpZ2h0IiwKICAgICAgICAgICAgICAicmVzaXplLXBhbmUtdXAiLAogICAgICAgICAgICAgICJyZXNpemUtcGFuZS1kb3duIiwKICAgICAgICAgICAgICAic3BsaXRCcm93c2VyUmlnaHQiLAogICAgICAgICAgICAgICJzcGxpdEJyb3dzZXJE", + "b3duIiwKICAgICAgICAgICAgICAidG9nZ2xlQ2FudmFzTGF5b3V0IiwKICAgICAgICAgICAgICAiY2FudmFzUmV2ZWFsRm9jdXNlZFBhbmUiLAogICAgICAgICAgICAgICJjYW52YXNPdmVydmlldyIsCiAgICAgICAgICAgICAgImNhbnZhc1pvb21JbiIsCiAgICAgICAgICAgICAgImNhbnZhc1pvb21PdXQiLAogICAgICAgICAgICAgICJjYW52YXNab29tUmVzZXQiLAogICAgICAgICAgICAgICJjYW52YXNUaWR5IiwKICAgICAgICAgICAgICAiY2FudmFzQWxpZ25MZWZ0IiwKICAgICAgICAgICAgICAiY2FudmFzQWxpZ25SaWdo", + "dCIsCiAgICAgICAgICAgICAgImNhbnZhc0FsaWduVG9wIiwKICAgICAgICAgICAgICAiY2FudmFzQWxpZ25Cb3R0b20iLAogICAgICAgICAgICAgICJjYW52YXNFcXVhbGl6ZVdpZHRocyIsCiAgICAgICAgICAgICAgImNhbnZhc0VxdWFsaXplSGVpZ2h0cyIsCiAgICAgICAgICAgICAgImNhbnZhc0Rpc3RyaWJ1dGVIb3Jpem9udGFsbHkiLAogICAgICAgICAgICAgICJjYW52YXNEaXN0cmlidXRlVmVydGljYWxseSIsCiAgICAgICAgICAgICAgInRvZ2dsZUZpbGVFeHBsb3JlciIsCiAgICAgICAgICAgICAgImZpbGVFeHBsb3Jl", + "ck9wZW5TZWxlY3Rpb24iLAogICAgICAgICAgICAgICJmaWxlRXhwbG9yZXJPcGVuU2VsZWN0aW9uRmluZGVyQWxpYXMiLAogICAgICAgICAgICAgICJzYXZlRmlsZVByZXZpZXciLAogICAgICAgICAgICAgICJ0b2dnbGVGaWxlRWRpdG9yV29yZFdyYXAiLAogICAgICAgICAgICAgICJvcGVuQnJvd3NlciIsCiAgICAgICAgICAgICAgImZvY3VzQnJvd3NlckFkZHJlc3NCYXIiLAogICAgICAgICAgICAgICJicm93c2VyQmFjayIsCiAgICAgICAgICAgICAgImJyb3dzZXJGb3J3YXJkIiwKICAgICAgICAgICAgICAiYnJvd3NlclJl", + "bG9hZCIsCiAgICAgICAgICAgICAgImJyb3dzZXJIYXJkUmVsb2FkIiwKICAgICAgICAgICAgICAiYnJvd3Nlclpvb21JbiIsCiAgICAgICAgICAgICAgImJyb3dzZXJab29tT3V0IiwKICAgICAgICAgICAgICAiYnJvd3Nlclpvb21SZXNldCIsCiAgICAgICAgICAgICAgIm1hcmtkb3duWm9vbUluIiwKICAgICAgICAgICAgICAibWFya2Rvd25ab29tT3V0IiwKICAgICAgICAgICAgICAibWFya2Rvd25ab29tUmVzZXQiLAogICAgICAgICAgICAgICJmaW5kIiwKICAgICAgICAgICAgICAiZmluZEluRGlyZWN0b3J5IiwKICAgICAg", + "ICAgICAgICAiZmluZE5leHQiLAogICAgICAgICAgICAgICJmaW5kUHJldmlvdXMiLAogICAgICAgICAgICAgICJoaWRlRmluZCIsCiAgICAgICAgICAgICAgInVzZVNlbGVjdGlvbkZvckZpbmQiLAogICAgICAgICAgICAgICJ0b2dnbGVCcm93c2VyRGV2ZWxvcGVyVG9vbHMiLAogICAgICAgICAgICAgICJzaG93QnJvd3NlckphdmFTY3JpcHRDb25zb2xlIiwKICAgICAgICAgICAgICAidG9nZ2xlQnJvd3NlckZvY3VzTW9kZSIsCiAgICAgICAgICAgICAgInRvZ2dsZUJyb3dzZXJEZXNpZ25Nb2RlIiwKICAgICAgICAgICAgICAi", + "dG9nZ2xlUmVhY3RHcmFiIiwKICAgICAgICAgICAgICAib3BlbkRpZmZWaWV3ZXIiLAogICAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsRG93biIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxVcCIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxIYWxmUGFnZURvd24iLAogICAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsSGFsZlBhZ2VVcCIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxEb3duRW1hY3MiLAogICAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsVXBFbWFjcyIsCiAg", + "ICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxUb0JvdHRvbSIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxUb1RvcCIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJPcGVuRmlsZVNlYXJjaCIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJOZXh0RmlsZSIsCiAgICAgICAgICAgICAgImRpZmZWaWV3ZXJQcmV2aW91c0ZpbGUiCiAgICAgICAgICAgIF0KICAgICAgICAgIH0sCiAgICAgICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAgICAgImZpbGVFeHBsb3Jlck9wZW5TZWxlY3Rpb24iOiB7CiAgICAgICAg", + "ICAgICAgIiRyZWYiOiAiIy8kZGVmcy9zaW5nbGVCYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImZpbGVFeHBsb3Jlck9wZW5TZWxlY3Rpb25GaW5kZXJBbGlhcyI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3NpbmdsZUJhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbERvd24iOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9i", + "YXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxVcCI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbEhhbGZQYWdlRG93biI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAg", + "ICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlclNjcm9sbEhhbGZQYWdlVXAiOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxEb3duRW1hY3MiOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxVcEVt", + "YWNzIjogewogICAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkaWZmVmlld2VyU2Nyb2xsVG9Cb3R0b20iOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRpZmZWaWV3ZXJTY3JvbGxUb1RvcCI6IHsKICAgICAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJz", + "dFN0cm9rZVNob3J0Y3V0QmluZGluZ051bGxhYmxlIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiZGlmZlZpZXdlck9wZW5GaWxlU2VhcmNoIjogewogICAgICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkaWZmVmlld2VyTmV4dEZpbGUiOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfSwKICAg", + "ICAgICAgICAgImRpZmZWaWV3ZXJQcmV2aW91c0ZpbGUiOiB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgICJhZGRpdGlvbmFsUHJvcGVydGllcyI6IHsKICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9zaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSIKICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgICJ3aGVuIjogewogICAgICAgICAgInR5cGUiOiAib2JqZWN0IiwKICAgICAg", + "ICAgICJkZWZhdWx0Ijoge30sCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiT3B0aW9uYWwgcGVyLWFjdGlvbiBjb250ZXh0IHByZWRpY2F0ZXMgKFZTIENvZGUtc3R5bGUgYHdoZW5gIGNsYXVzZXMpLCBrZXllZCBieSBjbXV4IGFjdGlvbiBpZC4gRWFjaCB2YWx1ZSBpcyBhIGJvb2xlYW4gZXhwcmVzc2lvbiBvdmVyIGNvbnRleHQga2V5cyBjb21iaW5lZCB3aXRoICEsICYmLCB8fCwgYW5kIHBhcmVudGhlc2VzLiBCb29sZWFuIGtleXM6IHNpZGViYXJGb2N1cywgYnJvd3NlckZvY3VzLCBtYXJrZG93bkZvY3VzLCBmaWxlUHJl", + "dmlld1RleHRFZGl0b3JGb2N1cywgc2ltdWxhdG9yRm9jdXMsIHRlcm1pbmFsRm9jdXMsIGNvbW1hbmRQYWxldHRlVmlzaWJsZSwgdGVybWluYWxGaW5kVmlzaWJsZSwgd29ya3NwYWNlQ2FudmFzTGF5b3V0LiBUeXBlZCBrZXlzIHN1cHBvcnQgY29tcGFyaXNvbnM6IHRoZSBzdHJpbmcgc2lkZWJhck1vZGUgKGZpbGVzLCBmaW5kLCBzZXNzaW9ucywgZmVlZCwgb3IgZG9jaykgYW5kIHRoZSBpbnRlZ2VycyBwYW5lQ291bnQgYW5kIHdvcmtzcGFjZUNvdW50LiBDb21wYXJpc29uIG9wZXJhdG9ycyBhcmUgPT0sICE9LCA9fiAocmVn", + "ZXgpLCA8LCA8PSwgPiwgPj0sIGFuZCBgaW4gW2EsIGJdYDsgYW4gdW5rbm93biBvciBhYnNlbnQga2V5IHJlYWRzIGFzIGZhbHNlLiBUaGUgYm9vbGVhbiBsaXRlcmFscyB0cnVlIGFuZCBmYWxzZSBhcmUgYWxzbyBhY2NlcHRlZDsgYGtleSA9PSBmYWxzZWAgaXMgdGhlIHNhbWUgYXMgYCFrZXlgLiBUaGUgYWN0aW9uJ3Mgc2hvcnRjdXQgb25seSBmaXJlcyAoYW5kIG9ubHkgY29uZmxpY3RzIHdpdGggb3RoZXIgc2hvcnRjdXRzKSB3aGVuIHRoZSBjbGF1c2UgaG9sZHMuIEV4YW1wbGVzOiB7IFwic2VsZWN0V29ya3NwYWNlQnlO", + "dW1iZXJcIjogXCIhc2lkZWJhckZvY3VzXCIgfSBzZWxlY3RzIHdvcmtzcGFjZXMgd2l0aCBDdHJsKzHigJM5IGV2ZXJ5d2hlcmUgZXhjZXB0IHdoZW4gdGhlIHJpZ2h0IHNpZGViYXIgaXMgZm9jdXNlZDsgeyBcInNlbGVjdFN1cmZhY2VCeU51bWJlclwiOiBcInNpZGViYXJNb2RlID09ICdmaW5kJyAmJiBwYW5lQ291bnQgPiAxXCIgfSBzY29wZXMgYSBiaW5kaW5nIHRvIHRoZSBGaW5kIHNpZGViYXIgd2hlbiB0aGUgd29ya3NwYWNlIGhhcyBtdWx0aXBsZSBwYW5lcy4iLAogICAgICAgICAgImRlc2NyaXB0aW9uS2V5IjogInNj", + "aGVtYURlc2NyaXB0aW9ucy5zaG9ydGN1dHMud2hlbiIsCiAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9LAogICIkZGVmcyI6IHsKICAgICJjb2xvckhleCI6IHsKICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgInBhdHRlcm4iOiAiXiNbMC05QS1GYS1mXXs2fSQiLAogICAgICAiZGVzY3JpcHRpb24iOiAiVXBwZXJjYXNlIG9yIGxvd2VyY2FzZSA2LWRpZ2l0IGhleCBjb2xvci4iCiAg", + "ICB9LAogICAgImNvbG9ySGV4T3JOdWxsIjogewogICAgICAib25lT2YiOiBbCiAgICAgICAgewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9jb2xvckhleCIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogIm51bGwiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgIm5vdGlmaWNhdGlvblNvdW5kT3ZlcnJpZGUiOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAidHlwZSI6ICJvYmplY3QiLAogICAgICAgICAgImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjogZmFsc2UsCiAgICAg", + "ICAgICAicmVxdWlyZWQiOiBbInNvdW5kIl0sCiAgICAgICAgICAicHJvcGVydGllcyI6IHsKICAgICAgICAgICAgInNvdW5kIjogewogICAgICAgICAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICAgICAgICAgImVudW0iOiBbImRlZmF1bHQiLCAiQmFzc28iLCAiQmxvdyIsICJCb3R0bGUiLCAiRnJvZyIsICJGdW5rIiwgIkdsYXNzIiwgIkhlcm8iLCAiTW9yc2UiLCAiUGluZyIsICJQb3AiLCAiUHVyciIsICJTb3N1bWkiLCAiU3VibWFyaW5lIiwgIlRpbmsiLCAibm9uZSJdCiAgICAgICAgICAgIH0KICAgICAgICAgIH0K", + "ICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogIm9iamVjdCIsCiAgICAgICAgICAiYWRkaXRpb25hbFByb3BlcnRpZXMiOiBmYWxzZSwKICAgICAgICAgICJyZXF1aXJlZCI6IFsic291bmQiLCAiY3VzdG9tU291bmRGaWxlUGF0aCJdLAogICAgICAgICAgInByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICJzb3VuZCI6IHsgImNvbnN0IjogImN1c3RvbV9maWxlIiB9LAogICAgICAgICAgICAiY3VzdG9tU291bmRGaWxlUGF0aCI6IHsKICAgICAgICAgICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAgICAg", + "ICAgICJtaW5MZW5ndGgiOiAxLAogICAgICAgICAgICAgICJwYXR0ZXJuIjogIi4qXFxTLioiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIF0sCiAgICAgICJkZXNjcmlwdGlvbktleSI6ICJzY2hlbWFEZXNjcmlwdGlvbnMubm90aWZpY2F0aW9ucy5zb3VuZE92ZXJyaWRlIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkEgbm90aWZpY2F0aW9uIHNvdW5kIG92ZXJyaWRlLiBgZGVmYXVsdGAgdXNlcyBub3RpZmljYXRpb25zLnNvdW5kLCBgbm9uZWAgZGlzYWJsZXMgcGxheWJhY2ssIGFuZCB0aGUgcmVt", + "YWluaW5nIHZhbHVlcyBzZWxlY3QgYSBidWlsdC1pbiBvciBjdXN0b20gc291bmQuIgogICAgfSwKICAgICJzaG9ydGN1dEJpbmRpbmciOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3VuYm91bmRTaG9ydGN1dEJpbmRpbmciLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlVuYmluZCB0aGlzIHNob3J0Y3V0LiBBY2NlcHRlZCB2YWx1ZXMgYXJlIGFuIGVtcHR5IHN0cmluZywgbm9uZSwgY2xlYXIsIHVuYm91bmQsIG9yIGRpc2FibGVkLiIKICAgICAgICB9LAogICAgICAg", + "IHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvc2hvcnRjdXRGaXJzdFN0cm9rZSIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiU2luZ2xlLXN0cm9rZSBzaG9ydGN1dCwgZm9yIGV4YW1wbGUgY21kK24gb3IgY21kK3NwYWNlLiIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICJtaW5JdGVtcyI6IDEsCiAgICAgICAgICAibWF4SXRlbXMiOiAyLAogICAgICAgICAgInByZWZpeEl0ZW1zIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8k", + "ZGVmcy9zaG9ydGN1dEZpcnN0U3Ryb2tlIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9zaG9ydGN1dFN0cm9rZSIKICAgICAgICAgICAgfQogICAgICAgICAgXSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDaG9yZGVkIHNob3J0Y3V0LiBFeGFtcGxlOiBbXCJjdHJsK2JcIiwgXCJjXCJdLiIKICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICAic2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAi", + "JHJlZiI6ICIjLyRkZWZzL3Nob3J0Y3V0QmluZGluZyIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogIm51bGwiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlVuYmluZCB0aGlzIHNob3J0Y3V0LiIKICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICAic2luZ2xlQmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nIjogewogICAgICAib25lT2YiOiBbCiAgICAgICAgewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy91bmJvdW5kU2hvcnRjdXRCaW5kaW5nIiwKICAgICAgICAgICJkZXNjcmlwdGlv", + "biI6ICJVbmJpbmQgdGhpcyBzaG9ydGN1dC4gQWNjZXB0ZWQgdmFsdWVzIGFyZSBhbiBlbXB0eSBzdHJpbmcsIG5vbmUsIGNsZWFyLCB1bmJvdW5kLCBvciBkaXNhYmxlZC4iCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0Rmlyc3RTdHJva2UiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNpbmdsZS1zdHJva2Ugc2hvcnRjdXQuIEJhcmUgZmlyc3Qgc3Ryb2tlcyBhcmUgYWNjZXB0ZWQgZm9yIHRoaXMgYWN0aW9uLiIKICAgICAgICB9CiAgICAg", + "IF0KICAgIH0sCiAgICAic2luZ2xlQmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nTnVsbGFibGUiOiB7CiAgICAgICJvbmVPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3NpbmdsZUJhcmVGaXJzdFN0cm9rZVNob3J0Y3V0QmluZGluZyIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogIm51bGwiLAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlVuYmluZCB0aGlzIHNob3J0Y3V0LiIKICAgICAgICB9CiAgICAgIF0KICAgIH0sCiAgICAiYmFyZUZpcnN0U3Ryb2tlU2hv", + "cnRjdXRCaW5kaW5nIjogewogICAgICAib25lT2YiOiBbCiAgICAgICAgewogICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy91bmJvdW5kU2hvcnRjdXRCaW5kaW5nIiwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJVbmJpbmQgdGhpcyBzaG9ydGN1dC4gQWNjZXB0ZWQgdmFsdWVzIGFyZSBhbiBlbXB0eSBzdHJpbmcsIG5vbmUsIGNsZWFyLCB1bmJvdW5kLCBvciBkaXNhYmxlZC4iCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL2JhcmVGaXJzdFN0cm9rZVNob3J0Y3V0Rmlyc3RTdHJva2Ui", + "LAogICAgICAgICAgImRlc2NyaXB0aW9uIjogIlNpbmdsZS1zdHJva2Ugc2hvcnRjdXQuIEJhcmUgZmlyc3Qgc3Ryb2tlcyBhcmUgYWNjZXB0ZWQgZm9yIHRoaXMgYWN0aW9uLiIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJ0eXBlIjogImFycmF5IiwKICAgICAgICAgICJtaW5JdGVtcyI6IDEsCiAgICAgICAgICAibWF4SXRlbXMiOiAyLAogICAgICAgICAgInByZWZpeEl0ZW1zIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9iYXJlRmlyc3RTdHJva2VTaG9ydGN1dEZpcnN0", + "U3Ryb2tlIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIiRyZWYiOiAiIy8kZGVmcy9zaG9ydGN1dFN0cm9rZSIKICAgICAgICAgICAgfQogICAgICAgICAgXSwKICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICJDaG9yZGVkIHNob3J0Y3V0LiBCYXJlIGZpcnN0IHN0cm9rZXMgYXJlIGFjY2VwdGVkIGZvciB0aGlzIGFjdGlvbi4gRXhhbXBsZTogW1wiZ1wiLCBcImdcIl0uIgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJiYXJlRmlyc3RTdHJva2VTaG9ydGN1dEJpbmRpbmdOdWxsYWJsZSI6", + "IHsKICAgICAgIm9uZU9mIjogWwogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRCaW5kaW5nIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAibnVsbCIsCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiVW5iaW5kIHRoaXMgc2hvcnRjdXQuIgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJ1bmJvdW5kU2hvcnRjdXRCaW5kaW5nIjogewogICAgICAidHlwZSI6ICJzdHJpbmciLAogICAgICAiZW51bSI6IFsiIiwgIm5vbmUiLCAiY2xlYXIi", + "LCAidW5ib3VuZCIsICJkaXNhYmxlZCJdCiAgICB9LAogICAgInNob3J0Y3V0Rmlyc3RTdHJva2UiOiB7CiAgICAgICJhbGxPZiI6IFsKICAgICAgICB7CiAgICAgICAgICAiJHJlZiI6ICIjLyRkZWZzL3Nob3J0Y3V0U3Ryb2tlIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgImFueU9mIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInBhdHRlcm4iOiAiXig/Oig/OltjQ11bbU1dW2REXXxbY0NdW29PXVttTV1bbU1dW2FBXVtuTl1bZERdfFtzU11baEhdW2lJXVtmRl1bdFRdfFtvT11bcFBdW3RUXXxbb09d", + "W3BQXVt0VF1baUldW29PXVtuTl18W2FBXVtsTF1bdFRdfFtjQ11bdFRdW3JSXVtsTF18W2NDXVtvT11bbk5dW3RUXVtyUl1bb09dW2xMXXxbY0NdW3RUXVtsTF184oyYfOKHp3zijKV84oyDKVxcKykrIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInBhdHRlcm4iOiAiXig/OiB8W3NTXVtwUF1bYUFdW2NDXVtlRV18W3NTXVtwUF1bYUFdW2NDXVtlRV1bYkJdW2FBXVtyUl18PFtzU11bcFBdW2FBXVtjQ11bZUVdPikkIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfQogICAgICBd", + "LAogICAgICAiZGVzY3JpcHRpb24iOiAiRmlyc3Qgb3Igb25seSBzaG9ydGN1dCBzdHJva2UuIE11c3QgaW5jbHVkZSBhIG1vZGlmaWVyIHVubGVzcyB0aGUga2V5IGlzIFNwYWNlLiIKICAgIH0sCiAgICAiYmFyZUZpcnN0U3Ryb2tlU2hvcnRjdXRGaXJzdFN0cm9rZSI6IHsKICAgICAgImFsbE9mIjogWwogICAgICAgIHsKICAgICAgICAgICIkcmVmIjogIiMvJGRlZnMvc2hvcnRjdXRTdHJva2UiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAiYW55T2YiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAicGF0", + "dGVybiI6ICJeKD86KD86W2NDXVttTV1bZERdfFtjQ11bb09dW21NXVttTV1bYUFdW25OXVtkRF18W3NTXVtoSF1baUldW2ZGXVt0VF18W29PXVtwUF1bdFRdfFtvT11bcFBdW3RUXVtpSV1bb09dW25OXXxbYUFdW2xMXVt0VF18W2NDXVt0VF1bclJdW2xMXXxbY0NdW29PXVtuTl1bdFRdW3JSXVtvT11bbExdfFtjQ11bdFRdW2xMXXzijJh84oenfOKMpXzijIMpXFwrKSsiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAicGF0dGVybiI6ICJeKD86IHxbc1NdW3BQXVthQV1bY0NdW2VFXXxbc1NdW3BQ", + "XVthQV1bY0NdW2VFXVtiQl1bYUFdW3JSXXw8W3NTXVtwUF1bYUFdW2NDXVtlRV0+KSQiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAicGF0dGVybiI6ICJeKD86W3JSXVtlRV1bdFRdW3VVXVtyUl1bbk5dfFtlRV1bbk5dW3RUXVtlRV1bclJdfOKGqSkkIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgInBhdHRlcm4iOiAiXig/OltsTF1bZUVdW2ZGXVt0VF18W2FBXVtyUl1bclJdW29PXVt3V11bbExdW2VFXVtmRl1bdFRdfFtsTF1bZUVdW2ZGXVt0VF1bYUFdW3JS", + "XVtyUl1bb09dW3dXXXzihpB8W3JSXVtpSV1bZ0ddW2hIXVt0VF18W2FBXVtyUl1bclJdW29PXVt3V11bclJdW2lJXVtnR11baEhdW3RUXXxbclJdW2lJXVtnR11baEhdW3RUXVthQV1bclJdW3JSXVtvT11bd1ddfOKGknxbdVVdW3BQXXxbYUFdW3JSXVtyUl1bb09dW3dXXVt1VV1bcFBdfFt1VV1bcFBdW2FBXVtyUl1bclJdW29PXVt3V1184oaRfFtkRF1bb09dW3dXXVtuTl18W2FBXVtyUl1bclJdW29PXVt3V11bZERdW29PXVt3V11bbk5dfFtkRF1bb09dW3dXXVtuTl1bYUFdW3JSXVtyUl1bb09dW3dXXXzihpMpJCIKICAgICAg", + "ICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJwYXR0ZXJuIjogIl4oPzpbQS1aYS16MC05XXxbLC4vXFxcXDsnYD1cXFtcXF0tXXxbY0NdW29PXVttTV1bbU1dW2FBXXxbcFBdW2VFXVtyUl1baUldW29PXVtkRF18W2REXVtvT11bdFRdfFtzU11bbExdW2FBXVtzU11baEhdfFtiQl1bYUFdW2NDXVtrS11bc1NdW2xMXVthQV1bc1NdW2hIXXxbc1NdW2VFXVttTV1baUldW2NDXVtvT11bbExdW29PXVtuTl18W3FRXVt1VV1bb09dW3RUXVtlRV18W2FBXVtwUF1bb09dW3NTXVt0VF1bclJdW29PXVtwUF1baEhdW2VF", + "XXxbYkJdW2FBXVtjQ11ba0tdW3RUXVtpSV1bY0NdW2tLXXxbZ0ddW3JSXVthQV1bdlZdW2VFXXxbbU1dW2lJXVtuTl1bdVVdW3NTXXxbaEhdW3lZXVtwUF1baEhdW2VFXVtuTl18W3BQXVtsTF1bdVVdW3NTXXxbZUVdW3FRXVt1VV1bYUFdW2xMXVtzU118W2xMXVtlRV1bZkZdW3RUXVtiQl1bclJdW2FBXVtjQ11ba0tdW2VFXVt0VF18W29PXVtwUF1bZUVdW25OXVtiQl1bclJdW2FBXVtjQ11ba0tdW2VFXVt0VF18W3JSXVtpSV1bZ0ddW2hIXVt0VF1bYkJdW3JSXVthQV1bY0NdW2tLXVtlRV1bdFRdfFtjQ11bbExdW29PXVtzU11b", + "ZUVdW2JCXVtyUl1bYUFdW2NDXVtrS11bZUVdW3RUXSkkIgogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfQogICAgICBdLAogICAgICAiZGVzY3JpcHRpb24iOiAiRmlyc3Qgb3Igb25seSBzaG9ydGN1dCBzdHJva2UuIEJhcmUgcHJpbnRhYmxlIGtleXMgYXJlIGFjY2VwdGVkIGZvciBhY3Rpb25zIHRoYXQgb3B0IGludG8gYmFyZSBmaXJzdCBzdHJva2VzLiIKICAgIH0sCiAgICAic2hvcnRjdXRTdHJva2UiOiB7CiAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICJwYXR0ZXJuIjogIl4oPzooPzpbY0NdW21N", + "XVtkRF18W2NDXVtvT11bbU1dW21NXVthQV1bbk5dW2REXXxbc1NdW2hIXVtpSV1bZkZdW3RUXXxbb09dW3BQXVt0VF18W29PXVtwUF1bdFRdW2lJXVtvT11bbk5dfFthQV1bbExdW3RUXXxbY0NdW3RUXVtyUl1bbExdfFtjQ11bb09dW25OXVt0VF1bclJdW29PXVtsTF18W2NDXVt0VF1bbExdfOKMmHzih6d84oylfOKMgylcXCspKig/OiB8W0EtWmEtejAtOV18W2xMXVtlRV1bZkZdW3RUXXxbYUFdW3JSXVtyUl1bb09dW3dXXVtsTF1bZUVdW2ZGXVt0VF18W2xMXVtlRV1bZkZdW3RUXVthQV1bclJdW3JSXVtvT11bd1ddfOKGkHxb", + "clJdW2lJXVtnR11baEhdW3RUXXxbYUFdW3JSXVtyUl1bb09dW3dXXVtyUl1baUldW2dHXVtoSF1bdFRdfFtyUl1baUldW2dHXVtoSF1bdFRdW2FBXVtyUl1bclJdW29PXVt3V1184oaSfFt1VV1bcFBdfFthQV1bclJdW3JSXVtvT11bd1ddW3VVXVtwUF18W3VVXVtwUF1bYUFdW3JSXVtyUl1bb09dW3dXXXzihpF8W2REXVtvT11bd1ddW25OXXxbYUFdW3JSXVtyUl1bb09dW3dXXVtkRF1bb09dW3dXXVtuTl18W2REXVtvT11bd1ddW25OXVthQV1bclJdW3JSXVtvT11bd1ddfOKGk3xbdFRdW2FBXVtiQl18W3JSXVtlRV1bdFRdW3VV", + "XVtyUl1bbk5dfFtlRV1bbk5dW3RUXVtlRV1bclJdfOKGqXxbc1NdW3BQXVthQV1bY0NdW2VFXXxbc1NdW3BQXVthQV1bY0NdW2VFXVtiQl1bYUFdW3JSXXw8W3NTXVtwUF1bYUFdW2NDXVtlRV0+fFssLi9cXFxcOydgPVxcW1xcXS1dfFtjQ11bb09dW21NXVttTV1bYUFdfFtwUF1bZUVdW3JSXVtpSV1bb09dW2REXXxbZERdW29PXVt0VF18W3NTXVtsTF1bYUFdW3NTXVtoSF18W2JCXVthQV1bY0NdW2tLXVtzU11bbExdW2FBXVtzU11baEhdfFtzU11bZUVdW21NXVtpSV1bY0NdW29PXVtsTF1bb09dW25OXXxbcVFdW3VVXVtvT11b", + "dFRdW2VFXXxbYUFdW3BQXVtvT11bc1NdW3RUXVtyUl1bb09dW3BQXVtoSF1bZUVdfFtiQl1bYUFdW2NDXVtrS11bdFRdW2lJXVtjQ11ba0tdfFtnR11bclJdW2FBXVt2Vl1bZUVdfFttTV1baUldW25OXVt1VV1bc1NdfFtoSF1beVldW3BQXVtoSF1bZUVdW25OXXxbcFBdW2xMXVt1VV1bc1NdfFtlRV1bcVFdW3VVXVthQV1bbExdW3NTXXxbbExdW2VFXVtmRl1bdFRdW2JCXVtyUl1bYUFdW2NDXVtrS11bZUVdW3RUXXxbb09dW3BQXVtlRV1bbk5dW2JCXVtyUl1bYUFdW2NDXVtrS11bZUVdW3RUXXxbclJdW2lJXVtnR11baEhdW3RU", + "XVtiQl1bclJdW2FBXVtjQ11ba0tdW2VFXVt0VF18W2NDXVtsTF1bb09dW3NTXVtlRV1bYkJdW3JSXVthQV1bY0NdW2tLXVtlRV1bdFRdfFtmRl0oPzpbMS05XXwxWzAtOV18MjApfFt2Vl1bb09dW2xMXVt1VV1bbU1dW2VFXVt1VV1bcFBdfFttTV1bZUVdW2REXVtpSV1bYUFdW3ZWXVtvT11bbExdW3VVXVttTV1bZUVdW3VVXVtwUF18W21NXVtlRV1bZERdW2lJXVthQV1cXC5bdlZdW29PXVtsTF1bdVVdW21NXVtlRV1bdVVdW3BQXXxbdlZdW29PXVtsTF1bdVVdW21NXVtlRV1bZERdW29PXVt3V11bbk5dfFttTV1bZUVdW2REXVtp", + "SV1bYUFdW3ZWXVtvT11bbExdW3VVXVttTV1bZUVdW2REXVtvT11bd1ddW25OXXxbbU1dW2VFXVtkRF1baUldW2FBXVxcLlt2Vl1bb09dW2xMXVt1VV1bbU1dW2VFXVtkRF1bb09dW3dXXVtuTl18W2JCXVtyUl1baUldW2dHXVtoSF1bdFRdW25OXVtlRV1bc1NdW3NTXVt1VV1bcFBdfFttTV1bZUVdW2REXVtpSV1bYUFdW2JCXVtyUl1baUldW2dHXVtoSF1bdFRdW25OXVtlRV1bc1NdW3NTXVt1VV1bcFBdfFttTV1bZUVdW2REXVtpSV1bYUFdXFwuW2JCXVtyUl1baUldW2dHXVtoSF1bdFRdW25OXVtlRV1bc1NdW3NTXVt1VV1bcFBd", + "fFtiQl1bclJdW2lJXVtnR11baEhdW3RUXVtuTl1bZUVdW3NTXVtzU11bZERdW29PXVt3V11bbk5dfFttTV1bZUVdW2REXVtpSV1bYUFdW2JCXVtyUl1baUldW2dHXVtoSF1bdFRdW25OXVtlRV1bc1NdW3NTXVtkRF1bb09dW3dXXVtuTl18W21NXVtlRV1bZERdW2lJXVthQV1cXC5bYkJdW3JSXVtpSV1bZ0ddW2hIXVt0VF1bbk5dW2VFXVtzU11bc1NdW2REXVtvT11bd1ddW25OXXxbbU1dW3VVXVt0VF1bZUVdfFttTV1bZUVdW2REXVtpSV1bYUFdW21NXVt1VV1bdFRdW2VFXXxbbU1dW2VFXVtkRF1baUldW2FBXVxcLlttTV1bdVVd", + "W3RUXVtlRV18W3BQXVtsTF1bYUFdW3lZXVtwUF1bYUFdW3VVXVtzU11bZUVdfFttTV1bZUVdW2REXVtpSV1bYUFdW3BQXVtsTF1bYUFdW3lZXVtwUF1bYUFdW3VVXVtzU11bZUVdfFttTV1bZUVdW2REXVtpSV1bYUFdXFwuW3BQXVtsTF1bYUFdW3lZXVtwUF1bYUFdW3VVXVtzU11bZUVdfFtuTl1bZUVdW3hYXVt0VF1bdFRdW3JSXVthQV1bY0NdW2tLXXxbbU1dW2VFXVtkRF1baUldW2FBXVtuTl1bZUVdW3hYXVt0VF18W21NXVtlRV1bZERdW2lJXVthQV1cXC5bbk5dW2VFXVt4WF1bdFRdfFttTV1bZUVdW2REXVtpSV1bYUFdXFwu", + "W25OXVtlRV1beFhdW3RUXVt0VF1bclJdW2FBXVtjQ11ba0tdfFtwUF1bclJdW2VFXVt2Vl1baUldW29PXVt1VV1bc1NdW3RUXVtyUl1bYUFdW2NDXVtrS118W21NXVtlRV1bZERdW2lJXVthQV1bcFBdW3JSXVtlRV1bdlZdW2lJXVtvT11bdVVdW3NTXXxbbU1dW2VFXVtkRF1baUldW2FBXVxcLltwUF1bclJdW2VFXVt2Vl1baUldW29PXVt1VV1bc1NdfFttTV1bZUVdW2REXVtpSV1bYUFdXFwuW3BQXVtyUl1bZUVdW3ZWXVtpSV1bb09dW3VVXVtzU11bdFRdW3JSXVthQV1bY0NdW2tLXSkkIiwKICAgICAgImRlc2NyaXB0aW9uIjog", + "Ik9uZSBrZXlib2FyZCBzaG9ydGN1dCBzdHJva2UgdXNpbmcgbW9kaWZpZXIra2V5IHN5bnRheC4gU3VwcG9ydGVkIGtleSBuYW1lcyBpbmNsdWRlIHNwYWNlLCBTcGFjZSwgPHNwYWNlPiwgYW5kIDxTcGFjZT4uIgogICAgfQogIH0KfQo=", ].joined() guard let data = Data(base64Encoded: base64) else { preconditionFailure("embedded cmux.json schema is corrupt") diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index 56c7921bcad7..a93f4f11daff 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -26,6 +26,13 @@ public struct NotificationsCatalogSection: SettingCatalogSection { userDefaultsKey: "notificationPaneFlashEnabled" ) + /// Blink the pane flash twice instead of one short pulse. + public let paneFlashDoubleBlink = DefaultsKey( + id: "notifications.paneFlashDoubleBlink", + defaultValue: false, + userDefaultsKey: "notificationPaneFlashDoubleBlink" + ) + /// Stroke color of the attention ring and pane flash, as a `#RRGGBB` hex. /// Empty (the default) keeps the built-in `systemBlue`. public let paneFlashColorHex = DefaultsKey( diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift index 55f6f5e465cb..d6f9b6a5bfdb 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift @@ -114,6 +114,7 @@ extension Array where Element == CuratedSettingEntry { .init(section: .app, id: "show-menu-bar", title: String(localized: "settings.app.showInMenuBar", defaultValue: "Show in Menu Bar"), synonyms: "Show in Menu Bar notifications.showInMenuBar menubar menu bar status item tray extra"), .init(section: .app, id: "unread-pane-ring", title: String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), synonyms: "Unread Pane Ring notifications.unreadPaneRing blue border unread ring notification pane outline"), .init(section: .app, id: "pane-flash", title: String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), synonyms: "Pane Flash notifications.paneFlash flash blink highlight pane notification pulse"), + .init(section: .app, id: "pane-flash-double-blink", title: String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), synonyms: "Double Blink notifications.paneFlashDoubleBlink pane flash blink twice pulse"), .init( section: .app, id: "agent-permission-prompt", diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift index 132a6ffc1b1a..cba9c83482ce 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift @@ -55,6 +55,7 @@ public struct AppSection: View { @State private var showInMenuBar: DefaultsValueModel @State private var paneRing: DefaultsValueModel @State private var paneFlash: DefaultsValueModel + @State private var paneFlashDoubleBlink: DefaultsValueModel @State private var desktopNotifications: DesktopNotificationAuthorizationModel @State private var agentPermissionPrompt: DefaultsValueModel @State private var agentTurnComplete: DefaultsValueModel @@ -121,6 +122,7 @@ public struct AppSection: View { _showInMenuBar = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.showInMenuBar)) _paneRing = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.unreadPaneRing)) _paneFlash = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlash)) + _paneFlashDoubleBlink = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashDoubleBlink)) _desktopNotifications = State(initialValue: DesktopNotificationAuthorizationModel(hostActions: hostActions)) _agentPermissionPrompt = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentPermissionPrompt)) _agentTurnComplete = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentTurnComplete)) @@ -160,7 +162,7 @@ public struct AppSection: View { mainCard } .task { - startSettingsObservation([language, appearance, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, hideCloseButton, renameSelects, paletteAllSurfaces]) + startSettingsObservation([language, appearance, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, hideCloseButton, renameSelects, paletteAllSurfaces]) if soundAgents.isEmpty { soundAgents = await hostActions.notificationSoundAgentOptions() } @@ -722,6 +724,19 @@ public struct AppSection: View { } SettingsCardDivider() + // Pane Flash: Double Blink + SettingsCardRow( + configurationReview: .json("notifications.paneFlashDoubleBlink"), + String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), + subtitle: String(localized: "settings.notifications.paneFlashDoubleBlink.subtitle", defaultValue: "Blink the pane flash twice instead of one short pulse.") + ) { + Toggle("", isOn: Binding(get: { paneFlashDoubleBlink.current }, set: { paneFlashDoubleBlink.set($0) })) + .labelsHidden() + .controlSize(.small) + .disabled(!paneFlash.current) + } + SettingsCardDivider() + // Agent: Needs Permission SettingsCardRow( configurationReview: .json("notifications.agentPermissionPrompt"), diff --git a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift index 95b4c485324f..395e4415275d 100644 --- a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift +++ b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift @@ -98,6 +98,7 @@ struct SettingsRowAnchorResolutionTests { "notifications.command", "notifications.dockBadge", "notifications.paneFlash", + "notifications.paneFlashDoubleBlink", "notifications.paneFlashColor", "notifications.showInMenuBar", "notifications.sound", diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index b6c62fd46106..5facce036715 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -343893,6 +343893,201 @@ } } }, + "settings.notifications.paneFlashDoubleBlink.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض مزدوج" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Doppeltes Blinken" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Parpadeo doble" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Double clignotement" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "2回点滅" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "두 번 깜빡임" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Подвійне блимання" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "闪烁两次" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "閃爍兩次" + } + } + } + }, + "settings.notifications.paneFlashDoubleBlink.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض الجزء مرتين بدلاً من نبضة قصيرة واحدة." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Den Bereich zweimal aufblinken lassen statt eines kurzen Pulses." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Hacer parpadear el panel dos veces en lugar de un pulso breve." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Faire clignoter le volet deux fois au lieu d’une brève pulsation." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペインの点滅を短い1回ではなく2回にします。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패인 깜빡임을 짧은 한 번 대신 두 번 표시합니다." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Блимати панеллю двічі замість одного короткого імпульсу." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗格闪烁两次,而不是一次短暂的脉冲。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "窗格閃爍兩次,而不是一次短暫的脈衝。" + } + } + } + }, + "settings.search.alias.setting.app.pane-flash-double-blink": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse وميض مزدوج" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse doppeltes blinken" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse parpadeo doble" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse double clignotement" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 2回点滅" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 두 번 깜빡임" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse подвійне блимання" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 闪烁两次" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 閃爍兩次" + } + } + } + }, "settings.notifications.paneFlash.title": { "extractionState": "manual", "localizations": { diff --git a/Sources/CmuxSettingsFileStore+SupportedPaths.swift b/Sources/CmuxSettingsFileStore+SupportedPaths.swift index 9969c49e2218..a84b41c7c1ac 100644 --- a/Sources/CmuxSettingsFileStore+SupportedPaths.swift +++ b/Sources/CmuxSettingsFileStore+SupportedPaths.swift @@ -64,6 +64,7 @@ extension CmuxSettingsFileStore { "notifications.showInMenuBar", "notifications.unreadPaneRing", "notifications.paneFlash", + "notifications.paneFlashDoubleBlink", "notifications.paneFlashColor", "notifications.sound", "notifications.customSoundFilePath", diff --git a/Sources/CmuxSettingsJSONPathSupport.swift b/Sources/CmuxSettingsJSONPathSupport.swift index a014d3751c2e..3c23671751cf 100644 --- a/Sources/CmuxSettingsJSONPathSupport.swift +++ b/Sources/CmuxSettingsJSONPathSupport.swift @@ -217,6 +217,7 @@ enum NotificationSettingsFileMapping { .init(jsonKey: "showInMenuBar", defaultsKey: MenuBarExtraSettings.showInMenuBarKey), .init(jsonKey: "unreadPaneRing", defaultsKey: NotificationPaneRingSettings.enabledKey), .init(jsonKey: "paneFlash", defaultsKey: NotificationPaneFlashSettings.enabledKey), + .init(jsonKey: "paneFlashDoubleBlink", defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey), .init( jsonKey: "suppressOnlyFocusedSurface", defaultsKey: notifications.suppressOnlyFocusedSurface.userDefaultsKey diff --git a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift index 193417f4e9f7..92fd2f3ab1a5 100644 --- a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift +++ b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift @@ -404,6 +404,17 @@ enum CommandPaletteSettingsToggleCommands { defaultValue: NotificationPaneFlashSettings.defaultEnabled, defaultsKey: NotificationPaneFlashSettings.enabledKey ), + CommandPaletteSettingToggleDescriptor( + commandId: commandIdPrefix + "paneFlashDoubleBlink", + settingsKey: "notifications.paneFlashDoubleBlink", + title: { + String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink") + }, + sectionTitle: app, + keywords: ["notifications.paneFlashDoubleBlink", "pane", "flash", "double", "blink", "pulse"], + defaultValue: NotificationPaneFlashSettings.defaultDoubleBlink, + defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey + ), CommandPaletteSettingToggleDescriptor( commandId: commandIdPrefix + "sendAnonymousTelemetry", settingsKey: "app.sendAnonymousTelemetry", diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 336b3e68295b..b15b01247117 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -11933,11 +11933,12 @@ final class GhosttySurfaceScrollView: NSView { updateFlashAppearance(style: style) flashLayer.removeAllAnimations() flashLayer.opacity = 0 + let pattern = FocusFlashPattern.current let animation = CAKeyframeAnimation(keyPath: "opacity") - animation.values = FocusFlashPattern.values.map { NSNumber(value: $0) } - animation.keyTimes = FocusFlashPattern.keyTimes.map { NSNumber(value: $0) } - animation.duration = FocusFlashPattern.duration - animation.timingFunctions = FocusFlashPattern.curves.map { curve in + animation.values = pattern.values.map { NSNumber(value: $0) } + animation.keyTimes = pattern.keyTimes.map { NSNumber(value: $0) } + animation.duration = pattern.duration + animation.timingFunctions = pattern.curves.map { curve in switch curve { case .easeIn: return CAMediaTimingFunction(name: .easeIn) diff --git a/Sources/KeyboardShortcutSettingsFileStore+Template.swift b/Sources/KeyboardShortcutSettingsFileStore+Template.swift index ed08e8499123..7b0ef84f67b1 100644 --- a/Sources/KeyboardShortcutSettingsFileStore+Template.swift +++ b/Sources/KeyboardShortcutSettingsFileStore+Template.swift @@ -126,6 +126,7 @@ extension CmuxSettingsFileStore { "showInMenuBar": MenuBarExtraSettings.defaultShowInMenuBar, "unreadPaneRing": NotificationPaneRingSettings.defaultEnabled, "paneFlash": NotificationPaneFlashSettings.defaultEnabled, + "paneFlashDoubleBlink": NotificationPaneFlashSettings.defaultDoubleBlink, "paneFlashColor": NSNull(), "sound": NotificationSoundSettings.defaultValue, "customSoundFilePath": NotificationSoundSettings.defaultCustomFilePath, diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 3dcee7fe4e2f..93a0c233881b 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -1953,9 +1953,9 @@ struct BrowserPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/Panels/CustomSidebarPanelView.swift b/Sources/Panels/CustomSidebarPanelView.swift index c8d402ef7231..a4ad2ed249f8 100644 --- a/Sources/Panels/CustomSidebarPanelView.swift +++ b/Sources/Panels/CustomSidebarPanelView.swift @@ -147,10 +147,10 @@ struct CustomSidebarPanelView: View { if shouldAnimateFocusFlash, let focusFlashStartedAt { TimelineView(TmuxWorkspacePaneFlashTimelineSchedule(startDate: focusFlashStartedAt)) { timeline in WorkspaceAttentionFlashRingView( - opacity: FocusFlashPattern.opacity(at: timeline.date.timeIntervalSince(focusFlashStartedAt)) + opacity: FocusFlashPattern.current.opacity(at: timeline.date.timeIntervalSince(focusFlashStartedAt)) ) .onChange(of: timeline.date) { _, date in - if date.timeIntervalSince(focusFlashStartedAt) >= FocusFlashPattern.duration { + if date.timeIntervalSince(focusFlashStartedAt) >= FocusFlashPattern.current.duration { completedFocusFlashStartedAt = focusFlashStartedAt } } @@ -163,6 +163,6 @@ struct CustomSidebarPanelView: View { private var shouldAnimateFocusFlash: Bool { guard let focusFlashStartedAt else { return false } guard completedFocusFlashStartedAt != focusFlashStartedAt else { return false } - return Date() <= focusFlashStartedAt.addingTimeInterval(FocusFlashPattern.duration) + return Date() <= focusFlashStartedAt.addingTimeInterval(FocusFlashPattern.current.duration) } } diff --git a/Sources/Panels/FilePreviewPanel.swift b/Sources/Panels/FilePreviewPanel.swift index 1f5923558425..3c160512f067 100644 --- a/Sources/Panels/FilePreviewPanel.swift +++ b/Sources/Panels/FilePreviewPanel.swift @@ -1879,9 +1879,9 @@ struct FilePreviewPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/Panels/MarkdownPanelView.swift b/Sources/Panels/MarkdownPanelView.swift index 831aeac29b7c..ea72e2a3b0ca 100644 --- a/Sources/Panels/MarkdownPanelView.swift +++ b/Sources/Panels/MarkdownPanelView.swift @@ -270,9 +270,9 @@ struct MarkdownPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index eb5516515288..988f35f084b4 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -246,17 +246,37 @@ struct FocusFlashSegment: Equatable { let curve: FocusFlashCurve } -/// One short pulse: enough to say where focus or attention landed without -/// replaying a blink every time someone moves between panes. -enum FocusFlashPattern { - static let values: [Double] = [0, 1, 0] - static let keyTimes: [Double] = [0, 0.3, 1] - static let duration: TimeInterval = 0.4 - static let curves: [FocusFlashCurve] = [.easeOut, .easeIn] +/// The attention flash shape. One short pulse by default: enough to say where +/// focus or attention landed without replaying a blink on every move between +/// panes. `notifications.paneFlashDoubleBlink` restores the older double blink. +struct FocusFlashPattern: Equatable { + let values: [Double] + let keyTimes: [Double] + let duration: TimeInterval + let curves: [FocusFlashCurve] + + static let pulse = FocusFlashPattern( + values: [0, 1, 0], + keyTimes: [0, 0.3, 1], + duration: 0.4, + curves: [.easeOut, .easeIn] + ) + static let doubleBlink = FocusFlashPattern( + values: [0, 1, 0, 1, 0], + keyTimes: [0, 0.25, 0.5, 0.75, 1], + duration: 0.9, + curves: [.easeOut, .easeIn, .easeOut, .easeIn] + ) + + /// The shape the user has chosen, read when a flash starts. + static var current: FocusFlashPattern { + NotificationPaneFlashSettings.usesDoubleBlink() ? doubleBlink : pulse + } + static let ringInset: Double = Double(PanelOverlayRingMetrics.inset) static let ringCornerRadius: Double = Double(PanelOverlayRingMetrics.cornerRadius) - static var segments: [FocusFlashSegment] { + var segments: [FocusFlashSegment] { let stepCount = min(curves.count, values.count - 1, keyTimes.count - 1) return (0.. Double { + func opacity(at elapsed: TimeInterval) -> Double { guard elapsed >= 0, elapsed <= duration else { return 0 } - for index in 0.. endTime { @@ -282,7 +302,7 @@ enum FocusFlashPattern { let segmentDuration = max(endTime - startTime, 0.0001) let rawProgress = max(0, min(1, (elapsed - startTime) / segmentDuration)) - let curvedProgress = interpolatedProgress(rawProgress, curve: curves[index]) + let curvedProgress = Self.interpolatedProgress(rawProgress, curve: curves[index]) let startOpacity = values[index] let endOpacity = values[index + 1] return startOpacity + ((endOpacity - startOpacity) * curvedProgress) diff --git a/Sources/Panels/TerminalPanel.swift b/Sources/Panels/TerminalPanel.swift index ff3476ee1149..a72cbba3aed3 100644 --- a/Sources/Panels/TerminalPanel.swift +++ b/Sources/Panels/TerminalPanel.swift @@ -788,7 +788,7 @@ final class TerminalPanel: Panel, ObservableObject { now < attentionFlashActiveUntil { return } - attentionFlashActiveUntil = now + FocusFlashPattern.duration + attentionFlashActiveUntil = now + FocusFlashPattern.current.duration switch TmuxOverlayExperimentSettings.target() { case .bonsplitPane: diff --git a/Sources/RightSidebarToolPanel.swift b/Sources/RightSidebarToolPanel.swift index 837acda0b347..3867ca9665c3 100644 --- a/Sources/RightSidebarToolPanel.swift +++ b/Sources/RightSidebarToolPanel.swift @@ -287,9 +287,9 @@ struct RightSidebarToolPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/SettingsSearchAliases.swift b/Sources/SettingsSearchAliases.swift index ddf5ac446469..d09371c59370 100644 --- a/Sources/SettingsSearchAliases.swift +++ b/Sources/SettingsSearchAliases.swift @@ -90,6 +90,7 @@ enum SettingsSearchAliasIndex { "app:show-menu-bar": localized("settings.search.alias.setting.app.show-menu-bar", defaultValue: "notifications.showInMenuBar menubar menu bar status item tray extra"), "app:unread-pane-ring": localized("settings.search.alias.setting.app.unread-pane-ring", defaultValue: "notifications.unreadPaneRing blue border unread ring notification pane outline"), "app:pane-flash": localized("settings.search.alias.setting.app.pane-flash", defaultValue: "notifications.paneFlash flash blink highlight pane notification pulse"), + "app:pane-flash-double-blink": localized("settings.search.alias.setting.app.pane-flash-double-blink", defaultValue: "notifications.paneFlashDoubleBlink pane flash double blink twice pulse"), "app:desktop-notifications": localized("settings.search.alias.setting.app.desktop-notifications", defaultValue: "macos desktop notifications system settings permission alerts notify test"), "app:notification-sound": localized("settings.search.alias.setting.app.notification-sound", defaultValue: "notifications.sound notifications.customSoundFilePath sound audio alert chime beep custom file wav mp3 caf aiff"), "app:notification-sound-overrides": localized("settings.search.alias.setting.app.notification-sound-overrides", defaultValue: "notifications.soundOverrides per-agent agent sound turn done needs input permission error stalled custom file"), diff --git a/Sources/SettingsSearchIndex.swift b/Sources/SettingsSearchIndex.swift index df4addc88572..fdf00356c939 100644 --- a/Sources/SettingsSearchIndex.swift +++ b/Sources/SettingsSearchIndex.swift @@ -49,6 +49,7 @@ enum SettingsSearchIndex { setting(.app, "show-menu-bar", String(localized: "settings.app.showInMenuBar", defaultValue: "Show in Menu Bar"), "menu extra status item"), setting(.app, "unread-pane-ring", String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), "notification blue ring pane"), setting(.app, "pane-flash", String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), "notification flash highlight"), + setting(.app, "pane-flash-double-blink", String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), "pane flash blink twice pulse"), setting(.app, "desktop-notifications", String(localized: "settings.notifications.desktop", defaultValue: "Desktop Notifications"), "permission alerts test notification"), setting(.app, "notification-sound", String(localized: "settings.notifications.sound.title", defaultValue: "Notification Sound"), "custom sound alert audio"), setting( @@ -237,6 +238,7 @@ enum SettingsSearchIndex { "notifications.showInMenuBar": settingID(for: .app, idSuffix: "show-menu-bar"), "notifications.unreadPaneRing": settingID(for: .app, idSuffix: "unread-pane-ring"), "notifications.paneFlash": settingID(for: .app, idSuffix: "pane-flash"), + "notifications.paneFlashDoubleBlink": settingID(for: .app, idSuffix: "pane-flash-double-blink"), "notifications.sound": settingID(for: .app, idSuffix: "notification-sound"), "notifications.customSoundFilePath": settingID(for: .app, idSuffix: "notification-sound"), "notifications.soundOverrides": settingID(for: .app, idSuffix: "notification-sound-overrides"), diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index c9eaa47da1a6..44a6e8ea4859 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -24,6 +24,13 @@ enum NotificationPaneRingSettings { enum NotificationPaneFlashSettings { static let enabledKey = "notificationPaneFlashEnabled" static let defaultEnabled = true + static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" + static let defaultDoubleBlink = false + + /// Whether the pane flash blinks twice instead of one short pulse. + static func usesDoubleBlink(defaults: UserDefaults = .standard) -> Bool { + defaults.object(forKey: doubleBlinkKey) as? Bool ?? defaultDoubleBlink + } static func isEnabled(defaults: UserDefaults = .standard) -> Bool { if defaults.object(forKey: enabledKey) == nil { diff --git a/Sources/TmuxWorkspacePaneOverlayView.swift b/Sources/TmuxWorkspacePaneOverlayView.swift index a04d87654110..76b4dc173559 100644 --- a/Sources/TmuxWorkspacePaneOverlayView.swift +++ b/Sources/TmuxWorkspacePaneOverlayView.swift @@ -25,7 +25,7 @@ struct TmuxWorkspacePaneOverlayView: View { TimelineView(TmuxWorkspacePaneFlashTimelineSchedule(startDate: flashStartedAt)) { timeline in overlayCanvas(timelineDate: timeline.date, attentionColor: attentionColor) .onChange(of: timeline.date) { _, date in - if date.timeIntervalSince(flashStartedAt) >= FocusFlashPattern.duration { + if date.timeIntervalSince(flashStartedAt) >= FocusFlashPattern.current.duration { completedFlashStartedAt = flashStartedAt } } @@ -42,7 +42,7 @@ struct TmuxWorkspacePaneOverlayView: View { let flashStartedAt else { return false } guard completedFlashStartedAt != flashStartedAt, ringPath(for: flashRect) != nil else { return false } - return Date() <= flashStartedAt.addingTimeInterval(FocusFlashPattern.duration) + return Date() <= flashStartedAt.addingTimeInterval(FocusFlashPattern.current.duration) } /// Clips the active border to the drawable canvas so its bottom and right @@ -66,7 +66,7 @@ struct TmuxWorkspacePaneOverlayView: View { let flashStartedAt, let timelineDate else { return } let elapsed = timelineDate.timeIntervalSince(flashStartedAt) - let opacity = FocusFlashPattern.opacity(at: elapsed) + let opacity = FocusFlashPattern.current.opacity(at: elapsed) guard opacity > 0.001 else { return } drawFlashRing( in: &context, @@ -155,7 +155,7 @@ struct TmuxWorkspacePaneFlashTimelineSchedule: TimelineSchedule { let interval = mode == .lowFrequency ? 1.0 / 10.0 : 1.0 / 60.0 return Entries( nextDate: firstDate, - endDate: startDate.addingTimeInterval(FocusFlashPattern.duration), + endDate: startDate.addingTimeInterval(FocusFlashPattern.current.duration), interval: interval ) } diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index 133839236416..850fcc809d89 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -711,17 +711,34 @@ final class TerminalDefaultFileOpenRequestTests: XCTestCase { final class FocusFlashPatternTests: XCTestCase { - func testFocusFlashPatternIsOneShortPulse() { - XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0]) - XCTAssertEqual(FocusFlashPattern.keyTimes, [0, 0.3, 1]) - XCTAssertEqual(FocusFlashPattern.duration, 0.4, accuracy: 0.0001) - XCTAssertEqual(FocusFlashPattern.curves, [.easeOut, .easeIn]) + func testDefaultPatternIsOneShortPulse() throws { + let defaults = try XCTUnwrap(UserDefaults(suiteName: "FocusFlashPatternTests.\(UUID().uuidString)")) + XCTAssertFalse(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) + + let pulse = FocusFlashPattern.pulse + XCTAssertEqual(pulse.values, [0, 1, 0]) + XCTAssertEqual(pulse.keyTimes, [0, 0.3, 1]) + XCTAssertEqual(pulse.duration, 0.4, accuracy: 0.0001) + XCTAssertEqual(pulse.curves, [.easeOut, .easeIn]) XCTAssertEqual(FocusFlashPattern.ringInset, Double(PanelOverlayRingMetrics.inset), accuracy: 0.0001) XCTAssertEqual(FocusFlashPattern.ringCornerRadius, Double(PanelOverlayRingMetrics.cornerRadius), accuracy: 0.0001) } - func testFocusFlashPatternSegmentsCoverThePulse() { - let segments = FocusFlashPattern.segments + func testDoubleBlinkSettingSelectsTheOlderPattern() throws { + let defaults = try XCTUnwrap(UserDefaults(suiteName: "FocusFlashPatternTests.\(UUID().uuidString)")) + defaults.set(true, forKey: NotificationPaneFlashSettings.doubleBlinkKey) + XCTAssertTrue(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) + + let doubleBlink = FocusFlashPattern.doubleBlink + XCTAssertEqual(doubleBlink.values, [0, 1, 0, 1, 0]) + XCTAssertEqual(doubleBlink.keyTimes, [0, 0.25, 0.5, 0.75, 1]) + XCTAssertEqual(doubleBlink.duration, 0.9, accuracy: 0.0001) + XCTAssertEqual(doubleBlink.segments.count, 4) + } + + func testPulseSegmentsCoverThePulse() { + let pulse = FocusFlashPattern.pulse + let segments = pulse.segments XCTAssertEqual(segments.count, 2) XCTAssertEqual(segments[0].delay, 0.0, accuracy: 0.0001) @@ -733,7 +750,8 @@ final class FocusFlashPatternTests: XCTestCase { XCTAssertEqual(segments[1].duration, 0.28, accuracy: 0.0001) XCTAssertEqual(segments[1].targetOpacity, 0, accuracy: 0.0001) XCTAssertEqual(segments[1].curve, .easeIn) - XCTAssertEqual(FocusFlashPattern.opacity(at: 0.4), 0, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.12), 1, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.4), 0, accuracy: 0.0001) } } diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index 605f3d20164f..314b1a4605d8 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -76,6 +76,7 @@ Notification behavior from Settings > Notifications. | `notifications.showInMenuBar` | boolean | `true` | Show the menu bar extra. | | `notifications.unreadPaneRing` | boolean | `true` | Highlight panes with unread notifications. | | `notifications.paneFlash` | boolean | `true` | Flash the focused pane when requested. | +| `notifications.paneFlashDoubleBlink` | boolean | `false` | Blink the pane flash twice instead of one short pulse. | | `notifications.sound` | `"default"` or `"Basso"` or `"Blow"` or `"Bottle"` or `"Frog"` or `"Funk"` or `"Glass"` or `"Hero"` or `"Morse"` or `"Ping"` or `"Pop"` or `"Purr"` or `"Sosumi"` or `"Submarine"` or `"Tink"` or `"custom_file"` or `"none"` | `"default"` | Notification sound preset. | | `notifications.customSoundFilePath` | string | `""` | Local path to the custom notification sound file. | | `notifications.command` | string | `""` | Optional shell command to run alongside notification delivery. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index f0a3d0f22ef0..58ace0e2498f 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1064,6 +1064,12 @@ "default": true, "description": "Flash the focused pane when requested." }, + "paneFlashDoubleBlink": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": false, + "description": "Blink the pane flash twice instead of one short pulse." + }, "paneFlashColor": { "x-cmux-scopes": ["global"], "$ref": "#/$defs/colorHexOrNull", From 655601ab99b0f58c5fc9964d3f2f557a51c2e4f7 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 28 Sep 2026 12:31:18 -0400 Subject: [PATCH 04/23] Pane flash: a 0.6 s pulse, which won the side-by-side vote over 0.4 s Co-Authored-By: Claude Opus 5.5 (1M context) --- Sources/Panels/Panel.swift | 2 +- cmuxTests/WindowAndDragTests.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index 870b335c6eab..21aa0a7a5356 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -259,7 +259,7 @@ struct FocusFlashPattern: Equatable { static let pulse = FocusFlashPattern( values: [0, 1, 0], keyTimes: [0, 0.3, 1], - duration: 0.4, + duration: 0.6, curves: [.easeOut, .easeIn] ) static let doubleBlink = FocusFlashPattern( diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index e7d4186ee8a4..ca00d022e186 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -747,7 +747,7 @@ final class FocusFlashPatternTests: XCTestCase { let pulse = FocusFlashPattern.pulse XCTAssertEqual(pulse.values, [0, 1, 0]) XCTAssertEqual(pulse.keyTimes, [0, 0.3, 1]) - XCTAssertEqual(pulse.duration, 0.4, accuracy: 0.0001) + XCTAssertEqual(pulse.duration, 0.6, accuracy: 0.0001) XCTAssertEqual(pulse.curves, [.easeOut, .easeIn]) XCTAssertEqual(FocusFlashPattern.ringInset, Double(PanelOverlayRingMetrics.inset), accuracy: 0.0001) XCTAssertEqual(FocusFlashPattern.ringCornerRadius, Double(PanelOverlayRingMetrics.cornerRadius), accuracy: 0.0001) @@ -771,16 +771,16 @@ final class FocusFlashPatternTests: XCTestCase { XCTAssertEqual(segments.count, 2) XCTAssertEqual(segments[0].delay, 0.0, accuracy: 0.0001) - XCTAssertEqual(segments[0].duration, 0.12, accuracy: 0.0001) + XCTAssertEqual(segments[0].duration, 0.18, accuracy: 0.0001) XCTAssertEqual(segments[0].targetOpacity, 1, accuracy: 0.0001) XCTAssertEqual(segments[0].curve, .easeOut) - XCTAssertEqual(segments[1].delay, 0.12, accuracy: 0.0001) - XCTAssertEqual(segments[1].duration, 0.28, accuracy: 0.0001) + XCTAssertEqual(segments[1].delay, 0.18, accuracy: 0.0001) + XCTAssertEqual(segments[1].duration, 0.42, accuracy: 0.0001) XCTAssertEqual(segments[1].targetOpacity, 0, accuracy: 0.0001) XCTAssertEqual(segments[1].curve, .easeIn) - XCTAssertEqual(pulse.opacity(at: 0.12), 1, accuracy: 0.0001) - XCTAssertEqual(pulse.opacity(at: 0.4), 0, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.18), 1, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.6), 0, accuracy: 0.0001) } } From d5323c9bfc798bd96c0001f88abb612835b54328 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 28 Sep 2026 12:54:50 -0400 Subject: [PATCH 05/23] Pane flash: follow the terminal theme foreground by default With no notifications.paneFlashColor set, the flash now draws in the terminal theme's foreground at 85% instead of the cmux blue, so it stays quiet on any theme. Unread rings keep the cmux accent, and a configured color still paints both. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../CmuxConfigSchema.generated.swift | 2 +- Sources/ContentView.swift | 42 ++++++++++++++++--- Sources/GhosttyTerminalView.swift | 8 ++-- Sources/Panels/WorkspaceAttentionColor.swift | 32 ++++++++++++-- .../WorkspaceAttentionFlashRingView.swift | 2 +- Sources/TmuxWorkspacePaneOverlayView.swift | 2 +- ...utModifierHoldHintsSettingsFileTests.swift | 18 ++++++++ skills/cmux-settings/references/all-keys.md | 2 +- web/data/cmux.schema.json | 2 +- 9 files changed, 94 insertions(+), 16 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index 2ac83cb834ba..f386ac09c9cf 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -1116,7 +1116,7 @@ enum CmuxEmbeddedConfigSchema { "$ref": "#/$defs/colorHexOrNull", "default": null, "descriptionKey": "schemaDescriptions.notifications.paneFlashColor", - "description": "Override the pane flash and unread ring color. Null keeps the built-in blue." + "description": "Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent." }, "suppressOnlyFocusedSurface": { "x-cmux-scopes": ["global"], diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 1d4e28fbcabd..c0ffe2d4fe48 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -908,6 +908,26 @@ struct ContentView: View { /// updated from its change notification. This view is the window root, /// so it cannot read the accent from its own environment modifier. @State private var cmuxAccent = AppDelegate.shared?.accentColor ?? CmuxAccentColor() + /// Terminal theme foreground, which colors pane flashes when no flash + /// color is configured. Updated from the default appearance notification. + @State private var terminalThemeForeground = GhosttyApp.shared.defaultForegroundColor + + private var resolvedWorkspaceAttentionColor: WorkspaceAttentionColor { + resolveWorkspaceAttentionColor() + } + + private func resolveWorkspaceAttentionColor( + configuredHex: String?? = nil, + accent: CmuxAccentColor? = nil, + themeForeground: NSColor? = nil + ) -> WorkspaceAttentionColor { + WorkspaceAttentionColor( + configuredHex: configuredHex ?? paneFlashColorHex, + accent: accent ?? cmuxAccent, + themeForeground: themeForeground ?? terminalThemeForeground + ) + } + /// Canonical sidebar width, deliberately NOT observed by ContentView: /// divider ticks re-evaluate only the SidebarWidthReader wrappers that /// consume the width, never this body. All reads/writes outside view @@ -1181,7 +1201,7 @@ struct ContentView: View { activePaneBorderColorHex: nil, flashToken: workspace.tmuxWorkspaceFlashToken, flashReason: workspace.tmuxWorkspaceFlashReason, - workspaceAttentionColor: WorkspaceAttentionColor(configuredHex: paneFlashColorHex, accent: cmuxAccent) + workspaceAttentionColor: resolvedWorkspaceAttentionColor ) } @@ -1193,7 +1213,7 @@ struct ContentView: View { activePaneBorderColorHex: activePaneBorderRect == nil ? nil : resolvedActivePaneBorderColorHex, flashToken: workspace.tmuxWorkspaceFlashToken, flashReason: workspace.tmuxWorkspaceFlashReason, - workspaceAttentionColor: WorkspaceAttentionColor(configuredHex: paneFlashColorHex, accent: cmuxAccent) + workspaceAttentionColor: resolvedWorkspaceAttentionColor ) } @@ -2825,6 +2845,18 @@ struct ContentView: View { }) view = AnyView(view.onReceive(NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)) { notification in + if let foreground = notification.userInfo?[GhosttyNotificationKey.foregroundColor] as? NSColor, + foreground.hexString() != terminalThemeForeground.hexString() { + terminalThemeForeground = foreground + if let window = observedWindow { + WindowTmuxWorkspacePaneOverlayController.controller( + for: window, + createIfNeeded: false + )?.updateWorkspaceAttentionColor( + resolveWorkspaceAttentionColor(themeForeground: foreground) + ) + } + } let payloadHex = (notification.userInfo?[GhosttyNotificationKey.backgroundColor] as? NSColor)?.hexString() let eventId = (notification.userInfo?[GhosttyNotificationKey.backgroundEventId] as? NSNumber)?.uint64Value let source = notification.userInfo?[GhosttyNotificationKey.backgroundSource] as? String @@ -2907,7 +2939,7 @@ struct ContentView: View { for: window, createIfNeeded: false )?.updateWorkspaceAttentionColor( - WorkspaceAttentionColor(configuredHex: newValue, accent: cmuxAccent) + resolveWorkspaceAttentionColor(configuredHex: newValue) ) }) @@ -2919,7 +2951,7 @@ struct ContentView: View { for: window, createIfNeeded: false )?.updateWorkspaceAttentionColor( - WorkspaceAttentionColor(configuredHex: paneFlashColorHex, accent: observer.current) + resolveWorkspaceAttentionColor(accent: observer.current) ) }) @@ -3485,7 +3517,7 @@ struct ContentView: View { view .environment( \.workspaceAttentionColor, - WorkspaceAttentionColor(configuredHex: paneFlashColorHex, accent: cmuxAccent) + resolvedWorkspaceAttentionColor ) .cmuxAppearanceColorScheme(appearanceMode) ) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 8d02775b639b..715a23a70654 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -10229,6 +10229,7 @@ final class GhosttySurfaceScrollView: NSView { private var lastFlashStyle: FlashStyle = .navigation private var workspaceAttentionColor = WorkspaceAttentionColor(configuredHex: nil) private var workspaceAttentionNSColor = WorkspaceAttentionColor(configuredHex: nil).nsColor + private var workspaceAttentionFlashNSColor = WorkspaceAttentionColor(configuredHex: nil).flashNSColor private let keyboardCopyModeBadgeContainerView: GhosttyFlashOverlayView private let keyboardCopyModeBadgeView: GhosttyPassthroughVisualEffectView private let keyboardCopyModeBadgeIconView: NSImageView @@ -10555,7 +10556,7 @@ final class GhosttySurfaceScrollView: NSView { flashOverlayView.layer?.masksToBounds = false flashOverlayView.autoresizingMask = [.width, .height] let flashStyle = WorkspaceAttentionCoordinator.flashStyle(for: .navigation) - let flashColor = WorkspaceAttentionColor(configuredHex: nil).nsColor + let flashColor = WorkspaceAttentionColor(configuredHex: nil).flashNSColor flashLayer.fillColor = NSColor.clear.cgColor flashLayer.strokeColor = flashColor.cgColor flashLayer.lineWidth = NotificationRingMetrics.lineWidth @@ -11431,6 +11432,7 @@ final class GhosttySurfaceScrollView: NSView { private func applyWorkspaceAttentionNSColor() { workspaceAttentionNSColor = workspaceAttentionColor.nsColor + workspaceAttentionFlashNSColor = workspaceAttentionColor.flashNSColor CATransaction.begin() CATransaction.setDisableActions(true) @@ -13634,8 +13636,8 @@ final class GhosttySurfaceScrollView: NSView { private func updateFlashAppearance(style: FlashStyle) { let presentation = Self.flashPresentation(for: style) - flashLayer.strokeColor = workspaceAttentionNSColor.cgColor - flashLayer.shadowColor = workspaceAttentionNSColor.cgColor + flashLayer.strokeColor = workspaceAttentionFlashNSColor.cgColor + flashLayer.shadowColor = workspaceAttentionFlashNSColor.cgColor flashLayer.shadowOpacity = Float(presentation.glowOpacity) flashLayer.shadowRadius = presentation.glowRadius } diff --git a/Sources/Panels/WorkspaceAttentionColor.swift b/Sources/Panels/WorkspaceAttentionColor.swift index 25fdb880abb9..a4df4d6ec365 100644 --- a/Sources/Panels/WorkspaceAttentionColor.swift +++ b/Sources/Panels/WorkspaceAttentionColor.swift @@ -4,6 +4,10 @@ import SwiftUI /// The resolved color shared by pane flashes and unread notification rings. /// +/// A configured color paints both. Without one, unread rings use the cmux +/// accent and flashes use the terminal theme's foreground, so the flash stays +/// quiet against whatever theme is active. +/// /// The setting store remains the only owner of the configured string. This /// value validates one immutable snapshot before it reaches a renderer, so /// AppKit layers and SwiftUI canvases never read ambient defaults or parse the @@ -12,21 +16,43 @@ struct WorkspaceAttentionColor: Equatable, Sendable { private let rgb: UInt32? /// The resolved cmux accent used when no valid color is configured. private let accent: CmuxAccentColor + /// The terminal theme foreground used by flashes when no color is configured. + private let themeForegroundRGB: UInt32? - init(configuredHex: String?, accent: CmuxAccentColor = CmuxAccentColor()) { + init( + configuredHex: String?, + accent: CmuxAccentColor = CmuxAccentColor(), + themeForeground: NSColor? = nil + ) { self.rgb = Self.strictRGB(configuredHex) self.accent = accent + self.themeForegroundRGB = themeForeground.flatMap { Self.strictRGB($0.hexString()) } } var nsColor: NSColor { guard let rgb else { return WorkspaceAttentionCoordinator.notificationRingStyle.accent.strokeColor(accent: accent) } - return NSColor( + return Self.color(rgb: rgb, alpha: 1) + } + + /// The pane flash color: the configured color, else the theme foreground + /// slightly softened, else the unread ring color. + var flashNSColor: NSColor { + if rgb == nil, let themeForegroundRGB { + return Self.color(rgb: themeForegroundRGB, alpha: Self.themeForegroundFlashAlpha) + } + return nsColor + } + + static let themeForegroundFlashAlpha: CGFloat = 0.85 + + private static func color(rgb: UInt32, alpha: CGFloat) -> NSColor { + NSColor( red: CGFloat((rgb >> 16) & 0xFF) / 255, green: CGFloat((rgb >> 8) & 0xFF) / 255, blue: CGFloat(rgb & 0xFF) / 255, - alpha: 1 + alpha: alpha ) } diff --git a/Sources/Panels/WorkspaceAttentionFlashRingView.swift b/Sources/Panels/WorkspaceAttentionFlashRingView.swift index 3f1394baa2a8..f8b9908b60fc 100644 --- a/Sources/Panels/WorkspaceAttentionFlashRingView.swift +++ b/Sources/Panels/WorkspaceAttentionFlashRingView.swift @@ -8,7 +8,7 @@ struct WorkspaceAttentionFlashRingView: View { var body: some View { let presentation = WorkspaceAttentionCoordinator.flashStyle(for: reason) - let color = Color(nsColor: workspaceAttentionColor.nsColor) + let color = Color(nsColor: workspaceAttentionColor.flashNSColor) RoundedRectangle(cornerRadius: CGFloat(FocusFlashPattern.ringCornerRadius)) .stroke(color.opacity(opacity), lineWidth: PanelOverlayRingMetrics.lineWidth) diff --git a/Sources/TmuxWorkspacePaneOverlayView.swift b/Sources/TmuxWorkspacePaneOverlayView.swift index 8a586541f2cc..0e1c0abe1487 100644 --- a/Sources/TmuxWorkspacePaneOverlayView.swift +++ b/Sources/TmuxWorkspacePaneOverlayView.swift @@ -73,7 +73,7 @@ struct TmuxWorkspacePaneOverlayView: View { rect: flashRect, opacity: opacity, reason: flashReason ?? .notificationArrival, - color: attentionColor + color: Color(nsColor: workspaceAttentionColor.flashNSColor) ) } } diff --git a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift index 1677b7a266e6..151fa74a2a53 100644 --- a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift +++ b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift @@ -223,6 +223,24 @@ struct PaneAttentionColorTests { ) } + @Test + func flashFollowsThemeForegroundWhenUnset() { + let color = WorkspaceAttentionColor( + configuredHex: nil, + themeForeground: NSColor(hex: "#CDD6F4") + ) + #expect(color.flashNSColor.hexString() == "#CDD6F4") + #expect(color.flashNSColor.alphaComponent == WorkspaceAttentionColor.themeForegroundFlashAlpha) + #expect(color.nsColor.hexString() == NSColor.systemBlue.hexString()) + } + + @Test + func configuredHexOverridesThemeForegroundForFlash() { + let color = WorkspaceAttentionColor(configuredHex: "#ff69b4", themeForeground: .white) + #expect(color.flashNSColor.hexString() == "#FF69B4") + #expect(color.flashNSColor.alphaComponent == 1) + } + @Test(arguments: ["not-a-color", "#FFZZZZ", "FF69B4", "#FF69B4AA"]) func rejectsValuesOutsideSchema(configuredHex: String) { #expect( diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index 3373abcdac56..6791a63585c0 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -88,7 +88,7 @@ Notification behavior from Settings > Notifications. | `notifications.command` | string | `""` | Optional shell command to run alongside notification delivery. | | `notifications.hooksMode` | `"append"` or `"replace"` | `"append"` | Controls whether project-local notification hooks append to inherited hooks or replace them. | | `notifications.hooks` | array | `[]` | Composable shell hooks that receive notification policy JSON on stdin and return updated policy JSON on stdout. | -| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null keeps the built-in blue. | +| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent. | | `notifications.suppressOnlyFocusedSurface` | boolean | `false` | When enabled, a notification banner is auto-withdrawn only when its surface is the exact focused surface. A banner delivered for a non-focused surface in the currently visible workspace stays up until you focus that surface (or click/dismiss it), instead of being retracted when the workspace becomes visible. Off preserves the legacy workspace-visibility withdraw. | | `notifications.agentPermissionPrompt` | boolean | `true` | Notify when an agent (e.g. Claude Code) is blocked waiting for your permission to run a tool. On by default, since this is the alert you must act on to unblock the agent. | | `notifications.agentTurnComplete` | `"whenIdle"` or `"always"` or `"never"` | `"whenIdle"` | When to notify that an agent finished a turn. whenIdle (default) suppresses the notification while the agent still has a running background task or a pending scheduled wakeup, so you are pinged once work truly drains. always notifies on every turn end; never disables it. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 14aad3b2d07f..f44cb9cb8f4b 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1106,7 +1106,7 @@ "$ref": "#/$defs/colorHexOrNull", "default": null, "descriptionKey": "schemaDescriptions.notifications.paneFlashColor", - "description": "Override the pane flash and unread ring color. Null keeps the built-in blue." + "description": "Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent." }, "suppressOnlyFocusedSurface": { "x-cmux-scopes": ["global"], From dd3c839d35b56300650c3deec50cf7fb84b57bbd Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 28 Sep 2026 15:17:22 -0400 Subject: [PATCH 06/23] Test typing and clicking dismissal against a live surface The keyDown test sent F1 before the runtime surface existed, so the key never reached the terminal and the workspace's startup focus pass cleared the notification (with its flash) instead. Both tests now wait for the surface and the startup pass, and check that typing "a" or clicking in the focused pane clears the notification without a flash. Co-Authored-By: Claude Opus 5.5 (1M context) --- cmuxTests/TerminalAndGhosttyTests.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cmuxTests/TerminalAndGhosttyTests.swift b/cmuxTests/TerminalAndGhosttyTests.swift index 9063044fc40e..82394f5e6e47 100644 --- a/cmuxTests/TerminalAndGhosttyTests.swift +++ b/cmuxTests/TerminalAndGhosttyTests.swift @@ -3230,9 +3230,13 @@ final class TerminalNotificationDirectInteractionTests: XCTestCase { return } - GhosttySurfaceScrollView.resetFlashCounts() AppFocusState.overrideIsFocused = true XCTAssertTrue(window.makeFirstResponder(surfaceView)) + // Let the runtime surface come up and the workspace's startup focus pass + // run first, so only the interaction below can clear the notification. + waitForRuntimeSurface(terminalPanel.surface) + drainMainQueue() + GhosttySurfaceScrollView.resetFlashCounts() store.addNotification( tabId: workspace.id, @@ -3303,9 +3307,13 @@ final class TerminalNotificationDirectInteractionTests: XCTestCase { return } - GhosttySurfaceScrollView.resetFlashCounts() AppFocusState.overrideIsFocused = true XCTAssertTrue(window.makeFirstResponder(surfaceView)) + // Let the runtime surface come up and the workspace's startup focus pass + // run first, so only the interaction below can clear the notification. + waitForRuntimeSurface(terminalPanel.surface) + drainMainQueue() + GhosttySurfaceScrollView.resetFlashCounts() store.addNotification( tabId: workspace.id, @@ -3316,12 +3324,13 @@ final class TerminalNotificationDirectInteractionTests: XCTestCase { ) XCTAssertTrue(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: terminalPanel.id)) - let event = makeKeyEvent(characters: "", keyCode: 122, window: window) + let event = makeKeyEvent(characters: "a", keyCode: 0, window: window) surfaceView.keyDown(with: event) drainMainQueue() XCTAssertFalse(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: terminalPanel.id)) - XCTAssertEqual(GhosttySurfaceScrollView.flashCount(for: terminalPanel.id), 1) + // Typing into the pane is the acknowledgement; the pane doesn't flash. + XCTAssertEqual(GhosttySurfaceScrollView.flashCount(for: terminalPanel.id), 0) } func testKeyDownRecoversReleasedSurfaceWhileHostedViewIsDetached() throws { From 02898f035c27162ab4989c01927127600d4ccdb2 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 04:13:15 -0700 Subject: [PATCH 07/23] fix: keep double blink as default focus flash --- .../CmuxConfigSchema.generated.swift | 2 +- Sources/Panels/WorkspaceAttentionColor.swift | 17 +++-------------- Sources/TerminalNotificationStoreSettings.swift | 2 +- web/data/cmux.schema.json | 2 +- 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index a305be2d11bf..787ebb949c1d 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -1140,7 +1140,7 @@ enum CmuxEmbeddedConfigSchema { "paneFlashDoubleBlink": { "x-cmux-scopes": ["global"], "type": "boolean", - "default": false, + "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, "paneFlashColor": { diff --git a/Sources/Panels/WorkspaceAttentionColor.swift b/Sources/Panels/WorkspaceAttentionColor.swift index a4df4d6ec365..58f194a50c6d 100644 --- a/Sources/Panels/WorkspaceAttentionColor.swift +++ b/Sources/Panels/WorkspaceAttentionColor.swift @@ -16,9 +16,6 @@ struct WorkspaceAttentionColor: Equatable, Sendable { private let rgb: UInt32? /// The resolved cmux accent used when no valid color is configured. private let accent: CmuxAccentColor - /// The terminal theme foreground used by flashes when no color is configured. - private let themeForegroundRGB: UInt32? - init( configuredHex: String?, accent: CmuxAccentColor = CmuxAccentColor(), @@ -26,7 +23,7 @@ struct WorkspaceAttentionColor: Equatable, Sendable { ) { self.rgb = Self.strictRGB(configuredHex) self.accent = accent - self.themeForegroundRGB = themeForeground.flatMap { Self.strictRGB($0.hexString()) } + _ = themeForeground } var nsColor: NSColor { @@ -36,16 +33,8 @@ struct WorkspaceAttentionColor: Equatable, Sendable { return Self.color(rgb: rgb, alpha: 1) } - /// The pane flash color: the configured color, else the theme foreground - /// slightly softened, else the unread ring color. - var flashNSColor: NSColor { - if rgb == nil, let themeForegroundRGB { - return Self.color(rgb: themeForegroundRGB, alpha: Self.themeForegroundFlashAlpha) - } - return nsColor - } - - static let themeForegroundFlashAlpha: CGFloat = 0.85 + /// The pane flash keeps the configured color or the existing cmux accent. + var flashNSColor: NSColor { nsColor } private static func color(rgb: UInt32, alpha: CGFloat) -> NSColor { NSColor( diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index 44a6e8ea4859..dff88f475039 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -25,7 +25,7 @@ enum NotificationPaneFlashSettings { static let enabledKey = "notificationPaneFlashEnabled" static let defaultEnabled = true static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" - static let defaultDoubleBlink = false + static let defaultDoubleBlink = true /// Whether the pane flash blinks twice instead of one short pulse. static func usesDoubleBlink(defaults: UserDefaults = .standard) -> Bool { diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 3c01eaafdb60..95c93b1f3806 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1130,7 +1130,7 @@ "paneFlashDoubleBlink": { "x-cmux-scopes": ["global"], "type": "boolean", - "default": false, + "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, "paneFlashColor": { From 662d005fb395ae6b998259df4dcb4ed519637ad4 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 04:13:23 -0700 Subject: [PATCH 08/23] test: assert double blink is the default --- cmuxTests/WindowAndDragTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index ca00d022e186..4f542e87e1a4 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -742,7 +742,7 @@ final class TerminalDefaultFileOpenRequestTests: XCTestCase { final class FocusFlashPatternTests: XCTestCase { func testDefaultPatternIsOneShortPulse() throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: "FocusFlashPatternTests.\(UUID().uuidString)")) - XCTAssertFalse(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) + XCTAssertTrue(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) let pulse = FocusFlashPattern.pulse XCTAssertEqual(pulse.values, [0, 1, 0]) From a811385c2d1a47e272335874e3e39fd1b6de26c1 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 04:59:32 -0700 Subject: [PATCH 09/23] feat: make focus flash color and pattern configurable --- .../CmuxConfigSchema.generated.swift | 6 + .../Keys/NotificationsCatalogSection.swift | 6 + .../Resources/Localizable.xcstrings | 118 ++++++++++++++++++ .../CmuxSettingsUI/Sections/AppSection.swift | 16 ++- .../SettingsRowAnchorResolutionTests.swift | 1 + ...CmuxSettingsFileStore+SupportedPaths.swift | 1 + Sources/CmuxSettingsJSONPathSupport.swift | 1 + Sources/ContentView.swift | 4 +- ...rdShortcutSettingsFileStore+Template.swift | 1 + Sources/Panels/WorkspaceAttentionColor.swift | 20 ++- Sources/SettingsSearchIndex.swift | 2 + .../TerminalNotificationStoreSettings.swift | 2 + skills/cmux-settings/references/all-keys.md | 5 +- web/data/cmux.schema.json | 6 + 14 files changed, 179 insertions(+), 10 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index 787ebb949c1d..89036bf56655 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -1143,6 +1143,12 @@ enum CmuxEmbeddedConfigSchema { "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, + "paneFlashThemeColor": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": false, + "description": "Use the terminal theme foreground for flashes when paneFlashColor is unset. Unread rings remain cmux blue." + }, "paneFlashColor": { "x-cmux-scopes": ["global"], "$ref": "#/$defs/colorHexOrNull", diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index e3df67b6f4af..8c489b1bfcbe 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -33,6 +33,12 @@ public struct NotificationsCatalogSection: SettingCatalogSection { userDefaultsKey: "notificationPaneFlashDoubleBlink" ) + public let paneFlashThemeColor = DefaultsKey( + id: "notifications.paneFlashThemeColor", + defaultValue: false, + userDefaultsKey: "notificationPaneFlashThemeColor" + ) + /// Stroke color of the attention ring and pane flash, as a `#RRGGBB` hex. /// Empty (the default) uses the cmux accent (`app.accentColor`). public let paneFlashColorHex = DefaultsKey( diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings index 7130c6b4c47d..f86e0a0f214f 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings @@ -7212,6 +7212,124 @@ } } } + }, + "settings.notifications.paneFlashThemeColor.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use the terminal theme foreground for flashes; unread rings stay cmux blue." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verwende die Vordergrundfarbe des Terminaldesigns für Blitze; ungelesene Ringe bleiben cmux-blau." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utiliser le premier plan du thème du terminal pour les flashs ; les anneaux non lus restent bleus cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استخدم مقدمة سمة الطرفية للومضات؛ تبقى حلقات عدم القراءة باللون الأزرق لـ cmux." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usa el primer plano del tema de la terminal para los destellos; los anillos no leídos siguen siendo azules de cmux." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用終端主題前景色顯示閃爍;未讀環維持 cmux 藍色。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用终端主题前景色显示闪烁;未读环保持 cmux 蓝色。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "플래시에 터미널 테마 전경색을 사용하며 읽지 않은 링은 cmux 파란색으로 유지합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フラッシュにターミナルテーマの前景色を使い、未読リングは cmux ブルーのままにします。" + } + } + } + }, + "settings.notifications.paneFlashThemeColor.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Theme Foreground" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vordergrund des Designs verwenden" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utiliser le premier plan du thème" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استخدام مقدمة السمة" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usar el primer plano del tema" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用主題前景色" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用主题前景色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "테마 전경색 사용" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "テーマの前景色を使う" + } + } + } } }, "version": "1.0" diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift index 446268e15741..f54dc0041496 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift @@ -57,6 +57,7 @@ public struct AppSection: View { @State private var paneRing: DefaultsValueModel @State private var paneFlash: DefaultsValueModel @State private var paneFlashDoubleBlink: DefaultsValueModel + @State private var paneFlashThemeColor: DefaultsValueModel @State private var desktopNotifications: DesktopNotificationAuthorizationModel @State private var agentPermissionPrompt: DefaultsValueModel @State private var agentTurnComplete: DefaultsValueModel @@ -130,6 +131,7 @@ public struct AppSection: View { _paneRing = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.unreadPaneRing)) _paneFlash = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlash)) _paneFlashDoubleBlink = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashDoubleBlink)) + _paneFlashThemeColor = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashThemeColor)) _desktopNotifications = State(initialValue: DesktopNotificationAuthorizationModel(hostActions: hostActions)) _agentPermissionPrompt = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentPermissionPrompt)) _agentTurnComplete = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentTurnComplete)) @@ -173,7 +175,7 @@ public struct AppSection: View { AppChannelSwitchCard(hostActions: hostActions) } .task { - startSettingsObservation([language, appearance, accentColor, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) + startSettingsObservation([language, appearance, accentColor, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, paneFlashThemeColor, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) await soundAgentCache.loadIfNeeded { await hostActions.notificationSoundAgentOptions() } if languageAtAppear == nil { languageAtAppear = language.current }; if telemetryAtAppear == nil { telemetryAtAppear = telemetry.current } } @@ -750,6 +752,18 @@ public struct AppSection: View { } SettingsCardDivider() + SettingsCardRow( + configurationReview: .json("notifications.paneFlashThemeColor"), + String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), + subtitle: String(localized: "settings.notifications.paneFlashThemeColor.subtitle", defaultValue: "Use the terminal theme foreground for flashes; unread rings stay cmux blue.") + ) { + Toggle("", isOn: Binding(get: { paneFlashThemeColor.current }, set: { paneFlashThemeColor.set($0) })) + .labelsHidden() + .controlSize(.small) + .disabled(!paneFlash.current) + } + SettingsCardDivider() + // Agent: Needs Permission SettingsCardRow( configurationReview: .json("notifications.agentPermissionPrompt"), diff --git a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift index 7358e0104cdf..a90192e10c8c 100644 --- a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift +++ b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift @@ -104,6 +104,7 @@ struct SettingsRowAnchorResolutionTests { "notifications.dockBadge", "notifications.paneFlash", "notifications.paneFlashDoubleBlink", + "notifications.paneFlashThemeColor", "notifications.paneFlashColor", "notifications.showInMenuBar", "notifications.sound", diff --git a/Sources/CmuxSettingsFileStore+SupportedPaths.swift b/Sources/CmuxSettingsFileStore+SupportedPaths.swift index d6cb9290bf2d..f536632afc72 100644 --- a/Sources/CmuxSettingsFileStore+SupportedPaths.swift +++ b/Sources/CmuxSettingsFileStore+SupportedPaths.swift @@ -74,6 +74,7 @@ extension CmuxSettingsFileStore { "notifications.unreadPaneRing", "notifications.paneFlash", "notifications.paneFlashDoubleBlink", + "notifications.paneFlashThemeColor", "notifications.paneFlashColor", "notifications.sound", "notifications.soundWhenFocused", diff --git a/Sources/CmuxSettingsJSONPathSupport.swift b/Sources/CmuxSettingsJSONPathSupport.swift index 30e7c46e8a2b..7f83815778ed 100644 --- a/Sources/CmuxSettingsJSONPathSupport.swift +++ b/Sources/CmuxSettingsJSONPathSupport.swift @@ -228,6 +228,7 @@ enum NotificationSettingsFileMapping { .init(jsonKey: "unreadPaneRing", defaultsKey: NotificationPaneRingSettings.enabledKey), .init(jsonKey: "paneFlash", defaultsKey: NotificationPaneFlashSettings.enabledKey), .init(jsonKey: "paneFlashDoubleBlink", defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey), + .init(jsonKey: "paneFlashThemeColor", defaultsKey: NotificationPaneFlashSettings.themeColorKey), .init( jsonKey: "soundWhenFocused", defaultsKey: notifications.soundWhenFocused.userDefaultsKey diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 0888d329451b..e5211701a92f 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -904,6 +904,7 @@ struct ContentView: View { @LiveSetting(\.shortcuts.showModifierHoldHints) private var showModifierHoldHints @LiveSetting(\.customSidebars.renderer) private var customSidebarRenderer @LiveSetting(\.notifications.paneFlashColorHex) private var paneFlashColorHex + @AppStorage("notificationPaneFlashThemeColor") private var paneFlashThemeColor = false /// Resolved cmux accent, seeded from the app delegate's observer and /// updated from its change notification. This view is the window root, /// so it cannot read the accent from its own environment modifier. @@ -924,7 +925,8 @@ struct ContentView: View { WorkspaceAttentionColor( configuredHex: configuredHex ?? paneFlashColorHex, accent: accent ?? cmuxAccent, - themeForeground: themeForeground ?? terminalThemeForeground + themeForeground: themeForeground ?? terminalThemeForeground, + useThemeForeground: paneFlashThemeColor ) } diff --git a/Sources/KeyboardShortcutSettingsFileStore+Template.swift b/Sources/KeyboardShortcutSettingsFileStore+Template.swift index ed35d82edaa7..b22e87358a61 100644 --- a/Sources/KeyboardShortcutSettingsFileStore+Template.swift +++ b/Sources/KeyboardShortcutSettingsFileStore+Template.swift @@ -132,6 +132,7 @@ extension CmuxSettingsFileStore { "unreadPaneRing": NotificationPaneRingSettings.defaultEnabled, "paneFlash": NotificationPaneFlashSettings.defaultEnabled, "paneFlashDoubleBlink": NotificationPaneFlashSettings.defaultDoubleBlink, + "paneFlashThemeColor": NotificationPaneFlashSettings.defaultThemeColor, "paneFlashColor": NSNull(), "sound": NotificationSoundSettings.defaultValue, "customSoundFilePath": NotificationSoundSettings.defaultCustomFilePath, diff --git a/Sources/Panels/WorkspaceAttentionColor.swift b/Sources/Panels/WorkspaceAttentionColor.swift index 58f194a50c6d..3f574c157819 100644 --- a/Sources/Panels/WorkspaceAttentionColor.swift +++ b/Sources/Panels/WorkspaceAttentionColor.swift @@ -4,9 +4,8 @@ import SwiftUI /// The resolved color shared by pane flashes and unread notification rings. /// -/// A configured color paints both. Without one, unread rings use the cmux -/// accent and flashes use the terminal theme's foreground, so the flash stays -/// quiet against whatever theme is active. +/// A configured color paints both. Without one, both signals use the existing +/// cmux accent, preserving the established blue default. /// /// The setting store remains the only owner of the configured string. This /// value validates one immutable snapshot before it reaches a renderer, so @@ -16,14 +15,18 @@ struct WorkspaceAttentionColor: Equatable, Sendable { private let rgb: UInt32? /// The resolved cmux accent used when no valid color is configured. private let accent: CmuxAccentColor + private let themeForegroundHex: String? + private let useThemeForeground: Bool init( configuredHex: String?, accent: CmuxAccentColor = CmuxAccentColor(), - themeForeground: NSColor? = nil + themeForeground: NSColor? = nil, + useThemeForeground: Bool = false ) { self.rgb = Self.strictRGB(configuredHex) self.accent = accent - _ = themeForeground + self.themeForegroundHex = themeForeground?.hexString() + self.useThemeForeground = useThemeForeground } var nsColor: NSColor { @@ -34,7 +37,12 @@ struct WorkspaceAttentionColor: Equatable, Sendable { } /// The pane flash keeps the configured color or the existing cmux accent. - var flashNSColor: NSColor { nsColor } + var flashNSColor: NSColor { + guard rgb == nil, useThemeForeground, + let themeForegroundHex, + let themeForeground = NSColor(hex: themeForegroundHex) else { return nsColor } + return themeForeground + } private static func color(rgb: UInt32, alpha: CGFloat) -> NSColor { NSColor( diff --git a/Sources/SettingsSearchIndex.swift b/Sources/SettingsSearchIndex.swift index ee4efbde2da1..1767b2141732 100644 --- a/Sources/SettingsSearchIndex.swift +++ b/Sources/SettingsSearchIndex.swift @@ -50,6 +50,7 @@ enum SettingsSearchIndex { setting(.app, "show-menu-bar", String(localized: "settings.app.showInMenuBar", defaultValue: "Show in Menu Bar"), "menu extra status item"), setting(.app, "unread-pane-ring", String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), "notification blue ring pane"), setting(.app, "pane-flash", String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), "notification flash highlight"), + setting(.app, "pane-flash-theme-color", String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), "notification flash terminal theme foreground blue ring"), setting(.app, "pane-flash-double-blink", String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), "pane flash blink twice pulse"), setting(.app, "desktop-notifications", String(localized: "settings.notifications.desktop", defaultValue: "Desktop Notifications"), "permission alerts test notification"), setting(.app, "notification-sound", String(localized: "settings.notifications.sound.title", defaultValue: "Notification Sound"), "custom sound alert audio"), @@ -259,6 +260,7 @@ enum SettingsSearchIndex { "notifications.showInMenuBar": settingID(for: .app, idSuffix: "show-menu-bar"), "notifications.unreadPaneRing": settingID(for: .app, idSuffix: "unread-pane-ring"), "notifications.paneFlash": settingID(for: .app, idSuffix: "pane-flash"), + "notifications.paneFlashThemeColor": settingID(for: .app, idSuffix: "pane-flash-theme-color"), "notifications.paneFlashDoubleBlink": settingID(for: .app, idSuffix: "pane-flash-double-blink"), "notifications.sound": settingID(for: .app, idSuffix: "notification-sound"), "notifications.customSoundFilePath": settingID(for: .app, idSuffix: "notification-sound"), diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index dff88f475039..88cc9ed7341d 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -26,6 +26,8 @@ enum NotificationPaneFlashSettings { static let defaultEnabled = true static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" static let defaultDoubleBlink = true + static let themeColorKey = "notificationPaneFlashThemeColor" + static let defaultThemeColor = false /// Whether the pane flash blinks twice instead of one short pulse. static func usesDoubleBlink(defaults: UserDefaults = .standard) -> Bool { diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index 5c1c1f0bc3cc..832f07afbfdb 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -85,14 +85,15 @@ Notification behavior from Settings > Notifications. | `notifications.showInMenuBar` | boolean | `true` | Show the menu bar extra. | | `notifications.unreadPaneRing` | boolean | `true` | Highlight panes with unread notifications. | | `notifications.paneFlash` | boolean | `true` | Flash the focused pane when requested. | -| `notifications.paneFlashDoubleBlink` | boolean | `false` | Blink the pane flash twice instead of one short pulse. | +| `notifications.paneFlashDoubleBlink` | boolean | `true` | Use the default double blink; set false for the configurable single 0.6-second pulse. | | `notifications.sound` | `"default"` or `"Basso"` or `"Blow"` or `"Bottle"` or `"Frog"` or `"Funk"` or `"Glass"` or `"Hero"` or `"Morse"` or `"Ping"` or `"Pop"` or `"Purr"` or `"Sosumi"` or `"Submarine"` or `"Tink"` or `"custom_file"` or `"none"` | `"default"` | Notification sound preset. | | `notifications.soundWhenFocused` | boolean | `false` | Play the notification sound even when the pane that notified is already focused. Off by default, so a focused pane shows only its ring and flash. | | `notifications.customSoundFilePath` | string | `""` | Local path to the custom notification sound file. | | `notifications.command` | string | `""` | Optional shell command to run alongside notification delivery. | | `notifications.hooksMode` | `"append"` or `"replace"` | `"append"` | Controls whether project-local notification hooks append to inherited hooks or replace them. | | `notifications.hooks` | array | `[]` | Composable shell hooks that receive notification policy JSON on stdin and return updated policy JSON on stdout. | -| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent. | +| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null keeps the cmux blue default. | +| `notifications.paneFlashThemeColor` | boolean | `false` | Use the terminal theme foreground for flashes when paneFlashColor is unset; unread rings remain cmux blue. | | `notifications.suppressOnlyFocusedSurface` | boolean | `false` | When enabled, a notification banner is auto-withdrawn only when its surface is the exact focused surface. A banner delivered for a non-focused surface in the currently visible workspace stays up until you focus that surface (or click/dismiss it), instead of being retracted when the workspace becomes visible. Off preserves the legacy workspace-visibility withdraw. | | `notifications.suppressWhenAppFocused` | boolean | `false` | When enabled, cmux skips the desktop banner for every notification while cmux is the active app, not only for the focused pane. Notifications still appear in the sidebar, the sound and custom command still run, and phone forwarding is unchanged. Off keeps showing banners for other workspaces and panes while cmux is focused. | | `notifications.agentPermissionPrompt` | boolean | `true` | Notify when an agent (e.g. Claude Code) is blocked waiting for your permission to run a tool. On by default, since this is the alert you must act on to unblock the agent. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 95c93b1f3806..88c425670836 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1133,6 +1133,12 @@ "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, + "paneFlashThemeColor": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": false, + "description": "Use the terminal theme foreground for flashes when paneFlashColor is unset. Unread rings remain cmux blue." + }, "paneFlashColor": { "x-cmux-scopes": ["global"], "$ref": "#/$defs/colorHexOrNull", From 832235fd158a0d702b71ff11951b9efcaa3b9c74 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 05:08:58 -0700 Subject: [PATCH 10/23] fix: keep catalog default double blink and expose theme flash color --- .../CmuxSettings/Keys/NotificationsCatalogSection.swift | 2 +- .../Navigation/CuratedSettingEntry+Default.swift | 1 + ...eyboardShortcutModifierHoldHintsSettingsFileTests.swift | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index 8c489b1bfcbe..536e57214e50 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -29,7 +29,7 @@ public struct NotificationsCatalogSection: SettingCatalogSection { /// Blink the pane flash twice instead of one short pulse. public let paneFlashDoubleBlink = DefaultsKey( id: "notifications.paneFlashDoubleBlink", - defaultValue: false, + defaultValue: true, userDefaultsKey: "notificationPaneFlashDoubleBlink" ) diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift index 6e5b3331476f..924ac19a71eb 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift @@ -134,6 +134,7 @@ extension Array where Element == CuratedSettingEntry { .init(section: .app, id: "unread-pane-ring", title: String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), synonyms: "Unread Pane Ring notifications.unreadPaneRing blue border unread ring notification pane outline"), .init(section: .app, id: "pane-flash", title: String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), synonyms: "Pane Flash notifications.paneFlash flash blink highlight pane notification pulse"), .init(section: .app, id: "pane-flash-double-blink", title: String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), synonyms: "Double Blink notifications.paneFlashDoubleBlink pane flash blink twice pulse"), + .init(section: .app, id: "pane-flash-theme-color", title: String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), synonyms: "Use Theme Foreground notifications.paneFlashThemeColor pane flash terminal theme foreground"), .init( section: .app, id: "agent-permission-prompt", diff --git a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift index c6a1124fefa1..3ac5e955403a 100644 --- a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift +++ b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift @@ -226,17 +226,18 @@ struct PaneAttentionColorTests { let color = WorkspaceAttentionColor( configuredHex: nil, accent: accent, - themeForeground: NSColor(hex: "#CDD6F4") + themeForeground: NSColor(hex: "#CDD6F4"), + useThemeForeground: true ) #expect(color.flashNSColor.hexString() == "#CDD6F4") - #expect(color.flashNSColor.alphaComponent == WorkspaceAttentionColor.themeForegroundFlashAlpha) + #expect(color.flashNSColor.alphaComponent == 1) // Unread rings keep the accent. #expect(color.nsColor.hexString() == accent.dynamicNSColor.hexString()) } @Test func configuredHexOverridesThemeForegroundForFlash() { - let color = WorkspaceAttentionColor(configuredHex: "#ff69b4", themeForeground: .white) + let color = WorkspaceAttentionColor(configuredHex: "#ff69b4", themeForeground: .white, useThemeForeground: true) #expect(color.flashNSColor.hexString() == "#FF69B4") #expect(color.flashNSColor.alphaComponent == 1) } From 790ea9df0d185e1a42f9577c4c1926faef8206c0 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 11:46:21 -0700 Subject: [PATCH 11/23] fix localization coverage for machine and session settings --- Resources/Localizable.xcstrings | 2504 ++++++++++++++++++++++++++----- web/data/cmux.schema.json | 1 + web/messages/ar.json | 3 +- web/messages/bs.json | 3 +- web/messages/da.json | 3 +- web/messages/de.json | 3 +- web/messages/en.json | 3 +- web/messages/es.json | 3 +- web/messages/fr.json | 3 +- web/messages/it.json | 3 +- web/messages/ja.json | 3 +- web/messages/km.json | 3 +- web/messages/ko.json | 3 +- web/messages/no.json | 3 +- web/messages/pl.json | 3 +- web/messages/pt-BR.json | 3 +- web/messages/ru.json | 3 +- web/messages/th.json | 3 +- web/messages/tr.json | 3 +- web/messages/uk.json | 3 +- web/messages/zh-CN.json | 3 +- web/messages/zh-TW.json | 3 +- 22 files changed, 2195 insertions(+), 370 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index bdf0bb71d0c2..82dc6d2d66fe 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -237623,6 +237623,72 @@ "state": "translated", "value": "マシン一覧を取得できません — 最後の状態を表示中" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Machine list unavailable — showing last known" + } } } }, @@ -237682,6 +237748,72 @@ "state": "translated", "value": "マシンは変更されていません。cmux はクラウドサービスから一覧を読み込めませんでした。自動的に再試行します。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Your machines are unchanged. cmux couldn’t load the list from the Cloud service and retries on its own." + } } } }, @@ -237741,6 +237873,72 @@ "state": "translated", "value": "マシン一覧を読み込めません" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Can’t load the machine list" + } } } }, @@ -237800,6 +237998,72 @@ "state": "translated", "value": "オフライン — 最後の状態を表示中" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Offline — showing last known" + } } } }, @@ -237859,6 +238123,72 @@ "state": "translated", "value": "この Mac がオンラインに戻ると Cloud マシンを読み込みます。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines load when this Mac is back online." + } } } }, @@ -237918,6 +238248,72 @@ "state": "translated", "value": "ネットワークを待機中" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Waiting for network" + } } } }, @@ -237977,6 +238373,72 @@ "state": "translated", "value": "クラウドに再接続中…" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Reconnecting to Cloud…" + } } } }, @@ -260956,130 +261418,130 @@ } }, "mobile.pairing.error.teamUnavailable": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Could not load your cmux team. Check your internet connection, then try again." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "cmux チームを読み込めませんでした。インターネット接続を確認してから、もう一度お試しください。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无法加载你的 cmux 团队。请检查网络连接,然后重试。" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Dein cmux-Team konnte nicht geladen werden. Prüfe deine Internetverbindung und versuche es erneut." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Impossible de charger votre équipe cmux. Vérifiez votre connexion Internet, puis réessayez." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تعذّر تحميل فريق cmux الخاص بك. تحقّق من اتصالك بالإنترنت، ثم حاول مرة أخرى." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se pudo cargar tu equipo de cmux. Comprueba tu conexión a internet e inténtalo de nuevo." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "無法載入你的 cmux 團隊。請檢查網路連線,然後再試一次。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "cmux 팀을 불러올 수 없습니다. 인터넷 연결을 확인한 후 다시 시도하세요." - } - }, - "bs": { - "stringUnit": { - "state": "translated", - "value": "Nije moguće učitati vaš cmux tim. Provjerite internetsku vezu i pokušajte ponovo." - } - }, - "da": { - "stringUnit": { - "state": "translated", - "value": "Din cmux-team kunne ikke indlæses. Kontrollér din internetforbindelse, og prøv igen." - } - }, - "it": { - "stringUnit": { - "state": "translated", - "value": "Impossibile caricare il tuo team cmux. Controlla la connessione Internet e riprova." - } - }, - "km": { - "stringUnit": { - "state": "translated", - "value": "មិនអាចផ្ទុកក្រុម cmux របស់អ្នកបានទេ។ សូមពិនិត្យការតភ្ជាប់អ៊ីនធឺណិត ហើយព្យាយាមម្ដងទៀត។" - } - }, - "nb": { - "stringUnit": { - "state": "translated", - "value": "Kunne ikke laste inn cmux-teamet ditt. Kontroller internettilkoblingen, og prøv igjen." - } - }, - "pl": { - "stringUnit": { - "state": "translated", - "value": "Nie udało się załadować Twojego zespołu cmux. Sprawdź połączenie z internetem i spróbuj ponownie." - } - }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Não foi possível carregar sua equipe cmux. Verifique sua conexão com a internet e tente novamente." - } - }, - "ru": { - "stringUnit": { - "state": "translated", - "value": "Не удалось загрузить вашу команду cmux. Проверьте подключение к интернету и попробуйте ещё раз." - } - }, - "th": { - "stringUnit": { - "state": "translated", - "value": "ไม่สามารถโหลดทีม cmux ของคุณได้ ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลองอีกครั้ง" - } - }, - "tr": { - "stringUnit": { - "state": "translated", - "value": "cmux ekibiniz yüklenemedi. İnternet bağlantınızı kontrol edip tekrar deneyin." + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Could not load your cmux team. Check your internet connection, then try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux チームを読み込めませんでした。インターネット接続を確認してから、もう一度お試しください。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "无法加载你的 cmux 团队。请检查网络连接,然后重试。" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Dein cmux-Team konnte nicht geladen werden. Prüfe deine Internetverbindung und versuche es erneut." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Impossible de charger votre équipe cmux. Vérifiez votre connexion Internet, puis réessayez." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعذّر تحميل فريق cmux الخاص بك. تحقّق من اتصالك بالإنترنت، ثم حاول مرة أخرى." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "No se pudo cargar tu equipo de cmux. Comprueba tu conexión a internet e inténtalo de nuevo." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "無法載入你的 cmux 團隊。請檢查網路連線,然後再試一次。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux 팀을 불러올 수 없습니다. 인터넷 연결을 확인한 후 다시 시도하세요." + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nije moguće učitati vaš cmux tim. Provjerite internetsku vezu i pokušajte ponovo." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Din cmux-team kunne ikke indlæses. Kontrollér din internetforbindelse, og prøv igen." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Impossibile caricare il tuo team cmux. Controlla la connessione Internet e riprova." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "មិនអាចផ្ទុកក្រុម cmux របស់អ្នកបានទេ។ សូមពិនិត្យការតភ្ជាប់អ៊ីនធឺណិត ហើយព្យាយាមម្ដងទៀត។" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Kunne ikke laste inn cmux-teamet ditt. Kontroller internettilkoblingen, og prøv igjen." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nie udało się załadować Twojego zespołu cmux. Sprawdź połączenie z internetem i spróbuj ponownie." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Não foi possível carregar sua equipe cmux. Verifique sua conexão com a internet e tente novamente." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить вашу команду cmux. Проверьте подключение к интернету и попробуйте ещё раз." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ไม่สามารถโหลดทีม cmux ของคุณได้ ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลองอีกครั้ง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux ekibiniz yüklenemedi. İnternet bağlantınızı kontrol edip tekrar deneyin." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Не вдалося завантажити вашу команду cmux. Перевірте підключення до інтернету й спробуйте ще раз." + } + } } }, - "uk": { - "stringUnit": { - "state": "translated", - "value": "Не вдалося завантажити вашу команду cmux. Перевірте підключення до інтернету й спробуйте ще раз." - } - } - } -}, "mobile.pairing.error.noTicket": { "extractionState": "manual", "localizations": { @@ -336948,6 +337410,66 @@ "state": "translated", "value": "閃爍兩次" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } } } }, @@ -337013,6 +337535,66 @@ "state": "translated", "value": "窗格閃爍兩次,而不是一次短暫的脈衝。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } } } }, @@ -337078,6 +337660,66 @@ "state": "translated", "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 閃爍兩次" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } } } }, @@ -547622,14 +548264,54 @@ "value": "Workspace Color: %@" } }, - "de": {"stringUnit": {"state": "translated", "value": "Arbeitsbereichfarbe: %@"}}, - "fr": {"stringUnit": {"state": "translated", "value": "Couleur de l’espace de travail : %@"}}, - "ar": {"stringUnit": {"state": "translated", "value": "لون مساحة العمل: %@"}}, - "es": {"stringUnit": {"state": "translated", "value": "Color del espacio de trabajo: %@"}}, - "zh-Hant": {"stringUnit": {"state": "translated", "value": "工作區顏色:%@"}}, - "zh-Hans": {"stringUnit": {"state": "translated", "value": "工作区颜色:%@"}}, - "ko": {"stringUnit": {"state": "translated", "value": "작업 공간 색상: %@"}}, - "ja": {"stringUnit": {"state": "translated", "value": "ワークスペースの色: %@"}} + "de": { + "stringUnit": { + "state": "translated", + "value": "Arbeitsbereichfarbe: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Couleur de l’espace de travail : %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لون مساحة العمل: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Color del espacio de trabajo: %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "工作區顏色:%@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "工作区颜色:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 색상: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの色: %@" + } + } } }, "fileExplorer.preview.failedTitle": { @@ -565748,70 +566430,70 @@ "value": "cmux 重新開啟後仍在還原此終端機,並確認要恢復哪個代理工作階段。請幾秒後再試。" } }, - "bs" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux još vraća terminal nakon ponovnog otvaranja i provjerava koju sesiju agenta treba nastaviti. Pokušajte ponovo za nekoliko sekundi." + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux još vraća terminal nakon ponovnog otvaranja i provjerava koju sesiju agenta treba nastaviti. Pokušajte ponovo za nekoliko sekundi." } }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux gendanner stadig terminalen efter genåbning og tjekker, hvilken agentsession der skal genoptages. Prøv igen om et par sekunder." + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux gendanner stadig terminalen efter genåbning og tjekker, hvilken agentsession der skal genoptages. Prøv igen om et par sekunder." } }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux sta ancora ripristinando il terminale dopo la riapertura e verifica quale sessione dell’agente riprendere. Riprova tra qualche secondo." + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux sta ancora ripristinando il terminale dopo la riapertura e verifica quale sessione dell’agente riprendere. Riprova tra qualche secondo." } }, - "km" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux នៅតែកំពុងស្ដារទែមីណាល់ឡើងវិញ បន្ទាប់ពីបើកម្ដងទៀត ហើយកំពុងពិនិត្យមើលថាត្រូវបន្តវគ្គភ្នាក់ងារមួយណា។ សូមព្យាយាមម្ដងទៀតក្នុងរយៈពេលពីរបីវិនាទី។" + "km": { + "stringUnit": { + "state": "translated", + "value": "cmux នៅតែកំពុងស្ដារទែមីណាល់ឡើងវិញ បន្ទាប់ពីបើកម្ដងទៀត ហើយកំពុងពិនិត្យមើលថាត្រូវបន្តវគ្គភ្នាក់ងារមួយណា។ សូមព្យាយាមម្ដងទៀតក្នុងរយៈពេលពីរបីវិនាទី។" } }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux gjenoppretter fortsatt terminalen etter at den ble åpnet på nytt, og sjekker hvilken agentøkt som skal gjenopptas. Prøv igjen om noen sekunder." + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux gjenoppretter fortsatt terminalen etter at den ble åpnet på nytt, og sjekker hvilken agentøkt som skal gjenopptas. Prøv igjen om noen sekunder." } }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux wciąż przywraca terminal po ponownym otwarciu i sprawdza, którą sesję agenta wznowić. Spróbuj ponownie za kilka sekund." + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux wciąż przywraca terminal po ponownym otwarciu i sprawdza, którą sesję agenta wznowić. Spróbuj ponownie za kilka sekund." } }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "O cmux ainda está restaurando o terminal após ser reaberto e verificando qual sessão do agente retomar. Tente novamente em alguns segundos." + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O cmux ainda está restaurando o terminal após ser reaberto e verificando qual sessão do agente retomar. Tente novamente em alguns segundos." } }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux ещё восстанавливает терминал после повторного открытия и проверяет, какой сеанс агента возобновить. Повторите попытку через несколько секунд." + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux ещё восстанавливает терминал после повторного открытия и проверяет, какой сеанс агента возобновить. Повторите попытку через несколько секунд." } }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux ยังคงกู้คืนเทอร์มินัลหลังจากเปิดใหม่ และกำลังตรวจสอบว่าจะทำเซสชันเอเจนต์ใดต่อ ลองอีกครั้งในอีกไม่กี่วินาที" + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux ยังคงกู้คืนเทอร์มินัลหลังจากเปิดใหม่ และกำลังตรวจสอบว่าจะทำเซสชันเอเจนต์ใดต่อ ลองอีกครั้งในอีกไม่กี่วินาที" } }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux yeniden açıldıktan sonra terminali hâlâ geri yüklüyor ve hangi ajan oturumunun sürdürüleceğini denetliyor. Birkaç saniye sonra yeniden deneyin." + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux yeniden açıldıktan sonra terminali hâlâ geri yüklüyor ve hangi ajan oturumunun sürdürüleceğini denetliyor. Birkaç saniye sonra yeniden deneyin." } }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "cmux ще відновлює термінал після повторного відкриття та перевіряє, який сеанс агента відновити. Спробуйте ще раз за кілька секунд." + "uk": { + "stringUnit": { + "state": "translated", + "value": "cmux ще відновлює термінал після повторного відкриття та перевіряє, який сеанс агента відновити. Спробуйте ще раз за кілька секунд." } } } @@ -565873,70 +566555,70 @@ "value": "此終端機正在關閉,無法讀取。" } }, - "bs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal se zatvara i ne može se pročitati." + "bs": { + "stringUnit": { + "state": "translated", + "value": "Terminal se zatvara i ne može se pročitati." } }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen lukkes og kan ikke læses." + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminalen lukkes og kan ikke læses." } }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Il terminale si sta chiudendo e non può essere letto." + "it": { + "stringUnit": { + "state": "translated", + "value": "Il terminale si sta chiudendo e non può essere letto." } }, - "km" : { - "stringUnit" : { - "state" : "translated", - "value" : "ទែមីណាល់កំពុងបិទ ហើយមិនអាចអានបានទេ។" + "km": { + "stringUnit": { + "state": "translated", + "value": "ទែមីណាល់កំពុងបិទ ហើយមិនអាចអានបានទេ។" } }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen lukkes og kan ikke leses." + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminalen lukkes og kan ikke leses." } }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal jest zamykany i nie można go odczytać." + "pl": { + "stringUnit": { + "state": "translated", + "value": "Terminal jest zamykany i nie można go odczytać." } }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "O terminal está sendo fechado e não pode ser lido." + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O terminal está sendo fechado e não pode ser lido." } }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Терминал закрывается, и его нельзя прочитать." + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал закрывается, и его нельзя прочитать." } }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เทอร์มินัลกำลังปิดและไม่สามารถอ่านได้" + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัลกำลังปิดและไม่สามารถอ่านได้" } }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal kapanıyor ve okunamıyor." + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal kapanıyor ve okunamıyor." } }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Термінал закривається, і його не можна прочитати." + "uk": { + "stringUnit": { + "state": "translated", + "value": "Термінал закривається, і його не можна прочитати." } } } @@ -565998,70 +566680,70 @@ "value": "此終端機已休眠:cmux 暫停了其閒置的代理以節省記憶體。顯示它即可喚醒(這會將其視窗帶到最前面),然後再試一次:%@" } }, - "bs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal je u hibernaciji: cmux je pauzirao njegovog neaktivnog agenta radi uštede memorije. Probudite ga tako što ćete ga prikazati (njegov prozor prelazi u prvi plan), pa pokušajte ponovo: %@" + "bs": { + "stringUnit": { + "state": "translated", + "value": "Terminal je u hibernaciji: cmux je pauzirao njegovog neaktivnog agenta radi uštede memorije. Probudite ga tako što ćete ga prikazati (njegov prozor prelazi u prvi plan), pa pokušajte ponovo: %@" } }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen er i dvale: cmux har sat dens inaktive agent på pause for at spare hukommelse. Væk den ved at vise den (dens vindue kommer forrest), og prøv igen: %@" + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminalen er i dvale: cmux har sat dens inaktive agent på pause for at spare hukommelse. Væk den ved at vise den (dens vindue kommer forrest), og prøv igen: %@" } }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Il terminale è ibernato: cmux ha sospeso il suo agente inattivo per risparmiare memoria. Riattivalo mostrandolo (la sua finestra passa in primo piano), quindi riprova: %@" + "it": { + "stringUnit": { + "state": "translated", + "value": "Il terminale è ibernato: cmux ha sospeso il suo agente inattivo per risparmiare memoria. Riattivalo mostrandolo (la sua finestra passa in primo piano), quindi riprova: %@" } }, - "km" : { - "stringUnit" : { - "state" : "translated", - "value" : "ទែមីណាល់កំពុងសម្រាក៖ cmux បានផ្អាកភ្នាក់ងារដែលទំនេររបស់វា ដើម្បីសន្សំអង្គចងចាំ។ ដាស់វាដោយបង្ហាញវា (បង្អួចរបស់វានឹងមកខាងមុខ) រួចព្យាយាមម្ដងទៀត៖ %@" + "km": { + "stringUnit": { + "state": "translated", + "value": "ទែមីណាល់កំពុងសម្រាក៖ cmux បានផ្អាកភ្នាក់ងារដែលទំនេររបស់វា ដើម្បីសន្សំអង្គចងចាំ។ ដាស់វាដោយបង្ហាញវា (បង្អួចរបស់វានឹងមកខាងមុខ) រួចព្យាយាមម្ដងទៀត៖ %@" } }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen er i dvale: cmux har satt den inaktive agenten på pause for å spare minne. Vekk den ved å vise den (vinduet kommer i forgrunnen), og prøv igjen: %@" + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminalen er i dvale: cmux har satt den inaktive agenten på pause for å spare minne. Vekk den ved å vise den (vinduet kommer i forgrunnen), og prøv igjen: %@" } }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal jest uśpiony: cmux wstrzymał jego bezczynnego agenta, aby oszczędzić pamięć. Wybudź go, wyświetlając go (jego okno przejdzie na pierwszy plan), a potem spróbuj ponownie: %@" + "pl": { + "stringUnit": { + "state": "translated", + "value": "Terminal jest uśpiony: cmux wstrzymał jego bezczynnego agenta, aby oszczędzić pamięć. Wybudź go, wyświetlając go (jego okno przejdzie na pierwszy plan), a potem spróbuj ponownie: %@" } }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "O terminal está hibernado: o cmux suspendeu o agente ocioso para economizar memória. Desperte-o exibindo-o (a janela vem para a frente) e tente novamente: %@" + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O terminal está hibernado: o cmux suspendeu o agente ocioso para economizar memória. Desperte-o exibindo-o (a janela vem para a frente) e tente novamente: %@" } }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Терминал в спящем режиме: cmux приостановил неактивного агента, чтобы сэкономить память. Разбудите его, показав терминал (его окно выйдет на передний план), и повторите попытку: %@" + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал в спящем режиме: cmux приостановил неактивного агента, чтобы сэкономить память. Разбудите его, показав терминал (его окно выйдет на передний план), и повторите попытку: %@" } }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เทอร์มินัลอยู่ในโหมดพัก: cmux หยุดเอเจนต์ที่ไม่ได้ใช้งานชั่วคราวเพื่อประหยัดหน่วยความจำ ปลุกโดยแสดงเทอร์มินัล (หน้าต่างจะมาอยู่ด้านหน้า) แล้วลองอีกครั้ง: %@" + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัลอยู่ในโหมดพัก: cmux หยุดเอเจนต์ที่ไม่ได้ใช้งานชั่วคราวเพื่อประหยัดหน่วยความจำ ปลุกโดยแสดงเทอร์มินัล (หน้าต่างจะมาอยู่ด้านหน้า) แล้วลองอีกครั้ง: %@" } }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal hazırda bekliyor: cmux bellek tasarrufu için boştaki ajanını askıya aldı. Onu göstererek uyandırın (penceresi öne gelir), ardından yeniden deneyin: %@" + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal hazırda bekliyor: cmux bellek tasarrufu için boştaki ajanını askıya aldı. Onu göstererek uyandırın (penceresi öne gelir), ardından yeniden deneyin: %@" } }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Термінал у режимі сну: cmux призупинив неактивного агента, щоб заощадити пам’ять. Розбудіть його, показавши термінал (його вікно перейде на передній план), і спробуйте ще раз: %@" + "uk": { + "stringUnit": { + "state": "translated", + "value": "Термінал у режимі сну: cmux призупинив неактивного агента, щоб заощадити пам’ять. Розбудіть його, показавши термінал (його вікно перейде на передній план), і спробуйте ще раз: %@" } } } @@ -566123,70 +566805,70 @@ "value": "此終端機已休眠:cmux 暫停了其閒置的代理以節省記憶體。請在 cmux 中顯示此終端機以喚醒它,然後再試一次。" } }, - "bs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal je u hibernaciji: cmux je pauzirao njegovog neaktivnog agenta radi uštede memorije. Prikažite terminal u cmuxu da ga probudite, pa pokušajte ponovo." + "bs": { + "stringUnit": { + "state": "translated", + "value": "Terminal je u hibernaciji: cmux je pauzirao njegovog neaktivnog agenta radi uštede memorije. Prikažite terminal u cmuxu da ga probudite, pa pokušajte ponovo." } }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen er i dvale: cmux har sat dens inaktive agent på pause for at spare hukommelse. Vis terminalen i cmux for at vække den, og prøv igen." + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminalen er i dvale: cmux har sat dens inaktive agent på pause for at spare hukommelse. Vis terminalen i cmux for at vække den, og prøv igen." } }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Il terminale è ibernato: cmux ha sospeso il suo agente inattivo per risparmiare memoria. Mostra il terminale in cmux per riattivarlo, quindi riprova." + "it": { + "stringUnit": { + "state": "translated", + "value": "Il terminale è ibernato: cmux ha sospeso il suo agente inattivo per risparmiare memoria. Mostra il terminale in cmux per riattivarlo, quindi riprova." } }, - "km" : { - "stringUnit" : { - "state" : "translated", - "value" : "ទែមីណាល់កំពុងសម្រាក៖ cmux បានផ្អាកភ្នាក់ងារដែលទំនេររបស់វា ដើម្បីសន្សំអង្គចងចាំ។ បង្ហាញទែមីណាល់នៅក្នុង cmux ដើម្បីដាស់វា រួចព្យាយាមម្ដងទៀត។" + "km": { + "stringUnit": { + "state": "translated", + "value": "ទែមីណាល់កំពុងសម្រាក៖ cmux បានផ្អាកភ្នាក់ងារដែលទំនេររបស់វា ដើម្បីសន្សំអង្គចងចាំ។ បង្ហាញទែមីណាល់នៅក្នុង cmux ដើម្បីដាស់វា រួចព្យាយាមម្ដងទៀត។" } }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen er i dvale: cmux har satt den inaktive agenten på pause for å spare minne. Vis terminalen i cmux for å vekke den, og prøv igjen." + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminalen er i dvale: cmux har satt den inaktive agenten på pause for å spare minne. Vis terminalen i cmux for å vekke den, og prøv igjen." } }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal jest uśpiony: cmux wstrzymał jego bezczynnego agenta, aby oszczędzić pamięć. Wyświetl terminal w cmux, aby go wybudzić, a potem spróbuj ponownie." + "pl": { + "stringUnit": { + "state": "translated", + "value": "Terminal jest uśpiony: cmux wstrzymał jego bezczynnego agenta, aby oszczędzić pamięć. Wyświetl terminal w cmux, aby go wybudzić, a potem spróbuj ponownie." } }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "O terminal está hibernado: o cmux suspendeu o agente ocioso para economizar memória. Mostre o terminal no cmux para despertá-lo e tente novamente." + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O terminal está hibernado: o cmux suspendeu o agente ocioso para economizar memória. Mostre o terminal no cmux para despertá-lo e tente novamente." } }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Терминал в спящем режиме: cmux приостановил неактивного агента, чтобы сэкономить память. Покажите терминал в cmux, чтобы разбудить его, и повторите попытку." + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал в спящем режиме: cmux приостановил неактивного агента, чтобы сэкономить память. Покажите терминал в cmux, чтобы разбудить его, и повторите попытку." } }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เทอร์มินัลอยู่ในโหมดพัก: cmux หยุดเอเจนต์ที่ไม่ได้ใช้งานชั่วคราวเพื่อประหยัดหน่วยความจำ แสดงเทอร์มินัลใน cmux เพื่อปลุก แล้วลองอีกครั้ง" + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัลอยู่ในโหมดพัก: cmux หยุดเอเจนต์ที่ไม่ได้ใช้งานชั่วคราวเพื่อประหยัดหน่วยความจำ แสดงเทอร์มินัลใน cmux เพื่อปลุก แล้วลองอีกครั้ง" } }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal hazırda bekliyor: cmux bellek tasarrufu için boştaki ajanını askıya aldı. Uyandırmak için terminali cmux'ta gösterin, ardından yeniden deneyin." + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal hazırda bekliyor: cmux bellek tasarrufu için boştaki ajanını askıya aldı. Uyandırmak için terminali cmux'ta gösterin, ardından yeniden deneyin." } }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Термінал у режимі сну: cmux призупинив неактивного агента, щоб заощадити пам’ять. Покажіть термінал у cmux, щоб розбудити його, і спробуйте ще раз." + "uk": { + "stringUnit": { + "state": "translated", + "value": "Термінал у режимі сну: cmux призупинив неактивного агента, щоб заощадити пам’ять. Покажіть термінал у cmux, щоб розбудити його, і спробуйте ще раз." } } } @@ -566248,70 +566930,70 @@ "value": "此終端機尚未啟動。請幾秒後再試。" } }, - "bs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal se još nije pokrenuo. Pokušajte ponovo za nekoliko sekundi." + "bs": { + "stringUnit": { + "state": "translated", + "value": "Terminal se još nije pokrenuo. Pokušajte ponovo za nekoliko sekundi." } }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen er ikke startet endnu. Prøv igen om et par sekunder." + "da": { + "stringUnit": { + "state": "translated", + "value": "Terminalen er ikke startet endnu. Prøv igen om et par sekunder." } }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Il terminale non è ancora stato avviato. Riprova tra qualche secondo." + "it": { + "stringUnit": { + "state": "translated", + "value": "Il terminale non è ancora stato avviato. Riprova tra qualche secondo." } }, - "km" : { - "stringUnit" : { - "state" : "translated", - "value" : "ទែមីណាល់មិនទាន់បានចាប់ផ្តើមនៅឡើយទេ។ សូមព្យាយាមម្ដងទៀតក្នុងរយៈពេលពីរបីវិនាទី។" + "km": { + "stringUnit": { + "state": "translated", + "value": "ទែមីណាល់មិនទាន់បានចាប់ផ្តើមនៅឡើយទេ។ សូមព្យាយាមម្ដងទៀតក្នុងរយៈពេលពីរបីវិនាទី។" } }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminalen har ikke startet ennå. Prøv igjen om noen sekunder." + "nb": { + "stringUnit": { + "state": "translated", + "value": "Terminalen har ikke startet ennå. Prøv igjen om noen sekunder." } }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal jeszcze się nie uruchomił. Spróbuj ponownie za kilka sekund." + "pl": { + "stringUnit": { + "state": "translated", + "value": "Terminal jeszcze się nie uruchomił. Spróbuj ponownie za kilka sekund." } }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "O terminal ainda não foi iniciado. Tente novamente em alguns segundos." + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "O terminal ainda não foi iniciado. Tente novamente em alguns segundos." } }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Терминал ещё не запущен. Повторите попытку через несколько секунд." + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал ещё не запущен. Повторите попытку через несколько секунд." } }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เทอร์มินัลยังไม่ได้เริ่มทำงาน ลองอีกครั้งในอีกไม่กี่วินาที" + "th": { + "stringUnit": { + "state": "translated", + "value": "เทอร์มินัลยังไม่ได้เริ่มทำงาน ลองอีกครั้งในอีกไม่กี่วินาที" } }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Terminal henüz başlamadı. Birkaç saniye sonra yeniden deneyin." + "tr": { + "stringUnit": { + "state": "translated", + "value": "Terminal henüz başlamadı. Birkaç saniye sonra yeniden deneyin." } }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Термінал ще не запущено. Спробуйте ще раз за кілька секунд." + "uk": { + "stringUnit": { + "state": "translated", + "value": "Термінал ще не запущено. Спробуйте ще раз за кілька секунд." } } } @@ -566431,6 +567113,72 @@ "state": "translated", "value": "%@ 已存在。使用 --force 以取代它。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%@ already exists. Pass --force to replace it." + } } } }, @@ -566490,6 +567238,72 @@ "state": "translated", "value": "%@ 是此 cmux 本身的工作階段檔案。請選擇其他路徑。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%@ is this cmux's own session file. Choose another path." + } } } }, @@ -566549,6 +567363,72 @@ "state": "translated", "value": "cmux 尚未儲存工作階段。請過幾秒再試一次。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "cmux has not saved a session yet. Try again in a few seconds." + } } } }, @@ -566608,6 +567488,72 @@ "state": "translated", "value": "無法寫入 %@。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Could not write %@." + } } } }, @@ -566667,6 +567613,72 @@ "state": "translated", "value": "%@ 沒有已儲存的 cmux 工作階段。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "No saved cmux session at %@." + } } } }, @@ -566726,6 +567738,72 @@ "state": "translated", "value": "%1$@ 是由較新版本的 cmux 儲存(工作階段格式 %2$@,此 cmux 讀取 %3$@)。請更新 cmux 後再匯入。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%1$@ was saved by a newer cmux (session format %2$@, this cmux reads %3$@). Update cmux to import it." + } } } }, @@ -566785,6 +567863,72 @@ "state": "translated", "value": "%@ 沒有可還原的視窗。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%@ has no windows to restore." + } } } }, @@ -566844,6 +567988,72 @@ "state": "translated", "value": "%@ 不是 cmux 工作階段快照。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%@ is not a cmux session snapshot." + } } } }, @@ -566903,6 +568113,72 @@ "state": "translated", "value": "無法重新開啟 %@ 中的任何內容。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Nothing in %@ could be reopened." + } } } }, @@ -566962,6 +568238,72 @@ "state": "translated", "value": "%1$@ 使用較舊的工作階段格式(%2$@),此 cmux 已不再讀取(%3$@)。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%1$@ uses an older session format (%2$@) that this cmux no longer reads (%3$@)." + } } } }, @@ -567021,6 +568363,72 @@ "state": "translated", "value": "未知的 cmux 通道「%@」。請使用 stable、nightly、rc、staging、debug: 或工作階段檔案路徑。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Unknown cmux channel \"%@\". Use stable, nightly, rc, staging, debug:, or a path to a session file." + } } } }, @@ -567080,6 +568488,72 @@ "state": "translated", "value": "無法讀取 %@。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Could not read %@." + } } } }, @@ -567139,6 +568613,72 @@ "state": "translated", "value": "%@ はこの cmux が現在保存しているセッションです。前回の起動を再度開くには、--from を付けずに cmux restore-session を実行してください。" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "%@ is the session this cmux is saving right now. Run cmux restore-session without --from to reopen the previous launch." + } } } }, @@ -567198,6 +568738,72 @@ "state": "translated", "value": "升級" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } } } }, @@ -567257,6 +568863,72 @@ "state": "translated", "value": "重新登入" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Sign In" + } } } }, @@ -567316,6 +568988,72 @@ "state": "translated", "value": "雲端機器需要 cmux Pro,顯示上次已知的狀態" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Cloud machines need cmux Pro, showing last known" + } } } }, @@ -567375,6 +569113,72 @@ "state": "translated", "value": "登入狀態需要重新整理,顯示上次已知的狀態" } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Sign-in needs a refresh, showing last known" + } } } }, diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 88c425670836..9a74bef03d46 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1131,6 +1131,7 @@ "x-cmux-scopes": ["global"], "type": "boolean", "default": true, + "descriptionKey": "schemaDescriptions.notifications.paneFlashDoubleBlink", "description": "Blink the pane flash twice instead of one short pulse." }, "paneFlashThemeColor": { diff --git a/web/messages/ar.json b/web/messages/ar.json index 86e8353e11d9..512333f618db 100644 --- a/web/messages/ar.json +++ b/web/messages/ar.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "عند التفعيل، يتخطى cmux لافتة سطح المكتب لكل إشعار أثناء كون cmux التطبيق النشط، وليس فقط للجزء المُركَّز عليه. تظل الإشعارات ظاهرة في الشريط الجانبي، ويستمر تشغيل الصوت والأمر المخصص، ولا تتغير إعادة التوجيه إلى الهاتف. عند الإيقاف تستمر اللافتات في الظهور لمساحات العمل والأجزاء الأخرى أثناء التركيز على cmux.", "agentIdleReminder": "أرسل إشعارًا عندما يكون الوكيل خاملاً وينتظر إدخالك (بعد نحو 60 ثانية من انتهاء الدور). يُكتم الإشعار ما دام عمل الخلفية من الدور السابق قيد التنفيذ، حتى لا يؤدي تشغيل بناء أو مراقب إلى تنبيه انتظار خاطئ.", "soundOverrides": "تجاوزات متفرقة لصوت الإشعار لكل وكيل ولكل نوع تنبيه. تستخدم الخلايا المفقودة notifications.sound.", - "soundOverride": "تجاوز لصوت الإشعار. يستخدم default قيمة notifications.sound، ويعطّل none التشغيل، وتختار القيم الأخرى صوتًا مدمجًا أو مخصصًا." + "soundOverride": "تجاوز لصوت الإشعار. يستخدم default قيمة notifications.sound، ويعطّل none التشغيل، وتختار القيم الأخرى صوتًا مدمجًا أو مخصصًا.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "محرك البحث الافتراضي لاستعلامات شريط عنوان المتصفح التي ليست عناوين URL. استخدم custom مع customSearchEngineURLTemplate لموفري بحث آخرين.", diff --git a/web/messages/bs.json b/web/messages/bs.json index 5a38c10fedcc..6e54b696a4cb 100644 --- a/web/messages/bs.json +++ b/web/messages/bs.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Kada je uključeno, cmux preskače baner na radnoj površini za svaku notifikaciju dok je cmux aktivna aplikacija, a ne samo za fokusirani panel. Notifikacije se i dalje prikazuju u bočnoj traci, zvuk i prilagođena komanda se i dalje pokreću, a prosljeđivanje na telefon ostaje isto. Isključeno i dalje prikazuje banere za druge workspaceove i panele dok je cmux u fokusu.", "agentIdleReminder": "Pošalji obavijest kada agent miruje i čeka vaš unos (oko 60 sekundi nakon završetka turna). Utišano je dok je pozadinski rad prethodnog turna još na čekanju, pa pokrenuta izgradnja ili nadzor ne izazivaju pogrešnu obavijest o čekanju.", "soundOverrides": "Rijetka nadjačavanja zvuka obavijesti po agentu i vrsti upozorenja. Ćelije koje nedostaju koriste notifications.sound.", - "soundOverride": "Nadjačavanje zvuka obavijesti. default koristi notifications.sound, none onemogućava reprodukciju, a ostale vrijednosti biraju ugrađeni ili prilagođeni zvuk." + "soundOverride": "Nadjačavanje zvuka obavijesti. default koristi notifications.sound, none onemogućava reprodukciju, a ostale vrijednosti biraju ugrađeni ili prilagođeni zvuk.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Zadani pretraživač za upite u adresnoj traci preglednika koji nisu URL-ovi. Koristite custom uz customSearchEngineURLTemplate za druge pružatelje.", diff --git a/web/messages/da.json b/web/messages/da.json index 253b76d524b1..65bcb8c456d2 100644 --- a/web/messages/da.json +++ b/web/messages/da.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Når dette er slået til, springer cmux skrivebordsbanneret over for alle notifikationer, mens cmux er den aktive app, ikke kun for det fokuserede panel. Notifikationer vises stadig i sidebjælken, lyden og den brugerdefinerede kommando kører stadig, og videresendelse til telefonen er uændret. Fra bliver ved med at vise bannere for andre workspaces og paneler, mens cmux er i fokus.", "agentIdleReminder": "Send en notifikation, når en agent har været inaktiv og venter på dit input (ca. 60 sekunder efter en tur slutter). Undertrykkes, mens baggrundsarbejde fra den seneste tur stadig kører, så en igangværende build eller overvågning ikke udløser en falsk vente-notifikation.", "soundOverrides": "Sparsomme tilsidesættelser af notifikationslyd pr. agent og alarmtype. Manglende felter bruger notifications.sound.", - "soundOverride": "En tilsidesættelse af notifikationslyden. default bruger notifications.sound, none deaktiverer afspilning, og de øvrige værdier vælger en indbygget eller brugerdefineret lyd." + "soundOverride": "En tilsidesættelse af notifikationslyden. default bruger notifications.sound, none deaktiverer afspilning, og de øvrige værdier vælger en indbygget eller brugerdefineret lyd.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Standardsøgemaskine for forespørgsler i browserens adresselinje, der ikke er URLer. Brug custom med customSearchEngineURLTemplate til andre udbydere.", diff --git a/web/messages/de.json b/web/messages/de.json index d43d14b3b144..b6960e197898 100644 --- a/web/messages/de.json +++ b/web/messages/de.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Wenn aktiviert, überspringt cmux das Desktop-Banner für jede Benachrichtigung, solange cmux die aktive App ist, nicht nur für den fokussierten Bereich. Benachrichtigungen erscheinen weiterhin in der Seitenleiste, Ton und benutzerdefinierter Befehl werden weiterhin ausgeführt, und die Weiterleitung ans Telefon bleibt unverändert. Deaktiviert werden Banner für andere Workspaces und Bereiche weiterhin angezeigt, während cmux fokussiert ist.", "agentIdleReminder": "Benachrichtige, wenn ein Agent untätig auf deine Eingabe wartet (etwa 60 Sekunden nach Ende eines Turns). Wird unterdrückt, solange Hintergrundarbeit des letzten Turns noch aussteht, damit ein laufender Build oder Watcher keinen falschen Wartehinweis auslöst.", "soundOverrides": "Sparsame Überschreibungen des Benachrichtigungstons pro Agent und Alarmtyp. Nicht gesetzte Felder verwenden notifications.sound.", - "soundOverride": "Eine Überschreibung des Benachrichtigungstons. default verwendet notifications.sound, none deaktiviert die Wiedergabe; die übrigen Werte wählen einen integrierten oder benutzerdefinierten Ton." + "soundOverride": "Eine Überschreibung des Benachrichtigungstons. default verwendet notifications.sound, none deaktiviert die Wiedergabe; die übrigen Werte wählen einen integrierten oder benutzerdefinierten Ton.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Standardsuchmaschine für Nicht-URL-Eingaben in der Browser-Adressleiste. Verwenden Sie custom mit customSearchEngineURLTemplate für beliebige Anbieter.", diff --git a/web/messages/en.json b/web/messages/en.json index 20dfe9f5ac0b..df5f1d0f3983 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -2672,7 +2672,8 @@ "agentTurnComplete": "When to notify that an agent finished a turn. whenIdle (default) suppresses the notification while the agent still has a running background task or a pending scheduled wakeup, so you are pinged once work truly drains. always notifies on every turn end; never disables it.", "agentIdleReminder": "Notify when an agent has been idle waiting for your input (about 60s after a turn ends). Suppressed while background work from the last turn is still pending, so a running build or watcher does not trigger a false waiting alert.", "soundOverrides": "Sparse per-agent and per-alert-type notification sound overrides. Missing cells use notifications.sound.", - "soundOverride": "A notification sound override. `default` uses notifications.sound, `none` disables playback, and the remaining values select a built-in or custom sound." + "soundOverride": "A notification sound override. `default` uses notifications.sound, `none` disables playback, and the remaining values select a built-in or custom sound.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." } }, "exampleMarkdownFontSize": "Default body font size (points) for newly opened markdown viewers.", diff --git a/web/messages/es.json b/web/messages/es.json index a59594cacd61..b8b0dcb257f2 100644 --- a/web/messages/es.json +++ b/web/messages/es.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Cuando está activado, cmux omite el banner de escritorio para todas las notificaciones mientras cmux es la app activa, no solo para el panel enfocado. Las notificaciones siguen apareciendo en la barra lateral, el sonido y el comando personalizado se siguen ejecutando, y el reenvío al teléfono no cambia. Desactivado sigue mostrando banners de otros workspaces y paneles mientras cmux tiene el foco.", "agentIdleReminder": "Notifica cuando un agente ha estado inactivo esperando tu entrada (unos 60 segundos después de terminar un turno). Se suprime mientras siga pendiente el trabajo en segundo plano del turno anterior, para que una compilación o un monitor en ejecución no active una alerta de espera falsa.", "soundOverrides": "Anulaciones dispersas del sonido de notificación por agente y tipo de alerta. Las celdas ausentes usan notifications.sound.", - "soundOverride": "Una anulación del sonido de notificación. default usa notifications.sound, none desactiva la reproducción y los demás valores seleccionan un sonido integrado o personalizado." + "soundOverride": "Una anulación del sonido de notificación. default usa notifications.sound, none desactiva la reproducción y los demás valores seleccionan un sonido integrado o personalizado.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Motor de búsqueda predeterminado para consultas de la barra de direcciones del navegador que no son URL. Usa custom con customSearchEngineURLTemplate para otros proveedores.", diff --git a/web/messages/fr.json b/web/messages/fr.json index e4a0729297df..c0d6091078d3 100644 --- a/web/messages/fr.json +++ b/web/messages/fr.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Lorsque cette option est activée, cmux ignore la bannière du bureau pour chaque notification tant que cmux est l'app active, et pas seulement pour le panneau ayant le focus. Les notifications apparaissent toujours dans la barre latérale, le son et la commande personnalisée sont toujours exécutés, et le transfert vers le téléphone reste inchangé. Désactivée, les bannières restent affichées pour les autres workspaces et panneaux lorsque cmux a le focus.", "agentIdleReminder": "Notifier lorsqu’un agent est inactif et attend votre saisie (environ 60 secondes après la fin d’un tour). La notification est supprimée tant qu’un travail en arrière-plan du tour précédent est encore en attente, afin qu’un build ou un watcher en cours ne déclenche pas une fausse alerte d’attente.", "soundOverrides": "Remplacements ponctuels du son de notification par agent et par type d’alerte. Les cellules absentes utilisent notifications.sound.", - "soundOverride": "Un remplacement du son de notification. default utilise notifications.sound, none désactive la lecture et les autres valeurs sélectionnent un son intégré ou personnalisé." + "soundOverride": "Un remplacement du son de notification. default utilise notifications.sound, none désactive la lecture et les autres valeurs sélectionnent un son intégré ou personnalisé.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Moteur de recherche par défaut pour les saisies de la barre d’adresse du navigateur qui ne sont pas des URL. Utilisez custom avec customSearchEngineURLTemplate pour d’autres fournisseurs.", diff --git a/web/messages/it.json b/web/messages/it.json index dffa516ca839..4ac4523ee218 100644 --- a/web/messages/it.json +++ b/web/messages/it.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Quando è attivo, cmux salta il banner sul desktop per ogni notifica mentre cmux è l'app attiva, non solo per il pannello con il focus. Le notifiche compaiono comunque nella barra laterale, il suono e il comando personalizzato vengono comunque eseguiti e l'inoltro al telefono non cambia. Disattivato continua a mostrare i banner per altri workspace e pannelli mentre cmux ha il focus.", "agentIdleReminder": "Notifica quando un agente è inattivo in attesa del tuo input (circa 60 secondi dopo la fine di un turno). La notifica è soppressa finché resta del lavoro in background del turno precedente, così una build o un watcher in esecuzione non attivano un falso avviso di attesa.", "soundOverrides": "Override sparsi del suono di notifica per agente e tipo di avviso. Le celle mancanti usano notifications.sound.", - "soundOverride": "Un override del suono di notifica. default usa notifications.sound, none disabilita la riproduzione e gli altri valori selezionano un suono integrato o personalizzato." + "soundOverride": "Un override del suono di notifica. default usa notifications.sound, none disabilita la riproduzione e gli altri valori selezionano un suono integrato o personalizzato.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Motore di ricerca predefinito per le query nella barra degli indirizzi del browser che non sono URL. Usa custom con customSearchEngineURLTemplate per altri provider.", diff --git a/web/messages/ja.json b/web/messages/ja.json index d8677adfd7bd..44198329f629 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -2595,7 +2595,8 @@ "agentTurnComplete": "エージェントがターンを終えたことを通知するタイミングです。whenIdle(既定)は、実行中のバックグラウンドタスクや保留中のスケジュール再開が残っている間は通知を抑制し、作業が完全に片付いてから通知します。always はターン終了ごとに通知し、never は通知しません。", "agentIdleReminder": "エージェントがあなたの入力を待って待機状態になったとき(ターン終了の約60秒後)に通知します。前のターンのバックグラウンド作業がまだ実行中の間は抑制されるため、ビルドやウォッチャーの実行中に誤った待機通知が出ることはありません。", "soundOverrides": "エージェントとアラート種別ごとの通知サウンド上書きです。空のセルは notifications.sound を使用します。", - "soundOverride": "通知サウンドの上書きです。default は notifications.sound を使用し、none は再生を無効にします。それ以外の値では組み込みまたはカスタムサウンドを選択します。" + "soundOverride": "通知サウンドの上書きです。default は notifications.sound を使用し、none は再生を無効にします。それ以外の値では組み込みまたはカスタムサウンドを選択します。", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." } }, "exampleMarkdownFontSize": "新しく開く Markdown ビューアの本文フォントサイズ(ポイント単位)。", diff --git a/web/messages/km.json b/web/messages/km.json index 8ea320f37819..f6d23e0d386b 100644 --- a/web/messages/km.json +++ b/web/messages/km.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "នៅពេលបើក cmux នឹងរំលង banner លើផ្ទៃតុ សម្រាប់ការជូនដំណឹងទាំងអស់ ខណៈពេល cmux ជាកម្មវិធីសកម្ម មិនត្រឹមតែសម្រាប់ផ្ទាំងដែលកំពុងផ្តោតប៉ុណ្ណោះទេ។ ការជូនដំណឹងនៅតែបង្ហាញក្នុងរបារចំហៀង សំឡេង និងពាក្យបញ្ជាផ្ទាល់ខ្លួននៅតែដំណើរការ ហើយការបញ្ជូនបន្តទៅទូរសព្ទមិនផ្លាស់ប្តូរទេ។ បិទ នឹងបន្តបង្ហាញ banner សម្រាប់ workspace និងផ្ទាំងផ្សេងទៀត ខណៈពេល cmux កំពុងត្រូវបានផ្តោត។", "agentIdleReminder": "ជូនដំណឹងនៅពេលភ្នាក់ងារមិនសកម្ម ហើយកំពុងរង់ចាំការបញ្ចូលរបស់អ្នក (ប្រហែល ៦០ វិនាទីបន្ទាប់ពីវេនបញ្ចប់)។ វាត្រូវបានទប់ស្កាត់ ខណៈការងារផ្ទៃខាងក្រោយពីវេនមុននៅតែរង់ចាំ ដើម្បីកុំឱ្យ build ឬ watcher ដែលកំពុងដំណើរការបង្កើតការជូនដំណឹងរង់ចាំខុស។", "soundOverrides": "ការបដិសេធសំឡេងជូនដំណឹងតាមភ្នាក់ងារ និងប្រភេទការជូនដំណឹង។ ក្រឡាដែលបាត់ប្រើ notifications.sound។", - "soundOverride": "ការបដិសេធសំឡេងជូនដំណឹង។ default ប្រើ notifications.sound, none បិទការចាក់សំឡេង ហើយតម្លៃផ្សេងទៀតជ្រើសសំឡេងដែលភ្ជាប់មកជាមួយ ឬសំឡេងផ្ទាល់ខ្លួន។" + "soundOverride": "ការបដិសេធសំឡេងជូនដំណឹង។ default ប្រើ notifications.sound, none បិទការចាក់សំឡេង ហើយតម្លៃផ្សេងទៀតជ្រើសសំឡេងដែលភ្ជាប់មកជាមួយ ឬសំឡេងផ្ទាល់ខ្លួន។", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "ម៉ាស៊ីនស្វែងរកលំនាំដើមសម្រាប់សំណួរនៅក្នុងរបារអាសយដ្ឋានដែលមិនមែនជា URL។ ប្រើ custom ជាមួយ customSearchEngineURLTemplate សម្រាប់អ្នកផ្តល់ផ្សេងៗ។", diff --git a/web/messages/ko.json b/web/messages/ko.json index abc974ddb275..4ca3ee385b69 100644 --- a/web/messages/ko.json +++ b/web/messages/ko.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "활성화하면 cmux가 활성 앱인 동안 포커스된 패널뿐 아니라 모든 알림에 대해 데스크톱 배너를 건너뜁니다. 알림은 계속 사이드바에 표시되고, 사운드와 사용자 지정 명령도 계속 실행되며, 휴대폰 전달은 바뀌지 않습니다. 끄면 cmux에 포커스가 있어도 다른 workspace와 패널의 배너가 계속 표시됩니다.", "agentIdleReminder": "에이전트가 유휴 상태로 입력을 기다릴 때(턴 종료 후 약 60초) 알림을 보냅니다. 이전 턴의 백그라운드 작업이 아직 남아 있는 동안에는 억제되므로 실행 중인 빌드나 감시자가 잘못된 대기 알림을 만들지 않습니다.", "soundOverrides": "에이전트 및 알림 유형별 알림 소리 재정의입니다. 비어 있는 셀은 notifications.sound를 사용합니다.", - "soundOverride": "알림 소리 재정의입니다. default는 notifications.sound를 사용하고, none은 재생을 끄며, 나머지 값은 내장 또는 사용자 지정 소리를 선택합니다." + "soundOverride": "알림 소리 재정의입니다. default는 notifications.sound를 사용하고, none은 재생을 끄며, 나머지 값은 내장 또는 사용자 지정 소리를 선택합니다.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "브라우저 주소 표시줄에서 URL이 아닌 입력에 사용할 기본 검색 엔진입니다. 다른 제공자는 custom과 customSearchEngineURLTemplate을 사용하세요.", diff --git a/web/messages/no.json b/web/messages/no.json index 1eb91aeb0f21..5141d0e8adc9 100644 --- a/web/messages/no.json +++ b/web/messages/no.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Når dette er slått på, hopper cmux over skrivebordsbanneret for alle varsler mens cmux er den aktive appen, ikke bare for det fokuserte panelet. Varsler vises fortsatt i sidefeltet, lyden og den egendefinerte kommandoen kjøres fortsatt, og videresending til telefonen er uendret. Av fortsetter å vise bannere for andre workspaces og paneler mens cmux er i fokus.", "agentIdleReminder": "Varsle når en agent har vært inaktiv og venter på inndata (omtrent 60 sekunder etter at en tur er avsluttet). Varslingen undertrykkes mens bakgrunnsarbeid fra forrige tur fortsatt pågår, slik at en kjørende bygging eller overvåker ikke utløser et falskt ventevarsel.", "soundOverrides": "Sparsomme overstyringer av varslingslyd per agent og varslingstype. Manglende felt bruker notifications.sound.", - "soundOverride": "En overstyring av varslingslyden. default bruker notifications.sound, none deaktiverer avspilling, og de øvrige verdiene velger en innebygd eller egendefinert lyd." + "soundOverride": "En overstyring av varslingslyden. default bruker notifications.sound, none deaktiverer avspilling, og de øvrige verdiene velger en innebygd eller egendefinert lyd.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Standard søkemotor for søk i nettleserens adresselinje som ikke er URLer. Bruk custom med customSearchEngineURLTemplate for andre leverandører.", diff --git a/web/messages/pl.json b/web/messages/pl.json index 23c4d66f72b1..e1cc05f4c068 100644 --- a/web/messages/pl.json +++ b/web/messages/pl.json @@ -1384,7 +1384,8 @@ "suppressWhenAppFocused": "Po włączeniu cmux pomija baner na pulpicie dla każdego powiadomienia, gdy cmux jest aktywną aplikacją, a nie tylko dla panelu z fokusem. Powiadomienia nadal pojawiają się na pasku bocznym, dźwięk i niestandardowe polecenie nadal są uruchamiane, a przekazywanie na telefon się nie zmienia. Po wyłączeniu banery dla innych workspace'ów i paneli są nadal wyświetlane, gdy cmux ma fokus.", "agentIdleReminder": "Powiadamiaj, gdy agent jest bezczynny i czeka na dane wejściowe (około 60 sekund po zakończeniu tury). Powiadomienie jest wstrzymywane, dopóki trwa praca w tle z poprzedniej tury, aby uruchomiony build lub obserwator nie wywołał fałszywego alertu oczekiwania.", "soundOverrides": "Rzadkie zastąpienia dźwięku powiadomień dla agenta i typu alertu. Brakujące komórki używają notifications.sound.", - "soundOverride": "Zastąpienie dźwięku powiadomienia. default używa notifications.sound, none wyłącza odtwarzanie, a pozostałe wartości wybierają dźwięk wbudowany lub niestandardowy." + "soundOverride": "Zastąpienie dźwięku powiadomienia. default używa notifications.sound, none wyłącza odtwarzanie, a pozostałe wartości wybierają dźwięk wbudowany lub niestandardowy.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Domyślna wyszukiwarka dla wpisów w pasku adresu przeglądarki, które nie są URL-ami. Użyj custom z customSearchEngineURLTemplate dla innych dostawców.", diff --git a/web/messages/pt-BR.json b/web/messages/pt-BR.json index 09f39821235e..e77708d14a04 100644 --- a/web/messages/pt-BR.json +++ b/web/messages/pt-BR.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Quando ativado, o cmux ignora o banner da área de trabalho em todas as notificações enquanto o cmux é o app ativo, não apenas no painel em foco. As notificações continuam aparecendo na barra lateral, o som e o comando personalizado continuam sendo executados, e o encaminhamento para o celular não muda. Desativado continua mostrando banners de outros workspaces e painéis enquanto o cmux está em foco.", "agentIdleReminder": "Notifique quando um agente estiver ocioso aguardando sua entrada (cerca de 60 segundos após o fim de um turno). A notificação é suprimida enquanto ainda houver trabalho em segundo plano do turno anterior, para que um build ou monitor em execução não gere um falso alerta de espera.", "soundOverrides": "Substituições esparsas do som de notificação por agente e tipo de alerta. Células ausentes usam notifications.sound.", - "soundOverride": "Uma substituição do som de notificação. default usa notifications.sound, none desativa a reprodução e os demais valores selecionam um som integrado ou personalizado." + "soundOverride": "Uma substituição do som de notificação. default usa notifications.sound, none desativa a reprodução e os demais valores selecionam um som integrado ou personalizado.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Mecanismo de busca padrão para consultas na barra de endereço do navegador que não são URLs. Use custom com customSearchEngineURLTemplate para outros provedores.", diff --git a/web/messages/ru.json b/web/messages/ru.json index 16fd64f8a289..22356f19f6f8 100644 --- a/web/messages/ru.json +++ b/web/messages/ru.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Если включено, cmux пропускает баннер на рабочем столе для всех уведомлений, пока cmux является активным приложением, а не только для панели в фокусе. Уведомления по-прежнему появляются на боковой панели, звук и пользовательская команда по-прежнему выполняются, а пересылка на телефон не меняется. Если выключено, баннеры для других workspace и панелей продолжают показываться, пока cmux в фокусе.", "agentIdleReminder": "Уведомлять, когда агент бездействует и ждёт вашего ввода (примерно через 60 секунд после завершения хода). Уведомление подавляется, пока ещё выполняется фоновая работа предыдущего хода, чтобы запущенная сборка или наблюдатель не вызвали ложное уведомление об ожидании.", "soundOverrides": "Разреженные переопределения звука уведомлений для каждого агента и типа оповещения. Для отсутствующих ячеек используется notifications.sound.", - "soundOverride": "Переопределение звука уведомления. default использует notifications.sound, none отключает воспроизведение, а остальные значения выбирают встроенный или пользовательский звук." + "soundOverride": "Переопределение звука уведомления. default использует notifications.sound, none отключает воспроизведение, а остальные значения выбирают встроенный или пользовательский звук.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Поисковая система по умолчанию для запросов в адресной строке браузера, которые не являются URL. Используйте custom с customSearchEngineURLTemplate для других провайдеров.", diff --git a/web/messages/th.json b/web/messages/th.json index 273c158f662a..738116305a78 100644 --- a/web/messages/th.json +++ b/web/messages/th.json @@ -1384,7 +1384,8 @@ "suppressWhenAppFocused": "เมื่อเปิดใช้ cmux จะข้ามแบนเนอร์บนเดสก์ท็อปสำหรับการแจ้งเตือนทุกรายการขณะที่ cmux เป็นแอปที่ใช้งานอยู่ ไม่ใช่เฉพาะบานหน้าต่างที่โฟกัสอยู่ การแจ้งเตือนยังคงแสดงในแถบด้านข้าง เสียงกับคำสั่งกำหนดเองยังคงทำงาน และการส่งต่อไปยังโทรศัพท์ไม่เปลี่ยนแปลง เมื่อปิดจะยังแสดงแบนเนอร์สำหรับ workspace และบานหน้าต่างอื่นขณะที่ cmux อยู่ในโฟกัส", "agentIdleReminder": "แจ้งเตือนเมื่อเอเจนต์ไม่ได้ใช้งานและกำลังรออินพุตของคุณ (ประมาณ 60 วินาทีหลังจบรอบ) ระบบจะระงับการแจ้งเตือนขณะที่งานเบื้องหลังจากรอบก่อนหน้ายังค้างอยู่ เพื่อไม่ให้บิลด์หรือ watcher ที่กำลังทำงานแจ้งเตือนการรอแบบผิดพลาด", "soundOverrides": "การแทนที่เสียงการแจ้งเตือนแบบแยกตามเอเจนต์และประเภทการแจ้งเตือน ช่องที่ไม่มีค่าจะใช้ notifications.sound", - "soundOverride": "การแทนที่เสียงการแจ้งเตือน default ใช้ notifications.sound, none ปิดการเล่น และค่าอื่นจะเลือกเสียงในตัวหรือเสียงกำหนดเอง" + "soundOverride": "การแทนที่เสียงการแจ้งเตือน default ใช้ notifications.sound, none ปิดการเล่น และค่าอื่นจะเลือกเสียงในตัวหรือเสียงกำหนดเอง", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "เครื่องมือค้นหาเริ่มต้นสำหรับคำค้นในแถบที่อยู่ของเบราว์เซอร์ที่ไม่ใช่ URL ใช้ custom กับ customSearchEngineURLTemplate สำหรับผู้ให้บริการอื่น", diff --git a/web/messages/tr.json b/web/messages/tr.json index 3d4c424a5862..59f4a53e8fe8 100644 --- a/web/messages/tr.json +++ b/web/messages/tr.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "Etkinleştirildiğinde cmux, etkin uygulama olduğu sürece yalnızca odaklanılan bölme için değil tüm bildirimler için masaüstü başlığını atlar. Bildirimler kenar çubuğunda görünmeye devam eder, ses ve özel komut çalışmaya devam eder ve telefona yönlendirme değişmez. Kapalıyken cmux odaktayken diğer workspace'ler ve bölmeler için başlıklar gösterilmeye devam eder.", "agentIdleReminder": "Bir ajan boşta olup girişinizi beklediğinde (tur sona erdikten yaklaşık 60 saniye sonra) bildirim gönderin. Önceki turdan kalan arka plan işi devam ederken bastırılır; böylece çalışan bir derleme veya izleyici yanlış bir bekleme uyarısı oluşturmaz.", "soundOverrides": "Ajan ve uyarı türüne göre seyrek bildirim sesi geçersiz kılmaları. Eksik hücreler notifications.sound kullanır.", - "soundOverride": "Bir bildirim sesi geçersiz kılması. default, notifications.sound kullanır; none oynatmayı devre dışı bırakır; diğer değerler yerleşik veya özel bir ses seçer." + "soundOverride": "Bir bildirim sesi geçersiz kılması. default, notifications.sound kullanır; none oynatmayı devre dışı bırakır; diğer değerler yerleşik veya özel bir ses seçer.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Tarayıcı adres çubuğundaki URL olmayan sorgular için varsayılan arama motoru. Diğer sağlayıcılar için custom ile customSearchEngineURLTemplate kullanın.", diff --git a/web/messages/uk.json b/web/messages/uk.json index 38fda67a1bbf..31b9e924d034 100644 --- a/web/messages/uk.json +++ b/web/messages/uk.json @@ -1363,7 +1363,8 @@ "suppressWhenAppFocused": "Якщо ввімкнено, cmux пропускає банер на робочому столі для всіх сповіщень, поки cmux є активною програмою, а не лише для панелі у фокусі. Сповіщення й надалі з'являються на бічній панелі, звук і власна команда й надалі виконуються, а пересилання на телефон не змінюється. Якщо вимкнено, банери для інших workspace і панелей і далі показуються, поки cmux у фокусі.", "agentIdleReminder": "Сповіщати, коли агент неактивний і чекає на ваше введення (приблизно через 60 секунд після завершення ходу). Сповіщення пригнічується, поки триває фонова робота попереднього ходу, щоб запущена збірка або спостерігач не створювали хибне сповіщення очікування.", "soundOverrides": "Розріджені перевизначення звуку сповіщень для кожного агента й типу сповіщення. Порожні клітинки використовують notifications.sound.", - "soundOverride": "Перевизначення звуку сповіщення. default використовує notifications.sound, none вимикає відтворення, а інші значення вибирають вбудований або власний звук." + "soundOverride": "Перевизначення звуку сповіщення. default використовує notifications.sound, none вимикає відтворення, а інші значення вибирають вбудований або власний звук.", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "Пошукова система за замовчуванням для запитів в адресному рядку браузера, які не є URL. Для інших провайдерів використовуйте custom з customSearchEngineURLTemplate.", diff --git a/web/messages/zh-CN.json b/web/messages/zh-CN.json index 6e03a370a69f..e2bdd0056326 100644 --- a/web/messages/zh-CN.json +++ b/web/messages/zh-CN.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "启用后,只要 cmux 是当前活跃的应用,cmux 就会对所有通知跳过桌面横幅,而不仅限于获得焦点的窗格。通知仍会显示在侧边栏中,声音和自定义命令也仍会运行,转发到手机的行为不变。关闭时,即使 cmux 获得焦点,其他 workspace 和窗格的横幅也会继续显示。", "agentIdleReminder": "当代理空闲并等待你的输入时(回合结束后约 60 秒)发送通知。只要上一回合的后台工作仍在等待,就会抑制通知,避免正在运行的构建或监视器触发错误的等待提醒。", "soundOverrides": "按代理和提醒类型设置的稀疏通知声音覆盖。缺少的单元格使用 notifications.sound。", - "soundOverride": "通知声音覆盖。default 使用 notifications.sound,none 禁用播放,其余值选择内置或自定义声音。" + "soundOverride": "通知声音覆盖。default 使用 notifications.sound,none 禁用播放,其余值选择内置或自定义声音。", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "浏览器地址栏输入不是 URL 时使用的默认搜索引擎。对于任意提供商,请使用 custom 和 customSearchEngineURLTemplate。", diff --git a/web/messages/zh-TW.json b/web/messages/zh-TW.json index 757ff526a18b..fc54a2bc125e 100644 --- a/web/messages/zh-TW.json +++ b/web/messages/zh-TW.json @@ -1383,7 +1383,8 @@ "suppressWhenAppFocused": "啟用後,只要 cmux 是目前使用中的 App,cmux 就會對所有通知略過桌面橫幅,而不只限於取得焦點的窗格。通知仍會顯示在側邊欄中,音效與自訂指令也仍會執行,轉送到手機的行為不變。關閉時,即使 cmux 取得焦點,其他 workspace 與窗格的橫幅也會繼續顯示。", "agentIdleReminder": "當代理程式閒置並等待你的輸入時(回合結束後約 60 秒)傳送通知。只要上一回合的背景工作仍在等待,就會抑制通知,避免正在執行的建置或監看程式觸發錯誤的等待提醒。", "soundOverrides": "依代理程式和提醒類型設定的稀疏通知聲音覆寫。缺少的儲存格會使用 notifications.sound。", - "soundOverride": "通知聲音覆寫。default 使用 notifications.sound,none 停用播放,其餘值選擇內建或自訂聲音。" + "soundOverride": "通知聲音覆寫。default 使用 notifications.sound,none 停用播放,其餘值選擇內建或自訂聲音。", + "paneFlashDoubleBlink": "Blink the pane flash twice instead of one short pulse." }, "browser": { "defaultSearchEngine": "瀏覽器位址列輸入不是 URL 時使用的預設搜尋引擎。任意提供者請使用 custom 與 customSearchEngineURLTemplate。", From e25a80eff3bc07b997953b31b85a1b56c2733b5a Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sat, 26 Sep 2026 22:40:57 -0400 Subject: [PATCH 12/23] Replace the focus double blink with one short pulse Focus navigation, focus-history jumps, and notification dismissal all play FocusFlashPattern: two blinks over 0.9 s. On the pane-focus hot path that repeats every time. One 0.4 s pulse still marks where focus landed. Co-Authored-By: Claude Opus 5.5 --- Sources/Panels/Panel.swift | 10 ++++++---- cmuxTests/WindowAndDragTests.swift | 31 +++++++++++------------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index c3b86fe519a2..ca690b92b8f6 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -252,11 +252,13 @@ struct FocusFlashSegment: Equatable { let curve: FocusFlashCurve } +/// One short pulse: enough to say where focus or attention landed without +/// replaying a blink every time someone moves between panes. enum FocusFlashPattern { - static let values: [Double] = [0, 1, 0, 1, 0] - static let keyTimes: [Double] = [0, 0.25, 0.5, 0.75, 1] - static let duration: TimeInterval = 0.9 - static let curves: [FocusFlashCurve] = [.easeOut, .easeIn, .easeOut, .easeIn] + static let values: [Double] = [0, 1, 0] + static let keyTimes: [Double] = [0, 0.3, 1] + static let duration: TimeInterval = 0.4 + static let curves: [FocusFlashCurve] = [.easeOut, .easeIn] static let ringInset: Double = Double(PanelOverlayRingMetrics.inset) static let ringCornerRadius: Double = Double(PanelOverlayRingMetrics.cornerRadius) diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index 1e3f89a5b772..af5300a8b099 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -740,38 +740,29 @@ final class TerminalDefaultFileOpenRequestTests: XCTestCase { final class FocusFlashPatternTests: XCTestCase { - func testFocusFlashPatternMatchesTerminalDoublePulseShape() { - XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0, 1, 0]) - XCTAssertEqual(FocusFlashPattern.keyTimes, [0, 0.25, 0.5, 0.75, 1]) - XCTAssertEqual(FocusFlashPattern.duration, 0.9, accuracy: 0.0001) - XCTAssertEqual(FocusFlashPattern.curves, [.easeOut, .easeIn, .easeOut, .easeIn]) + func testFocusFlashPatternIsOneShortPulse() { + XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0]) + XCTAssertEqual(FocusFlashPattern.keyTimes, [0, 0.3, 1]) + XCTAssertEqual(FocusFlashPattern.duration, 0.4, accuracy: 0.0001) + XCTAssertEqual(FocusFlashPattern.curves, [.easeOut, .easeIn]) XCTAssertEqual(FocusFlashPattern.ringInset, Double(PanelOverlayRingMetrics.inset), accuracy: 0.0001) XCTAssertEqual(FocusFlashPattern.ringCornerRadius, Double(PanelOverlayRingMetrics.cornerRadius), accuracy: 0.0001) } - func testFocusFlashPatternSegmentsCoverFullDoublePulseTimeline() { + func testFocusFlashPatternSegmentsCoverThePulse() { let segments = FocusFlashPattern.segments - XCTAssertEqual(segments.count, 4) + XCTAssertEqual(segments.count, 2) XCTAssertEqual(segments[0].delay, 0.0, accuracy: 0.0001) - XCTAssertEqual(segments[0].duration, 0.225, accuracy: 0.0001) + XCTAssertEqual(segments[0].duration, 0.12, accuracy: 0.0001) XCTAssertEqual(segments[0].targetOpacity, 1, accuracy: 0.0001) XCTAssertEqual(segments[0].curve, .easeOut) - XCTAssertEqual(segments[1].delay, 0.225, accuracy: 0.0001) - XCTAssertEqual(segments[1].duration, 0.225, accuracy: 0.0001) + XCTAssertEqual(segments[1].delay, 0.12, accuracy: 0.0001) + XCTAssertEqual(segments[1].duration, 0.28, accuracy: 0.0001) XCTAssertEqual(segments[1].targetOpacity, 0, accuracy: 0.0001) XCTAssertEqual(segments[1].curve, .easeIn) - - XCTAssertEqual(segments[2].delay, 0.45, accuracy: 0.0001) - XCTAssertEqual(segments[2].duration, 0.225, accuracy: 0.0001) - XCTAssertEqual(segments[2].targetOpacity, 1, accuracy: 0.0001) - XCTAssertEqual(segments[2].curve, .easeOut) - - XCTAssertEqual(segments[3].delay, 0.675, accuracy: 0.0001) - XCTAssertEqual(segments[3].duration, 0.225, accuracy: 0.0001) - XCTAssertEqual(segments[3].targetOpacity, 0, accuracy: 0.0001) - XCTAssertEqual(segments[3].curve, .easeIn) + XCTAssertEqual(FocusFlashPattern.opacity(at: 0.4), 0, accuracy: 0.0001) } } From e09a396a998aff0d42539673ce61dc5727870281 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 02:38:06 -0400 Subject: [PATCH 13/23] Don't flash a pane when typing is what dismissed its notification Typing into a terminal dismisses its notification or unread mark and used to play the dismiss flash on the pane under the user's cursor. The ring clearing is the acknowledgement there. Clicks, jumps and workspace selection still flash, since they move the user's eye to a pane. Co-Authored-By: Claude Opus 5.5 --- .../NotificationDismissalContext.swift | 7 +++++++ .../NotificationDismissalModel.swift | 2 +- .../NotificationDismissalModelTests.swift | 13 +++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift index fe79e58b024f..94d0440932d6 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift @@ -22,6 +22,13 @@ public enum NotificationDismissalContext: Sendable { } } + /// Whether a dismissal in this context flashes the pane. Typing means the + /// user is already working in that pane, so the cleared ring is the + /// acknowledgement; a flash under their cursor only adds noise. + public var flashesOnDismiss: Bool { + self != .terminalInteraction + } + /// Whether this context may clear a manually-set unread indicator. public var canDismissManualUnreadIndicator: Bool { self == .terminalInteraction diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift index 270b27760081..2ce3d20b5826 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift @@ -240,7 +240,7 @@ public final class NotificationDismissalModel: NotificationDismissing { host.storeClearFocusedReadIndicator(workspaceId: workspaceId, surfaceId: surfaceId) } } - if let targetPanelId { + if let targetPanelId, context.flashesOnDismiss { if hasUnreadNotification || hasFocusedIndicator { host.workspaceTriggerNotificationDismissFlash(workspaceId: workspaceId, panelId: targetPanelId) } else if didDismissUnreadIndicator { diff --git a/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift b/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift index f60f7aa64426..5b360f46f9fe 100644 --- a/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift +++ b/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift @@ -250,16 +250,25 @@ struct NotificationDismissalModelTests { #expect(!model.dismissNotificationOnDirectInteraction(workspaceId: workspaceId, surfaceId: panelId)) #expect(host.log.isEmpty) - // Terminal interaction clears it and triggers the indicator flash. + // Terminal interaction clears it without flashing the pane being typed in. #expect(model.dismissNotificationOnTerminalInteraction(workspaceId: workspaceId, surfaceId: panelId)) let prefix = String(panelId.uuidString.prefix(4)) #expect(host.log == [ "panelClearManualUnread", "storeClearManualUnread", "storeClearManualUnread:\(prefix)", - "clearFocusedRead:\(prefix)", "unreadIndicatorFlash", + "clearFocusedRead:\(prefix)", ]) } + @Test func typingDismissesAnUnreadNotificationWithoutFlashing() { + let (model, host, workspaceId, panelId) = makeModel() + host.unreadNotificationSurfaces = [panelId] + + #expect(model.dismissNotificationOnTerminalInteraction(workspaceId: workspaceId, surfaceId: panelId)) + let prefix = String(panelId.uuidString.prefix(4)) + #expect(host.log == ["markRead:\(prefix)", "clearFocusedRead:\(prefix)"]) + } + @Test func restoredUnreadNotClearedByPlainActiveFocus() { let (model, host, workspaceId, panelId) = makeModel() host.restoredPanelUnread = [panelId] From ed5f4b582fec978cf16e30658032956a07a94d74 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 05:00:23 -0400 Subject: [PATCH 14/23] Add a Double Blink setting for the pane flash `notifications.paneFlashDoubleBlink` (off by default) brings back the 0.9 s double blink for anyone who prefers it; the default stays one short pulse. FocusFlashPattern becomes a value with `pulse` and `doubleBlink` shapes, and every flash reads `FocusFlashPattern.current` when it starts. The setting sits under Pane Flash in Settings, in the command palette, cmux.json, the schema, and settings search. Co-Authored-By: Claude Opus 5.5 --- .../Keys/NotificationsCatalogSection.swift | 7 + .../CuratedSettingEntry+Default.swift | 1 + .../CmuxSettingsUI/Sections/AppSection.swift | 17 +- .../SettingsRowAnchorResolutionTests.swift | 1 + Resources/Localizable.xcstrings | 195 ++++++++++++++++++ ...CmuxSettingsFileStore+SupportedPaths.swift | 1 + Sources/CmuxSettingsJSONPathSupport.swift | 1 + .../CommandPaletteSettingsToggle.swift | 11 + Sources/GhosttyTerminalView.swift | 9 +- ...rdShortcutSettingsFileStore+Template.swift | 1 + Sources/Panels/BrowserPanelView.swift | 4 +- Sources/Panels/CustomSidebarPanelView.swift | 6 +- Sources/Panels/FilePreviewPanel.swift | 4 +- Sources/Panels/MarkdownPanelView.swift | 4 +- Sources/Panels/Panel.swift | 42 +++- Sources/Panels/TerminalPanel.swift | 2 +- Sources/RightSidebarToolPanel.swift | 4 +- Sources/SettingsSearchAliases.swift | 1 + Sources/SettingsSearchIndex.swift | 2 + .../TerminalNotificationStoreSettings.swift | 7 + Sources/TmuxWorkspacePaneOverlayView.swift | 8 +- cmuxTests/WindowAndDragTests.swift | 34 ++- skills/cmux-settings/references/all-keys.md | 1 + web/data/cmux.schema.json | 6 + 24 files changed, 329 insertions(+), 40 deletions(-) diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index 4f775a196810..e3df67b6f4af 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -26,6 +26,13 @@ public struct NotificationsCatalogSection: SettingCatalogSection { userDefaultsKey: "notificationPaneFlashEnabled" ) + /// Blink the pane flash twice instead of one short pulse. + public let paneFlashDoubleBlink = DefaultsKey( + id: "notifications.paneFlashDoubleBlink", + defaultValue: false, + userDefaultsKey: "notificationPaneFlashDoubleBlink" + ) + /// Stroke color of the attention ring and pane flash, as a `#RRGGBB` hex. /// Empty (the default) uses the cmux accent (`app.accentColor`). public let paneFlashColorHex = DefaultsKey( diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift index 0fb0699e4c12..8168d4a79f3f 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift @@ -146,6 +146,7 @@ extension Array where Element == CuratedSettingEntry { .init(section: .app, id: "show-menu-bar", title: String(localized: "settings.app.showInMenuBar", defaultValue: "Show in Menu Bar"), synonyms: "Show in Menu Bar notifications.showInMenuBar menubar menu bar status item tray extra"), .init(section: .app, id: "unread-pane-ring", title: String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), synonyms: "Unread Pane Ring notifications.unreadPaneRing blue border unread ring notification pane outline"), .init(section: .app, id: "pane-flash", title: String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), synonyms: "Pane Flash notifications.paneFlash flash blink highlight pane notification pulse"), + .init(section: .app, id: "pane-flash-double-blink", title: String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), synonyms: "Double Blink notifications.paneFlashDoubleBlink pane flash blink twice pulse"), .init( section: .app, id: "agent-permission-prompt", diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift index 7f797cb499a8..cec693f95520 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift @@ -54,6 +54,7 @@ public struct AppSection: View { @State private var showInMenuBar: DefaultsValueModel @State private var paneRing: DefaultsValueModel @State private var paneFlash: DefaultsValueModel + @State private var paneFlashDoubleBlink: DefaultsValueModel @State private var desktopNotifications: DesktopNotificationAuthorizationModel @State private var agentPermissionPrompt: DefaultsValueModel @State private var agentTurnComplete: DefaultsValueModel @@ -124,6 +125,7 @@ public struct AppSection: View { _showInMenuBar = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.showInMenuBar)) _paneRing = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.unreadPaneRing)) _paneFlash = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlash)) + _paneFlashDoubleBlink = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashDoubleBlink)) _desktopNotifications = State(initialValue: DesktopNotificationAuthorizationModel(hostActions: hostActions)) _agentPermissionPrompt = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentPermissionPrompt)) _agentTurnComplete = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentTurnComplete)) @@ -167,7 +169,7 @@ public struct AppSection: View { AppChannelSwitchCard(hostActions: hostActions) } .task { - startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) + startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) await soundAgentCache.loadIfNeeded { await hostActions.notificationSoundAgentOptions() } if languageAtAppear == nil { languageAtAppear = language.current }; if telemetryAtAppear == nil { telemetryAtAppear = telemetry.current } } @@ -706,6 +708,19 @@ public struct AppSection: View { } SettingsCardDivider() + // Pane Flash: Double Blink + SettingsCardRow( + configurationReview: .json("notifications.paneFlashDoubleBlink"), + String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), + subtitle: String(localized: "settings.notifications.paneFlashDoubleBlink.subtitle", defaultValue: "Blink the pane flash twice instead of one short pulse.") + ) { + Toggle("", isOn: Binding(get: { paneFlashDoubleBlink.current }, set: { paneFlashDoubleBlink.set($0) })) + .labelsHidden() + .controlSize(.small) + .disabled(!paneFlash.current) + } + SettingsCardDivider() + // Agent: Needs Permission SettingsCardRow( configurationReview: .json("notifications.agentPermissionPrompt"), diff --git a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift index 84f91119553c..959255c479ba 100644 --- a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift +++ b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift @@ -108,6 +108,7 @@ struct SettingsRowAnchorResolutionTests { "notifications.command", "notifications.dockBadge", "notifications.paneFlash", + "notifications.paneFlashDoubleBlink", "notifications.paneFlashColor", "notifications.showInMenuBar", "notifications.sound", diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 79d6e43c5ac8..704d3e3511b5 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -606424,6 +606424,201 @@ } } } + }, + "settings.notifications.paneFlashDoubleBlink.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Double Blink" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض مزدوج" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Doppeltes Blinken" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Parpadeo doble" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Double clignotement" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "2回点滅" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "두 번 깜빡임" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Подвійне блимання" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "闪烁两次" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "閃爍兩次" + } + } + } + }, + "settings.notifications.paneFlashDoubleBlink.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blink the pane flash twice instead of one short pulse." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض الجزء مرتين بدلاً من نبضة قصيرة واحدة." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Den Bereich zweimal aufblinken lassen statt eines kurzen Pulses." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Hacer parpadear el panel dos veces en lugar de un pulso breve." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Faire clignoter le volet deux fois au lieu d’une brève pulsation." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペインの点滅を短い1回ではなく2回にします。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "패인 깜빡임을 짧은 한 번 대신 두 번 표시합니다." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Блимати панеллю двічі замість одного короткого імпульсу." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "窗格闪烁两次,而不是一次短暂的脉冲。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "窗格閃爍兩次,而不是一次短暫的脈衝。" + } + } + } + }, + "settings.search.alias.setting.app.pane-flash-double-blink": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse وميض مزدوج" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse doppeltes blinken" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse parpadeo doble" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse double clignotement" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 2回点滅" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 두 번 깜빡임" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse подвійне блимання" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 闪烁两次" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashDoubleBlink pane flash double blink twice pulse 閃爍兩次" + } + } + } } }, "version": "1.0" diff --git a/Sources/CmuxSettingsFileStore+SupportedPaths.swift b/Sources/CmuxSettingsFileStore+SupportedPaths.swift index c81f09ce48d9..53d8dc1b5e44 100644 --- a/Sources/CmuxSettingsFileStore+SupportedPaths.swift +++ b/Sources/CmuxSettingsFileStore+SupportedPaths.swift @@ -74,6 +74,7 @@ extension CmuxSettingsFileStore { "notifications.showInMenuBar", "notifications.unreadPaneRing", "notifications.paneFlash", + "notifications.paneFlashDoubleBlink", "notifications.paneFlashColor", "notifications.sound", "notifications.soundWhenFocused", diff --git a/Sources/CmuxSettingsJSONPathSupport.swift b/Sources/CmuxSettingsJSONPathSupport.swift index 7ba85203c73b..8ae102e50edd 100644 --- a/Sources/CmuxSettingsJSONPathSupport.swift +++ b/Sources/CmuxSettingsJSONPathSupport.swift @@ -238,6 +238,7 @@ enum NotificationSettingsFileMapping { .init(jsonKey: "showInMenuBar", defaultsKey: MenuBarExtraSettings.showInMenuBarKey), .init(jsonKey: "unreadPaneRing", defaultsKey: NotificationPaneRingSettings.enabledKey), .init(jsonKey: "paneFlash", defaultsKey: NotificationPaneFlashSettings.enabledKey), + .init(jsonKey: "paneFlashDoubleBlink", defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey), .init( jsonKey: "soundWhenFocused", defaultsKey: notifications.soundWhenFocused.userDefaultsKey diff --git a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift index 221b5ab5c3c4..2aa3701d5279 100644 --- a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift +++ b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift @@ -429,6 +429,17 @@ enum CommandPaletteSettingsToggleCommands { defaultValue: NotificationPaneFlashSettings.defaultEnabled, defaultsKey: NotificationPaneFlashSettings.enabledKey ), + CommandPaletteSettingToggleDescriptor( + commandId: commandIdPrefix + "paneFlashDoubleBlink", + settingsKey: "notifications.paneFlashDoubleBlink", + title: { + String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink") + }, + sectionTitle: app, + keywords: ["notifications.paneFlashDoubleBlink", "pane", "flash", "double", "blink", "pulse"], + defaultValue: NotificationPaneFlashSettings.defaultDoubleBlink, + defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey + ), CommandPaletteSettingToggleDescriptor( commandId: commandIdPrefix + "sendAnonymousTelemetry", settingsKey: "app.sendAnonymousTelemetry", diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 3b51222659e1..32f6b25f8f50 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -12098,11 +12098,12 @@ final class GhosttySurfaceScrollView: NSView { updateFlashAppearance(style: style) flashLayer.removeAllAnimations() flashLayer.opacity = 0 + let pattern = FocusFlashPattern.current let animation = CAKeyframeAnimation(keyPath: "opacity") - animation.values = FocusFlashPattern.values.map { NSNumber(value: $0) } - animation.keyTimes = FocusFlashPattern.keyTimes.map { NSNumber(value: $0) } - animation.duration = FocusFlashPattern.duration - animation.timingFunctions = FocusFlashPattern.curves.map { curve in + animation.values = pattern.values.map { NSNumber(value: $0) } + animation.keyTimes = pattern.keyTimes.map { NSNumber(value: $0) } + animation.duration = pattern.duration + animation.timingFunctions = pattern.curves.map { curve in switch curve { case .easeIn: return CAMediaTimingFunction(name: .easeIn) diff --git a/Sources/KeyboardShortcutSettingsFileStore+Template.swift b/Sources/KeyboardShortcutSettingsFileStore+Template.swift index d961d9db5603..76d7ace89100 100644 --- a/Sources/KeyboardShortcutSettingsFileStore+Template.swift +++ b/Sources/KeyboardShortcutSettingsFileStore+Template.swift @@ -131,6 +131,7 @@ extension CmuxSettingsFileStore { "showInMenuBar": MenuBarExtraSettings.defaultShowInMenuBar, "unreadPaneRing": NotificationPaneRingSettings.defaultEnabled, "paneFlash": NotificationPaneFlashSettings.defaultEnabled, + "paneFlashDoubleBlink": NotificationPaneFlashSettings.defaultDoubleBlink, "paneFlashColor": NSNull(), "sound": NotificationSoundSettings.defaultValue, "customSoundFilePath": NotificationSoundSettings.defaultCustomFilePath, diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index 14c25a14737a..4a105ad16247 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -1956,9 +1956,9 @@ struct BrowserPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/Panels/CustomSidebarPanelView.swift b/Sources/Panels/CustomSidebarPanelView.swift index c8d402ef7231..a4ad2ed249f8 100644 --- a/Sources/Panels/CustomSidebarPanelView.swift +++ b/Sources/Panels/CustomSidebarPanelView.swift @@ -147,10 +147,10 @@ struct CustomSidebarPanelView: View { if shouldAnimateFocusFlash, let focusFlashStartedAt { TimelineView(TmuxWorkspacePaneFlashTimelineSchedule(startDate: focusFlashStartedAt)) { timeline in WorkspaceAttentionFlashRingView( - opacity: FocusFlashPattern.opacity(at: timeline.date.timeIntervalSince(focusFlashStartedAt)) + opacity: FocusFlashPattern.current.opacity(at: timeline.date.timeIntervalSince(focusFlashStartedAt)) ) .onChange(of: timeline.date) { _, date in - if date.timeIntervalSince(focusFlashStartedAt) >= FocusFlashPattern.duration { + if date.timeIntervalSince(focusFlashStartedAt) >= FocusFlashPattern.current.duration { completedFocusFlashStartedAt = focusFlashStartedAt } } @@ -163,6 +163,6 @@ struct CustomSidebarPanelView: View { private var shouldAnimateFocusFlash: Bool { guard let focusFlashStartedAt else { return false } guard completedFocusFlashStartedAt != focusFlashStartedAt else { return false } - return Date() <= focusFlashStartedAt.addingTimeInterval(FocusFlashPattern.duration) + return Date() <= focusFlashStartedAt.addingTimeInterval(FocusFlashPattern.current.duration) } } diff --git a/Sources/Panels/FilePreviewPanel.swift b/Sources/Panels/FilePreviewPanel.swift index 24c9163daa25..6af010a6103e 100644 --- a/Sources/Panels/FilePreviewPanel.swift +++ b/Sources/Panels/FilePreviewPanel.swift @@ -1880,9 +1880,9 @@ struct FilePreviewPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/Panels/MarkdownPanelView.swift b/Sources/Panels/MarkdownPanelView.swift index 831aeac29b7c..ea72e2a3b0ca 100644 --- a/Sources/Panels/MarkdownPanelView.swift +++ b/Sources/Panels/MarkdownPanelView.swift @@ -270,9 +270,9 @@ struct MarkdownPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index ca690b92b8f6..fd0f517c83ea 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -252,17 +252,37 @@ struct FocusFlashSegment: Equatable { let curve: FocusFlashCurve } -/// One short pulse: enough to say where focus or attention landed without -/// replaying a blink every time someone moves between panes. -enum FocusFlashPattern { - static let values: [Double] = [0, 1, 0] - static let keyTimes: [Double] = [0, 0.3, 1] - static let duration: TimeInterval = 0.4 - static let curves: [FocusFlashCurve] = [.easeOut, .easeIn] +/// The attention flash shape. One short pulse by default: enough to say where +/// focus or attention landed without replaying a blink on every move between +/// panes. `notifications.paneFlashDoubleBlink` restores the older double blink. +struct FocusFlashPattern: Equatable { + let values: [Double] + let keyTimes: [Double] + let duration: TimeInterval + let curves: [FocusFlashCurve] + + static let pulse = FocusFlashPattern( + values: [0, 1, 0], + keyTimes: [0, 0.3, 1], + duration: 0.4, + curves: [.easeOut, .easeIn] + ) + static let doubleBlink = FocusFlashPattern( + values: [0, 1, 0, 1, 0], + keyTimes: [0, 0.25, 0.5, 0.75, 1], + duration: 0.9, + curves: [.easeOut, .easeIn, .easeOut, .easeIn] + ) + + /// The shape the user has chosen, read when a flash starts. + static var current: FocusFlashPattern { + NotificationPaneFlashSettings.usesDoubleBlink() ? doubleBlink : pulse + } + static let ringInset: Double = Double(PanelOverlayRingMetrics.inset) static let ringCornerRadius: Double = Double(PanelOverlayRingMetrics.cornerRadius) - static var segments: [FocusFlashSegment] { + var segments: [FocusFlashSegment] { let stepCount = min(curves.count, values.count - 1, keyTimes.count - 1) return (0.. Double { + func opacity(at elapsed: TimeInterval) -> Double { guard elapsed >= 0, elapsed <= duration else { return 0 } - for index in 0.. endTime { @@ -288,7 +308,7 @@ enum FocusFlashPattern { let segmentDuration = max(endTime - startTime, 0.0001) let rawProgress = max(0, min(1, (elapsed - startTime) / segmentDuration)) - let curvedProgress = interpolatedProgress(rawProgress, curve: curves[index]) + let curvedProgress = Self.interpolatedProgress(rawProgress, curve: curves[index]) let startOpacity = values[index] let endOpacity = values[index + 1] return startOpacity + ((endOpacity - startOpacity) * curvedProgress) diff --git a/Sources/Panels/TerminalPanel.swift b/Sources/Panels/TerminalPanel.swift index 8d757c4e264b..61dc4ab9cf2e 100644 --- a/Sources/Panels/TerminalPanel.swift +++ b/Sources/Panels/TerminalPanel.swift @@ -822,7 +822,7 @@ final class TerminalPanel: Panel, ObservableObject { now < attentionFlashActiveUntil { return } - attentionFlashActiveUntil = now + FocusFlashPattern.duration + attentionFlashActiveUntil = now + FocusFlashPattern.current.duration switch TmuxOverlayExperimentSettings.target() { case .bonsplitPane: diff --git a/Sources/RightSidebarToolPanel.swift b/Sources/RightSidebarToolPanel.swift index 837acda0b347..3867ca9665c3 100644 --- a/Sources/RightSidebarToolPanel.swift +++ b/Sources/RightSidebarToolPanel.swift @@ -287,9 +287,9 @@ struct RightSidebarToolPanelView: View { private func triggerFocusFlashAnimation() { focusFlashAnimationGeneration &+= 1 let generation = focusFlashAnimationGeneration - focusFlashOpacity = FocusFlashPattern.values.first ?? 0 + focusFlashOpacity = FocusFlashPattern.current.values.first ?? 0 - for segment in FocusFlashPattern.segments { + for segment in FocusFlashPattern.current.segments { DispatchQueue.main.asyncAfter(deadline: .now() + segment.delay) { guard focusFlashAnimationGeneration == generation else { return } withAnimation(focusFlashAnimation(for: segment.curve, duration: segment.duration)) { diff --git a/Sources/SettingsSearchAliases.swift b/Sources/SettingsSearchAliases.swift index 9ec7cb9e4346..eed41f64bcf6 100644 --- a/Sources/SettingsSearchAliases.swift +++ b/Sources/SettingsSearchAliases.swift @@ -93,6 +93,7 @@ enum SettingsSearchAliasIndex { "app:show-menu-bar": localized("settings.search.alias.setting.app.show-menu-bar", defaultValue: "notifications.showInMenuBar menubar menu bar status item tray extra"), "app:unread-pane-ring": localized("settings.search.alias.setting.app.unread-pane-ring", defaultValue: "notifications.unreadPaneRing blue border unread ring notification pane outline"), "app:pane-flash": localized("settings.search.alias.setting.app.pane-flash", defaultValue: "notifications.paneFlash flash blink highlight pane notification pulse"), + "app:pane-flash-double-blink": localized("settings.search.alias.setting.app.pane-flash-double-blink", defaultValue: "notifications.paneFlashDoubleBlink pane flash double blink twice pulse"), "app:desktop-notifications": localized("settings.search.alias.setting.app.desktop-notifications", defaultValue: "macos desktop notifications system settings permission alerts notify test"), "app:notification-sound": localized("settings.search.alias.setting.app.notification-sound", defaultValue: "notifications.sound notifications.customSoundFilePath sound audio alert chime beep custom file wav mp3 caf aiff"), "app:notification-sound-overrides": localized("settings.search.alias.setting.app.notification-sound-overrides", defaultValue: "notifications.soundOverrides per-agent agent sound turn done needs input permission error stalled custom file"), diff --git a/Sources/SettingsSearchIndex.swift b/Sources/SettingsSearchIndex.swift index 801b9f5292da..6fdcfd1ba69e 100644 --- a/Sources/SettingsSearchIndex.swift +++ b/Sources/SettingsSearchIndex.swift @@ -50,6 +50,7 @@ enum SettingsSearchIndex { setting(.app, "show-menu-bar", String(localized: "settings.app.showInMenuBar", defaultValue: "Show in Menu Bar"), "menu extra status item"), setting(.app, "unread-pane-ring", String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), "notification blue ring pane"), setting(.app, "pane-flash", String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), "notification flash highlight"), + setting(.app, "pane-flash-double-blink", String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), "pane flash blink twice pulse"), setting(.app, "desktop-notifications", String(localized: "settings.notifications.desktop", defaultValue: "Desktop Notifications"), "permission alerts test notification"), setting(.app, "notification-sound", String(localized: "settings.notifications.sound.title", defaultValue: "Notification Sound"), "custom sound alert audio"), setting( @@ -272,6 +273,7 @@ enum SettingsSearchIndex { "notifications.showInMenuBar": settingID(for: .app, idSuffix: "show-menu-bar"), "notifications.unreadPaneRing": settingID(for: .app, idSuffix: "unread-pane-ring"), "notifications.paneFlash": settingID(for: .app, idSuffix: "pane-flash"), + "notifications.paneFlashDoubleBlink": settingID(for: .app, idSuffix: "pane-flash-double-blink"), "notifications.sound": settingID(for: .app, idSuffix: "notification-sound"), "notifications.customSoundFilePath": settingID(for: .app, idSuffix: "notification-sound"), "notifications.soundOverrides": settingID(for: .app, idSuffix: "notification-sound-overrides"), diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index c9eaa47da1a6..44a6e8ea4859 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -24,6 +24,13 @@ enum NotificationPaneRingSettings { enum NotificationPaneFlashSettings { static let enabledKey = "notificationPaneFlashEnabled" static let defaultEnabled = true + static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" + static let defaultDoubleBlink = false + + /// Whether the pane flash blinks twice instead of one short pulse. + static func usesDoubleBlink(defaults: UserDefaults = .standard) -> Bool { + defaults.object(forKey: doubleBlinkKey) as? Bool ?? defaultDoubleBlink + } static func isEnabled(defaults: UserDefaults = .standard) -> Bool { if defaults.object(forKey: enabledKey) == nil { diff --git a/Sources/TmuxWorkspacePaneOverlayView.swift b/Sources/TmuxWorkspacePaneOverlayView.swift index e97d3d56eb2c..8a586541f2cc 100644 --- a/Sources/TmuxWorkspacePaneOverlayView.swift +++ b/Sources/TmuxWorkspacePaneOverlayView.swift @@ -25,7 +25,7 @@ struct TmuxWorkspacePaneOverlayView: View { TimelineView(TmuxWorkspacePaneFlashTimelineSchedule(startDate: flashStartedAt)) { timeline in overlayCanvas(timelineDate: timeline.date, attentionColor: attentionColor) .onChange(of: timeline.date) { _, date in - if date.timeIntervalSince(flashStartedAt) >= FocusFlashPattern.duration { + if date.timeIntervalSince(flashStartedAt) >= FocusFlashPattern.current.duration { completedFlashStartedAt = flashStartedAt } } @@ -42,7 +42,7 @@ struct TmuxWorkspacePaneOverlayView: View { let flashStartedAt else { return false } guard completedFlashStartedAt != flashStartedAt, ringPath(for: flashRect) != nil else { return false } - return Date() <= flashStartedAt.addingTimeInterval(FocusFlashPattern.duration) + return Date() <= flashStartedAt.addingTimeInterval(FocusFlashPattern.current.duration) } /// Clips the active border to the drawable canvas so its bottom and right @@ -66,7 +66,7 @@ struct TmuxWorkspacePaneOverlayView: View { let flashStartedAt, let timelineDate else { return } let elapsed = timelineDate.timeIntervalSince(flashStartedAt) - let opacity = FocusFlashPattern.opacity(at: elapsed) + let opacity = FocusFlashPattern.current.opacity(at: elapsed) guard opacity > 0.001 else { return } drawFlashRing( in: &context, @@ -155,7 +155,7 @@ struct TmuxWorkspacePaneFlashTimelineSchedule: TimelineSchedule { let interval = mode == .lowFrequency ? 1.0 / 10.0 : 1.0 / 60.0 return Entries( nextDate: firstDate, - endDate: startDate.addingTimeInterval(FocusFlashPattern.duration), + endDate: startDate.addingTimeInterval(FocusFlashPattern.current.duration), interval: interval ) } diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index af5300a8b099..e7d4186ee8a4 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -740,17 +740,34 @@ final class TerminalDefaultFileOpenRequestTests: XCTestCase { final class FocusFlashPatternTests: XCTestCase { - func testFocusFlashPatternIsOneShortPulse() { - XCTAssertEqual(FocusFlashPattern.values, [0, 1, 0]) - XCTAssertEqual(FocusFlashPattern.keyTimes, [0, 0.3, 1]) - XCTAssertEqual(FocusFlashPattern.duration, 0.4, accuracy: 0.0001) - XCTAssertEqual(FocusFlashPattern.curves, [.easeOut, .easeIn]) + func testDefaultPatternIsOneShortPulse() throws { + let defaults = try XCTUnwrap(UserDefaults(suiteName: "FocusFlashPatternTests.\(UUID().uuidString)")) + XCTAssertFalse(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) + + let pulse = FocusFlashPattern.pulse + XCTAssertEqual(pulse.values, [0, 1, 0]) + XCTAssertEqual(pulse.keyTimes, [0, 0.3, 1]) + XCTAssertEqual(pulse.duration, 0.4, accuracy: 0.0001) + XCTAssertEqual(pulse.curves, [.easeOut, .easeIn]) XCTAssertEqual(FocusFlashPattern.ringInset, Double(PanelOverlayRingMetrics.inset), accuracy: 0.0001) XCTAssertEqual(FocusFlashPattern.ringCornerRadius, Double(PanelOverlayRingMetrics.cornerRadius), accuracy: 0.0001) } - func testFocusFlashPatternSegmentsCoverThePulse() { - let segments = FocusFlashPattern.segments + func testDoubleBlinkSettingSelectsTheOlderPattern() throws { + let defaults = try XCTUnwrap(UserDefaults(suiteName: "FocusFlashPatternTests.\(UUID().uuidString)")) + defaults.set(true, forKey: NotificationPaneFlashSettings.doubleBlinkKey) + XCTAssertTrue(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) + + let doubleBlink = FocusFlashPattern.doubleBlink + XCTAssertEqual(doubleBlink.values, [0, 1, 0, 1, 0]) + XCTAssertEqual(doubleBlink.keyTimes, [0, 0.25, 0.5, 0.75, 1]) + XCTAssertEqual(doubleBlink.duration, 0.9, accuracy: 0.0001) + XCTAssertEqual(doubleBlink.segments.count, 4) + } + + func testPulseSegmentsCoverThePulse() { + let pulse = FocusFlashPattern.pulse + let segments = pulse.segments XCTAssertEqual(segments.count, 2) XCTAssertEqual(segments[0].delay, 0.0, accuracy: 0.0001) @@ -762,7 +779,8 @@ final class FocusFlashPatternTests: XCTestCase { XCTAssertEqual(segments[1].duration, 0.28, accuracy: 0.0001) XCTAssertEqual(segments[1].targetOpacity, 0, accuracy: 0.0001) XCTAssertEqual(segments[1].curve, .easeIn) - XCTAssertEqual(FocusFlashPattern.opacity(at: 0.4), 0, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.12), 1, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.4), 0, accuracy: 0.0001) } } diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index b2915ee5e1fd..020f0a56e0c0 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -86,6 +86,7 @@ Notification behavior from Settings > Notifications. | `notifications.showInMenuBar` | boolean | `true` | Show the menu bar extra. | | `notifications.unreadPaneRing` | boolean | `true` | Highlight panes with unread notifications. | | `notifications.paneFlash` | boolean | `true` | Flash the focused pane when requested. | +| `notifications.paneFlashDoubleBlink` | boolean | `false` | Blink the pane flash twice instead of one short pulse. | | `notifications.sound` | `"default"` or `"Basso"` or `"Blow"` or `"Bottle"` or `"Frog"` or `"Funk"` or `"Glass"` or `"Hero"` or `"Morse"` or `"Ping"` or `"Pop"` or `"Purr"` or `"Sosumi"` or `"Submarine"` or `"Tink"` or `"custom_file"` or `"none"` | `"default"` | Notification sound preset. | | `notifications.soundWhenFocused` | boolean | `false` | Play the notification sound even when the pane that notified is already focused. Off by default, so a focused pane shows only its ring and flash. | | `notifications.customSoundFilePath` | string | `""` | Local path to the custom notification sound file. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index c92a986eac12..6dbfc601c92d 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1153,6 +1153,12 @@ "default": true, "description": "Flash the focused pane when requested." }, + "paneFlashDoubleBlink": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": false, + "description": "Blink the pane flash twice instead of one short pulse." + }, "paneFlashColor": { "x-cmux-scopes": ["global"], "$ref": "#/$defs/colorHexOrNull", From 3e8f0e905ab9f002c32f5860e2a06d39a7521cf2 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 28 Sep 2026 12:31:18 -0400 Subject: [PATCH 15/23] Pane flash: a 0.6 s pulse, which won the side-by-side vote over 0.4 s Co-Authored-By: Claude Opus 5.5 (1M context) --- Sources/Panels/Panel.swift | 2 +- cmuxTests/WindowAndDragTests.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Panels/Panel.swift b/Sources/Panels/Panel.swift index fd0f517c83ea..5bbb18a6e699 100644 --- a/Sources/Panels/Panel.swift +++ b/Sources/Panels/Panel.swift @@ -264,7 +264,7 @@ struct FocusFlashPattern: Equatable { static let pulse = FocusFlashPattern( values: [0, 1, 0], keyTimes: [0, 0.3, 1], - duration: 0.4, + duration: 0.6, curves: [.easeOut, .easeIn] ) static let doubleBlink = FocusFlashPattern( diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index e7d4186ee8a4..ca00d022e186 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -747,7 +747,7 @@ final class FocusFlashPatternTests: XCTestCase { let pulse = FocusFlashPattern.pulse XCTAssertEqual(pulse.values, [0, 1, 0]) XCTAssertEqual(pulse.keyTimes, [0, 0.3, 1]) - XCTAssertEqual(pulse.duration, 0.4, accuracy: 0.0001) + XCTAssertEqual(pulse.duration, 0.6, accuracy: 0.0001) XCTAssertEqual(pulse.curves, [.easeOut, .easeIn]) XCTAssertEqual(FocusFlashPattern.ringInset, Double(PanelOverlayRingMetrics.inset), accuracy: 0.0001) XCTAssertEqual(FocusFlashPattern.ringCornerRadius, Double(PanelOverlayRingMetrics.cornerRadius), accuracy: 0.0001) @@ -771,16 +771,16 @@ final class FocusFlashPatternTests: XCTestCase { XCTAssertEqual(segments.count, 2) XCTAssertEqual(segments[0].delay, 0.0, accuracy: 0.0001) - XCTAssertEqual(segments[0].duration, 0.12, accuracy: 0.0001) + XCTAssertEqual(segments[0].duration, 0.18, accuracy: 0.0001) XCTAssertEqual(segments[0].targetOpacity, 1, accuracy: 0.0001) XCTAssertEqual(segments[0].curve, .easeOut) - XCTAssertEqual(segments[1].delay, 0.12, accuracy: 0.0001) - XCTAssertEqual(segments[1].duration, 0.28, accuracy: 0.0001) + XCTAssertEqual(segments[1].delay, 0.18, accuracy: 0.0001) + XCTAssertEqual(segments[1].duration, 0.42, accuracy: 0.0001) XCTAssertEqual(segments[1].targetOpacity, 0, accuracy: 0.0001) XCTAssertEqual(segments[1].curve, .easeIn) - XCTAssertEqual(pulse.opacity(at: 0.12), 1, accuracy: 0.0001) - XCTAssertEqual(pulse.opacity(at: 0.4), 0, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.18), 1, accuracy: 0.0001) + XCTAssertEqual(pulse.opacity(at: 0.6), 0, accuracy: 0.0001) } } From 3945e25018d02400f386382cb1b83b58d91eaaf7 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 28 Sep 2026 12:54:50 -0400 Subject: [PATCH 16/23] Pane flash: follow the terminal theme foreground by default With no notifications.paneFlashColor set, the flash now draws in the terminal theme's foreground at 85% instead of the cmux blue, so it stays quiet on any theme. Unread rings keep the cmux accent, and a configured color still paints both. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../CmuxConfigSchema.generated.swift | 2 +- Sources/GhosttyTerminalView.swift | 8 +++-- Sources/Panels/WorkspaceAttentionColor.swift | 32 +++++++++++++++++-- .../WorkspaceAttentionFlashRingView.swift | 2 +- Sources/TmuxWorkspacePaneOverlayView.swift | 2 +- ...utModifierHoldHintsSettingsFileTests.swift | 18 +++++++++++ skills/cmux-settings/references/all-keys.md | 2 +- web/data/cmux.schema.json | 2 +- 8 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index ac09e56a9ff8..13c72692a14c 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -1168,7 +1168,7 @@ enum CmuxEmbeddedConfigSchema { "$ref": "#/$defs/colorHexOrNull", "default": null, "descriptionKey": "schemaDescriptions.notifications.paneFlashColor", - "description": "Override the pane flash and unread ring color. Null keeps the built-in blue." + "description": "Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent." }, "soundWhenFocused": { "x-cmux-scopes": ["global"], diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 32f6b25f8f50..7b1d3d84c77e 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -10357,6 +10357,7 @@ final class GhosttySurfaceScrollView: NSView { private var lastFlashStyle: FlashStyle = .navigation private var workspaceAttentionColor = WorkspaceAttentionColor(configuredHex: nil) private var workspaceAttentionNSColor = WorkspaceAttentionColor(configuredHex: nil).nsColor + private var workspaceAttentionFlashNSColor = WorkspaceAttentionColor(configuredHex: nil).flashNSColor private let keyboardCopyModeBadgeContainerView: GhosttyFlashOverlayView private let keyboardCopyModeBadgeView: GhosttyPassthroughVisualEffectView private let keyboardCopyModeBadgeIconView: NSImageView @@ -10684,7 +10685,7 @@ final class GhosttySurfaceScrollView: NSView { flashOverlayView.layer?.masksToBounds = false flashOverlayView.autoresizingMask = [.width, .height] let flashStyle = WorkspaceAttentionCoordinator.flashStyle(for: .navigation) - let flashColor = WorkspaceAttentionColor(configuredHex: nil).nsColor + let flashColor = WorkspaceAttentionColor(configuredHex: nil).flashNSColor flashLayer.fillColor = NSColor.clear.cgColor flashLayer.strokeColor = flashColor.cgColor flashLayer.lineWidth = NotificationRingMetrics.lineWidth @@ -11577,6 +11578,7 @@ final class GhosttySurfaceScrollView: NSView { private func applyWorkspaceAttentionNSColor() { workspaceAttentionNSColor = workspaceAttentionColor.nsColor + workspaceAttentionFlashNSColor = workspaceAttentionColor.flashNSColor CATransaction.begin() CATransaction.setDisableActions(true) @@ -13715,8 +13717,8 @@ final class GhosttySurfaceScrollView: NSView { private func updateFlashAppearance(style: FlashStyle) { let presentation = Self.flashPresentation(for: style) - flashLayer.strokeColor = workspaceAttentionNSColor.cgColor - flashLayer.shadowColor = workspaceAttentionNSColor.cgColor + flashLayer.strokeColor = workspaceAttentionFlashNSColor.cgColor + flashLayer.shadowColor = workspaceAttentionFlashNSColor.cgColor flashLayer.shadowOpacity = Float(presentation.glowOpacity) flashLayer.shadowRadius = presentation.glowRadius } diff --git a/Sources/Panels/WorkspaceAttentionColor.swift b/Sources/Panels/WorkspaceAttentionColor.swift index dfc1c6e32e19..92a4cb88f73f 100644 --- a/Sources/Panels/WorkspaceAttentionColor.swift +++ b/Sources/Panels/WorkspaceAttentionColor.swift @@ -4,6 +4,10 @@ import SwiftUI /// The resolved color shared by pane flashes and unread notification rings. /// +/// A configured color paints both. Without one, unread rings use the cmux +/// accent and flashes use the terminal theme's foreground, so the flash stays +/// quiet against whatever theme is active. +/// /// The setting store remains the only owner of the configured string. This /// value validates one immutable snapshot before it reaches a renderer, so /// AppKit layers and SwiftUI canvases never read ambient defaults or parse the @@ -12,21 +16,43 @@ struct WorkspaceAttentionColor: Hashable, Sendable { private let rgb: UInt32? /// The resolved cmux accent used when no valid color is configured. private let accent: CmuxAccentColor + /// The terminal theme foreground used by flashes when no color is configured. + private let themeForegroundRGB: UInt32? - init(configuredHex: String?, accent: CmuxAccentColor = CmuxAccentColor()) { + init( + configuredHex: String?, + accent: CmuxAccentColor = CmuxAccentColor(), + themeForeground: NSColor? = nil + ) { self.rgb = Self.strictRGB(configuredHex) self.accent = accent + self.themeForegroundRGB = themeForeground.flatMap { Self.strictRGB($0.hexString()) } } var nsColor: NSColor { guard let rgb else { return WorkspaceAttentionCoordinator.notificationRingStyle.accent.strokeColor(accent: accent) } - return NSColor( + return Self.color(rgb: rgb, alpha: 1) + } + + /// The pane flash color: the configured color, else the theme foreground + /// slightly softened, else the unread ring color. + var flashNSColor: NSColor { + if rgb == nil, let themeForegroundRGB { + return Self.color(rgb: themeForegroundRGB, alpha: Self.themeForegroundFlashAlpha) + } + return nsColor + } + + static let themeForegroundFlashAlpha: CGFloat = 0.85 + + private static func color(rgb: UInt32, alpha: CGFloat) -> NSColor { + NSColor( red: CGFloat((rgb >> 16) & 0xFF) / 255, green: CGFloat((rgb >> 8) & 0xFF) / 255, blue: CGFloat(rgb & 0xFF) / 255, - alpha: 1 + alpha: alpha ) } diff --git a/Sources/Panels/WorkspaceAttentionFlashRingView.swift b/Sources/Panels/WorkspaceAttentionFlashRingView.swift index 3f1394baa2a8..f8b9908b60fc 100644 --- a/Sources/Panels/WorkspaceAttentionFlashRingView.swift +++ b/Sources/Panels/WorkspaceAttentionFlashRingView.swift @@ -8,7 +8,7 @@ struct WorkspaceAttentionFlashRingView: View { var body: some View { let presentation = WorkspaceAttentionCoordinator.flashStyle(for: reason) - let color = Color(nsColor: workspaceAttentionColor.nsColor) + let color = Color(nsColor: workspaceAttentionColor.flashNSColor) RoundedRectangle(cornerRadius: CGFloat(FocusFlashPattern.ringCornerRadius)) .stroke(color.opacity(opacity), lineWidth: PanelOverlayRingMetrics.lineWidth) diff --git a/Sources/TmuxWorkspacePaneOverlayView.swift b/Sources/TmuxWorkspacePaneOverlayView.swift index 8a586541f2cc..0e1c0abe1487 100644 --- a/Sources/TmuxWorkspacePaneOverlayView.swift +++ b/Sources/TmuxWorkspacePaneOverlayView.swift @@ -73,7 +73,7 @@ struct TmuxWorkspacePaneOverlayView: View { rect: flashRect, opacity: opacity, reason: flashReason ?? .notificationArrival, - color: attentionColor + color: Color(nsColor: workspaceAttentionColor.flashNSColor) ) } } diff --git a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift index 7b600bd8e7bb..fdc980b450ec 100644 --- a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift +++ b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift @@ -220,6 +220,24 @@ struct PaneAttentionColorTests { ) } + @Test + func flashFollowsThemeForegroundWhenUnset() { + let color = WorkspaceAttentionColor( + configuredHex: nil, + themeForeground: NSColor(hex: "#CDD6F4") + ) + #expect(color.flashNSColor.hexString() == "#CDD6F4") + #expect(color.flashNSColor.alphaComponent == WorkspaceAttentionColor.themeForegroundFlashAlpha) + #expect(color.nsColor.hexString() == NSColor.systemBlue.hexString()) + } + + @Test + func configuredHexOverridesThemeForegroundForFlash() { + let color = WorkspaceAttentionColor(configuredHex: "#ff69b4", themeForeground: .white) + #expect(color.flashNSColor.hexString() == "#FF69B4") + #expect(color.flashNSColor.alphaComponent == 1) + } + @Test(arguments: ["not-a-color", "#FFZZZZ", "FF69B4", "#FF69B4AA"]) func rejectsValuesOutsideSchema(configuredHex: String) { assertUsesInjectedAccent(configuredHex: configuredHex) diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index 020f0a56e0c0..d479287262f7 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -93,7 +93,7 @@ Notification behavior from Settings > Notifications. | `notifications.command` | string | `""` | Optional shell command to run alongside notification delivery. | | `notifications.hooksMode` | `"append"` or `"replace"` | `"append"` | Controls whether project-local notification hooks append to inherited hooks or replace them. | | `notifications.hooks` | array | `[]` | Composable shell hooks that receive notification policy JSON on stdin and return updated policy JSON on stdout. | -| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null keeps the built-in blue. | +| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent. | | `notifications.suppressOnlyFocusedSurface` | boolean | `false` | When enabled, a notification banner is auto-withdrawn only when its surface is the exact focused surface. A banner delivered for a non-focused surface in the currently visible workspace stays up until you focus that surface (or click/dismiss it), instead of being retracted when the workspace becomes visible. Off preserves the legacy workspace-visibility withdraw. | | `notifications.suppressWhenAppFocused` | boolean | `false` | When enabled, cmux skips the desktop banner for every notification while cmux is the active app, not only for the focused pane. Notifications still appear in the sidebar, the sound and custom command still run, and phone forwarding is unchanged. Off keeps showing banners for other workspaces and panes while cmux is focused. | | `notifications.agentPermissionPrompt` | boolean | `true` | Notify when an agent (e.g. Claude Code) is blocked waiting for your permission to run a tool. On by default, since this is the alert you must act on to unblock the agent. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 6dbfc601c92d..a58630072a04 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1164,7 +1164,7 @@ "$ref": "#/$defs/colorHexOrNull", "default": null, "descriptionKey": "schemaDescriptions.notifications.paneFlashColor", - "description": "Override the pane flash and unread ring color. Null keeps the built-in blue." + "description": "Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent." }, "soundWhenFocused": { "x-cmux-scopes": ["global"], From 9b5814acbb8f8296b7624ec4362f24e8952be22b Mon Sep 17 00:00:00 2001 From: Leo Li Date: Mon, 28 Sep 2026 15:17:22 -0400 Subject: [PATCH 17/23] Test typing and clicking dismissal against a live surface The keyDown test sent F1 before the runtime surface existed, so the key never reached the terminal and the workspace's startup focus pass cleared the notification (with its flash) instead. Both tests now wait for the surface and the startup pass, and check that typing "a" or clicking in the focused pane clears the notification without a flash. Co-Authored-By: Claude Opus 5.5 (1M context) --- cmuxTests/TerminalAndGhosttyTests.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cmuxTests/TerminalAndGhosttyTests.swift b/cmuxTests/TerminalAndGhosttyTests.swift index 8eca6a7f7f1d..7eaee4ac07bc 100644 --- a/cmuxTests/TerminalAndGhosttyTests.swift +++ b/cmuxTests/TerminalAndGhosttyTests.swift @@ -3313,9 +3313,13 @@ final class TerminalNotificationDirectInteractionTests: XCTestCase { return } - GhosttySurfaceScrollView.resetFlashCounts() AppFocusState.overrideIsFocused = true XCTAssertTrue(window.makeFirstResponder(surfaceView)) + // Let the runtime surface come up and the workspace's startup focus pass + // run first, so only the interaction below can clear the notification. + waitForRuntimeSurface(terminalPanel.surface) + drainMainQueue() + GhosttySurfaceScrollView.resetFlashCounts() store.addNotification( tabId: workspace.id, @@ -3384,9 +3388,13 @@ final class TerminalNotificationDirectInteractionTests: XCTestCase { return } - GhosttySurfaceScrollView.resetFlashCounts() AppFocusState.overrideIsFocused = true XCTAssertTrue(window.makeFirstResponder(surfaceView)) + // Let the runtime surface come up and the workspace's startup focus pass + // run first, so only the interaction below can clear the notification. + waitForRuntimeSurface(terminalPanel.surface) + drainMainQueue() + GhosttySurfaceScrollView.resetFlashCounts() store.addNotification( tabId: workspace.id, @@ -3397,12 +3405,13 @@ final class TerminalNotificationDirectInteractionTests: XCTestCase { ) XCTAssertTrue(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: terminalPanel.id)) - let event = makeKeyEvent(characters: "", keyCode: 122, window: window) + let event = makeKeyEvent(characters: "a", keyCode: 0, window: window) surfaceView.keyDown(with: event) drainMainQueue() XCTAssertFalse(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: terminalPanel.id)) - XCTAssertEqual(GhosttySurfaceScrollView.flashCount(for: terminalPanel.id), 1) + // Typing into the pane is the acknowledgement; the pane doesn't flash. + XCTAssertEqual(GhosttySurfaceScrollView.flashCount(for: terminalPanel.id), 0) } func testKeyDownRecoversReleasedSurfaceWhileHostedViewIsDetached() throws { From c0e48b38f75286d5a23998f8081b6d7240c33527 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 04:13:15 -0700 Subject: [PATCH 18/23] fix: keep double blink as default focus flash --- Sources/Panels/WorkspaceAttentionColor.swift | 17 +++-------------- Sources/TerminalNotificationStoreSettings.swift | 2 +- web/data/cmux.schema.json | 2 +- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Sources/Panels/WorkspaceAttentionColor.swift b/Sources/Panels/WorkspaceAttentionColor.swift index 92a4cb88f73f..56df7566c5f4 100644 --- a/Sources/Panels/WorkspaceAttentionColor.swift +++ b/Sources/Panels/WorkspaceAttentionColor.swift @@ -16,9 +16,6 @@ struct WorkspaceAttentionColor: Hashable, Sendable { private let rgb: UInt32? /// The resolved cmux accent used when no valid color is configured. private let accent: CmuxAccentColor - /// The terminal theme foreground used by flashes when no color is configured. - private let themeForegroundRGB: UInt32? - init( configuredHex: String?, accent: CmuxAccentColor = CmuxAccentColor(), @@ -26,7 +23,7 @@ struct WorkspaceAttentionColor: Hashable, Sendable { ) { self.rgb = Self.strictRGB(configuredHex) self.accent = accent - self.themeForegroundRGB = themeForeground.flatMap { Self.strictRGB($0.hexString()) } + _ = themeForeground } var nsColor: NSColor { @@ -36,16 +33,8 @@ struct WorkspaceAttentionColor: Hashable, Sendable { return Self.color(rgb: rgb, alpha: 1) } - /// The pane flash color: the configured color, else the theme foreground - /// slightly softened, else the unread ring color. - var flashNSColor: NSColor { - if rgb == nil, let themeForegroundRGB { - return Self.color(rgb: themeForegroundRGB, alpha: Self.themeForegroundFlashAlpha) - } - return nsColor - } - - static let themeForegroundFlashAlpha: CGFloat = 0.85 + /// The pane flash keeps the configured color or the existing cmux accent. + var flashNSColor: NSColor { nsColor } private static func color(rgb: UInt32, alpha: CGFloat) -> NSColor { NSColor( diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index 44a6e8ea4859..dff88f475039 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -25,7 +25,7 @@ enum NotificationPaneFlashSettings { static let enabledKey = "notificationPaneFlashEnabled" static let defaultEnabled = true static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" - static let defaultDoubleBlink = false + static let defaultDoubleBlink = true /// Whether the pane flash blinks twice instead of one short pulse. static func usesDoubleBlink(defaults: UserDefaults = .standard) -> Bool { diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index a58630072a04..675cd94a3f8e 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1156,7 +1156,7 @@ "paneFlashDoubleBlink": { "x-cmux-scopes": ["global"], "type": "boolean", - "default": false, + "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, "paneFlashColor": { From 72a9f5802b09da9fa87942c23d10a4b361141d5d Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 04:13:23 -0700 Subject: [PATCH 19/23] test: assert double blink is the default --- cmuxTests/WindowAndDragTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmuxTests/WindowAndDragTests.swift b/cmuxTests/WindowAndDragTests.swift index ca00d022e186..4f542e87e1a4 100644 --- a/cmuxTests/WindowAndDragTests.swift +++ b/cmuxTests/WindowAndDragTests.swift @@ -742,7 +742,7 @@ final class TerminalDefaultFileOpenRequestTests: XCTestCase { final class FocusFlashPatternTests: XCTestCase { func testDefaultPatternIsOneShortPulse() throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: "FocusFlashPatternTests.\(UUID().uuidString)")) - XCTAssertFalse(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) + XCTAssertTrue(NotificationPaneFlashSettings.usesDoubleBlink(defaults: defaults)) let pulse = FocusFlashPattern.pulse XCTAssertEqual(pulse.values, [0, 1, 0]) From 97bce1a9647772478af47fd545472468db48719b Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 04:59:32 -0700 Subject: [PATCH 20/23] feat: make focus flash color and pattern configurable --- .../Keys/NotificationsCatalogSection.swift | 6 + .../Resources/Localizable.xcstrings | 118 ++++++++++++++++++ .../CmuxSettingsUI/Sections/AppSection.swift | 16 ++- .../SettingsRowAnchorResolutionTests.swift | 1 + ...CmuxSettingsFileStore+SupportedPaths.swift | 1 + Sources/CmuxSettingsJSONPathSupport.swift | 1 + Sources/ContentView.swift | 21 ++++ ...rdShortcutSettingsFileStore+Template.swift | 1 + Sources/Panels/WorkspaceAttentionColor.swift | 20 ++- Sources/SettingsSearchIndex.swift | 2 + .../TerminalNotificationStoreSettings.swift | 2 + skills/cmux-settings/references/all-keys.md | 5 +- web/data/cmux.schema.json | 6 + 13 files changed, 191 insertions(+), 9 deletions(-) diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index e3df67b6f4af..8c489b1bfcbe 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -33,6 +33,12 @@ public struct NotificationsCatalogSection: SettingCatalogSection { userDefaultsKey: "notificationPaneFlashDoubleBlink" ) + public let paneFlashThemeColor = DefaultsKey( + id: "notifications.paneFlashThemeColor", + defaultValue: false, + userDefaultsKey: "notificationPaneFlashThemeColor" + ) + /// Stroke color of the attention ring and pane flash, as a `#RRGGBB` hex. /// Empty (the default) uses the cmux accent (`app.accentColor`). public let paneFlashColorHex = DefaultsKey( diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings index 2b3c94b79751..2970f4d325ae 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings @@ -13338,6 +13338,124 @@ } } } + }, + "settings.notifications.paneFlashThemeColor.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use the terminal theme foreground for flashes; unread rings stay cmux blue." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verwende die Vordergrundfarbe des Terminaldesigns für Blitze; ungelesene Ringe bleiben cmux-blau." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utiliser le premier plan du thème du terminal pour les flashs ; les anneaux non lus restent bleus cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استخدم مقدمة سمة الطرفية للومضات؛ تبقى حلقات عدم القراءة باللون الأزرق لـ cmux." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usa el primer plano del tema de la terminal para los destellos; los anillos no leídos siguen siendo azules de cmux." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用終端主題前景色顯示閃爍;未讀環維持 cmux 藍色。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用终端主题前景色显示闪烁;未读环保持 cmux 蓝色。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "플래시에 터미널 테마 전경색을 사용하며 읽지 않은 링은 cmux 파란색으로 유지합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フラッシュにターミナルテーマの前景色を使い、未読リングは cmux ブルーのままにします。" + } + } + } + }, + "settings.notifications.paneFlashThemeColor.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Theme Foreground" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Vordergrund des Designs verwenden" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utiliser le premier plan du thème" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استخدام مقدمة السمة" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Usar el primer plano del tema" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用主題前景色" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "使用主题前景色" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "테마 전경색 사용" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "テーマの前景色を使う" + } + } + } } }, "version": "1.0" diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift index cec693f95520..0976d364b3a7 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift @@ -55,6 +55,7 @@ public struct AppSection: View { @State private var paneRing: DefaultsValueModel @State private var paneFlash: DefaultsValueModel @State private var paneFlashDoubleBlink: DefaultsValueModel + @State private var paneFlashThemeColor: DefaultsValueModel @State private var desktopNotifications: DesktopNotificationAuthorizationModel @State private var agentPermissionPrompt: DefaultsValueModel @State private var agentTurnComplete: DefaultsValueModel @@ -126,6 +127,7 @@ public struct AppSection: View { _paneRing = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.unreadPaneRing)) _paneFlash = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlash)) _paneFlashDoubleBlink = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashDoubleBlink)) + _paneFlashThemeColor = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashThemeColor)) _desktopNotifications = State(initialValue: DesktopNotificationAuthorizationModel(hostActions: hostActions)) _agentPermissionPrompt = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentPermissionPrompt)) _agentTurnComplete = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentTurnComplete)) @@ -169,7 +171,7 @@ public struct AppSection: View { AppChannelSwitchCard(hostActions: hostActions) } .task { - startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) + startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, paneFlashThemeColor, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) await soundAgentCache.loadIfNeeded { await hostActions.notificationSoundAgentOptions() } if languageAtAppear == nil { languageAtAppear = language.current }; if telemetryAtAppear == nil { telemetryAtAppear = telemetry.current } } @@ -721,6 +723,18 @@ public struct AppSection: View { } SettingsCardDivider() + SettingsCardRow( + configurationReview: .json("notifications.paneFlashThemeColor"), + String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), + subtitle: String(localized: "settings.notifications.paneFlashThemeColor.subtitle", defaultValue: "Use the terminal theme foreground for flashes; unread rings stay cmux blue.") + ) { + Toggle("", isOn: Binding(get: { paneFlashThemeColor.current }, set: { paneFlashThemeColor.set($0) })) + .labelsHidden() + .controlSize(.small) + .disabled(!paneFlash.current) + } + SettingsCardDivider() + // Agent: Needs Permission SettingsCardRow( configurationReview: .json("notifications.agentPermissionPrompt"), diff --git a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift index 959255c479ba..e417e8373326 100644 --- a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift +++ b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift @@ -109,6 +109,7 @@ struct SettingsRowAnchorResolutionTests { "notifications.dockBadge", "notifications.paneFlash", "notifications.paneFlashDoubleBlink", + "notifications.paneFlashThemeColor", "notifications.paneFlashColor", "notifications.showInMenuBar", "notifications.sound", diff --git a/Sources/CmuxSettingsFileStore+SupportedPaths.swift b/Sources/CmuxSettingsFileStore+SupportedPaths.swift index 53d8dc1b5e44..8b0838d4937a 100644 --- a/Sources/CmuxSettingsFileStore+SupportedPaths.swift +++ b/Sources/CmuxSettingsFileStore+SupportedPaths.swift @@ -75,6 +75,7 @@ extension CmuxSettingsFileStore { "notifications.unreadPaneRing", "notifications.paneFlash", "notifications.paneFlashDoubleBlink", + "notifications.paneFlashThemeColor", "notifications.paneFlashColor", "notifications.sound", "notifications.soundWhenFocused", diff --git a/Sources/CmuxSettingsJSONPathSupport.swift b/Sources/CmuxSettingsJSONPathSupport.swift index 8ae102e50edd..40cb2246c8b5 100644 --- a/Sources/CmuxSettingsJSONPathSupport.swift +++ b/Sources/CmuxSettingsJSONPathSupport.swift @@ -239,6 +239,7 @@ enum NotificationSettingsFileMapping { .init(jsonKey: "unreadPaneRing", defaultsKey: NotificationPaneRingSettings.enabledKey), .init(jsonKey: "paneFlash", defaultsKey: NotificationPaneFlashSettings.enabledKey), .init(jsonKey: "paneFlashDoubleBlink", defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey), + .init(jsonKey: "paneFlashThemeColor", defaultsKey: NotificationPaneFlashSettings.themeColorKey), .init( jsonKey: "soundWhenFocused", defaultsKey: notifications.soundWhenFocused.userDefaultsKey diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index b63bd4dcccba..d9d895ac8ef4 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -906,10 +906,31 @@ struct ContentView: View { @LiveSetting(\.shortcuts.showModifierHoldHints) private var showModifierHoldHints @LiveSetting(\.customSidebars.renderer) private var customSidebarRenderer @LiveSetting(\.notifications.paneFlashColorHex) private var paneFlashColorHex + @AppStorage("notificationPaneFlashThemeColor") private var paneFlashThemeColor = false /// Resolved cmux accent, seeded from the app delegate's observer and /// updated from its change notification. This view is the window root, /// so it cannot read the accent from its own environment modifier. @State private var cmuxAccent = AppDelegate.shared?.accentColor ?? CmuxAccentColor() + /// Terminal theme foreground, which colors pane flashes when no flash + /// color is configured. Updated from the default appearance notification. + @State private var terminalThemeForeground = GhosttyApp.shared.defaultForegroundColor + + private var resolvedWorkspaceAttentionColor: WorkspaceAttentionColor { + resolveWorkspaceAttentionColor() + } + + private func resolveWorkspaceAttentionColor( + configuredHex: String?? = nil, + accent: CmuxAccentColor? = nil, + themeForeground: NSColor? = nil + ) -> WorkspaceAttentionColor { + WorkspaceAttentionColor( + configuredHex: configuredHex ?? paneFlashColorHex, + accent: accent ?? cmuxAccent, + themeForeground: themeForeground ?? terminalThemeForeground, + useThemeForeground: paneFlashThemeColor + ) + } /// Canonical sidebar width, deliberately NOT observed by ContentView: /// divider ticks re-evaluate only the SidebarWidthReader wrappers that /// consume the width, never this body. All reads/writes outside view diff --git a/Sources/KeyboardShortcutSettingsFileStore+Template.swift b/Sources/KeyboardShortcutSettingsFileStore+Template.swift index 76d7ace89100..a9b7fb42c65f 100644 --- a/Sources/KeyboardShortcutSettingsFileStore+Template.swift +++ b/Sources/KeyboardShortcutSettingsFileStore+Template.swift @@ -132,6 +132,7 @@ extension CmuxSettingsFileStore { "unreadPaneRing": NotificationPaneRingSettings.defaultEnabled, "paneFlash": NotificationPaneFlashSettings.defaultEnabled, "paneFlashDoubleBlink": NotificationPaneFlashSettings.defaultDoubleBlink, + "paneFlashThemeColor": NotificationPaneFlashSettings.defaultThemeColor, "paneFlashColor": NSNull(), "sound": NotificationSoundSettings.defaultValue, "customSoundFilePath": NotificationSoundSettings.defaultCustomFilePath, diff --git a/Sources/Panels/WorkspaceAttentionColor.swift b/Sources/Panels/WorkspaceAttentionColor.swift index 56df7566c5f4..2468f3fc6b9d 100644 --- a/Sources/Panels/WorkspaceAttentionColor.swift +++ b/Sources/Panels/WorkspaceAttentionColor.swift @@ -4,9 +4,8 @@ import SwiftUI /// The resolved color shared by pane flashes and unread notification rings. /// -/// A configured color paints both. Without one, unread rings use the cmux -/// accent and flashes use the terminal theme's foreground, so the flash stays -/// quiet against whatever theme is active. +/// A configured color paints both. Without one, both signals use the existing +/// cmux accent, preserving the established blue default. /// /// The setting store remains the only owner of the configured string. This /// value validates one immutable snapshot before it reaches a renderer, so @@ -16,14 +15,18 @@ struct WorkspaceAttentionColor: Hashable, Sendable { private let rgb: UInt32? /// The resolved cmux accent used when no valid color is configured. private let accent: CmuxAccentColor + private let themeForegroundHex: String? + private let useThemeForeground: Bool init( configuredHex: String?, accent: CmuxAccentColor = CmuxAccentColor(), - themeForeground: NSColor? = nil + themeForeground: NSColor? = nil, + useThemeForeground: Bool = false ) { self.rgb = Self.strictRGB(configuredHex) self.accent = accent - _ = themeForeground + self.themeForegroundHex = themeForeground?.hexString() + self.useThemeForeground = useThemeForeground } var nsColor: NSColor { @@ -34,7 +37,12 @@ struct WorkspaceAttentionColor: Hashable, Sendable { } /// The pane flash keeps the configured color or the existing cmux accent. - var flashNSColor: NSColor { nsColor } + var flashNSColor: NSColor { + guard rgb == nil, useThemeForeground, + let themeForegroundHex, + let themeForeground = NSColor(hex: themeForegroundHex) else { return nsColor } + return themeForeground + } private static func color(rgb: UInt32, alpha: CGFloat) -> NSColor { NSColor( diff --git a/Sources/SettingsSearchIndex.swift b/Sources/SettingsSearchIndex.swift index 6fdcfd1ba69e..ba732fe5ddb5 100644 --- a/Sources/SettingsSearchIndex.swift +++ b/Sources/SettingsSearchIndex.swift @@ -50,6 +50,7 @@ enum SettingsSearchIndex { setting(.app, "show-menu-bar", String(localized: "settings.app.showInMenuBar", defaultValue: "Show in Menu Bar"), "menu extra status item"), setting(.app, "unread-pane-ring", String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), "notification blue ring pane"), setting(.app, "pane-flash", String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), "notification flash highlight"), + setting(.app, "pane-flash-theme-color", String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), "notification flash terminal theme foreground blue ring"), setting(.app, "pane-flash-double-blink", String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), "pane flash blink twice pulse"), setting(.app, "desktop-notifications", String(localized: "settings.notifications.desktop", defaultValue: "Desktop Notifications"), "permission alerts test notification"), setting(.app, "notification-sound", String(localized: "settings.notifications.sound.title", defaultValue: "Notification Sound"), "custom sound alert audio"), @@ -273,6 +274,7 @@ enum SettingsSearchIndex { "notifications.showInMenuBar": settingID(for: .app, idSuffix: "show-menu-bar"), "notifications.unreadPaneRing": settingID(for: .app, idSuffix: "unread-pane-ring"), "notifications.paneFlash": settingID(for: .app, idSuffix: "pane-flash"), + "notifications.paneFlashThemeColor": settingID(for: .app, idSuffix: "pane-flash-theme-color"), "notifications.paneFlashDoubleBlink": settingID(for: .app, idSuffix: "pane-flash-double-blink"), "notifications.sound": settingID(for: .app, idSuffix: "notification-sound"), "notifications.customSoundFilePath": settingID(for: .app, idSuffix: "notification-sound"), diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index dff88f475039..88cc9ed7341d 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -26,6 +26,8 @@ enum NotificationPaneFlashSettings { static let defaultEnabled = true static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" static let defaultDoubleBlink = true + static let themeColorKey = "notificationPaneFlashThemeColor" + static let defaultThemeColor = false /// Whether the pane flash blinks twice instead of one short pulse. static func usesDoubleBlink(defaults: UserDefaults = .standard) -> Bool { diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index d479287262f7..49232416d8f9 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -86,14 +86,15 @@ Notification behavior from Settings > Notifications. | `notifications.showInMenuBar` | boolean | `true` | Show the menu bar extra. | | `notifications.unreadPaneRing` | boolean | `true` | Highlight panes with unread notifications. | | `notifications.paneFlash` | boolean | `true` | Flash the focused pane when requested. | -| `notifications.paneFlashDoubleBlink` | boolean | `false` | Blink the pane flash twice instead of one short pulse. | +| `notifications.paneFlashDoubleBlink` | boolean | `true` | Use the default double blink; set false for the configurable single 0.6-second pulse. | | `notifications.sound` | `"default"` or `"Basso"` or `"Blow"` or `"Bottle"` or `"Frog"` or `"Funk"` or `"Glass"` or `"Hero"` or `"Morse"` or `"Ping"` or `"Pop"` or `"Purr"` or `"Sosumi"` or `"Submarine"` or `"Tink"` or `"custom_file"` or `"none"` | `"default"` | Notification sound preset. | | `notifications.soundWhenFocused` | boolean | `false` | Play the notification sound even when the pane that notified is already focused. Off by default, so a focused pane shows only its ring and flash. | | `notifications.customSoundFilePath` | string | `""` | Local path to the custom notification sound file. | | `notifications.command` | string | `""` | Optional shell command to run alongside notification delivery. | | `notifications.hooksMode` | `"append"` or `"replace"` | `"append"` | Controls whether project-local notification hooks append to inherited hooks or replace them. | | `notifications.hooks` | array | `[]` | Composable shell hooks that receive notification policy JSON on stdin and return updated policy JSON on stdout. | -| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null flashes in the terminal theme's foreground and keeps unread rings in the cmux accent. | +| `notifications.paneFlashColor` | string or null | `null` | Override the pane flash and unread ring color. Null keeps the cmux blue default. | +| `notifications.paneFlashThemeColor` | boolean | `false` | Use the terminal theme foreground for flashes when paneFlashColor is unset; unread rings remain cmux blue. | | `notifications.suppressOnlyFocusedSurface` | boolean | `false` | When enabled, a notification banner is auto-withdrawn only when its surface is the exact focused surface. A banner delivered for a non-focused surface in the currently visible workspace stays up until you focus that surface (or click/dismiss it), instead of being retracted when the workspace becomes visible. Off preserves the legacy workspace-visibility withdraw. | | `notifications.suppressWhenAppFocused` | boolean | `false` | When enabled, cmux skips the desktop banner for every notification while cmux is the active app, not only for the focused pane. Notifications still appear in the sidebar, the sound and custom command still run, and phone forwarding is unchanged. Off keeps showing banners for other workspaces and panes while cmux is focused. | | `notifications.agentPermissionPrompt` | boolean | `true` | Notify when an agent (e.g. Claude Code) is blocked waiting for your permission to run a tool. On by default, since this is the alert you must act on to unblock the agent. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 675cd94a3f8e..6c79ad559107 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1159,6 +1159,12 @@ "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, + "paneFlashThemeColor": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": false, + "description": "Use the terminal theme foreground for flashes when paneFlashColor is unset. Unread rings remain cmux blue." + }, "paneFlashColor": { "x-cmux-scopes": ["global"], "$ref": "#/$defs/colorHexOrNull", From b69220ad2c1b42fd90b323cf5cd2c1f9b227c191 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 05:08:58 -0700 Subject: [PATCH 21/23] fix: keep catalog default double blink and expose theme flash color --- .../Keys/NotificationsCatalogSection.swift | 2 +- .../Navigation/CuratedSettingEntry+Default.swift | 1 + ...dShortcutModifierHoldHintsSettingsFileTests.swift | 12 ++++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index 8c489b1bfcbe..536e57214e50 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -29,7 +29,7 @@ public struct NotificationsCatalogSection: SettingCatalogSection { /// Blink the pane flash twice instead of one short pulse. public let paneFlashDoubleBlink = DefaultsKey( id: "notifications.paneFlashDoubleBlink", - defaultValue: false, + defaultValue: true, userDefaultsKey: "notificationPaneFlashDoubleBlink" ) diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift index 8168d4a79f3f..383e8b1b0073 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift @@ -147,6 +147,7 @@ extension Array where Element == CuratedSettingEntry { .init(section: .app, id: "unread-pane-ring", title: String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), synonyms: "Unread Pane Ring notifications.unreadPaneRing blue border unread ring notification pane outline"), .init(section: .app, id: "pane-flash", title: String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), synonyms: "Pane Flash notifications.paneFlash flash blink highlight pane notification pulse"), .init(section: .app, id: "pane-flash-double-blink", title: String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), synonyms: "Double Blink notifications.paneFlashDoubleBlink pane flash blink twice pulse"), + .init(section: .app, id: "pane-flash-theme-color", title: String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), synonyms: "Use Theme Foreground notifications.paneFlashThemeColor pane flash terminal theme foreground"), .init( section: .app, id: "agent-permission-prompt", diff --git a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift index fdc980b450ec..3ac5e955403a 100644 --- a/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift +++ b/cmuxTests/KeyboardShortcutModifierHoldHintsSettingsFileTests.swift @@ -222,18 +222,22 @@ struct PaneAttentionColorTests { @Test func flashFollowsThemeForegroundWhenUnset() { + let accent = CmuxAccentColor() let color = WorkspaceAttentionColor( configuredHex: nil, - themeForeground: NSColor(hex: "#CDD6F4") + accent: accent, + themeForeground: NSColor(hex: "#CDD6F4"), + useThemeForeground: true ) #expect(color.flashNSColor.hexString() == "#CDD6F4") - #expect(color.flashNSColor.alphaComponent == WorkspaceAttentionColor.themeForegroundFlashAlpha) - #expect(color.nsColor.hexString() == NSColor.systemBlue.hexString()) + #expect(color.flashNSColor.alphaComponent == 1) + // Unread rings keep the accent. + #expect(color.nsColor.hexString() == accent.dynamicNSColor.hexString()) } @Test func configuredHexOverridesThemeForegroundForFlash() { - let color = WorkspaceAttentionColor(configuredHex: "#ff69b4", themeForeground: .white) + let color = WorkspaceAttentionColor(configuredHex: "#ff69b4", themeForeground: .white, useThemeForeground: true) #expect(color.flashNSColor.hexString() == "#FF69B4") #expect(color.flashNSColor.alphaComponent == 1) } From 8cb5ce239e23b2bc4e71721ad84a595c47dcdb60 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 1 Oct 2026 15:21:08 -0700 Subject: [PATCH 22/23] feat: make typing pane flash configurable Co-Authored-By: Claude Opus 5.5 --- .../CmuxConfigSchema.generated.swift | 18 ++ .../NotificationDismissalContext.swift | 20 +- .../NotificationDismissalHosting.swift | 5 + .../NotificationDismissalModel.swift | 2 +- .../NotificationDismissalModelTests.swift | 11 + .../Keys/NotificationsCatalogSection.swift | 7 + Resources/Localizable.xcstrings | 195 ++++++++++++++++++ ...CmuxSettingsFileStore+SupportedPaths.swift | 1 + Sources/CmuxSettingsJSONPathSupport.swift | 1 + .../CommandPaletteSettingsToggle.swift | 11 + ...rdShortcutSettingsFileStore+Template.swift | 1 + Sources/SettingsSearchAliases.swift | 1 + Sources/SettingsSearchIndex.swift | 1 + ...Manager+NotificationDismissalHosting.swift | 5 + .../TerminalNotificationStoreSettings.swift | 7 + skills/cmux-settings/references/all-keys.md | 1 + web/data/cmux.schema.json | 6 + 17 files changed, 288 insertions(+), 5 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index 13c72692a14c..96771508854a 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -1163,6 +1163,24 @@ enum CmuxEmbeddedConfigSchema { "default": true, "description": "Flash the focused pane when requested." }, + "paneFlashDoubleBlink": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": true, + "description": "Blink the pane flash twice instead of one short pulse." + }, + "paneFlashOnTyping": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": true, + "description": "Flash the pane when terminal typing dismisses its notification." + }, + "paneFlashThemeColor": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": false, + "description": "Use the terminal theme foreground for flashes when paneFlashColor is unset. Unread rings remain cmux blue." + }, "paneFlashColor": { "x-cmux-scopes": ["global"], "$ref": "#/$defs/colorHexOrNull", diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift index 94d0440932d6..457ec7e39a6e 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalContext.swift @@ -22,11 +22,23 @@ public enum NotificationDismissalContext: Sendable { } } - /// Whether a dismissal in this context flashes the pane. Typing means the - /// user is already working in that pane, so the cleared ring is the - /// acknowledgement; a flash under their cursor only adds noise. + /// Whether a dismissal in this context flashes the pane. + /// + /// - Parameter flashOnTyping: Whether terminal typing should flash the + /// pane after it dismisses a notification. + public func flashesOnDismiss(flashOnTyping: Bool) -> Bool { + switch self { + case .terminalInteraction: + return flashOnTyping + case .activeFocus, .explicitWorkspaceResume, .directInteraction: + return true + } + } + + /// Whether a dismissal in this context flashes the pane using the calmer + /// default for terminal typing. public var flashesOnDismiss: Bool { - self != .terminalInteraction + flashesOnDismiss(flashOnTyping: false) } /// Whether this context may clear a manually-set unread indicator. diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalHosting.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalHosting.swift index ce08e85d5805..a7343a564500 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalHosting.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalHosting.swift @@ -60,6 +60,11 @@ public protocol NotificationDismissalHosting: AnyObject { /// default) the legacy workspace-visibility withdraw is preserved. var suppressOnlyFocusedSurface: Bool { get } + /// Whether terminal typing should flash the pane after dismissing its + /// notification. The app setting defaults to the legacy flashing behavior; + /// turning it off gives typing the calmer dismissal feedback. + var paneFlashOnTyping: Bool { get } + // MARK: Workspace indicator reads /// Whether the panel carries a manually-set unread indicator. diff --git a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift index 2ce3d20b5826..5f0313156274 100644 --- a/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift +++ b/Packages/macOS/CmuxNotifications/Sources/CmuxNotifications/NotificationDismissalModel.swift @@ -240,7 +240,7 @@ public final class NotificationDismissalModel: NotificationDismissing { host.storeClearFocusedReadIndicator(workspaceId: workspaceId, surfaceId: surfaceId) } } - if let targetPanelId, context.flashesOnDismiss { + if let targetPanelId, context.flashesOnDismiss(flashOnTyping: host.paneFlashOnTyping) { if hasUnreadNotification || hasFocusedIndicator { host.workspaceTriggerNotificationDismissFlash(workspaceId: workspaceId, panelId: targetPanelId) } else if didDismissUnreadIndicator { diff --git a/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift b/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift index 5b360f46f9fe..5ba23b95bfb7 100644 --- a/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift +++ b/Packages/macOS/CmuxNotifications/Tests/CmuxNotificationsTests/NotificationDismissalModelTests.swift @@ -13,6 +13,7 @@ private final class FakeHost: NotificationDismissalHosting { var focusedPanelIds: [UUID: UUID] = [:] var focusedSurfaceIds: [UUID: UUID] = [:] var suppressOnlyFocusedSurface = false + var paneFlashOnTyping = false var panelIdsBySurface: [UUID: UUID] = [:] var manualPanelUnread: Set = [] var restoredPanelUnread: Set = [] @@ -269,6 +270,16 @@ struct NotificationDismissalModelTests { #expect(host.log == ["markRead:\(prefix)", "clearFocusedRead:\(prefix)"]) } + @Test func typingCanFlashWhenEnabled() { + let (model, host, workspaceId, panelId) = makeModel() + host.paneFlashOnTyping = true + host.unreadNotificationSurfaces = [panelId] + + #expect(model.dismissNotificationOnTerminalInteraction(workspaceId: workspaceId, surfaceId: panelId)) + let prefix = String(panelId.uuidString.prefix(4)) + #expect(host.log == ["markRead:\(prefix)", "clearFocusedRead:\(prefix)", "notificationFlash"]) + } + @Test func restoredUnreadNotClearedByPlainActiveFocus() { let (model, host, workspaceId, panelId) = makeModel() host.restoredPanelUnread = [panelId] diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift index 536e57214e50..4f6784fe88a6 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/NotificationsCatalogSection.swift @@ -33,6 +33,13 @@ public struct NotificationsCatalogSection: SettingCatalogSection { userDefaultsKey: "notificationPaneFlashDoubleBlink" ) + /// Flash the pane when terminal typing dismisses its notification. + public let paneFlashOnTyping = DefaultsKey( + id: "notifications.paneFlashOnTyping", + defaultValue: true, + userDefaultsKey: "notificationPaneFlashOnTyping" + ) + public let paneFlashThemeColor = DefaultsKey( id: "notifications.paneFlashThemeColor", defaultValue: false, diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 704d3e3511b5..86389375ff53 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -606555,6 +606555,201 @@ } } }, + "settings.notifications.paneFlashOnTyping.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash While Typing" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض أثناء الكتابة" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Tippen blinken" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Parpadeo al escribir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Clignoter pendant la saisie" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "入力中に点滅" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "입력 중 깜빡임" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Блимати під час введення" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "输入时闪烁" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "輸入時閃爍" + } + } + } + }, + "settings.notifications.paneFlashOnTyping.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash the pane when typing dismisses its notification." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "وميض اللوحة عند رفض إشعارها أثناء الكتابة." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Fenster aufblitzen lassen, wenn Tippen die Benachrichtigung schließt." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Hacer parpadear el panel cuando escribir descarta su notificación." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Faire clignoter le volet lorsque la saisie efface sa notification." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "入力で通知を消したときにペインを点滅させます。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "입력으로 알림을 지울 때 패인을 깜빡입니다." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Блимати панеллю, коли введення закриває її сповіщення." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "输入文字清除通知时闪烁窗格。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "輸入文字清除通知時閃爍窗格。" + } + } + } + }, + "settings.search.alias.setting.app.pane-flash-on-typing": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse وميض أثناء الكتابة" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse blinken beim tippen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse destello al escribir" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse flash pendant la saisie" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse 入力中に点滅" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse 입력 중 깜빡임" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse спалах під час введення" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse 输入时闪烁" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse 輸入時閃爍" + } + } + } + }, "settings.search.alias.setting.app.pane-flash-double-blink": { "extractionState": "manual", "localizations": { diff --git a/Sources/CmuxSettingsFileStore+SupportedPaths.swift b/Sources/CmuxSettingsFileStore+SupportedPaths.swift index 8b0838d4937a..2f8d6ac22c18 100644 --- a/Sources/CmuxSettingsFileStore+SupportedPaths.swift +++ b/Sources/CmuxSettingsFileStore+SupportedPaths.swift @@ -75,6 +75,7 @@ extension CmuxSettingsFileStore { "notifications.unreadPaneRing", "notifications.paneFlash", "notifications.paneFlashDoubleBlink", + "notifications.paneFlashOnTyping", "notifications.paneFlashThemeColor", "notifications.paneFlashColor", "notifications.sound", diff --git a/Sources/CmuxSettingsJSONPathSupport.swift b/Sources/CmuxSettingsJSONPathSupport.swift index 40cb2246c8b5..da9a0e8194ee 100644 --- a/Sources/CmuxSettingsJSONPathSupport.swift +++ b/Sources/CmuxSettingsJSONPathSupport.swift @@ -239,6 +239,7 @@ enum NotificationSettingsFileMapping { .init(jsonKey: "unreadPaneRing", defaultsKey: NotificationPaneRingSettings.enabledKey), .init(jsonKey: "paneFlash", defaultsKey: NotificationPaneFlashSettings.enabledKey), .init(jsonKey: "paneFlashDoubleBlink", defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey), + .init(jsonKey: "paneFlashOnTyping", defaultsKey: NotificationPaneFlashSettings.onTypingKey), .init(jsonKey: "paneFlashThemeColor", defaultsKey: NotificationPaneFlashSettings.themeColorKey), .init( jsonKey: "soundWhenFocused", diff --git a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift index 2aa3701d5279..8909575ec131 100644 --- a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift +++ b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift @@ -440,6 +440,17 @@ enum CommandPaletteSettingsToggleCommands { defaultValue: NotificationPaneFlashSettings.defaultDoubleBlink, defaultsKey: NotificationPaneFlashSettings.doubleBlinkKey ), + CommandPaletteSettingToggleDescriptor( + commandId: commandIdPrefix + "paneFlashOnTyping", + settingsKey: "notifications.paneFlashOnTyping", + title: { + String(localized: "settings.notifications.paneFlashOnTyping.title", defaultValue: "Flash While Typing") + }, + sectionTitle: app, + keywords: ["notifications.paneFlashOnTyping", "pane", "flash", "typing", "notification"], + defaultValue: NotificationPaneFlashSettings.defaultOnTyping, + defaultsKey: NotificationPaneFlashSettings.onTypingKey + ), CommandPaletteSettingToggleDescriptor( commandId: commandIdPrefix + "sendAnonymousTelemetry", settingsKey: "app.sendAnonymousTelemetry", diff --git a/Sources/KeyboardShortcutSettingsFileStore+Template.swift b/Sources/KeyboardShortcutSettingsFileStore+Template.swift index a9b7fb42c65f..aa717444e3f3 100644 --- a/Sources/KeyboardShortcutSettingsFileStore+Template.swift +++ b/Sources/KeyboardShortcutSettingsFileStore+Template.swift @@ -132,6 +132,7 @@ extension CmuxSettingsFileStore { "unreadPaneRing": NotificationPaneRingSettings.defaultEnabled, "paneFlash": NotificationPaneFlashSettings.defaultEnabled, "paneFlashDoubleBlink": NotificationPaneFlashSettings.defaultDoubleBlink, + "paneFlashOnTyping": NotificationPaneFlashSettings.defaultOnTyping, "paneFlashThemeColor": NotificationPaneFlashSettings.defaultThemeColor, "paneFlashColor": NSNull(), "sound": NotificationSoundSettings.defaultValue, diff --git a/Sources/SettingsSearchAliases.swift b/Sources/SettingsSearchAliases.swift index eed41f64bcf6..91767d5018de 100644 --- a/Sources/SettingsSearchAliases.swift +++ b/Sources/SettingsSearchAliases.swift @@ -94,6 +94,7 @@ enum SettingsSearchAliasIndex { "app:unread-pane-ring": localized("settings.search.alias.setting.app.unread-pane-ring", defaultValue: "notifications.unreadPaneRing blue border unread ring notification pane outline"), "app:pane-flash": localized("settings.search.alias.setting.app.pane-flash", defaultValue: "notifications.paneFlash flash blink highlight pane notification pulse"), "app:pane-flash-double-blink": localized("settings.search.alias.setting.app.pane-flash-double-blink", defaultValue: "notifications.paneFlashDoubleBlink pane flash double blink twice pulse"), + "app:pane-flash-on-typing": localized("settings.search.alias.setting.app.pane-flash-on-typing", defaultValue: "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse"), "app:desktop-notifications": localized("settings.search.alias.setting.app.desktop-notifications", defaultValue: "macos desktop notifications system settings permission alerts notify test"), "app:notification-sound": localized("settings.search.alias.setting.app.notification-sound", defaultValue: "notifications.sound notifications.customSoundFilePath sound audio alert chime beep custom file wav mp3 caf aiff"), "app:notification-sound-overrides": localized("settings.search.alias.setting.app.notification-sound-overrides", defaultValue: "notifications.soundOverrides per-agent agent sound turn done needs input permission error stalled custom file"), diff --git a/Sources/SettingsSearchIndex.swift b/Sources/SettingsSearchIndex.swift index ba732fe5ddb5..38f4a43c2a7e 100644 --- a/Sources/SettingsSearchIndex.swift +++ b/Sources/SettingsSearchIndex.swift @@ -52,6 +52,7 @@ enum SettingsSearchIndex { setting(.app, "pane-flash", String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), "notification flash highlight"), setting(.app, "pane-flash-theme-color", String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), "notification flash terminal theme foreground blue ring"), setting(.app, "pane-flash-double-blink", String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), "pane flash blink twice pulse"), + setting(.app, "pane-flash-on-typing", String(localized: "settings.notifications.paneFlashOnTyping.title", defaultValue: "Flash While Typing"), "notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse"), setting(.app, "desktop-notifications", String(localized: "settings.notifications.desktop", defaultValue: "Desktop Notifications"), "permission alerts test notification"), setting(.app, "notification-sound", String(localized: "settings.notifications.sound.title", defaultValue: "Notification Sound"), "custom sound alert audio"), setting( diff --git a/Sources/TabManager+NotificationDismissalHosting.swift b/Sources/TabManager+NotificationDismissalHosting.swift index fef4c5bbdf30..d494f6dd15e9 100644 --- a/Sources/TabManager+NotificationDismissalHosting.swift +++ b/Sources/TabManager+NotificationDismissalHosting.swift @@ -65,6 +65,11 @@ extension TabManager: NotificationDismissalHosting { .value(for: Self.notificationsSettings.suppressOnlyFocusedSurface) } + var paneFlashOnTyping: Bool { + UserDefaultsSettingsClient(defaults: .standard) + .value(for: Self.notificationsSettings.paneFlashOnTyping) + } + func panelId(forSurfaceOrPanelId surfaceId: UUID, in workspaceId: UUID) -> UUID? { if let dock = DockSplitStore.liveStore(containingPanel: surfaceId), dock.workspaceId == workspaceId, diff --git a/Sources/TerminalNotificationStoreSettings.swift b/Sources/TerminalNotificationStoreSettings.swift index 88cc9ed7341d..6338d145ee29 100644 --- a/Sources/TerminalNotificationStoreSettings.swift +++ b/Sources/TerminalNotificationStoreSettings.swift @@ -26,6 +26,8 @@ enum NotificationPaneFlashSettings { static let defaultEnabled = true static let doubleBlinkKey = "notificationPaneFlashDoubleBlink" static let defaultDoubleBlink = true + static let onTypingKey = "notificationPaneFlashOnTyping" + static let defaultOnTyping = true static let themeColorKey = "notificationPaneFlashThemeColor" static let defaultThemeColor = false @@ -34,6 +36,11 @@ enum NotificationPaneFlashSettings { defaults.object(forKey: doubleBlinkKey) as? Bool ?? defaultDoubleBlink } + /// Whether terminal typing should flash the pane after dismissing a notification. + static func flashesOnTyping(defaults: UserDefaults = .standard) -> Bool { + defaults.object(forKey: onTypingKey) as? Bool ?? defaultOnTyping + } + static func isEnabled(defaults: UserDefaults = .standard) -> Bool { if defaults.object(forKey: enabledKey) == nil { return defaultEnabled diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index 49232416d8f9..ac0a76ed1e8d 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -87,6 +87,7 @@ Notification behavior from Settings > Notifications. | `notifications.unreadPaneRing` | boolean | `true` | Highlight panes with unread notifications. | | `notifications.paneFlash` | boolean | `true` | Flash the focused pane when requested. | | `notifications.paneFlashDoubleBlink` | boolean | `true` | Use the default double blink; set false for the configurable single 0.6-second pulse. | +| `notifications.paneFlashOnTyping` | boolean | `true` | Flash the pane when terminal typing dismisses its notification. Set false for a calmer typing interaction. | | `notifications.sound` | `"default"` or `"Basso"` or `"Blow"` or `"Bottle"` or `"Frog"` or `"Funk"` or `"Glass"` or `"Hero"` or `"Morse"` or `"Ping"` or `"Pop"` or `"Purr"` or `"Sosumi"` or `"Submarine"` or `"Tink"` or `"custom_file"` or `"none"` | `"default"` | Notification sound preset. | | `notifications.soundWhenFocused` | boolean | `false` | Play the notification sound even when the pane that notified is already focused. Off by default, so a focused pane shows only its ring and flash. | | `notifications.customSoundFilePath` | string | `""` | Local path to the custom notification sound file. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 6c79ad559107..fdb86f0529fa 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -1159,6 +1159,12 @@ "default": true, "description": "Blink the pane flash twice instead of one short pulse." }, + "paneFlashOnTyping": { + "x-cmux-scopes": ["global"], + "type": "boolean", + "default": true, + "description": "Flash the pane when terminal typing dismisses its notification." + }, "paneFlashThemeColor": { "x-cmux-scopes": ["global"], "type": "boolean", From 3f87103769e19560c9e992a44e439c04f2f68eb7 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 1 Oct 2026 15:21:26 -0700 Subject: [PATCH 23/23] feat: expose typing flash in Settings Co-Authored-By: Claude Opus 5.5 --- .../Navigation/CuratedSettingEntry+Default.swift | 1 + .../CmuxSettingsUI/Sections/AppSection.swift | 16 +++++++++++++++- .../SettingsRowAnchorResolutionTests.swift | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift index 383e8b1b0073..f19db211de71 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift @@ -147,6 +147,7 @@ extension Array where Element == CuratedSettingEntry { .init(section: .app, id: "unread-pane-ring", title: String(localized: "settings.notifications.paneRing.title", defaultValue: "Unread Pane Ring"), synonyms: "Unread Pane Ring notifications.unreadPaneRing blue border unread ring notification pane outline"), .init(section: .app, id: "pane-flash", title: String(localized: "settings.notifications.paneFlash.title", defaultValue: "Pane Flash"), synonyms: "Pane Flash notifications.paneFlash flash blink highlight pane notification pulse"), .init(section: .app, id: "pane-flash-double-blink", title: String(localized: "settings.notifications.paneFlashDoubleBlink.title", defaultValue: "Double Blink"), synonyms: "Double Blink notifications.paneFlashDoubleBlink pane flash blink twice pulse"), + .init(section: .app, id: "pane-flash-on-typing", title: String(localized: "settings.notifications.paneFlashOnTyping.title", defaultValue: "Flash While Typing"), synonyms: "Flash While Typing notifications.paneFlashOnTyping pane flash typing terminal interaction notification pulse"), .init(section: .app, id: "pane-flash-theme-color", title: String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), synonyms: "Use Theme Foreground notifications.paneFlashThemeColor pane flash terminal theme foreground"), .init( section: .app, diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift index 0976d364b3a7..e8ba35dcdbcd 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift @@ -55,6 +55,7 @@ public struct AppSection: View { @State private var paneRing: DefaultsValueModel @State private var paneFlash: DefaultsValueModel @State private var paneFlashDoubleBlink: DefaultsValueModel + @State private var paneFlashOnTyping: DefaultsValueModel @State private var paneFlashThemeColor: DefaultsValueModel @State private var desktopNotifications: DesktopNotificationAuthorizationModel @State private var agentPermissionPrompt: DefaultsValueModel @@ -127,6 +128,7 @@ public struct AppSection: View { _paneRing = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.unreadPaneRing)) _paneFlash = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlash)) _paneFlashDoubleBlink = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashDoubleBlink)) + _paneFlashOnTyping = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashOnTyping)) _paneFlashThemeColor = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.paneFlashThemeColor)) _desktopNotifications = State(initialValue: DesktopNotificationAuthorizationModel(hostActions: hostActions)) _agentPermissionPrompt = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.notifications.agentPermissionPrompt)) @@ -171,7 +173,7 @@ public struct AppSection: View { AppChannelSwitchCard(hostActions: hostActions) } .task { - startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, paneFlashThemeColor, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) + startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, paneFlashOnTyping, paneFlashThemeColor, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) await soundAgentCache.loadIfNeeded { await hostActions.notificationSoundAgentOptions() } if languageAtAppear == nil { languageAtAppear = language.current }; if telemetryAtAppear == nil { telemetryAtAppear = telemetry.current } } @@ -723,6 +725,18 @@ public struct AppSection: View { } SettingsCardDivider() + SettingsCardRow( + configurationReview: .json("notifications.paneFlashOnTyping"), + String(localized: "settings.notifications.paneFlashOnTyping.title", defaultValue: "Flash While Typing"), + subtitle: String(localized: "settings.notifications.paneFlashOnTyping.subtitle", defaultValue: "Flash the pane when typing dismisses its notification.") + ) { + Toggle("", isOn: Binding(get: { paneFlashOnTyping.current }, set: { paneFlashOnTyping.set($0) })) + .labelsHidden() + .controlSize(.small) + .disabled(!paneFlash.current) + } + SettingsCardDivider() + SettingsCardRow( configurationReview: .json("notifications.paneFlashThemeColor"), String(localized: "settings.notifications.paneFlashThemeColor.title", defaultValue: "Use Theme Foreground"), diff --git a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift index e417e8373326..4a552882aaa0 100644 --- a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift +++ b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift @@ -109,6 +109,7 @@ struct SettingsRowAnchorResolutionTests { "notifications.dockBadge", "notifications.paneFlash", "notifications.paneFlashDoubleBlink", + "notifications.paneFlashOnTyping", "notifications.paneFlashThemeColor", "notifications.paneFlashColor", "notifications.showInMenuBar",