From f549fde874749d6c5c298e040064c4dde0b50546 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Apr 2026 15:07:46 -0400 Subject: [PATCH 1/2] test(app): emit junit artifacts for playwright (#20732) --- .github/workflows/test.yml | 4 +++- packages/app/playwright.config.ts | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03c0741b52..803093fc59 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -105,15 +105,17 @@ jobs: run: bun --cwd packages/app test:e2e:local env: CI: true + PLAYWRIGHT_JUNIT_OUTPUT: e2e/junit-${{ matrix.settings.name }}.xml timeout-minutes: 30 - name: Upload Playwright artifacts - if: failure() + if: always() uses: actions/upload-artifact@v4 with: name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }} if-no-files-found: ignore retention-days: 7 path: | + packages/app/e2e/junit-*.xml packages/app/e2e/test-results packages/app/e2e/playwright-report diff --git a/packages/app/playwright.config.ts b/packages/app/playwright.config.ts index 2667b89a1c..e9fb1cfe4e 100644 --- a/packages/app/playwright.config.ts +++ b/packages/app/playwright.config.ts @@ -7,6 +7,11 @@ const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096" const command = `bun run dev -- --host 0.0.0.0 --port ${port}` const reuse = !process.env.CI const workers = Number(process.env.PLAYWRIGHT_WORKERS ?? (process.env.CI ? 5 : 0)) || undefined +const reporter = [["html", { outputFolder: "e2e/playwright-report", open: "never" }], ["line"]] as const + +if (process.env.PLAYWRIGHT_JUNIT_OUTPUT) { + reporter.push(["junit", { outputFile: process.env.PLAYWRIGHT_JUNIT_OUTPUT }]) +} export default defineConfig({ testDir: "./e2e", @@ -19,7 +24,7 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers, - reporter: [["html", { outputFolder: "e2e/playwright-report", open: "never" }], ["line"]], + reporter, webServer: { command, url: baseURL, From 5e1b5135276294e3740d4d0ca560b53b5563f582 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Apr 2026 15:11:23 -0400 Subject: [PATCH 2/2] refactor(todo): effectify session todo (#20595) --- packages/opencode/src/session/todo.ts | 90 +++++++++++++++++++-------- packages/opencode/src/tool/todo.ts | 2 +- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 02ad0d3b33..8bb5dc522a 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -1,6 +1,8 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" +import { makeRuntime } from "@/effect/run-service" import { SessionID } from "./schema" +import { Effect, Layer, ServiceMap } from "effect" import z from "zod" import { Database, eq, asc } from "../storage/db" import { TodoTable } from "./session.sql" @@ -25,33 +27,69 @@ export namespace Todo { ), } - export function update(input: { sessionID: SessionID; todos: Info[] }) { - Database.transaction((db) => { - db.delete(TodoTable).where(eq(TodoTable.session_id, input.sessionID)).run() - if (input.todos.length === 0) return - db.insert(TodoTable) - .values( - input.todos.map((todo, position) => ({ - session_id: input.sessionID, - content: todo.content, - status: todo.status, - priority: todo.priority, - position, - })), - ) - .run() - }) - Bus.publish(Event.Updated, input) + export interface Interface { + readonly update: (input: { sessionID: SessionID; todos: Info[] }) => Effect.Effect + readonly get: (sessionID: SessionID) => Effect.Effect } - export function get(sessionID: SessionID) { - const rows = Database.use((db) => - db.select().from(TodoTable).where(eq(TodoTable.session_id, sessionID)).orderBy(asc(TodoTable.position)).all(), - ) - return rows.map((row) => ({ - content: row.content, - status: row.status, - priority: row.priority, - })) + export class Service extends ServiceMap.Service()("@opencode/SessionTodo") {} + + export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const bus = yield* Bus.Service + + const update = Effect.fn("Todo.update")(function* (input: { sessionID: SessionID; todos: Info[] }) { + yield* Effect.sync(() => + Database.transaction((db) => { + db.delete(TodoTable).where(eq(TodoTable.session_id, input.sessionID)).run() + if (input.todos.length === 0) return + db.insert(TodoTable) + .values( + input.todos.map((todo, position) => ({ + session_id: input.sessionID, + content: todo.content, + status: todo.status, + priority: todo.priority, + position, + })), + ) + .run() + }), + ) + yield* bus.publish(Event.Updated, input) + }) + + const get = Effect.fn("Todo.get")(function* (sessionID: SessionID) { + const rows = yield* Effect.sync(() => + Database.use((db) => + db + .select() + .from(TodoTable) + .where(eq(TodoTable.session_id, sessionID)) + .orderBy(asc(TodoTable.position)) + .all(), + ), + ) + return rows.map((row) => ({ + content: row.content, + status: row.status, + priority: row.priority, + })) + }) + + return Service.of({ update, get }) + }), + ) + + const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) + const { runPromise } = makeRuntime(Service, defaultLayer) + + export async function update(input: { sessionID: SessionID; todos: Info[] }) { + return runPromise((svc) => svc.update(input)) + } + + export async function get(sessionID: SessionID) { + return runPromise((svc) => svc.get(sessionID)) } } diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index 53b687b1d1..a5e56cb23e 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -16,7 +16,7 @@ export const TodoWriteTool = Tool.define("todowrite", { metadata: {}, }) - Todo.update({ + await Todo.update({ sessionID: ctx.sessionID, todos: params.todos, })