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
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
# Allowed: true or false.
# DRIGA_NO_SERVICE_HINT=

# ui.theme: string; default=auto; optional. Palette for the whole terminal UI.
# Allowed: auto, mocha or latte.
# ui.theme: string; default=system; optional. Palette for the whole terminal UI: light, dark, or system to follow the terminal.
# Allowed: system, light or dark.
# DRIGA_UI_THEME=

# adapters.claude.enabled: bool; default=true; optional. Enable Claude session discovery.
Expand Down
4 changes: 2 additions & 2 deletions docs/machine-health.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ for retained history and uncapped evidence lists. MCP additionally retains its
standard envelope `limit`/`truncated` contract. These are observed processes, not an exhaustive
accounting of kernel memory or unreadable tasks.

The TUI Machine tab (`5`, or `driga top --tab machine`) expands an app with
The TUI Machines tab (`2`, or `driga top --tab machines`) expands an app with
Right and collapses it with Left. Enter shows process details, including memory
measures and attribution proofs. The web `/machines` page uses keyboard-accessible
native disclosure controls. Both preserve the existing compact table/pane styles.
Expand Down Expand Up @@ -276,7 +276,7 @@ On macOS, Driga.app sends native UserNotifications with Driga's name and icon.
A local single-item notification click runs `driga inbox jump <id>` through its
delegate while the collector is running. Unbundled builds have no macOS alerts.
The stored destination is `machine`. On macOS, the click opens Terminal and runs
the installed executable's `top --tab machine`; on Linux, a manual jump uses
the installed executable's `top --tab machines`; on Linux, a manual jump uses
`x-terminal-emulator -e`. This avoids requiring a running web server. As with
existing Linux alerts, notify-send itself has no click action. A click does not
acknowledge the item. No notification is sent by tests; their runners/notifiers
Expand Down
28 changes: 17 additions & 11 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ with activity within the last 24 hours, alongside recently ended sessions.
Collection views merge paired machines by default; `machines show` defaults to local. `--machine all|local|<name|id>`
selects a scope; `--project .` resolves against local repositories only.
View flags are scoped to commands that consume them; see the [command reference](#command-reference).
`--theme=auto|mocha|latte` belongs to `top`; `NO_COLOR` is also honored. View JSON has schema 2,
`--theme=system|light|dark` belongs to `top`; `NO_COLOR` is also honored. View JSON has schema 2,
an observation time, collection mode, source freshness, coverage gaps, and data.
`doctor --json` returns diagnostic checks rather than a view envelope.
Use `driga <command> --help` for command-specific flags.
Expand Down Expand Up @@ -120,7 +120,7 @@ background collector posts notifications on macOS, so run the service
Clicking a local item's notification runs `driga inbox jump <item-id>` without
acknowledging it. Jumps select a proven tmux pane, focus a proven Terminal/iTerm
window with `inbox.jump_terminal = true`, or print a resume command. Machine
warnings open Terminal on `driga top --tab machine`. Batch summaries and remote
warnings open Terminal on `driga top --tab machines`. Batch summaries and remote
alerts have no click action. The collector keeps the native notification delegate
and main run loop alive while it runs. Re-sending an item replaces its existing
notification. Linux uses notify-send; Windows uses native WinRT toasts.
Expand Down Expand Up @@ -155,7 +155,7 @@ Defaults and their purpose:

| Setting | Default | Purpose |
| --- | --- | --- |
| `ui.theme` | `auto` | Whole TUI palette: `auto`, `mocha`, `latte`; override with `--theme`. |
| `ui.theme` | `system` | Whole TUI palette: `system`, `light`, `dark`; override with `--theme`. |
| `roots` | none | Discover from sessions; add scanning roots only when wanted. |
| `root_depth` | `3` | Bound discovery below configured roots. |
| `exclude` | `**/node_modules`, `**/.tmp/**` | Avoid dependency and temporary directories. |
Expand Down Expand Up @@ -839,18 +839,24 @@ process ancestry, reparenting, activity, and running app ownership also matter.

### Themes

Set `[ui] theme = 'auto'` (the default), `'mocha'`, or `'latte'` in TOML.
For example:
Themes are **light**, **dark** and **system** (the default), set with
`[ui] theme` in TOML:

```toml
[ui]
theme = 'latte'
theme = 'light'
```

`driga top --theme mocha` overrides the config for the whole TUI. Auto follows
terminal background detection; `--no-color` keeps the layout without ANSI styling.
The web console's Mocha, Latte, and System choices use the same Catppuccin tokens,
including assistant Markdown. Web System follows the browser's color preference.
`driga top --theme dark` overrides the config for the whole TUI. System follows
the terminal's background in the TUI and the browser's colour preference on the
web; `--no-color` keeps the layout without ANSI styling.

Light and dark are the [Catppuccin](https://catppuccin.com/palette/) Latte and
Mocha palettes, taken from the official `github.com/catppuccin/go` package and
shared by the TUI, the web console and assistant Markdown. Dark uses the
official colours unchanged. In light, text in Latte's yellow, green, blue,
peach, teal and sky is darkened, keeping the hue, just enough to reach 4.5:1
contrast on the background; fills and bars keep the official colours.

### Answer Claude permission prompts

Expand Down Expand Up @@ -912,7 +918,7 @@ contains related sessions; selecting a session never selects the whole family.
| `cleanup list` | Local candidates; `--kind=worktree\|scratch\|history`, `--sizes`, `--why`, `--json`. History is report-only. |
| `cleanup remove <candidate-id>` | Verify and remove one candidate; `--dry-run`, `--sizes`, `--include-ignored`, `--yes/-y`, `--json`. |
| `cleanup remove --all --kind worktree` or `--kind scratch` | Remove ready candidates of the selected kind; report completed/skipped/failed counts. `--include-ignored` requires one explicit ID. |
| `top` | Interactive terminal dashboard; `--tab=home\|agents\|projects\|inbox\|orphans\|machine\|cleanup\|flow\|chat`, `--theme=auto\|mocha\|latte`, `--machine`, `--project`, `--since`, `--snapshot`, `--no-color`. |
| `top` | Interactive terminal dashboard; `--tab=flow\|projects\|machines`, `--theme=system\|light\|dark`, `--machine`, `--project`, `--since`, `--snapshot`, `--no-color`. |
| `web` | Serve the interactive browser dashboard on loopback; `--listen`, `--open`, `--machine`, `--project`, `--since`, `--snapshot`. |
| `ask <question>...` | One-shot Chat question; `--machine`, `--project`, `--json`. |
| `mcp` | Serve read-only MCP tools over stdio; `--snapshot` forces one-shot collection for read tools without history. |
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/alecthomas/kong v1.16.1
github.com/aymanbagabas/go-udiff v0.4.1
github.com/bmatcuk/doublestar/v4 v4.10.3
github.com/catppuccin/go v0.3.0
github.com/charmbracelet/x/ansi v0.11.9
github.com/charmbracelet/x/term v0.2.2
github.com/ebitengine/purego v0.11.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/bmatcuk/doublestar/v4 v4.10.3 h1:1UIv1+fqH3++ViVkpnaPTB+Pi1UHycnPFAq14zgK+gI=
github.com/bmatcuk/doublestar/v4 v4.10.3/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 h1:hISLzUbwcvJbGoNR6A3L/TpguRRnACBmZOee8LcHLJw=
Expand Down
4 changes: 2 additions & 2 deletions internal/attention/jump.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ func JumpCommands(j *model.InboxJump, opts JumpOptions) ([][]string, error) {
}
if goos == "darwin" {
const script = "on run argv\ntell application \"Terminal\"\nactivate\ndo script (item 1 of argv)\nend tell\nend run"
return [][]string{{"osascript", "-e", script, Quote(bin) + " top --tab machine"}}, nil
return [][]string{{"osascript", "-e", script, Quote(bin) + " top --tab machines"}}, nil
}
if goos == "linux" {
return [][]string{{"x-terminal-emulator", "-e", bin, "top", "--tab", "machine"}}, nil
return [][]string{{"x-terminal-emulator", "-e", bin, "top", "--tab", "machines"}}, nil
}
return nil, fmt.Errorf("machine jump unsupported on %s", goos)
case "tmux":
Expand Down
4 changes: 2 additions & 2 deletions internal/attention/machine_health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ func TestMachineAlertRepeatAndBatch(t *testing.T) {
func TestMachineJumpPlansSafeTerminal(t *testing.T) {
bin := "/opt/my app's/driga"
cmds, err := JumpCommands(&model.InboxJump{Kind: "machine"}, JumpOptions{OS: "darwin", DrigaPath: bin})
if err != nil || len(cmds) != 1 || cmds[0][0] != "osascript" || cmds[0][3] != Quote(bin)+" top --tab machine" {
if err != nil || len(cmds) != 1 || cmds[0][0] != "osascript" || cmds[0][3] != Quote(bin)+" top --tab machines" {
t.Fatalf("%q %v", cmds, err)
}
cmds, err = JumpCommands(&model.InboxJump{Kind: "machine"}, JumpOptions{OS: "linux", DrigaPath: bin})
if err != nil || !reflect.DeepEqual(cmds, [][]string{{"x-terminal-emulator", "-e", bin, "top", "--tab", "machine"}}) {
if err != nil || !reflect.DeepEqual(cmds, [][]string{{"x-terminal-emulator", "-e", bin, "top", "--tab", "machines"}}) {
t.Fatalf("%q %v", cmds, err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/command_help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestHelpOnlyAdvertisesSupportedJSON(t *testing.T) {
if code != 0 || strings.Contains(out.String(), "--json") != tc.json {
t.Fatalf("%v: %d %s %s", tc.args, code, &out, &diagnostic)
}
if tc.args[0] == "top" && !strings.Contains(strings.Join(strings.Fields(out.String()), " "), "Initial tab: home, agents, projects, inbox, orphans, machine, cleanup, flow, or chat.") {
if tc.args[0] == "top" && !strings.Contains(strings.Join(strings.Fields(out.String()), " "), "Initial tab: flow, projects or machines.") {
t.Fatal(out.String())
}
}
Expand Down
7 changes: 6 additions & 1 deletion internal/cli/command_tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ func TestCommandTree(t *testing.T) {
}
})
}
for _, tab := range []string{"home", "agents", "projects", "inbox", "orphans", "machine", "cleanup", "flow", "chat"} {
for _, tab := range []string{"flow", "projects", "machines"} {
if _, _, err := parseCommand(t, "top", "--tab", tab); err != nil {
t.Fatal(tab, err)
}
}
for _, tab := range []string{"home", "agents", "inbox", "machine", "chat"} {
if _, _, err := parseCommand(t, "top", "--tab", tab); err == nil {
t.Fatal("removed tab accepted:", tab)
}
}
}
func TestRemovedSpellingsAndUnsupportedFlags(t *testing.T) {
paths := []string{"kill item", "agent 12", "where app", "summarize app", "jump item", "serve", "machine show", "machine reset-id", "fleet add host", "fleet remove peer", "fleet revoke viewer", "fleet peers", "fleet viewers", "fleet peers peer", "fleet viewers viewer", "fleet enroll --allow id", "fleet enroll --ack", "fleet peers add host --id id", "hooks install --agent claude", "hooks status --yes", "hooks status --dry-run", "hooks status --traces", "service start -f", "agents kill 12 --approved", "agents jump 12 --machine remote", "machines show --machine local", "projects summarize app --project other", "cleanup item", "cleanup remove item --why", "cleanup list --yes", "version --machine local", "doctor --snapshot", "mcp --json", "web --json", "agents --theme mocha", "agents show 12 --since 1h", "flow --watch 2s --theme mocha", "projects show app --project other", "projects show app --since 1h", "inbox ack item --watch 2s"}
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/jump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func TestMachineNotificationClickUsesInstalledExecutable(t *testing.T) {
readEnv(t)
t.Setenv("PATH", t.TempDir())
st := readDB(t, func(tx *store.Tx) error {
return tx.UpsertInboxItem(model.InboxItem{ID: "low_disk:1", Kind: "low_disk", TS: time.Now(), Jump: &model.InboxJump{Kind: "machine", Command: "driga top --tab machine"}})
return tx.UpsertInboxItem(model.InboxItem{ID: "low_disk:1", Kind: "low_disk", TS: time.Now(), Jump: &model.InboxJump{Kind: "machine", Command: "driga top --tab machines"}})
})
bin, err := os.Executable()
if err != nil {
Expand All @@ -183,7 +183,7 @@ func TestMachineNotificationClickUsesInstalledExecutable(t *testing.T) {
var calls int
err = (JumpCmd{ID: "low_disk:1"}).run(&Globals{Stdout: &bytes.Buffer{}}, jumpRunner(func(_ context.Context, name string, args ...string) ([]byte, error) {
calls++
if name != "osascript" || len(args) != 3 || args[2] != attention.Quote(bin)+" top --tab machine" {
if name != "osascript" || len(args) != 3 || args[2] != attention.Quote(bin)+" top --tab machines" {
t.Fatalf("%s %q", name, args)
}
return nil, nil
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ type TopOptions struct {
Since string `help:"Only show items since a duration ago or yesterday."`
Snapshot bool `help:"Force one-shot snapshot collection without history."`
NoColor bool `name:"no-color" help:"Disable colors."`
Theme *string `enum:"auto,mocha,latte" help:"TUI theme (overrides ui.theme)."`
Theme *string `enum:"system,light,dark" help:"TUI theme: system, light or dark (overrides ui.theme)."`
}

func (o *TopOptions) AfterApply(g *Globals) error {
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/top.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// TopCmd opens the interactive viewer using daemon data or live snapshots.
type TopCmd struct {
TopOptions
Tab string `enum:"home,agents,projects,inbox,orphans,machine,cleanup,flow,chat" default:"home" help:"Initial tab: home, agents, projects, inbox, orphans, machine, cleanup, flow, or chat."`
Tab string `enum:"flow,projects,machines" default:"flow" help:"Initial tab: flow, projects or machines."`
}

// Run keeps the viewer and its optional collector within the same lifetime.
Expand Down
2 changes: 1 addition & 1 deletion internal/collector/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func lowDiskInbox(tx *store.Tx, cfg config.MachineConfig, s model.MachineSample,
for _, w := range worktrees[:min(3, len(worktrees))] {
names = append(names, w.Path)
}
summary := fmt.Sprintf("%s free, below %s. Inspect known cleanup candidates with driga cleanup list --sizes; driga top --tab machine shows the largest apps.", attention.DecimalBytesFloor(s.DiskFree), attention.DecimalBytes(threshold))
summary := fmt.Sprintf("%s free, below %s. Inspect known cleanup candidates with driga cleanup list --sizes; driga top --tab machines shows the largest apps.", attention.DecimalBytesFloor(s.DiskFree), attention.DecimalBytes(threshold))
if len(names) > 0 {
summary += " Oldest stored worktrees to inspect: " + strings.Join(names, ", ")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/collector/machine_health.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func machineJump() *model.InboxJump {
return &model.InboxJump{Kind: "machine", Command: "driga top --tab machine"}
return &model.InboxJump{Kind: "machine", Command: "driga top --tab machines"}
}

// Only successful, distinct OS readings count. Unknown readings and source
Expand Down
4 changes: 2 additions & 2 deletions internal/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var settings = []Setting{
{"capture_text", "CaptureText", "string", "titles", "Local text capture policy.", "titles or snippets"},
{"open_cmd", "OpenCmd", "string", "", "Editor command; {cwd} is substituted.", "empty = auto-detect"},
{"no_service_hint", "NoServiceHint", "bool", false, "Suppress optional service hints.", "true or false"},
{"ui.theme", "UI.Theme", "string", "auto", "Palette for the whole terminal UI.", "auto, mocha or latte"},
{"ui.theme", "UI.Theme", "string", "system", "Palette for the whole terminal UI: light, dark, or system to follow the terminal.", "system, light or dark"},
{"adapters.claude.enabled", "", "bool", true, "Enable Claude session discovery.", "true or false"},
{"adapters.codex.enabled", "", "bool", true, "Enable Codex session discovery.", "true or false"},
{"adapters.gemini.enabled", "", "bool", true, "Enable Gemini session discovery.", "true or false"},
Expand Down Expand Up @@ -174,7 +174,7 @@ func (s Setting) assign(c *Config, raw any) error {
}
switch s.Key {
case "ui.theme":
if text != "auto" && text != "mocha" && text != "latte" {
if text != "system" && text != "light" && text != "dark" {
return fmt.Errorf("allowed values: %s", s.Range)
}
case "motion":
Expand Down
16 changes: 8 additions & 8 deletions internal/config/theme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ import (
)

func TestUITheme(t *testing.T) {
if Default().UI.Theme != "auto" {
if Default().UI.Theme != "system" {
t.Fatal("wrong default")
}
path := filepath.Join(t.TempDir(), "config.toml")
for _, value := range []string{"'auto'", "'mocha'", "'latte'", "'dark'", "''", "true", "1"} {
for _, value := range []string{"'system'", "'light'", "'dark'", "'auto'", "'mocha'", "''", "true", "1"} {
if err := os.WriteFile(path, []byte("[ui]\ntheme = "+value), 0600); err != nil {
t.Fatal(err)
}
c, err := Load(path)
valid := value == "'auto'" || value == "'mocha'" || value == "'latte'"
valid := value == "'system'" || value == "'light'" || value == "'dark'"
if (err == nil) != valid {
t.Fatalf("%s => %+v %v", value, c, err)
}
}
if err := os.WriteFile(path, []byte("[ui]\ntheme = 'latte'"), 0600); err != nil {
if err := os.WriteFile(path, []byte("[ui]\ntheme = 'light'"), 0600); err != nil {
t.Fatal(err)
}
t.Setenv("DRIGA_UI_THEME", "mocha")
t.Setenv("DRIGA_UI_THEME", "dark")
c, err := Load(path)
if err != nil || c.UI.Theme != "mocha" {
if err != nil || c.UI.Theme != "dark" {
t.Fatal(c, err)
}
c, err = LoadWithOverrides(path, map[string]any{"ui.theme": "auto"})
if err != nil || c.UI.Theme != "auto" {
c, err = LoadWithOverrides(path, map[string]any{"ui.theme": "system"})
if err != nil || c.UI.Theme != "system" {
t.Fatal(c, err)
}
if _, err = LoadWithOverrides(path, map[string]any{"ui.theme": "bad"}); err == nil {
Expand Down
13 changes: 5 additions & 8 deletions internal/tui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,11 @@ func New(src Source, opts Options) tea.Model {
input.Placeholder = "filter"
input.SetStyles(textinput.Styles{})
m := &app{chatInput: newChatInput(), chatSpinner: spinner.New(spinner.WithSpinner(spinner.MiniDot)), chatEphemeral: opts.ChatEphemeral, tab: flowTab, machineScope: opts.Machine, src: src, opts: opts, width: 80, height: 24, expanded: map[string]bool{}, filter: input, table: table.New(table.WithFocused(true)), viewport: viewport.New()}
for i, name := range []string{"agents", "projects", "inbox", "orphans", "machine", "cleanup", "home", "flow", "chat"} {
if opts.InitialTab == name {
m.tab = i
break
}
}
if m.tab == 6 {
m.tab = flowTab
switch opts.InitialTab {
case "projects":
m.tab = 1
case "machines":
m.tab = 4
}
m.rebuild()
return m
Expand Down
4 changes: 2 additions & 2 deletions internal/tui/board_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ func TestBoardGoldenFrames(t *testing.T) {
}

func TestBoardThemesAndMotionKeepGeometry(t *testing.T) {
defer func() { _ = view.SetTheme("auto") }()
defer func() { _ = view.SetTheme("system") }()
for _, width := range []int{80, 100, 140, 200} {
m := boardApp(t, "busy")
m.width = width
plain := m.flowView()
for _, theme := range []string{"latte", "mocha"} {
for _, theme := range []string{"light", "dark"} {
if err := view.SetTheme(theme); err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/tui/chat_markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestChatMarkdownElements(t *testing.T) {
t.Fatal(err)
}
input = bytes.ReplaceAll(input, []byte("\r\n"), []byte("\n"))
defer view.SetTheme("auto")
defer view.SetTheme("system")
for _, width := range []int{120, 60, 24} {
t.Run(fmt.Sprint(width), func(t *testing.T) {
plain := chatMarkdown(string(input), width, true)
Expand All @@ -97,7 +97,7 @@ func TestChatMarkdownElements(t *testing.T) {
t.Fatalf("Markdown layout differs from %s:\n%s", path, plain)
}
var mocha string
for _, theme := range []string{"mocha", "latte"} {
for _, theme := range []string{"dark", "light"} {
_ = view.SetTheme(theme)
colored := chatMarkdown(string(input), width, false)
if ansi.Strip(colored) != plain {
Expand All @@ -108,7 +108,7 @@ func TestChatMarkdownElements(t *testing.T) {
t.Fatalf("overflow: %q", line)
}
}
if theme == "mocha" {
if theme == "dark" {
mocha = colored
} else if colored == mocha {
t.Fatal("theme colors did not switch")
Expand Down
Loading
Loading