Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .swiftlint-baseline.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/ios/Codevisor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODEVISOR_IOS_BUNDLE_IDENTIFIER = com.dylanplayer.codevisor.ios;
CODEVISOR_IOS_BUNDLE_IDENTIFIER = com.codevisor.ios;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
Expand Down Expand Up @@ -324,7 +324,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.dylanplayer.codevisor.ios;
PRODUCT_BUNDLE_IDENTIFIER = com.codevisor.ios;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct IOSNavigationDiagnosticState: Equatable {
enum IOSNavigationDiagnostics {
#if DEBUG || NAVIGATION_DIAGNOSTICS
private static let logger = Logger(
subsystem: "com.851labs.codevisor",
subsystem: "com.codevisor",
category: "ios-navigation"
)
private static var sequence: UInt64 = 0
Expand Down
2 changes: 1 addition & 1 deletion apps/ios/Codevisor/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.dylanplayer.codevisor.ios</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>codevisor</string>
Expand Down
10 changes: 7 additions & 3 deletions apps/macos/Codevisor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Info.plist;
CODEVISOR_DEV_BUNDLE_IDENTIFIER = com.851labs.Codevisor.Development;
CODEVISOR_DEV_BUNDLE_IDENTIFIER = com.codevisor.macos.dev;
CODEVISOR_URL_SCHEME = "codevisor-dev";
CODEVISOR_DEV_DISPLAY_NAME = "Codevisor Dev";
CODEVISOR_DEV_PRODUCT_NAME = "Codevisor Dev";
Expand Down Expand Up @@ -454,8 +454,12 @@
"-framework",
Combine,
);
// Keep the shipping identity stable so HerdMan installs update in place.
PRODUCT_BUNDLE_IDENTIFIER = com.851labs.HerdMan;
// Codevisor LLC identity. Sparkle carries existing installs across the
// rename from com.851labs.HerdMan: trust rides the EdDSA feed signature
// (SUPublicEDKey), and SUInstaller matches the update by app filename.
// First launch after the rename runs the legacy-identity migration
// (defaults domain, launch agent) in CodevisorClient/CodevisorCoreMac.
PRODUCT_BUNDLE_IDENTIFIER = com.codevisor.macos;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
Expand Down
4 changes: 4 additions & 0 deletions apps/macos/Codevisor/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ struct CodevisorApp: App {
@State private var startupInProgress = false

init() {
// Before anything reads defaults (Sparkle, window restoration):
// adopt the com.851labs.HerdMan domain left behind by pre-rename
// installs. One-time, marker-guarded.
LegacyDefaultsMigration.migrateIfNeeded()
let serverAgent = MacServerAgentController()
_environment = State(initialValue: nil)
_serverAgent = State(initialValue: serverAgent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import ServiceManagement
/// relocatable with the signed app bundle, and survives app/UI restarts.
@MainActor
final class MacServerAgentController {
static let plistName = "com.851labs.Codevisor.ServerAgent.plist"
static let plistName = "com.codevisor.macos.ServerAgent.plist"
/// The pre-team-migration agent (851labs identity). Its plist still ships
/// in the bundle solely so SMAppService can cleanly unregister the
/// registration left behind by pre-migration installs — unregistering
/// requires the service definition to exist. Remove the plist and this
/// path once the fleet has migrated.
static let legacyPlistName = "com.851labs.Codevisor.ServerAgent.plist"
private let legacyJobs = LegacyServerJobRetirer()

// Constructed on demand (it is cheap) so each detached closure below can
Expand All @@ -17,6 +23,10 @@ final class MacServerAgentController {
SMAppService.agent(plistName: plistName)
}

private nonisolated static var legacyService: SMAppService {
SMAppService.agent(plistName: legacyPlistName)
}

var managedService: LocalCodevisorManagedService {
LocalCodevisorManagedService(
prepare: { [weak self] in try await self?.retireLegacyJobs() },
Expand All @@ -33,6 +43,13 @@ final class MacServerAgentController {
// SMAppService.status/register/unregister are synchronous XPC round
// trips to launchd/smd, so keep them off the main actor.
try await Task.detached {
// A pre-migration install registered the agent under the 851labs
// plist; drop that registration before the renamed agent takes
// ownership, or launchd would bootstrap both at next login.
let legacy = Self.legacyService
if legacy.status == .enabled || legacy.status == .requiresApproval {
try? await legacy.unregister()
}
let current = Self.service
// This closure is reached only when no matching service is healthy.
// Re-register an enabled-but-dead job so launchd resolves BundleProgram
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.codevisor.macos.ServerAgent</string>
<key>BundleProgram</key>
<string>Contents/Resources/codevisor-server-agent</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>2</integer>
</dict>
</plist>
8 changes: 4 additions & 4 deletions apps/macos/ERROR_HANDLING_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Audit date: 2026-07-11. Scope: `apps/macos/` excluding `Vendor/` (Ghostty) and tests.
Raw counts: ~141 `try?` sites, ~57 bare `catch {` blocks. Only one `os.Logger` exists in
non-vendor code (`ScratchpadModel`, subsystem `com.851labs.codevisor`, category `scratchpad`).
non-vendor code (`ScratchpadModel`, subsystem `com.codevisor`, category `scratchpad`).

Legend: **(a)** must surface a human-readable error to the user · **(b)** log-only is fine
(best-effort/cosmetic) · **(c)** judgment call.
Expand Down Expand Up @@ -110,7 +110,7 @@ Persistence and sync failures that lose state invisibly:
import os

public enum Log {
public static let subsystem = "com.851labs.codevisor" // matches ScratchpadModel's existing subsystem
public static let subsystem = "com.codevisor" // matches ScratchpadModel's existing subsystem
public static let persistence = Logger(subsystem: subsystem, category: "persistence")
public static let server = Logger(subsystem: subsystem, category: "server")
public static let session = Logger(subsystem: subsystem, category: "session")
Expand All @@ -124,8 +124,8 @@ public enum Log {
```

- Viewing: Console.app filtered on subsystem, or
`log show --predicate 'subsystem == "com.851labs.codevisor"' --last 1h --info --debug`
`log stream --predicate 'subsystem == "com.851labs.codevisor"' --level debug`
`log show --predicate 'subsystem == "com.codevisor"' --last 1h --info --debug`
`log stream --predicate 'subsystem == "com.codevisor"' --level debug`
- Use `.error` for swallowed failures, `privacy: .public` on error descriptions so release builds aren't `<private>`.
- Optional "Export Logs" (Help menu) via `OSLogStore(scope: .currentProcessIdentifier)` → save to file for bug reports.
- Small packages (ACPKit/StreamMarkdown/CodeHighlighter/CodevisorTheming) that shouldn't depend on CodevisorCore: give each a private `Logger` with the same subsystem string.
Expand Down
3 changes: 3 additions & 0 deletions apps/www/public/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ install_macos() {
fi
[ -d "$app_src" ] || fail "Codevisor.app not found in downloaded archive"

# Quit whichever identity is running: com.851labs.HerdMan predates the
# Codevisor, LLC migration.
osascript -e 'tell application id "com.codevisor.macos" to quit' >/dev/null 2>&1 || true
osascript -e 'tell application id "com.851labs.HerdMan" to quit' >/dev/null 2>&1 || true
[ ! -d "$app_dest" ] || { say "Replacing existing $app_dest"; rm -rf "$app_dest"; }
[ ! -d "$legacy_app_dest" ] || { say "Removing former $legacy_app_dest"; rm -rf "$legacy_app_dest"; }
Expand Down
4 changes: 2 additions & 2 deletions docs/ios-app-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Guiding principles:
platform blocks merge.

Status note: scaffolding is already done — `apps/ios/Codevisor.xcodeproj`
(bundle id `com.dylanplayer.codevisor.ios`, temp until the Codevisor LLC
(bundle id `com.codevisor.ios` under the Codevisor LLC
developer account exists), shared package linkage proven (ACPKit +
CodevisorTheming), `bun run dev:ios` starts the Dev Remote server and boots the
app in the simulator, and the Xcode MCP tooling loop (build/run/tap/logs) is
Expand Down Expand Up @@ -183,7 +183,7 @@ Work items:
- Machine picker affordance + settings entry point.
3. **Worktree dev-experience parity** (`scripts/dev-ios.mjs`)
- Per-worktree display name `Codevisor (worktree)` and bundle id suffix
`com.dylanplayer.codevisor.ios.<instanceHash>` so multiple worktree builds
`com.codevisor.ios.<instanceHash>` so multiple worktree builds
coexist on one simulator/device.
- Generated worktree-colored app icon: port `createDevelopmentAppIcon()` to
produce an iOS asset catalog icon (same hash→hue derivation, same
Expand Down
2 changes: 1 addition & 1 deletion packages/swift/ACPKit/Sources/ACPKit/Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import os
/// it carries its own `Logger` under the app's shared subsystem. Interpolated
/// error strings use `privacy: .public` so release-build diagnostics stay
/// readable; never log message bodies or file contents.
let acpLog = Logger(subsystem: "com.851labs.codevisor", category: "acp")
let acpLog = Logger(subsystem: "com.codevisor", category: "acp")
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import os
/// Package-private logging handle: CodeHighlighter must not depend on
/// CodevisorCore, so it carries its own `Logger` under the app's shared
/// subsystem.
private let log = Logger(subsystem: "com.851labs.codevisor", category: "highlighting")
private let log = Logger(subsystem: "com.codevisor", category: "highlighting")

/// Syntax highlighting via Shiki running inside JavaScriptCore. The bundled
/// `highlighter.js` (generated by apps/macos/scripts/build-highlighter.mjs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,9 @@ public enum CodevisorAppVariant: Sendable {
)
}

/// One-time rescue for the HerdMan → Codevisor rename: the app updates in
/// place (the bundle id stayed `com.851labs.HerdMan`) but the Application
/// One-time rescue for the HerdMan → Codevisor rename: the app updated in
/// place (the bundle id stayed `com.851labs.HerdMan` until the Codevisor,
/// LLC identity migration to `com.codevisor.macos`) but the Application
/// Support folder name changed, orphaning every file-backed preference in
/// the old folder. Copies legacy files that don't exist at the new
/// location yet; never overwrites, and leaves the old folder as a backup.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Foundation

/// One-time rescue for the 851labs → Codevisor, LLC identity migration: the
/// shipping bundle id changed (com.851labs.HerdMan → com.codevisor.macos),
/// which moves the UserDefaults domain out from under every existing install.
/// Copies each legacy key the new domain doesn't define yet — window frames,
/// Sparkle's SU* bookkeeping, and any stray preference — then never runs
/// again. File-backed data needs no rescue: Application Support is named
/// "Codevisor", not derived from the bundle id.
public enum LegacyDefaultsMigration {
public static let legacyDomain = "com.851labs.HerdMan"
public static let markerKey = "codevisor.legacyDefaultsMigrated"

public static func migrateIfNeeded(defaults: UserDefaults = .standard) {
// Dev builds never shipped under the legacy identity.
guard !CodevisorAppVariant.isDevelopment else { return }
apply(legacy: defaults.persistentDomain(forName: legacyDomain) ?? [:], to: defaults)
}

/// Marker-guarded merge, separated from the domain read so tests can
/// exercise it against a scratch suite. Existing keys always win: a value
/// the renamed app already wrote is newer than anything in the legacy
/// domain.
static func apply(legacy: [String: Any], to defaults: UserDefaults) {
guard !defaults.bool(forKey: markerKey) else { return }
for (key, value) in legacy where defaults.object(forKey: key) == nil {
defaults.set(value, forKey: key)
}
defaults.set(true, forKey: markerKey)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

/// Centralized handles into the unified logging system (Console.app,
/// `log stream --predicate 'subsystem == "com.851labs.codevisor"'`).
/// `log stream --predicate 'subsystem == "com.codevisor"'`).
///
/// Every error the app absorbs — even ones that are fine to swallow from the
/// user's point of view — must leave a trace here so failures are diagnosable
Expand All @@ -12,7 +12,7 @@ import os
/// redaction turns release-build diagnostics into `<private>`, which defeats
/// the purpose. Never log message bodies, tokens, or file contents.
public enum Log {
public static let subsystem = "com.851labs.codevisor"
public static let subsystem = "com.codevisor"

public static let persistence = Logger(subsystem: subsystem, category: "persistence")
public static let server = Logger(subsystem: subsystem, category: "server")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation
import Testing

@testable import CodevisorClient

struct LegacyDefaultsMigrationTests {
private func scratchDefaults() -> UserDefaults {
let suite = "codevisor.tests.legacy-defaults.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
return defaults
}

@Test func copiesLegacyKeysAndSetsMarker() {
let defaults = scratchDefaults()
LegacyDefaultsMigration.apply(
legacy: ["SUEnableAutomaticChecks": true, "someKey": "legacy"],
to: defaults
)
#expect(defaults.bool(forKey: "SUEnableAutomaticChecks") == true)
#expect(defaults.string(forKey: "someKey") == "legacy")
#expect(defaults.bool(forKey: LegacyDefaultsMigration.markerKey) == true)
}

@Test func existingKeysWin() {
let defaults = scratchDefaults()
defaults.set("current", forKey: "someKey")
LegacyDefaultsMigration.apply(legacy: ["someKey": "legacy"], to: defaults)
#expect(defaults.string(forKey: "someKey") == "current")
}

@Test func runsOnlyOnce() {
let defaults = scratchDefaults()
LegacyDefaultsMigration.apply(legacy: [:], to: defaults)
LegacyDefaultsMigration.apply(legacy: ["late": "value"], to: defaults)
#expect(defaults.object(forKey: "late") == nil)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public struct LegacyServerJobRetirer: Sendable {
"com.851labs.codevisor-recovery",
"com.851labs.herdman-recovery",
"com.851labs.HerdMan-recovery",
// Pre-team-migration SMAppService agent (851labs identity). Booted
// out here so the renamed com.codevisor.macos.ServerAgent can claim
// the port; its BTM registration is unregistered separately by
// MacServerAgentController using the legacy plist.
"com.851labs.Codevisor.ServerAgent",
]

private let runner: any CommandRunner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import os
/// CodevisorCore, so it carries its own `Logger` under the app's shared
/// subsystem. Interpolated error strings use `privacy: .public` so
/// release-build diagnostics stay readable; never log file contents.
let themingLog = Logger(subsystem: "com.851labs.codevisor", category: "theming")
let themingLog = Logger(subsystem: "com.codevisor", category: "theming")
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import os
/// Package-private logging handle: StreamMarkdown must not depend on
/// CodevisorCore, so it carries its own `Logger` under the app's shared
/// subsystem. `.debug` only — this file is on the per-render hot path.
private let log = Logger(subsystem: "com.851labs.codevisor", category: "markdown")
private let log = Logger(subsystem: "com.codevisor", category: "markdown")

/// Renders inline markdown spans (emphasis, code, links) to `AttributedString`.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import Foundation
/// the TextKit layout manager paints behind the selectable glyphs.
enum InlineCodeChipAttribute: AttributedStringKey {
typealias Value = Bool
static let name = "com.851labs.codevisor.inlineCodeChip"
static let name = "com.codevisor.inlineCodeChip"
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@

extension NSAttributedString.Key {
static let streamMarkdownRoundedBackground = NSAttributedString.Key(
"com.851labs.codevisor.streamMarkdownRoundedBackground"
"com.codevisor.streamMarkdownRoundedBackground"
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@

extension NSAttributedString.Key {
static let streamMarkdownRoundedBackground = NSAttributedString.Key(
"com.851labs.codevisor.streamMarkdownRoundedBackground"
"com.codevisor.streamMarkdownRoundedBackground"
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ final class StreamingTextFadeMetadata: NSObject, NSCopying {

extension NSAttributedString.Key {
static let streamMarkdownFade = NSAttributedString.Key(
"com.851labs.codevisor.streamMarkdownFade"
"com.codevisor.streamMarkdownFade"
)
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/dev-ios.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const developmentIconColor = colorFromHash(worktreeHash)
const appDisplayName = `Codevisor (${worktreeName})`
// Per-worktree bundle id so several worktrees' dev builds coexist on one
// simulator/device, mirroring the macOS dev instance identifiers.
const bundleIdentifier = `com.dylanplayer.codevisor.ios.${instanceHash}`
const bundleIdentifier = `com.codevisor.ios.${instanceHash}`
const simulatorName = process.env.CODEVISOR_IOS_SIMULATOR ?? "iPhone 17 Pro"

const preferredPort = 51_000 + (Number.parseInt(instanceHash.slice(0, 8), 16) % 10_000)
Expand Down
2 changes: 1 addition & 1 deletion scripts/dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ try {
derivedDataPath,
`CODEVISOR_DEV_PRODUCT_NAME=${appName}`,
`CODEVISOR_DEV_DISPLAY_NAME=${appName}`,
`CODEVISOR_DEV_BUNDLE_IDENTIFIER=com.851labs.Codevisor.Development.${instanceHash}`,
`CODEVISOR_DEV_BUNDLE_IDENTIFIER=com.codevisor.macos.dev.${instanceHash}`,
"ASSETCATALOG_COMPILER_APPICON_NAME=AppIconDevGenerated",
"INFOPLIST_KEY_CFBundleIconFile=AppIconDevGenerated",
"INFOPLIST_KEY_CFBundleIconName=AppIconDevGenerated",
Expand Down
Loading