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/cli.js
CHANGED
|
@@ -703,6 +703,11 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
703
703
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
704
704
|
// compares those directly, so only the choice of source is identity.
|
|
705
705
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
706
|
+
// The whole group's ordered descriptions, not just this asset's own —
|
|
707
|
+
// any sibling's prompt changing, or a member being added or removed,
|
|
708
|
+
// must mark every member stale together, since they all ride on one
|
|
709
|
+
// submitted job with no way to amend it after the fact.
|
|
710
|
+
batch: spec.batch ? { role: spec.batch.role, itemDescriptions: spec.batch.itemDescriptions, index: spec.batch.index } : void 0,
|
|
706
711
|
revision: spec.revision ? {
|
|
707
712
|
mode: spec.revision.mode,
|
|
708
713
|
from: spec.revision.sourceAssetId,
|
|
@@ -857,6 +862,12 @@ var CharacterProportionsSchema = z.union([
|
|
|
857
862
|
}).strict()
|
|
858
863
|
]);
|
|
859
864
|
var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
|
|
865
|
+
var AssetBatchSchema = z.object({
|
|
866
|
+
/** The batch leader: another `1dir` asset in the same style. */
|
|
867
|
+
of: z.string().min(1),
|
|
868
|
+
/** 1-based slot in the batch (the leader is implicitly slot 0). Unique and contiguous among siblings. */
|
|
869
|
+
index: z.number().int().min(1)
|
|
870
|
+
}).strict();
|
|
860
871
|
var CharacterStateSchema = z.object({
|
|
861
872
|
/** The base character, or another state, in the same style. */
|
|
862
873
|
of: z.string().min(1),
|
|
@@ -1430,6 +1441,12 @@ var AssetSchema = z.object({
|
|
|
1430
1441
|
* `prompt` instead.
|
|
1431
1442
|
*/
|
|
1432
1443
|
animation: CharacterAnimationSchema.optional(),
|
|
1444
|
+
/**
|
|
1445
|
+
* `1dir` styles only: this asset rides along on another `1dir` asset's
|
|
1446
|
+
* batch submission instead of generating on its own. See
|
|
1447
|
+
* `AssetBatchSchema`.
|
|
1448
|
+
*/
|
|
1449
|
+
batch: AssetBatchSchema.optional(),
|
|
1433
1450
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1434
1451
|
proportions: CharacterProportionsSchema.optional(),
|
|
1435
1452
|
/**
|
|
@@ -1506,7 +1523,7 @@ var AssetSchema = z.object({
|
|
|
1506
1523
|
path: ["revision"]
|
|
1507
1524
|
});
|
|
1508
1525
|
}
|
|
1509
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror"].filter(Boolean);
|
|
1526
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
1510
1527
|
if (shapes.length > 1) {
|
|
1511
1528
|
context.addIssue({
|
|
1512
1529
|
code: z.ZodIssueCode.custom,
|
|
@@ -1657,6 +1674,12 @@ var LockEntrySchema = z.object({
|
|
|
1657
1674
|
sourceAssetId: z.string().min(1),
|
|
1658
1675
|
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
1659
1676
|
}).strict().nullable().default(null),
|
|
1677
|
+
/** For a `1dir` batch member: which leader and slot it rode along on. */
|
|
1678
|
+
batch: z.object({
|
|
1679
|
+
role: z.enum(["leader", "member"]),
|
|
1680
|
+
leaderAssetId: z.string().min(1).optional(),
|
|
1681
|
+
index: z.number().int().min(1).optional()
|
|
1682
|
+
}).strict().nullable().default(null),
|
|
1660
1683
|
/**
|
|
1661
1684
|
* Output hashes owned by the previous generation while its replacement is
|
|
1662
1685
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -6219,14 +6242,15 @@ var PixelLabClient = class {
|
|
|
6219
6242
|
/**
|
|
6220
6243
|
* `/reduce-colors`, PixelLab's "Cleanup" tier: quantize one image onto a
|
|
6221
6244
|
* smaller palette, synchronously — no `background_job_id`, the result
|
|
6222
|
-
* comes back in this same response, like `createImagePixflux`.
|
|
6223
|
-
*
|
|
6224
|
-
*
|
|
6225
|
-
*
|
|
6226
|
-
*
|
|
6227
|
-
*
|
|
6228
|
-
*
|
|
6229
|
-
*
|
|
6245
|
+
* comes back in this same response, like `createImagePixflux`. The
|
|
6246
|
+
* schema's own response example is dollar-denominated (`usage: {type:
|
|
6247
|
+
* "usd", usd: 0.02}`), which — matching this codebase's repeated
|
|
6248
|
+
* experience with PixelLab's documented-vs-billed cost mismatches
|
|
6249
|
+
* (`isometricTile`, `objectPro`) — did not hold: confirmed live against a
|
|
6250
|
+
* Tier 2 account at exactly 0.1 generations for a 32x32 source
|
|
6251
|
+
* (docs/REVISIONS.md). `numColors` and `paletteImage` are mutually
|
|
6252
|
+
* exclusive upstream; the manifest schema already enforces that before
|
|
6253
|
+
* this is ever called.
|
|
6230
6254
|
*/
|
|
6231
6255
|
async reduceColors(args) {
|
|
6232
6256
|
const body = { images: [args.image] };
|
|
@@ -6249,8 +6273,9 @@ var PixelLabClient = class {
|
|
|
6249
6273
|
/**
|
|
6250
6274
|
* `/correct-pixelart`, PixelLab's "Cleanup" tier: sharpen edges and drop
|
|
6251
6275
|
* stray pixels without resizing, synchronously — same shape as
|
|
6252
|
-
* `reduceColors` above, no background job. Cost is likewise
|
|
6253
|
-
*
|
|
6276
|
+
* `reduceColors` above, no background job. Cost is likewise confirmed
|
|
6277
|
+
* live at 0.1 generations for a 32x32 source, not the schema's own
|
|
6278
|
+
* dollar-denominated example (`usage: {type: "usd", usd: 0.02}`).
|
|
6254
6279
|
*/
|
|
6255
6280
|
async correctPixelart(args) {
|
|
6256
6281
|
const body = { images: [args.image] };
|
|
@@ -6518,6 +6543,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6518
6543
|
return dir;
|
|
6519
6544
|
}
|
|
6520
6545
|
estimate(spec) {
|
|
6546
|
+
if (spec.batch?.role === "member") {
|
|
6547
|
+
return { unit: "generations", amount: 0, candidates: 1 };
|
|
6548
|
+
}
|
|
6521
6549
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
6522
6550
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
6523
6551
|
}
|
|
@@ -7316,11 +7344,21 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7316
7344
|
return { jobId: res2.background_job_id };
|
|
7317
7345
|
}
|
|
7318
7346
|
if (spec.generator === "1dir") {
|
|
7347
|
+
if (spec.batch?.role === "member") {
|
|
7348
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: a batch member cannot submit on its own`);
|
|
7349
|
+
}
|
|
7319
7350
|
const res2 = await this.client.create1Direction({
|
|
7320
7351
|
description: spec.prompt,
|
|
7321
7352
|
size: spec.size,
|
|
7322
7353
|
view: spec.view === "sidescroller" ? "sidescroller" : "top-down",
|
|
7323
|
-
styleImages
|
|
7354
|
+
styleImages,
|
|
7355
|
+
// The leader's own subject is item_descriptions[0], so `description`
|
|
7356
|
+
// matches it exactly. Confirmed live: item_descriptions[0] owns
|
|
7357
|
+
// candidate slot 0 (a 3-item batch returned exactly the declared
|
|
7358
|
+
// items at frames 0/1/2, in order; docs/GENERATORS.md#1dir), so
|
|
7359
|
+
// sending the same text as `description` too is redundant but
|
|
7360
|
+
// harmless, not a hedge against ambiguity.
|
|
7361
|
+
itemDescriptions: spec.batch?.role === "leader" ? spec.batch.itemDescriptions : void 0
|
|
7324
7362
|
});
|
|
7325
7363
|
return { jobId: res2.object_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
7326
7364
|
}
|
|
@@ -9339,6 +9377,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9339
9377
|
};
|
|
9340
9378
|
styleSpecs.set(assetId, resolved);
|
|
9341
9379
|
}
|
|
9380
|
+
const batchMembersByLeader = /* @__PURE__ */ new Map();
|
|
9381
|
+
for (const id of styleSpecs.keys()) {
|
|
9382
|
+
const batch = manifest.assets[id].batch;
|
|
9383
|
+
if (!batch) continue;
|
|
9384
|
+
const members = batchMembersByLeader.get(batch.of) ?? [];
|
|
9385
|
+
members.push({ id, index: batch.index });
|
|
9386
|
+
batchMembersByLeader.set(batch.of, members);
|
|
9387
|
+
}
|
|
9342
9388
|
const finalized = /* @__PURE__ */ new Set();
|
|
9343
9389
|
const finalize = async (assetId) => {
|
|
9344
9390
|
const resolved = styleSpecs.get(assetId);
|
|
@@ -9497,6 +9543,56 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9497
9543
|
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
9498
9544
|
};
|
|
9499
9545
|
}
|
|
9546
|
+
if (asset.batch) {
|
|
9547
|
+
if (asset.batch.of === assetId) throw new Error(`assets.${assetId}: a batch cannot name itself as its own leader`);
|
|
9548
|
+
const leaderSpec = await finalize(asset.batch.of);
|
|
9549
|
+
if (!leaderSpec.batch || leaderSpec.batch.role !== "leader") {
|
|
9550
|
+
throw new Error(`assets.${assetId}: ${asset.batch.of} is not a 1dir batch leader`);
|
|
9551
|
+
}
|
|
9552
|
+
if (resolved.size !== leaderSpec.size) {
|
|
9553
|
+
throw new Error(
|
|
9554
|
+
`assets.${assetId}: batch members share the leader's canvas size; ${asset.batch.of} is ${leaderSpec.size}px, this is ${resolved.size}px`
|
|
9555
|
+
);
|
|
9556
|
+
}
|
|
9557
|
+
resolved.batch = {
|
|
9558
|
+
role: "member",
|
|
9559
|
+
itemDescriptions: leaderSpec.batch.itemDescriptions,
|
|
9560
|
+
index: asset.batch.index,
|
|
9561
|
+
leaderAssetId: asset.batch.of,
|
|
9562
|
+
leaderSpec
|
|
9563
|
+
};
|
|
9564
|
+
} else {
|
|
9565
|
+
const members = batchMembersByLeader.get(assetId);
|
|
9566
|
+
if (members) {
|
|
9567
|
+
if (style.generator !== "1dir") {
|
|
9568
|
+
throw new Error(`assets.${assetId}: only a 1dir asset can lead a batch`);
|
|
9569
|
+
}
|
|
9570
|
+
const sorted = [...members].sort((a, b) => a.index - b.index);
|
|
9571
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9572
|
+
for (const { index } of sorted) {
|
|
9573
|
+
if (seen.has(index)) throw new Error(`assets.${assetId}: two batch members both claim index ${index}`);
|
|
9574
|
+
seen.add(index);
|
|
9575
|
+
}
|
|
9576
|
+
const expectedIndices = sorted.map((_, i) => i + 1).join(",");
|
|
9577
|
+
if (sorted.map((m) => m.index).join(",") !== expectedIndices) {
|
|
9578
|
+
throw new Error(
|
|
9579
|
+
`assets.${assetId}: batch member indices must be 1..${sorted.length} with no gaps; got ${sorted.map((m) => m.index).join(",")}`
|
|
9580
|
+
);
|
|
9581
|
+
}
|
|
9582
|
+
const total = 1 + sorted.length;
|
|
9583
|
+
const limit = candidateCount(resolved.size);
|
|
9584
|
+
if (total > limit) {
|
|
9585
|
+
throw new Error(
|
|
9586
|
+
`assets.${assetId}: a ${resolved.size}px batch holds at most ${limit} items (1 leader + ${limit - 1} members); this one declares ${total}`
|
|
9587
|
+
);
|
|
9588
|
+
}
|
|
9589
|
+
resolved.batch = {
|
|
9590
|
+
role: "leader",
|
|
9591
|
+
itemDescriptions: [resolved.prompt, ...sorted.map(({ id }) => styleSpecs.get(id).prompt)],
|
|
9592
|
+
memberAssetIds: sorted.map((m) => m.id)
|
|
9593
|
+
};
|
|
9594
|
+
}
|
|
9595
|
+
}
|
|
9500
9596
|
resolved.specHash = specHash(
|
|
9501
9597
|
resolved,
|
|
9502
9598
|
styleImageHashes,
|
|
@@ -13764,6 +13860,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13764
13860
|
for (const styleId of new Set(items.map((i) => i.spec.styleId))) {
|
|
13765
13861
|
styleImages.set(styleId, await resolveStyleImages(loaded, styleId));
|
|
13766
13862
|
}
|
|
13863
|
+
const byKey = new Map(items.map((item) => [item.key, item]));
|
|
13767
13864
|
let submitted = 0;
|
|
13768
13865
|
let failed = 0;
|
|
13769
13866
|
let spent = 0;
|
|
@@ -13793,8 +13890,62 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13793
13890
|
await pruneInFlight();
|
|
13794
13891
|
}
|
|
13795
13892
|
}
|
|
13893
|
+
function writeBatchMembers(leaderSpec, leaderKey, error) {
|
|
13894
|
+
if (leaderSpec.batch?.role !== "leader") return;
|
|
13895
|
+
const leaderEntry = lock.entries[leaderKey];
|
|
13896
|
+
for (const memberAssetId of leaderSpec.batch.memberAssetIds ?? []) {
|
|
13897
|
+
const memberKey = lockKey(leaderSpec.styleId, memberAssetId);
|
|
13898
|
+
const memberItem = byKey.get(memberKey);
|
|
13899
|
+
if (!memberItem) {
|
|
13900
|
+
throw new Error(
|
|
13901
|
+
`${leaderKey}: batch member "${memberAssetId}" must be submitted in the same run as its leader; run without --only, or include every sibling`
|
|
13902
|
+
);
|
|
13903
|
+
}
|
|
13904
|
+
const memberSpec = memberItem.spec;
|
|
13905
|
+
const memberEstimate = estimates.get(memberKey);
|
|
13906
|
+
const previousMemberEntry = lock.entries[memberKey];
|
|
13907
|
+
const memberSubmittedAt = leaderEntry.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
13908
|
+
upsert(lock, memberKey, {
|
|
13909
|
+
styleId: memberSpec.styleId,
|
|
13910
|
+
assetId: memberSpec.assetId,
|
|
13911
|
+
specHash: memberSpec.specHash,
|
|
13912
|
+
generator: memberSpec.generator,
|
|
13913
|
+
prompt: memberSpec.prompt,
|
|
13914
|
+
width: memberSpec.width,
|
|
13915
|
+
height: memberSpec.height,
|
|
13916
|
+
batch: { role: "member", leaderAssetId: leaderSpec.assetId, index: memberSpec.batch.index },
|
|
13917
|
+
status: error ? "failed" : "processing",
|
|
13918
|
+
error,
|
|
13919
|
+
jobId: error ? null : leaderEntry.jobId,
|
|
13920
|
+
submissionComplete: error ? void 0 : true,
|
|
13921
|
+
reviewObjectId: error ? null : leaderEntry.reviewObjectId,
|
|
13922
|
+
objectId: null,
|
|
13923
|
+
candidateIndex: null,
|
|
13924
|
+
outputs: [],
|
|
13925
|
+
supersededOutputs: previousMemberEntry?.outputs.length ? previousMemberEntry.outputs : previousMemberEntry?.supersededOutputs ?? [],
|
|
13926
|
+
providerMetadata: leaderEntry.providerMetadata,
|
|
13927
|
+
sourceUrl: null,
|
|
13928
|
+
sourceUrls: [],
|
|
13929
|
+
submittedAt: memberSubmittedAt,
|
|
13930
|
+
history: historyAfterReplacing(previousMemberEntry, historyLimit(loaded.manifest), memberSubmittedAt),
|
|
13931
|
+
cost: memberEstimate.amount,
|
|
13932
|
+
costUnit: memberEstimate.unit,
|
|
13933
|
+
provider: provider.id,
|
|
13934
|
+
downloadedAt: null
|
|
13935
|
+
});
|
|
13936
|
+
if (error) failed++;
|
|
13937
|
+
else {
|
|
13938
|
+
submitted++;
|
|
13939
|
+
spent += memberEstimate.amount;
|
|
13940
|
+
log2(` ${memberKey} \u2192 ${leaderEntry.jobId} (rides on ${leaderKey}'s batch)`);
|
|
13941
|
+
}
|
|
13942
|
+
}
|
|
13943
|
+
}
|
|
13796
13944
|
for (const { spec, key } of items) {
|
|
13797
13945
|
const estimate = estimates.get(key);
|
|
13946
|
+
if (spec.batch?.role === "member") {
|
|
13947
|
+
continue;
|
|
13948
|
+
}
|
|
13798
13949
|
if (spec.mirror) {
|
|
13799
13950
|
try {
|
|
13800
13951
|
await requireRevisionReady(spec, lock);
|
|
@@ -13827,6 +13978,14 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13827
13978
|
const since = Date.now() - lastSubmitAt;
|
|
13828
13979
|
if (since < spacing) await sleep2(spacing - since);
|
|
13829
13980
|
await requireRevisionReady(spec, lock);
|
|
13981
|
+
if (spec.batch?.role === "leader") {
|
|
13982
|
+
const missing = (spec.batch.memberAssetIds ?? []).filter((id) => !byKey.has(lockKey(spec.styleId, id)));
|
|
13983
|
+
if (missing.length) {
|
|
13984
|
+
throw new Error(
|
|
13985
|
+
`${key}: batch member(s) ${missing.join(", ")} must be submitted in the same run as this leader; run without --only, or include every sibling`
|
|
13986
|
+
);
|
|
13987
|
+
}
|
|
13988
|
+
}
|
|
13830
13989
|
const previousEntry = lock.entries[key];
|
|
13831
13990
|
const resumesCheckpoint = Boolean(
|
|
13832
13991
|
previousEntry?.specHash === spec.specHash && previousEntry.provider === provider.id && previousEntry.generator === spec.generator && previousEntry.submissionComplete === false && previousEntry.jobId
|
|
@@ -13932,6 +14091,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13932
14091
|
inFlight.set(jobId, spec);
|
|
13933
14092
|
submitted++;
|
|
13934
14093
|
spent += estimate.amount;
|
|
14094
|
+
writeBatchMembers(spec, key, null);
|
|
13935
14095
|
log2(
|
|
13936
14096
|
` ${key} \u2192 ${jobId} (${spec.width}x${spec.height}` + (estimate.candidates > 1 ? `, ${estimate.candidates} ${spec.tileFeature ? "outputs" : "candidates"}` : "") + `, ${estimate.amount})`
|
|
13937
14097
|
);
|
|
@@ -13943,6 +14103,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13943
14103
|
inFlight.set(entry.jobId, spec);
|
|
13944
14104
|
submitted++;
|
|
13945
14105
|
spent += estimate.amount;
|
|
14106
|
+
writeBatchMembers(spec, key, null);
|
|
13946
14107
|
log2(` ${key} \u2192 ${entry.jobId} (recovered from completed checkpoint)`);
|
|
13947
14108
|
} else {
|
|
13948
14109
|
failed++;
|
|
@@ -13951,6 +14112,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13951
14112
|
error: message6,
|
|
13952
14113
|
cost: entry.jobId ? estimate.amount : 0
|
|
13953
14114
|
});
|
|
14115
|
+
writeBatchMembers(spec, key, message6);
|
|
13954
14116
|
log2(` FAILED ${key}: ${message6}`);
|
|
13955
14117
|
}
|
|
13956
14118
|
}
|
|
@@ -14151,6 +14313,9 @@ function renderSheet(groups, options = {}) {
|
|
|
14151
14313
|
.cand.active { border-color:var(--dim); box-shadow:0 0 0 2px color-mix(in srgb, var(--dim) 20%, transparent); }
|
|
14152
14314
|
.cand.sel { border-color:var(--ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent); }
|
|
14153
14315
|
.cand.set-frame { cursor:pointer; }
|
|
14316
|
+
.cand.recommended:not(.sel) { border-color:var(--accent); }
|
|
14317
|
+
.cand.recommended .badge { position:absolute; top:-1px; left:-1px; font-size:9.5px; font-weight:650;
|
|
14318
|
+
text-transform:uppercase; letter-spacing:.03em; color:var(--bg); background:var(--accent); padding:1px 5px; }
|
|
14154
14319
|
.cand img { image-rendering:pixelated; display:block;
|
|
14155
14320
|
background-image:
|
|
14156
14321
|
linear-gradient(45deg,#0000 25%,#7f7f7f22 25%,#7f7f7f22 75%,#0000 75%),
|
|
@@ -14308,14 +14473,17 @@ GROUPS.forEach((g, gi) => {
|
|
|
14308
14473
|
}).observe(loop);
|
|
14309
14474
|
}
|
|
14310
14475
|
}
|
|
14476
|
+
const recommended = g.recommendedIndex ?? 0;
|
|
14311
14477
|
g.frameUrls.forEach((url, i) => {
|
|
14312
14478
|
const c = document.createElement('button');
|
|
14313
14479
|
c.type = 'button';
|
|
14314
|
-
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '')
|
|
14480
|
+
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '') +
|
|
14481
|
+
(g.recommendedIndex != null && i === recommended ? ' recommended' : '');
|
|
14315
14482
|
c.tabIndex = -1;
|
|
14316
|
-
c.setAttribute('aria-label', g.mode === 'frame-set'
|
|
14483
|
+
c.setAttribute('aria-label', (g.mode === 'frame-set'
|
|
14317
14484
|
? 'Accept ordered frame set from frame ' + (i + 1)
|
|
14318
|
-
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length)
|
|
14485
|
+
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length) +
|
|
14486
|
+
(g.recommendedIndex != null && i === recommended ? ' (this asset\u2019s declared slot)' : ''));
|
|
14319
14487
|
|
|
14320
14488
|
const preview = document.createElement('img');
|
|
14321
14489
|
preview.className = 'preview';
|
|
@@ -14328,6 +14496,12 @@ GROUPS.forEach((g, gi) => {
|
|
|
14328
14496
|
index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
|
|
14329
14497
|
g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
|
|
14330
14498
|
c.append(preview, index);
|
|
14499
|
+
if (g.recommendedIndex != null && i === recommended) {
|
|
14500
|
+
const badge = document.createElement('span');
|
|
14501
|
+
badge.className = 'badge';
|
|
14502
|
+
badge.textContent = 'declared';
|
|
14503
|
+
c.append(badge);
|
|
14504
|
+
}
|
|
14331
14505
|
if (displayScale > 1 && largestSide <= 96) {
|
|
14332
14506
|
const actual = document.createElement('img');
|
|
14333
14507
|
actual.className = 'actual';
|
|
@@ -14352,6 +14526,7 @@ GROUPS.forEach((g, gi) => {
|
|
|
14352
14526
|
});
|
|
14353
14527
|
frames.dataset.active = '0';
|
|
14354
14528
|
frames.querySelector('.cand')?.classList.add('active');
|
|
14529
|
+
if (recommended > 0) activate(recommended, frames);
|
|
14355
14530
|
root.appendChild(el);
|
|
14356
14531
|
});
|
|
14357
14532
|
|
|
@@ -14526,7 +14701,11 @@ async function prepareReview(provider, lock, opts = {}) {
|
|
|
14526
14701
|
height: spec.revision.sourceHeight
|
|
14527
14702
|
}
|
|
14528
14703
|
} : {},
|
|
14529
|
-
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {}
|
|
14704
|
+
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {},
|
|
14705
|
+
// A batch member's declared slot in the shared candidate set — the
|
|
14706
|
+
// leader's own slot 0 is already the sheet's ordinary default, so
|
|
14707
|
+
// only a member (whose slot is 1+) needs to say so explicitly.
|
|
14708
|
+
...entry.batch?.index != null ? { recommendedIndex: entry.batch.index } : {}
|
|
14530
14709
|
});
|
|
14531
14710
|
} catch (err) {
|
|
14532
14711
|
log2(` could not load candidates for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|