@cfbender/cesium 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/prompt/system-fragment.md +73 -8
- package/src/render/annotate-frozen.ts +90 -0
- package/src/render/blocks/render.ts +20 -0
- package/src/render/blocks/renderers/callout.ts +3 -2
- package/src/render/blocks/renderers/code.ts +17 -2
- package/src/render/blocks/renderers/compare-table.ts +3 -2
- package/src/render/blocks/renderers/diagram.ts +3 -2
- package/src/render/blocks/renderers/diff.ts +23 -9
- package/src/render/blocks/renderers/hero.ts +3 -2
- package/src/render/blocks/renderers/kv.ts +3 -2
- package/src/render/blocks/renderers/list.ts +5 -4
- package/src/render/blocks/renderers/pill-row.ts +3 -2
- package/src/render/blocks/renderers/prose.ts +8 -2
- package/src/render/blocks/renderers/raw-html.ts +8 -2
- package/src/render/blocks/renderers/risk-table.ts +3 -2
- package/src/render/blocks/renderers/section.ts +4 -2
- package/src/render/blocks/renderers/timeline.ts +3 -2
- package/src/render/blocks/renderers/tldr.ts +3 -2
- package/src/render/client-js.ts +804 -6
- package/src/render/critique.ts +5 -335
- package/src/render/theme.ts +431 -6
- package/src/render/validate.ts +353 -97
- package/src/render/wrap.ts +67 -9
- package/src/server/api.ts +162 -3
- package/src/storage/index-gen.ts +4 -2
- package/src/storage/mutate.ts +433 -27
- package/src/tools/annotate.ts +336 -0
- package/src/tools/ask.ts +2 -6
- package/src/tools/critique.ts +15 -45
- package/src/tools/publish.ts +16 -56
- package/src/tools/styleguide.ts +7 -1
- package/src/tools/wait.ts +77 -24
package/src/server/api.ts
CHANGED
|
@@ -4,13 +4,22 @@
|
|
|
4
4
|
// Routes:
|
|
5
5
|
// POST /api/sessions/:projectSlug/:filename/answers/:questionId
|
|
6
6
|
// GET /api/sessions/:projectSlug/:filename/state
|
|
7
|
+
// POST /api/sessions/:projectSlug/:filename/comments
|
|
8
|
+
// DELETE /api/sessions/:projectSlug/:filename/comments/:commentId
|
|
9
|
+
// POST /api/sessions/:projectSlug/:filename/verdict
|
|
7
10
|
//
|
|
8
11
|
// Any other /api/* path returns a JSON 404 (rather than falling through to the
|
|
9
12
|
// static file handler, which would return the HTML 404 page).
|
|
10
13
|
|
|
11
14
|
import { join, resolve, relative } from "node:path";
|
|
12
15
|
import { Hono } from "hono";
|
|
13
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
submitAnswer,
|
|
18
|
+
getState,
|
|
19
|
+
addComment,
|
|
20
|
+
removeComment,
|
|
21
|
+
setVerdict,
|
|
22
|
+
} from "../storage/mutate.ts";
|
|
14
23
|
import type { AnswerValue } from "../render/validate.ts";
|
|
15
24
|
|
|
16
25
|
export interface ApiHandlerOptions {
|
|
@@ -136,16 +145,166 @@ export function createApiApp(options: ApiHandlerOptions): Hono {
|
|
|
136
145
|
return c.json({ ok: false, reason: outcome.reason }, 404);
|
|
137
146
|
}
|
|
138
147
|
|
|
148
|
+
if (outcome.kind === "ask") {
|
|
149
|
+
return c.json(
|
|
150
|
+
{
|
|
151
|
+
kind: "ask",
|
|
152
|
+
status: outcome.status,
|
|
153
|
+
answers: outcome.answers,
|
|
154
|
+
remaining: outcome.remaining,
|
|
155
|
+
},
|
|
156
|
+
200,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// kind === "annotate"
|
|
139
161
|
return c.json(
|
|
140
162
|
{
|
|
163
|
+
kind: "annotate",
|
|
141
164
|
status: outcome.status,
|
|
142
|
-
|
|
143
|
-
|
|
165
|
+
comments: outcome.comments,
|
|
166
|
+
verdict: outcome.verdict,
|
|
167
|
+
verdictMode: outcome.verdictMode,
|
|
144
168
|
},
|
|
145
169
|
200,
|
|
146
170
|
);
|
|
147
171
|
});
|
|
148
172
|
|
|
173
|
+
// POST /api/sessions/:projectSlug/:filename/comments
|
|
174
|
+
app.post("/api/sessions/:projectSlug/:filename/comments", async (c) => {
|
|
175
|
+
const { projectSlug, filename } = c.req.param();
|
|
176
|
+
|
|
177
|
+
const resolved = resolveArtifact(stateDir, projectSlug, filename);
|
|
178
|
+
if (resolved instanceof Response) return resolved;
|
|
179
|
+
|
|
180
|
+
let body: unknown;
|
|
181
|
+
try {
|
|
182
|
+
body = await c.req.json();
|
|
183
|
+
} catch {
|
|
184
|
+
return c.json({ ok: false, error: "invalid JSON body" }, 400);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
188
|
+
return c.json({ ok: false, error: "body must be an object" }, 400);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const raw = body as Record<string, unknown>;
|
|
192
|
+
if (
|
|
193
|
+
typeof raw["anchor"] !== "string" ||
|
|
194
|
+
typeof raw["selectedText"] !== "string" ||
|
|
195
|
+
typeof raw["comment"] !== "string"
|
|
196
|
+
) {
|
|
197
|
+
return c.json(
|
|
198
|
+
{ ok: false, error: 'body must contain "anchor", "selectedText", and "comment" fields' },
|
|
199
|
+
400,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const outcome = await addComment({
|
|
204
|
+
artifactPath: resolved.artifactPath,
|
|
205
|
+
anchor: raw["anchor"],
|
|
206
|
+
selectedText: raw["selectedText"],
|
|
207
|
+
comment: raw["comment"],
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (outcome.ok) {
|
|
211
|
+
return c.json({ ok: true, comment: outcome.comment }, 200);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
switch (outcome.reason) {
|
|
215
|
+
case "not-found":
|
|
216
|
+
case "not-interactive":
|
|
217
|
+
return c.json({ ok: false, reason: outcome.reason }, 404);
|
|
218
|
+
case "session-ended":
|
|
219
|
+
return c.json({ ok: false, status: outcome.status }, 410);
|
|
220
|
+
case "expired":
|
|
221
|
+
return c.json({ ok: false, status: "expired" }, 410);
|
|
222
|
+
case "invalid-value":
|
|
223
|
+
return c.json({ ok: false, message: outcome.message }, 422);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return c.json({ ok: false, error: "internal error" }, 500);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// DELETE /api/sessions/:projectSlug/:filename/comments/:commentId
|
|
230
|
+
app.delete("/api/sessions/:projectSlug/:filename/comments/:commentId", async (c) => {
|
|
231
|
+
const { projectSlug, filename, commentId } = c.req.param();
|
|
232
|
+
|
|
233
|
+
const resolved = resolveArtifact(stateDir, projectSlug, filename);
|
|
234
|
+
if (resolved instanceof Response) return resolved;
|
|
235
|
+
|
|
236
|
+
const outcome = await removeComment({
|
|
237
|
+
artifactPath: resolved.artifactPath,
|
|
238
|
+
commentId,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (outcome.ok) {
|
|
242
|
+
return c.json({ ok: true }, 200);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
switch (outcome.reason) {
|
|
246
|
+
case "not-found":
|
|
247
|
+
case "not-interactive":
|
|
248
|
+
return c.json({ ok: false, reason: outcome.reason }, 404);
|
|
249
|
+
case "comment-not-found":
|
|
250
|
+
return c.json({ ok: false, reason: "comment-not-found" }, 404);
|
|
251
|
+
case "session-ended":
|
|
252
|
+
return c.json({ ok: false, status: outcome.status }, 410);
|
|
253
|
+
case "expired":
|
|
254
|
+
return c.json({ ok: false, status: "expired" }, 410);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return c.json({ ok: false, error: "internal error" }, 500);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// POST /api/sessions/:projectSlug/:filename/verdict
|
|
261
|
+
app.post("/api/sessions/:projectSlug/:filename/verdict", async (c) => {
|
|
262
|
+
const { projectSlug, filename } = c.req.param();
|
|
263
|
+
|
|
264
|
+
const resolved = resolveArtifact(stateDir, projectSlug, filename);
|
|
265
|
+
if (resolved instanceof Response) return resolved;
|
|
266
|
+
|
|
267
|
+
let body: unknown;
|
|
268
|
+
try {
|
|
269
|
+
body = await c.req.json();
|
|
270
|
+
} catch {
|
|
271
|
+
return c.json({ ok: false, error: "invalid JSON body" }, 400);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
275
|
+
return c.json({ ok: false, error: "body must be an object" }, 400);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const raw = body as Record<string, unknown>;
|
|
279
|
+
if (typeof raw["verdict"] !== "string") {
|
|
280
|
+
return c.json({ ok: false, error: 'body must contain a string "verdict" field' }, 400);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Pass verdict as-is to setVerdict; mode-checking happens internally
|
|
284
|
+
const outcome = await setVerdict({
|
|
285
|
+
artifactPath: resolved.artifactPath,
|
|
286
|
+
verdict: raw["verdict"] as import("../render/validate.ts").Verdict,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (outcome.ok) {
|
|
290
|
+
return c.json({ ok: true, status: outcome.status, verdict: outcome.verdict }, 200);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
switch (outcome.reason) {
|
|
294
|
+
case "not-found":
|
|
295
|
+
case "not-interactive":
|
|
296
|
+
return c.json({ ok: false, reason: outcome.reason }, 404);
|
|
297
|
+
case "session-ended":
|
|
298
|
+
return c.json({ ok: false, status: outcome.status }, 410);
|
|
299
|
+
case "expired":
|
|
300
|
+
return c.json({ ok: false, status: "expired" }, 410);
|
|
301
|
+
case "invalid-value":
|
|
302
|
+
return c.json({ ok: false, message: outcome.message }, 422);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return c.json({ ok: false, error: "internal error" }, 500);
|
|
306
|
+
});
|
|
307
|
+
|
|
149
308
|
// Catch-all under /api/* — keeps unmatched API paths as JSON 404 instead of
|
|
150
309
|
// falling through to the static file handler.
|
|
151
310
|
app.all("/api/*", (c) => c.json({ ok: false, error: "not found" }, 404));
|
package/src/storage/index-gen.ts
CHANGED
|
@@ -94,6 +94,8 @@ function indexCss(): string {
|
|
|
94
94
|
/* the eyebrow text is uppercased + tracked; the emblem sits flush left */
|
|
95
95
|
}
|
|
96
96
|
.cesium-eyebrow svg { display: block; }
|
|
97
|
+
a.cesium-eyebrow { color: inherit; text-decoration: none; transition: opacity 0.15s; }
|
|
98
|
+
a.cesium-eyebrow:hover { opacity: 0.7; }
|
|
97
99
|
.filter-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; align-items: center; }
|
|
98
100
|
.filter-chip {
|
|
99
101
|
display: inline-block; font-family: var(--sans); font-size: 0.8em; font-weight: 500;
|
|
@@ -365,7 +367,7 @@ ${cardsHtml}
|
|
|
365
367
|
</head>
|
|
366
368
|
<body>
|
|
367
369
|
<div class="page">
|
|
368
|
-
<
|
|
370
|
+
<a class="eyebrow cesium-eyebrow" href="../../index.html">${faviconEmblemSvg(18)}<span>cesium · project</span></a>
|
|
369
371
|
<h1 class="h-display">${esc(projectName)}</h1>
|
|
370
372
|
${subhead}
|
|
371
373
|
${filterRow}
|
|
@@ -454,7 +456,7 @@ export function renderGlobalIndex(args: RenderGlobalIndexArgs): string {
|
|
|
454
456
|
</head>
|
|
455
457
|
<body>
|
|
456
458
|
<div class="page">
|
|
457
|
-
<
|
|
459
|
+
<a class="eyebrow cesium-eyebrow" href="index.html">${faviconEmblemSvg(18)}<span>cesium</span></a>
|
|
458
460
|
<h1 class="h-display">All projects</h1>
|
|
459
461
|
${subhead}
|
|
460
462
|
${bodyContent}
|