Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
310 lines
9.1 KiB
Swift
310 lines
9.1 KiB
Swift
import CoreGraphics
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
extension RootTabs {
|
|
struct PhoneChatReturn: Equatable {
|
|
let destination: SidebarDestination
|
|
let openChatRequestID: Int
|
|
}
|
|
|
|
struct PhoneControlNavigationRequest: Equatable {
|
|
enum Target: Equatable {
|
|
case root
|
|
case detail(SidebarDestination)
|
|
}
|
|
|
|
let id: Int
|
|
let target: Target
|
|
}
|
|
|
|
private static var sidebarPersistentWidthThreshold: CGFloat {
|
|
980
|
|
}
|
|
|
|
static let sidebarSplitIdealWidth: CGFloat = 316
|
|
static let sidebarSplitMaximumWidth: CGFloat = 340
|
|
static let sidebarDrawerMaximumWidth: CGFloat = 340
|
|
static let sidebarShowButtonAccessibilityIdentifier = "RootTabs.Sidebar.Show"
|
|
static let sidebarHideButtonAccessibilityIdentifier = "RootTabs.Sidebar.Hide"
|
|
|
|
enum AppTab: Hashable {
|
|
case control
|
|
case chat
|
|
case talk
|
|
case agent
|
|
case settings
|
|
}
|
|
|
|
enum SidebarDestination: String, CaseIterable, Hashable, Identifiable {
|
|
case chat
|
|
case talk
|
|
case overview
|
|
case activity
|
|
case agents
|
|
case workboard
|
|
case skillWorkshop
|
|
case instances
|
|
case sessions
|
|
case dreaming
|
|
case usage
|
|
case cron
|
|
case terminal
|
|
case docs
|
|
case settings
|
|
case gateway
|
|
|
|
var id: String {
|
|
rawValue
|
|
}
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .chat: "Chat"
|
|
case .talk: "Talk"
|
|
case .overview: "Overview"
|
|
case .activity: "Activity"
|
|
case .agents: "Agents"
|
|
case .workboard: "Workboard"
|
|
case .skillWorkshop: "Skill Workshop"
|
|
case .instances: "Instances"
|
|
case .sessions: "Sessions"
|
|
case .dreaming: "Dreaming"
|
|
case .usage: "Usage"
|
|
case .cron: "Cron Jobs"
|
|
case .terminal: "Terminal"
|
|
case .docs: "Docs"
|
|
case .settings: "Settings"
|
|
case .gateway: "Settings / Gateway"
|
|
}
|
|
}
|
|
|
|
var sidebarTitle: String {
|
|
switch self {
|
|
case .gateway: "Connection"
|
|
default: self.title
|
|
}
|
|
}
|
|
|
|
var systemImage: String {
|
|
switch self {
|
|
case .chat: "bubble.left"
|
|
case .talk: "waveform.circle"
|
|
case .overview: "chart.bar"
|
|
case .activity: "waveform.path.ecg"
|
|
case .agents: "person.2"
|
|
case .workboard: "folder"
|
|
case .skillWorkshop: "hammer"
|
|
case .instances: "dot.radiowaves.left.and.right"
|
|
case .sessions: "doc.text"
|
|
case .dreaming: "moon.stars"
|
|
case .usage: "chart.bar.xaxis"
|
|
case .cron: "timer"
|
|
case .terminal: "terminal"
|
|
case .docs: "book"
|
|
case .settings: "gearshape"
|
|
case .gateway: "gearshape"
|
|
}
|
|
}
|
|
|
|
var appTab: AppTab {
|
|
switch self {
|
|
case .chat:
|
|
.chat
|
|
case .talk:
|
|
.talk
|
|
case .agents:
|
|
.agent
|
|
case .settings, .gateway:
|
|
.settings
|
|
case .overview, .activity, .workboard, .skillWorkshop, .instances, .sessions, .dreaming,
|
|
.usage,
|
|
.cron, .terminal, .docs:
|
|
.control
|
|
}
|
|
}
|
|
|
|
var settingsRoute: SettingsRoute? {
|
|
switch self {
|
|
case .gateway:
|
|
.gateway
|
|
case .chat, .talk, .overview, .activity, .agents, .workboard, .skillWorkshop, .instances, .sessions,
|
|
.dreaming,
|
|
.usage, .cron, .terminal, .settings, .docs:
|
|
nil
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SidebarLayoutMode: Equatable {
|
|
case drawer
|
|
case split
|
|
}
|
|
|
|
static func sidebarLayoutMode(containerSize: CGSize) -> SidebarLayoutMode {
|
|
containerSize.width < self.sidebarPersistentWidthThreshold || containerSize.height > containerSize.width
|
|
? .drawer
|
|
: .split
|
|
}
|
|
|
|
static func preferredSidebarVisibility(layoutMode: SidebarLayoutMode) -> Bool {
|
|
layoutMode == .split
|
|
}
|
|
|
|
static func shouldCollapseSidebarAfterSelection(layoutMode: SidebarLayoutMode) -> Bool {
|
|
layoutMode == .drawer
|
|
}
|
|
|
|
static func sidebarWidth(containerWidth: CGFloat, isDrawerLayout: Bool) -> CGFloat {
|
|
if isDrawerLayout {
|
|
return min(self.sidebarDrawerMaximumWidth, max(280, containerWidth * 0.86))
|
|
}
|
|
return min(self.sidebarSplitMaximumWidth, max(self.sidebarSplitIdealWidth, containerWidth * 0.25))
|
|
}
|
|
|
|
static func shouldShowSidebarRevealControl(isSidebarVisible: Bool) -> Bool {
|
|
!isSidebarVisible
|
|
}
|
|
|
|
static func shouldShowSidebarRevealInDestinationHeader(
|
|
isSidebarVisible: Bool,
|
|
layoutMode: SidebarLayoutMode) -> Bool
|
|
{
|
|
switch layoutMode {
|
|
case .split:
|
|
true
|
|
case .drawer:
|
|
self.shouldShowSidebarRevealControl(isSidebarVisible: isSidebarVisible)
|
|
}
|
|
}
|
|
|
|
static func requestedInitialSidebarVisibility(arguments: [String]) -> Bool? {
|
|
guard let flagIndex = arguments.firstIndex(of: "--openclaw-sidebar-visibility") else {
|
|
return nil
|
|
}
|
|
let valueIndex = arguments.index(after: flagIndex)
|
|
guard arguments.indices.contains(valueIndex) else { return nil }
|
|
|
|
switch arguments[valueIndex].trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
|
|
case "visible", "show", "shown", "open", "true", "1":
|
|
return true
|
|
case "hidden", "hide", "closed", "false", "0":
|
|
return false
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static func shouldOpenRootTabFromPhoneHub(_ destination: SidebarDestination) -> Bool {
|
|
switch destination {
|
|
case .chat, .talk, .agents, .gateway, .settings:
|
|
true
|
|
case .overview, .activity, .workboard, .skillWorkshop, .instances, .sessions, .dreaming,
|
|
.usage,
|
|
.cron, .terminal, .docs:
|
|
false
|
|
}
|
|
}
|
|
|
|
static func defaultSidebarDestination(for tab: AppTab) -> SidebarDestination {
|
|
switch tab {
|
|
case .control:
|
|
.overview
|
|
case .chat:
|
|
.chat
|
|
case .talk:
|
|
.talk
|
|
case .agent:
|
|
.agents
|
|
case .settings:
|
|
.settings
|
|
}
|
|
}
|
|
|
|
enum StartupPresentationRoute: Equatable {
|
|
case none
|
|
case onboarding
|
|
case settings
|
|
}
|
|
|
|
static func startupPresentationRoute(
|
|
gatewayConnected: Bool,
|
|
hasConnectedOnce: Bool,
|
|
onboardingComplete: Bool,
|
|
hasExistingGatewayConfig: Bool,
|
|
shouldPresentOnLaunch: Bool) -> StartupPresentationRoute
|
|
{
|
|
if gatewayConnected {
|
|
return .none
|
|
}
|
|
if shouldPresentOnLaunch || !hasConnectedOnce || !onboardingComplete {
|
|
return .onboarding
|
|
}
|
|
if !hasExistingGatewayConfig {
|
|
return .settings
|
|
}
|
|
return .none
|
|
}
|
|
|
|
static func shouldPresentQuickSetup(
|
|
quickSetupDismissed: Bool,
|
|
showOnboarding: Bool,
|
|
hasPresentedSheet: Bool,
|
|
gatewayConnected: Bool,
|
|
hasExistingGatewayConfig: Bool,
|
|
discoveredGatewayCount: Int) -> Bool
|
|
{
|
|
guard !quickSetupDismissed else { return false }
|
|
guard !showOnboarding else { return false }
|
|
guard !hasPresentedSheet else { return false }
|
|
guard !gatewayConnected else { return false }
|
|
guard !hasExistingGatewayConfig else { return false }
|
|
return discoveredGatewayCount > 0
|
|
}
|
|
|
|
struct SidebarGroup: Identifiable {
|
|
let title: String
|
|
let destinations: [SidebarDestination]
|
|
|
|
var id: String {
|
|
self.title
|
|
}
|
|
}
|
|
|
|
static let sidebarGroups: [SidebarGroup] = [
|
|
SidebarGroup(title: "CHAT", destinations: [.chat, .talk]),
|
|
SidebarGroup(
|
|
title: "CONTROL",
|
|
destinations: [
|
|
.overview,
|
|
.activity,
|
|
.agents,
|
|
.workboard,
|
|
.skillWorkshop,
|
|
.instances,
|
|
.sessions,
|
|
.dreaming,
|
|
.usage,
|
|
.cron,
|
|
.terminal,
|
|
]),
|
|
SidebarGroup(
|
|
title: "SETTINGS",
|
|
destinations: [.settings]),
|
|
SidebarGroup(title: "REFERENCE", destinations: [.docs]),
|
|
]
|
|
|
|
static var phoneControlGroups: [SidebarGroup] {
|
|
// Agents owns a bottom tab and its hub entry duplicated the same destination;
|
|
// Chat and Talk stay per the tested Control-hub IA contract.
|
|
let tabOwned: Set<SidebarDestination> = [.agents]
|
|
return self.sidebarGroups
|
|
.map { group in
|
|
SidebarGroup(
|
|
title: group.title,
|
|
destinations: group.destinations.filter { !tabOwned.contains($0) })
|
|
}
|
|
.filter { !$0.destinations.isEmpty }
|
|
}
|
|
}
|