mockpit 3.0.0 → 3.1.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 +11 -23
- package/bin/initDesign.js +11 -16
- package/bin/mockpit.js +242 -52
- package/bin/poster.js +64 -7
- package/dist/mcp/server.js +48 -6
- package/dist/server/apiViews.d.ts +562 -557
- package/dist/server/apiViews.js +18 -9
- package/dist/server/app.js +723 -206
- package/dist/server/cdnInline.js +6 -4
- package/dist/server/codeWorker.js +2 -2
- package/dist/server/contrast.d.ts +4 -0
- package/dist/server/contrast.js +157 -0
- package/dist/server/decisionList.d.ts +4 -1
- package/dist/server/decisionList.js +43 -5
- package/dist/server/designGuide.d.ts +13 -6
- package/dist/server/designGuide.js +206 -41
- package/dist/server/events.d.ts +6 -0
- package/dist/server/feedbackBatch.d.ts +4 -0
- package/dist/server/feedbackBatch.js +19 -1
- package/dist/server/guidance.d.ts +1 -1
- package/dist/server/guidance.js +2 -2
- package/dist/server/kits.d.ts +2 -2
- package/dist/server/kits.js +54 -1
- package/dist/server/mcpHttp.d.ts +2 -0
- package/dist/server/mcpHttp.js +13 -1
- package/dist/server/mcpSpec.d.ts +27 -27
- package/dist/server/mcpSpec.js +78 -10
- package/dist/server/run.js +22 -1
- package/dist/server/runApi.d.ts +2 -2
- package/dist/server/runApi.js +9 -2
- package/dist/server/sqlStore.d.ts +68 -39
- package/dist/server/sqlStore.js +163 -26
- package/dist/server/surfacePage.d.ts +4 -2
- package/dist/server/surfacePage.js +80 -32
- package/dist/server/system.d.ts +234 -0
- package/dist/server/system.js +1508 -0
- package/dist/server/systemChecks.d.ts +15 -0
- package/dist/server/systemChecks.js +224 -0
- package/dist/server/systemSpecimen.d.ts +2 -0
- package/dist/server/systemSpecimen.js +49 -0
- package/dist/server/themes.d.ts +9 -7
- package/dist/server/themes.js +43 -90
- package/dist/server/types.d.ts +43 -24
- package/dist/server/types.js +3 -1
- package/guide/AGENT_SETUP.md +3 -1
- package/guide/packs/foundations.md +35 -0
- package/guide/topics/asks.md +2 -1
- package/guide/topics/feedback.md +5 -3
- package/guide/topics/html.md +2 -2
- package/guide/topics/http.md +3 -1
- package/guide/topics/system.md +157 -0
- package/package.json +12 -12
- package/skills/mockpit/SKILL.md +7 -4
- package/viewer/dist/index.html +9 -8
package/dist/server/sqlStore.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { collectAssetIds, collectAttachedIds, DEFAULT_PROJECT, DEFAULT_VARIANT, hashAssetId, HISTORY_LIMIT, htmlSurface, MAX_WORKSPACE_ASSET_BYTES, isDesignKind, newId, normalizeSurfaceIds, isLive, projectFromCwd, AREA_ORDER_SETTING, DECISION_EDITS_SETTING, reservedAgent, sameScreen, selectEvictions, slugify, stripNul, summarizeProjects, } from "./types.js";
|
|
2
2
|
import { renameSlugRefs } from "./embeds.js";
|
|
3
|
+
import { CURRENT_CONTRACT, normalizeSystem, SYSTEM_LIMITS, systemFromDesignSettings, } from "./system.js";
|
|
3
4
|
import { RULES_SETTING } from "./guidance.js";
|
|
4
5
|
// The settings keyed `<prefix>:<project>`, which a project rename carries.
|
|
5
|
-
const PROJECT_SETTINGS = [
|
|
6
|
-
"design",
|
|
7
|
-
AREA_ORDER_SETTING,
|
|
8
|
-
RULES_SETTING,
|
|
9
|
-
DECISION_EDITS_SETTING,
|
|
10
|
-
];
|
|
6
|
+
const PROJECT_SETTINGS = [AREA_ORDER_SETTING, RULES_SETTING, DECISION_EDITS_SETTING];
|
|
11
7
|
// An optional free-text label: blank means none.
|
|
12
8
|
const label = (v) => stripNul(v ?? "").trim() || null;
|
|
13
9
|
function parseJson(raw, fallback) {
|
|
@@ -59,6 +55,9 @@ const surfaceHeads = (surfaces) => JSON.stringify(surfaces.map((s) => {
|
|
|
59
55
|
kind: s.kind,
|
|
60
56
|
...(s.poster ? { poster: s.poster } : {}),
|
|
61
57
|
...(s.poster && s.posterMode ? { posterMode: s.posterMode } : {}),
|
|
58
|
+
...(s.poster && s.posterMode && s.posterSystem !== undefined
|
|
59
|
+
? { posterSystem: s.posterSystem }
|
|
60
|
+
: {}),
|
|
62
61
|
...(/<mockpit-embed|data-fill/i.test(s.html) ? { marked: true } : {}),
|
|
63
62
|
};
|
|
64
63
|
}));
|
|
@@ -350,6 +349,14 @@ export class SqlStore {
|
|
|
350
349
|
createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL,
|
|
351
350
|
PRIMARY KEY (project, name)
|
|
352
351
|
);
|
|
352
|
+
CREATE TABLE IF NOT EXISTS systems (
|
|
353
|
+
project TEXT PRIMARY KEY, rev INTEGER NOT NULL, json TEXT NOT NULL
|
|
354
|
+
);
|
|
355
|
+
CREATE TABLE IF NOT EXISTS system_revisions (
|
|
356
|
+
project TEXT NOT NULL, rev INTEGER NOT NULL, json TEXT NOT NULL, at TEXT NOT NULL,
|
|
357
|
+
sessionId TEXT, summary TEXT NOT NULL DEFAULT '',
|
|
358
|
+
PRIMARY KEY (project, rev)
|
|
359
|
+
);
|
|
353
360
|
`);
|
|
354
361
|
this.addMissing("sessions", {
|
|
355
362
|
agentSeq: "INTEGER NOT NULL DEFAULT 0",
|
|
@@ -377,7 +384,12 @@ export class SqlStore {
|
|
|
377
384
|
});
|
|
378
385
|
this.migrateSituations();
|
|
379
386
|
// Before the legacy lift, whose history rows are written with every column.
|
|
380
|
-
this.addMissing("post_versions", {
|
|
387
|
+
this.addMissing("post_versions", {
|
|
388
|
+
sessionId: "TEXT",
|
|
389
|
+
amendedAt: "TEXT",
|
|
390
|
+
contract: "INTEGER",
|
|
391
|
+
system: "INTEGER",
|
|
392
|
+
});
|
|
381
393
|
if (legacy)
|
|
382
394
|
this.migrateLegacy();
|
|
383
395
|
// After the legacy lift, which rebuilds posts from POST_COLUMNS alone.
|
|
@@ -397,6 +409,8 @@ export class SqlStore {
|
|
|
397
409
|
archiveReason: "TEXT",
|
|
398
410
|
stayedVersion: "INTEGER",
|
|
399
411
|
archivedAt: "TEXT",
|
|
412
|
+
curContract: "INTEGER",
|
|
413
|
+
curSystem: "INTEGER",
|
|
400
414
|
});
|
|
401
415
|
// Before the pin, accepted meant "its latest": freeze that as the version in use.
|
|
402
416
|
if (!pinned) {
|
|
@@ -410,6 +424,7 @@ export class SqlStore {
|
|
|
410
424
|
}
|
|
411
425
|
if (this.settingSync("sketchKind") !== "1")
|
|
412
426
|
this.migrateToSketches();
|
|
427
|
+
this.migrateSystems();
|
|
413
428
|
if (this.settingSync("contractTerms") !== "1")
|
|
414
429
|
this.migrateToContractTerms();
|
|
415
430
|
if (this.settingSync("designWords") !== "1")
|
|
@@ -753,7 +768,7 @@ export class SqlStore {
|
|
|
753
768
|
}
|
|
754
769
|
// --- rows ---
|
|
755
770
|
insertVersion(postId, v) {
|
|
756
|
-
this.sql.exec("INSERT OR REPLACE INTO post_versions (postId, version, title, surfaces, head, at, fromVersion, prompt, author, sessionId, amendedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", postId, v.version, v.title, JSON.stringify(v.surfaces ?? []), surfaceHeads(v.surfaces ?? []), v.at, v.from ?? null, v.prompt ?? null, v.author ?? null, v.publishedBy ?? null, v.amendedAt ?? null);
|
|
771
|
+
this.sql.exec("INSERT OR REPLACE INTO post_versions (postId, version, title, surfaces, head, at, fromVersion, prompt, author, sessionId, amendedAt, contract, system) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", postId, v.version, v.title, JSON.stringify(v.surfaces ?? []), surfaceHeads(v.surfaces ?? []), v.at, v.from ?? null, v.prompt ?? null, v.author ?? null, v.publishedBy ?? null, v.amendedAt ?? null, v.contract ?? null, v.system ?? null);
|
|
757
772
|
}
|
|
758
773
|
rowToVersion(r) {
|
|
759
774
|
return {
|
|
@@ -767,6 +782,8 @@ export class SqlStore {
|
|
|
767
782
|
...(r.author == null ? {} : { author: r.author }),
|
|
768
783
|
...(r.sessionId == null ? {} : { publishedBy: r.sessionId }),
|
|
769
784
|
...(r.amendedAt == null ? {} : { amendedAt: r.amendedAt }),
|
|
785
|
+
...(r.contract == null ? {} : { contract: r.contract }),
|
|
786
|
+
...(r.system == null ? {} : { system: r.system }),
|
|
770
787
|
};
|
|
771
788
|
}
|
|
772
789
|
historiesFor(ids, weigh) {
|
|
@@ -865,6 +882,8 @@ export class SqlStore {
|
|
|
865
882
|
...(r.curAuthor == null ? {} : { author: r.curAuthor }),
|
|
866
883
|
...(r.curSession == null ? {} : { publishedBy: r.curSession }),
|
|
867
884
|
...(r.amendedAt == null ? {} : { amendedAt: r.amendedAt }),
|
|
885
|
+
...(r.curContract == null ? {} : { contract: r.curContract }),
|
|
886
|
+
...(r.curSystem == null ? {} : { system: r.curSystem }),
|
|
868
887
|
...(r.rewrites ? { rewrites: r.rewrites } : {}),
|
|
869
888
|
};
|
|
870
889
|
}
|
|
@@ -999,8 +1018,6 @@ export class SqlStore {
|
|
|
999
1018
|
return this.settingSync(key);
|
|
1000
1019
|
}
|
|
1001
1020
|
async setSetting(key, value) {
|
|
1002
|
-
if (key.startsWith("design:"))
|
|
1003
|
-
this.invalidateAssetRefs();
|
|
1004
1021
|
this.sql.exec("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value", stripNul(key), stripNul(value));
|
|
1005
1022
|
}
|
|
1006
1023
|
// --- projects / designs ---
|
|
@@ -1246,12 +1263,98 @@ export class SqlStore {
|
|
|
1246
1263
|
continue;
|
|
1247
1264
|
this.sql.exec("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", `${prefix}:${target}`, value);
|
|
1248
1265
|
this.sql.exec("DELETE FROM settings WHERE key = ?", `${prefix}:${from}`);
|
|
1249
|
-
|
|
1250
|
-
|
|
1266
|
+
}
|
|
1267
|
+
if (this.sql.exec("SELECT 1 FROM systems WHERE project = ?", from).toArray().length) {
|
|
1268
|
+
this.sql.exec("DELETE FROM systems WHERE project = ?", target);
|
|
1269
|
+
this.sql.exec("DELETE FROM system_revisions WHERE project = ?", target);
|
|
1270
|
+
for (const table of ["systems", "system_revisions"]) {
|
|
1271
|
+
for (const r of this.sql
|
|
1272
|
+
.exec(`SELECT rowid AS id, json FROM ${table} WHERE project = ?`, from)
|
|
1273
|
+
.toArray()) {
|
|
1274
|
+
const moved = { ...parseJson(r.json, {}), project: target };
|
|
1275
|
+
this.sql.exec(`UPDATE ${table} SET project = ?, json = ? WHERE rowid = ?`, target, JSON.stringify(moved), r.id);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1251
1278
|
}
|
|
1252
1279
|
return true;
|
|
1253
1280
|
});
|
|
1254
1281
|
}
|
|
1282
|
+
// --- systems ---
|
|
1283
|
+
async getSystem(project) {
|
|
1284
|
+
const rows = this.sql.exec("SELECT json FROM systems WHERE project = ?", project).toArray();
|
|
1285
|
+
return rows.length ? normalizeSystem(project, parseJson(rows[0].json, null)) : null;
|
|
1286
|
+
}
|
|
1287
|
+
async putSystem(system, meta) {
|
|
1288
|
+
this.atomic(() => this.writeSystem(system, meta));
|
|
1289
|
+
this.invalidateAssetRefs();
|
|
1290
|
+
return system;
|
|
1291
|
+
}
|
|
1292
|
+
async commitSystemDecision(input) {
|
|
1293
|
+
const tell = input.tell;
|
|
1294
|
+
const comment = this.atomic(() => {
|
|
1295
|
+
this.writeSystem(input.system, { sessionId: null, summary: input.summary });
|
|
1296
|
+
return tell
|
|
1297
|
+
? this.insertComment({
|
|
1298
|
+
sessionId: tell.sessionId,
|
|
1299
|
+
author: "user",
|
|
1300
|
+
text: "",
|
|
1301
|
+
kind: "reply",
|
|
1302
|
+
payload: tell.payload,
|
|
1303
|
+
})
|
|
1304
|
+
: null;
|
|
1305
|
+
});
|
|
1306
|
+
this.invalidateAssetRefs();
|
|
1307
|
+
return comment;
|
|
1308
|
+
}
|
|
1309
|
+
// Unwrapped, so a reply's transaction can carry it.
|
|
1310
|
+
writeSystem(system, meta) {
|
|
1311
|
+
const json = JSON.stringify(system);
|
|
1312
|
+
const at = new Date().toISOString();
|
|
1313
|
+
{
|
|
1314
|
+
this.sql.exec("INSERT INTO systems (project, rev, json) VALUES (?, ?, ?) ON CONFLICT(project) DO UPDATE SET rev = excluded.rev, json = excluded.json", system.project, system.rev, json);
|
|
1315
|
+
this.sql.exec("INSERT OR REPLACE INTO system_revisions (project, rev, json, at, sessionId, summary) VALUES (?, ?, ?, ?, ?, ?)", system.project, system.rev, json, at, meta.sessionId, stripNul(meta.summary).slice(0, SYSTEM_LIMITS.summary));
|
|
1316
|
+
this.sql.exec("DELETE FROM system_revisions WHERE project = ? AND rev <= ?", system.project, system.rev - SYSTEM_LIMITS.revisions);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
async listSystemRevisions(project) {
|
|
1320
|
+
return this.sql
|
|
1321
|
+
.exec("SELECT rev, at, sessionId, summary FROM system_revisions WHERE project = ? ORDER BY rev DESC", project)
|
|
1322
|
+
.toArray()
|
|
1323
|
+
.map((r) => ({
|
|
1324
|
+
rev: r.rev,
|
|
1325
|
+
at: r.at,
|
|
1326
|
+
sessionId: r.sessionId ?? null,
|
|
1327
|
+
summary: r.summary,
|
|
1328
|
+
}));
|
|
1329
|
+
}
|
|
1330
|
+
async getSystemRevision(project, rev) {
|
|
1331
|
+
const rows = this.sql
|
|
1332
|
+
.exec("SELECT json FROM system_revisions WHERE project = ? AND rev = ?", project, rev)
|
|
1333
|
+
.toArray();
|
|
1334
|
+
return rows.length ? normalizeSystem(project, parseJson(rows[0].json, null)) : null;
|
|
1335
|
+
}
|
|
1336
|
+
// Each project's old design settings become its system, with the repo's
|
|
1337
|
+
// tokens derived, and the setting goes.
|
|
1338
|
+
migrateSystems() {
|
|
1339
|
+
const rows = this.sql
|
|
1340
|
+
.exec("SELECT key, value FROM settings WHERE key LIKE 'design:%'")
|
|
1341
|
+
.toArray();
|
|
1342
|
+
if (rows.length === 0)
|
|
1343
|
+
return;
|
|
1344
|
+
const at = new Date().toISOString();
|
|
1345
|
+
for (const r of rows) {
|
|
1346
|
+
const project = r.key.slice("design:".length);
|
|
1347
|
+
const have = this.sql.exec("SELECT 1 FROM systems WHERE project = ?", project).toArray();
|
|
1348
|
+
if (have.length === 0) {
|
|
1349
|
+
const system = { ...systemFromDesignSettings(project, parseJson(r.value, null)), rev: 1 };
|
|
1350
|
+
const json = JSON.stringify(system);
|
|
1351
|
+
this.sql.exec("INSERT INTO systems (project, rev, json) VALUES (?, 1, ?)", project, json);
|
|
1352
|
+
this.sql.exec("INSERT INTO system_revisions (project, rev, json, at, sessionId, summary) VALUES (?, 1, ?, ?, NULL, ?)", project, json, at, "from what the project stored before");
|
|
1353
|
+
}
|
|
1354
|
+
this.sql.exec("DELETE FROM settings WHERE key = ?", r.key);
|
|
1355
|
+
}
|
|
1356
|
+
this.invalidateAssetRefs();
|
|
1357
|
+
}
|
|
1255
1358
|
// --- flows ---
|
|
1256
1359
|
flowsOf(project) {
|
|
1257
1360
|
return this.sql
|
|
@@ -1505,8 +1608,10 @@ export class SqlStore {
|
|
|
1505
1608
|
...(input.prompt === undefined ? {} : { prompt: stripNul(input.prompt) }),
|
|
1506
1609
|
...(input.author === undefined ? {} : { author: stripNul(input.author) }),
|
|
1507
1610
|
publishedBy: input.publishedBy ?? input.sessionId,
|
|
1611
|
+
contract: input.contract ?? CURRENT_CONTRACT,
|
|
1612
|
+
...(input.system === undefined ? {} : { system: input.system }),
|
|
1508
1613
|
};
|
|
1509
|
-
this.writingPosts(post.design, [post.id], () => this.sql.exec("INSERT INTO posts (id, sessionId, designId, state, variant, status, title, surfaces, head, knobs, slots, createdAt, updatedAt, version, curFrom, curPrompt, curAuthor, description, forkedFrom, copiedFrom, curSession) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)", post.id, post.sessionId, post.design, post.state, post.variant, post.title, JSON.stringify(post.surfaces), surfaceHeads(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.description ?? null, post.forkedFrom ? JSON.stringify(post.forkedFrom) : null, post.copiedFrom ? JSON.stringify(post.copiedFrom) : null, post.publishedBy ?? null));
|
|
1614
|
+
this.writingPosts(post.design, [post.id], () => this.sql.exec("INSERT INTO posts (id, sessionId, designId, state, variant, status, title, surfaces, head, knobs, slots, createdAt, updatedAt, version, curFrom, curPrompt, curAuthor, description, forkedFrom, copiedFrom, curSession, curContract, curSystem) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)", post.id, post.sessionId, post.design, post.state, post.variant, post.title, JSON.stringify(post.surfaces), surfaceHeads(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.description ?? null, post.forkedFrom ? JSON.stringify(post.forkedFrom) : null, post.copiedFrom ? JSON.stringify(post.copiedFrom) : null, post.publishedBy ?? null, post.contract ?? null, post.system ?? null));
|
|
1510
1615
|
this.touch(input.sessionId);
|
|
1511
1616
|
this.touchDesign(post.design, now);
|
|
1512
1617
|
this.addAssetRefs(post.surfaces);
|
|
@@ -1529,6 +1634,8 @@ export class SqlStore {
|
|
|
1529
1634
|
...(post.author === undefined ? {} : { author: post.author }),
|
|
1530
1635
|
...(post.publishedBy === undefined ? {} : { publishedBy: post.publishedBy }),
|
|
1531
1636
|
...(post.amendedAt === undefined ? {} : { amendedAt: post.amendedAt }),
|
|
1637
|
+
...(post.contract === undefined ? {} : { contract: post.contract }),
|
|
1638
|
+
...(post.system === undefined ? {} : { system: post.system }),
|
|
1532
1639
|
};
|
|
1533
1640
|
// The version in use is kept past the limit: export and embeds resolve to it.
|
|
1534
1641
|
const pin = post.acceptedVersion ?? -1;
|
|
@@ -1551,8 +1658,10 @@ export class SqlStore {
|
|
|
1551
1658
|
const prompt = stripNul(patch.prompt ?? "");
|
|
1552
1659
|
const author = patch.author === undefined ? null : stripNul(patch.author);
|
|
1553
1660
|
const publishedBy = patch.publishedBy ?? null;
|
|
1661
|
+
const contract = patch.contract ?? CURRENT_CONTRACT;
|
|
1662
|
+
const system = patch.system ?? null;
|
|
1554
1663
|
const wrote = this.writingPosts(post.design, [id], () => {
|
|
1555
|
-
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, head = ?, knobs = ?, description = ?, slots = ?, state = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ?, curSession = ?, amendedAt = NULL WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), surfaceHeads(surfaces), knobs ? JSON.stringify(knobs) : null, description ?? null, JSON.stringify(embeds), state, updatedAt, version, from, prompt, author, publishedBy, id, post.version);
|
|
1664
|
+
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, head = ?, knobs = ?, description = ?, slots = ?, state = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ?, curSession = ?, amendedAt = NULL, curContract = ?, curSystem = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), surfaceHeads(surfaces), knobs ? JSON.stringify(knobs) : null, description ?? null, JSON.stringify(embeds), state, updatedAt, version, from, prompt, author, publishedBy, contract, system, id, post.version);
|
|
1556
1665
|
if (this.sql.exec("SELECT changes() AS n").one().n === 0)
|
|
1557
1666
|
return false;
|
|
1558
1667
|
this.insertVersion(id, archived);
|
|
@@ -1564,9 +1673,11 @@ export class SqlStore {
|
|
|
1564
1673
|
this.touchDesign(post.design, updatedAt);
|
|
1565
1674
|
if (patch.surfaces !== undefined)
|
|
1566
1675
|
this.addAssetRefs(surfaces);
|
|
1567
|
-
const { knobs: _knobs, description: _description, author: _author, publishedBy: _by, amendedAt: _amended, ...rest } = post;
|
|
1676
|
+
const { knobs: _knobs, description: _description, author: _author, publishedBy: _by, amendedAt: _amended, contract: _contract, system: _system, ...rest } = post;
|
|
1568
1677
|
return {
|
|
1569
1678
|
...rest,
|
|
1679
|
+
contract,
|
|
1680
|
+
...(system === null ? {} : { system }),
|
|
1570
1681
|
title,
|
|
1571
1682
|
surfaces,
|
|
1572
1683
|
...(knobs ? { knobs } : {}),
|
|
@@ -1601,8 +1712,10 @@ export class SqlStore {
|
|
|
1601
1712
|
const author = patch.author === undefined ? post.author : stripNul(patch.author);
|
|
1602
1713
|
const amendedAt = new Date().toISOString();
|
|
1603
1714
|
const rewrites = (post.rewrites ?? 0) + 1;
|
|
1715
|
+
const contract = patch.contract ?? CURRENT_CONTRACT;
|
|
1716
|
+
const system = patch.system ?? post.system;
|
|
1604
1717
|
const wrote = this.writingPosts(post.design, [id], () => {
|
|
1605
|
-
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, head = ?, knobs = ?, description = ?, slots = ?, curFrom = ?, curPrompt = ?, curAuthor = ?, amendedAt = ?, rewrites = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), surfaceHeads(surfaces), knobs ? JSON.stringify(knobs) : null, description ?? null, JSON.stringify(embeds), from ?? null, prompt ?? null, author ?? null, amendedAt, rewrites, id, version);
|
|
1718
|
+
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, head = ?, knobs = ?, description = ?, slots = ?, curFrom = ?, curPrompt = ?, curAuthor = ?, amendedAt = ?, rewrites = ?, curContract = ?, curSystem = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), surfaceHeads(surfaces), knobs ? JSON.stringify(knobs) : null, description ?? null, JSON.stringify(embeds), from ?? null, prompt ?? null, author ?? null, amendedAt, rewrites, contract, system ?? null, id, version);
|
|
1606
1719
|
return this.sql.exec("SELECT changes() AS n").one().n > 0;
|
|
1607
1720
|
});
|
|
1608
1721
|
if (!wrote)
|
|
@@ -1624,17 +1737,33 @@ export class SqlStore {
|
|
|
1624
1737
|
...(author === undefined ? {} : { author }),
|
|
1625
1738
|
amendedAt,
|
|
1626
1739
|
rewrites,
|
|
1740
|
+
contract,
|
|
1741
|
+
...(system === undefined ? {} : { system }),
|
|
1627
1742
|
};
|
|
1628
1743
|
}
|
|
1629
|
-
async setPosterOnce(id, version, poster, mode) {
|
|
1744
|
+
async setPosterOnce(id, version, poster, mode, system) {
|
|
1630
1745
|
const post = await this.getPost(id);
|
|
1631
1746
|
if (!post || post.version !== version)
|
|
1632
1747
|
return false;
|
|
1633
1748
|
const at = post.surfaces.findIndex((s) => s.kind === "html");
|
|
1634
1749
|
const page = post.surfaces[at];
|
|
1635
|
-
if (!page || page.kind !== "html"
|
|
1750
|
+
if (!page || page.kind !== "html")
|
|
1751
|
+
return false;
|
|
1752
|
+
// A still mockpit took under another system revision no longer shows, so a new
|
|
1753
|
+
// one may take its place; an agent's own, or a current one, stays.
|
|
1754
|
+
const stale = page.posterMode !== undefined &&
|
|
1755
|
+
system !== undefined &&
|
|
1756
|
+
(page.posterSystem === undefined ? system > 1 : page.posterSystem !== system);
|
|
1757
|
+
if (page.poster && !stale)
|
|
1636
1758
|
return false;
|
|
1637
|
-
const surfaces = post.surfaces.map((s, i) => i === at
|
|
1759
|
+
const surfaces = post.surfaces.map((s, i) => i === at
|
|
1760
|
+
? {
|
|
1761
|
+
...page,
|
|
1762
|
+
poster,
|
|
1763
|
+
...(mode ? { posterMode: mode } : {}),
|
|
1764
|
+
...(mode && system !== undefined ? { posterSystem: system } : {}),
|
|
1765
|
+
}
|
|
1766
|
+
: s);
|
|
1638
1767
|
const wrote = this.writingPosts(post.design, [id], () => {
|
|
1639
1768
|
this.sql.exec("UPDATE posts SET surfaces = ?, head = ? WHERE id = ? AND version = ?", JSON.stringify(surfaces), surfaceHeads(surfaces), id, version);
|
|
1640
1769
|
return this.sql.exec("SELECT changes() AS n").one().n > 0;
|
|
@@ -1664,12 +1793,12 @@ export class SqlStore {
|
|
|
1664
1793
|
const rewrites = (post.rewrites ?? 0) + 1;
|
|
1665
1794
|
const hidden = (post.hiddenVersions ?? []).filter((v) => v !== version);
|
|
1666
1795
|
this.writingPosts(post.design, [id], () => this.atomic(() => {
|
|
1667
|
-
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, head = ?, slots = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ?, curSession = ?, amendedAt = ?, rewrites = ?, hiddenVersions = ? WHERE id = ? AND version = ?", previous.title, JSON.stringify(previous.surfaces), surfaceHeads(previous.surfaces), JSON.stringify(embeds), previous.at, previous.version, previous.from ?? null, previous.prompt ?? null, previous.author ?? null, previous.publishedBy ?? null, previous.amendedAt ?? null, rewrites, hidden.length ? JSON.stringify(hidden) : null, id, version);
|
|
1796
|
+
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, head = ?, slots = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ?, curSession = ?, amendedAt = ?, rewrites = ?, hiddenVersions = ?, curContract = ?, curSystem = ? WHERE id = ? AND version = ?", previous.title, JSON.stringify(previous.surfaces), surfaceHeads(previous.surfaces), JSON.stringify(embeds), previous.at, previous.version, previous.from ?? null, previous.prompt ?? null, previous.author ?? null, previous.publishedBy ?? null, previous.amendedAt ?? null, rewrites, hidden.length ? JSON.stringify(hidden) : null, previous.contract ?? null, previous.system ?? null, id, version);
|
|
1668
1797
|
this.sql.exec("DELETE FROM post_versions WHERE postId = ? AND version = ?", id, previous.version);
|
|
1669
1798
|
}));
|
|
1670
1799
|
this.touchDesign(post.design, now);
|
|
1671
1800
|
this.invalidateAssetRefs();
|
|
1672
|
-
const { from: _from, prompt: _prompt, author: _author, publishedBy: _by, amendedAt: _amended, hiddenVersions: _hidden, ...rest } = post;
|
|
1801
|
+
const { from: _from, prompt: _prompt, author: _author, publishedBy: _by, amendedAt: _amended, hiddenVersions: _hidden, contract: _contract, system: _system, ...rest } = post;
|
|
1673
1802
|
return {
|
|
1674
1803
|
post: {
|
|
1675
1804
|
...rest,
|
|
@@ -1685,6 +1814,8 @@ export class SqlStore {
|
|
|
1685
1814
|
...(previous.publishedBy === undefined ? {} : { publishedBy: previous.publishedBy }),
|
|
1686
1815
|
...(previous.amendedAt === undefined ? {} : { amendedAt: previous.amendedAt }),
|
|
1687
1816
|
...(hidden.length ? { hiddenVersions: hidden } : {}),
|
|
1817
|
+
...(previous.contract === undefined ? {} : { contract: previous.contract }),
|
|
1818
|
+
...(previous.system === undefined ? {} : { system: previous.system }),
|
|
1688
1819
|
rewrites,
|
|
1689
1820
|
},
|
|
1690
1821
|
};
|
|
@@ -1809,6 +1940,8 @@ export class SqlStore {
|
|
|
1809
1940
|
async listComments(query) {
|
|
1810
1941
|
const clauses = [];
|
|
1811
1942
|
const params = [];
|
|
1943
|
+
if (query.noDesign)
|
|
1944
|
+
clauses.push("designId IS NULL");
|
|
1812
1945
|
if (query.sessionId !== undefined) {
|
|
1813
1946
|
clauses.push("sessionId = ?");
|
|
1814
1947
|
params.push(query.sessionId);
|
|
@@ -1916,6 +2049,12 @@ export class SqlStore {
|
|
|
1916
2049
|
postVersion: input.payload.version,
|
|
1917
2050
|
payload: input.payload,
|
|
1918
2051
|
});
|
|
2052
|
+
if (input.system) {
|
|
2053
|
+
this.writeSystem(input.system.next, {
|
|
2054
|
+
sessionId: input.sessionId,
|
|
2055
|
+
summary: input.system.summary,
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
1919
2058
|
this.sql.exec(input.keepDraft
|
|
1920
2059
|
? "UPDATE designs SET asks = ? WHERE id = ?"
|
|
1921
2060
|
: "UPDATE designs SET asks = ?, draft = NULL WHERE id = ?", JSON.stringify(input.asks), input.designId);
|
|
@@ -1971,12 +2110,10 @@ export class SqlStore {
|
|
|
1971
2110
|
.toArray()) {
|
|
1972
2111
|
collectAttachedIds(parseJson(r.payload, null), out);
|
|
1973
2112
|
}
|
|
1974
|
-
// An installed icon set is referenced by its project's
|
|
2113
|
+
// An installed icon set is referenced by its project's system, not by a
|
|
1975
2114
|
// surface; evicting it would blank that project's icons.
|
|
1976
|
-
for (const r of this.sql
|
|
1977
|
-
|
|
1978
|
-
.toArray()) {
|
|
1979
|
-
const sets = parseJson(r.value, null)?.iconSets;
|
|
2115
|
+
for (const r of this.sql.exec("SELECT json FROM systems").toArray()) {
|
|
2116
|
+
const sets = parseJson(r.json, null)?.iconSets;
|
|
1980
2117
|
for (const ref of Array.isArray(sets) ? sets : []) {
|
|
1981
2118
|
if (typeof ref?.assetId === "string")
|
|
1982
2119
|
out.add(ref.assetId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type IconResolver } from "./icons.ts";
|
|
2
|
-
import { type
|
|
2
|
+
import { type System } from "./system.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 FREEZE_JS = "\n(function () {\n if (location.hash !== '#small' || !document.getAnimations) return;\n var QUIET_MS = 800, CAP_MS = 10000, LOAD_CAP_MS = 15000, MAX_STEP_MS = 100, RUN_MAX_MS = 30000;\n var HELD_MAX = 32;\n var raf = window.requestAnimationFrame.bind(window);\n var caf = window.cancelAnimationFrame.bind(window);\n var asked = new Map(), nextId = 0;\n var frozen = false, loaded = false;\n var run = 0, last = 0, settleFrom = 0, active = 0, runUntil = RUN_MAX_MS;\n var paused = [], sweepQueued = false;\n window.addEventListener('load', function () { loaded = true; settleFrom = run; active = run; });\n\n function fire(id, t) {\n var entry = asked.get(id);\n if (!entry) return;\n entry.native = 0;\n if (frozen) return;\n asked.delete(id);\n entry.cb(t);\n }\n function queue(id, entry) {\n entry.native = raf(function (t) { fire(id, t); });\n }\n window.requestAnimationFrame = function (cb) {\n if (frozen && run < runUntil) thaw();\n var id = ++nextId, entry = { cb: cb, native: 0 };\n if (frozen && asked.size >= HELD_MAX) return id;\n asked.set(id, entry);\n active = run;\n if (!frozen) queue(id, entry);\n return id;\n };\n window.cancelAnimationFrame = function (id) {\n var entry = asked.get(id);\n if (!entry) return;\n if (entry.native) caf(entry.native);\n asked.delete(id);\n };\n if (window.webkitRequestAnimationFrame) {\n window.webkitRequestAnimationFrame = window.requestAnimationFrame;\n window.webkitCancelAnimationFrame = window.cancelAnimationFrame;\n }\n\n function own(a) { return a.timeline === document.timeline && a.effect; }\n function ends(a) { return isFinite(a.effect.getComputedTiming().endTime); }\n function stillDrawing() {\n return document.getAnimations().some(function (a) {\n return own(a) && a.playState === 'running' && ends(a);\n });\n }\n function sweep() {\n sweepQueued = false;\n if (!frozen) return;\n document.getAnimations().forEach(function (a) {\n if (!own(a)) return;\n try {\n if (ends(a)) a.finish();\n else if (a.playState !== 'paused') { a.pause(); a.currentTime = 0; paused.push(a); }\n } catch (e) {}\n });\n Array.prototype.forEach.call(document.querySelectorAll('svg'), function (svg) {\n if (svg.pauseAnimations) svg.pauseAnimations();\n });\n }\n function queueSweep() {\n if (frozen && !sweepQueued) { sweepQueued = true; setTimeout(sweep, 0); }\n }\n function freeze() { frozen = true; sweep(); }\n function thaw() {\n frozen = false;\n active = settleFrom = run;\n paused.splice(0).forEach(function (a) { try { a.play(); } catch (e) {} });\n Array.prototype.forEach.call(document.querySelectorAll('svg'), function (svg) {\n if (svg.unpauseAnimations) svg.unpauseAnimations();\n });\n asked.forEach(function (entry, id) { if (!entry.native) queue(id, entry); });\n last = 0;\n raf(tick);\n }\n function settled() {\n return run - active >= QUIET_MS && !stillDrawing();\n }\n function tick(t) {\n if (frozen) return;\n run += last ? Math.min(t - last, MAX_STEP_MS) : 0;\n last = t;\n if (loaded ? settled() || run - settleFrom >= CAP_MS : run >= LOAD_CAP_MS) freeze();\n else raf(tick);\n }\n new MutationObserver(function () {\n active = run;\n queueSweep();\n }).observe(document, { subtree: true, childList: true, attributes: true, characterData: true });\n document.addEventListener('animationstart', queueSweep, true);\n document.addEventListener('transitionstart', queueSweep, true);\n window.addEventListener('message', function (e) {\n var d = e.data;\n if (e.source !== window.parent || !d || d.__mockpit !== true || d.type !== 'knobs') return;\n runUntil = run + RUN_MAX_MS;\n if (frozen) thaw();\n });\n raf(tick);\n})();\n";
|
|
@@ -42,7 +42,9 @@ export declare function renderHtmlPage(doc: {
|
|
|
42
42
|
theme?: Theme | string;
|
|
43
43
|
mode?: Mode;
|
|
44
44
|
kits?: string[];
|
|
45
|
-
|
|
45
|
+
system?: System | null;
|
|
46
|
+
palette?: string;
|
|
47
|
+
contract?: number;
|
|
46
48
|
version?: number;
|
|
47
49
|
knobs?: Record<string, unknown>;
|
|
48
50
|
resolveIcon?: IconResolver;
|
|
@@ -2,6 +2,7 @@ import { scanFills, slotCss } from "./fills.js";
|
|
|
2
2
|
import { CDN_ALLOWLIST, checkCdnUrl } from "./cdn.js";
|
|
3
3
|
import { CORE_CSS, KITS, kitAssets } from "./kits.js";
|
|
4
4
|
import { expandIcons } from "./icons.js";
|
|
5
|
+
import { CURRENT_CONTRACT, systemCss } from "./system.js";
|
|
5
6
|
import { PARAGRAPH_TEXT_MAX, PARAGRAPHS_MAX } from "./types.js";
|
|
6
7
|
import { schemeCss, themeById, tokenThemeCss, viewerThemeCss, } from "./themes.js";
|
|
7
8
|
// The kit's two custom SVG accent ramps (teal, coral) aren't in the theme
|
|
@@ -1126,7 +1127,7 @@ export function knobAttributes(knobs) {
|
|
|
1126
1127
|
const PART_HIGHLIGHT_CSS = `.mockpit-part-hl{outline:2px solid #8b7bff !important;outline-offset:2px}html.mockpit-try [data-goto]{outline:2px dashed #3fb27f !important;outline-offset:3px;cursor:pointer}`;
|
|
1127
1128
|
// Tailwind's browser build, pinned to a major (jsdelivr is already on the CDN
|
|
1128
1129
|
// allowlist, so this needs no CSP widening). Injected only when a project's
|
|
1129
|
-
//
|
|
1130
|
+
// system says `kit: "tailwind"`, i.e. the repo itself is a Tailwind
|
|
1130
1131
|
// repo — so the agent writes the same classes it writes in the codebase.
|
|
1131
1132
|
const TAILWIND_CDN = "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4";
|
|
1132
1133
|
// Sizing for the `<svg class="icon">` that icons.ts inlines for every
|
|
@@ -1163,6 +1164,14 @@ const PARAGRAPHS_TAG = inlineScript(PARAGRAPHS_JS);
|
|
|
1163
1164
|
// The base html-surface stylesheet: static design tokens + the surface kit.
|
|
1164
1165
|
const BASE_CSS_TAG = inlineStyle(`${TOKENS_CSS}${KIT_CSS}${PART_HIGHLIGHT_CSS}${ICON_CSS}`);
|
|
1165
1166
|
const KIT_CORE_TAG = inlineStyle(CORE_CSS);
|
|
1167
|
+
const LAYER_ORDER_TAG = inlineStyle("@layer mockpit, kit, system;");
|
|
1168
|
+
// Tailwind's browser build declares `theme, base, components, utilities` after
|
|
1169
|
+
// this tag, so layers it names first would come last and preflight (`base`)
|
|
1170
|
+
// would undo the sheet. Naming them here keeps preflight under the sheet and
|
|
1171
|
+
// utilities over it, as in the repo.
|
|
1172
|
+
const LAYER_ORDER_TAILWIND_TAG = inlineStyle("@layer theme, base, mockpit, kit, system, components, utilities;");
|
|
1173
|
+
const BASE_CSS_LAYERED_TAG = inlineStyle(`@layer mockpit{${TOKENS_CSS}${KIT_CSS}${PART_HIGHLIGHT_CSS}${ICON_CSS}}`);
|
|
1174
|
+
const KIT_CORE_LAYERED_TAG = inlineStyle(`@layer kit{${CORE_CSS}}`);
|
|
1166
1175
|
const versionAttr = (version) => version === undefined ? "" : ` data-mockpit-version="${version}"`;
|
|
1167
1176
|
// The same resolution kitAssets does (known ids, first occurrence wins), but
|
|
1168
1177
|
// yielding one stylesheet per kit instead of one concatenated string. Project
|
|
@@ -1194,33 +1203,49 @@ function resolveKits(ids, projectKits = []) {
|
|
|
1194
1203
|
}
|
|
1195
1204
|
return chosen;
|
|
1196
1205
|
}
|
|
1197
|
-
// Everything a project's
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
+
// Everything a project's system contributes to one html-surface doc.
|
|
1207
|
+
function systemAssets(system, opts) {
|
|
1208
|
+
if (!system)
|
|
1209
|
+
return {
|
|
1210
|
+
repoVars: "",
|
|
1211
|
+
sheet: "",
|
|
1212
|
+
tokens: "",
|
|
1213
|
+
fonts: [],
|
|
1214
|
+
kits: [],
|
|
1215
|
+
projectKits: [],
|
|
1216
|
+
headScripts: "",
|
|
1217
|
+
tailwind: false,
|
|
1218
|
+
dark: false,
|
|
1219
|
+
attrs: "",
|
|
1220
|
+
};
|
|
1221
|
+
const repo = system.repo;
|
|
1222
|
+
const tailwindCss = system.kit === "tailwind" ? (repo?.tailwindCss?.trim() ?? "") : "";
|
|
1223
|
+
const raw = tailwindCss ? "" : (repo?.cssVars?.trim() ?? "");
|
|
1206
1224
|
// `cssVars` is stored as the repo's raw block; accept either the full
|
|
1207
1225
|
// `:root{…}` text or a bare declaration list.
|
|
1208
1226
|
const vars = raw ? (raw.includes("{") ? raw : `:root{${raw}}`) : "";
|
|
1209
|
-
const kit =
|
|
1227
|
+
const kit = system.kit;
|
|
1210
1228
|
// The browser build reads its input from the style tags present when its
|
|
1211
1229
|
// script runs, so the stylesheet goes first. Its compiled output already
|
|
1212
1230
|
// carries the repo's `:root` block, hence no `cssVars` beside it.
|
|
1213
1231
|
const tailwind = kit !== "tailwind"
|
|
1214
1232
|
? ""
|
|
1215
1233
|
: `${tailwindCss ? `<style type="text/tailwindcss">${tailwindCss.replace(/<\/style/gi, "<\\/style")}</style>\n` : ""}<script src="${TAILWIND_CDN}"></script>`;
|
|
1234
|
+
const css = systemCss(system, opts);
|
|
1216
1235
|
return {
|
|
1217
|
-
|
|
1236
|
+
repoVars: vars,
|
|
1237
|
+
sheet: css.sheet.replace(/<\/style/gi, "<\\/style"),
|
|
1238
|
+
tokens: css.tokens,
|
|
1239
|
+
fonts: css.fonts,
|
|
1218
1240
|
kits: kit && kit !== "tailwind" && kit !== "none" ? [kit] : [],
|
|
1219
|
-
projectKits: Array.isArray(
|
|
1241
|
+
projectKits: Array.isArray(system.projectKits) ? system.projectKits : [],
|
|
1220
1242
|
headScripts: tailwind,
|
|
1243
|
+
tailwind: kit === "tailwind",
|
|
1221
1244
|
// Repos theme dark through a `.dark` ancestor (shadcn's `.dark {…}` block,
|
|
1222
1245
|
// `@custom-variant dark (&:is(.dark *))`), so the frame wears the class.
|
|
1223
|
-
dark: mode === "dark" && /\.dark\b/.test(tailwindCss || raw),
|
|
1246
|
+
dark: (css.attrs.scheme ?? opts.mode) === "dark" && /\.dark\b/.test(tailwindCss || raw),
|
|
1247
|
+
attrs: `${css.attrs.scheme ? ` data-scheme="${css.attrs.scheme}"` : ""}${css.attrs.palette ? ` data-palette="${escapeHtml(css.attrs.palette)}"` : ""}`,
|
|
1248
|
+
...(css.attrs.scheme ? { scheme: css.attrs.scheme } : {}),
|
|
1224
1249
|
};
|
|
1225
1250
|
}
|
|
1226
1251
|
export const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -1453,29 +1478,52 @@ ${PARAGRAPHS_TAG}
|
|
|
1453
1478
|
}
|
|
1454
1479
|
export function renderHtmlPage(doc) {
|
|
1455
1480
|
const theme = typeof doc.theme === "string" || doc.theme == null ? themeById(doc.theme) : doc.theme;
|
|
1456
|
-
const
|
|
1481
|
+
const contract = doc.contract ?? CURRENT_CONTRACT;
|
|
1482
|
+
const project = systemAssets(doc.system, { mode: doc.mode, palette: doc.palette, contract });
|
|
1457
1483
|
const preamble = knobPreamble(doc.version, doc.knobs);
|
|
1458
|
-
|
|
1484
|
+
let htmlTag = project.dark
|
|
1459
1485
|
? preamble.htmlTag.replace("<html ", '<html class="dark" ')
|
|
1460
1486
|
: preamble.htmlTag;
|
|
1487
|
+
if (project.attrs && contract >= 2)
|
|
1488
|
+
htmlTag = htmlTag.replace("<html ", `<html${project.attrs} `);
|
|
1461
1489
|
// The project kit is appended, so with both present its components win over
|
|
1462
1490
|
// a surface-requested kit's same-named classes.
|
|
1463
|
-
const kitIds = [...(doc.kits ?? []), ...
|
|
1464
|
-
const kits = resolveKits(kitIds,
|
|
1491
|
+
const kitIds = [...(doc.kits ?? []), ...project.kits];
|
|
1492
|
+
const kits = resolveKits(kitIds, project.projectKits);
|
|
1465
1493
|
const kit = kitAssets(kitIds);
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const styles =
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1494
|
+
const scheme = colorSchemeCss(project.scheme ?? doc.mode);
|
|
1495
|
+
const fonts = project.fonts.map((href) => `<link rel="stylesheet" href="${escapeHtml(href)}">`);
|
|
1496
|
+
const styles = contract >= 2
|
|
1497
|
+
? // Layers decide the cascade: mockpit's own styles, then kits, then the
|
|
1498
|
+
// project sheet. The tokens and the page are unlayered, so they win
|
|
1499
|
+
// over all three without extra specificity.
|
|
1500
|
+
[
|
|
1501
|
+
project.tailwind ? LAYER_ORDER_TAILWIND_TAG : LAYER_ORDER_TAG,
|
|
1502
|
+
`<style>@layer mockpit{${tokenThemeCss(theme, doc.mode)}${kitAccentCss(doc.mode)}}</style>`,
|
|
1503
|
+
BASE_CSS_LAYERED_TAG,
|
|
1504
|
+
...(kits.some((k) => !k.href) ? [KIT_CORE_LAYERED_TAG] : []),
|
|
1505
|
+
...kits.flatMap((k) => [
|
|
1506
|
+
...(k.href ? [`<style>@import url("${escapeHtml(k.href)}") layer(kit);</style>`] : []),
|
|
1507
|
+
...(k.css ? [inlineStyle(`@layer kit{${k.css}}`)] : []),
|
|
1508
|
+
]),
|
|
1509
|
+
...(project.sheet ? [`<style>@layer system{${project.sheet}}</style>`] : []),
|
|
1510
|
+
`<style>${project.repoVars}${project.tokens}${scheme}</style>`,
|
|
1511
|
+
...fonts,
|
|
1512
|
+
].join("\n")
|
|
1513
|
+
: // Document order decides the cascade: theme tokens, base, kit accents,
|
|
1514
|
+
// kit(s), then the project's own vars (which must win last).
|
|
1515
|
+
[
|
|
1516
|
+
`<style>${tokenThemeCss(theme, doc.mode)}</style>`,
|
|
1517
|
+
BASE_CSS_TAG,
|
|
1518
|
+
`<style>${kitAccentCss(doc.mode)}</style>`,
|
|
1519
|
+
...(kits.some((k) => !k.href) ? [KIT_CORE_TAG] : []),
|
|
1520
|
+
...kits.flatMap((k) => [
|
|
1521
|
+
...(k.href ? [`<link rel="stylesheet" href="${escapeHtml(k.href)}">`] : []),
|
|
1522
|
+
...(k.css ? [inlineStyle(k.css)] : []),
|
|
1523
|
+
]),
|
|
1524
|
+
`<style>${project.repoVars}${project.sheet}${project.tokens}${scheme}</style>`,
|
|
1525
|
+
...fonts,
|
|
1526
|
+
].join("\n");
|
|
1479
1527
|
// Slots (fills.ts): only the chosen fill of each shows, keyed on the knob
|
|
1480
1528
|
// attribute; with no value yet, the default fill.
|
|
1481
1529
|
const fillCss = slotCss(scanFills(doc.html).slots);
|
|
@@ -1491,7 +1539,7 @@ ${preamble.headScript}
|
|
|
1491
1539
|
${KNOBS_TAG}
|
|
1492
1540
|
${styles}
|
|
1493
1541
|
${fillCss ? `<style>${fillCss}</style>` : ""}
|
|
1494
|
-
${
|
|
1542
|
+
${project.headScripts}
|
|
1495
1543
|
${kits.flatMap((k) => (k.script ? [`<script src="${escapeHtml(k.script)}" defer></script>`] : [])).join("\n")}
|
|
1496
1544
|
</head>
|
|
1497
1545
|
<body>
|