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
17 changes: 17 additions & 0 deletions .changeset/update-preserves-surface-kind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"sideshow": patch
---

`sideshow update` now preserves the surface kind instead of always treating
content as HTML. A markdown post updated with `sideshow update <id> file.md`
stays markdown; a code post stays code with its language preserved; diffs keep
their layout, terminals keep their cols — every kind-specific field is carried
forward. The same fix applies to all text-content surface kinds (html,
markdown, code, diff, terminal, mermaid, json).

Implemented as a new `PATCH /api/posts/:id` endpoint that accepts raw
`content` (plus optional `title` and `kits`) and slots it into the existing
surface's kind, rather than requiring the caller to construct the full typed
surface object. Multi-surface posts return a 400 for now — surface-level
targeting is a future addition. The existing `PUT` full-replacement API is
unchanged.
13 changes: 8 additions & 5 deletions bin/sideshow.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,13 +1074,16 @@ const commands = {
});
const id = positionals[0];
if (!id) fail("usage: sideshow update <id> <file|->");
const part = { kind: "html", html: readContent(positionals[1]) };
const body = { title: flags.title };
if (positionals[1] !== undefined) {
body.content = readContent(positionals[1]);
}
const kits = normalizeKits(flags.kit);
if (kits) part.kits = kits;
if (kits) body.kits = kits;
outSurface(
await api(`/api/surfaces/${id}`, {
method: "PUT",
body: JSON.stringify({ parts: [part], title: flags.title }),
await api(`/api/posts/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
);
},
Expand Down
74 changes: 74 additions & 0 deletions server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,80 @@ export function createApp({
app.put("/api/posts/:id", revise); // canonical alias
app.put("/api/snippets/:id", revise); // legacy alias

// Content-only update: accepts raw content and slots it into the existing
// surface's kind, preserving extra fields (language, cols, layout, etc.).
// Only single-surface posts for now; multi-surface needs --surface N.
const CONTENT_FIELD: Record<string, string> = {
html: "html",
markdown: "markdown",
mermaid: "mermaid",
diff: "patch",
terminal: "text",
code: "code",
json: "data",
};
app.patch("/api/posts/:id", async (c: any) => {
const body = await c.req.json().catch(() => null);
if (!body) return c.json({ error: "invalid JSON body" }, 400);
const { content, title, kits } = body;
if (content === undefined && title === undefined) {
return c.json({ error: "provide content and/or title" }, 400);
}
const existing = await store.getPost(c.req.param("id"));
if (!existing) return c.json({ error: "post not found" }, 404);
// Build the updated surfaces array.
let parts: Surface[] | undefined;
if (content !== undefined) {
if (typeof content !== "string") {
return c.json({ error: '"content" must be a string' }, 400);
}
if (existing.surfaces.length > 1) {
return c.json(
{
error:
"content update not supported for multi-surface posts; use PUT with a full surfaces array",
},
400,
);
}
const surface = existing.surfaces[0];
const field = CONTENT_FIELD[surface.kind];
if (!field) {
return c.json({ error: `content update not supported for ${surface.kind} surfaces` }, 400);
}
// For json surfaces, parse the content string into a value.
let value: unknown = content;
if (surface.kind === "json") {
try {
value = JSON.parse(content);
} catch {
return c.json({ error: "content is not valid JSON" }, 400);
}
}
// Clone the existing surface, replace the content field, optionally update kits.
const updated: Surface =
surface.kind === "html"
? {
...surface,
html: value as string,
...(kits !== undefined && { kits: Array.isArray(kits) ? kits : undefined }),
}
: ({ ...surface, [field]: value } as Surface);
const parsed = await validateSurfaces([updated]);
if (!parsed.ok) return c.json({ error: parsed.error }, 400);
parts = parsed.parts;
}
const result = await reviseSurface(c.req.param("id"), {
parts,
title: typeof title === "string" ? title : undefined,
});
if ("error" in result) return c.json({ error: result.error }, result.status);
return c.json({
...writeResult(result.surface),
...(result.userFeedback && { userFeedback: result.userFeedback }),
});
});

const remove = async (c: any) => {
const surface = await store.getPost(c.req.param("id"));
if (!surface) return c.json({ error: "surface not found" }, 404);
Expand Down
Loading
Loading