mockpit 1.0.0 → 1.2.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/README.md +12 -3
- package/bin/mockpit.js +47 -21
- package/dist/mcp/server.js +43 -4
- package/dist/server/apiViews.d.ts +11 -8
- package/dist/server/apiViews.js +21 -16
- package/dist/server/app.d.ts +3 -1
- package/dist/server/app.js +374 -67
- package/dist/server/appIcon.d.ts +22 -0
- package/dist/server/appIcon.js +43 -0
- package/dist/server/appIconPng.d.ts +1 -0
- package/dist/server/appIconPng.js +6 -0
- package/dist/server/askNudges.d.ts +3 -0
- package/dist/server/askNudges.js +40 -0
- package/dist/server/base64.d.ts +1 -0
- package/dist/server/base64.js +8 -0
- package/dist/server/designGuide.js +15 -13
- package/dist/server/eventLog.d.ts +27 -0
- package/dist/server/eventLog.js +187 -0
- package/dist/server/feedbackBatch.d.ts +16 -4
- package/dist/server/feedbackBatch.js +59 -9
- package/dist/server/handoff.d.ts +6 -0
- package/dist/server/handoff.js +38 -0
- package/dist/server/index.js +12 -0
- package/dist/server/knobs.js +2 -3
- package/dist/server/logFile.d.ts +2 -0
- package/dist/server/logFile.js +27 -0
- package/dist/server/mcpHttp.d.ts +2 -2
- package/dist/server/mcpHttp.js +11 -5
- package/dist/server/mcpSpec.d.ts +8 -0
- package/dist/server/mcpSpec.js +26 -1
- package/dist/server/run.js +2 -2
- package/dist/server/runApi.d.ts +1 -1
- package/dist/server/runApi.js +1 -1
- package/dist/server/sqlStore.d.ts +3 -1
- package/dist/server/sqlStore.js +40 -8
- package/dist/server/types.d.ts +25 -2
- package/dist/server/types.js +47 -4
- package/extensions/mockpit.js +24 -9
- package/guide/topics/asks.md +19 -7
- package/guide/topics/feedback.md +17 -7
- package/guide/topics/knobs.md +3 -3
- package/guide/topics/surfaces.md +8 -8
- package/package.json +2 -2
- package/skills/mockpit/SKILL.md +4 -2
- package/viewer/dist/index.html +9 -9
package/README.md
CHANGED
|
@@ -70,8 +70,10 @@ mockpit turns that into a design-decision loop:
|
|
|
70
70
|
|
|
71
71
|
**Home** lists the project's mocks with a thumbnail, their states and how many
|
|
72
72
|
questions are open, with "Answer next ›" to jump to the first one. Dark and
|
|
73
|
-
light themes, toggled from the top bar. On a
|
|
74
|
-
bottom sheet
|
|
73
|
+
light themes, toggled from the top bar. On a phone or tablet the panel becomes a
|
|
74
|
+
bottom sheet, the stage scrolls under your finger and renders the design at a
|
|
75
|
+
chosen width (Phone 390, Tablet 744, Desktop 820, Wide 1280), and Inspect opens
|
|
76
|
+
the stage full screen to pan and pinch-zoom it.
|
|
75
77
|
|
|
76
78
|
## Quick start
|
|
77
79
|
|
|
@@ -155,7 +157,7 @@ and `mockpit watch` (one line per Send, for a background monitor).
|
|
|
155
157
|
list (an entry that is only `{id}` keeps that surface), or `parts` to replace
|
|
156
158
|
just the marked elements.
|
|
157
159
|
- **`feedback` returns at once** with what the user sent since the agent last
|
|
158
|
-
heard, plus `pending`: whether
|
|
160
|
+
heard, plus `pending`: whether each mock is open in the viewer and how far the user's
|
|
159
161
|
draft has got (`2 of 3 answered`). Every write returns `feedback` too, and
|
|
160
162
|
each Send is delivered exactly once across all of them.
|
|
161
163
|
- **Asks are nudged.** A publish that leaves several variants with no ask
|
|
@@ -185,6 +187,13 @@ It runs locally as a small Node server (SQLite at `~/.mockpit/mockpit.db`), or o
|
|
|
185
187
|
Cloudflare Workers when your agent and browser are on different machines. See
|
|
186
188
|
**[docs/deploying.md](docs/deploying.md)**.
|
|
187
189
|
|
|
190
|
+
The server keeps an event log, one JSON line per agent call, viewer write and
|
|
191
|
+
live-feed connection (ids and shapes, never content), at
|
|
192
|
+
`~/.mockpit/events.jsonl` next to the database. It rolls over at 10 MB. Set
|
|
193
|
+
`MOCKPIT_LOG` to another path, or to `off` to disable it; on Workers it goes to
|
|
194
|
+
Workers Logs. `node scripts/inspect.ts` in a checkout reads it alongside the
|
|
195
|
+
database and Claude Code transcripts.
|
|
196
|
+
|
|
188
197
|
The server app is importable from `mockpit/server` (`createApp`, `SqlStore`,
|
|
189
198
|
`createSqliteStorage`). The embeddable viewer engine (`mockpit/viewer-embed`,
|
|
190
199
|
`mountViewer`) is gone: the viewer is self-hosted only.
|
package/bin/mockpit.js
CHANGED
|
@@ -19,6 +19,12 @@ const BASE = (process.env.MOCKPIT_URL ?? "http://localhost:8228").replace(/\/$/,
|
|
|
19
19
|
const TOKEN = process.env.MOCKPIT_TOKEN;
|
|
20
20
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
21
21
|
const PKG_VERSION = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version;
|
|
22
|
+
// Every request names this client and its version, so the server's event log
|
|
23
|
+
// can tell the CLI from MCP and spot one older than the server.
|
|
24
|
+
const HEADERS = {
|
|
25
|
+
"x-mockpit-client": `cli/${PKG_VERSION}`,
|
|
26
|
+
...(TOKEN ? { authorization: `Bearer ${TOKEN}` } : {}),
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
const HELP = `mockpit — a live visual surface for terminal coding agents
|
|
24
30
|
|
|
@@ -31,7 +37,9 @@ the loop (never blocks on the user):
|
|
|
31
37
|
1. mockpit init once per repo; read the brief (mockpit guide).
|
|
32
38
|
2. mockpit publish once per variant; mark parts with data-part.
|
|
33
39
|
3. A choice is several variants plus one ask that binds them: mockpit ask
|
|
34
|
-
with options bound to variants.
|
|
40
|
+
with options bound to variants. A variant decides everything it differs
|
|
41
|
+
on; don't re-ask it. Visual options carry a variant or knob set. One
|
|
42
|
+
render plus a control: --knobs.
|
|
35
43
|
4. Tell the user in one line where to look, then end your turn. The question
|
|
36
44
|
itself lives in the mock (ask), never in chat. The user answers in the
|
|
37
45
|
browser at their own pace. Never poll.
|
|
@@ -66,7 +74,8 @@ verbs:
|
|
|
66
74
|
--data is a JSON tree)
|
|
67
75
|
mockpit ask --mock <slug> "<question>" --option <label[=variant]> ...
|
|
68
76
|
ask the user; bind options to variants
|
|
69
|
-
--option <l[=v]> an option (repeatable); "=variant" binds it to a variant
|
|
77
|
+
--option <l[=v]> an option (repeatable); "=variant" binds it to a variant,
|
|
78
|
+
"=knob:path=value" sets one knob
|
|
70
79
|
--scope <s> mock|state|part (default mock)
|
|
71
80
|
--state <label> the state a state-scoped ask is about
|
|
72
81
|
--part <name> the part a part-scoped ask is about
|
|
@@ -118,6 +127,7 @@ environment:
|
|
|
118
127
|
MOCKPIT_URL server base URL (default http://localhost:8228)
|
|
119
128
|
MOCKPIT_TOKEN bearer token for a deployed instance
|
|
120
129
|
MOCKPIT_HOST address serve binds to (default: every interface)
|
|
130
|
+
MOCKPIT_LOG serve's event log file, or "off" (default ~/.mockpit/events.jsonl)
|
|
121
131
|
MOCKPIT_SESSION fixed session id (overrides auto-detection)
|
|
122
132
|
MOCKPIT_AGENT agent name used when creating sessions
|
|
123
133
|
`;
|
|
@@ -241,7 +251,7 @@ async function api(path, init = {}, { report, fix } = {}) {
|
|
|
241
251
|
...init,
|
|
242
252
|
headers: {
|
|
243
253
|
"content-type": "application/json",
|
|
244
|
-
...
|
|
254
|
+
...HEADERS,
|
|
245
255
|
...init.headers,
|
|
246
256
|
},
|
|
247
257
|
});
|
|
@@ -364,7 +374,7 @@ async function resolveSession(flags, { create = false } = {}) {
|
|
|
364
374
|
const state = readState();
|
|
365
375
|
if (state.session && !flags["new-session"]) {
|
|
366
376
|
const ok = await fetch(`${BASE}/api/sessions/${state.session}`, {
|
|
367
|
-
headers:
|
|
377
|
+
headers: HEADERS,
|
|
368
378
|
}).then(
|
|
369
379
|
(r) => r.ok,
|
|
370
380
|
() => false,
|
|
@@ -394,7 +404,7 @@ async function resolveSession(flags, { create = false } = {}) {
|
|
|
394
404
|
async function resolveSessionByCwd(cwd = process.cwd()) {
|
|
395
405
|
try {
|
|
396
406
|
const res = await fetch(`${BASE}/api/sessions`, {
|
|
397
|
-
headers:
|
|
407
|
+
headers: HEADERS,
|
|
398
408
|
});
|
|
399
409
|
if (!res.ok) return null;
|
|
400
410
|
const sessions = await res.json();
|
|
@@ -533,7 +543,7 @@ async function uploadBytes(bytes, { filename, contentType, session, kind } = {})
|
|
|
533
543
|
method: "POST",
|
|
534
544
|
headers: {
|
|
535
545
|
"content-type": contentType ?? "application/octet-stream",
|
|
536
|
-
...
|
|
546
|
+
...HEADERS,
|
|
537
547
|
},
|
|
538
548
|
body: bytes,
|
|
539
549
|
});
|
|
@@ -818,19 +828,17 @@ function printFeedback(feedback) {
|
|
|
818
828
|
if (feedback?.length) console.log(`feedback: ${JSON.stringify(feedback)}`);
|
|
819
829
|
}
|
|
820
830
|
|
|
821
|
-
// What the user is doing right now
|
|
822
|
-
// said once rather than per mock.
|
|
831
|
+
// What the user is doing right now, per mock.
|
|
823
832
|
function pendingLines(pending) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return lines;
|
|
833
|
+
return (pending ?? []).flatMap((p) =>
|
|
834
|
+
p.draft
|
|
835
|
+
? [
|
|
836
|
+
`pending: ${p.mock} — the user is answering (${p.draft.answered} of ${p.draft.of} answered, ${p.draft.comments} comment${p.draft.comments === 1 ? "" : "s"})`,
|
|
837
|
+
]
|
|
838
|
+
: p.viewerOpen
|
|
839
|
+
? [`pending: ${p.mock} — open in the viewer`]
|
|
840
|
+
: [],
|
|
841
|
+
);
|
|
834
842
|
}
|
|
835
843
|
|
|
836
844
|
function parsePublishFlags() {
|
|
@@ -953,8 +961,23 @@ async function publishMock() {
|
|
|
953
961
|
return result;
|
|
954
962
|
}
|
|
955
963
|
|
|
956
|
-
// "Quiet" is an unbound option; "Quiet=quiet" binds it to the quiet variant
|
|
964
|
+
// "Quiet" is an unbound option; "Quiet=quiet" binds it to the quiet variant;
|
|
965
|
+
// "Above=knob:trim.position=top" sets one knob. The value is JSON when it
|
|
966
|
+
// parses (17, true), else a string, so slider and toggle values keep their type.
|
|
957
967
|
function parseOption(raw) {
|
|
968
|
+
const knob = raw.indexOf("=knob:");
|
|
969
|
+
if (knob > 0) {
|
|
970
|
+
const pair = raw.slice(knob + "=knob:".length);
|
|
971
|
+
const eq = pair.indexOf("=");
|
|
972
|
+
if (eq <= 0)
|
|
973
|
+
die(`option "${raw}" needs knob:<path>=<value>`, "--option Above=knob:trim.position=top");
|
|
974
|
+
const text = pair.slice(eq + 1);
|
|
975
|
+
let value = text;
|
|
976
|
+
try {
|
|
977
|
+
value = JSON.parse(text);
|
|
978
|
+
} catch {}
|
|
979
|
+
return { label: raw.slice(0, knob), set: { [pair.slice(0, eq)]: value } };
|
|
980
|
+
}
|
|
958
981
|
const at = raw.lastIndexOf("=");
|
|
959
982
|
if (at <= 0) return { label: raw };
|
|
960
983
|
return { label: raw.slice(0, at), variant: raw.slice(at + 1) };
|
|
@@ -981,6 +1004,7 @@ function printRun(r) {
|
|
|
981
1004
|
function watchLines(batch) {
|
|
982
1005
|
const lines = [];
|
|
983
1006
|
const name = batch.mock ?? "a mock";
|
|
1007
|
+
if (batch.handoff) lines.push(`mockpit handoff on ${name}: ${batch.handoff}`);
|
|
984
1008
|
if (batch.reply) {
|
|
985
1009
|
const r = batch.reply;
|
|
986
1010
|
const bits = [
|
|
@@ -999,6 +1023,7 @@ function watchLines(batch) {
|
|
|
999
1023
|
.join(", ")}`
|
|
1000
1024
|
: "",
|
|
1001
1025
|
r.comments?.length ? `${r.comments.length} comment${r.comments.length === 1 ? "" : "s"}` : "",
|
|
1026
|
+
r.reactions?.length ? r.reactions.join(", ") : "",
|
|
1002
1027
|
r.decision ? r.decision.kind : "",
|
|
1003
1028
|
r.text ?? "",
|
|
1004
1029
|
].filter(Boolean);
|
|
@@ -1020,7 +1045,7 @@ async function apiSoft(path) {
|
|
|
1020
1045
|
let res;
|
|
1021
1046
|
try {
|
|
1022
1047
|
res = await fetch(`${BASE}${path}`, {
|
|
1023
|
-
headers:
|
|
1048
|
+
headers: HEADERS,
|
|
1024
1049
|
});
|
|
1025
1050
|
} catch {
|
|
1026
1051
|
die(`cannot reach mockpit at ${BASE}`, "mockpit serve");
|
|
@@ -1375,6 +1400,7 @@ const commands = {
|
|
|
1375
1400
|
`asked on ${result.mock}: ${ask.text} [${ask.options.map((o) => o.label).join(" | ")}]`,
|
|
1376
1401
|
);
|
|
1377
1402
|
}
|
|
1403
|
+
for (const nudge of result.nudges ?? []) console.log(`nudge: ${nudge}`);
|
|
1378
1404
|
console.log(`tell the user to look at ${result.url}, then end your turn`);
|
|
1379
1405
|
printFeedback(result.feedback);
|
|
1380
1406
|
},
|
|
@@ -1543,7 +1569,7 @@ const commands = {
|
|
|
1543
1569
|
try {
|
|
1544
1570
|
const res = await fetch(
|
|
1545
1571
|
`${BASE}/api/comments${query({ session, author: "user", after: firstAfter, wait: 60 })}`,
|
|
1546
|
-
{ headers:
|
|
1572
|
+
{ headers: HEADERS },
|
|
1547
1573
|
);
|
|
1548
1574
|
if (!res.ok) {
|
|
1549
1575
|
await sleep(2000);
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
8
|
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
import { MCP_INSTRUCTIONS, MCP_SERVER_INFO, runToolResult, STDIO_MCP_CATALOG, STDIO_MCP_TOOLS, STDIO_RUN_CATALOG, STDIO_RUN_TOOLS, toolResult, } from "../server/mcpSpec.js";
|
|
9
|
+
import { MCP_INSTRUCTIONS, MCP_SERVER_INFO, runToolResult, STDIO_MCP_CATALOG, STDIO_MCP_TOOLS, STDIO_RUN_CATALOG, STDIO_RUN_TOOLS, toolResult, withFeedbackImages, } from "../server/mcpSpec.js";
|
|
8
10
|
import { RUN_INSTRUCTIONS } from "../server/runApi.js";
|
|
9
11
|
// Point at a deployed instance later by setting MOCKPIT_URL.
|
|
10
12
|
const API = process.env.MOCKPIT_URL ?? "http://localhost:8228";
|
|
@@ -12,8 +14,28 @@ const TOKEN = process.env.MOCKPIT_TOKEN;
|
|
|
12
14
|
const AGENT = process.env.MOCKPIT_AGENT ?? "claude-code";
|
|
13
15
|
// "code" serves the codemode catalog: one `run` tool instead of the mock tools.
|
|
14
16
|
const CODE_MODE = process.env.MOCKPIT_MCP_MODE === "code";
|
|
17
|
+
// Names this client in the server's event log; the version is how a stdio
|
|
18
|
+
// server left running across an upgrade shows up there.
|
|
19
|
+
const CLIENT = `mcp-stdio/${packageVersion()}`;
|
|
20
|
+
// Source runs as mcp/server.ts, the published copy as dist/mcp/server.js.
|
|
21
|
+
function packageVersion() {
|
|
22
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), rel), "utf8"));
|
|
25
|
+
if (pkg.name === "mockpit")
|
|
26
|
+
return String(pkg.version);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Not at this depth.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
15
34
|
async function api(path, init = {}) {
|
|
16
|
-
const headers = {
|
|
35
|
+
const headers = {
|
|
36
|
+
"content-type": "application/json",
|
|
37
|
+
"x-mockpit-client": CLIENT,
|
|
38
|
+
};
|
|
17
39
|
if (TOKEN)
|
|
18
40
|
headers.authorization = `Bearer ${TOKEN}`;
|
|
19
41
|
let res;
|
|
@@ -208,18 +230,35 @@ async function run(args) {
|
|
|
208
230
|
body: JSON.stringify({ code, session, project: resolveProject(args.project), agent: AGENT }),
|
|
209
231
|
});
|
|
210
232
|
}
|
|
233
|
+
async function loadImage(ref) {
|
|
234
|
+
const res = await fetch(`${API}/a/${encodeURIComponent(ref.id)}`, {
|
|
235
|
+
headers: TOKEN ? { authorization: `Bearer ${TOKEN}` } : {},
|
|
236
|
+
});
|
|
237
|
+
if (!res.ok)
|
|
238
|
+
return null;
|
|
239
|
+
return {
|
|
240
|
+
data: new Uint8Array(await res.arrayBuffer()),
|
|
241
|
+
mimeType: res.headers.get("content-type") ?? "image/png",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
211
244
|
const server = new McpServer(MCP_SERVER_INFO, {
|
|
212
245
|
instructions: CODE_MODE ? RUN_INSTRUCTIONS : MCP_INSTRUCTIONS,
|
|
213
246
|
});
|
|
214
247
|
if (CODE_MODE) {
|
|
215
248
|
const [{ name, ...config }] = STDIO_RUN_TOOLS;
|
|
216
|
-
server.registerTool(name, config, async (args) =>
|
|
249
|
+
server.registerTool(name, config, async (args) => {
|
|
250
|
+
const envelope = await run(args);
|
|
251
|
+
return withFeedbackImages(runToolResult(envelope), envelope, loadImage);
|
|
252
|
+
});
|
|
217
253
|
}
|
|
218
254
|
else {
|
|
219
255
|
for (const tool of STDIO_MCP_TOOLS) {
|
|
220
256
|
const handler = handlers[tool.name];
|
|
221
257
|
const { name, ...config } = tool;
|
|
222
|
-
server.registerTool(name, config, async (args) =>
|
|
258
|
+
server.registerTool(name, config, async (args) => {
|
|
259
|
+
const value = await handler(args);
|
|
260
|
+
return withFeedbackImages(toolResult(name, value), value, loadImage);
|
|
261
|
+
});
|
|
223
262
|
}
|
|
224
263
|
}
|
|
225
264
|
// Replaces the SDK's own listing (registered above), so stdio advertises the
|
|
@@ -203,6 +203,15 @@ export interface StateParts {
|
|
|
203
203
|
}
|
|
204
204
|
export declare function partsByState(mock: Mock, posts: Post[]): StateParts[];
|
|
205
205
|
export declare const mockSummaryView: (mock: Mock, posts: Post[]) => {
|
|
206
|
+
sessionId: string | null;
|
|
207
|
+
createdAt: string;
|
|
208
|
+
updatedAt: string;
|
|
209
|
+
thumbnail: {
|
|
210
|
+
postId: string;
|
|
211
|
+
surface: number;
|
|
212
|
+
version: number;
|
|
213
|
+
} | null;
|
|
214
|
+
drafted?: number | undefined;
|
|
206
215
|
id: string;
|
|
207
216
|
project: string;
|
|
208
217
|
slug: string;
|
|
@@ -212,14 +221,6 @@ export declare const mockSummaryView: (mock: Mock, posts: Post[]) => {
|
|
|
212
221
|
stateCount: number;
|
|
213
222
|
variants: number;
|
|
214
223
|
open: number;
|
|
215
|
-
sessionId: string | null;
|
|
216
|
-
createdAt: string;
|
|
217
|
-
updatedAt: string;
|
|
218
|
-
thumbnail: {
|
|
219
|
-
postId: string;
|
|
220
|
-
surface: number;
|
|
221
|
-
version: number;
|
|
222
|
-
} | null;
|
|
223
224
|
};
|
|
224
225
|
export declare const mockDetailView: (mock: Mock, posts: Post[], opts?: VariantViewOptions & {
|
|
225
226
|
tuned?: Record<string, unknown>;
|
|
@@ -326,4 +327,6 @@ export declare const sessionRowView: (session: Session, postCount: number) => {
|
|
|
326
327
|
lastActiveAt: string;
|
|
327
328
|
agentSeq: number;
|
|
328
329
|
project: string | null;
|
|
330
|
+
agentActiveAt: string | null;
|
|
331
|
+
keyed: boolean;
|
|
329
332
|
};
|
package/dist/server/apiViews.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Response shapes shared by every tier (REST, both MCP transports, the CLI), so
|
|
2
2
|
// one mock reads the same however an agent asks for it.
|
|
3
3
|
import { mergeParts, partsInSurfaces } from "./parts.js";
|
|
4
|
-
import { isSandboxedSurfaceKind, openCount, SURFACE_CONTENT_FIELDS, } from "./types.js";
|
|
4
|
+
import { isSandboxedSurfaceKind, draftedCount, openCount, SURFACE_CONTENT_FIELDS, } from "./types.js";
|
|
5
5
|
export const surfaceRef = (surface, index) => ({
|
|
6
6
|
id: surface.id,
|
|
7
7
|
kind: surface.kind,
|
|
@@ -94,21 +94,26 @@ function thumbnail(mock, posts) {
|
|
|
94
94
|
const surface = pick.surfaces.findIndex((s) => isSandboxedSurfaceKind(s.kind));
|
|
95
95
|
return { postId: pick.id, surface: Math.max(surface, 0), version: pick.version };
|
|
96
96
|
}
|
|
97
|
-
export const mockSummaryView = (mock, posts) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
97
|
+
export const mockSummaryView = (mock, posts) => {
|
|
98
|
+
const drafted = draftedCount(mock, posts);
|
|
99
|
+
return {
|
|
100
|
+
id: mock.id,
|
|
101
|
+
project: mock.project,
|
|
102
|
+
slug: mock.slug,
|
|
103
|
+
title: mock.title,
|
|
104
|
+
kind: mock.kind,
|
|
105
|
+
states: mock.states,
|
|
106
|
+
stateCount: Math.max(mock.states.length, 1),
|
|
107
|
+
variants: posts.length,
|
|
108
|
+
open: openCount(mock, posts),
|
|
109
|
+
// Only once the user has started one, so a list with no drafts costs nothing.
|
|
110
|
+
...(drafted === null ? {} : { drafted }),
|
|
111
|
+
sessionId: mock.sessionId,
|
|
112
|
+
createdAt: mock.createdAt,
|
|
113
|
+
updatedAt: mock.updatedAt,
|
|
114
|
+
thumbnail: thumbnail(mock, posts),
|
|
115
|
+
};
|
|
116
|
+
};
|
|
112
117
|
// One mock as anyone outside the viewer's own draft sees it: the draft is the
|
|
113
118
|
// user's unsent work and never leaves through this view.
|
|
114
119
|
export const mockDetailView = (mock, posts, opts = {}) => ({
|
package/dist/server/app.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { type FeedEvent } from "./events.ts";
|
|
3
3
|
import { type GuideTopic } from "./designGuide.ts";
|
|
4
4
|
import { type Executor, type RunLimits } from "./run.ts";
|
|
5
|
+
import { type LogSink } from "./eventLog.ts";
|
|
5
6
|
import { type Store } from "./types.ts";
|
|
6
7
|
export type { FeedEvent } from "./events.ts";
|
|
7
8
|
export type { FeedbackBatch } from "./feedbackBatch.ts";
|
|
@@ -25,6 +26,7 @@ export interface AppOptions {
|
|
|
25
26
|
maxHoldConnections?: number;
|
|
26
27
|
executor?: Executor;
|
|
27
28
|
runLimits?: Partial<RunLimits>;
|
|
29
|
+
log?: LogSink;
|
|
28
30
|
}
|
|
29
31
|
export interface LatestRelease {
|
|
30
32
|
version: string;
|
|
@@ -50,4 +52,4 @@ export interface FlowContext {
|
|
|
50
52
|
request?: Request;
|
|
51
53
|
signal?: AbortSignal;
|
|
52
54
|
}
|
|
53
|
-
export declare function createApp({ store, viewerHtml, setupText, topics, authenticate, authToken, basePath, publicRead, screenshots, version, upgradeCommand, fetchLatestRelease, onEvent, maxHoldConnections, executor, runLimits, }: AppOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
55
|
+
export declare function createApp({ store, viewerHtml, setupText, topics, authenticate, authToken, basePath, publicRead, screenshots, version, upgradeCommand, fetchLatestRelease, onEvent, maxHoldConnections, executor, runLimits, log, }: AppOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|