Skip to content
Merged
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
21 changes: 20 additions & 1 deletion Sources/Runway/App/SettingsMigrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct SettingsMigration: Sendable {
enum SettingsSchema {
/// Current schema version. Keep equal to the highest migration `version` below (or the baseline when
/// there are none). This is NOT the app version — bump it only alongside a migration you add.
static let current = 5
static let current = 6

/// The provider IDs that existed when the v2 migration shipped, frozen forever. A migration is a
/// point-in-time transform: any future build with more providers also contains this migration, so a
Expand Down Expand Up @@ -97,6 +97,25 @@ enum SettingsSchema {
)
}
defaults.set(try JSONEncoder().encode(order), forKey: key)
},
// v6 promotes Muse Weekly Usage out of On Demand (it shipped below the caret) and stars
// both Muse meters. Fresh installs already declare that layout; this only rewrites saved
// expanded / pin lists so existing cards match without a reset.
SettingsMigration(version: 6) { defaults in
if var expanded = defaults.stringArray(forKey: "runway.layout.v1.expandedMetrics") {
expanded.removeAll { $0 == "muse.weekly" }
defaults.set(expanded, forKey: "runway.layout.v1.expandedMetrics")
}
if var onEnable = defaults.stringArray(forKey: "runway.layout.v1.expandOnEnable") {
onEnable.removeAll { $0 == "muse.weekly" }
defaults.set(onEnable, forKey: "runway.layout.v1.expandOnEnable")
}
if var pins = defaults.stringArray(forKey: "runway.layout.v1.menuBarPins") {
for id in ["muse.session", "muse.weekly"] where !pins.contains(id) {
pins.append(id)
}
defaults.set(pins, forKey: "runway.layout.v1.menuBarPins")
}
}
]

Expand Down
4 changes: 2 additions & 2 deletions Sources/Runway/Resources/ProviderIcons/muse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions Sources/Runway/Stores/DefaultLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ enum DefaultLayout {
"codex.weekly",
"cursor.auto", "cursor.api",
"copilot.premium",
"muse.session", "muse.weekly",
"openrouter.credits",
"zai.session", "zai.weekly"
]
Expand Down Expand Up @@ -121,8 +122,7 @@ enum DefaultLayout {
// Kimi: Five-Hour Usage stays above the fold; Weekly Usage and the two Extra Usage
// account/billing rows sit below the caret.
"kimi.weekly", "kimi.extraBalance", "kimi.extraMonthly",
// Muse: Five-Hour Usage stays above the fold; Weekly Usage sits below the caret.
"muse.weekly",
// Muse: Five-Hour Usage and Weekly Usage both stay above the fold; there is no On Demand row.
// OpenCode: the three Go caps (Session/Weekly/Monthly) and Usage Trend stay above the fold —
// matching every other provider — with the spend tiles (Today/Yesterday/Last 30 Days) below.
"opencode.today", "opencode.yesterday", "opencode.last30",
Expand Down
6 changes: 3 additions & 3 deletions Tests/RunwayTests/MuseProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,11 @@ final class MuseLayoutTests: XCTestCase {
)

XCTAssertEqual(store.placed.map(\.descriptorID), ["muse.session", "muse.weekly"])
XCTAssertTrue(store.pinnedMetricIDs.isEmpty)
XCTAssertEqual(Set(store.pinnedMetricIDs), ["muse.session", "muse.weekly"])

let group = store.customizeGroups.first { $0.provider.id == "muse" }
XCTAssertEqual(group?.alwaysShownMetrics.map(\.id), ["muse.session"])
XCTAssertEqual(group?.expandedMetrics.map(\.id), ["muse.weekly"])
XCTAssertEqual(group?.alwaysShownMetrics.map(\.id), ["muse.session", "muse.weekly"])
XCTAssertEqual(group?.expandedMetrics.map(\.id) ?? [], [])
}
}

Expand Down
11 changes: 11 additions & 0 deletions Tests/RunwayTests/ProviderMarksTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ import XCTest

@MainActor
final class ProviderMarksTests: XCTestCase {
func testMuseUsesMetaInfinityMark() throws {
let mark = try XCTUnwrap(ProviderMarks.mark(for: "muse"))
XCTAssertTrue(
mark.pathData.hasPrefix("M6.915"),
"Muse must use the Meta infinity-M path, not the old sparkle star"
)
XCTAssertGreaterThan(mark.bounds.width, 20)
XCTAssertGreaterThan(mark.bounds.height, 14)
XCTAssertFalse(mark.path.isEmpty)
}

func testGrokResolvesToVectorMarkNotBoltFallback() {
let mark = ProviderMarks.mark(for: "grok")
XCTAssertNotNil(mark, "Grok must load a real vector mark instead of the bolt.fill fallback")
Expand Down
33 changes: 33 additions & 0 deletions Tests/RunwayTests/SettingsMigratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,39 @@ final class SettingsMigratorTests: XCTestCase {
XCTAssertEqual(migrated["codex"], saved["codex"], "unrelated providers stay untouched")
}

func testV6PromotesMuseWeeklyAndStarsBothMeters() {
let (defaults, domain) = makeDefaults("V6MuseLayout")
defer { defaults.removePersistentDomain(forName: domain) }
defaults.set(5, forKey: SettingsMigrator.schemaVersionKey)
defaults.set(
["muse.weekly", "claude.trend", "cursor.onDemand"],
forKey: "runway.layout.v1.expandedMetrics"
)
defaults.set(
["muse.weekly", "cursor.requests"],
forKey: "runway.layout.v1.expandOnEnable"
)
defaults.set(
["claude.session", "cursor.auto"],
forKey: "runway.layout.v1.menuBarPins"
)

XCTAssertEqual(SettingsMigrator.migrate(defaults: defaults, domainName: domain), SettingsSchema.current)

XCTAssertEqual(
defaults.stringArray(forKey: "runway.layout.v1.expandedMetrics"),
["claude.trend", "cursor.onDemand"]
)
XCTAssertEqual(
defaults.stringArray(forKey: "runway.layout.v1.expandOnEnable"),
["cursor.requests"]
)
XCTAssertEqual(
defaults.stringArray(forKey: "runway.layout.v1.menuBarPins"),
["claude.session", "cursor.auto", "muse.session", "muse.weekly"]
)
}

/// A legacy install with no disabled providers (the all-on default) converts to all-on.
func testV2ConvertsAllOnLegacyInstall() {
let (defaults, domain) = makeDefaults("V2AllOn")
Expand Down
2 changes: 1 addition & 1 deletion docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ A provider's **detail** has a back button and provider-specific Reset control in

Drag-reorder also works directly on the dashboard — drag a row within its provider, drag it across the caret boundary while the card is open, or drag a provider header to reorder sections. On a Force Touch trackpad you'll feel a light tap each time the dragged item snaps into a new slot.

For Claude, the default reset layout keeps Session, Weekly, and Fable always visible. Codex and Grok keep only Weekly always visible, while Sakana Fugu keeps Five-Hour Usage and Weekly Usage always visible. For those four providers, Usage Trend and the Today, Yesterday, and Last 30 Days history rows start on demand; Codex and Grok also put Rate Limit Resets there, above the usage history. Their other metrics start off. Other providers keep their own core meters above the caret and secondary details on demand.
For Claude, the default reset layout keeps Session, Weekly, and Fable always visible. Codex and Grok keep only Weekly always visible, while Sakana Fugu and Muse keep Five-Hour Usage and Weekly Usage always visible. For Claude, Codex, Grok, and Sakana Fugu, Usage Trend and the Today, Yesterday, and Last 30 Days history rows start on demand; Codex and Grok also put Rate Limit Resets there, above the usage history. Their other metrics start off. Other providers keep their own core meters above the caret and secondary details on demand.

Made a change you didn't mean to? Press **⌘Z** to undo — it works anywhere in the popover (the dashboard and Customize alike) and steps back through your recent customization changes one at a time: hiding or showing a metric, reordering metrics or whole providers, starring or unstarring, and moving a metric across the divider all undo. Each step restores the exact previous arrangement. Undo is per-session (it starts fresh after a relaunch), and resetting clears it.

Expand Down
2 changes: 1 addition & 1 deletion docs/providers/muse.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Tracks Muse Code subscription quota using the Meta account login from the offici
| Five-Hour Usage | Usage percentage in the rolling five-hour prompt window |
| Weekly Usage | Usage percentage in the weekly quota window |

When Muse reports a subscription tier (Everyday Usage, High Usage, or Power Usage), Runway shows it beside the provider name. A pay-as-you-go `META_API_KEY` is not a subscription login and is not used.
When Muse reports a subscription tier (Everyday Usage, High Usage, or Power Usage), Runway shows it beside the provider name. A pay-as-you-go `META_API_KEY` is not a subscription login and is not used. Both meters start always visible, with both starred in the menu bar.

## Where credentials come from

Expand Down