refactor(web): open produced files through the Host, not over HTTP
Scope decision: previews for a browser that is not on the Host machine are not supported. With that settled, host.openPath answers the supported case completely — a file:// document in a real browser has full page capabilities and no reach into /api — and the HTTP serving this branch had built answered only the unsupported one. Removed: the /f route and its listener, the workspace-file URL shape, ApiProxy.workspaceRootOf, ConnectionHandle.fileUrl, and the port published into the index page. Kept, and finished: - the produced-files row a turn ends with, derived from mutation locations; - the path link now reads as a link at rest, not only on hover — the reported "I can't open what it made" was this, sitting on a working capability; - the Host opener prefers the default BROWSER for .html/.htm/.xhtml/.svg, so a developer who binds .html to an editor still gets a rendered page (macOS via the LaunchServices https handler, Linux via $BROWSER, every failure falling back to the default application). The retired designs and their measurements stay in the Agent Note, including why same-origin serving was unsafe and why the sandbox that fixed it broke the pages invisibly.
This commit is contained in:
@@ -25,7 +25,6 @@
|
||||
"vitest": "^4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-client-connection": "^0.0.1",
|
||||
"@deepseek-ai/dsh-client-runtime": "^0.0.1",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1",
|
||||
"@deepseek-ai/dsh-client-web-react": "^0.0.1",
|
||||
@@ -36,7 +35,6 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-web-react": "workspace:^",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/** Test-owned connection face: the transport members features read off `ctx.connection`. */
|
||||
import { workspaceFileSegments, workspaceFileUrl } from '@deepseek-ai/dsh-host-apiproxy/api'
|
||||
import type { ConnectionHandle, IApiClient, SessionId } from '@deepseek-ai/dsh-client-connection/client'
|
||||
|
||||
/**
|
||||
* Connection test double. Implements the same `ConnectionHandle` face features
|
||||
* receive as `ctx.connection`, so a production face change breaks this double
|
||||
* at compile time. The wire client is not modelled — a feature that needs one
|
||||
* composes its own connection over a fake api client; this double exists for
|
||||
* the transport facts features read synchronously, above all the
|
||||
* workspace-file URL.
|
||||
*/
|
||||
export class TestConnection implements ConnectionHandle {
|
||||
/**
|
||||
* The workspace-file port the host would have published into the page.
|
||||
* Unset — the default, and the keyless fixture lane's real state — makes
|
||||
* {@link TestConnection.fileUrl} answer `undefined`, which is the signal a
|
||||
* caller falls back to the Host opener on.
|
||||
*/
|
||||
filesPort: number | undefined
|
||||
|
||||
/** The wire client; unused by this double's consumers and absent by construction. */
|
||||
readonly api: IApiClient = undefined as unknown as IApiClient
|
||||
|
||||
/**
|
||||
* Stream-loop starter (inert).
|
||||
* @returns a stop handle that does nothing.
|
||||
*/
|
||||
start(): { stop(): void } {
|
||||
return { stop: () => {} }
|
||||
}
|
||||
|
||||
/**
|
||||
* Workspace-file URL, deriving exactly as production does so a feature test
|
||||
* sees the real inside/outside-workspace split.
|
||||
* @param sessionId - the Session whose cwd anchors the path.
|
||||
* @param cwd - that Session's working directory.
|
||||
* @param path - the path a tool reported.
|
||||
* @returns the absolute URL on the workspace-file origin, or undefined when
|
||||
* the path leaves the workspace or no port is published.
|
||||
*/
|
||||
fileUrl(sessionId: SessionId, cwd: string | undefined, path: string): string | undefined {
|
||||
if (this.filesPort === undefined) return undefined
|
||||
const segments = workspaceFileSegments(cwd, path)
|
||||
if (segments === undefined) return undefined
|
||||
return `http://localhost:${String(this.filesPort)}${workspaceFileUrl(sessionId, segments)}`
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,11 @@ import type {
|
||||
} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { registerDomSnapshotSerializer } from './snapshot.ts'
|
||||
import { TestSessions } from './sessions.ts'
|
||||
import { TestConnection } from './connection.ts'
|
||||
import { TestWorkspaces } from './workspaces.ts'
|
||||
import type { Stabilizer } from './fixtures.ts'
|
||||
|
||||
export { domSnapshotSerializer, registerDomSnapshotSerializer } from './snapshot.ts'
|
||||
export { FixtureSession, TestSessions } from './sessions.ts'
|
||||
export { TestConnection } from './connection.ts'
|
||||
export { TestWorkspaces } from './workspaces.ts'
|
||||
export { conversationSnapshot, workspaceListState } from './fixtures.ts'
|
||||
export type { SessionBehaviorOverrides, SessionFixture, Stabilizer } from './fixtures.ts'
|
||||
@@ -177,8 +175,6 @@ export class SlotTestRuntime {
|
||||
readonly sessions: TestSessions
|
||||
/** Workspaces double (list observable, recorded intent actions). */
|
||||
readonly workspaces: TestWorkspaces
|
||||
/** The transport double features read as `ctx.connection`. */
|
||||
readonly connection: TestConnection
|
||||
|
||||
private readonly stabilizer: Stabilizer = async (fn) => {
|
||||
await act(async () => { await fn() })
|
||||
@@ -199,10 +195,8 @@ export class SlotTestRuntime {
|
||||
this.root = new TestRoot(slots, this.stabilizer)
|
||||
this.sessions = new TestSessions(this.stabilizer, ctx)
|
||||
this.workspaces = new TestWorkspaces(this.stabilizer)
|
||||
this.connection = new TestConnection()
|
||||
ctx.provide('sessions', this.sessions)
|
||||
ctx.provide('workspaces', this.workspaces)
|
||||
ctx.provide('connection', this.connection)
|
||||
// Capturing install: the production renderer does the rendering; the
|
||||
// wrapper only takes the host face for storeOf (no machinery copied).
|
||||
const renderer = createSlotRenderer()
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
{
|
||||
"path": "../web-react"
|
||||
},
|
||||
{
|
||||
"path": "../connection"
|
||||
},
|
||||
{
|
||||
"path": "../runtime"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user