Open script service URLs in-app or external

Adds a Service URLs setting (ask / in-Paseo / external browser) that
controls where URLs from running scripts open on desktop. First click
prompts with a "don't ask again" checkbox; in-Paseo opens a workspace
browser tab. Closing a browser tab now clears its session partition.
This commit is contained in:
Mohamed Boudra
2026-05-03 15:14:28 +07:00
parent 02adadbbc8
commit f58cfe9008
12 changed files with 257 additions and 18 deletions
@@ -39,6 +39,7 @@ import {
useSettings,
type AppSettings,
type SendBehavior,
type ServiceUrlBehavior,
type Settings as EffectiveSettings,
} from "@/hooks/use-settings";
import { THEME_SWATCHES } from "@/styles/theme";
@@ -190,14 +191,24 @@ const RELEASE_CHANNEL_OPTIONS = [
{ value: "beta" as const, label: "Beta" },
];
const SERVICE_URL_BEHAVIOR_LABELS: Record<ServiceUrlBehavior, string> = {
ask: "Ask",
"in-app": "In Paseo",
external: "External browser",
};
const SERVICE_URL_BEHAVIOR_VALUES: ServiceUrlBehavior[] = ["ask", "in-app", "external"];
// ---------------------------------------------------------------------------
// Section components
// ---------------------------------------------------------------------------
interface GeneralSectionProps {
settings: AppSettings;
isDesktopApp: boolean;
handleThemeChange: (theme: AppSettings["theme"]) => void;
handleSendBehaviorChange: (behavior: SendBehavior) => void;
handleServiceUrlBehaviorChange: (behavior: ServiceUrlBehavior) => void;
}
interface ThemeMenuItemProps {
@@ -229,10 +240,33 @@ function ThemeMenuItem({
);
}
interface ServiceUrlBehaviorMenuItemProps {
value: ServiceUrlBehavior;
selected: boolean;
onChange: (value: ServiceUrlBehavior) => void;
}
function ServiceUrlBehaviorMenuItem({
value,
selected,
onChange,
}: ServiceUrlBehaviorMenuItemProps) {
const handleSelect = useCallback(() => {
onChange(value);
}, [onChange, value]);
return (
<DropdownMenuItem selected={selected} onSelect={handleSelect}>
{SERVICE_URL_BEHAVIOR_LABELS[value]}
</DropdownMenuItem>
);
}
function GeneralSection({
settings,
isDesktopApp,
handleThemeChange,
handleSendBehaviorChange,
handleServiceUrlBehaviorChange,
}: GeneralSectionProps) {
const { theme } = useUnistyles();
const iconSize = theme.iconSize.md;
@@ -290,6 +324,32 @@ function GeneralSection({
options={SEND_BEHAVIOR_OPTIONS}
/>
</View>
{isDesktopApp ? (
<View style={ROW_WITH_BORDER_STYLE}>
<View style={settingsStyles.rowContent}>
<Text style={settingsStyles.rowTitle}>Service URLs</Text>
<Text style={settingsStyles.rowHint}>Where to open URLs from running scripts</Text>
</View>
<DropdownMenu>
<DropdownMenuTrigger style={themeTriggerStyle}>
<Text style={styles.themeTriggerText}>
{SERVICE_URL_BEHAVIOR_LABELS[settings.serviceUrlBehavior]}
</Text>
<ChevronDown size={theme.iconSize.sm} color={iconColor} />
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" width={200}>
{SERVICE_URL_BEHAVIOR_VALUES.map((value) => (
<ServiceUrlBehaviorMenuItem
key={value}
value={value}
selected={settings.serviceUrlBehavior === value}
onChange={handleServiceUrlBehaviorChange}
/>
))}
</DropdownMenuContent>
</DropdownMenu>
</View>
) : null}
</View>
</SettingsSection>
);
@@ -768,6 +828,13 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
[updateSettings],
);
const handleServiceUrlBehaviorChange = useCallback(
(behavior: ServiceUrlBehavior) => {
void updateSettings({ serviceUrlBehavior: behavior });
},
[updateSettings],
);
const handlePlaybackTest = useCallback(async () => {
if (!voiceAudioEngine || isPlaybackTestRunning) {
return;
@@ -949,8 +1016,10 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
return (
<GeneralSection
settings={settings}
isDesktopApp={isDesktopApp}
handleThemeChange={handleThemeChange}
handleSendBehaviorChange={handleSendBehaviorChange}
handleServiceUrlBehaviorChange={handleServiceUrlBehaviorChange}
/>
);
case "shortcuts":