mockpit 1.2.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 (59) hide show
  1. package/README.md +69 -48
  2. package/bin/demoData.js +3 -3
  3. package/bin/mockpit.js +426 -51
  4. package/dist/mcp/server.js +45 -7
  5. package/dist/server/apiViews.d.ts +407 -21
  6. package/dist/server/apiViews.js +110 -11
  7. package/dist/server/app.d.ts +5 -1
  8. package/dist/server/app.js +1454 -192
  9. package/dist/server/codeWorker.js +2 -1
  10. package/dist/server/decisions.d.ts +23 -0
  11. package/dist/server/decisions.js +100 -0
  12. package/dist/server/designGuide.d.ts +6 -2
  13. package/dist/server/designGuide.js +96 -27
  14. package/dist/server/embeds.d.ts +26 -0
  15. package/dist/server/embeds.js +144 -0
  16. package/dist/server/eventLog.js +2 -1
  17. package/dist/server/feedbackBatch.d.ts +15 -4
  18. package/dist/server/feedbackBatch.js +31 -7
  19. package/dist/server/fills.d.ts +41 -0
  20. package/dist/server/fills.js +328 -0
  21. package/dist/server/guidance.d.ts +23 -0
  22. package/dist/server/guidance.js +71 -0
  23. package/dist/server/index.js +25 -2
  24. package/dist/server/knobs.d.ts +6 -2
  25. package/dist/server/knobs.js +11 -1
  26. package/dist/server/mcpHttp.d.ts +6 -0
  27. package/dist/server/mcpHttp.js +35 -0
  28. package/dist/server/mcpSpec.d.ts +14 -13
  29. package/dist/server/mcpSpec.js +133 -18
  30. package/dist/server/postSurfaces.js +6 -12
  31. package/dist/server/richRender.js +3 -1
  32. package/dist/server/run.js +10 -1
  33. package/dist/server/runApi.d.ts +3 -3
  34. package/dist/server/runApi.js +18 -13
  35. package/dist/server/sqlStore.d.ts +97 -5
  36. package/dist/server/sqlStore.js +367 -27
  37. package/dist/server/surfacePage.d.ts +6 -2
  38. package/dist/server/surfacePage.js +142 -10
  39. package/dist/server/themes.d.ts +2 -0
  40. package/dist/server/themes.js +23 -3
  41. package/dist/server/types.d.ts +107 -16
  42. package/dist/server/types.js +73 -10
  43. package/dist/server/viewerRequest.d.ts +2 -0
  44. package/dist/server/viewerRequest.js +14 -0
  45. package/extensions/mockpit.js +179 -16
  46. package/guide/packs/design-craft.md +35 -0
  47. package/guide/topics/asks.md +82 -31
  48. package/guide/topics/feedback.md +151 -43
  49. package/guide/topics/html.md +8 -10
  50. package/guide/topics/http.md +8 -4
  51. package/guide/topics/knobs.md +4 -3
  52. package/guide/topics/scripts.md +2 -2
  53. package/guide/topics/surfaces.md +45 -11
  54. package/package.json +3 -15
  55. package/skills/launch-video/SKILL.md +2 -2
  56. package/skills/mockpit/SKILL.md +23 -7
  57. package/viewer/dist/index.html +9 -8
  58. package/dist/server/slots.d.ts +0 -14
  59. package/dist/server/slots.js +0 -71
@@ -1,7 +1,8 @@
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
+ import { slotKnobs, slotsInSurfaces } from "./fills.js";
3
4
  import { mergeParts, partsInSurfaces } from "./parts.js";
4
- import { isSandboxedSurfaceKind, draftedCount, openCount, SURFACE_CONTENT_FIELDS, } from "./types.js";
5
+ import { draftedCount, inUseVersion, isSandboxedSurfaceKind, isLive, momentsOf, openCount, SURFACE_CONTENT_FIELDS, variantStatus, } from "./types.js";
5
6
  export const surfaceRef = (surface, index) => ({
6
7
  id: surface.id,
7
8
  kind: surface.kind,
@@ -24,6 +25,27 @@ export const hydratedSurfaceView = (surface, index) => {
24
25
  delete view[field];
25
26
  return view;
26
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
+ };
27
49
  // One past version without its bodies: enough to list versions or pick one to
28
50
  // fetch, at a bounded cost.
29
51
  export const historyMetaView = (version) => ({
@@ -34,12 +56,13 @@ export const historyMetaView = (version) => ({
34
56
  ...(version.prompt ? { prompt: version.prompt } : {}),
35
57
  ...(version.author === undefined ? {} : { author: version.author }),
36
58
  surfaceKinds: version.surfaces.map((s) => s.kind),
59
+ ...posterOf(version.surfaces),
37
60
  });
38
61
  export const variantView = (post, opts = {}) => ({
39
62
  postId: post.id,
40
63
  state: post.state,
41
64
  variant: post.variant,
42
- status: post.status,
65
+ status: variantStatus(post.status),
43
66
  version: post.version,
44
67
  title: post.title,
45
68
  sessionId: post.sessionId,
@@ -48,10 +71,23 @@ export const variantView = (post, opts = {}) => ({
48
71
  ...(post.from === undefined ? {} : { from: post.from }),
49
72
  ...(post.prompt ? { prompt: post.prompt } : {}),
50
73
  ...(post.author === undefined ? {} : { author: post.author }),
51
- ...(post.knobs ? { knobs: post.knobs } : {}),
52
- ...(post.slots.length ? { slots: post.slots } : {}),
74
+ // A part with fills is a select knob on its part (fills.ts); declared knobs win.
75
+ ...(slotsInSurfaces(post.surfaces).length || post.knobs
76
+ ? { knobs: { ...slotKnobs(slotsInSurfaces(post.surfaces)), ...post.knobs } }
77
+ : {}),
78
+ ...(slotsInSurfaces(post.surfaces).length ? { fills: slotsInSurfaces(post.surfaces) } : {}),
79
+ ...(post.note ? { note: post.note } : {}),
80
+ // Embedded components as they render now (server/embeds.ts); `publishedWith`
81
+ // is what this version rendered with when it was published.
82
+ ...(opts.embeds?.get(post.id)
83
+ ? { embeds: opts.embeds.get(post.id).list, embedsKey: opts.embeds.get(post.id).key }
84
+ : {}),
85
+ ...(post.embeds.length ? { publishedWith: post.embeds } : {}),
86
+ ...(post.forkedFrom ? { forkedFrom: post.forkedFrom } : {}),
87
+ ...(post.starred ? { starred: true } : {}),
88
+ ...(post.acceptedVersion === undefined ? {} : { inUseVersion: post.acceptedVersion }),
53
89
  // The part names this variant's own markup marks, so the viewer can tell
54
- // which looks render a part (Mix) without the html bodies.
90
+ // which variants render a part (Borrow) without the html bodies.
55
91
  parts: partsInSurfaces(post.surfaces).map((p) => p.name),
56
92
  surfaces: post.surfaces.map(opts.body ? fullSurfaceView : hydratedSurfaceView),
57
93
  ...(opts.history
@@ -67,20 +103,32 @@ export const variantView = (post, opts = {}) => ({
67
103
  ...(post.author === undefined ? {} : { author: post.author }),
68
104
  }),
69
105
  ...[...post.history].sort((a, b) => b.version - a.version).map(historyMetaView),
70
- ],
106
+ ].map((row) => post.hiddenVersions?.includes(row.version) ? { ...row, hidden: true } : row),
71
107
  }
72
108
  : {}),
73
109
  });
74
110
  const stateOrder = (mock) => (mock.states.length ? mock.states : [null]);
111
+ // What the user put in use, per state, in the mock's order; a state with
112
+ // nothing accepted is absent. Export and embeds resolve to exactly these.
113
+ export const inUseByState = (mock, posts) => stateOrder(mock).flatMap((state) => {
114
+ const p = posts.find((x) => x.state === state && x.status === "accepted");
115
+ return p ? [{ state, variant: p.variant, version: inUseVersion(p) }] : [];
116
+ });
75
117
  // The parts the agent marked, per state, across the variants still in play.
76
118
  export function partsByState(mock, posts) {
77
119
  return stateOrder(mock).map((state) => ({
78
120
  state,
79
121
  parts: mergeParts(posts
80
- .filter((p) => p.state === state && p.status !== "archived")
122
+ .filter((p) => p.state === state && isLive(p.status))
81
123
  .map((p) => partsInSurfaces(p.surfaces))),
82
124
  }));
83
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
+ });
84
132
  // What Home shows for a mock: the first state's chosen (else first) variant.
85
133
  function thumbnail(mock, posts) {
86
134
  const first = stateOrder(mock)[0];
@@ -92,8 +140,13 @@ function thumbnail(mock, posts) {
92
140
  if (!pick)
93
141
  return null;
94
142
  const surface = pick.surfaces.findIndex((s) => isSandboxedSurfaceKind(s.kind));
95
- return { postId: pick.id, surface: Math.max(surface, 0), version: pick.version };
143
+ const version = inUseVersion(pick);
144
+ const poster = posterAt(pick, version);
145
+ return { postId: pick.id, surface: Math.max(surface, 0), version, ...(poster ? { poster } : {}) };
96
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 } : {};
97
150
  export const mockSummaryView = (mock, posts) => {
98
151
  const drafted = draftedCount(mock, posts);
99
152
  return {
@@ -102,13 +155,16 @@ export const mockSummaryView = (mock, posts) => {
102
155
  slug: mock.slug,
103
156
  title: mock.title,
104
157
  kind: mock.kind,
158
+ ...placeOf(mock),
105
159
  states: mock.states,
106
- stateCount: Math.max(mock.states.length, 1),
160
+ ...situationsOf(mock),
161
+ stateCount: Math.max(momentsOf(mock.states, mock.situations).length, 1),
107
162
  variants: posts.length,
108
163
  open: openCount(mock, posts),
109
164
  // Only once the user has started one, so a list with no drafts costs nothing.
110
165
  ...(drafted === null ? {} : { drafted }),
111
166
  sessionId: mock.sessionId,
167
+ closedAt: mock.closedAt,
112
168
  createdAt: mock.createdAt,
113
169
  updatedAt: mock.updatedAt,
114
170
  thumbnail: thumbnail(mock, posts),
@@ -122,19 +178,62 @@ export const mockDetailView = (mock, posts, opts = {}) => ({
122
178
  slug: mock.slug,
123
179
  title: mock.title,
124
180
  kind: mock.kind,
181
+ ...placeOf(mock),
125
182
  states: mock.states,
183
+ ...situationsOf(mock),
126
184
  asks: mock.asks,
127
185
  open: openCount(mock, posts),
128
186
  knobs: mock.knobs,
187
+ widths: mock.widths,
129
188
  sessionId: mock.sessionId,
189
+ closedAt: mock.closedAt,
130
190
  createdAt: mock.createdAt,
131
191
  updatedAt: mock.updatedAt,
132
192
  variants: posts.map((p) => variantView(p, opts)),
193
+ inUse: inUseByState(mock, posts),
133
194
  parts: partsByState(mock, posts),
134
- // The knob values of the latest reply — what the user last sent, not drafts.
135
- tuned: opts.tuned ?? {},
195
+ // Every knob value the user has sent (answers' sets and tuning, later replies
196
+ // winning), never drafts. Later questions render on top of it.
197
+ settled: opts.settled ?? {},
136
198
  });
137
199
  export const sessionRowView = (session, postCount) => ({
138
200
  ...session,
139
201
  postCount,
140
202
  });
203
+ // One mock as a node of the project tree (GET /api/projects/:name/tree): its
204
+ // variants per state with status and lineage, and the mocks it embeds (`embeds`);
205
+ // `embeddedIn` is filled in once every node is known.
206
+ export const treeNodeView = (mock, posts, extra) => ({
207
+ id: mock.id,
208
+ slug: mock.slug,
209
+ title: mock.title,
210
+ kind: mock.kind,
211
+ ...placeOf(mock),
212
+ open: openCount(mock, posts),
213
+ // Home shows a mock's states, never their situations.
214
+ states: mock.states.length ? momentsOf(mock.states, mock.situations) : [null],
215
+ widths: mock.widths,
216
+ createdAt: mock.createdAt,
217
+ updatedAt: mock.updatedAt,
218
+ closedAt: mock.closedAt,
219
+ settled: extra.settled,
220
+ embeds: extra.embeds,
221
+ embeddedIn: [],
222
+ inUse: inUseByState(mock, posts),
223
+ variants: posts.map((p) => ({
224
+ postId: p.id,
225
+ state: p.state,
226
+ variant: p.variant,
227
+ status: variantStatus(p.status),
228
+ version: p.version,
229
+ updatedAt: p.updatedAt,
230
+ surface: p.surfaces.findIndex((x) => isSandboxedSurfaceKind(x.kind)),
231
+ ...posterOf(p.surfaces),
232
+ ...inUsePoster(p),
233
+ ...(p.note ? { note: p.note } : {}),
234
+ ...(p.forkedFrom ? { forkedFrom: p.forkedFrom } : {}),
235
+ ...(p.starred ? { starred: true } : {}),
236
+ ...(p.acceptedVersion === undefined ? {} : { inUseVersion: p.acceptedVersion }),
237
+ ...(extra.embedViews.get(p.id) ? { embedsKey: extra.embedViews.get(p.id).key } : {}),
238
+ })),
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;
@@ -21,6 +24,7 @@ export interface AppOptions {
21
24
  screenshots?: boolean;
22
25
  version?: string;
23
26
  upgradeCommand?: string;
27
+ renderRev?: string;
24
28
  fetchLatestRelease?: () => Promise<LatestRelease | null>;
25
29
  onEvent?: (event: FeedEvent) => void;
26
30
  maxHoldConnections?: number;
@@ -52,4 +56,4 @@ export interface FlowContext {
52
56
  request?: Request;
53
57
  signal?: AbortSignal;
54
58
  }
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, "/">;
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, "/">;