From d4fede1577f1bf798a90dddfba5e29d5285a2dec Mon Sep 17 00:00:00 2001 From: mpstaton Date: Fri, 21 Aug 2026 15:21:13 -0500 Subject: [PATCH] fix(workspace, apps, shell): resolve the workspace WS endpoint from PUBLIC_WS_URL in one place, not sixteen literals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Org Workbench rendered its chrome and nothing else on augment.didi.sh: no roster, no organizations, a red `closed` pill in the corner. The cause was a single line repeated across the app tree — every remote declared `const WS_URL = 'ws://localhost:3001/ws'` and never read the environment, so a deployed surface opened its data socket against the *visitor's* laptop. It could not have worked in production for two independent reasons: the host is wrong, and `ws://` from an `https://` origin is blocked as mixed content. Local dev hid it perfectly, because the operator's machine really is running workspace-service on :3001. Three of the sixteen affected remotes are deployed, which took down the whole Augment-from-DB flow rather than one surface. The endpoint now has exactly one home. `packages/workspace/src/ws-url.ts` exports `resolveWsUrl()` and `resolveHttpBase()`, and `TransportConfig.url` becomes optional — omit it and the transport resolves from PUBLIC_WS_URL on every connect attempt. That is the part that makes the bug structurally unavailable: the failure mode was never a logic error, it was copy-paste into a new remote, and the copy-pasteable thing no longer exists. Every call site was converted, including the two that had quietly got it right (chat, and corpora-curator's real client in curation.svelte.ts) so there is no second pattern to imitate, and the WorkspaceSwitcher tooltip stops telling operators that localhost is unreachable when the endpoint is somewhere else entirely. Verified by building org-workbench with PUBLIC_WS_URL set and reading the emitted bundle: the resolver compiles to the prod endpoint with localhost left only as the dead fallback branch. A new guard in the workspace suite sweeps apps/, shell/, and packages/ for the literal and fails with the offending filenames; planting the old line back into org-workbench makes it fail, so it is not vacuous. svelte-check is clean across all 17 surfaces. Deploy note: PUBLIC_* is baked at build time, so the three deployed remotes need a rebuild, not a restart, once their variables are set. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017HuzSPdPK4QEhs7eztHgoC --- .../src/App.svelte | 4 +- apps/chat/src/App.svelte | 6 +- apps/corpora-curator/src/App.svelte | 4 +- apps/corpora-curator/src/curation.svelte.ts | 6 +- apps/enhanced-records-list/src/App.svelte | 1 - apps/org-workbench/src/App.svelte | 4 +- apps/pack-runner/src/App.svelte | 4 +- apps/person-db-resolver/src/App.svelte | 4 +- apps/prompt-template-manager/src/App.svelte | 4 +- apps/record-collector/src/App.svelte | 4 +- apps/record-db-resolver/src/App.svelte | 4 +- apps/records-surface/src/App.svelte | 4 +- apps/request-reviewer/src/App.svelte | 5 +- apps/response-reviewer/src/App.svelte | 5 +- apps/search-and-add/src/App.svelte | 4 +- apps/search-results/src/App.svelte | 4 +- apps/sort-filter-lens/src/App.svelte | 4 +- packages/workspace/src/index.ts | 1 + packages/workspace/src/state.svelte.ts | 3 +- packages/workspace/src/transport.ts | 16 ++++- packages/workspace/src/ws-url.ts | 39 ++++++++++ packages/workspace/test/ws-url.test.ts | 72 +++++++++++++++++++ shell/src/App.svelte | 8 +-- shell/src/WorkspaceSwitcher.svelte | 4 +- 24 files changed, 164 insertions(+), 50 deletions(-) create mode 100644 packages/workspace/src/ws-url.ts create mode 100644 packages/workspace/test/ws-url.test.ts diff --git a/apps/affiliation-rating-resolver/src/App.svelte b/apps/affiliation-rating-resolver/src/App.svelte index 10d0c6c..299c94f 100644 --- a/apps/affiliation-rating-resolver/src/App.svelte +++ b/apps/affiliation-rating-resolver/src/App.svelte @@ -8,7 +8,7 @@ // context-v/specs/Augment-From-Affiliations.md. import { onMount } from 'svelte'; - import { workspace, type RecordSet, type Row } from '@augment-it/workspace'; + import { workspace, type RecordSet, type Row, resolveWsUrl } from '@augment-it/workspace'; import ColumnMapper from './components/ColumnMapper.svelte'; import { normalizeRatingRecord, guessMapping, MAPPING_NONE } from './lib/normalize'; import { @@ -23,7 +23,7 @@ import type { RatingFieldMapping, RatingNormRecord, AffiliationDetail, Link, CorpusEntry } from './lib/types'; const TOKEN_KEY = 'augment-it:session-token'; - const WS_URL = 'ws://localhost:3001/ws'; + const WS_URL = resolveWsUrl(); const ACTIVE_RECORD_SET_KEY = 'augment-it:active-record-set'; const MAPPING_KEY_PREFIX = 'augment-it:affiliation-rating-resolver:mapping:'; diff --git a/apps/chat/src/App.svelte b/apps/chat/src/App.svelte index c5dbcd7..8e2bf1a 100644 --- a/apps/chat/src/App.svelte +++ b/apps/chat/src/App.svelte @@ -6,7 +6,7 @@ // is established independently (no shared workspace singleton). import { onMount } from 'svelte'; - import { workspace } from '@augment-it/workspace'; + import { workspace, resolveWsUrl } from '@augment-it/workspace'; import CharacterCastRow from './CharacterCastRow.svelte'; import ChatSurface from './ChatSurface.svelte'; @@ -15,9 +15,7 @@ // even when mounted inside the shell, so it needs the same env-configured // WS_URL the shell and corpora-curator each read (rsbuild inlines // PUBLIC_-prefixed vars into import.meta.env at build time). - const WS_URL = - ((import.meta as { env?: Record }).env?.PUBLIC_WS_URL as string | undefined) || - 'ws://localhost:3001/ws'; + const WS_URL = resolveWsUrl(); let connectionStatus = $state<'connecting' | 'open' | 'closed' | 'error' | 'auth_required'>('connecting'); diff --git a/apps/corpora-curator/src/App.svelte b/apps/corpora-curator/src/App.svelte index 70a5bee..f8afe8f 100644 --- a/apps/corpora-curator/src/App.svelte +++ b/apps/corpora-curator/src/App.svelte @@ -1,12 +1,12 @@