mockpit 1.3.0 → 2.0.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.
Files changed (45) hide show
  1. package/README.md +61 -45
  2. package/bin/demoData.js +3 -3
  3. package/bin/mockpit.js +327 -52
  4. package/dist/mcp/server.js +35 -0
  5. package/dist/server/apiViews.d.ts +308 -23
  6. package/dist/server/apiViews.js +60 -19
  7. package/dist/server/app.d.ts +4 -1
  8. package/dist/server/app.js +608 -119
  9. package/dist/server/codeWorker.js +2 -1
  10. package/dist/server/decisions.d.ts +5 -5
  11. package/dist/server/decisions.js +10 -9
  12. package/dist/server/designGuide.d.ts +6 -2
  13. package/dist/server/designGuide.js +77 -43
  14. package/dist/server/embeds.d.ts +1 -0
  15. package/dist/server/embeds.js +30 -4
  16. package/dist/server/feedbackBatch.d.ts +6 -6
  17. package/dist/server/feedbackBatch.js +18 -11
  18. package/dist/server/guidance.d.ts +23 -0
  19. package/dist/server/guidance.js +71 -0
  20. package/dist/server/index.js +10 -1
  21. package/dist/server/mcpHttp.d.ts +3 -0
  22. package/dist/server/mcpHttp.js +32 -0
  23. package/dist/server/mcpSpec.d.ts +2 -1
  24. package/dist/server/mcpSpec.js +74 -24
  25. package/dist/server/postSurfaces.js +3 -0
  26. package/dist/server/run.js +6 -1
  27. package/dist/server/runApi.d.ts +3 -3
  28. package/dist/server/runApi.js +16 -12
  29. package/dist/server/sqlStore.d.ts +10 -3
  30. package/dist/server/sqlStore.js +230 -16
  31. package/dist/server/types.d.ts +46 -17
  32. package/dist/server/types.js +34 -11
  33. package/extensions/mockpit.js +179 -16
  34. package/guide/packs/design-craft.md +35 -0
  35. package/guide/topics/asks.md +60 -56
  36. package/guide/topics/feedback.md +104 -74
  37. package/guide/topics/html.md +5 -11
  38. package/guide/topics/http.md +6 -3
  39. package/guide/topics/knobs.md +2 -2
  40. package/guide/topics/scripts.md +2 -2
  41. package/guide/topics/surfaces.md +28 -10
  42. package/package.json +1 -4
  43. package/skills/launch-video/SKILL.md +1 -1
  44. package/skills/mockpit/SKILL.md +18 -10
  45. package/viewer/dist/index.html +8 -7
@@ -2,7 +2,7 @@
2
2
  // one mock reads the same however an agent asks for it.
3
3
  import { slotKnobs, slotsInSurfaces } from "./fills.js";
4
4
  import { mergeParts, partsInSurfaces } from "./parts.js";
5
- import { draftedCount, inUseVersion, isSandboxedSurfaceKind, isLive, openCount, SURFACE_CONTENT_FIELDS, } from "./types.js";
5
+ import { draftedCount, inUseVersion, isSandboxedSurfaceKind, isLive, momentsOf, openCount, SURFACE_CONTENT_FIELDS, variantStatus, } from "./types.js";
6
6
  export const surfaceRef = (surface, index) => ({
7
7
  id: surface.id,
8
8
  kind: surface.kind,
@@ -25,6 +25,27 @@ export const hydratedSurfaceView = (surface, index) => {
25
25
  delete view[field];
26
26
  return view;
27
27
  };
28
+ // The still overviews show for a version: its html page's poster.
29
+ const pagePoster = (surfaces) => {
30
+ const page = surfaces.find((s) => s.kind === "html");
31
+ return page?.kind === "html" ? page.poster : undefined;
32
+ };
33
+ const posterOf = (surfaces) => {
34
+ const poster = pagePoster(surfaces);
35
+ return poster ? { poster } : {};
36
+ };
37
+ // The poster of one version of a variant: what Home shows for it.
38
+ const posterAt = (post, version) => pagePoster((version === post.version
39
+ ? post.surfaces
40
+ : post.history.find((h) => h.version === version)?.surfaces) ?? []);
41
+ // The in-use version's poster, when Home shows an older version than the
42
+ // newest.
43
+ const inUsePoster = (p) => {
44
+ if (p.acceptedVersion === undefined || p.acceptedVersion === p.version)
45
+ return {};
46
+ const poster = posterAt(p, p.acceptedVersion);
47
+ return poster ? { inUsePoster: poster } : {};
48
+ };
28
49
  // One past version without its bodies: enough to list versions or pick one to
29
50
  // fetch, at a bounded cost.
30
51
  export const historyMetaView = (version) => ({
@@ -35,12 +56,13 @@ export const historyMetaView = (version) => ({
35
56
  ...(version.prompt ? { prompt: version.prompt } : {}),
36
57
  ...(version.author === undefined ? {} : { author: version.author }),
37
58
  surfaceKinds: version.surfaces.map((s) => s.kind),
59
+ ...posterOf(version.surfaces),
38
60
  });
39
61
  export const variantView = (post, opts = {}) => ({
40
62
  postId: post.id,
41
63
  state: post.state,
42
64
  variant: post.variant,
43
- status: post.status,
65
+ status: variantStatus(post.status),
44
66
  version: post.version,
45
67
  title: post.title,
46
68
  sessionId: post.sessionId,
@@ -49,11 +71,11 @@ export const variantView = (post, opts = {}) => ({
49
71
  ...(post.from === undefined ? {} : { from: post.from }),
50
72
  ...(post.prompt ? { prompt: post.prompt } : {}),
51
73
  ...(post.author === undefined ? {} : { author: post.author }),
52
- // A slot is a select knob on its part (fills.ts); declared knobs win.
74
+ // A part with fills is a select knob on its part (fills.ts); declared knobs win.
53
75
  ...(slotsInSurfaces(post.surfaces).length || post.knobs
54
76
  ? { knobs: { ...slotKnobs(slotsInSurfaces(post.surfaces)), ...post.knobs } }
55
77
  : {}),
56
- ...(slotsInSurfaces(post.surfaces).length ? { slots: slotsInSurfaces(post.surfaces) } : {}),
78
+ ...(slotsInSurfaces(post.surfaces).length ? { fills: slotsInSurfaces(post.surfaces) } : {}),
57
79
  ...(post.note ? { note: post.note } : {}),
58
80
  // Embedded components as they render now (server/embeds.ts); `publishedWith`
59
81
  // is what this version rendered with when it was published.
@@ -63,9 +85,9 @@ export const variantView = (post, opts = {}) => ({
63
85
  ...(post.embeds.length ? { publishedWith: post.embeds } : {}),
64
86
  ...(post.forkedFrom ? { forkedFrom: post.forkedFrom } : {}),
65
87
  ...(post.starred ? { starred: true } : {}),
66
- ...(post.acceptedVersion === undefined ? {} : { acceptedVersion: post.acceptedVersion }),
88
+ ...(post.acceptedVersion === undefined ? {} : { inUseVersion: post.acceptedVersion }),
67
89
  // The part names this variant's own markup marks, so the viewer can tell
68
- // which looks render a part (Mix) without the html bodies.
90
+ // which variants render a part (Borrow) without the html bodies.
69
91
  parts: partsInSurfaces(post.surfaces).map((p) => p.name),
70
92
  surfaces: post.surfaces.map(opts.body ? fullSurfaceView : hydratedSurfaceView),
71
93
  ...(opts.history
@@ -101,6 +123,12 @@ export function partsByState(mock, posts) {
101
123
  .map((p) => partsInSurfaces(p.surfaces))),
102
124
  }));
103
125
  }
126
+ // Where a mock sits in the product and the code that implements it, only once
127
+ // declared, so the views of a project that names neither cost nothing.
128
+ export const placeOf = (mock) => ({
129
+ ...(mock.area ? { area: mock.area } : {}),
130
+ ...(mock.source ? { source: mock.source } : {}),
131
+ });
104
132
  // What Home shows for a mock: the first state's chosen (else first) variant.
105
133
  function thumbnail(mock, posts) {
106
134
  const first = stateOrder(mock)[0];
@@ -112,8 +140,13 @@ function thumbnail(mock, posts) {
112
140
  if (!pick)
113
141
  return null;
114
142
  const surface = pick.surfaces.findIndex((s) => isSandboxedSurfaceKind(s.kind));
115
- return { postId: pick.id, surface: Math.max(surface, 0), version: inUseVersion(pick) };
143
+ const version = inUseVersion(pick);
144
+ const poster = posterAt(pick, version);
145
+ return { postId: pick.id, surface: Math.max(surface, 0), version, ...(poster ? { poster } : {}) };
116
146
  }
147
+ // A mock's situations, only once it has any, so the views of mocks without
148
+ // them are unchanged.
149
+ const situationsOf = (mock) => Object.keys(mock.situations).length ? { situations: mock.situations } : {};
117
150
  export const mockSummaryView = (mock, posts) => {
118
151
  const drafted = draftedCount(mock, posts);
119
152
  return {
@@ -122,14 +155,16 @@ export const mockSummaryView = (mock, posts) => {
122
155
  slug: mock.slug,
123
156
  title: mock.title,
124
157
  kind: mock.kind,
158
+ ...placeOf(mock),
125
159
  states: mock.states,
126
- stateCount: Math.max(mock.states.length, 1),
160
+ ...situationsOf(mock),
161
+ stateCount: Math.max(momentsOf(mock.states, mock.situations).length, 1),
127
162
  variants: posts.length,
128
163
  open: openCount(mock, posts),
129
164
  // Only once the user has started one, so a list with no drafts costs nothing.
130
165
  ...(drafted === null ? {} : { drafted }),
131
166
  sessionId: mock.sessionId,
132
- resolvedAt: mock.resolvedAt,
167
+ closedAt: mock.closedAt,
133
168
  createdAt: mock.createdAt,
134
169
  updatedAt: mock.updatedAt,
135
170
  thumbnail: thumbnail(mock, posts),
@@ -143,13 +178,15 @@ export const mockDetailView = (mock, posts, opts = {}) => ({
143
178
  slug: mock.slug,
144
179
  title: mock.title,
145
180
  kind: mock.kind,
181
+ ...placeOf(mock),
146
182
  states: mock.states,
183
+ ...situationsOf(mock),
147
184
  asks: mock.asks,
148
185
  open: openCount(mock, posts),
149
186
  knobs: mock.knobs,
150
187
  widths: mock.widths,
151
188
  sessionId: mock.sessionId,
152
- resolvedAt: mock.resolvedAt,
189
+ closedAt: mock.closedAt,
153
190
  createdAt: mock.createdAt,
154
191
  updatedAt: mock.updatedAt,
155
192
  variants: posts.map((p) => variantView(p, opts)),
@@ -164,35 +201,39 @@ export const sessionRowView = (session, postCount) => ({
164
201
  postCount,
165
202
  });
166
203
  // One mock as a node of the project tree (GET /api/projects/:name/tree): its
167
- // variants per state with status and lineage, and the mocks it embeds (`uses`);
168
- // `usedIn` is filled in once every node is known.
204
+ // variants per state with status and lineage, and the mocks it embeds (`embeds`);
205
+ // `embeddedIn` is filled in once every node is known.
169
206
  export const treeNodeView = (mock, posts, extra) => ({
170
207
  id: mock.id,
171
208
  slug: mock.slug,
172
209
  title: mock.title,
173
210
  kind: mock.kind,
211
+ ...placeOf(mock),
174
212
  open: openCount(mock, posts),
175
- states: mock.states.length ? mock.states : [null],
213
+ // Home shows a mock's states, never their situations.
214
+ states: mock.states.length ? momentsOf(mock.states, mock.situations) : [null],
176
215
  widths: mock.widths,
177
216
  createdAt: mock.createdAt,
178
217
  updatedAt: mock.updatedAt,
179
- resolvedAt: mock.resolvedAt,
218
+ closedAt: mock.closedAt,
180
219
  settled: extra.settled,
181
- uses: extra.uses,
182
- usedIn: [],
220
+ embeds: extra.embeds,
221
+ embeddedIn: [],
183
222
  inUse: inUseByState(mock, posts),
184
223
  variants: posts.map((p) => ({
185
224
  postId: p.id,
186
225
  state: p.state,
187
226
  variant: p.variant,
188
- status: p.status,
227
+ status: variantStatus(p.status),
189
228
  version: p.version,
190
229
  updatedAt: p.updatedAt,
191
230
  surface: p.surfaces.findIndex((x) => isSandboxedSurfaceKind(x.kind)),
231
+ ...posterOf(p.surfaces),
232
+ ...inUsePoster(p),
192
233
  ...(p.note ? { note: p.note } : {}),
193
234
  ...(p.forkedFrom ? { forkedFrom: p.forkedFrom } : {}),
194
235
  ...(p.starred ? { starred: true } : {}),
195
- ...(p.acceptedVersion === undefined ? {} : { acceptedVersion: p.acceptedVersion }),
196
- ...(extra.embeds.get(p.id) ? { embedsKey: extra.embeds.get(p.id).key } : {}),
236
+ ...(p.acceptedVersion === undefined ? {} : { inUseVersion: p.acceptedVersion }),
237
+ ...(extra.embedViews.get(p.id) ? { embedsKey: extra.embedViews.get(p.id).key } : {}),
197
238
  })),
198
239
  });
@@ -1,6 +1,7 @@
1
1
  import { Hono } from "hono";
2
2
  import { type FeedEvent } from "./events.ts";
3
3
  import { type GuideTopic } from "./designGuide.ts";
4
+ import { type GuidePack } from "./guidance.ts";
4
5
  import { type Executor, type RunLimits } from "./run.ts";
5
6
  import { type LogSink } from "./eventLog.ts";
6
7
  import { type Store } from "./types.ts";
@@ -14,6 +15,8 @@ export interface AppOptions {
14
15
  viewerHtml: string;
15
16
  setupText: string;
16
17
  topics?: Partial<Record<GuideTopic, string>>;
18
+ packs?: Partial<Record<GuidePack, string>>;
19
+ userRules?: () => Promise<string | null>;
17
20
  authenticate?: AuthenticateHook;
18
21
  authToken?: string;
19
22
  basePath?: BasePathHook;
@@ -53,4 +56,4 @@ export interface FlowContext {
53
56
  request?: Request;
54
57
  signal?: AbortSignal;
55
58
  }
56
- export declare function createApp({ store, viewerHtml, setupText, topics, authenticate, authToken, basePath, publicRead, screenshots, version, upgradeCommand, renderRev, fetchLatestRelease, onEvent, maxHoldConnections, executor, runLimits, log, }: AppOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
59
+ export declare function createApp({ store, viewerHtml, setupText, topics, packs, userRules, authenticate, authToken, basePath, publicRead, screenshots, version, upgradeCommand, renderRev, fetchLatestRelease, onEvent, maxHoldConnections, executor, runLimits, log, }: AppOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;