Files
learnharness/app/api/chats/[id]/artifact-ui/route.ts
T
amosblomqvist 34f498b128 artifacts: the height ratchet fixed, invert switch persisted + restyled (release polish 7.2)
- bridge measures the BODY (scrollHeight + rect + margins) instead of the
  viewport-clamped documentElement — documentElement.scrollHeight latches
  at the parent's own height, so demos could grow but never shrink on
  window resize (owner-reported); smoke: 1036 → 2386 → 1036 px
- invert now PERSISTS as details.ui.inverted on the steer record, written
  by the silent artifact-ui route (dismissal pattern: no wake, no SSE) and
  seeded through the projection (SubagentArtifactData.ui); survives
  navigation + reload, never enters teacher context or exports
- invert restyled as the house Toggle switch (settings controls) on both
  cards; demo header is the caption grammar: 'INTERACTIVE DEMO — title'
  left (title normal-case, same size/tracking), switch + download far
  right; figure header: label left, switch/download/open far right

1439 tests green (route + pure-seam pins added); live smoke passed
2026-10-07 19:13:24 +03:00

42 lines
1.9 KiB
TypeScript

/**
* THE ARTIFACT-UI ROUTE (owner, 2026-10-07): persists the learner's invert
* switch onto a demo/figure steer message (details.ui.inverted) so it
* survives session navigation and restarts. Modeled on the dismissal
* route's SILENCE: no wake, no agent, no tokens, no SSE — the record is
* the only state that moves, and the client already holds the toggled
* state optimistically (no refetch churn).
*
* Guards: 404 unknown chat, 400 for a body that isn't
* { input: { messageId, inverted } } or targets no demo/visual steer.
*/
import { setSteerUiInverted, type SteerDetails } from "@/src/core";
import { getChat, stores } from "@/src/server";
export async function POST(req: Request, ctx: { params: Promise<{ id: string }> }): Promise<Response> {
const { id } = await ctx.params;
const chat = getChat(id);
if (!chat) return Response.json({ error: "Unknown chat" }, { status: 404 });
const body = (await req.json().catch(() => null)) as { input?: unknown } | null;
const input = body?.input as { messageId?: unknown; inverted?: unknown } | null | undefined;
if (
!input ||
typeof input !== "object" ||
typeof input.messageId !== "string" ||
typeof input.inverted !== "boolean"
) {
return Response.json({ error: "input must be { messageId: string, inverted: boolean }" }, { status: 400 });
}
// Honesty: the target must actually be a demo/figure steer.
const target = chat.messages.find((m) => m.id === input.messageId);
const details = target ? ((target as { details?: unknown }).details as Partial<SteerDetails> | undefined) : undefined;
if (!details || (details.kind !== "demo" && details.kind !== "visual")) {
return Response.json({ error: "No demo/figure steer with that message id" }, { status: 400 });
}
const messages = setSteerUiInverted(chat.messages, input.messageId, input.inverted);
stores.chatStore.saveMessages(id, messages);
return Response.json({ ok: true });
}