Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
1e8cb84
Vault: add History section — unified timeline with group-by-anything
austinywang Jul 29, 2026
bdf0a43
Fix warning-budget drift inherited from main in AgentSessionRetryCoor…
austinywang Jul 29, 2026
ae2d08f
Address review: serialize history appends before publishing revision;…
austinywang Jul 29, 2026
5553029
Address review: single sort per refresh; drop redundant init
austinywang Jul 29, 2026
ad4c59d
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Jul 29, 2026
fa8c666
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Aug 9, 2026
a3520c9
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Aug 9, 2026
adf9478
Extract Vault History domain and harden lifecycle recording
austinywang Aug 9, 2026
b70ed9e
Flush Vault History before app termination
austinywang Aug 9, 2026
18b3ca8
Defer termination only for pending History writes
austinywang Aug 9, 2026
3a87737
Merge origin/main and complete Vault History audit
austinywang Sep 4, 2026
8192fb9
Merge latest origin/main
austinywang Sep 4, 2026
170519a
Fix inherited Freestyle SDK test drift
austinywang Sep 4, 2026
5c92ff9
Address current-main CI and Vault History review
austinywang Sep 4, 2026
5d73550
Repair current-main macOS CI drift
austinywang Sep 4, 2026
259406d
Clear inherited Swift warning drift
austinywang Sep 4, 2026
d926667
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 4, 2026
cb4ef35
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 4, 2026
314254e
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 4, 2026
3eb0c55
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 4, 2026
503a9d9
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 4, 2026
f84f565
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 4, 2026
2534130
Merge origin/main into issue-9127-vault-history
austinywang Sep 4, 2026
30155a0
test: cover narrow Vault History row layout
austinywang Sep 4, 2026
fa4ecaf
fix: restore compact Vault styling in History
austinywang Sep 4, 2026
b696e06
fix: merge main and record window workspace closures
austinywang Sep 6, 2026
0dbd3f8
Merge origin/main into issue-9127-vault-history
austinywang Sep 6, 2026
778affc
test: align cloud state coverage with current catalog API
austinywang Sep 6, 2026
8094a9d
Merge origin/main into issue-9127-vault-history
austinywang Sep 6, 2026
d9b9b8d
fix: return available sidebar modes
austinywang Sep 6, 2026
3c89765
fix: call cloud policy error from instance context
austinywang Sep 6, 2026
b5f37df
fix: silence inherited browser payload warning
austinywang Sep 6, 2026
47a27ff
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 6, 2026
c5eea69
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 6, 2026
dbc0b81
Merge branch 'main' into issue-9127-vault-history
austinywang Sep 6, 2026
4bb3a92
Merge branch 'main' into issue-9127-vault-history
austinywang Sep 7, 2026
4c4f1a4
Merge branch 'main' into issue-9127-vault-history
austinywang Sep 8, 2026
a115213
fix: compile local tmux helpers in the CLI regression target
austinywang Sep 8, 2026
c2ff759
test: expose temporary new-window workspaces in Vault History
austinywang Sep 8, 2026
8062307
fix: exclude temporary new-window workspaces from Vault History
austinywang Sep 8, 2026
c0e45ba
test: cover suppressed temporary-window History cleanup
austinywang Sep 8, 2026
d772acc
fix: honor History suppression when finalizing window workspaces
austinywang Sep 8, 2026
8f23af2
fix: align Bonsplit with the current main integration
austinywang Sep 8, 2026
7235c7a
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
a5c309e
test: measure the 1000-session History projection
austinywang Sep 8, 2026
f81b849
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
084659d
refactor: inject configured window action dependencies
austinywang Sep 8, 2026
fd75bb0
test: expose uncommitted window and Cloud VM History events
austinywang Sep 8, 2026
d41d75c
fix: commit only retained window and configured workspace History
austinywang Sep 8, 2026
e13a115
fix: isolate configured window dependencies to the main actor
austinywang Sep 8, 2026
f34b28d
test: exercise Vault tabs grouping and standalone pane in the UI
austinywang Sep 8, 2026
421e53f
test: use the built Vault accessibility labels for UI interaction
austinywang Sep 8, 2026
9239406
refactor: inject the startup restore readiness boundary
austinywang Sep 8, 2026
f561736
test: cover History while startup restore waits and year-crossing weeks
austinywang Sep 8, 2026
ca16052
fix: enable History when the first window is registered
austinywang Sep 8, 2026
9c41ffe
test: select Vault tabs without pinning their preference
austinywang Sep 8, 2026
4aa8ae5
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
0f80176
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
c97b482
test: query Vault grouping by its macOS accessibility title
austinywang Sep 8, 2026
fe4ab61
test: expose discarded reopen events and retained History reload loss
austinywang Sep 8, 2026
f5b3d2c
fix: commit usable reopened windows and reload the full retained History
austinywang Sep 8, 2026
c4eb017
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
ac5c520
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
34278fd
Merge main and adopt its canonical CLI test-bundle repair
austinywang Sep 8, 2026
07308b4
Merge origin/main and preserve Vault History wiring
austinywang Sep 8, 2026
b6ca712
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
c8d1e14
fix: reject unreadable History stores before rewriting
austinywang Sep 8, 2026
b78763d
fix: persist session snapshot on normal quit
austinywang Sep 8, 2026
a36233d
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
062be7c
fix: pass explicit tint to Vault symbols
austinywang Sep 8, 2026
d1a2d01
fix: clear new Swift warning budget entries
austinywang Sep 8, 2026
e084fb1
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
0d620e5
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
2b41a57
chore: normalize Xcode project ordering
austinywang Sep 8, 2026
4ee9c7e
test: target Vault history picker by accessibility id
austinywang Sep 8, 2026
1cf854c
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
6146857
fix: preserve pending Vault history action outcomes
austinywang Sep 8, 2026
a42f58f
fix: discard startup restore history transaction
austinywang Sep 8, 2026
b5a5cfa
fix: keep deferred startup history interactive
austinywang Sep 8, 2026
85c0423
fix: retry startup restore after window close
austinywang Sep 8, 2026
1fe7f54
test: cover deferred restore ownership and action completion
austinywang Sep 8, 2026
68d4351
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 8, 2026
cc6e70d
fix: own deferred restore and finalize History on completion
austinywang Sep 8, 2026
d76996f
test: cover completion of suppressed configured splits
austinywang Sep 8, 2026
966b1c4
test: require a stable accessible History grouping control
austinywang Sep 9, 2026
c9ff2a4
fix: complete suppressed actions and stabilize verification contracts
austinywang Sep 9, 2026
164f02c
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
2abf952
fix: restore History translations lost during main integration
austinywang Sep 9, 2026
c6ecd0c
fix: retain all reviewed History locales
austinywang Sep 9, 2026
753d7c2
test: require stable Vault control identifiers in both mounts
austinywang Sep 9, 2026
cb75df3
fix: preserve Vault accessibility boundaries and isolate blocking tes…
austinywang Sep 9, 2026
4b96dd8
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
885592a
ci: allow opt-in crash symbolication for focused E2E diagnostics
austinywang Sep 9, 2026
3022583
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
71a311c
fix: retain committed history events during launch
austinywang Sep 9, 2026
1286544
test: cover discarding a committed launch-window transaction
austinywang Sep 9, 2026
557ee67
fix: retract discarded launch events and await the relay scheduling s…
austinywang Sep 9, 2026
41c9875
test: reproduce background destruction of the agent chat service
austinywang Sep 9, 2026
3633cc6
fix: transfer agent chat teardown to the main actor
austinywang Sep 9, 2026
28184f3
test: require sidebar text accessibility leaves to stay acyclic
austinywang Sep 9, 2026
bdbd7ea
fix: keep sidebar text accessibility children free of cell aliases
austinywang Sep 9, 2026
517ef00
test: retain ownership of the accessibility fixture window
austinywang Sep 9, 2026
7f823ca
test: query Vault grouping by its cross-version identifier
austinywang Sep 9, 2026
616759f
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
273cf17
test: match visible Vault menus and row-owned theme semantics
austinywang Sep 9, 2026
62d408a
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
dd84412
ci: provision Node before app-host hook tests
austinywang Sep 9, 2026
99174ef
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
cb12081
ci: forward installed runtimes into XCTest app hosts
austinywang Sep 9, 2026
e514a4c
test: reproduce losing committed launch History on quit
austinywang Sep 9, 2026
1c254dd
fix: drain accepted History on quit and bound hook test socket paths
austinywang Sep 9, 2026
0b44f24
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
1198ba6
ci: share the selected-test execution guard with Depot
austinywang Sep 9, 2026
1364eaa
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
52ab6f3
test: preserve OpenCode execution after the Bun runtime merge
austinywang Sep 9, 2026
5ccec50
ci: move the pinned Bun version with its setup step
austinywang Sep 9, 2026
d5e60bc
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
e8fda62
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
f7fa345
Merge remote-tracking branch 'origin/main' into issue-9127-vault-history
austinywang Sep 9, 2026
df065ab
test: reproduce History commits crossing lifecycle phases
austinywang Sep 9, 2026
66a0ff3
fix: preserve accepted History across lifecycle phase changes
austinywang Sep 9, 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
62 changes: 30 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,35 @@ jobs:
-only-testing:cmuxTests/GhosttyTerminalViewVisibilityPolicyTests \
test

- name: Ensure node for hook and wrapper regressions
id: detect-node
run: |
set -euo pipefail
# App-host hook tests and the CLI wrapper regressions both execute
# Node. Prepare it before unit tests on every shard; runner images
# are not required to ship a Node runtime.
for candidate in "$(command -v node || true)" /opt/homebrew/bin/node /usr/local/bin/node; do
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
dirname "$candidate" >> "$GITHUB_PATH"
"$candidate" --version
echo "found=true" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "no node runtime in the runner image; installing via setup-node"
echo "found=false" >> "$GITHUB_OUTPUT"

- name: Install node (runner image has none)
if: ${{ steps.detect-node.outputs.found == 'false' }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"

- name: Set up Bun for app-host and Pi extension regressions
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
Comment thread
cursor[bot] marked this conversation as resolved.
with:
bun-version: "1.3.6"

- name: Run unit tests
run: |
set -euo pipefail
Expand Down Expand Up @@ -1361,38 +1390,6 @@ jobs:
CMUX_SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" \
./tests/test_bundled_ghostty_theme_picker_helper.sh

- name: Ensure node for wrapper regressions
id: detect-node
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
run: |
set -euo pipefail
# The claude wrapper regressions below exec `node` from fake claude
# binaries. The tart-macos-15 runner image shipped without node on
# the runner PATH (2026-07-10), which failed every branch. Reuse an
# image-provided node when present; otherwise install one below.
for candidate in "$(command -v node || true)" /opt/homebrew/bin/node /usr/local/bin/node; do
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
dirname "$candidate" >> "$GITHUB_PATH"
"$candidate" --version
echo "found=true" >> "$GITHUB_OUTPUT"
exit 0
fi
done
echo "no node runtime in the runner image; installing via setup-node"
echo "found=false" >> "$GITHUB_OUTPUT"

- name: Install node (runner image has none)
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) && steps.detect-node.outputs.found == 'false' }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"

- name: Set up Bun for Pi extension dispatch regression
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.6"

- name: Install pinned nushell (shell-integration tests)
if: ${{ matrix.shard == fromJSON(env.CMUX_APP_HOST_FOCUSED_REGRESSION_SHARD) }}
run: |
Expand Down Expand Up @@ -1766,6 +1763,7 @@ jobs:
CmuxTerminal
CmuxTerminalCore
CmuxUpdater
CmuxVaultHistory
CMUXAgentLaunch
CmuxAgentJournal
CmuxFilePreviewCore
Expand Down
24 changes: 16 additions & 8 deletions .github/workflows/test-depot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2

- name: Set up Node for hook integration tests
if: ${{ !inputs.skip_unit_tests }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"

- name: Download pre-built GhosttyKit.xcframework
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -144,6 +150,7 @@ jobs:
set -euo pipefail
SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages"
run_unit_suite() {
TEST_RUNNER_PATH="${TEST_RUNNER_PATH-$PATH:__CURRENT_VALUE__}" \
scripts/ci/xcodebuild_noninteractive.py \
xcodebuild -project cmux.xcodeproj -scheme cmux-unit -configuration Debug \
-clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \
Expand All @@ -165,14 +172,15 @@ jobs:
suite_output=$(run_unit_suite "-only-testing:cmuxTests/$suite") || suite_status=$?
printf '%s\n' "$suite_output"
if [ "$suite_status" -ne 0 ]; then return "$suite_status"; fi
# A pipeline here makes `set -o pipefail` treat grep's normal
# early exit as a printf SIGPIPE when the xcodebuild log is
# large. Feed grep with a here-string so a passing suite cannot
# be reported as empty because of that logging artifact.
if ! grep -Eq 'Test run with [1-9][0-9]* tests|Executed [1-9][0-9]* tests' <<<"$suite_output"; then
echo "No tests executed for $suite" >&2
return 1
fi
# Share the E2E lane's execution guard, including singular
# Swift Testing summaries and the app-activation failure check.
local suite_log execution_status=0
suite_log=$(mktemp "${RUNNER_TEMP:-/tmp}/cmux-unit-suite.XXXXXX")
printf '%s\n' "$suite_output" > "$suite_log"
scripts/ci/require_selected_test_execution.sh "$suite_log" "cmuxTests/$suite" \
|| execution_status=$?
rm -f -- "$suite_log"
if [ "$execution_status" -ne 0 ]; then return "$execution_status"; fi
done
}

Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ on:
required: false
default: true
type: boolean
symbolicate_crashes:
description: Resolve Swift crash symbols for a focused diagnostic run
required: false
default: false
type: boolean
runner:
description: "Runner OS (auto follows MACOS_RUNNER_15; tart-* choices use isolated VMs)"
required: false
Expand Down Expand Up @@ -170,6 +175,12 @@ jobs:
echo "Resolved target: $test_target"
echo "Resolved selector: $test_selector"

- name: Set up Node for hook integration tests
if: ${{ steps.filter.outputs.target == 'cmuxTests' }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"

- name: Select Xcode
run: |
set -euo pipefail
Expand Down Expand Up @@ -341,7 +352,7 @@ jobs:
RECORD_VIDEO: ${{ steps.filter.outputs.record_video }}
# App-host crash recovery must not stall on interactive backtraces
# (mirrors ci.yml's tests job hardening).
SWIFT_BACKTRACE: "interactive=no,timeout=0s,symbolicate=off,color=no"
SWIFT_BACKTRACE: ${{ inputs.symbolicate_crashes && 'interactive=no,timeout=0s,symbolicate=full,color=no' || 'interactive=no,timeout=0s,symbolicate=off,color=no' }}
CMUX_XCODEBUILD_NONINTERACTIVE_POST_TEST_TIMEOUT_SECONDS: "45"
run: |
set -euo pipefail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ struct CmxIrohRelayCredentialCoordinatorTests {
let endpoint = TestIrohEndpoint(identity: fixture.identity)
let supervisor = try await fixture.activeSupervisor(endpoint: endpoint)
let clock = TestRelayClock(now: fixture.now)
var clockEvents = clock.events().makeAsyncIterator()
let persistence = TestRelayCredentialPersistenceGate()
let coordinator = CmxIrohRelayCredentialCoordinator(
supervisor: supervisor,
Expand All @@ -154,7 +155,7 @@ struct CmxIrohRelayCredentialCoordinatorTests {
)
}
await persistence.waitUntilStarted()
for _ in 0 ..< 20 { await Task.yield() }
#expect(await clockEvents.next() != nil, "Expected refresh scheduling while persistence is blocked")

#expect(clock.observedSleepDeadlines() == [fixture.refreshAfter])
#expect(await endpoint.observedRelayUpdates().count == 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ actor MobileTerminalLaneCoordinator {
self.inputOnlyProvider = inputOnlyProvider
}

/// Output lanes must never fall back to the input-only transport.
func resolvedProvider(for mode: LaneMode) -> MobileTerminalLaneProvider? {
switch mode {
case .output:
return provider
case .inputOnly:
return inputOnlyProvider ?? provider
}
}

func ensure(_ configuration: Configuration) async {
guard let key = LaneKey(configuration: configuration) else { return }
focusedKeyBySurfaceID[configuration.surfaceID] = key
Expand Down Expand Up @@ -232,14 +242,7 @@ actor MobileTerminalLaneCoordinator {
? nil
: await configuration.cursor()
do {
let laneProvider: MobileTerminalLaneProvider?
switch configuration.mode {
case .output:
laneProvider = provider
case .inputOnly:
laneProvider = inputOnlyProvider ?? provider
}
guard let laneProvider else {
guard let laneProvider = resolvedProvider(for: configuration.mode) else {
await markFailed(key: key, id: id)
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,32 @@ struct MobileTerminalLaneCoordinatorTests {
#expect(await coordinator.isOutputReady(surfaceID: Self.surfaceID) == false)
}

@Test
func missingOutputProviderDoesNotSelectInputOnlyTransport() async throws {
let inputProvider = TerminalLaneTestProvider(lanes: [
TerminalLaneTestConnection(
frames: [Self.frame(kind: .replay, sequence: 0, bytes: "")],
waitsAfterFrames: true
),
])
let coordinator = MobileTerminalLaneCoordinator(
provider: nil,
inputOnlyProvider: { request, surfaceID, cursor in
try await inputProvider.callAsFunction(request, surfaceID, cursor: cursor)
}
)

// Keep the absent-provider boundary covered separately from the
// failing-provider integration test above, without waiting for a non-call.
#expect(await coordinator.resolvedProvider(for: .output) == nil)
#expect(await inputProvider.requestCount() == 0)

let selectedInputProvider = try #require(await coordinator.resolvedProvider(for: .inputOnly))
let lane = try await selectedInputProvider(try Self.request(), Self.surfaceID, nil)
#expect(await inputProvider.requestCount() == 1)
await lane.close()
}

@Test
func sequenceGapSuspendsUntilAuthoritativeCursorThenReopens() async throws {
let firstLane = TerminalLaneTestConnection(
Expand Down
35 changes: 35 additions & 0 deletions Packages/macOS/CmuxVaultHistory/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "CmuxVaultHistory",
platforms: [
.macOS(.v14),
],
products: [
.library(
name: "CmuxVaultHistory",
targets: ["CmuxVaultHistory"]
),
],
targets: [
.target(
name: "CmuxVaultHistory",
swiftSettings: [
.swiftLanguageMode(.v6),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault"),
]
),
.testTarget(
name: "CmuxVaultHistoryTests",
dependencies: ["CmuxVaultHistory"],
swiftSettings: [
.swiftLanguageMode(.v6),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault"),
]
),
]
)
25 changes: 25 additions & 0 deletions Packages/macOS/CmuxVaultHistory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# CmuxVaultHistory

`CmuxVaultHistory` owns the locale-independent values, bounded JSONL
persistence, and deterministic grouping used by the macOS History timeline.
The app target owns lifecycle coordination and localized presentation.

Construct a repository with an explicit file URL. Passing `nil` gives tests an
in-memory repository with the same retention behavior:

```swift
let store = VaultHistoryEventStore(fileURL: nil)
let accepted = await store.append(event)
let events = await store.recentEvents()
```

Grouping is a pure operation. Inject `now` at the call site and inspect group
identities without loading AppKit or SwiftUI:

```swift
let groups = VaultHistoryGrouper(calendar: calendar).groups(
events: events,
by: .date,
now: now
)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
public import Foundation

/// Browser-history-style date buckets ordered from newest to oldest.
public enum VaultHistoryDateBucket: Int, CaseIterable, Identifiable, Sendable {
/// Events in the rolling 24-hour window or on the current calendar day.
case last24Hours
/// Events on the previous calendar day that are outside the rolling window.
case yesterday
/// Older events in the current calendar week.
case thisWeek
/// Older events in the current calendar month.
case thisMonth
/// Events preceding the current calendar month.
case older

/// Stable ordinal identity used by grouped views.
public var id: Int { rawValue }

/// Classifies a timestamp relative to an injected clock and calendar.
///
/// - Parameters:
/// - date: Event timestamp to classify.
/// - now: Reference time used for the rolling 24-hour window.
/// - calendar: Calendar used for day, week, and month boundaries.
/// - Returns: The single date bucket containing `date`.
public static func bucket(
for date: Date,
now: Date,
calendar: Calendar
) -> VaultHistoryDateBucket {
if date >= now.addingTimeInterval(-24 * 60 * 60)
|| calendar.isDate(date, inSameDayAs: now) {
return .last24Hours
}
if isYesterday(date, now: now, calendar: calendar) {
return .yesterday
}
if calendar.isDate(date, equalTo: now, toGranularity: .weekOfYear) {
return .thisWeek
Comment thread
austinywang marked this conversation as resolved.
}
if calendar.isDate(date, equalTo: now, toGranularity: .month) {
return .thisMonth
}
return .older
}

private static func isYesterday(_ date: Date, now: Date, calendar: Calendar) -> Bool {
guard let yesterday = calendar.date(byAdding: .day, value: -1, to: now) else {
return false
}
return calendar.isDate(date, inSameDayAs: yesterday)
}
}
Loading
Loading