Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/ui-mac/src/main/alpha-surfaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("resolveSurfaces — Alpha-only composition", () => {
const defaults = resolveSurfaces({ env: {}, file: {}, appVersion: "1.0.0" })
expect(defaults.home).toEqual({ mode: "alpha", reason: "release-default" })
expect(defaults.newSession).toEqual({ mode: "alpha", reason: "release-default" })
expect(defaults.session).toEqual({ mode: "legacy", reason: "release-default" })
expect(defaults.session).toEqual({ mode: "alpha", reason: "release-default" })

const pin = resolveSurfaces({ env: {}, file: { pins: { session: "alpha" } }, appVersion: "1.0.0" })
expect(pin.session).toEqual({ mode: "alpha", reason: "pin" })
Expand All @@ -41,6 +41,12 @@ describe("resolveSurfaces — Alpha-only composition", () => {
appVersion: "1.0.0",
})
expect(env.home).toEqual({ mode: "legacy", reason: "env-override" })
const sessionEnv = resolveSurfaces({
env: { ALPHA_SURFACE_SESSION: "legacy" },
file: { pins: { session: "alpha" } },
appVersion: "1.0.0",
})
expect(sessionEnv.session).toEqual({ mode: "legacy", reason: "env-override" })
})

test("a current or historical crash record never changes an Alpha surface to legacy", () => {
Expand All @@ -63,7 +69,7 @@ describe("resolveSurfaces — Alpha-only composition", () => {
file: { pins: { session: "auto-fallback" as never } },
appVersion: "1.0.0",
})
expect(resolved.session).toEqual({ mode: "legacy", reason: "release-default" })
expect(resolved.session).toEqual({ mode: "alpha", reason: "release-default" })
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,16 @@ describe("REQ-087 §5 timeline 关键机制锚点", () => {
})

describe("REQ-087 §6 通道结论锁定(REQ-088 C1 合法窄通道,不可静默漂移)", () => {
test("session surface 发布默认仍是 legacy(REQ-088 未交付,flags-off 零变化)", () => {
expect(SURFACE_RELEASE_STATES.session).toBe("legacy")
test("REQ-088 T5:session defaults to alpha and the spike host/localStorage gate are no longer mounted", () => {
expect(SURFACE_RELEASE_STATES.session).toBe("alpha")
const rendererIndex = readFileSync(join(import.meta.dir, "../../index.tsx"), "utf8")
const workspace = readFileSync(
join(import.meta.dir, "../session-workspace/alpha-session-workspace.tsx"),
"utf8",
)
expect(rendererIndex).not.toContain("SessionSpikeHost")
expect(workspace).not.toContain("isSessionSpikeEnabled")
expect(workspace).not.toContain("ALPHA_SESSION_SPIKE")
})
test("@opencode-ai/app exports map = 既有六条 + ./surface/session 窄导出(C1,ADR-027 修订)", () => {
const pkg = JSON.parse(readFileSync(join(APP, "package.json"), "utf8")) as { exports: Record<string, string> }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// R2 chrome 一律 alpha 命名空间(a-swk-* 类 + data-alpha-* 属性),不与上游锚点同名;
// R7 三个 takeover 不移进叶/workspace 内(挂载通道零耦合,另有 takeover-adapter-coexistence
// 测试①钉 children 通道;此处钉 workspace 侧不 import)。
// 另钉:双闸工厂形态、SurfaceBoundary 组合、跨 server 引导的 rethrow 纪律、窄导出唯一消费、
// 另钉:单一 release-state seam、SurfaceBoundary 组合、跨 server 引导的 rethrow 纪律、窄导出唯一消费、
// C4 携带项③ 的侧栏预热接线。断言红 = 红线破坏,回 T6 审计 §3 重评,不得只改测试。

import { describe, expect, test } from "bun:test"
Expand Down Expand Up @@ -88,15 +88,38 @@ describe("R7 / Stage C-1:takeover 不进 workspace;本期不自渲染 AlphaCompo
})
})

describe("双闸 + seam 契约(发布态本期保持 legacy,T5 才升级)", () => {
test("工厂:localStorage 闸关 ⇒ undefined(= surfaces.session 未注入,seam 走上游默认叶)", () => {
expect(tsx).toContain("if (!isSessionSpikeEnabled()) return undefined")
describe("single release-state seam contract", () => {
test("factory returns a preloadable component without localStorage", () => {
const result = Bun.spawnSync({
cmd: [
process.execPath,
"--eval",
`import { mock } from "bun:test";
mock.module("react/jsx-dev-runtime", () => ({ Fragment: Symbol(), jsxDEV: () => ({}) }));
mock.module("@solidjs/router", () => ({ useLocation: () => ({ pathname: "" }), useNavigate: () => () => {} }));
const module = await import("./src/renderer/alpha-ui/session-workspace/alpha-session-workspace.tsx");
const component = module.alphaSessionWorkspaceSurface();
console.log(JSON.stringify({ localStorage: typeof globalThis.localStorage, component: typeof component, preload: typeof component.preload }));`,
],
cwd: join(here, "../../../.."),
env: process.env,
})
expect(result.stderr.toString()).toBe("")
expect(JSON.parse(result.stdout.toString())).toEqual({
localStorage: "undefined",
component: "function",
preload: "function",
})
expect(tsx).not.toContain("isSessionSpikeEnabled")
expect(tsx).not.toContain("ALPHA_SESSION_SPIKE")
})

test("env-override 闸:index.tsx 只在 resolved.session.mode === alpha 时调用工厂", () => {
test("index.tsx calls the factory only when resolved.session.mode is alpha", () => {
expect(rendererIndex).toContain(`if (resolved?.session.mode !== "alpha") return undefined`)
expect(rendererIndex).toContain(`return alphaSessionWorkspaceSurface()`)
expect(rendererIndex).toContain(`const session = productionRoutes.session.mount(resolved)`)
expect(rendererIndex.match(/return alphaSessionWorkspaceSurface\(\)/g)).toHaveLength(1)
expect(rendererIndex).not.toContain("SessionSpikeHost")
})

test("SurfaceBoundary 语义保持(C4 真机实证链路):workspace 最外层即 surface 边界", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// AlphaSessionWorkspace — REQ-088 T2(Issue #181):session surface 的正式 Alpha 外框。
// REQ-087 spike 的 surface 侧原型(session-spike-host.tsx 旧 sessionSpikeSurface)在此转正;
// 容器侧探针仍留在 session-spike/(T7 统一清理)。
// 未挂载的容器侧探针模块仍留在 session-spike/(T7 统一清理)。
//
// 结构与纪律(变更任何一条先回 spike 报告 §6 / T6 审计 §3 重评):
// - 叶经 C1 合法窄导出(app 包 exports 的 ./surface/session 子路径,ADR-027 修订、
Expand All @@ -9,8 +9,8 @@
// 全部保持上游默认生命周期,外框零 upstream context(只读版本化路由 ABI)。
// - surface override 与默认叶 XOR(app.tsx `props.surfaces?.session ?? Session`),结构上不存在
// 双挂载;SurfaceBoundary 兜致命 render 错误 → 留在 Alpha 区域并进入 Recovery。
// - 双闸:主进程 env-override `ALPHA_SURFACE_SESSION=alpha` + localStorage 闸(spike-flag),
// 任一闸关 ⇒ 工厂返回 undefined = seam 走上游默认叶,零变化。发布态本期保持 legacy(T5 才升级)。
// - 单一挂载条件:调用方仅在 resolved.session.mode === "alpha" 时注入本工厂返回的组件;
// legacy 解析结果不注入,由 seam 走上游默认叶。
// - 宿主红线(T6 审计 §3;alpha-session-workspace.test.ts 钉死):
// R1 活叶包裹保持普通流(flex:1 + min-height:0,不隐藏、不脱流)——takeover 的
// offsetParent 可见性口径依赖于此;
Expand All @@ -30,7 +30,6 @@ import { useLocation, useNavigate } from "@solidjs/router"
import type { MaybePreloadableComponent } from "@opencode-ai/app"
import { hrefFor, parseRoute } from "../../../shared/route-manifest"
import { SurfaceBoundary } from "../surface-boundary"
import { isSessionSpikeEnabled } from "../session-spike/spike-flag"
import { isCrossServerSessionError, workspaceContextOf } from "./session-workspace-core"
import { t } from "../../i18n"
import "./session-workspace.css"
Expand Down Expand Up @@ -129,13 +128,10 @@ export function AlphaSessionWorkspace() {
}

/**
* surface 工厂:双闸(localStorage 闸 + 主进程 ALPHA_SURFACE_SESSION=alpha,后者由调用方的
* resolved.session.mode === "alpha" 前置保证)全开才返回组件,否则 undefined ⇒ surfaces.session
* 维持未注入,seam 走上游默认叶(严格零变化)。localStorage 闸沿用 spike-flag(键
* ALPHA_SESSION_SPIKE)——T5 发布态阶梯前不改口径,T7 统一裁决其去留。
* surface 工厂无条件返回 Alpha 组件。唯一挂载条件由调用方的
* resolved.session.mode === "alpha" 保证;legacy 解析结果保持上游默认叶。
*/
export function alphaSessionWorkspaceSurface(): MaybePreloadableComponent | undefined {
if (!isSessionSpikeEnabled()) return undefined
export function alphaSessionWorkspaceSurface(): MaybePreloadableComponent {
const Comp: MaybePreloadableComponent = () => <AlphaSessionWorkspace />
// 与 seam 的 preload 契约对齐(app.tsx `preload: () => Leaf.preload?.()`)。
Comp.preload = preloadSessionLeaf
Expand Down
6 changes: 3 additions & 3 deletions packages/ui-mac/src/renderer/dev/surface-map-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("surface map model", () => {
const resolved = {
home: { mode: "alpha", reason: "release-default" },
newSession: { mode: "alpha", reason: "release-default" },
session: { mode: "legacy", reason: "release-default" },
session: { mode: "alpha", reason: "release-default" },
} as const

expect(surfaceRuntimeState(home, resolved)).toEqual({
Expand All @@ -28,9 +28,9 @@ describe("surface map model", () => {
release: "alpha",
})
expect(surfaceRuntimeState(session, resolved)).toEqual({
mode: "Legacy",
mode: "Alpha",
detail: "release-default",
release: "legacy",
release: "alpha",
})
expect(surfaceRuntimeState(home, null).detail).toBe("resolver-error fallback")
})
Expand Down
9 changes: 1 addition & 8 deletions packages/ui-mac/src/renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import { SurfaceBoundary } from "./alpha-ui/surface-boundary"
import { RuntimeRecoveryHost } from "./alpha-ui/RuntimeRecoveryHost"
import { UpstreamDialogHost } from "./alpha-ui/UpstreamDialogHost"
import type { ResolvedSurfaces } from "../shared/alpha-surfaces"
import { SessionSpikeHost } from "./alpha-ui/session-spike/session-spike-host" // REQ-087 spike 探针(默认恒 off,T7 清理)
import { alphaSessionWorkspaceSurface } from "./alpha-ui/session-workspace/alpha-session-workspace" // REQ-088 T2
import { AlphaOnboarding } from "./alpha-ui/AlphaOnboarding"
import { AlphaSettings } from "./alpha-ui/settings"
Expand Down Expand Up @@ -497,9 +496,7 @@ render(() => {
const session = productionRoutes.session.mount(resolved)
if (home) surfaces[productionRoutes.home.surface] = home
if (newSession) surfaces[productionRoutes["new-session"].surface] = newSession
// REQ-088 T2:session 叶经 AlphaSessionWorkspace 正式外框注入(SurfaceBoundary + 窄导出叶)。
// 发布态本期仍 legacy(T5 才升级);双闸(ALPHA_SURFACE_SESSION=alpha env-override +
// localStorage ALPHA_SESSION_SPIKE)未全开时返回 undefined = 上游默认叶,零变化。
// REQ-088 T5:resolved session mode 是唯一挂载条件;legacy 保持上游默认叶。
if (session) surfaces[productionRoutes.session.surface] = session
return surfaces
})
Expand Down Expand Up @@ -557,10 +554,6 @@ render(() => {
</AlphaBoundary>
<RecoverySurface />
<DevSurfaceMapInspector resolved={resolvedSurfaces.latest} />
{/* REQ-087 spike 容器侧探针:flag off ⇒ 恒 null(session-spike/spike-flag.ts) */}
<AlphaBoundary name="SessionSpikeHost">
<SessionSpikeHost />
</AlphaBoundary>
</AppInterface>
)}
</Show>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-mac/src/shared/alpha-surfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type ResolvedSurfaces = Record<SurfaceId, ResolvedSurface>
export const SURFACE_RELEASE_STATES: Record<SurfaceId, SurfaceReleaseState> = {
home: "alpha",
newSession: "alpha",
session: "legacy", // REQ-088 not yet delivered
session: "alpha",
}

export const SURFACE_ABI_VERSION = 1
Loading