pixelkiln 0.58.0 → 0.59.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/dist/cli.js +195 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +197 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +236 -11
- package/dist/index.d.ts +236 -11
- package/dist/index.js +196 -16
- package/dist/index.js.map +1 -1
- package/docs/ENDPOINTS.md +36 -10
- package/docs/GENERATORS.md +58 -0
- package/docs/MANIFEST.md +1 -0
- package/docs/REVISIONS.md +21 -21
- package/package.json +1 -1
- package/schema/manifest.schema.json +18 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +32 -14
- package/skills/pixelkiln/references/pixellab.md +27 -4
- package/skills/pixelkiln/references/revisions.md +3 -2
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AssetBatchSchema: () => AssetBatchSchema,
|
|
33
34
|
AssetSchema: () => AssetSchema,
|
|
34
35
|
BudgetError: () => BudgetError,
|
|
35
36
|
CHARACTER_DIRECTIONS_4: () => CHARACTER_DIRECTIONS_4,
|
|
@@ -427,6 +428,12 @@ var CharacterProportionsSchema = import_zod.z.union([
|
|
|
427
428
|
}).strict()
|
|
428
429
|
]);
|
|
429
430
|
var CharacterAnimationModeSchema = import_zod.z.enum(["template", "v3", "pro"]);
|
|
431
|
+
var AssetBatchSchema = import_zod.z.object({
|
|
432
|
+
/** The batch leader: another `1dir` asset in the same style. */
|
|
433
|
+
of: import_zod.z.string().min(1),
|
|
434
|
+
/** 1-based slot in the batch (the leader is implicitly slot 0). Unique and contiguous among siblings. */
|
|
435
|
+
index: import_zod.z.number().int().min(1)
|
|
436
|
+
}).strict();
|
|
430
437
|
var CharacterStateSchema = import_zod.z.object({
|
|
431
438
|
/** The base character, or another state, in the same style. */
|
|
432
439
|
of: import_zod.z.string().min(1),
|
|
@@ -1000,6 +1007,12 @@ var AssetSchema = import_zod.z.object({
|
|
|
1000
1007
|
* `prompt` instead.
|
|
1001
1008
|
*/
|
|
1002
1009
|
animation: CharacterAnimationSchema.optional(),
|
|
1010
|
+
/**
|
|
1011
|
+
* `1dir` styles only: this asset rides along on another `1dir` asset's
|
|
1012
|
+
* batch submission instead of generating on its own. See
|
|
1013
|
+
* `AssetBatchSchema`.
|
|
1014
|
+
*/
|
|
1015
|
+
batch: AssetBatchSchema.optional(),
|
|
1003
1016
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1004
1017
|
proportions: CharacterProportionsSchema.optional(),
|
|
1005
1018
|
/**
|
|
@@ -1076,7 +1089,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1076
1089
|
path: ["revision"]
|
|
1077
1090
|
});
|
|
1078
1091
|
}
|
|
1079
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror"].filter(Boolean);
|
|
1092
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
1080
1093
|
if (shapes.length > 1) {
|
|
1081
1094
|
context.addIssue({
|
|
1082
1095
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -1227,6 +1240,12 @@ var LockEntrySchema = import_zod.z.object({
|
|
|
1227
1240
|
sourceAssetId: import_zod.z.string().min(1),
|
|
1228
1241
|
sourceSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
1229
1242
|
}).strict().nullable().default(null),
|
|
1243
|
+
/** For a `1dir` batch member: which leader and slot it rode along on. */
|
|
1244
|
+
batch: import_zod.z.object({
|
|
1245
|
+
role: import_zod.z.enum(["leader", "member"]),
|
|
1246
|
+
leaderAssetId: import_zod.z.string().min(1).optional(),
|
|
1247
|
+
index: import_zod.z.number().int().min(1).optional()
|
|
1248
|
+
}).strict().nullable().default(null),
|
|
1230
1249
|
/**
|
|
1231
1250
|
* Output hashes owned by the previous generation while its replacement is
|
|
1232
1251
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -2277,14 +2296,15 @@ var PixelLabClient = class {
|
|
|
2277
2296
|
/**
|
|
2278
2297
|
* `/reduce-colors`, PixelLab's "Cleanup" tier: quantize one image onto a
|
|
2279
2298
|
* smaller palette, synchronously — no `background_job_id`, the result
|
|
2280
|
-
* comes back in this same response, like `createImagePixflux`.
|
|
2281
|
-
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2285
|
-
*
|
|
2286
|
-
*
|
|
2287
|
-
*
|
|
2299
|
+
* comes back in this same response, like `createImagePixflux`. The
|
|
2300
|
+
* schema's own response example is dollar-denominated (`usage: {type:
|
|
2301
|
+
* "usd", usd: 0.02}`), which — matching this codebase's repeated
|
|
2302
|
+
* experience with PixelLab's documented-vs-billed cost mismatches
|
|
2303
|
+
* (`isometricTile`, `objectPro`) — did not hold: confirmed live against a
|
|
2304
|
+
* Tier 2 account at exactly 0.1 generations for a 32x32 source
|
|
2305
|
+
* (docs/REVISIONS.md). `numColors` and `paletteImage` are mutually
|
|
2306
|
+
* exclusive upstream; the manifest schema already enforces that before
|
|
2307
|
+
* this is ever called.
|
|
2288
2308
|
*/
|
|
2289
2309
|
async reduceColors(args) {
|
|
2290
2310
|
const body = { images: [args.image] };
|
|
@@ -2307,8 +2327,9 @@ var PixelLabClient = class {
|
|
|
2307
2327
|
/**
|
|
2308
2328
|
* `/correct-pixelart`, PixelLab's "Cleanup" tier: sharpen edges and drop
|
|
2309
2329
|
* stray pixels without resizing, synchronously — same shape as
|
|
2310
|
-
* `reduceColors` above, no background job. Cost is likewise
|
|
2311
|
-
*
|
|
2330
|
+
* `reduceColors` above, no background job. Cost is likewise confirmed
|
|
2331
|
+
* live at 0.1 generations for a 32x32 source, not the schema's own
|
|
2332
|
+
* dollar-denominated example (`usage: {type: "usd", usd: 0.02}`).
|
|
2312
2333
|
*/
|
|
2313
2334
|
async correctPixelart(args) {
|
|
2314
2335
|
const body = { images: [args.image] };
|
|
@@ -3064,6 +3085,11 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3064
3085
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
3065
3086
|
// compares those directly, so only the choice of source is identity.
|
|
3066
3087
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
3088
|
+
// The whole group's ordered descriptions, not just this asset's own —
|
|
3089
|
+
// any sibling's prompt changing, or a member being added or removed,
|
|
3090
|
+
// must mark every member stale together, since they all ride on one
|
|
3091
|
+
// submitted job with no way to amend it after the fact.
|
|
3092
|
+
batch: spec.batch ? { role: spec.batch.role, itemDescriptions: spec.batch.itemDescriptions, index: spec.batch.index } : void 0,
|
|
3067
3093
|
revision: spec.revision ? {
|
|
3068
3094
|
mode: spec.revision.mode,
|
|
3069
3095
|
from: spec.revision.sourceAssetId,
|
|
@@ -3245,6 +3271,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3245
3271
|
return dir;
|
|
3246
3272
|
}
|
|
3247
3273
|
estimate(spec) {
|
|
3274
|
+
if (spec.batch?.role === "member") {
|
|
3275
|
+
return { unit: "generations", amount: 0, candidates: 1 };
|
|
3276
|
+
}
|
|
3248
3277
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
3249
3278
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
3250
3279
|
}
|
|
@@ -4043,11 +4072,21 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4043
4072
|
return { jobId: res2.background_job_id };
|
|
4044
4073
|
}
|
|
4045
4074
|
if (spec.generator === "1dir") {
|
|
4075
|
+
if (spec.batch?.role === "member") {
|
|
4076
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: a batch member cannot submit on its own`);
|
|
4077
|
+
}
|
|
4046
4078
|
const res2 = await this.client.create1Direction({
|
|
4047
4079
|
description: spec.prompt,
|
|
4048
4080
|
size: spec.size,
|
|
4049
4081
|
view: spec.view === "sidescroller" ? "sidescroller" : "top-down",
|
|
4050
|
-
styleImages
|
|
4082
|
+
styleImages,
|
|
4083
|
+
// The leader's own subject is item_descriptions[0], so `description`
|
|
4084
|
+
// matches it exactly. Confirmed live: item_descriptions[0] owns
|
|
4085
|
+
// candidate slot 0 (a 3-item batch returned exactly the declared
|
|
4086
|
+
// items at frames 0/1/2, in order; docs/GENERATORS.md#1dir), so
|
|
4087
|
+
// sending the same text as `description` too is redundant but
|
|
4088
|
+
// harmless, not a hedge against ambiguity.
|
|
4089
|
+
itemDescriptions: spec.batch?.role === "leader" ? spec.batch.itemDescriptions : void 0
|
|
4051
4090
|
});
|
|
4052
4091
|
return { jobId: res2.object_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
4053
4092
|
}
|
|
@@ -7457,6 +7496,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7457
7496
|
};
|
|
7458
7497
|
styleSpecs.set(assetId, resolved);
|
|
7459
7498
|
}
|
|
7499
|
+
const batchMembersByLeader = /* @__PURE__ */ new Map();
|
|
7500
|
+
for (const id of styleSpecs.keys()) {
|
|
7501
|
+
const batch = manifest.assets[id].batch;
|
|
7502
|
+
if (!batch) continue;
|
|
7503
|
+
const members = batchMembersByLeader.get(batch.of) ?? [];
|
|
7504
|
+
members.push({ id, index: batch.index });
|
|
7505
|
+
batchMembersByLeader.set(batch.of, members);
|
|
7506
|
+
}
|
|
7460
7507
|
const finalized = /* @__PURE__ */ new Set();
|
|
7461
7508
|
const finalize = async (assetId) => {
|
|
7462
7509
|
const resolved = styleSpecs.get(assetId);
|
|
@@ -7615,6 +7662,56 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7615
7662
|
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
7616
7663
|
};
|
|
7617
7664
|
}
|
|
7665
|
+
if (asset.batch) {
|
|
7666
|
+
if (asset.batch.of === assetId) throw new Error(`assets.${assetId}: a batch cannot name itself as its own leader`);
|
|
7667
|
+
const leaderSpec = await finalize(asset.batch.of);
|
|
7668
|
+
if (!leaderSpec.batch || leaderSpec.batch.role !== "leader") {
|
|
7669
|
+
throw new Error(`assets.${assetId}: ${asset.batch.of} is not a 1dir batch leader`);
|
|
7670
|
+
}
|
|
7671
|
+
if (resolved.size !== leaderSpec.size) {
|
|
7672
|
+
throw new Error(
|
|
7673
|
+
`assets.${assetId}: batch members share the leader's canvas size; ${asset.batch.of} is ${leaderSpec.size}px, this is ${resolved.size}px`
|
|
7674
|
+
);
|
|
7675
|
+
}
|
|
7676
|
+
resolved.batch = {
|
|
7677
|
+
role: "member",
|
|
7678
|
+
itemDescriptions: leaderSpec.batch.itemDescriptions,
|
|
7679
|
+
index: asset.batch.index,
|
|
7680
|
+
leaderAssetId: asset.batch.of,
|
|
7681
|
+
leaderSpec
|
|
7682
|
+
};
|
|
7683
|
+
} else {
|
|
7684
|
+
const members = batchMembersByLeader.get(assetId);
|
|
7685
|
+
if (members) {
|
|
7686
|
+
if (style.generator !== "1dir") {
|
|
7687
|
+
throw new Error(`assets.${assetId}: only a 1dir asset can lead a batch`);
|
|
7688
|
+
}
|
|
7689
|
+
const sorted = [...members].sort((a, b) => a.index - b.index);
|
|
7690
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7691
|
+
for (const { index } of sorted) {
|
|
7692
|
+
if (seen.has(index)) throw new Error(`assets.${assetId}: two batch members both claim index ${index}`);
|
|
7693
|
+
seen.add(index);
|
|
7694
|
+
}
|
|
7695
|
+
const expectedIndices = sorted.map((_, i) => i + 1).join(",");
|
|
7696
|
+
if (sorted.map((m) => m.index).join(",") !== expectedIndices) {
|
|
7697
|
+
throw new Error(
|
|
7698
|
+
`assets.${assetId}: batch member indices must be 1..${sorted.length} with no gaps; got ${sorted.map((m) => m.index).join(",")}`
|
|
7699
|
+
);
|
|
7700
|
+
}
|
|
7701
|
+
const total = 1 + sorted.length;
|
|
7702
|
+
const limit = candidateCount(resolved.size);
|
|
7703
|
+
if (total > limit) {
|
|
7704
|
+
throw new Error(
|
|
7705
|
+
`assets.${assetId}: a ${resolved.size}px batch holds at most ${limit} items (1 leader + ${limit - 1} members); this one declares ${total}`
|
|
7706
|
+
);
|
|
7707
|
+
}
|
|
7708
|
+
resolved.batch = {
|
|
7709
|
+
role: "leader",
|
|
7710
|
+
itemDescriptions: [resolved.prompt, ...sorted.map(({ id }) => styleSpecs.get(id).prompt)],
|
|
7711
|
+
memberAssetIds: sorted.map((m) => m.id)
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7618
7715
|
resolved.specHash = specHash(
|
|
7619
7716
|
resolved,
|
|
7620
7717
|
styleImageHashes,
|
|
@@ -12000,6 +12097,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12000
12097
|
for (const styleId of new Set(items.map((i) => i.spec.styleId))) {
|
|
12001
12098
|
styleImages.set(styleId, await resolveStyleImages(loaded, styleId));
|
|
12002
12099
|
}
|
|
12100
|
+
const byKey = new Map(items.map((item) => [item.key, item]));
|
|
12003
12101
|
let submitted = 0;
|
|
12004
12102
|
let failed = 0;
|
|
12005
12103
|
let spent = 0;
|
|
@@ -12029,8 +12127,62 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12029
12127
|
await pruneInFlight();
|
|
12030
12128
|
}
|
|
12031
12129
|
}
|
|
12130
|
+
function writeBatchMembers(leaderSpec, leaderKey, error) {
|
|
12131
|
+
if (leaderSpec.batch?.role !== "leader") return;
|
|
12132
|
+
const leaderEntry = lock.entries[leaderKey];
|
|
12133
|
+
for (const memberAssetId of leaderSpec.batch.memberAssetIds ?? []) {
|
|
12134
|
+
const memberKey = lockKey(leaderSpec.styleId, memberAssetId);
|
|
12135
|
+
const memberItem = byKey.get(memberKey);
|
|
12136
|
+
if (!memberItem) {
|
|
12137
|
+
throw new Error(
|
|
12138
|
+
`${leaderKey}: batch member "${memberAssetId}" must be submitted in the same run as its leader; run without --only, or include every sibling`
|
|
12139
|
+
);
|
|
12140
|
+
}
|
|
12141
|
+
const memberSpec = memberItem.spec;
|
|
12142
|
+
const memberEstimate = estimates.get(memberKey);
|
|
12143
|
+
const previousMemberEntry = lock.entries[memberKey];
|
|
12144
|
+
const memberSubmittedAt = leaderEntry.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12145
|
+
upsert(lock, memberKey, {
|
|
12146
|
+
styleId: memberSpec.styleId,
|
|
12147
|
+
assetId: memberSpec.assetId,
|
|
12148
|
+
specHash: memberSpec.specHash,
|
|
12149
|
+
generator: memberSpec.generator,
|
|
12150
|
+
prompt: memberSpec.prompt,
|
|
12151
|
+
width: memberSpec.width,
|
|
12152
|
+
height: memberSpec.height,
|
|
12153
|
+
batch: { role: "member", leaderAssetId: leaderSpec.assetId, index: memberSpec.batch.index },
|
|
12154
|
+
status: error ? "failed" : "processing",
|
|
12155
|
+
error,
|
|
12156
|
+
jobId: error ? null : leaderEntry.jobId,
|
|
12157
|
+
submissionComplete: error ? void 0 : true,
|
|
12158
|
+
reviewObjectId: error ? null : leaderEntry.reviewObjectId,
|
|
12159
|
+
objectId: null,
|
|
12160
|
+
candidateIndex: null,
|
|
12161
|
+
outputs: [],
|
|
12162
|
+
supersededOutputs: previousMemberEntry?.outputs.length ? previousMemberEntry.outputs : previousMemberEntry?.supersededOutputs ?? [],
|
|
12163
|
+
providerMetadata: leaderEntry.providerMetadata,
|
|
12164
|
+
sourceUrl: null,
|
|
12165
|
+
sourceUrls: [],
|
|
12166
|
+
submittedAt: memberSubmittedAt,
|
|
12167
|
+
history: historyAfterReplacing(previousMemberEntry, historyLimit(loaded.manifest), memberSubmittedAt),
|
|
12168
|
+
cost: memberEstimate.amount,
|
|
12169
|
+
costUnit: memberEstimate.unit,
|
|
12170
|
+
provider: provider.id,
|
|
12171
|
+
downloadedAt: null
|
|
12172
|
+
});
|
|
12173
|
+
if (error) failed++;
|
|
12174
|
+
else {
|
|
12175
|
+
submitted++;
|
|
12176
|
+
spent += memberEstimate.amount;
|
|
12177
|
+
log(` ${memberKey} \u2192 ${leaderEntry.jobId} (rides on ${leaderKey}'s batch)`);
|
|
12178
|
+
}
|
|
12179
|
+
}
|
|
12180
|
+
}
|
|
12032
12181
|
for (const { spec, key } of items) {
|
|
12033
12182
|
const estimate = estimates.get(key);
|
|
12183
|
+
if (spec.batch?.role === "member") {
|
|
12184
|
+
continue;
|
|
12185
|
+
}
|
|
12034
12186
|
if (spec.mirror) {
|
|
12035
12187
|
try {
|
|
12036
12188
|
await requireRevisionReady(spec, lock);
|
|
@@ -12063,6 +12215,14 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12063
12215
|
const since = Date.now() - lastSubmitAt;
|
|
12064
12216
|
if (since < spacing) await sleep(spacing - since);
|
|
12065
12217
|
await requireRevisionReady(spec, lock);
|
|
12218
|
+
if (spec.batch?.role === "leader") {
|
|
12219
|
+
const missing = (spec.batch.memberAssetIds ?? []).filter((id) => !byKey.has(lockKey(spec.styleId, id)));
|
|
12220
|
+
if (missing.length) {
|
|
12221
|
+
throw new Error(
|
|
12222
|
+
`${key}: batch member(s) ${missing.join(", ")} must be submitted in the same run as this leader; run without --only, or include every sibling`
|
|
12223
|
+
);
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12066
12226
|
const previousEntry = lock.entries[key];
|
|
12067
12227
|
const resumesCheckpoint = Boolean(
|
|
12068
12228
|
previousEntry?.specHash === spec.specHash && previousEntry.provider === provider.id && previousEntry.generator === spec.generator && previousEntry.submissionComplete === false && previousEntry.jobId
|
|
@@ -12168,6 +12328,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12168
12328
|
inFlight.set(jobId, spec);
|
|
12169
12329
|
submitted++;
|
|
12170
12330
|
spent += estimate.amount;
|
|
12331
|
+
writeBatchMembers(spec, key, null);
|
|
12171
12332
|
log(
|
|
12172
12333
|
` ${key} \u2192 ${jobId} (${spec.width}x${spec.height}` + (estimate.candidates > 1 ? `, ${estimate.candidates} ${spec.tileFeature ? "outputs" : "candidates"}` : "") + `, ${estimate.amount})`
|
|
12173
12334
|
);
|
|
@@ -12179,6 +12340,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12179
12340
|
inFlight.set(entry.jobId, spec);
|
|
12180
12341
|
submitted++;
|
|
12181
12342
|
spent += estimate.amount;
|
|
12343
|
+
writeBatchMembers(spec, key, null);
|
|
12182
12344
|
log(` ${key} \u2192 ${entry.jobId} (recovered from completed checkpoint)`);
|
|
12183
12345
|
} else {
|
|
12184
12346
|
failed++;
|
|
@@ -12187,6 +12349,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12187
12349
|
error: message6,
|
|
12188
12350
|
cost: entry.jobId ? estimate.amount : 0
|
|
12189
12351
|
});
|
|
12352
|
+
writeBatchMembers(spec, key, message6);
|
|
12190
12353
|
log(` FAILED ${key}: ${message6}`);
|
|
12191
12354
|
}
|
|
12192
12355
|
}
|
|
@@ -12778,6 +12941,9 @@ function renderSheet(groups, options = {}) {
|
|
|
12778
12941
|
.cand.active { border-color:var(--dim); box-shadow:0 0 0 2px color-mix(in srgb, var(--dim) 20%, transparent); }
|
|
12779
12942
|
.cand.sel { border-color:var(--ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent); }
|
|
12780
12943
|
.cand.set-frame { cursor:pointer; }
|
|
12944
|
+
.cand.recommended:not(.sel) { border-color:var(--accent); }
|
|
12945
|
+
.cand.recommended .badge { position:absolute; top:-1px; left:-1px; font-size:9.5px; font-weight:650;
|
|
12946
|
+
text-transform:uppercase; letter-spacing:.03em; color:var(--bg); background:var(--accent); padding:1px 5px; }
|
|
12781
12947
|
.cand img { image-rendering:pixelated; display:block;
|
|
12782
12948
|
background-image:
|
|
12783
12949
|
linear-gradient(45deg,#0000 25%,#7f7f7f22 25%,#7f7f7f22 75%,#0000 75%),
|
|
@@ -12935,14 +13101,17 @@ GROUPS.forEach((g, gi) => {
|
|
|
12935
13101
|
}).observe(loop);
|
|
12936
13102
|
}
|
|
12937
13103
|
}
|
|
13104
|
+
const recommended = g.recommendedIndex ?? 0;
|
|
12938
13105
|
g.frameUrls.forEach((url, i) => {
|
|
12939
13106
|
const c = document.createElement('button');
|
|
12940
13107
|
c.type = 'button';
|
|
12941
|
-
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '')
|
|
13108
|
+
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '') +
|
|
13109
|
+
(g.recommendedIndex != null && i === recommended ? ' recommended' : '');
|
|
12942
13110
|
c.tabIndex = -1;
|
|
12943
|
-
c.setAttribute('aria-label', g.mode === 'frame-set'
|
|
13111
|
+
c.setAttribute('aria-label', (g.mode === 'frame-set'
|
|
12944
13112
|
? 'Accept ordered frame set from frame ' + (i + 1)
|
|
12945
|
-
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length)
|
|
13113
|
+
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length) +
|
|
13114
|
+
(g.recommendedIndex != null && i === recommended ? ' (this asset\u2019s declared slot)' : ''));
|
|
12946
13115
|
|
|
12947
13116
|
const preview = document.createElement('img');
|
|
12948
13117
|
preview.className = 'preview';
|
|
@@ -12955,6 +13124,12 @@ GROUPS.forEach((g, gi) => {
|
|
|
12955
13124
|
index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
|
|
12956
13125
|
g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
|
|
12957
13126
|
c.append(preview, index);
|
|
13127
|
+
if (g.recommendedIndex != null && i === recommended) {
|
|
13128
|
+
const badge = document.createElement('span');
|
|
13129
|
+
badge.className = 'badge';
|
|
13130
|
+
badge.textContent = 'declared';
|
|
13131
|
+
c.append(badge);
|
|
13132
|
+
}
|
|
12958
13133
|
if (displayScale > 1 && largestSide <= 96) {
|
|
12959
13134
|
const actual = document.createElement('img');
|
|
12960
13135
|
actual.className = 'actual';
|
|
@@ -12979,6 +13154,7 @@ GROUPS.forEach((g, gi) => {
|
|
|
12979
13154
|
});
|
|
12980
13155
|
frames.dataset.active = '0';
|
|
12981
13156
|
frames.querySelector('.cand')?.classList.add('active');
|
|
13157
|
+
if (recommended > 0) activate(recommended, frames);
|
|
12982
13158
|
root.appendChild(el);
|
|
12983
13159
|
});
|
|
12984
13160
|
|
|
@@ -13234,7 +13410,11 @@ async function prepareReview(provider, lock, opts = {}) {
|
|
|
13234
13410
|
height: spec.revision.sourceHeight
|
|
13235
13411
|
}
|
|
13236
13412
|
} : {},
|
|
13237
|
-
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {}
|
|
13413
|
+
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {},
|
|
13414
|
+
// A batch member's declared slot in the shared candidate set — the
|
|
13415
|
+
// leader's own slot 0 is already the sheet's ordinary default, so
|
|
13416
|
+
// only a member (whose slot is 1+) needs to say so explicitly.
|
|
13417
|
+
...entry.batch?.index != null ? { recommendedIndex: entry.batch.index } : {}
|
|
13238
13418
|
});
|
|
13239
13419
|
} catch (err) {
|
|
13240
13420
|
log(` could not load candidates for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -16282,6 +16462,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
16282
16462
|
}
|
|
16283
16463
|
// Annotate the CommonJS export names for ESM import in node:
|
|
16284
16464
|
0 && (module.exports = {
|
|
16465
|
+
AssetBatchSchema,
|
|
16285
16466
|
AssetSchema,
|
|
16286
16467
|
BudgetError,
|
|
16287
16468
|
CHARACTER_DIRECTIONS_4,
|