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
1 change: 1 addition & 0 deletions backend/browser_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ async def launch(self, profile: dict[str, Any]) -> RunningProfile:
human_preset=profile.get("human_preset", "default"),
geoip=bool(profile.get("geoip", False)),
color_scheme=profile.get("color_scheme") or None,
search_engine=profile.get("search_engine") or None,
user_agent=profile.get("user_agent") or None,
viewport={
"width": profile.get("screen_width", 1920),
Expand Down
14 changes: 11 additions & 3 deletions backend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def init_db():
clipboard_sync BOOLEAN DEFAULT 1,
auto_launch BOOLEAN DEFAULT 0,
color_scheme TEXT,
search_engine TEXT,
notes TEXT,
user_data_dir TEXT NOT NULL,
created_at TEXT NOT NULL,
Expand All @@ -78,6 +79,12 @@ def init_db():
if "auto_launch" not in cols:
conn.execute("ALTER TABLE profiles ADD COLUMN auto_launch BOOLEAN DEFAULT 0")
conn.commit()
if "color_scheme" not in cols:
conn.execute("ALTER TABLE profiles ADD COLUMN color_scheme TEXT")
conn.commit()
if "search_engine" not in cols:
conn.execute("ALTER TABLE profiles ADD COLUMN search_engine TEXT")
conn.commit()


def _now() -> str:
Expand All @@ -101,9 +108,9 @@ def create_profile(
id, name, fingerprint_seed, proxy, timezone, locale, platform,
user_agent, screen_width, screen_height, gpu_vendor, gpu_renderer,
hardware_concurrency, humanize, human_preset, headless, geoip,
clipboard_sync, auto_launch, color_scheme, launch_args, notes,
clipboard_sync, auto_launch, color_scheme, search_engine, launch_args, notes,
user_data_dir, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
profile_id, name, seed,
fields.get("proxy"),
Expand All @@ -123,6 +130,7 @@ def create_profile(
fields.get("clipboard_sync", True),
fields.get("auto_launch", False),
fields.get("color_scheme"),
fields.get("search_engine"),
json.dumps(fields.get("launch_args") or []),
fields.get("notes"),
user_data_dir, now, now,
Expand Down Expand Up @@ -187,7 +195,7 @@ def update_profile(profile_id: str, **fields: Any) -> dict[str, Any] | None:
"name", "fingerprint_seed", "proxy", "timezone", "locale", "platform",
"user_agent", "screen_width", "screen_height", "gpu_vendor", "gpu_renderer",
"hardware_concurrency", "humanize", "human_preset", "headless", "geoip",
"clipboard_sync", "auto_launch", "color_scheme", "launch_args", "notes",
"clipboard_sync", "auto_launch", "color_scheme", "search_engine", "launch_args", "notes",
):
if col in fields:
update_cols.append(f"{col} = ?")
Expand Down
3 changes: 3 additions & 0 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ProfileCreate(BaseModel):
clipboard_sync: bool = True
auto_launch: bool = False
color_scheme: Literal["light", "dark", "no-preference"] | None = None
search_engine: Literal["google", "bing", "duckduckgo"] | None = None
launch_args: list[str] = Field(default_factory=list)
notes: str | None = None
tags: list[TagCreate] | None = None
Expand All @@ -52,6 +53,7 @@ class ProfileUpdate(BaseModel):
clipboard_sync: bool | None = None
auto_launch: bool | None = None
color_scheme: Literal["light", "dark", "no-preference"] | None = Field(default=None)
search_engine: Literal["google", "bing", "duckduckgo"] | None = Field(default=None)
launch_args: list[str] | None = None
notes: str | None = Field(default=None)
tags: list[TagCreate] | None = None
Expand Down Expand Up @@ -94,6 +96,7 @@ def coerce_clipboard_sync(cls, v: object) -> bool:
return v if v is not None else True

color_scheme: str | None = None
search_engine: str | None = None
launch_args: list[str] = []
notes: str | None = None
user_data_dir: str
Expand Down
3 changes: 2 additions & 1 deletion backend/tests/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_create_profile_all_fields(tmp_db: Path):
headless=True,
geoip=True,
color_scheme="dark",
search_engine="google",
notes="test note",
)
assert p["proxy"] == "http://host:8080"
Expand All @@ -80,7 +81,7 @@ def test_create_profile_all_fields(tmp_db: Path):
assert p["humanize"] == 1 # SQLite stores bool as int
assert p["human_preset"] == "careful"
assert p["color_scheme"] == "dark"

assert p["search_engine"] == "google"

def test_create_profile_with_tags(tmp_db: Path):
p = db.create_profile(
Expand Down
6 changes: 6 additions & 0 deletions backend/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ def test_profile_create_all_fields():
headless=True,
geoip=True,
color_scheme="dark",
search_engine="google",
notes="test note",
tags=[TagCreate(tag="work", color="#ff0000")],
)
assert p.platform == "macos"
assert p.human_preset == "careful"
assert p.color_scheme == "dark"
assert p.search_engine == "google"
assert len(p.tags) == 1


Expand Down Expand Up @@ -90,6 +92,10 @@ def test_profile_create_invalid_color_scheme():
with pytest.raises(ValidationError):
ProfileCreate(name="Bad", color_scheme="auto")

def test_profile_create_invalid_search_engine():
with pytest.raises(ValidationError):
ProfileCreate(name="Bad", search_engine="yahoo")


# ── ProfileUpdate ────────────────────────────────────────────────────────────

Expand Down
14 changes: 14 additions & 0 deletions frontend/src/components/ProfileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function ProfileForm({ profile, onSave, onDelete, onCancel }: ProfileForm
clipboard_sync: profile.clipboard_sync,
auto_launch: profile.auto_launch,
color_scheme: profile.color_scheme,
search_engine: profile.search_engine,
launch_args: profile.launch_args ?? [],
notes: profile.notes,
tags: profile.tags ?? [],
Expand Down Expand Up @@ -467,6 +468,19 @@ export function ProfileForm({ profile, onSave, onDelete, onCancel }: ProfileForm
<option value="no-preference">No preference</option>
</select>
</div>
<div>
<label className="label">Default Search Engine</label>
<select
className="input"
value={form.search_engine ?? ""}
onChange={(e) => set("search_engine", e.target.value || null)}
>
<option value="">System default</option>
<option value="google">Google</option>
<option value="bing">Bing</option>
<option value="duckduckgo">DuckDuckGo</option>
</select>
</div>
<div>
<label className="label">User Agent</label>
<input
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/useProfiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const fakeProfile = {
geoip: false,
clipboard_sync: true,
color_scheme: null,
search_engine: null,
notes: null,
user_data_dir: "/data/profiles/abc-123",
created_at: "2026-01-01T00:00:00Z",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface Profile {
clipboard_sync: boolean;
auto_launch: boolean;
color_scheme: string | null;
search_engine: string | null;
launch_args: string[];
notes: string | null;
user_data_dir: string;
Expand Down Expand Up @@ -54,6 +55,7 @@ export interface ProfileCreateData {
clipboard_sync?: boolean;
auto_launch?: boolean;
color_scheme?: string | null;
search_engine?: string | null;
launch_args?: string[];
notes?: string | null;
tags?: { tag: string; color: string | null }[];
Expand Down