feat: redesign remote tunnel settings and named tunnel workflow (#546)

* feat: add Cloudflare Tunnel settings for desktop app

Add a 'Remote Tunnel' section in Settings (desktop-only) that lets users
start/stop a Cloudflare quick tunnel on demand, with auto-generated
password protection and a QR code for easy mobile access.

- Server: 4 new API endpoints (check/status/start/stop) reusing the
  existing cloudflare-tunnel module
- UI: TunnelSettings component with full state machine
  (checking → idle/not-available → starting → active → stopping)
- QR code rendered via the qrcode package for in-app display
- Hidden from VS Code extension (desktop/web only)

* fix: use ?token= instead of ?p= in tunnel password URLs

REST API endpoints were building passwordUrl with ?p=<token> but
SessionAuthGate reads the ?token= query param, causing QR code
auto-login to fail — the password was never extracted from the URL.

Standardize all three tunnel URL construction sites to use ?token=
so scanning the QR code correctly pre-fills and submits the password.

* feat: secure remote tunnel access with one-time connect links

* feat: redesign remote tunnel settings and access flow

* fix: cleaned up unused desktop close code path

* feat: overhaul named tunnel setup and persistence flow

* chore: align codemirror language dependency resolution

---------

Co-authored-by: Brian-Hwang <brian.hwang@cornelisnetworks.com>
This commit is contained in:
Iuliia Ivashko
2026-02-28 04:21:46 +02:00
committed by GitHub
co-authored by Brian-Hwang
parent a505378d79
commit d5d0d35083
15 changed files with 2853 additions and 150 deletions
@@ -20,7 +20,7 @@ import {
RiCloudLine,
RiFoldersLine,
RiGitBranchLine,
RiGlobalLine,
RiMicLine,
RiNotification3Line,
RiPaletteLine,
@@ -97,6 +97,7 @@ const pageOrder: SettingsPageSlug[] = [
'skills.installed',
'skills.catalog',
'voice',
'tunnel',
];
function buildRuntimeContext(isDesktop: boolean): SettingsRuntimeContext {
@@ -150,6 +151,8 @@ function getSettingsNavIcon(slug: SettingsPageSlug): React.ComponentType<{ class
return RiBarChart2Line;
case 'voice':
return RiMicLine;
case 'tunnel':
return RiGlobalLine;
case 'home':
return null;
default:
@@ -367,6 +370,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
sessions: 'sessions',
notifications: 'notifications',
voice: 'voice',
tunnel: 'tunnel',
}), []);
const renderUnavailable = React.useCallback(() => {
@@ -437,7 +441,8 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
case 'shortcuts':
case 'sessions':
case 'notifications':
case 'voice': {
case 'voice':
case 'tunnel': {
const section = openChamberSectionBySlug[slug] ?? 'visual';
return <OpenChamberPage section={section} />;
}
@@ -511,7 +516,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
)}
>
<span className="typography-ui-label font-normal truncate">{page.title}</span>
{page.slug === 'voice' && (
{(page.slug === 'voice' || page.slug === 'tunnel') && (
<span className="shrink-0 typography-micro px-1 rounded leading-none pb-px text-[var(--status-warning)] bg-[var(--status-warning)]/10">
beta
</span>