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.
- package/README.md +69 -48
- package/bin/demoData.js +3 -3
- package/bin/mockpit.js +426 -51
- package/dist/mcp/server.js +45 -7
- package/dist/server/apiViews.d.ts +407 -21
- package/dist/server/apiViews.js +110 -11
- package/dist/server/app.d.ts +5 -1
- package/dist/server/app.js +1454 -192
- package/dist/server/codeWorker.js +2 -1
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +100 -0
- package/dist/server/designGuide.d.ts +6 -2
- package/dist/server/designGuide.js +96 -27
- package/dist/server/embeds.d.ts +26 -0
- package/dist/server/embeds.js +144 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +15 -4
- package/dist/server/feedbackBatch.js +31 -7
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/guidance.d.ts +23 -0
- package/dist/server/guidance.js +71 -0
- package/dist/server/index.js +25 -2
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +11 -1
- package/dist/server/mcpHttp.d.ts +6 -0
- package/dist/server/mcpHttp.js +35 -0
- package/dist/server/mcpSpec.d.ts +14 -13
- package/dist/server/mcpSpec.js +133 -18
- package/dist/server/postSurfaces.js +6 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +10 -1
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +18 -13
- package/dist/server/sqlStore.d.ts +97 -5
- package/dist/server/sqlStore.js +367 -27
- package/dist/server/surfacePage.d.ts +6 -2
- package/dist/server/surfacePage.js +142 -10
- package/dist/server/themes.d.ts +2 -0
- package/dist/server/themes.js +23 -3
- package/dist/server/types.d.ts +107 -16
- package/dist/server/types.js +73 -10
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/extensions/mockpit.js +179 -16
- package/guide/packs/design-craft.md +35 -0
- package/guide/topics/asks.md +82 -31
- package/guide/topics/feedback.md +151 -43
- package/guide/topics/html.md +8 -10
- package/guide/topics/http.md +8 -4
- package/guide/topics/knobs.md +4 -3
- package/guide/topics/scripts.md +2 -2
- package/guide/topics/surfaces.md +45 -11
- package/package.json +3 -15
- package/skills/launch-video/SKILL.md +2 -2
- package/skills/mockpit/SKILL.md +23 -7
- package/viewer/dist/index.html +9 -8
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
package/dist/server/types.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export interface HtmlSurface {
|
|
|
68
68
|
kind: "html";
|
|
69
69
|
html: string;
|
|
70
70
|
kits?: string[];
|
|
71
|
+
poster?: string;
|
|
71
72
|
}
|
|
72
73
|
export interface MarkdownSurface {
|
|
73
74
|
kind: "markdown";
|
|
@@ -196,15 +197,27 @@ export interface PadValue {
|
|
|
196
197
|
y: number;
|
|
197
198
|
}
|
|
198
199
|
export type KnobValue = number | boolean | string | PadValue | SpringKnob | EasingKnob;
|
|
199
|
-
export type MockKind = "page" | "component";
|
|
200
|
+
export type MockKind = "page" | "component" | "sketch";
|
|
201
|
+
export declare const isMockKind: (k: unknown) => k is MockKind;
|
|
200
202
|
export type PostStatus = "open" | "accepted" | "archived";
|
|
203
|
+
export type VariantStatus = "open" | "inUse" | "archived";
|
|
204
|
+
export declare const variantStatus: (status: PostStatus) => VariantStatus;
|
|
205
|
+
export declare const isLive: (status: PostStatus | VariantStatus) => boolean;
|
|
206
|
+
export declare const inUseVersion: (post: Pick<Post, "status" | "version" | "acceptedVersion">) => number;
|
|
207
|
+
export declare function postAtVersion(post: Post, version: number): Post | null;
|
|
208
|
+
export declare function versionAt(post: Pick<Post, "version" | "history">, n: number): number;
|
|
201
209
|
export interface AskOption {
|
|
202
210
|
id: string;
|
|
203
211
|
label: string;
|
|
204
212
|
variant?: string;
|
|
213
|
+
version?: number;
|
|
205
214
|
set?: Record<string, KnobValue>;
|
|
215
|
+
recommended?: true;
|
|
206
216
|
}
|
|
207
217
|
export type AskAnswer = string | string[];
|
|
218
|
+
export declare const optionState: (ask: Pick<Ask, "state">, mock: {
|
|
219
|
+
states: string[];
|
|
220
|
+
}) => string | null;
|
|
208
221
|
export interface Ask {
|
|
209
222
|
id: string;
|
|
210
223
|
text: string;
|
|
@@ -213,11 +226,13 @@ export interface Ask {
|
|
|
213
226
|
part?: string;
|
|
214
227
|
options: AskOption[];
|
|
215
228
|
multi?: boolean;
|
|
229
|
+
after?: string;
|
|
216
230
|
answer?: AskAnswer;
|
|
217
231
|
other?: string;
|
|
218
232
|
note?: string;
|
|
219
233
|
otherImages?: string[];
|
|
220
234
|
noteImages?: string[];
|
|
235
|
+
dismissed?: true;
|
|
221
236
|
at: string;
|
|
222
237
|
}
|
|
223
238
|
export declare const OTHER_ID = "other";
|
|
@@ -237,16 +252,22 @@ export interface PartComment {
|
|
|
237
252
|
anchor?: PartCommentAnchor;
|
|
238
253
|
reaction?: Reaction;
|
|
239
254
|
variant?: string;
|
|
255
|
+
version?: number;
|
|
256
|
+
borrow?: {
|
|
257
|
+
from: string;
|
|
258
|
+
part?: string;
|
|
259
|
+
};
|
|
240
260
|
}
|
|
241
|
-
export interface
|
|
242
|
-
kind: "
|
|
261
|
+
export interface ReplyVerdict {
|
|
262
|
+
kind: "use" | "revise";
|
|
243
263
|
state: string | null;
|
|
244
264
|
variant: string;
|
|
265
|
+
version?: number;
|
|
245
266
|
}
|
|
246
267
|
export interface Draft {
|
|
247
268
|
version: number;
|
|
248
269
|
answers: Record<string, AskAnswer>;
|
|
249
|
-
|
|
270
|
+
borrowed: Record<string, string>;
|
|
250
271
|
tuned: Record<string, KnobValue>;
|
|
251
272
|
comments: PartComment[];
|
|
252
273
|
others?: Record<string, string>;
|
|
@@ -259,7 +280,7 @@ export interface Reply {
|
|
|
259
280
|
mockId: string;
|
|
260
281
|
version: number;
|
|
261
282
|
answers: Record<string, AskAnswer>;
|
|
262
|
-
|
|
283
|
+
borrowed: Record<string, string>;
|
|
263
284
|
tuned: Record<string, KnobValue>;
|
|
264
285
|
comments: PartComment[];
|
|
265
286
|
others?: Record<string, string>;
|
|
@@ -267,26 +288,45 @@ export interface Reply {
|
|
|
267
288
|
otherImages?: Record<string, string[]>;
|
|
268
289
|
noteImages?: Record<string, string[]>;
|
|
269
290
|
text?: string;
|
|
270
|
-
|
|
291
|
+
verdict?: ReplyVerdict;
|
|
292
|
+
closed?: {
|
|
293
|
+
dismissed: string[];
|
|
294
|
+
};
|
|
295
|
+
edited?: MockEdit;
|
|
296
|
+
}
|
|
297
|
+
export interface MockEdit {
|
|
298
|
+
kind?: MockKind;
|
|
299
|
+
area?: string | null;
|
|
271
300
|
}
|
|
301
|
+
export declare const AREA_ORDER_SETTING = "areaOrder";
|
|
272
302
|
export interface Mock {
|
|
273
303
|
id: string;
|
|
274
304
|
project: string;
|
|
275
305
|
slug: string;
|
|
276
306
|
title: string;
|
|
277
307
|
kind: MockKind;
|
|
308
|
+
area: string | null;
|
|
309
|
+
source: string | null;
|
|
278
310
|
states: string[];
|
|
311
|
+
situations: Record<string, string[]>;
|
|
279
312
|
asks: Ask[];
|
|
280
313
|
knobs: Knobs;
|
|
314
|
+
widths: number[];
|
|
281
315
|
draft: Draft | null;
|
|
282
316
|
sessionId: string | null;
|
|
317
|
+
closedAt: string | null;
|
|
283
318
|
createdAt: string;
|
|
284
319
|
updatedAt: string;
|
|
285
320
|
}
|
|
286
|
-
export
|
|
321
|
+
export declare const NORMAL_SITUATION = "Normal";
|
|
322
|
+
export declare const situationKey: (state: string, situation: string) => string;
|
|
323
|
+
export declare function momentOf(situations: Record<string, string[]>, state: string | null): string | null;
|
|
324
|
+
export declare const momentsOf: (states: string[], situations: Record<string, string[]>) => string[];
|
|
325
|
+
export interface Embed {
|
|
287
326
|
slug: string;
|
|
288
327
|
variant: string;
|
|
289
328
|
version: number;
|
|
329
|
+
pinned?: true;
|
|
290
330
|
}
|
|
291
331
|
export interface PostVersion {
|
|
292
332
|
version: number;
|
|
@@ -297,6 +337,10 @@ export interface PostVersion {
|
|
|
297
337
|
prompt?: string;
|
|
298
338
|
author?: string;
|
|
299
339
|
}
|
|
340
|
+
export interface ForkRef {
|
|
341
|
+
variant: string;
|
|
342
|
+
version: number;
|
|
343
|
+
}
|
|
300
344
|
export interface Post {
|
|
301
345
|
id: string;
|
|
302
346
|
sessionId: string;
|
|
@@ -311,11 +355,28 @@ export interface Post {
|
|
|
311
355
|
version: number;
|
|
312
356
|
history: PostVersion[];
|
|
313
357
|
knobs?: Knobs;
|
|
314
|
-
|
|
358
|
+
note?: string;
|
|
359
|
+
embeds: Embed[];
|
|
360
|
+
forkedFrom?: ForkRef;
|
|
361
|
+
starred?: true;
|
|
362
|
+
acceptedVersion?: number;
|
|
363
|
+
hiddenVersions?: number[];
|
|
315
364
|
from?: number;
|
|
316
365
|
prompt?: string;
|
|
317
366
|
author?: string;
|
|
318
367
|
}
|
|
368
|
+
export interface FlowStep {
|
|
369
|
+
mock: string;
|
|
370
|
+
state: string | null;
|
|
371
|
+
}
|
|
372
|
+
export interface Flow {
|
|
373
|
+
project: string;
|
|
374
|
+
name: string;
|
|
375
|
+
steps: FlowStep[];
|
|
376
|
+
createdAt: string;
|
|
377
|
+
updatedAt: string;
|
|
378
|
+
}
|
|
379
|
+
export declare const sameScreen: (a: FlowStep, b: FlowStep) => boolean;
|
|
319
380
|
export interface ProjectSummary {
|
|
320
381
|
name: string;
|
|
321
382
|
mocks: number;
|
|
@@ -468,17 +529,25 @@ export interface CreateMockInput {
|
|
|
468
529
|
slug: string;
|
|
469
530
|
title?: string;
|
|
470
531
|
kind?: MockKind;
|
|
532
|
+
area?: string | null;
|
|
533
|
+
source?: string | null;
|
|
471
534
|
states?: string[];
|
|
472
535
|
knobs?: Knobs;
|
|
536
|
+
widths?: number[];
|
|
473
537
|
sessionId?: string | null;
|
|
474
538
|
}
|
|
475
539
|
export interface UpdateMockInput {
|
|
476
540
|
title?: string;
|
|
477
541
|
kind?: MockKind;
|
|
542
|
+
area?: string | null;
|
|
543
|
+
source?: string | null;
|
|
478
544
|
states?: string[];
|
|
545
|
+
situations?: Record<string, string[]>;
|
|
479
546
|
asks?: Ask[];
|
|
480
547
|
knobs?: Knobs;
|
|
548
|
+
widths?: number[];
|
|
481
549
|
sessionId?: string | null;
|
|
550
|
+
closedAt?: string | null;
|
|
482
551
|
}
|
|
483
552
|
export interface CreatePostInput {
|
|
484
553
|
sessionId: string;
|
|
@@ -488,7 +557,9 @@ export interface CreatePostInput {
|
|
|
488
557
|
title?: string;
|
|
489
558
|
surfaces: Surface[];
|
|
490
559
|
knobs?: Knobs;
|
|
491
|
-
|
|
560
|
+
note?: string;
|
|
561
|
+
embeds?: Embed[];
|
|
562
|
+
forkedFrom?: ForkRef;
|
|
492
563
|
from?: number;
|
|
493
564
|
prompt?: string;
|
|
494
565
|
author?: string;
|
|
@@ -497,7 +568,8 @@ export interface UpdatePostInput {
|
|
|
497
568
|
title?: string;
|
|
498
569
|
surfaces?: Surface[];
|
|
499
570
|
knobs?: Knobs | null;
|
|
500
|
-
|
|
571
|
+
note?: string;
|
|
572
|
+
embeds?: Embed[];
|
|
501
573
|
from?: number;
|
|
502
574
|
prompt?: string;
|
|
503
575
|
author?: string;
|
|
@@ -528,8 +600,13 @@ export interface CommitReplyInput {
|
|
|
528
600
|
text: string;
|
|
529
601
|
payload: Reply;
|
|
530
602
|
asks: Ask[];
|
|
531
|
-
accept:
|
|
603
|
+
accept: {
|
|
604
|
+
id: string;
|
|
605
|
+
version: number;
|
|
606
|
+
}[];
|
|
532
607
|
archive: string[];
|
|
608
|
+
closedAt?: string;
|
|
609
|
+
keepDraft?: true;
|
|
533
610
|
}
|
|
534
611
|
export interface PostQuery {
|
|
535
612
|
mockId?: string;
|
|
@@ -554,6 +631,10 @@ export interface Store {
|
|
|
554
631
|
createMock(input: CreateMockInput): Promise<Mock>;
|
|
555
632
|
updateMock(id: string, patch: UpdateMockInput): Promise<Mock | null>;
|
|
556
633
|
removeMock(id: string): Promise<boolean>;
|
|
634
|
+
renameMock(id: string, slug: string): Promise<Mock | null>;
|
|
635
|
+
renameProject(from: string, to: string): Promise<boolean>;
|
|
636
|
+
listFlows(project: string): Promise<Flow[]>;
|
|
637
|
+
placeFlowStep(project: string, name: string, screen: FlowStep, step: number): Promise<Flow | null>;
|
|
557
638
|
putDraft(mockId: string, draft: Draft | null): Promise<Mock | null>;
|
|
558
639
|
listPosts(query?: PostQuery): Promise<Post[]>;
|
|
559
640
|
countPostsBySession(): Promise<Map<string, number>>;
|
|
@@ -562,7 +643,9 @@ export interface Store {
|
|
|
562
643
|
createPost(input: CreatePostInput): Promise<Post | null>;
|
|
563
644
|
updatePost(id: string, patch: UpdatePostInput): Promise<Post | null>;
|
|
564
645
|
removePost(id: string): Promise<boolean>;
|
|
565
|
-
setPostStatus(id: string, status: PostStatus): Promise<Post | null>;
|
|
646
|
+
setPostStatus(id: string, status: PostStatus, acceptedVersion?: number): Promise<Post | null>;
|
|
647
|
+
setPostStarred(id: string, starred: boolean): Promise<Post | null>;
|
|
648
|
+
setVersionHidden(id: string, version: number, hidden: boolean): Promise<Post | null>;
|
|
566
649
|
listComments(query: CommentQuery): Promise<Comment[]>;
|
|
567
650
|
createComment(input: CreateCommentInput): Promise<Comment | null>;
|
|
568
651
|
removeComment(id: string): Promise<Comment | null>;
|
|
@@ -600,10 +683,18 @@ export declare function projectFromCwd(cwd: string | null | undefined): string |
|
|
|
600
683
|
export declare const htmlSurface: (html: string, kits?: unknown) => HtmlSurface;
|
|
601
684
|
export declare const askAnswered: (a: Pick<Ask, "answer" | "other" | "note">) => boolean;
|
|
602
685
|
export declare const openAsks: (mock: Pick<Mock, "asks">) => Ask[];
|
|
603
|
-
export declare function builtinAskIds(mock: Pick<Mock, "asks" | "states"
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
export declare
|
|
686
|
+
export declare function builtinAskIds(mock: Pick<Mock, "asks" | "states"> & {
|
|
687
|
+
closedAt?: string | null;
|
|
688
|
+
}, posts: Pick<Post, "mock" | "state" | "variant" | "status">[], mockId: string): string[];
|
|
689
|
+
export declare const builtinAskCount: (mock: Pick<Mock, "asks" | "states"> & {
|
|
690
|
+
closedAt?: string | null;
|
|
691
|
+
}, posts: Pick<Post, "mock" | "state" | "variant" | "status">[], mockId: string) => number;
|
|
692
|
+
export declare const openCount: (mock: Pick<Mock, "id" | "asks" | "states"> & {
|
|
693
|
+
closedAt?: string | null;
|
|
694
|
+
}, posts: Pick<Post, "mock" | "state" | "variant" | "status">[]) => number;
|
|
695
|
+
export declare function draftedCount(mock: Pick<Mock, "id" | "asks" | "states" | "draft"> & {
|
|
696
|
+
closedAt?: string | null;
|
|
697
|
+
}, posts: Pick<Post, "mock" | "state" | "variant" | "status">[]): number | null;
|
|
607
698
|
export declare const BUILTIN_ASK_ID = "variant";
|
|
608
699
|
export declare function builtinAsk(mock: Pick<Mock, "states">, posts: Pick<Post, "state" | "variant">[], id: string): Ask | undefined;
|
|
609
700
|
export declare function summarizeProjects(mocks: Mock[], sessions: Session[], posts?: Post[]): ProjectSummary[];
|
package/dist/server/types.js
CHANGED
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
// build their `kind` enums from it, so a kind can't be added to the model
|
|
10
10
|
// without the MCP tier advertising it too (the gap that left `json`/`code`
|
|
11
11
|
// publishable over CLI/REST but invisible to MCP). The per-kind FIELD schemas
|
|
12
|
-
// in postSurfaces.ts and mcpSpec.ts are still hand-written
|
|
13
|
-
//
|
|
14
|
-
// validator with its fields, so neither half can silently fall behind.
|
|
12
|
+
// in postSurfaces.ts and mcpSpec.ts are still hand-written: a kind added here
|
|
13
|
+
// needs its fields in both, or one half silently falls behind.
|
|
15
14
|
export const SURFACE_KINDS = [
|
|
16
15
|
"html",
|
|
17
16
|
"diff",
|
|
@@ -51,6 +50,44 @@ export function isSurfaceKind(kind) {
|
|
|
51
50
|
export function isSandboxedSurfaceKind(kind) {
|
|
52
51
|
return isSurfaceKind(kind) && SURFACE_KIND_METADATA[kind].sandboxed;
|
|
53
52
|
}
|
|
53
|
+
export const isMockKind = (k) => k === "page" || k === "component" || k === "sketch";
|
|
54
|
+
export const variantStatus = (status) => status === "accepted" ? "inUse" : status;
|
|
55
|
+
// Still in the running: what the user can choose between.
|
|
56
|
+
export const isLive = (status) => status === "open" || status === "accepted" || status === "inUse";
|
|
57
|
+
// The version a variant is resolved at when nothing names one: the version in
|
|
58
|
+
// use when accepted, else its latest.
|
|
59
|
+
export const inUseVersion = (post) => post.status === "accepted" ? (post.acceptedVersion ?? post.version) : post.version;
|
|
60
|
+
// A variant as it stood at one of its versions (current or retained), or null
|
|
61
|
+
// when that version is gone.
|
|
62
|
+
export function postAtVersion(post, version) {
|
|
63
|
+
if (version === post.version)
|
|
64
|
+
return post;
|
|
65
|
+
const old = post.history.find((h) => h.version === version);
|
|
66
|
+
if (!old)
|
|
67
|
+
return null;
|
|
68
|
+
const { from: _f, prompt: _p, author: _a, ...rest } = post;
|
|
69
|
+
return {
|
|
70
|
+
...rest,
|
|
71
|
+
version: old.version,
|
|
72
|
+
title: old.title,
|
|
73
|
+
surfaces: old.surfaces,
|
|
74
|
+
updatedAt: old.at,
|
|
75
|
+
...(old.from === undefined ? {} : { from: old.from }),
|
|
76
|
+
...(old.prompt === undefined ? {} : { prompt: old.prompt }),
|
|
77
|
+
...(old.author === undefined ? {} : { author: old.author }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// The variant's newest version not after mock version `n` — what the stage
|
|
81
|
+
// showed when a draft or reply was bound to n (the viewer's frameVersion).
|
|
82
|
+
export function versionAt(post, n) {
|
|
83
|
+
const versions = [post.version, ...post.history.map((h) => h.version)];
|
|
84
|
+
const fit = versions.filter((v) => v <= n);
|
|
85
|
+
return fit.length ? Math.max(...fit) : Math.min(...versions);
|
|
86
|
+
}
|
|
87
|
+
// The state a version option names a version in: the ask's state, else the
|
|
88
|
+
// mock's first (`ask` accepts version options without a state only while the
|
|
89
|
+
// mock has one).
|
|
90
|
+
export const optionState = (ask, mock) => ask.state ?? mock.states[0] ?? null;
|
|
54
91
|
// The viewer's write-in option on every ask. Reserved, so a delivered
|
|
55
92
|
// `{id: "other", other: true}` choice never shadows an option the agent declared.
|
|
56
93
|
export const OTHER_ID = "other";
|
|
@@ -58,6 +95,26 @@ export const OTHER_ID = "other";
|
|
|
58
95
|
// for a reviewer on a phone who would rather not type: like, dislike, or
|
|
59
96
|
// "more like this". Rides as a part comment, so it travels in the one reply.
|
|
60
97
|
export const REACTIONS = ["up", "down", "more"];
|
|
98
|
+
// The settings key prefix of a project's area order (`areaOrder:<project>`): a
|
|
99
|
+
// JSON array of area names, the order Home lists them in.
|
|
100
|
+
export const AREA_ORDER_SETTING = "areaOrder";
|
|
101
|
+
// The state's own render, when a state has situations.
|
|
102
|
+
export const NORMAL_SITUATION = "Normal";
|
|
103
|
+
// The state a situation is stored as: what feedback, decisions and export name.
|
|
104
|
+
export const situationKey = (state, situation) => `${state} · ${situation}`;
|
|
105
|
+
// The state a situation belongs to, or the state itself.
|
|
106
|
+
export function momentOf(situations, state) {
|
|
107
|
+
if (state === null)
|
|
108
|
+
return null;
|
|
109
|
+
for (const [moment, names] of Object.entries(situations)) {
|
|
110
|
+
if (names.some((n) => situationKey(moment, n) === state))
|
|
111
|
+
return moment;
|
|
112
|
+
}
|
|
113
|
+
return state;
|
|
114
|
+
}
|
|
115
|
+
// The states that are moments of the mock, not situations of one.
|
|
116
|
+
export const momentsOf = (states, situations) => states.filter((st) => momentOf(situations, st) === st);
|
|
117
|
+
export const sameScreen = (a, b) => a.mock === b.mock && a.state === b.state;
|
|
61
118
|
export const HISTORY_LIMIT = 20;
|
|
62
119
|
// "user" is the reserved trust label for genuine human comments. A session
|
|
63
120
|
// agent with that name could otherwise have its programmatic comments delivered
|
|
@@ -152,15 +209,18 @@ export const htmlSurface = (html, kits) => ({
|
|
|
152
209
|
// An open ask is one no reply has answered yet: a write-in or a note alone
|
|
153
210
|
// answers it too ("Neither, because…").
|
|
154
211
|
export const askAnswered = (a) => a.answer !== undefined || a.other !== undefined || a.note !== undefined;
|
|
155
|
-
export const openAsks = (mock) => mock.asks.filter((a) => !askAnswered(a));
|
|
156
|
-
// How many built-in "Which
|
|
212
|
+
export const openAsks = (mock) => mock.asks.filter((a) => !askAnswered(a) && !a.dismissed);
|
|
213
|
+
// How many built-in "Which variant?" questions the viewer shows: one mock-wide when every
|
|
157
214
|
// state needs one and the live names line up, else one per needing state. Same
|
|
158
215
|
// rule as viewer/src/logic.ts builtinAsks, so Home, `read` and `pending` count
|
|
159
216
|
// the question the user actually sees. Answering it archives the losers, which
|
|
160
217
|
// is what makes it stop counting.
|
|
161
218
|
export function builtinAskIds(mock, posts, mockId) {
|
|
219
|
+
// Closing the mock closed the choice along with every other open question.
|
|
220
|
+
if (mock.closedAt)
|
|
221
|
+
return [];
|
|
162
222
|
const live = (state) => posts
|
|
163
|
-
.filter((p) => p.mock === mockId && p.state === state && p.status
|
|
223
|
+
.filter((p) => p.mock === mockId && p.state === state && isLive(p.status))
|
|
164
224
|
.map((p) => p.variant);
|
|
165
225
|
const needs = (state) => {
|
|
166
226
|
const names = live(state);
|
|
@@ -196,7 +256,7 @@ export function draftedCount(mock, posts) {
|
|
|
196
256
|
};
|
|
197
257
|
return open.filter(said).length;
|
|
198
258
|
}
|
|
199
|
-
// The viewer's built-in "Which
|
|
259
|
+
// The viewer's built-in "Which variant?" for variants no agent ask binds: `variant`
|
|
200
260
|
// mock-wide, `variant:<state>` per state. Never stored; synthesized from the
|
|
201
261
|
// variants so a reply answering it validates, flips and reads like any ask. An
|
|
202
262
|
// option's id is the variant name, so the viewer needs no id mapping.
|
|
@@ -217,7 +277,7 @@ export function builtinAsk(mock, posts, id) {
|
|
|
217
277
|
return undefined;
|
|
218
278
|
return {
|
|
219
279
|
id,
|
|
220
|
-
text: "Which
|
|
280
|
+
text: "Which variant?",
|
|
221
281
|
scope: state === null ? "mock" : "state",
|
|
222
282
|
...(state === null ? {} : { state }),
|
|
223
283
|
options: names.map((v) => ({ id: v, label: v, variant: v })),
|
|
@@ -266,7 +326,7 @@ export function surfacesByteLength(surfaces) {
|
|
|
266
326
|
let n = 0;
|
|
267
327
|
for (const p of surfaces) {
|
|
268
328
|
if (p.kind === "html")
|
|
269
|
-
n += p.html.length;
|
|
329
|
+
n += p.html.length + (p.poster?.length ?? 0);
|
|
270
330
|
else if (p.kind === "diff") {
|
|
271
331
|
n += p.patch?.length ?? 0;
|
|
272
332
|
for (const f of p.files ?? [])
|
|
@@ -294,7 +354,8 @@ export function surfacesByteLength(surfaces) {
|
|
|
294
354
|
}
|
|
295
355
|
return n;
|
|
296
356
|
}
|
|
297
|
-
// Collect the asset ids an ordered surfaces list references (image surfaces
|
|
357
|
+
// Collect the asset ids an ordered surfaces list references (image surfaces
|
|
358
|
+
// and html posters).
|
|
298
359
|
// Used to keep referenced assets out of eviction's first wave. Assets embedded
|
|
299
360
|
// by raw URL inside html markup are invisible here — touch-on-serve keeps those
|
|
300
361
|
// warm instead.
|
|
@@ -302,6 +363,8 @@ export function collectAssetIds(surfaces, out) {
|
|
|
302
363
|
for (const p of surfaces) {
|
|
303
364
|
if (p.kind === "image")
|
|
304
365
|
out.add(p.assetId);
|
|
366
|
+
else if (p.kind === "html" && p.poster)
|
|
367
|
+
out.add(p.poster);
|
|
305
368
|
}
|
|
306
369
|
}
|
|
307
370
|
// The images a draft, a reply or a sent ask carries (all keep the user's
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Whether a request comes from the trusted viewer, the page this server
|
|
2
|
+
// served: only it may act as the user (drafts, replies, user comments).
|
|
3
|
+
// Fetch Metadata says so directly, but browsers omit it on plain http away
|
|
4
|
+
// from localhost (a LAN or tailnet address), where the viewer then looked
|
|
5
|
+
// like any other client and every draft call failed. The viewer also names
|
|
6
|
+
// itself with a custom header, which a cross-origin caller cannot send: that
|
|
7
|
+
// header forces a CORS preflight, and this server answers none, so a sandboxed
|
|
8
|
+
// surface (opaque origin) or another site is refused by the browser itself.
|
|
9
|
+
export const VIEWER_CLIENT = "viewer";
|
|
10
|
+
export function isViewerRequest(request) {
|
|
11
|
+
if (request.headers.get("sec-fetch-site") === "same-origin")
|
|
12
|
+
return true;
|
|
13
|
+
return request.headers.get("x-mockpit-client") === VIEWER_CLIENT;
|
|
14
|
+
}
|