mockpit 1.2.0 → 1.3.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 +13 -8
- package/bin/mockpit.js +116 -16
- package/dist/mcp/server.js +10 -7
- package/dist/server/apiViews.d.ts +118 -17
- package/dist/server/apiViews.js +66 -8
- package/dist/server/app.d.ts +2 -1
- package/dist/server/app.js +917 -144
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +99 -0
- package/dist/server/designGuide.js +45 -10
- package/dist/server/embeds.d.ts +25 -0
- package/dist/server/embeds.js +118 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +12 -1
- package/dist/server/feedbackBatch.js +20 -3
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/index.js +15 -1
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +11 -1
- package/dist/server/mcpHttp.d.ts +3 -0
- package/dist/server/mcpHttp.js +3 -0
- package/dist/server/mcpSpec.d.ts +13 -13
- package/dist/server/mcpSpec.js +73 -8
- package/dist/server/postSurfaces.js +3 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +4 -0
- package/dist/server/runApi.d.ts +1 -1
- package/dist/server/runApi.js +4 -3
- package/dist/server/sqlStore.d.ts +89 -4
- package/dist/server/sqlStore.js +149 -23
- 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 +73 -11
- package/dist/server/types.js +42 -2
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/guide/topics/asks.md +50 -3
- package/guide/topics/feedback.md +89 -11
- package/guide/topics/html.md +5 -1
- package/guide/topics/http.md +2 -1
- package/guide/topics/knobs.md +2 -1
- package/guide/topics/surfaces.md +24 -8
- package/package.json +4 -13
- package/skills/launch-video/SKILL.md +1 -1
- package/skills/mockpit/SKILL.md +12 -4
- package/viewer/dist/index.html +8 -8
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
package/dist/server/sqlStore.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { collectAssetIds, collectAttachedIds, DEFAULT_PROJECT, DEFAULT_VARIANT, hashAssetId, HISTORY_LIMIT, htmlSurface, MAX_WORKSPACE_ASSET_BYTES, newId, normalizeSurfaceIds, projectFromCwd, reservedAgent, selectEvictions, slugify, stripNul, summarizeProjects, } from "./types.js";
|
|
1
|
+
import { collectAssetIds, collectAttachedIds, DEFAULT_PROJECT, DEFAULT_VARIANT, hashAssetId, HISTORY_LIMIT, htmlSurface, MAX_WORKSPACE_ASSET_BYTES, newId, normalizeSurfaceIds, projectFromCwd, reservedAgent, sameScreen, selectEvictions, slugify, stripNul, summarizeProjects, } from "./types.js";
|
|
2
2
|
function parseJson(raw, fallback) {
|
|
3
3
|
if (typeof raw !== "string" || !raw)
|
|
4
4
|
return fallback;
|
|
@@ -14,7 +14,7 @@ const POST_COLUMNS = `
|
|
|
14
14
|
variant TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'open', title TEXT NOT NULL,
|
|
15
15
|
surfaces TEXT NOT NULL, knobs TEXT, slots TEXT NOT NULL DEFAULT '[]',
|
|
16
16
|
createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL, version INTEGER NOT NULL,
|
|
17
|
-
curFrom INTEGER, curPrompt TEXT, curAuthor TEXT`;
|
|
17
|
+
curFrom INTEGER, curPrompt TEXT, curAuthor TEXT, note TEXT`;
|
|
18
18
|
// Store implementation on SQLite — a Durable Object's `ctx.storage.sql` in the
|
|
19
19
|
// Worker, or node:sqlite via an adapter on Node (see server/sqliteStorage.ts).
|
|
20
20
|
// One workspace = one database, so plain SQL with no tenant columns.
|
|
@@ -71,7 +71,8 @@ export class SqlStore {
|
|
|
71
71
|
id TEXT PRIMARY KEY, project TEXT NOT NULL, slug TEXT NOT NULL, title TEXT NOT NULL,
|
|
72
72
|
kind TEXT NOT NULL DEFAULT 'component', states TEXT NOT NULL DEFAULT '[]',
|
|
73
73
|
asks TEXT NOT NULL DEFAULT '[]', knobs TEXT NOT NULL DEFAULT '{}', draft TEXT,
|
|
74
|
-
sessionId TEXT, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL
|
|
74
|
+
sessionId TEXT, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL,
|
|
75
|
+
widths TEXT NOT NULL DEFAULT '[]'
|
|
75
76
|
);
|
|
76
77
|
CREATE TABLE IF NOT EXISTS posts (${POST_COLUMNS});
|
|
77
78
|
CREATE TABLE IF NOT EXISTS post_versions (
|
|
@@ -95,6 +96,11 @@ export class SqlStore {
|
|
|
95
96
|
CREATE TABLE IF NOT EXISTS settings (
|
|
96
97
|
key TEXT PRIMARY KEY, value TEXT NOT NULL
|
|
97
98
|
);
|
|
99
|
+
CREATE TABLE IF NOT EXISTS flows (
|
|
100
|
+
project TEXT NOT NULL, name TEXT NOT NULL, steps TEXT NOT NULL DEFAULT '[]',
|
|
101
|
+
createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL,
|
|
102
|
+
PRIMARY KEY (project, name)
|
|
103
|
+
);
|
|
98
104
|
`);
|
|
99
105
|
this.addMissing("sessions", {
|
|
100
106
|
agentSeq: "INTEGER NOT NULL DEFAULT 0",
|
|
@@ -102,8 +108,22 @@ export class SqlStore {
|
|
|
102
108
|
key: "TEXT",
|
|
103
109
|
agentActiveAt: "TEXT",
|
|
104
110
|
});
|
|
111
|
+
this.addMissing("mocks", { widths: "TEXT NOT NULL DEFAULT '[]'", resolvedAt: "TEXT" });
|
|
105
112
|
if (legacy)
|
|
106
113
|
this.migrateLegacy();
|
|
114
|
+
// After the legacy lift, which rebuilds posts from POST_COLUMNS alone.
|
|
115
|
+
const pinned = this.columns("posts").has("acceptedVersion");
|
|
116
|
+
this.addMissing("posts", {
|
|
117
|
+
note: "TEXT",
|
|
118
|
+
forkedFrom: "TEXT",
|
|
119
|
+
starred: "INTEGER NOT NULL DEFAULT 0",
|
|
120
|
+
acceptedVersion: "INTEGER",
|
|
121
|
+
hiddenVersions: "TEXT",
|
|
122
|
+
});
|
|
123
|
+
// Before the pin, accepted meant "its latest": freeze that as the version in use.
|
|
124
|
+
if (!pinned) {
|
|
125
|
+
this.sql.exec("UPDATE posts SET acceptedVersion = version WHERE status = 'accepted'");
|
|
126
|
+
}
|
|
107
127
|
this.createIndexes();
|
|
108
128
|
}
|
|
109
129
|
createIndexes() {
|
|
@@ -396,14 +416,17 @@ export class SqlStore {
|
|
|
396
416
|
states: parseJson(r.states, []),
|
|
397
417
|
asks: parseJson(r.asks, []),
|
|
398
418
|
knobs: parseJson(r.knobs, {}),
|
|
419
|
+
widths: parseJson(r.widths, []),
|
|
399
420
|
draft: parseJson(r.draft, null),
|
|
400
421
|
sessionId: r.sessionId ?? null,
|
|
422
|
+
resolvedAt: r.resolvedAt ?? null,
|
|
401
423
|
createdAt: r.createdAt,
|
|
402
424
|
updatedAt: r.updatedAt,
|
|
403
425
|
};
|
|
404
426
|
}
|
|
405
427
|
rowToPost(r, history) {
|
|
406
428
|
const knobs = parseJson(r.knobs, null);
|
|
429
|
+
const hidden = parseJson(r.hiddenVersions, []);
|
|
407
430
|
return {
|
|
408
431
|
id: r.id,
|
|
409
432
|
sessionId: r.sessionId,
|
|
@@ -418,7 +441,15 @@ export class SqlStore {
|
|
|
418
441
|
version: r.version,
|
|
419
442
|
history,
|
|
420
443
|
...(knobs ? { knobs } : {}),
|
|
421
|
-
|
|
444
|
+
...(r.note == null ? {} : { note: r.note }),
|
|
445
|
+
// The column keeps its first name, from when embeds were called slots.
|
|
446
|
+
embeds: parseJson(r.slots, []),
|
|
447
|
+
...(r.forkedFrom == null ? {} : { forkedFrom: parseJson(r.forkedFrom, null) }),
|
|
448
|
+
...(r.starred ? { starred: true } : {}),
|
|
449
|
+
...(r.status === "accepted" && r.acceptedVersion != null
|
|
450
|
+
? { acceptedVersion: r.acceptedVersion }
|
|
451
|
+
: {}),
|
|
452
|
+
...(hidden.length ? { hiddenVersions: hidden } : {}),
|
|
422
453
|
...(r.curFrom == null ? {} : { from: r.curFrom }),
|
|
423
454
|
...(r.curPrompt == null ? {} : { prompt: r.curPrompt }),
|
|
424
455
|
...(r.curAuthor == null ? {} : { author: r.curAuthor }),
|
|
@@ -585,12 +616,14 @@ export class SqlStore {
|
|
|
585
616
|
states: (input.states ?? []).map((s) => stripNul(s)),
|
|
586
617
|
asks: [],
|
|
587
618
|
knobs: input.knobs ?? {},
|
|
619
|
+
widths: input.widths ?? [],
|
|
588
620
|
draft: null,
|
|
589
621
|
sessionId: input.sessionId ?? null,
|
|
622
|
+
resolvedAt: null,
|
|
590
623
|
createdAt: now,
|
|
591
624
|
updatedAt: now,
|
|
592
625
|
};
|
|
593
|
-
this.sql.exec("INSERT INTO mocks (id, project, slug, title, kind, states, asks, knobs, draft, sessionId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, '[]', ?, NULL, ?, ?, ?)", mock.id, mock.project, mock.slug, mock.title, mock.kind, JSON.stringify(mock.states), JSON.stringify(mock.knobs), mock.sessionId, mock.createdAt, mock.updatedAt);
|
|
626
|
+
this.sql.exec("INSERT INTO mocks (id, project, slug, title, kind, states, asks, knobs, widths, draft, sessionId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, '[]', ?, ?, NULL, ?, ?, ?)", mock.id, mock.project, mock.slug, mock.title, mock.kind, JSON.stringify(mock.states), JSON.stringify(mock.knobs), JSON.stringify(mock.widths), mock.sessionId, mock.createdAt, mock.updatedAt);
|
|
594
627
|
return mock;
|
|
595
628
|
}
|
|
596
629
|
async updateMock(id, patch) {
|
|
@@ -604,26 +637,80 @@ export class SqlStore {
|
|
|
604
637
|
...(patch.states !== undefined && { states: patch.states.map((s) => stripNul(s)) }),
|
|
605
638
|
...(patch.asks !== undefined && { asks: patch.asks }),
|
|
606
639
|
...(patch.knobs !== undefined && { knobs: patch.knobs }),
|
|
640
|
+
...(patch.widths !== undefined && { widths: patch.widths }),
|
|
607
641
|
...(patch.sessionId !== undefined && { sessionId: patch.sessionId }),
|
|
642
|
+
...(patch.resolvedAt !== undefined && { resolvedAt: patch.resolvedAt }),
|
|
608
643
|
updatedAt: new Date().toISOString(),
|
|
609
644
|
};
|
|
610
|
-
this.sql.exec("UPDATE mocks SET title = ?, kind = ?, states = ?, asks = ?, knobs = ?, sessionId = ?, updatedAt = ? WHERE id = ?", next.title, next.kind, JSON.stringify(next.states), JSON.stringify(next.asks), JSON.stringify(next.knobs), next.sessionId, next.updatedAt, id);
|
|
645
|
+
this.sql.exec("UPDATE mocks SET title = ?, kind = ?, states = ?, asks = ?, knobs = ?, widths = ?, sessionId = ?, resolvedAt = ?, updatedAt = ? WHERE id = ?", next.title, next.kind, JSON.stringify(next.states), JSON.stringify(next.asks), JSON.stringify(next.knobs), JSON.stringify(next.widths), next.sessionId, next.resolvedAt, next.updatedAt, id);
|
|
611
646
|
if (patch.asks !== undefined)
|
|
612
647
|
this.invalidateAssetRefs();
|
|
613
648
|
return next;
|
|
614
649
|
}
|
|
615
650
|
async removeMock(id) {
|
|
616
|
-
|
|
651
|
+
const mock = await this.getMock(id);
|
|
652
|
+
if (!mock)
|
|
617
653
|
return false;
|
|
618
654
|
this.atomic(() => {
|
|
619
655
|
this.sql.exec("DELETE FROM comments WHERE mockId = ?", id);
|
|
620
656
|
this.sql.exec("DELETE FROM post_versions WHERE postId IN (SELECT id FROM posts WHERE mockId = ?)", id);
|
|
621
657
|
this.sql.exec("DELETE FROM posts WHERE mockId = ?", id);
|
|
622
658
|
this.sql.exec("DELETE FROM mocks WHERE id = ?", id);
|
|
659
|
+
for (const flow of this.flowsOf(mock.project)) {
|
|
660
|
+
const steps = flow.steps.filter((x) => x.mock !== mock.slug);
|
|
661
|
+
if (steps.length !== flow.steps.length)
|
|
662
|
+
this.writeFlow(flow, steps);
|
|
663
|
+
}
|
|
623
664
|
});
|
|
624
665
|
this.invalidateAssetRefs();
|
|
625
666
|
return true;
|
|
626
667
|
}
|
|
668
|
+
// --- flows ---
|
|
669
|
+
flowsOf(project) {
|
|
670
|
+
return this.sql
|
|
671
|
+
.exec("SELECT * FROM flows WHERE project = ? ORDER BY createdAt, rowid", project)
|
|
672
|
+
.toArray()
|
|
673
|
+
.map((r) => ({
|
|
674
|
+
project: r.project,
|
|
675
|
+
name: r.name,
|
|
676
|
+
steps: parseJson(r.steps, []),
|
|
677
|
+
createdAt: r.createdAt,
|
|
678
|
+
updatedAt: r.updatedAt,
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
// An emptied flow is deleted rather than kept as a name with nothing to walk.
|
|
682
|
+
writeFlow(flow, steps) {
|
|
683
|
+
if (steps.length === 0) {
|
|
684
|
+
this.sql.exec("DELETE FROM flows WHERE project = ? AND name = ?", flow.project, flow.name);
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const next = { ...flow, steps, updatedAt: new Date().toISOString() };
|
|
688
|
+
this.sql.exec("INSERT INTO flows (project, name, steps, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?) ON CONFLICT(project, name) DO UPDATE SET steps = excluded.steps, updatedAt = excluded.updatedAt", next.project, next.name, JSON.stringify(next.steps), next.createdAt, next.updatedAt);
|
|
689
|
+
return next;
|
|
690
|
+
}
|
|
691
|
+
async listFlows(project) {
|
|
692
|
+
return this.flowsOf(project);
|
|
693
|
+
}
|
|
694
|
+
async placeFlowStep(project, name, screen, step) {
|
|
695
|
+
const clean = { mock: stripNul(screen.mock), state: stripNul(screen.state) };
|
|
696
|
+
const label = stripNul(name);
|
|
697
|
+
return this.atomic(() => {
|
|
698
|
+
const now = new Date().toISOString();
|
|
699
|
+
const flow = this.flowsOf(project).find((f) => f.name === label) ?? {
|
|
700
|
+
project,
|
|
701
|
+
name: label,
|
|
702
|
+
steps: [],
|
|
703
|
+
createdAt: now,
|
|
704
|
+
updatedAt: now,
|
|
705
|
+
};
|
|
706
|
+
const steps = flow.steps.filter((x) => !sameScreen(x, clean));
|
|
707
|
+
if (step > 0)
|
|
708
|
+
steps.splice(Math.min(step, steps.length + 1) - 1, 0, clean);
|
|
709
|
+
if (steps.length === 0 && flow.steps.length === 0)
|
|
710
|
+
return null;
|
|
711
|
+
return this.writeFlow(flow, steps);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
627
714
|
async putDraft(mockId, draft) {
|
|
628
715
|
const mock = await this.getMock(mockId);
|
|
629
716
|
if (!mock)
|
|
@@ -692,12 +779,14 @@ export class SqlStore {
|
|
|
692
779
|
version: 1,
|
|
693
780
|
history: [],
|
|
694
781
|
...(input.knobs ? { knobs: input.knobs } : {}),
|
|
695
|
-
|
|
782
|
+
...(stripNul(input.note ?? "").trim() ? { note: stripNul(input.note ?? "").trim() } : {}),
|
|
783
|
+
embeds: input.embeds ?? [],
|
|
784
|
+
...(input.forkedFrom ? { forkedFrom: input.forkedFrom } : {}),
|
|
696
785
|
...(input.from === undefined ? {} : { from: input.from }),
|
|
697
786
|
...(input.prompt === undefined ? {} : { prompt: stripNul(input.prompt) }),
|
|
698
787
|
...(input.author === undefined ? {} : { author: stripNul(input.author) }),
|
|
699
788
|
};
|
|
700
|
-
this.sql.exec("INSERT INTO posts (id, sessionId, mockId, state, variant, status, title, surfaces, knobs, slots, createdAt, updatedAt, version, curFrom, curPrompt, curAuthor) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)", post.id, post.sessionId, post.mock, post.state, post.variant, post.title, JSON.stringify(post.surfaces), post.knobs ? JSON.stringify(post.knobs) : null, JSON.stringify(post.
|
|
789
|
+
this.sql.exec("INSERT INTO posts (id, sessionId, mockId, state, variant, status, title, surfaces, knobs, slots, createdAt, updatedAt, version, curFrom, curPrompt, curAuthor, note, forkedFrom) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?)", post.id, post.sessionId, post.mock, post.state, post.variant, post.title, JSON.stringify(post.surfaces), post.knobs ? JSON.stringify(post.knobs) : null, JSON.stringify(post.embeds), post.createdAt, post.updatedAt, post.from ?? null, post.prompt ?? null, post.author ?? null, post.note ?? null, post.forkedFrom ? JSON.stringify(post.forkedFrom) : null);
|
|
701
790
|
this.touch(input.sessionId);
|
|
702
791
|
this.touchMock(post.mock, now);
|
|
703
792
|
this.addAssetRefs(post.surfaces);
|
|
@@ -719,13 +808,17 @@ export class SqlStore {
|
|
|
719
808
|
...(post.prompt === undefined ? {} : { prompt: post.prompt }),
|
|
720
809
|
...(post.author === undefined ? {} : { author: post.author }),
|
|
721
810
|
};
|
|
811
|
+
// The version in use is kept past the limit: export and embeds resolve to it.
|
|
812
|
+
const pin = post.acceptedVersion ?? -1;
|
|
722
813
|
const history = [...post.history, archived];
|
|
723
|
-
|
|
724
|
-
|
|
814
|
+
const prunable = history.filter((h) => h.version !== pin);
|
|
815
|
+
if (prunable.length > HISTORY_LIMIT)
|
|
816
|
+
history.splice(history.indexOf(prunable[0]), 1);
|
|
725
817
|
const title = patch.title !== undefined ? stripNul(patch.title).trim() || post.title : post.title;
|
|
726
818
|
const surfaces = patch.surfaces !== undefined ? normalizeSurfaceIds(patch.surfaces) : post.surfaces;
|
|
727
819
|
const knobs = patch.knobs === undefined ? post.knobs : (patch.knobs ?? undefined);
|
|
728
|
-
const
|
|
820
|
+
const note = patch.note === undefined ? post.note : stripNul(patch.note).trim() || undefined;
|
|
821
|
+
const embeds = patch.embeds ?? post.embeds;
|
|
729
822
|
const state = patch.state === undefined ? post.state : patch.state;
|
|
730
823
|
const version = post.version + 1;
|
|
731
824
|
const updatedAt = new Date().toISOString();
|
|
@@ -733,22 +826,23 @@ export class SqlStore {
|
|
|
733
826
|
const from = patch.from ?? post.version;
|
|
734
827
|
const prompt = stripNul(patch.prompt ?? "");
|
|
735
828
|
const author = patch.author === undefined ? null : stripNul(patch.author);
|
|
736
|
-
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, knobs = ?, slots = ?, state = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), knobs ? JSON.stringify(knobs) : null, JSON.stringify(
|
|
829
|
+
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, knobs = ?, note = ?, slots = ?, state = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), knobs ? JSON.stringify(knobs) : null, note ?? null, JSON.stringify(embeds), state, updatedAt, version, from, prompt, author, id, post.version);
|
|
737
830
|
const affected = this.sql.exec("SELECT changes() AS n").one().n;
|
|
738
831
|
if (affected > 0) {
|
|
739
832
|
this.insertVersion(id, archived);
|
|
740
|
-
this.sql.exec("DELETE FROM post_versions WHERE postId = ? AND version <= ?", id, version - 1 - HISTORY_LIMIT);
|
|
833
|
+
this.sql.exec("DELETE FROM post_versions WHERE postId = ? AND version <= ? AND version != ?", id, version - 1 - HISTORY_LIMIT, pin);
|
|
741
834
|
this.touch(post.sessionId);
|
|
742
835
|
this.touchMock(post.mock, updatedAt);
|
|
743
836
|
if (patch.surfaces !== undefined)
|
|
744
837
|
this.addAssetRefs(surfaces);
|
|
745
|
-
const { knobs: _knobs, author: _author, ...rest } = post;
|
|
838
|
+
const { knobs: _knobs, note: _note, author: _author, ...rest } = post;
|
|
746
839
|
return {
|
|
747
840
|
...rest,
|
|
748
841
|
title,
|
|
749
842
|
surfaces,
|
|
750
843
|
...(knobs ? { knobs } : {}),
|
|
751
|
-
|
|
844
|
+
...(note ? { note } : {}),
|
|
845
|
+
embeds,
|
|
752
846
|
state,
|
|
753
847
|
version,
|
|
754
848
|
updatedAt,
|
|
@@ -772,12 +866,36 @@ export class SqlStore {
|
|
|
772
866
|
this.invalidateAssetRefs();
|
|
773
867
|
return true;
|
|
774
868
|
}
|
|
775
|
-
async setPostStatus(id, status) {
|
|
869
|
+
async setPostStatus(id, status, acceptedVersion) {
|
|
776
870
|
const post = await this.getPost(id);
|
|
777
871
|
if (!post)
|
|
778
872
|
return null;
|
|
779
|
-
|
|
780
|
-
|
|
873
|
+
const pin = status === "accepted" ? (acceptedVersion ?? post.version) : null;
|
|
874
|
+
this.sql.exec("UPDATE posts SET status = ?, acceptedVersion = ? WHERE id = ?", status, pin, id);
|
|
875
|
+
const { acceptedVersion: _, ...rest } = post;
|
|
876
|
+
return pin === null ? { ...rest, status } : { ...rest, status, acceptedVersion: pin };
|
|
877
|
+
}
|
|
878
|
+
async setPostStarred(id, starred) {
|
|
879
|
+
const post = await this.getPost(id);
|
|
880
|
+
if (!post)
|
|
881
|
+
return null;
|
|
882
|
+
this.sql.exec("UPDATE posts SET starred = ? WHERE id = ?", starred ? 1 : 0, id);
|
|
883
|
+
const { starred: _, ...rest } = post;
|
|
884
|
+
return starred ? { ...rest, starred: true } : rest;
|
|
885
|
+
}
|
|
886
|
+
async setVersionHidden(id, version, hidden) {
|
|
887
|
+
const post = await this.getPost(id);
|
|
888
|
+
if (!post)
|
|
889
|
+
return null;
|
|
890
|
+
const set = new Set(post.hiddenVersions ?? []);
|
|
891
|
+
if (hidden)
|
|
892
|
+
set.add(version);
|
|
893
|
+
else
|
|
894
|
+
set.delete(version);
|
|
895
|
+
const list = [...set].sort((a, b) => a - b);
|
|
896
|
+
this.sql.exec("UPDATE posts SET hiddenVersions = ? WHERE id = ?", list.length ? JSON.stringify(list) : null, id);
|
|
897
|
+
const { hiddenVersions: _, ...rest } = post;
|
|
898
|
+
return list.length ? { ...rest, hiddenVersions: list } : rest;
|
|
781
899
|
}
|
|
782
900
|
// --- comments ---
|
|
783
901
|
async listComments(query) {
|
|
@@ -864,12 +982,20 @@ export class SqlStore {
|
|
|
864
982
|
postVersion: input.payload.version,
|
|
865
983
|
payload: input.payload,
|
|
866
984
|
});
|
|
867
|
-
this.sql.exec(
|
|
868
|
-
|
|
869
|
-
|
|
985
|
+
this.sql.exec(input.keepDraft
|
|
986
|
+
? "UPDATE mocks SET asks = ? WHERE id = ?"
|
|
987
|
+
: "UPDATE mocks SET asks = ?, draft = NULL WHERE id = ?", JSON.stringify(input.asks), input.mockId);
|
|
988
|
+
if (input.resolvedAt !== undefined) {
|
|
989
|
+
this.sql.exec("UPDATE mocks SET resolvedAt = ?, updatedAt = ? WHERE id = ?", input.resolvedAt, input.resolvedAt, input.mockId);
|
|
990
|
+
}
|
|
991
|
+
for (const { id, version } of input.accept) {
|
|
992
|
+
this.sql.exec("UPDATE posts SET status = 'accepted', acceptedVersion = ? WHERE id = ? AND mockId = ?", version, id, input.mockId);
|
|
870
993
|
}
|
|
871
994
|
for (const id of input.archive) {
|
|
872
|
-
this.sql.exec("UPDATE posts SET status = 'archived' WHERE id = ? AND mockId = ?", id, input.mockId);
|
|
995
|
+
this.sql.exec("UPDATE posts SET status = 'archived', acceptedVersion = NULL WHERE id = ? AND mockId = ?", id, input.mockId);
|
|
996
|
+
}
|
|
997
|
+
for (const id of input.drop ?? []) {
|
|
998
|
+
this.sql.exec("UPDATE posts SET status = 'dropped', acceptedVersion = NULL WHERE id = ? AND mockId = ?", id, input.mockId);
|
|
873
999
|
}
|
|
874
1000
|
this.invalidateAssetRefs();
|
|
875
1001
|
return comment;
|
|
@@ -3,8 +3,8 @@ import type { DesignSettings } from "./types.ts";
|
|
|
3
3
|
import { type Mode, type Theme } from "./themes.ts";
|
|
4
4
|
export declare const BRIDGE_JS = "\nwindow.sendPrompt = function (text) {\n parent.postMessage({ __mockpit: true, type: 'send-prompt', text: String(text) }, '*');\n};\nwindow.openLink = function (url) {\n parent.postMessage({ __mockpit: true, type: 'open-link', url: String(url) }, '*');\n};\nwindow.copyToClipboard = function (text) {\n parent.postMessage({ __mockpit: true, type: 'copy', text: String(text) }, '*');\n};\ndocument.addEventListener('click', function (e) {\n var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;\n if (a && /^https?:/.test(a.href)) { e.preventDefault(); window.openLink(a.href); }\n});\n// Cmd+Option+Up/Down switches sessions in the sidebar, but keydowns fire in\n// whichever document holds focus \u2014 once the user clicks into a surface, this\n// sandboxed iframe swallows them. Forward just that combo to the host.\ndocument.addEventListener('keydown', function (e) {\n if (!e.metaKey || !e.altKey || e.ctrlKey || e.shiftKey) return;\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n parent.postMessage({ __mockpit: true, type: 'switch-session', key: e.key }, '*');\n});\n// Report content height to the parent so it can size this iframe, while\n// breaking a feedback loop that can peg a CPU core.\n//\n// The loop: the parent sets the iframe's height to whatever we report, but some\n// content's height *inverts* with the frame's height \u2014 a scrollbar that appears\n// at height A reflows the content to height B, then disappears at B and reflows\n// back to A (or any 100vh / percentage-derived layout). The ResizeObserver then\n// fires on every flip, so reported heights alternate A, B, A, B... forever. With\n// a cheap surface that's a brief blip; with a heavy one (a big syntax-highlighted\n// diff/markdown surface) each relayout is expensive and the tab sits at 100% CPU\n// until the surface unmounts.\n//\n// A plain h !== __lastH guard can't stop this: in a 2-cycle every value differs\n// from the one immediately before it. So we remember the previous height too and\n// defer a return to it *if it recurs faster than a human could* (< 250ms) \u2014 that's\n// the runaway. The deferred pass keeps one trailing re-measure and reports the\n// taller height in the pair, so an ordinary font/image reflow can't leave the\n// frame permanently clipped.\nvar __lastH = 0;\nvar __prevH = 0;\nvar __lastT = 0;\nvar __seenH = 0;\nvar __trailTimer = 0;\nvar __trailH = 0;\nvar __FLIP_MS = 250;\nvar __TRAIL_MS = 350;\nfunction __now() {\n return typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();\n}\nfunction __measureHeight() {\n return document.body\n ? document.body.scrollHeight\n : document.documentElement.scrollHeight;\n}\nfunction __postHeight(h, t) {\n __prevH = __lastH;\n __lastH = h;\n __lastT = t;\n parent.postMessage({ __mockpit: true, type: 'resize', height: h }, '*');\n}\nfunction __clearTrailing() {\n if (__trailTimer && typeof clearTimeout !== 'undefined') clearTimeout(__trailTimer);\n __trailTimer = 0;\n __trailH = 0;\n}\nfunction __flushTrailing() {\n __trailTimer = 0;\n var measured = __measureHeight();\n if (measured > 0) __seenH = measured;\n var target = Math.max(__trailH || 0, measured || 0);\n __trailH = 0;\n if (target <= 0 || target === __lastH) return;\n __postHeight(target, __now());\n}\nfunction __scheduleTrailing(h, reset) {\n __trailH = Math.max(__trailH || 0, h || 0, __lastH || 0);\n if (__trailTimer) {\n if (!reset) return;\n if (typeof clearTimeout !== 'undefined') clearTimeout(__trailTimer);\n }\n __trailTimer = setTimeout(__flushTrailing, __TRAIL_MS);\n}\nfunction __report() {\n var h = __measureHeight();\n if (h <= 0) return; // no content yet\n var changed = h !== __seenH;\n __seenH = h;\n if (h === __lastH) return; // unchanged\n var t = __now();\n if (h === __prevH && (__trailTimer || t - __lastT < __FLIP_MS)) {\n __scheduleTrailing(h, true); // rapid A<->B flip: defer one settled report\n return;\n }\n __clearTrailing();\n __postHeight(h, t);\n}\nif (document.readyState === 'complete') __report();\nelse window.addEventListener('load', function () { requestAnimationFrame(__report); });\nsetTimeout(__report, 60);\nsetTimeout(__report, 350);\nsetTimeout(__report, 1500);\nsetTimeout(__report, 3000);\nsetTimeout(__report, 6000);\nsetTimeout(__report, 10000);\nif (document.fonts && document.fonts.ready && document.fonts.ready.then) {\n document.fonts.ready.then(function () { __report(); });\n}\nif (window.ResizeObserver) {\n window.__ssRO = new ResizeObserver(__report);\n window.__ssRO.observe(document.documentElement);\n if (document.body) window.__ssRO.observe(document.body);\n}\n";
|
|
5
5
|
export declare const HIT_TEST_JS = "\n(function () {\n var MAX_DEPTH = 6;\n var MAX_TEXT = 60;\n function docBox() {\n var w = document.documentElement.clientWidth || 1;\n var h = (document.body && document.body.scrollHeight) || document.documentElement.scrollHeight || 1;\n return { w: w, h: h };\n }\n function ident(v) { return typeof v === 'string' && /^[A-Za-z][\\w-]*$/.test(v); }\n function cssPath(el) {\n var parts = [];\n while (el && el.nodeType === 1 && el !== document.body && el !== document.documentElement) {\n var tag = el.tagName.toLowerCase();\n if (ident(el.id)) { parts.unshift(tag + '#' + el.id); break; }\n var sel = tag;\n var cls = (el.getAttribute('class') || '').split(/\\s+/).filter(ident).slice(0, 2);\n if (cls.length) sel += '.' + cls.join('.');\n var p = el.parentElement;\n if (p) {\n var sibs = [];\n for (var i = 0; i < p.children.length; i++) {\n if (p.children[i].tagName === el.tagName) sibs.push(p.children[i]);\n }\n if (sibs.length > 1) sel += ':nth-of-type(' + (sibs.indexOf(el) + 1) + ')';\n }\n parts.unshift(sel);\n if (parts.length >= MAX_DEPTH) break;\n el = p;\n }\n return parts.join(' > ');\n }\n function firstLine(el) {\n var t = (el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim();\n var line = t.split('\\n')[0].trim();\n return line.length > MAX_TEXT ? line.slice(0, MAX_TEXT - 1) + '\\u2026' : line;\n }\n window.addEventListener('message', function (e) {\n if (e.source !== parent || parent === window) return;\n var d = e.data;\n if (!d || d.__mockpit !== true || d.type !== 'hit-test') return;\n var box = docBox();\n var px = Math.max(0, Math.min(1, Number(d.x) || 0)) * box.w;\n var py = Math.max(0, Math.min(1, Number(d.y) || 0)) * box.h;\n var el = document.elementFromPoint(px - (window.scrollX || 0), py - (window.scrollY || 0));\n var reply = { __mockpit: true, type: 'hit-test-result', ref: d.ref, path: '', text: '', rect: [0, 0, 0, 0] };\n if (el && el.nodeType === 1) {\n var r = el.getBoundingClientRect();\n reply.path = cssPath(el);\n reply.text = firstLine(el);\n reply.rect = [\n (r.left + (window.scrollX || 0)) / box.w,\n (r.top + (window.scrollY || 0)) / box.h,\n r.width / box.w,\n r.height / box.h,\n ];\n }\n parent.postMessage(reply, '*');\n });\n})();\n";
|
|
6
|
-
export declare const PARTS_JS = "\n(function () {\n if (window.parent === window) return;\n var HL = 'mockpit-part-hl';\n var MAX_PARTS = 200;\n var MAX_TEXT = 200;\n var doc = window.__mockpitDoc || {};\n var version = typeof doc.version === 'number' ? doc.version : null;\n function text(v) { return v == null ? null : String(v).slice(0, MAX_TEXT); }\n function isFixed(el) {\n for (var n = el; n && n.nodeType === 1; n = n.parentElement) {\n if (getComputedStyle(n).position === 'fixed') return true;\n }\n return false;\n }\n function collect() {\n var sx = window.scrollX || 0, sy = window.scrollY || 0;\n var els = document.querySelectorAll('[data-part]');\n var parts = [];\n for (var i = 0; i < els.length && parts.length < MAX_PARTS; i++) {\n var el = els[i];\n var name = text(el.getAttribute('data-part'));\n if (!name) continue;\n var r = el.getBoundingClientRect();\n var depth = 0;\n for (var p = el.parentElement; p; p = p.parentElement) if (p.hasAttribute('data-part')) depth++;\n var outer = el.parentElement && el.parentElement.closest('[data-part]');\n parts.push({\n name: name,\n label: text(el.getAttribute('data-part-label')) || name,\n key: text(el.getAttribute('data-part-key')),\n box: { x: r.left + sx, y: r.top + sy, w: r.width, h: r.height },\n visible: (r.width > 0 || r.height > 0) && getComputedStyle(el).visibility !== 'hidden',\n fixed: isFixed(el),\n depth: depth,\n parent: outer ? text(outer.getAttribute('data-part')) : null,\n order: i,\n });\n }\n return {\n version: version,\n parts: parts,\n height: document.body ? document.body.scrollHeight : document.documentElement.scrollHeight,\n scroll: { x: sx, y: sy },\n viewport: { w: innerWidth, h: innerHeight },\n };\n }\n var last = '';\n var timer = 0;\n // setTimeout, not rAF: a hidden cross-origin frame may have rendering (and\n // with it rAF and ResizeObserver) throttled, but timers still run.\n function schedule() {\n if (timer) return;\n timer = setTimeout(function () {\n timer = 0;\n var m = collect();\n var key = JSON.stringify(m);\n if (key === last) return;\n last = key;\n m.__mockpit = true;\n m.type = 'parts';\n parent.postMessage(m, '*');\n }, 0);\n }\n\n var ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(schedule) : null;\n var observed = typeof WeakSet !== 'undefined' ? new WeakSet() : null;\n function observeAll() {\n if (!ro || !observed) return;\n // html/body too: a non-part sibling growing moves parts without resizing them.\n var els = [document.documentElement, document.body].concat([].slice.call(document.querySelectorAll('[data-part]')));\n for (var i = 0; i < els.length; i++) {\n var el = els[i];\n if (el && !observed.has(el)) { observed.add(el); ro.observe(el); }\n }\n }\n var moTimer = 0;\n var mo = new MutationObserver(function () {\n clearTimeout(moTimer);\n moTimer = setTimeout(function () { observeAll(); schedule(); }, 40);\n });\n mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });\n\n // Transforms and transitions move boxes without resizing or mutating anything.\n document.addEventListener('animationend', schedule, true);\n document.addEventListener('transitionend', schedule, true);\n window.addEventListener('scroll', schedule, { passive: true });\n window.addEventListener('resize', schedule);\n if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);\n observeAll();\n schedule();\n // WebKit: RO's initial callback may never fire in a sandboxed frame.\n function settle() {\n schedule();\n [50, 150, 400, 1000].forEach(function (ms) { setTimeout(schedule, ms); });\n }\n if (document.readyState === 'complete') settle();\n else window.addEventListener('load', settle);\n\n function setHighlight(names) {\n var on = document.querySelectorAll('.' + HL);\n for (var i = 0; i < on.length; i++) on[i].classList.remove(HL);\n if (names && names.length) {\n var want = {};\n for (var k = 0; k < names.length && k < MAX_PARTS; k++) want[String(names[k])] = true;\n var els = document.querySelectorAll('[data-part]');\n for (var j = 0; j < els.length; j++) {\n if (want[els[j].getAttribute('data-part')] === true) els[j].classList.add(HL);\n }\n }\n // Our own class flip must not look like the agent's page changing.\n mo.takeRecords();\n }\n\n window.addEventListener('message', function (e) {\n if (e.source !== parent) return;\n var d = e.data;\n if (!d || d.__mockpit !== true) return;\n if (d.type === 'highlight') setHighlight(Array.isArray(d.parts) ? d.parts : []);\n else if (d.type === 'clear') setHighlight(null);\n else if (d.type === 'scroll') window.scrollBy(Number(d.dx) || 0, Number(d.dy) || 0);\n else if (d.type === 'hit') {\n var el = document.elementFromPoint(\n (Number(d.x) || 0) - (window.scrollX || 0),\n (Number(d.y) || 0) - (window.scrollY || 0)\n );\n var part = el && el.closest ? el.closest('[data-part]') : null;\n parent.postMessage({\n __mockpit: true,\n type: 'hit',\n ref: d.ref,\n part: part ? text(part.getAttribute('data-part')) : null,\n key: part ? text(part.getAttribute('data-part-key')) : null,\n }, '*');\n }\n });\n})();\n";
|
|
7
|
-
export declare const KNOBS_JS = "\n(function () {\n\nvar __kPathRe = /^[A-Za-z_][\\w-]{0,63}(\\.[A-Za-z_][\\w-]{0,63}){0,3}$/;\nvar __kTokenRe = /^[#\\w(),.%/+-][#\\w\\s(),.%/+-]*$/;\nfunction __kName(path) { return path.replace(/\\./g, '-'); }\nfunction __kCssString(s) {\n return '\"' + s.replace(/[\\\\\"]/g, function (c) { return '\\\\' + c; })\n .replace(/[\\u0000-\\u001f\\u007f]/g, function (c) { return '\\\\' + c.charCodeAt(0).toString(16) + ' '; }) + '\"';\n}\nfunction __kCss(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? '1' : '0';\n if (typeof v === 'string') return __kTokenRe.test(v) ? v : __kCssString(v);\n return null;\n}\nfunction __kVars(path, v) {\n var name = __kName(path);\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n var out = [];\n for (var f in v) {\n if (!Object.prototype.hasOwnProperty.call(v, f) || !/^[A-Za-z_][\\w-]{0,63}$/.test(f)) continue;\n var c = __kCss(v[f]);\n if (c !== null) out.push([name + '-' + f, c]);\n }\n return out;\n }\n var css = __kCss(v);\n return css === null ? [] : [[name, css]];\n}\nfunction __kAttr(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'string') return v;\n return null;\n}\n\n var doc = window.__mockpitDoc || {};\n var current = {};\n function own(o, k) { return Object.prototype.hasOwnProperty.call(o, k); }\n function bind(path, v) {\n var els = document.querySelectorAll('[data-k-bind]');\n for (var i = 0; i < els.length; i++) {\n if (els[i].getAttribute('data-k-bind') !== path) continue;\n els[i].textContent = v && typeof v === 'object' ? JSON.stringify(v) : String(v);\n }\n }\n function apply(values, live) {\n var root = document.documentElement;\n for (var path in values) {\n if (!own(values, path) || !__kPathRe.test(path)) continue;\n var v = values[path];\n if (v === null || v === undefined) continue;\n current[path] = v;\n if (live) {\n var vars = __kVars(path, v);\n if (!vars.length) root.style.removeProperty('--k-' + __kName(path));\n for (var i = 0; i < vars.length; i++) root.style.setProperty('--k-' + vars[i][0], vars[i][1]);\n var attr = __kAttr(v);\n if (attr === null) root.removeAttribute('data-k-' + __kName(path));\n else root.setAttribute('data-k-' + __kName(path), attr);\n }\n bind(path, v);\n }\n window.dispatchEvent(new CustomEvent('mockpit:knobs', {\n detail: { values: JSON.parse(JSON.stringify(current)) },\n }));\n }\n function initial() {
|
|
6
|
+
export declare const PARTS_JS = "\n(function () {\n if (window.parent === window) return;\n var HL = 'mockpit-part-hl';\n var MAX_PARTS = 200;\n var MAX_TEXT = 200;\n var doc = window.__mockpitDoc || {};\n var version = typeof doc.version === 'number' ? doc.version : null;\n function text(v) { return v == null ? null : String(v).slice(0, MAX_TEXT); }\n function isFixed(el) {\n for (var n = el; n && n.nodeType === 1; n = n.parentElement) {\n if (getComputedStyle(n).position === 'fixed') return true;\n }\n return false;\n }\n function collect() {\n var sx = window.scrollX || 0, sy = window.scrollY || 0;\n var els = document.querySelectorAll('[data-part]');\n var parts = [];\n for (var i = 0; i < els.length && parts.length < MAX_PARTS; i++) {\n var el = els[i];\n var name = text(el.getAttribute('data-part'));\n if (!name) continue;\n var r = el.getBoundingClientRect();\n var depth = 0;\n for (var p = el.parentElement; p; p = p.parentElement) if (p.hasAttribute('data-part')) depth++;\n var outer = el.parentElement && el.parentElement.closest('[data-part]');\n parts.push({\n name: name,\n label: text(el.getAttribute('data-part-label')) || name,\n key: text(el.getAttribute('data-part-key')),\n box: { x: r.left + sx, y: r.top + sy, w: r.width, h: r.height },\n visible: (r.width > 0 || r.height > 0) && getComputedStyle(el).visibility !== 'hidden',\n fixed: isFixed(el),\n depth: depth,\n parent: outer ? text(outer.getAttribute('data-part')) : null,\n order: i,\n });\n }\n return {\n version: version,\n parts: parts,\n height: document.body ? document.body.scrollHeight : document.documentElement.scrollHeight,\n scroll: { x: sx, y: sy },\n // scrollWidth past innerWidth: the design is wider than the frame.\n viewport: { w: innerWidth, h: innerHeight, scrollW: document.documentElement.scrollWidth },\n };\n }\n var last = '';\n var timer = 0;\n // setTimeout, not rAF: a hidden cross-origin frame may have rendering (and\n // with it rAF and ResizeObserver) throttled, but timers still run.\n function schedule() {\n if (timer) return;\n timer = setTimeout(function () {\n timer = 0;\n var m = collect();\n var key = JSON.stringify(m);\n if (key === last) return;\n last = key;\n m.__mockpit = true;\n m.type = 'parts';\n parent.postMessage(m, '*');\n }, 0);\n }\n\n var ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(schedule) : null;\n var observed = typeof WeakSet !== 'undefined' ? new WeakSet() : null;\n function observeAll() {\n if (!ro || !observed) return;\n // html/body too: a non-part sibling growing moves parts without resizing them.\n var els = [document.documentElement, document.body].concat([].slice.call(document.querySelectorAll('[data-part]')));\n for (var i = 0; i < els.length; i++) {\n var el = els[i];\n if (el && !observed.has(el)) { observed.add(el); ro.observe(el); }\n }\n }\n var moTimer = 0;\n var mo = new MutationObserver(function () {\n clearTimeout(moTimer);\n moTimer = setTimeout(function () { observeAll(); schedule(); }, 40);\n });\n mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });\n\n // Transforms and transitions move boxes without resizing or mutating anything.\n document.addEventListener('animationend', schedule, true);\n document.addEventListener('transitionend', schedule, true);\n window.addEventListener('scroll', schedule, { passive: true });\n window.addEventListener('resize', schedule);\n if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);\n observeAll();\n schedule();\n // WebKit: RO's initial callback may never fire in a sandboxed frame.\n function settle() {\n schedule();\n [50, 150, 400, 1000].forEach(function (ms) { setTimeout(schedule, ms); });\n }\n if (document.readyState === 'complete') settle();\n else window.addEventListener('load', settle);\n\n function setHighlight(names) {\n var on = document.querySelectorAll('.' + HL);\n for (var i = 0; i < on.length; i++) on[i].classList.remove(HL);\n if (names && names.length) {\n var want = {};\n for (var k = 0; k < names.length && k < MAX_PARTS; k++) want[String(names[k])] = true;\n var els = document.querySelectorAll('[data-part]');\n for (var j = 0; j < els.length; j++) {\n if (want[els[j].getAttribute('data-part')] === true) els[j].classList.add(HL);\n }\n }\n // Our own class flip must not look like the agent's page changing.\n mo.takeRecords();\n }\n\n // Try: the host lets the user click through the design; an element marked\n // data-goto moves them to another screen. Only the host knows what that\n // names, so the frame just reports it, and the click goes no further.\n var trying = false;\n function setTrying(on) {\n trying = on;\n document.documentElement.classList.toggle('mockpit-try', on);\n mo.takeRecords();\n }\n window.addEventListener('click', function (e) {\n if (!trying) return;\n var t = e.target && e.target.closest ? e.target.closest('[data-goto]') : null;\n if (!t) return;\n e.preventDefault();\n e.stopPropagation();\n parent.postMessage({\n __mockpit: true,\n type: 'goto',\n version: version,\n to: text(t.getAttribute('data-goto')),\n }, '*');\n }, true);\n\n // A finger's tap, in document px, for a host that lets touches through to the\n // page (its canvas, where the page scrolls natively). A drag is a scroll: the\n // browser cancels the pointer or it moves too far. A mouse clicks the host's\n // own overlay instead, so it never reports here.\n var tapAt = null;\n window.addEventListener('pointerdown', function (e) {\n tapAt = e.isPrimary && e.pointerType !== 'mouse' ? { x: e.clientX, y: e.clientY, t: Date.now() } : null;\n }, true);\n window.addEventListener('pointercancel', function () { tapAt = null; }, true);\n window.addEventListener('pointerup', function (e) {\n var a = tapAt;\n tapAt = null;\n if (!a || !e.isPrimary) return;\n if (Math.abs(e.clientX - a.x) > 10 || Math.abs(e.clientY - a.y) > 10 || Date.now() - a.t > 600) return;\n parent.postMessage({\n __mockpit: true,\n type: 'tap',\n x: e.clientX + (window.scrollX || 0),\n y: e.clientY + (window.scrollY || 0),\n }, '*');\n }, true);\n\n // Two fingers are the host's: a page laid on its canvas takes one-finger\n // scrolls itself, but a pinch zooms the canvas, not this page or the browser.\n var pinching = false;\n function pinch(phase, e) {\n var t = e.touches;\n parent.postMessage({\n __mockpit: true,\n type: 'pinch',\n phase: phase,\n a: t[0] ? { x: t[0].clientX, y: t[0].clientY } : null,\n b: t[1] ? { x: t[1].clientX, y: t[1].clientY } : null,\n }, '*');\n }\n window.addEventListener('touchstart', function (e) {\n if (e.touches.length !== 2) return;\n e.preventDefault();\n pinching = true;\n pinch('start', e);\n }, { capture: true, passive: false });\n window.addEventListener('touchmove', function (e) {\n if (!pinching || e.touches.length < 2) return;\n e.preventDefault();\n pinch('move', e);\n }, { capture: true, passive: false });\n function release(e) {\n if (!pinching || e.touches.length >= 2) return;\n pinching = false;\n pinch('end', e);\n }\n window.addEventListener('touchend', release, true);\n window.addEventListener('touchcancel', release, true);\n // \u2318/Ctrl+scroll and a trackpad pinch (ctrl+wheel) are the host's too: they\n // zoom the canvas around the pointer, never this page or the browser.\n window.addEventListener('wheel', function (e) {\n if (!e.ctrlKey && !e.metaKey) return;\n e.preventDefault();\n parent.postMessage({ __mockpit: true, type: 'zoom', x: e.clientX, y: e.clientY, dy: e.deltaY }, '*');\n }, { capture: true, passive: false });\n\n window.addEventListener('message', function (e) {\n if (e.source !== parent) return;\n var d = e.data;\n if (!d || d.__mockpit !== true) return;\n if (d.type === 'highlight') setHighlight(Array.isArray(d.parts) ? d.parts : []);\n else if (d.type === 'clear') setHighlight(null);\n else if (d.type === 'try') setTrying(d.on === true);\n else if (d.type === 'scroll') window.scrollBy(Number(d.dx) || 0, Number(d.dy) || 0);\n else if (d.type === 'hit') {\n var el = document.elementFromPoint(\n (Number(d.x) || 0) - (window.scrollX || 0),\n (Number(d.y) || 0) - (window.scrollY || 0)\n );\n var part = el && el.closest ? el.closest('[data-part]') : null;\n parent.postMessage({\n __mockpit: true,\n type: 'hit',\n ref: d.ref,\n part: part ? text(part.getAttribute('data-part')) : null,\n key: part ? text(part.getAttribute('data-part-key')) : null,\n }, '*');\n }\n });\n})();\n";
|
|
7
|
+
export declare const KNOBS_JS = "\n(function () {\n\nvar __kPathRe = /^[A-Za-z_][\\w-]{0,63}(\\.[A-Za-z_][\\w-]{0,63}){0,3}$/;\nvar __kTokenRe = /^[#\\w(),.%/+-][#\\w\\s(),.%/+-]*$/;\nfunction __kName(path) { return path.replace(/\\./g, '-'); }\nfunction __kCssString(s) {\n return '\"' + s.replace(/[\\\\\"]/g, function (c) { return '\\\\' + c; })\n .replace(/[\\u0000-\\u001f\\u007f]/g, function (c) { return '\\\\' + c.charCodeAt(0).toString(16) + ' '; }) + '\"';\n}\nfunction __kCss(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? '1' : '0';\n if (typeof v === 'string') return __kTokenRe.test(v) ? v : __kCssString(v);\n return null;\n}\nfunction __kVars(path, v) {\n var name = __kName(path);\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n var out = [];\n for (var f in v) {\n if (!Object.prototype.hasOwnProperty.call(v, f) || !/^[A-Za-z_][\\w-]{0,63}$/.test(f)) continue;\n var c = __kCss(v[f]);\n if (c !== null) out.push([name + '-' + f, c]);\n }\n return out;\n }\n var css = __kCss(v);\n return css === null ? [] : [[name, css]];\n}\nfunction __kAttr(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'string') return v;\n return null;\n}\n\n var doc = window.__mockpitDoc || {};\n var current = {};\n function own(o, k) { return Object.prototype.hasOwnProperty.call(o, k); }\n function bind(path, v) {\n var els = document.querySelectorAll('[data-k-bind]');\n for (var i = 0; i < els.length; i++) {\n if (els[i].getAttribute('data-k-bind') !== path) continue;\n els[i].textContent = v && typeof v === 'object' ? JSON.stringify(v) : String(v);\n }\n }\n function apply(values, live) {\n var root = document.documentElement;\n for (var path in values) {\n if (!own(values, path) || !__kPathRe.test(path)) continue;\n var v = values[path];\n if (v === null || v === undefined) continue;\n current[path] = v;\n if (live) {\n var vars = __kVars(path, v);\n if (!vars.length) root.style.removeProperty('--k-' + __kName(path));\n for (var i = 0; i < vars.length; i++) root.style.setProperty('--k-' + vars[i][0], vars[i][1]);\n var attr = __kAttr(v);\n if (attr === null) root.removeAttribute('data-k-' + __kName(path));\n else root.setAttribute('data-k-' + __kName(path), attr);\n }\n bind(path, v);\n }\n window.dispatchEvent(new CustomEvent('mockpit:knobs', {\n detail: { values: JSON.parse(JSON.stringify(current)) },\n }));\n }\n // The URL's values are already on <html>; a live command may also have come\n // in before the body existed, so both are bound once it does.\n function initial() {\n var start = doc.knobs && typeof doc.knobs === 'object' ? doc.knobs : {};\n for (var k in start) if (own(start, k) && !own(current, k)) current[k] = start[k];\n apply(current, false);\n }\n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initial);\n else initial();\n // Runs from <head>, before the page's own markup and anything slow it loads\n // (a web font, a CDN stylesheet), so the host can send values at once rather\n // than wait for the first parts report, which those can hold back.\n if (parent !== window) parent.postMessage({ __mockpit: true, type: 'ready', version: doc.version }, '*');\n window.addEventListener('message', function (e) {\n if (e.source !== parent || parent === window) return;\n var d = e.data;\n if (!d || d.__mockpit !== true || d.type !== 'knobs') return;\n if (d.values && typeof d.values === 'object' && !Array.isArray(d.values)) apply(d.values, true);\n });\n})();\n";
|
|
8
8
|
export declare function knobCss(v: unknown): string | null;
|
|
9
9
|
export declare function knobVars(path: string, v: unknown): [string, string][];
|
|
10
10
|
export declare function knobAttr(v: unknown): string | null;
|
|
@@ -13,6 +13,10 @@ export declare function knobPreamble(version: number | undefined, knobs: Record<
|
|
|
13
13
|
htmlTag: string;
|
|
14
14
|
headScript: string;
|
|
15
15
|
};
|
|
16
|
+
export declare function knobAttributes(knobs: Record<string, unknown> | undefined): {
|
|
17
|
+
attrs: string;
|
|
18
|
+
values: Record<string, unknown>;
|
|
19
|
+
};
|
|
16
20
|
export interface StaticAsset {
|
|
17
21
|
path: string;
|
|
18
22
|
contentType: string;
|