diff --git a/.changeset/restore-design-tui.md b/.changeset/restore-design-tui.md index aaf16ba3b5b8..2c63acd63df2 100644 --- a/.changeset/restore-design-tui.md +++ b/.changeset/restore-design-tui.md @@ -1,5 +1,5 @@ --- -"@reddb-io/redcode": patch +"@reddb-io/redcode": minor --- -Restore Design as the cyan third mode in the fullscreen TUI, alongside Build and Plan. Keep prototyping, browser feedback, image assets, SVG-to-GIF exports and approved Plan handoffs attached to the same conversation. Reopen pre-0.22 prototypes without overwriting their sources, and retain the new Design revision and rendering services. +Restore Design as the cyan third mode in the fullscreen TUI, alongside Build and Plan. Keep prototyping, browser feedback, image assets, SVG-to-GIF exports and approved Plan handoffs attached to the same conversation. Reopen pre-0.22 prototypes without overwriting their sources, and retain the new Design revision and rendering services. Queued browser feedback respects session interruption and requires an explicit retry to resume. diff --git a/packages/redcode/src/design/feedback.ts b/packages/redcode/src/design/feedback.ts index fc1b778b2a06..ef76ef586de6 100644 --- a/packages/redcode/src/design/feedback.ts +++ b/packages/redcode/src/design/feedback.ts @@ -2,7 +2,7 @@ import { EventV2Bridge } from "@/event-v2-bridge" import { SessionEvent } from "@reddb-io/redcode-core/session/event" export * as DesignFeedback from "./feedback" -import { Context, Deferred, Effect, Exit, Layer, Schedule, Scope, Semaphore, Schema, Option } from "effect" +import { Context, Deferred, Effect, Exit, Layer, Schedule, Scope, Semaphore, Schema } from "effect" import { eq } from "drizzle-orm" import { Design } from "@reddb-io/redcode-schema/design" import { DesignStore } from "@reddb-io/redcode-core/design/store" @@ -27,9 +27,9 @@ const make = Effect.gen(function* () { const off = yield* events.listen((event) => Effect.sync(() => { if (event.type !== SessionEvent.Turn.Ended.type || event.location?.directory !== instance.directory) return - const data = Schema.decodeUnknownOption(SessionEvent.Turn.Ended.data)(event.data) - if (Option.isNone(data) || data.value.finished) return - const queue = queues.get(data.value.sessionID) + // Live events already contain decoded DateTime values. Validate their type, not the wire representation. + if (!Schema.is(Schema.toType(SessionEvent.Turn.Ended.data))(event.data) || event.data.finished) return + const queue = queues.get(event.data.sessionID) if (queue) queue.interruption++ }), ) diff --git a/packages/redcode/test/design/tui-studio.test.ts b/packages/redcode/test/design/tui-studio.test.ts index 77df2a8f3f6f..9ec35804769b 100644 --- a/packages/redcode/test/design/tui-studio.test.ts +++ b/packages/redcode/test/design/tui-studio.test.ts @@ -1,6 +1,7 @@ import { SessionMessage } from "@reddb-io/redcode-schema/session-message" import { DesignFeedback } from "../../src/design/feedback" import { SessionStatus } from "../../src/session/status" +import { SessionPrompt } from "../../src/session/prompt" import { EventV2Bridge } from "../../src/event-v2-bridge" import { SessionEvent } from "@reddb-io/redcode-core/session/event" import { DesignLegacy } from "../../src/design/legacy" @@ -26,6 +27,7 @@ const it = testEffect( DesignStudio.node, DesignFeedback.node, SessionStatus.node, + SessionPrompt.node, EventV2Bridge.node, Agent.node, Session.node, @@ -179,6 +181,13 @@ it.instance("queued browser feedback does not restart an interrupted TUI session const status = yield* SessionStatus.Service const events = yield* EventV2Bridge.Service const session = yield* sessions.create({ agent: "design" }) + const prompt = yield* SessionPrompt.Service + const initial = yield* prompt.prompt({ + sessionID: session.id, + agent: "design", + noReply: true, + parts: [{ type: "text", text: "Create a settings prototype" }], + }) const document = yield* studio.use( Effect.gen(function* () { const store = yield* DesignStore.Service @@ -210,8 +219,22 @@ it.instance("queued browser feedback does not restart an interrupted TUI session finished: false, }) yield* status.set(session.id, { type: "idle" }) - yield* Effect.sleep("300 millis") - expect(yield* sessions.messages({ sessionID: session.id })).toHaveLength(0) + yield* Effect.sleep("2 seconds") + expect((yield* sessions.messages({ sessionID: session.id })).map((message) => message.info.id)).toEqual([ + initial.info.id, + ]) expect(yield* status.get(session.id)).toEqual({ type: "idle" }) }), + { + config: { + model: "fixture/fixture", + provider: { + fixture: { + npm: "@ai-sdk/openai-compatible", + models: { fixture: { name: "Fixture", limit: { context: 100000, output: 4096 } } }, + options: { apiKey: "fixture", baseURL: "http://127.0.0.1:1/v1" }, + }, + }, + }, + }, )