From 08a6c4e7ff46bb42199b2d59aa1c331744832b51 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 15 Jun 2026 13:39:21 +0200 Subject: [PATCH 1/8] refac --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c66d989..7ec7528 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,14 @@ cptr run --host 0.0.0.0 Open `http://:8000` on your phone. -Not on the same network? Use a tunnel: +Not on the same network? Use a tunnel to reach your machine remotely: - **[Tailscale](https://tailscale.com)** creates a private mesh network between your devices. Recommended. - **[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)** gives you a permanent URL through Cloudflare's edge. - **[ngrok](https://ngrok.com)** gives you a public URL in one command. +Most tunnels forward to `localhost`, so the default `cptr run` works. If your tunnel connects to a specific interface, bind accordingly with `--host`. + Or skip networking entirely and connect a [messaging bot](#messaging-bots) instead. ## What you get From ffbb47a52dfea51067658db6b91768b4dab4b7b9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 15 Jun 2026 14:57:52 +0200 Subject: [PATCH 2/8] refac --- cptr/frontend/src/lib/components/GitBar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cptr/frontend/src/lib/components/GitBar.svelte b/cptr/frontend/src/lib/components/GitBar.svelte index cf31bea..9b1dd2b 100644 --- a/cptr/frontend/src/lib/components/GitBar.svelte +++ b/cptr/frontend/src/lib/components/GitBar.svelte @@ -685,7 +685,7 @@
- {#each gitStatus?.files ?? [] as file (file.path)} + {#each gitStatus?.files ?? [] as file (`${file.path}:${file.staged}`)} {@const fp = fPath(file.path)} {@const sc = statusChar(file.status)} + {#if $chatModels.length > 0} + + + + {/if} + -{#if open && btnEl && $chatModels.length > 0} - (open = false)} - preferAbove={preferAbove} - maxHeight="15rem" - className="w-48" - align={align} - > - {#snippet header()} -
- - - - { - if (e.key === 'Escape') open = false; - }} - /> -
- {/snippet} - {#snippet empty()} -
{$t('modelSelector.noMatches')}
- {/snippet} -
-{/if} + {#if open && btnEl && $chatModels.length > 0} + (open = false)} + preferAbove={preferAbove} + forceAbove={preferAbove} + inlineAbove={preferAbove} + maxHeight={selectorMaxHeight} + className="w-48" + align={align} + > + {#snippet header()} +
+ + + + { + if (e.key === 'Escape') open = false; + }} + /> +
+ {/snippet} + {#snippet empty()} +
{$t('modelSelector.noMatches')}
+ {/snippet} +
+ {/if} + From 5cf75138e9f5bd65cd84f0acfb4fe0ce3816642f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 15 Jun 2026 15:56:20 +0200 Subject: [PATCH 7/8] refac --- .../src/lib/components/DropdownMenu.svelte | 64 ++++++++++++++++++- .../components/common/ModelSelector.svelte | 3 +- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/cptr/frontend/src/lib/components/DropdownMenu.svelte b/cptr/frontend/src/lib/components/DropdownMenu.svelte index 567f369..6740d98 100644 --- a/cptr/frontend/src/lib/components/DropdownMenu.svelte +++ b/cptr/frontend/src/lib/components/DropdownMenu.svelte @@ -62,8 +62,35 @@ let menuMaxHeight = $state(); let ready = $state(false); let frame: number | undefined; + let anchorFrame: number | undefined; let settleTimers: number[] = []; let lastViewportState = ''; + let lastAnchorState = ''; + + function portal(node: HTMLElement, enabled = true) { + const parent = node.parentNode; + const sibling = node.nextSibling; + + function move() { + if (enabled && node.parentNode !== document.body) { + document.body.appendChild(node); + } else if (!enabled && parent && node.parentNode === document.body) { + parent.insertBefore(node, sibling); + } + } + + move(); + + return { + update(nextEnabled: boolean) { + enabled = nextEnabled; + move(); + }, + destroy() { + node.remove(); + } + }; + } function visualViewportRect() { const vv = window.visualViewport; @@ -87,6 +114,25 @@ ].join(':'); } + function anchorState() { + if (!(anchor instanceof HTMLElement)) { + return `${anchor.x}:${anchor.y}:${viewportState()}`; + } + + const rect = anchor.getBoundingClientRect(); + return [ + rect.left, + rect.top, + rect.right, + rect.bottom, + rect.width, + rect.height, + viewportState() + ] + .map((value) => (typeof value === 'number' ? value.toFixed(2) : value)) + .join(':'); + } + function measureMenu() { if (!menuEl) return { width: 0, height: 0 }; @@ -204,12 +250,23 @@ } } + function trackAnchor() { + const nextAnchorState = anchorState(); + if (nextAnchorState !== lastAnchorState) { + lastAnchorState = nextAnchorState; + updatePosition(); + } + anchorFrame = requestAnimationFrame(trackAnchor); + } + onMount(() => { let dvhProbe: HTMLDivElement | undefined; let dvhObserver: ResizeObserver | undefined; lastViewportState = viewportState(); + lastAnchorState = anchorState(); scheduleUpdate(); + anchorFrame = requestAnimationFrame(trackAnchor); // Follow anchor on scroll/resize window.addEventListener('scroll', scheduleUpdate, true); @@ -235,6 +292,7 @@ return () => { if (frame != null) cancelAnimationFrame(frame); + if (anchorFrame != null) cancelAnimationFrame(anchorFrame); for (const timer of settleTimers) window.clearTimeout(timer); dvhObserver?.disconnect(); dvhProbe?.remove(); @@ -254,7 +312,8 @@
{ e.preventDefault(); @@ -264,10 +323,11 @@
+