Skip to content
Closed
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
93e2a8b
Test stale agent cleanup preserves sidebar activity
lawrencecchen Jul 15, 2026
18d22a6
Preserve active sidebar agent state during stale cleanup
lawrencecchen Jul 15, 2026
f65f3e5
Test sidebar state precedes slow turn snapshots
lawrencecchen Jul 16, 2026
596352b
Publish agent activity before turn snapshots
lawrencecchen Jul 16, 2026
3b6b1a2
Test agent trajectory last-turn diffs
lawrencecchen Jul 16, 2026
e119286
Resolve last-turn diffs from agent trajectories
lawrencecchen Jul 16, 2026
2ead6f5
Test agent trajectory diff viewer sessions
lawrencecchen Jul 16, 2026
993bf22
Stream agent trajectory sources in diff viewer
lawrencecchen Jul 16, 2026
6755ca6
Test persistent diff sidecar RPC sessions
lawrencecchen Jul 16, 2026
259ea62
Keep the Rust diff sidecar process alive
lawrencecchen Jul 16, 2026
6fb14b9
test: require immediate diff viewer loading surface
lawrencecchen Jul 16, 2026
5417bf8
perf: open diff viewer before renderer setup
lawrencecchen Jul 16, 2026
2b2e617
test: isolate pending diff opens by viewer page
lawrencecchen Jul 16, 2026
8e01128
fix: isolate diff opens across page replacement
lawrencecchen Jul 16, 2026
d151b28
test: require serialized diff scheme cancellation
lawrencecchen Jul 16, 2026
5ee667f
fix: serialize diff scheme callbacks on main actor
lawrencecchen Jul 16, 2026
e6e642b
test: reject reused diff scheme task identities
lawrencecchen Jul 16, 2026
f220970
fix: version diff scheme task registrations
lawrencecchen Jul 16, 2026
5ffc69e
test: require long-lived sidecar reply draining
lawrencecchen Jul 16, 2026
4a50da1
fix: drain sidecar replies without blocking Swift executor
lawrencecchen Jul 16, 2026
db57b05
test: require in-place diff viewer navigation
lawrencecchen Jul 16, 2026
6b2082e
fix: present diff viewer loading surface immediately
lawrencecchen Jul 17, 2026
87e72c8
test: keep one diff skeleton until renderer ready
lawrencecchen Jul 17, 2026
4790fe1
test: require a single skeleton loading surface
lawrencecchen Jul 17, 2026
0a5fca9
fix: keep one skeleton through diff readiness
lawrencecchen Jul 17, 2026
ef90154
test: unwrap diff viewer web view readiness hook
lawrencecchen Jul 17, 2026
6ebe989
test: isolate diff viewer loading lifecycle coverage
lawrencecchen Jul 17, 2026
7389897
test: cover trajectory review regressions
lawrencecchen Jul 17, 2026
427e27b
fix: close trajectory review gaps
lawrencecchen Jul 17, 2026
d8a2f5f
test: cover review resource bounds
lawrencecchen Jul 17, 2026
8a8a01e
fix: bound diff viewer resource work
lawrencecchen Jul 17, 2026
a7c6e4b
test: keep OpenCode coverage in CLI target
lawrencecchen Jul 17, 2026
07650a6
test: cover final review boundaries
lawrencecchen Jul 17, 2026
6a30c23
fix: coalesce trajectory resource work
lawrencecchen Jul 17, 2026
0d30159
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 17, 2026
c5f7b5b
fix: preserve single opening skeleton after merge
lawrencecchen Jul 17, 2026
c1046e3
test: cover final merge regressions
lawrencecchen Jul 17, 2026
9705fac
fix: decouple trajectory and loading placement
lawrencecchen Jul 17, 2026
0728229
fix: type optional trajectory repository context
lawrencecchen Jul 17, 2026
30f2a72
fix: open agent trajectory without caller repository
lawrencecchen Jul 17, 2026
4fadc8d
test: keep agent identity without cwd
lawrencecchen Jul 17, 2026
2bb9f09
fix: preserve agent source without cwd
lawrencecchen Jul 17, 2026
5faa775
test: cover explicit non-git trajectory cwd
lawrencecchen Jul 17, 2026
63c3274
fix: bypass git lookup for trajectory cwd
lawrencecchen Jul 17, 2026
ed0d1e4
test: cover final lifecycle review regressions
lawrencecchen Jul 17, 2026
af01689
fix: close diff viewer lifecycle gaps
lawrencecchen Jul 17, 2026
c56c925
test: require pre-browser diff skeleton
lawrencecchen Jul 17, 2026
3e2fa0f
fix: present diff skeleton before browser setup
lawrencecchen Jul 17, 2026
e7fffa7
fix: initialize agent identity provenance
lawrencecchen Jul 17, 2026
8b1831e
test: cover final diff viewer review regressions
lawrencecchen Jul 17, 2026
c392ffc
fix: close final diff viewer review gaps
lawrencecchen Jul 17, 2026
2e538e5
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 17, 2026
e3d687e
test: cover inherited Codex patch turn identity
lawrencecchen Jul 17, 2026
6cc5bd8
fix: inherit active Codex turn for patches
lawrencecchen Jul 17, 2026
e3c5114
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 18, 2026
db07e87
test: preserve rendered diff viewer errors
lawrencecchen Jul 18, 2026
cb62179
fix: preserve rendered diff viewer errors
lawrencecchen Jul 18, 2026
cc8d0e0
test: preserve diff open without immediate target
lawrencecchen Jul 18, 2026
e021f03
test: preserve sessions on lease lock errors
lawrencecchen Jul 18, 2026
d46c730
fix: close final diff viewer review gaps
lawrencecchen Jul 18, 2026
d340622
test: cover final diff trajectory regressions
lawrencecchen Jul 18, 2026
c738da0
fix: close final trajectory review findings
lawrencecchen Jul 18, 2026
b9a0c66
test: reject cross-repository agent trajectories
lawrencecchen Jul 18, 2026
efea5fa
fix: authorize resolved trajectory repositories
lawrencecchen Jul 18, 2026
626bec3
test: authorize non-git trajectory roots
lawrencecchen Jul 18, 2026
c237bd3
fix: authorize non-git trajectory roots
lawrencecchen Jul 18, 2026
e37e0b8
test: retire diff skeleton after navigation
lawrencecchen Jul 18, 2026
8bca964
fix: retire diff skeleton after navigation
lawrencecchen Jul 18, 2026
20302d5
test: cover final loading lifecycle races
lawrencecchen Jul 18, 2026
c8dff4d
fix: close final loading lifecycle races
lawrencecchen Jul 18, 2026
0e7e2cc
test: require async sidecar exit signaling
lawrencecchen Jul 18, 2026
ad9cf31
fix: await sidecar process exit asynchronously
lawrencecchen Jul 18, 2026
42a9e2c
fix: route IndexNow through Linux runner variable
lawrencecchen Jul 18, 2026
ada2643
fix: exempt transient diff loading views from window lint
lawrencecchen Jul 18, 2026
6765f67
test: assert synchronous diff loading attachment
lawrencecchen Jul 18, 2026
c5d881a
test: cover trajectory and scheme lifecycle review gaps
lawrencecchen Jul 18, 2026
021f212
fix: preserve trajectory and scheme lifecycle invariants
lawrencecchen Jul 18, 2026
57c7493
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 18, 2026
4573396
test: keep connection timing checks deterministic
lawrencecchen Jul 18, 2026
d69e0c2
test: cover final trajectory and asset cache gaps
lawrencecchen Jul 18, 2026
3a62633
fix: close final trajectory and asset cache gaps
lawrencecchen Jul 18, 2026
03e08ee
test: cover trajectory trust boundary gaps
lawrencecchen Jul 18, 2026
1be7e68
fix: enforce trajectory trust boundaries
lawrencecchen Jul 18, 2026
77aed64
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 18, 2026
d56ed31
fix: refresh merged webview assets
lawrencecchen Jul 18, 2026
fa975a7
test: cover final review regressions
lawrencecchen Jul 18, 2026
4823924
fix: close final review regressions
lawrencecchen Jul 18, 2026
3617021
test: align final regressions with policy
lawrencecchen Jul 18, 2026
c6847f5
test: cover final resource bounds
lawrencecchen Jul 18, 2026
c3af0fa
fix: bound final sidecar resources
lawrencecchen Jul 18, 2026
a823b39
test: cover long-lived sidecar bounds
lawrencecchen Jul 18, 2026
3083210
fix: bound long-lived sidecar work
lawrencecchen Jul 18, 2026
aaef48e
test: cover final migration lifecycle gaps
lawrencecchen Jul 18, 2026
8147f62
fix: complete sidecar lifecycle migration
lawrencecchen Jul 18, 2026
0b554bb
test: cover disabled-browser diff preload
lawrencecchen Jul 18, 2026
02000c0
fix: close diff opening review gaps
lawrencecchen Jul 18, 2026
9232594
test: cover trajectory freshness and legacy coexistence
lawrencecchen Jul 18, 2026
a3b0872
fix: preserve trajectory freshness across builds
lawrencecchen Jul 18, 2026
74945f9
test: scope diff opening page ownership
lawrencecchen Jul 18, 2026
1214fe9
fix: bind diff loading ownership to operation
lawrencecchen Jul 18, 2026
d664cf9
test: authorize last-turn capability by identity
lawrencecchen Jul 18, 2026
d88677d
fix: authorize last-turn sessions by identity
lawrencecchen Jul 18, 2026
d454167
test: bound diff agent snapshot refresh
lawrencecchen Jul 18, 2026
13cc266
fix: bound diff agent snapshot refresh
lawrencecchen Jul 18, 2026
949955e
test: cover codex patch payload authorization
lawrencecchen Jul 18, 2026
53fa196
fix: parse codex patch payloads securely
lawrencecchen Jul 18, 2026
0a7534d
test: reject incomplete codex patch fallbacks
lawrencecchen Jul 18, 2026
541e5be
fix: reject incomplete codex patch fallbacks
lawrencecchen Jul 18, 2026
cbf662a
test: cover diff identity safety boundaries
lawrencecchen Jul 18, 2026
8a55064
fix: fail closed at diff identity boundaries
lawrencecchen Jul 18, 2026
955b591
test: cover diff navigation writer cleanup
lawrencecchen Jul 18, 2026
e5b122e
fix: close diff viewer lifecycle leaks
lawrencecchen Jul 18, 2026
0aaa9b5
test: cover fast diff viewer navigation ownership
lawrencecchen Jul 18, 2026
9ad8230
fix: preserve owned diff navigation handoff
lawrencecchen Jul 18, 2026
d24560c
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 18, 2026
e12946f
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 18, 2026
276685f
test: cover final diff sidecar review failures
lawrencecchen Jul 18, 2026
f4299b1
fix: harden diff sidecar lifecycle and trajectory scope
lawrencecchen Jul 18, 2026
d955dc5
fix: preserve process hook defaults without token overrides
lawrencecchen Jul 18, 2026
205ed06
test: cover final diff lifecycle races
lawrencecchen Jul 18, 2026
c896379
fix: complete initial diff rendering lifecycle
lawrencecchen Jul 18, 2026
7722c40
fix: configure diff workers before mounting viewer
lawrencecchen Jul 18, 2026
ef5be07
test: cover first diff renderer mount
lawrencecchen Jul 18, 2026
43c821d
fix: preserve initial diff render and agent source
lawrencecchen Jul 18, 2026
30d0dcc
fix: close final sidecar lifecycle feedback
lawrencecchen Jul 18, 2026
8b3d6de
fix: make diff renderer readiness explicit
lawrencecchen Jul 18, 2026
cd78bd9
fix: bound diff viewer loading gates
lawrencecchen Jul 18, 2026
45dd0ab
fix: fail closed on agent identity timeout
lawrencecchen Jul 18, 2026
d761331
test: stub diff workers in jsdom
lawrencecchen Jul 18, 2026
d921040
test: isolate async assertion values
lawrencecchen Jul 18, 2026
8363723
fix: trust bounded agent trajectory patches
lawrencecchen Jul 18, 2026
b08b285
build: declare trajectory sidecar input
lawrencecchen Jul 18, 2026
8f16444
perf: key opencode cache by message update
lawrencecchen Jul 18, 2026
351774a
fix: reject ambiguous claude transcripts
lawrencecchen Jul 18, 2026
b9cf2e9
fix: share diff asset pruning policy
lawrencecchen Jul 25, 2026
b42a935
Merge remote-tracking branch 'origin/main' into feat-trajectory-last-…
lawrencecchen Jul 25, 2026
94ebaf7
test: cover mixed-scope codex fallback
lawrencecchen Jul 25, 2026
b6b5c3f
fix: retain in-repo codex fallback sections
lawrencecchen Jul 25, 2026
ddf120c
fix: link shared diff asset pruning in CLI
lawrencecchen Jul 25, 2026
ce2d94c
test: cover blocked diff sidecar writes
lawrencecchen Jul 25, 2026
670d27a
fix: bound diff sidecar pipe writes
lawrencecchen Jul 25, 2026
2600f24
fix: resolve final merge gate failures
lawrencecchen Jul 25, 2026
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
11 changes: 10 additions & 1 deletion CLI/CMUXCLI+DiffSidecar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,16 @@ extension CMUXCLI {
guard let baseRef = context.branchBaseRef, !baseRef.isEmpty else { return nil }
return ["kind": "branch", "repoRoot": repoRoot, "baseRef": baseRef]
case .lastTurn:
return nil
guard let providerInput = normalizedDiffSourceValue(context.agentProvider),
let sessionId = normalizedDiffSourceValue(context.sessionId) else {
return nil
}
let provider = providerInput.lowercased() == "opencode" ? "openCode" : providerInput
return [
"kind": "agentTurn",
"provider": provider,
"sessionId": sessionId,
]
}
}

Expand Down
46 changes: 46 additions & 0 deletions CLI/CMUXCLI+DiffViewerBundledAssets.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,53 @@
import Foundation

struct DiffViewerBundledAssetManifest: Decodable {
struct File: Decodable {
var logicalPath: String
var storedPath: String
}

var version: Int
var contentKey: String
var files: [File]
}

extension CMUXCLI {
static let diffViewerBundledAssetManifestName = ".cmux-asset-manifest.json"

func diffViewerBundledAssetManifest(in sourceDirectory: URL) -> DiffViewerBundledAssetManifest? {
let url = sourceDirectory.appendingPathComponent(
Self.diffViewerBundledAssetManifestName,
isDirectory: false
)
guard let data = try? Data(contentsOf: url),
let manifest = try? JSONDecoder().decode(DiffViewerBundledAssetManifest.self, from: data),
manifest.version == 1,
manifest.contentKey.count == 64,
manifest.contentKey.allSatisfy({ $0.isHexDigit }),
!manifest.files.isEmpty,
Set(manifest.files.map(\.logicalPath)).count == manifest.files.count,
Set(manifest.files.map(\.storedPath)).count == manifest.files.count,
manifest.files.allSatisfy({ file in
diffViewerBundledAssetManifestPathIsSafe(file.logicalPath) &&
diffViewerBundledAssetManifestPathIsSafe(file.storedPath) &&
["js", "mjs"].contains(URL(fileURLWithPath: file.logicalPath).pathExtension) &&
(file.storedPath == file.logicalPath || file.storedPath == file.logicalPath + ".deflate")
}) else {
return nil
}
return manifest
}

private func diffViewerBundledAssetManifestPathIsSafe(_ path: String) -> Bool {
guard !path.isEmpty, !path.hasPrefix("/") else { return false }
let components = path.split(separator: "/", omittingEmptySubsequences: false)
return !components.isEmpty && components.allSatisfy { !$0.isEmpty && $0 != "." && $0 != ".." }
}

func diffViewerBundledAssetRelativePaths(in sourceDirectory: URL) throws -> [String] {
if let manifest = diffViewerBundledAssetManifest(in: sourceDirectory) {
return manifest.files.map(\.logicalPath)
}
let rootURL = sourceDirectory.standardizedFileURL.resolvingSymlinksInPath()
guard let enumerator = FileManager.default.enumerator(
at: rootURL,
Expand Down
168 changes: 63 additions & 105 deletions CLI/CMUXCLI+TypedDiffViewer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ extension CMUXCLI {
target: DiffViewerGitHTMLSetTarget,
extraAllowedPageURL: URL?
) throws -> DiffViewerWriteResult {
let repoRoot = try gitRepoRootForDiff(context)
let repoRoot: String? = if selectedSource == .lastTurn {
nil
} else {
try gitRepoRootForDiff(context)
}
let fileURL = target.directory.appendingPathComponent(
"diff-\(target.groupID)-viewer.html",
isDirectory: false
Expand All @@ -97,11 +101,13 @@ extension CMUXCLI {
shortcuts: diffViewerShortcutPayload(),
generatedAt: ISO8601DateFormatter().string(from: Date())
)
let repoCandidates = gitDiffViewerRepoOptions(selectedRepoRoot: repoRoot, context: context)
let repoCandidates: [DiffViewerRepoOption] = repoRoot.map {
gitDiffViewerRepoOptions(selectedRepoRoot: $0, context: context)
} ?? []
let session = DiffViewerBranchSession(
token: target.mapper.token,
groupID: target.groupID,
repoRoot: repoRoot,
repoRoot: repoRoot ?? "",
allowedRepoRoots: repoCandidates.map(\.repoRoot),
layout: layout,
layoutSource: layoutSource,
Expand All @@ -111,26 +117,32 @@ extension CMUXCLI {
surfaceId: context.surfaceId
)
try writeDiffViewerBranchSession(session, rootDirectory: target.directory)
let lastTurnInput = try? readGitDiffInput(source: .lastTurn, context: context)

func sessionSource(_ source: DiffSource, repo: String) -> [String: Any]? {
func sessionSource(_ source: DiffSource, repo: String?) -> [String: Any]? {
switch source {
case .unstaged:
guard let repo else { return nil }
return ["kind": "unstaged", "repoRoot": repo]
case .staged:
guard let repo else { return nil }
return ["kind": "staged", "repoRoot": repo]
case .branch:
guard let repo else { return nil }
var payload: [String: Any] = ["kind": "branch", "repoRoot": repo]
if repo == repoRoot,
let base = normalizedDiffSourceValue(context.branchBaseRef) {
payload["baseRef"] = base
}
return payload
case .lastTurn:
guard lastTurnInput != nil else { return nil }
guard let providerInput = normalizedDiffSourceValue(context.agentProvider),
let sessionId = normalizedDiffSourceValue(context.sessionId) else {
return nil
}
let provider = providerInput.lowercased() == "opencode" ? "openCode" : providerInput
return [
"kind": "patch",
"path": "/\(diffViewerPatchFileURL(for: fileURL).lastPathComponent)",
"kind": "agentTurn",
"provider": provider,
"sessionId": sessionId,
]
}
}
Expand All @@ -148,7 +160,9 @@ extension CMUXCLI {
)
}
let repoOptions: [DiffViewerSourceOption]
if repoCandidates.count > 1 {
if selectedSource == .lastTurn {
repoOptions = []
} else if let repoRoot, repoCandidates.count > 1 {
repoOptions = repoCandidates.map { option in
DiffViewerSourceOption(
value: option.repoRoot,
Expand All @@ -165,95 +179,41 @@ extension CMUXCLI {
repoOptions = []
}

var responseInput: DiffInput
if selectedSource == .lastTurn {
do {
responseInput = try nonEmptyGitDiffInput(source: selectedSource, context: context)
try writeDiffViewerHTML(
to: fileURL,
patch: responseInput.patch,
title: titleOverride ?? responseInput.defaultTitle,
sourceLabel: responseInput.sourceLabel,
externalURL: responseInput.externalURL,
remotePatchURL: responseInput.remotePatchURL,
layout: layout,
layoutSource: layoutSource,
appearance: appearance,
sourceOptions: sourceOptions,
repoOptions: repoOptions,
repoRoot: repoRoot,
sessionSource: sessionSource(.lastTurn, repo: repoRoot),
capabilityToken: target.mapper.token,
assets: assets,
sharedPayload: sharedPayload,
runtime: target.runtime
)
} catch let error as EmptyDiffSourceError {
responseInput = DiffInput(
patch: "",
sourceLabel: "git \(selectedSource.slug)",
defaultTitle: selectedSource.title,
emptyMessage: error.message,
externalURL: nil
)
try writeDiffViewerStatusHTML(
to: fileURL,
title: titleOverride ?? selectedSource.title,
sourceLabel: responseInput.sourceLabel,
message: error.message,
isError: false,
pollForReplacement: false,
layout: layout,
layoutSource: layoutSource,
appearance: appearance,
sourceOptions: sourceOptions,
repoOptions: repoOptions,
repoRoot: repoRoot,
sessionSource: sessionSource(.lastTurn, repo: repoRoot),
capabilityToken: target.mapper.token,
assets: assets,
sharedPayload: sharedPayload,
runtime: target.runtime
)
}
} else {
let selectedSessionSource = sessionSource(selectedSource, repo: repoRoot)
responseInput = DiffInput(
patch: "",
sourceLabel: "git \(selectedSource.slug)",
defaultTitle: selectedSource.title,
emptyMessage: selectedSource.emptyMessage,
externalURL: nil
)
try writeDiffViewerStatusHTML(
to: fileURL,
title: titleOverride ?? selectedSource.title,
sourceLabel: responseInput.sourceLabel,
message: diffViewerLoadingDiffMessage(selectedSource.menuLabel),
emptyMessage: selectedSource.emptyMessage,
isError: false,
pollForReplacement: true,
layout: layout,
layoutSource: layoutSource,
appearance: appearance,
sourceOptions: sourceOptions,
repoOptions: repoOptions,
repoRoot: repoRoot,
branchBaseRef: context.branchBaseRef,
sessionSource: selectedSessionSource,
capabilityToken: target.mapper.token,
assets: assets,
sharedPayload: sharedPayload,
runtime: target.runtime
)
if let lastTurnInput {
try lastTurnInput.patch.write(
to: diffViewerPatchFileURL(for: fileURL),
atomically: true,
encoding: .utf8
)
}
let selectedSessionSource = sessionSource(selectedSource, repo: repoRoot)
if selectedSource == .lastTurn, selectedSessionSource == nil {
throw CLIError(message: CMUXDiffViewerLocalization.string(
"cli.diff.error.agentSessionRequired",
defaultValue: "cmux diff --last-turn requires --agent and --session."
))
}
let responseInput = DiffInput(
patch: "",
sourceLabel: "git \(selectedSource.slug)",
defaultTitle: selectedSource.title,
emptyMessage: selectedSource.emptyMessage,
externalURL: nil
)
try writeDiffViewerStatusHTML(
to: fileURL,
title: titleOverride ?? selectedSource.title,
sourceLabel: responseInput.sourceLabel,
message: diffViewerLoadingDiffMessage(selectedSource.menuLabel),
emptyMessage: selectedSource.emptyMessage,
isError: false,
pollForReplacement: true,
layout: layout,
layoutSource: layoutSource,
appearance: appearance,
sourceOptions: sourceOptions,
repoOptions: repoOptions,
repoRoot: repoRoot,
branchBaseRef: repoRoot == nil ? nil : context.branchBaseRef,
sessionSource: selectedSessionSource,
capabilityToken: target.mapper.token,
assets: assets,
sharedPayload: sharedPayload,
runtime: target.runtime
)

var pageURLs = [fileURL]
if let extraAllowedPageURL { pageURLs.append(extraAllowedPageURL) }
Expand Down Expand Up @@ -282,11 +242,9 @@ extension CMUXCLI {
func writeDiffViewerOpeningHTML(
to viewerURL: URL,
title: String,
message: String,
appearance: DiffViewerAppearance
) throws {
let escapedTitle = htmlEscaped(title)
let escapedMessage = htmlEscaped(message)
let html = """
<!doctype html>
<html data-cmux-diff-pending="true">
Expand All @@ -297,16 +255,16 @@ extension CMUXCLI {
\(diffViewerPrepaintStyle(appearance: appearance))
<style>
body { margin: 0; color: var(--cmux-diff-fg); font: 13px -apple-system, BlinkMacSystemFont, sans-serif; }
.loading { display: flex; align-items: center; gap: 10px; margin: 20px 16px; opacity: .72; }
.spinner { width: 16px; height: 16px; border: 3px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; }
.skeleton { margin: 38px 20px; display: grid; gap: 20px; opacity: .12; }
.skeleton i { display: block; height: 14px; border-radius: 6px; background: currentColor; }
.skeleton i:nth-child(2n) { width: 72%; }
@keyframes spin { to { transform: rotate(360deg); } }
.skeleton i:nth-child(2) { width: 72%; }
.skeleton i:nth-child(3) { width: 88%; }
.skeleton i:nth-child(4) { width: 64%; }
.skeleton i:nth-child(5) { width: 94%; }
.skeleton i:nth-child(6) { width: 76%; }
</style>
</head>
<body>
<div class="loading"><span class="spinner"></span><span>\(escapedMessage)</span></div>
<div class="skeleton"><i></i><i></i><i></i><i></i><i></i><i></i></div>
</body>
</html>
Expand Down
Loading