Skip to content

1Helm 0.0.23: desktop connections and Cowork polish - #32

Merged
gitcommit90 merged 4 commits into
mainfrom
feat/desktop-connect-presentation-fit
Jul 28, 2026
Merged

1Helm 0.0.23: desktop connections and Cowork polish#32
gitcommit90 merged 4 commits into
mainfrom
feat/desktop-connect-presentation-fit

Conversation

@gitcommit90

@gitcommit90 gitcommit90 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Outcome

Ships the complete 0.0.23 source candidate with the requested desktop connection gateway and targeted product fixes.

Acceptance ledger

  1. Fresh macOS and Windows installs can connect to [workspace].1helm.com or a different HTTPS URL, while New User? exposes Set this PC up as a 1Helm Server to Get Started. Existing server and client installs retain their mode; Linux remains server-oriented.
  2. Presentations fit the complete dotted printable boundary when a deck opens or a slide is selected, created, or duplicated.
  3. The Routes graphic includes a collapsed Custom provider node and illuminates it for custom/OpenAI-compatible activity while preserving endpoint detail.
  4. Cowork exposes its agent at section/folder scope before a file opens and supplies the exact /workspace/... folder or file context.
  5. Long Cowork Code files have a finite CodeMirror viewport and respond to real wheel/trackpad scrolling.
  6. Android and iOS gateways release the native splash after first paint, use the real 1Helm artwork, and start with the workspace-name connection flow.
  7. Admins receive a one-time Later / Restart Now prompt after a desktop update has downloaded and verified; deferring leaves it ready in Profile without repeat nagging for that version.

Verification

  • npm run typecheck
  • npm run build
  • node --test test/desktop.mjs
  • node test/brief-regressions-browser.mjs — 52/52
  • node --test test/cowork-browser.mjs — real wheel input passes
  • node --test test/channel-surfaces.mjs
  • node --test test/mobile.mjs
  • node test/native-world.mjs — 125/125
  • git diff --check

A full-suite retry encountered one transient mock-provider fetch failure; the affected native-world lane then passed 125/125 standalone. The first full suite before the final update-prompt commit passed 125 native assertions and 107/109 remaining tests with two environment skips.

Summary by CodeRabbit

  • New Features
    • Added a one-time host Later / Restart Now prompt for ready desktop updates.
    • Enhanced desktop and mobile connection onboarding with workspace-name vs custom instance URL flows.
    • Updated live routing visuals to include a Custom provider node for OpenAI-compatible endpoints.
  • Bug Fixes
    • Improved presentation viewport fitting, Cowork folder navigation/chat context, Cowork code scrolling, and mobile splash-screen timing.
  • Documentation
    • Clarified macOS update behavior and refreshed configuration guidance for version 0.0.23.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0505e9e3-76e2-46e9-a99c-ab16a0806683

📥 Commits

Reviewing files that changed from the base of the PR and between 47343d3 and e7c6c4a.

📒 Files selected for processing (1)
  • test/mobile.mjs

📝 Walkthrough

Walkthrough

The 0.0.23 release adds desktop and mobile workspace connection flows, one-time host update prompts, Cowork folder and viewport handling, custom provider route visualization, and updated channel-machine defaults with corresponding tests and documentation.

Changes

Desktop workspace targeting

Layer / File(s) Summary
Gateway origin validation and connection UI
desktop/workspace-target.cjs, desktop/gateway.html
Workspace names and custom HTTPS origins are validated and converted into setup or connect actions.
Desktop mode startup and routing
desktop/main.cjs
Persisted desktop mode selects local runtime startup, remote workspace loading, gateway navigation, and login-item behavior.
Desktop targeting coverage
test/desktop.mjs
Tests cover origin filtering, gateway actions, sandbox allowlisting, and startup routing.

Mobile workspace connection

Layer / File(s) Summary
Mobile gateway connection modes
mobile-gateway/index.html, mobile-gateway/error.html
Mobile onboarding supports workspace-name and custom-URL flows, compatibility checks, native server selection, and delayed splash dismissal.
Mobile shell validation
test/mobile.mjs
Tests verify splash timing, logo usage, and connection-mode switching.

Host update prompts

Layer / File(s) Summary
Update-ready prompt lifecycle
src/client/app.ts, docs/USER_GUIDE.md
The client polls host update status and shows a one-time admin Later / Restart Now modal for verified downloads.
Update prompt regression coverage
test/brief-regressions-browser.mjs, test/desktop.mjs
Tests cover prompt display, deferral, suppression, and restart installation.

Cowork context and viewport behavior

Layer / File(s) Summary
Editor and presentation viewport handling
src/client/cowork-editors.ts, src/client/cowork.ts, src/client/styles.css
Code editors scroll within their viewport, and presentation canvases fit the printable boundary after initialization.
Folder navigation and chat context
src/client/cowork.ts
Folder navigation and chat threading consistently use file-or-folder context paths.
Folder path validation and message handling
src/server/cowork-collaboration.ts, src/server/index.ts
Folder paths use dedicated validation and directory listing, while file handling retains file viewers.
Cowork behavior tests
test/channel-surfaces.mjs, test/cowork-browser.mjs
Contract and browser tests cover folder chat, scrolling, and presentation fitting.

Custom provider routing

Layer / File(s) Summary
Custom provider route rendering
src/client/routing.ts, test/brief-regressions-browser.mjs, test/routing-ui-contract.mjs
The live route map includes a Custom family and marks OpenAI-compatible requests as active custom routes.

Release and provisioning defaults

Layer / File(s) Summary
Version and release metadata
package.json, CHANGELOG.md, README.md, public/index.html
Release metadata and cache-busted assets advance to version 0.0.23.
Channel-machine image defaults
src/server/channel-computers.ts, src/server/db.ts, test/channel-computers.mjs
Provisioning defaults and assertions use the 0.0.23 channel-machine image.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DesktopGateway
  participant DesktopMain
  participant CompatibilityAPI
  DesktopGateway->>DesktopMain: Submit workspace or instance origin
  DesktopMain->>CompatibilityAPI: Validate remote compatibility
  CompatibilityAPI-->>DesktopMain: Return compatibility and setup status
  DesktopMain->>DesktopGateway: Load workspace or display connection error
Loading
sequenceDiagram
  participant MobileGateway
  participant InstanceGateway
  participant CompatibilityAPI
  MobileGateway->>InstanceGateway: Read existing server origin
  MobileGateway->>CompatibilityAPI: Check selected origin
  CompatibilityAPI-->>MobileGateway: Return compatibility and setup status
  MobileGateway->>InstanceGateway: Select server origin
Loading

Possibly related PRs

  • gitcommit90/1Helm#31: Introduced the mobile gateway and native instance-selection behavior extended by this PR.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning It covers outcome, acceptance items, and verification, but misses required template sections like Summary, Type of change, Release notes, and Post-merge. Add the missing template sections: Summary, Type of change, Release notes with CHANGELOG bullets, checklist items, and Post-merge notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately highlights the main changes: desktop connections and Cowork polish for 0.0.23.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-connect-presentation-fit

Comment @coderabbitai help to get the list of available commands.

@gitcommit90
gitcommit90 merged commit 8c0af2d into main Jul 28, 2026
5 of 6 checks passed
@gitcommit90
gitcommit90 deleted the feat/desktop-connect-presentation-fit branch July 28, 2026 21:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/main.cjs`:
- Around line 342-350: Gate createWindow() calls triggered by second-instance
and activate behind the same startup promise used by startLocalRuntime(),
ensuring loadInitialWorkspace() runs only after the runtime has initialized
localOrigin. Introduce or reuse a shared startup guard in the desktop startup
flow, while preserving the existing behavior for platforms that call
stopAutomaticServerStartup().

In `@desktop/workspace-target.cjs`:
- Around line 3-4: Expand the RESERVED_WORKSPACES set to include every
system-reserved *.1helm.com hostname defined by the backend, including the
listed admin, api, app, assets, auth, billing, blog, cdn, docs, help, mail,
security, status, support, www, 1helm, cloudflare, login, signup, and static
entries, while preserving the existing demo and provision reservations.

In `@src/client/app.ts`:
- Around line 174-182: Update scheduleHostUpdatePromptChecks and boot to
invalidate in-flight polls across boot and rescheduling: increment a shared
updateReadyPollGeneration before clearing the timeout, capture the current
generation when poll starts, and verify it before showing the update prompt and
before scheduling the next timeout. Ensure stale requests cannot process
responses or recreate the polling loop after logout or reboot.
- Around line 155-162: Update maybeShowUpdateReadyPrompt so the stored
suppression identity includes S.me.id along with the existing update and version
fields, ensuring prompt dismissal is scoped to the current admin. Add a
regression test covering two admin accounts using the same browser storage and
verify the second account still receives its prompt.

In `@src/client/cowork.ts`:
- Around line 558-565: Update the send function to capture the current Cowork
context and chatRootId before awaiting api, then persist any newly created
thread root using the captured context rather than current shared state. After
the request resolves, only clear coworkContextPending, input, drafts, and
rerender when the captured context remains active; ensure newer context state is
not overwritten.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ddc0c055-cc5c-4c09-bfc0-c627daa0f12e

📥 Commits

Reviewing files that changed from the base of the PR and between 8a90acc and 47343d3.

⛔ Files ignored due to path filters (2)
  • mobile-gateway/1helm-logo.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • CHANGELOG.md
  • README.md
  • desktop/gateway.html
  • desktop/main.cjs
  • desktop/workspace-target.cjs
  • docs/USER_GUIDE.md
  • mobile-gateway/error.html
  • mobile-gateway/index.html
  • package.json
  • public/index.html
  • src/client/app.ts
  • src/client/cowork-editors.ts
  • src/client/cowork.ts
  • src/client/routing.ts
  • src/client/styles.css
  • src/server/channel-computers.ts
  • src/server/cowork-collaboration.ts
  • src/server/db.ts
  • src/server/index.ts
  • test/brief-regressions-browser.mjs
  • test/channel-computers.mjs
  • test/channel-surfaces.mjs
  • test/cowork-browser.mjs
  • test/desktop.mjs
  • test/mobile.mjs
  • test/routing-ui-contract.mjs

Comment thread desktop/main.cjs
Comment on lines +342 to +350
const mode = desktopMode();
if (mode === "server" || process.platform === "linux") {
keepSkipperAvailable();
prepareWindowsWslDataRoot();
await startLocalRuntime();
hostUpdateService.schedule();
} else {
stopAutomaticServerStartup();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '330,365p' desktop/main.cjs

Repository: gitcommit90/1Helm

Length of output: 1560


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== createWindow and loadInitialWorkspace definitions =="
rg -n "function createWindow|const createWindow|loadInitialWorkspace|createWindow\\(" desktop/main.cjs

echo
echo "== surrounding createWindow implementation =="
python3 - <<'PY'
from pathlib import Path
p = Path("desktop/main.cjs")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if "function createWindow" in line or "const createWindow" in line:
        start = max(1, i-25)
        end = min(len(text), i+120)
        for j in range(start, end+1):
            print(f"{j}:{text[j-1]}")
        print("\n---\n")
PY

Repository: gitcommit90/1Helm

Length of output: 7132


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all app.on registrations and startup ordering =="
rg -n "app\\.on\\(|whenReady\\(|createWindow\\(|loadInitialWorkspace\\(" desktop/main.cjs

Repository: gitcommit90/1Helm

Length of output: 727


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== loadInitialWorkspace implementation =="
python3 - <<'PY'
from pathlib import Path
p = Path("desktop/main.cjs")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if "loadInitialWorkspace" in line:
        start = max(1, i-30)
        end = min(len(text), i+80)
        for j in range(start, end+1):
            print(f"{j}:{text[j-1]}")
        print("\n---\n")
PY

Repository: gitcommit90/1Helm

Length of output: 10068


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== any other main-window creation paths =="
rg -n "new BrowserWindow|createWindow\\(|mainWindow" desktop/main.cjs

Repository: gitcommit90/1Helm

Length of output: 934


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runtime/origin definitions =="
rg -n "let localOrigin|const localOrigin|function startLocalRuntime|async function startLocalRuntime|desktopMode\\(" desktop/main.cjs

echo
echo "== startLocalRuntime implementation slice =="
python3 - <<'PY'
from pathlib import Path
p = Path("desktop/main.cjs")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if "function startLocalRuntime" in line or "async function startLocalRuntime" in line:
        start = max(1, i-40)
        end = min(len(text), i+160)
        for j in range(start, end+1):
            print(f"{j}:{text[j-1]}")
        print("\n---\n")
PY

Repository: gitcommit90/1Helm

Length of output: 8870


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== second-instance and activate semantics in file =="
sed -n '295,370p' desktop/main.cjs | cat -n

Repository: gitcommit90/1Helm

Length of output: 4245


Gate window creation on the runtime startup promise. second-instance and activate can still reach createWindow() before await startLocalRuntime() finishes, so loadInitialWorkspace() may try to load an empty localOrigin. A shared startup guard would remove the race.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/main.cjs` around lines 342 - 350, Gate createWindow() calls triggered
by second-instance and activate behind the same startup promise used by
startLocalRuntime(), ensuring loadInitialWorkspace() runs only after the runtime
has initialized localOrigin. Introduce or reuse a shared startup guard in the
desktop startup flow, while preserving the existing behavior for platforms that
call stopAutomaticServerStartup().

Comment on lines +3 to +4
const WORKSPACE_HOST = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.1helm\.com$/i;
const RESERVED_WORKSPACES = new Set(["demo.1helm.com", "provision.1helm.com"]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare desktop reserved-workspace list against backend reserved-slug validation
rg -n 'RESERVED_WORKSPACES|reserved' desktop/workspace-target.cjs
rg -n 'slugValid|reserved' cloudflare/src/worker.ts

Repository: gitcommit90/1Helm

Length of output: 972


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the backend reserved-slug definition and nearby usage.
sed -n '1,90p' cloudflare/src/worker.ts

Repository: gitcommit90/1Helm

Length of output: 4593


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all reserved-slug definitions/usages in the repo.
rg -n 'const RESERVED|RESERVED_WORKSPACES|slugValid|reserved slug|reserved' .

Repository: gitcommit90/1Helm

Length of output: 8911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the exact backend reserved-slug definition and its surrounding context.
sed -n '36,56p' cloudflare/src/worker.ts

Repository: gitcommit90/1Helm

Length of output: 1401


Expand RESERVED_WORKSPACES to match the backend reserved-slug list. cloudflare/src/worker.ts reserves more system slugs than demo/provision (for example admin, api, app, assets, auth, billing, blog, cdn, docs, help, mail, security, status, support, www, 1helm, cloudflare, login, signup, static), so the desktop host check can treat reserved *.1helm.com names as workspace targets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/workspace-target.cjs` around lines 3 - 4, Expand the
RESERVED_WORKSPACES set to include every system-reserved *.1helm.com hostname
defined by the backend, including the listed admin, api, app, assets, auth,
billing, blog, cdn, docs, help, mail, security, status, support, www, 1helm,
cloudflare, login, signup, and static entries, while preserving the existing
demo and provision reservations.

Comment thread src/client/app.ts
Comment on lines +155 to +162
const UPDATE_READY_PROMPT_KEY = "1helm.updateReadyPrompt";
let updateReadyPoll = 0;

function maybeShowUpdateReadyPrompt(update: HostUpdate): void {
if (!S.me?.is_admin || update.status !== "ready") return;
const identity = `${update.mode}:${update.current_version}->${update.version || "downloaded"}`;
if (localStorage.getItem(UPDATE_READY_PROMPT_KEY) === identity || document.querySelector("[data-update-ready-prompt]")) return;
localStorage.setItem(UPDATE_READY_PROMPT_KEY, identity);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and inspect the surrounding code.
ast-grep outline src/client/app.ts --view expanded

printf '\n--- lines around maybeShowUpdateReadyPrompt ---\n'
sed -n '130,210p' src/client/app.ts

printf '\n--- search for helper usage ---\n'
rg -n "getServerOrigin|UPDATE_READY_PROMPT_KEY|maybeShowUpdateReadyPrompt|localStorage.getItem\\(|localStorage.setItem\\(" src/client/app.ts src/client -g '!**/dist/**'

Repository: gitcommit90/1Helm

Length of output: 24507


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect how user identity / target context is represented elsewhere.
rg -n "S\\.me|is_admin|server origin|target|host|origin|account" src/client -g '!**/dist/**'

# Look for tests or related code paths around the update prompt.
rg -n "updateReady|update ready|Later|data-update-ready-prompt" src -g '!**/dist/**'

Repository: gitcommit90/1Helm

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the definition of HostUpdate to understand whether the prompt identity already encodes a target.
rg -n "type HostUpdate|interface HostUpdate|HostUpdate" src -g '!**/dist/**'

# Read the type if found.
file=$(rg -l "type HostUpdate|interface HostUpdate" src -g '!**/dist/**' | head -n 1 || true)
if [ -n "${file:-}" ]; then
  echo "--- $file ---"
  sed -n '1,220p' "$file"
fi

Repository: gitcommit90/1Helm

Length of output: 14657


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,220p' src/client/mobile.ts

Repository: gitcommit90/1Helm

Length of output: 9455


Scope the prompt suppression to the current admin. The stored identity only uses update/version data, so one admin’s “Later” choice can suppress the prompt for another admin on the same browser profile. Include S.me.id in the key and add a regression case for a second account.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/app.ts` around lines 155 - 162, Update maybeShowUpdateReadyPrompt
so the stored suppression identity includes S.me.id along with the existing
update and version fields, ensuring prompt dismissal is scoped to the current
admin. Add a regression test covering two admin accounts using the same browser
storage and verify the second account still receives its prompt.

Comment thread src/client/app.ts
Comment on lines +174 to +182
function scheduleHostUpdatePromptChecks(): void {
window.clearTimeout(updateReadyPoll);
if (!S.me?.is_admin) return;
const poll = async (): Promise<void> => {
try { maybeShowUpdateReadyPrompt(await api<HostUpdate>("/api/app/update")); }
catch { /* Profile retains the visible manual retry path. */ }
updateReadyPoll = window.setTimeout(() => { void poll(); }, 30_000);
};
updateReadyPoll = window.setTimeout(() => { void poll(); }, 25_000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Invalidate in-flight polls during boot and rescheduling.

boot() only clears the timeout. If poll() has already started its request, that request still schedules a new timeout when it finishes, leaving a stale or duplicate polling loop after logout/reboot and potentially processing a response under a new session.

Track a generation token or abort the request, and check it before showing the prompt and before scheduling the next timeout.

Suggested generation guard
+let updateReadyPollGeneration = 0;

 function scheduleHostUpdatePromptChecks(): void {
+  const generation = ++updateReadyPollGeneration;
   window.clearTimeout(updateReadyPoll);
   if (!S.me?.is_admin) return;
   const poll = async (): Promise<void> => {
-    try { maybeShowUpdateReadyPrompt(await api<HostUpdate>("/api/app/update")); }
+    try {
+      const update = await api<HostUpdate>("/api/app/update");
+      if (generation !== updateReadyPollGeneration) return;
+      maybeShowUpdateReadyPrompt(update);
+    }
     catch { /* Profile retains the visible manual retry path. */ }
+    if (generation !== updateReadyPollGeneration) return;
     updateReadyPoll = window.setTimeout(() => { void poll(); }, 30_000);
   };

Increment updateReadyPollGeneration in boot() before clearing the timeout.

Also applies to: 283-283

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/app.ts` around lines 174 - 182, Update
scheduleHostUpdatePromptChecks and boot to invalidate in-flight polls across
boot and rescheduling: increment a shared updateReadyPollGeneration before
clearing the timeout, capture the current generation when poll starts, and
verify it before showing the update prompt and before scheduling the next
timeout. Ensure stale requests cannot process responses or recreate the polling
loop after logout or reboot.

Comment thread src/client/cowork.ts
Comment on lines 558 to +565
const send = async (): Promise<void> => {
const message = input.value.trim(); if (!message || !session.path) return;
const message = input.value.trim(); if (!message) return;
input.disabled = true;
try {
const body = chatRootId ? message : `@${channel.agent?.name || "agent"} ${message}`;
const result = await api<{ message: Message }>(`/api/channels/${channelId}/messages`, { body: { body, parentId: chatRootId || null, ...(coworkContextPending ? { coworkPath: session.path } : {}) } });
if (!chatRootId) { chatRootId = result.message.id; localStorage.setItem(threadKey(session.path), String(chatRootId)); }
coworkContextPending = false; input.value = ""; agentDrafts.delete(session.path); await renderChatMessages();
const result = await api<{ message: Message }>(`/api/channels/${channelId}/messages`, { body: { body, parentId: chatRootId || null, ...(coworkContextPending ? { coworkPath: context, coworkKind: session.path ? "file" : "folder" } : {}) } });
if (!chatRootId) { chatRootId = result.message.id; localStorage.setItem(threadKey(context), String(chatRootId)); }
coworkContextPending = false; input.value = ""; agentDrafts.delete(context); await renderChatMessages();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent an in-flight request from overwriting a newer Cowork context.

If the user switches folders/sections before await api(...) resolves, this response writes its old chatRootId into shared state and clears coworkContextPending. The next message in the new context can become a reply to the old context’s thread without sending its own coworkPath.

Capture the request context/root before awaiting, persist a newly created root under that captured context, and update shared UI state only if that context is still active.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/cowork.ts` around lines 558 - 565, Update the send function to
capture the current Cowork context and chatRootId before awaiting api, then
persist any newly created thread root using the captured context rather than
current shared state. After the request resolves, only clear
coworkContextPending, input, drafts, and rerender when the captured context
remains active; ensure newer context state is not overwritten.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant