From e5933471f2e3e3f2a75d248e20ada8e0c87da4b6 Mon Sep 17 00:00:00 2001
From: sigma-andex <77549848+sigma-andex@users.noreply.github.com>
Date: Fri, 20 Mar 2026 09:37:04 +0000
Subject: [PATCH] Various improvements
---
apps/backend/src/services/generate/prompts.ts | 24 ++++++++---
apps/backend/src/services/generate/service.ts | 43 +++++++++++--------
apps/backend/src/services/generate/types.ts | 2 +
apps/webpage/src/main.ts | 1 +
4 files changed, 46 insertions(+), 24 deletions(-)
diff --git a/apps/backend/src/services/generate/prompts.ts b/apps/backend/src/services/generate/prompts.ts
index ad1bdc5..b8ae102 100644
--- a/apps/backend/src/services/generate/prompts.ts
+++ b/apps/backend/src/services/generate/prompts.ts
@@ -1,6 +1,6 @@
import type { GenerationError } from "./errors.js";
-import type { Patch } from "../vdom/index.js";
import type { Action } from "@cuttlekit/common/client";
+import type { LLMResponse } from "./types.js";
export const MAX_RETRY_ATTEMPTS = 3;
@@ -20,6 +20,7 @@ COMPONENTS: Register reusable UI components with define, then use custom tags in
- Use custom tags in patches: {"selector":"#root","append":""}
- Define components BEFORE first use. One define per line.
- Components persist across requests — do NOT re-emit define unless restyling. Check [COMPONENTS] for already-defined tags.
+- When creating 3+ similar elements, ALWAYS define a component first. Repeated inline HTML wastes context — components keep page state compact.
JSON ESCAPING: Use single quotes for HTML attributes to avoid escaping.
CORRECT: {"html":"
"}
@@ -50,6 +51,7 @@ INTERACTIVITY - NO JavaScript/onclick (won't work):
Use " for JSON in data-action-data. Input values auto-sent with actions.
ACTIONS: Update data only — don't redesign or restyle the UI. Exception: inherently visual actions (color pickers, theme toggles).
+RESTYLING: Visual-only changes must preserve existing data — never silently replace content.
IDs REQUIRED: All interactive/dynamic elements need unique id. Containers: id="todo-list". Items: id="todo-1". Buttons: id="add-btn".
@@ -105,12 +107,22 @@ export const buildSystemPrompt = (deps?: PackageInfo[]): string =>
// Build corrective prompt for retry after error
export const buildCorrectivePrompt = (
error: GenerationError,
- successfulPatches: readonly Patch[] = [],
+ successfulOps: readonly LLMResponse[] = [],
currentHtml?: string,
): string => {
+ const defines = successfulOps.filter((o) => o.op === "define");
+ const patches = successfulOps.flatMap((o) =>
+ o.op === "patches" ? o.patches : [],
+ );
+
+ const defined =
+ defines.length > 0
+ ? `\nDEFINED: ${defines.map((d) => d.tag).join(", ")} (already registered — do NOT re-emit)`
+ : "";
+
const applied =
- successfulPatches.length > 0
- ? `\nAPPLIED: ${JSON.stringify(successfulPatches)}\nContinue from here.`
+ patches.length > 0
+ ? `\nAPPLIED: ${JSON.stringify(patches)}\nContinue from here.`
: "";
const pageState = currentHtml
@@ -120,11 +132,11 @@ export const buildCorrectivePrompt = (
if (error._tag === "JsonParseError") {
return `${pageState}JSON ERROR: ${error.message}
Bad: ${error.line.slice(0, 100)}
-Fix: valid JSONL, one JSON/line, single quotes in HTML attrs${applied}`;
+Fix: valid JSONL, one JSON/line, single quotes in HTML attrs${defined}${applied}`;
}
return `${pageState}PATCH ERROR "${error.patch.selector}": ${error.reason}
-Fix: selector must exist, use #id only${applied}`;
+Fix: selector must exist, use #id only${defined}${applied}`;
};
// Build a compact, LLM-readable description of a single action.
diff --git a/apps/backend/src/services/generate/service.ts b/apps/backend/src/services/generate/service.ts
index 22487a4..695406b 100644
--- a/apps/backend/src/services/generate/service.ts
+++ b/apps/backend/src/services/generate/service.ts
@@ -4,13 +4,14 @@ import { z } from "zod";
import type { LanguageModelConfig } from "@cuttlekit/common/server";
import { MemoryService, type MemorySearchResult } from "../memory/index.js";
import { accumulateLinesWithFlush } from "../../stream/utils.js";
-import { PatchValidator, renderCETree, getCompactHtmlFromCtx, type Patch, type ValidationContext } from "../vdom/index.js";
+import { PatchValidator, renderCETree, getCompactHtmlFromCtx, type ValidationContext } from "../vdom/index.js";
import { ModelRegistry } from "../model-registry.js";
import { loadAppConfig } from "../app-config.js";
import {
PatchSchema,
LLMResponseSchema,
JsonParseError,
+ type LLMResponse,
type UnifiedResponse,
type UnifiedGenerateOptions,
type Message,
@@ -255,7 +256,7 @@ export class GenerateService extends Effect.Service
()(
validationCtx: ValidationContext,
usageRef: Ref.Ref,
ttftRef: Ref.Ref,
- patchesRef: Ref.Ref,
+ opsRef: Ref.Ref,
modeRef: Ref.Ref<"patches" | "full">,
attempt: number,
modelConfig: LanguageModelConfig,
@@ -277,15 +278,13 @@ export class GenerateService extends Effect.Service()(
requestTools,
),
- // Track successful patches, mode, and log
+ // Track successful operations, mode, and log
Stream.tap((response) =>
Effect.gen(function* () {
- if (response.op === "patches") {
- yield* Ref.update(patchesRef, (ps) => [
- ...ps,
- ...response.patches,
- ]);
- } else if (response.op === "full") {
+ if (response.op !== "stats") {
+ yield* Ref.update(opsRef, (ops) => [...ops, response]);
+ }
+ if (response.op === "full") {
yield* Ref.set(modeRef, "full");
}
yield* Effect.log(`[Attempt ${attempt}] Emitting response`, {
@@ -304,14 +303,18 @@ export class GenerateService extends Effect.Service()(
const genError = error as GenerationError;
return Stream.unwrap(
Effect.gen(function* () {
- const successfulPatches = yield* Ref.get(patchesRef);
- yield* Ref.set(patchesRef, []); // Reset for next attempt
+ const successfulOps = yield* Ref.get(opsRef);
+ // Keep defines (persist in registry across retries), reset patches/full
+ yield* Ref.set(
+ opsRef,
+ successfulOps.filter((o) => o.op === "define"),
+ );
const compactHtml = yield* getCompactHtmlFromCtx(validationCtx);
yield* Effect.log(
`[Attempt ${attempt}] ${genError._tag}, retrying...`,
{
error: genError.message,
- successfulPatches: successfulPatches.length,
+ successfulOps: successfulOps.length,
},
);
@@ -324,7 +327,7 @@ export class GenerateService extends Effect.Service()(
role: "user",
content: buildCorrectivePrompt(
genError,
- successfulPatches,
+ successfulOps,
compactHtml,
),
},
@@ -332,7 +335,7 @@ export class GenerateService extends Effect.Service()(
validationCtx,
usageRef,
ttftRef,
- patchesRef,
+ opsRef,
modeRef,
attempt + 1,
modelConfig,
@@ -507,7 +510,7 @@ export class GenerateService extends Effect.Service()(
// Create Refs to track state across retries
const usageRef = yield* Ref.make([]);
const ttftRef = yield* Ref.make(0);
- const patchesRef = yield* Ref.make([]);
+ const opsRef = yield* Ref.make([]);
const modeRef = yield* Ref.make<"patches" | "full">("patches");
const startTime = yield* DateTime.now;
@@ -517,7 +520,7 @@ export class GenerateService extends Effect.Service()(
validationCtx,
usageRef,
ttftRef,
- patchesRef,
+ opsRef,
modeRef,
0,
modelConfig,
@@ -577,7 +580,11 @@ export class GenerateService extends Effect.Service()(
});
const mode = yield* Ref.get(modeRef);
- const patches = yield* Ref.get(patchesRef);
+ const ops = yield* Ref.get(opsRef);
+ const patchCount = ops.reduce(
+ (n, o) => n + (o.op === "patches" ? o.patches.length : 0),
+ 0,
+ );
const ttft = yield* Ref.get(ttftRef);
return {
@@ -585,7 +592,7 @@ export class GenerateService extends Effect.Service()(
cacheRate: Math.round(cacheRate),
tokensPerSecond: Math.round(tokensPerSecond),
mode,
- patchCount: patches.length,
+ patchCount,
ttft: Math.round(ttft),
ttc: Math.round(elapsedMs),
};
diff --git a/apps/backend/src/services/generate/types.ts b/apps/backend/src/services/generate/types.ts
index 92e0c4c..1da7088 100644
--- a/apps/backend/src/services/generate/types.ts
+++ b/apps/backend/src/services/generate/types.ts
@@ -43,6 +43,8 @@ export const LLMResponseSchema = z.union([
DefineOpSchema,
]);
+export type LLMResponse = z.infer;
+
// Full response schema includes stats (generated by code, not LLM)
export const UnifiedResponseSchema = z.union([
LLMResponseSchema,
diff --git a/apps/webpage/src/main.ts b/apps/webpage/src/main.ts
index 33fa4c2..0134886 100644
--- a/apps/webpage/src/main.ts
+++ b/apps/webpage/src/main.ts
@@ -182,6 +182,7 @@ const app = {
`TTC ${ttc}`,
`${s.tokensPerSecond} tok/s`,
`${s.cacheRate}% cache`,
+ `${s.patchCount} patches`,
].join(" · ");
statsEl.style.display = "flex";
},