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.js
CHANGED
|
@@ -123,6 +123,12 @@ var CharacterProportionsSchema = z.union([
|
|
|
123
123
|
}).strict()
|
|
124
124
|
]);
|
|
125
125
|
var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
|
|
126
|
+
var AssetBatchSchema = z.object({
|
|
127
|
+
/** The batch leader: another `1dir` asset in the same style. */
|
|
128
|
+
of: z.string().min(1),
|
|
129
|
+
/** 1-based slot in the batch (the leader is implicitly slot 0). Unique and contiguous among siblings. */
|
|
130
|
+
index: z.number().int().min(1)
|
|
131
|
+
}).strict();
|
|
126
132
|
var CharacterStateSchema = z.object({
|
|
127
133
|
/** The base character, or another state, in the same style. */
|
|
128
134
|
of: z.string().min(1),
|
|
@@ -696,6 +702,12 @@ var AssetSchema = z.object({
|
|
|
696
702
|
* `prompt` instead.
|
|
697
703
|
*/
|
|
698
704
|
animation: CharacterAnimationSchema.optional(),
|
|
705
|
+
/**
|
|
706
|
+
* `1dir` styles only: this asset rides along on another `1dir` asset's
|
|
707
|
+
* batch submission instead of generating on its own. See
|
|
708
|
+
* `AssetBatchSchema`.
|
|
709
|
+
*/
|
|
710
|
+
batch: AssetBatchSchema.optional(),
|
|
699
711
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
700
712
|
proportions: CharacterProportionsSchema.optional(),
|
|
701
713
|
/**
|
|
@@ -772,7 +784,7 @@ var AssetSchema = z.object({
|
|
|
772
784
|
path: ["revision"]
|
|
773
785
|
});
|
|
774
786
|
}
|
|
775
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror"].filter(Boolean);
|
|
787
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
776
788
|
if (shapes.length > 1) {
|
|
777
789
|
context.addIssue({
|
|
778
790
|
code: z.ZodIssueCode.custom,
|
|
@@ -923,6 +935,12 @@ var LockEntrySchema = z.object({
|
|
|
923
935
|
sourceAssetId: z.string().min(1),
|
|
924
936
|
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
925
937
|
}).strict().nullable().default(null),
|
|
938
|
+
/** For a `1dir` batch member: which leader and slot it rode along on. */
|
|
939
|
+
batch: z.object({
|
|
940
|
+
role: z.enum(["leader", "member"]),
|
|
941
|
+
leaderAssetId: z.string().min(1).optional(),
|
|
942
|
+
index: z.number().int().min(1).optional()
|
|
943
|
+
}).strict().nullable().default(null),
|
|
926
944
|
/**
|
|
927
945
|
* Output hashes owned by the previous generation while its replacement is
|
|
928
946
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -1973,14 +1991,15 @@ var PixelLabClient = class {
|
|
|
1973
1991
|
/**
|
|
1974
1992
|
* `/reduce-colors`, PixelLab's "Cleanup" tier: quantize one image onto a
|
|
1975
1993
|
* smaller palette, synchronously — no `background_job_id`, the result
|
|
1976
|
-
* comes back in this same response, like `createImagePixflux`.
|
|
1977
|
-
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
*
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1994
|
+
* comes back in this same response, like `createImagePixflux`. The
|
|
1995
|
+
* schema's own response example is dollar-denominated (`usage: {type:
|
|
1996
|
+
* "usd", usd: 0.02}`), which — matching this codebase's repeated
|
|
1997
|
+
* experience with PixelLab's documented-vs-billed cost mismatches
|
|
1998
|
+
* (`isometricTile`, `objectPro`) — did not hold: confirmed live against a
|
|
1999
|
+
* Tier 2 account at exactly 0.1 generations for a 32x32 source
|
|
2000
|
+
* (docs/REVISIONS.md). `numColors` and `paletteImage` are mutually
|
|
2001
|
+
* exclusive upstream; the manifest schema already enforces that before
|
|
2002
|
+
* this is ever called.
|
|
1984
2003
|
*/
|
|
1985
2004
|
async reduceColors(args) {
|
|
1986
2005
|
const body = { images: [args.image] };
|
|
@@ -2003,8 +2022,9 @@ var PixelLabClient = class {
|
|
|
2003
2022
|
/**
|
|
2004
2023
|
* `/correct-pixelart`, PixelLab's "Cleanup" tier: sharpen edges and drop
|
|
2005
2024
|
* stray pixels without resizing, synchronously — same shape as
|
|
2006
|
-
* `reduceColors` above, no background job. Cost is likewise
|
|
2007
|
-
*
|
|
2025
|
+
* `reduceColors` above, no background job. Cost is likewise confirmed
|
|
2026
|
+
* live at 0.1 generations for a 32x32 source, not the schema's own
|
|
2027
|
+
* dollar-denominated example (`usage: {type: "usd", usd: 0.02}`).
|
|
2008
2028
|
*/
|
|
2009
2029
|
async correctPixelart(args) {
|
|
2010
2030
|
const body = { images: [args.image] };
|
|
@@ -2760,6 +2780,11 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2760
2780
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2761
2781
|
// compares those directly, so only the choice of source is identity.
|
|
2762
2782
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
2783
|
+
// The whole group's ordered descriptions, not just this asset's own —
|
|
2784
|
+
// any sibling's prompt changing, or a member being added or removed,
|
|
2785
|
+
// must mark every member stale together, since they all ride on one
|
|
2786
|
+
// submitted job with no way to amend it after the fact.
|
|
2787
|
+
batch: spec.batch ? { role: spec.batch.role, itemDescriptions: spec.batch.itemDescriptions, index: spec.batch.index } : void 0,
|
|
2763
2788
|
revision: spec.revision ? {
|
|
2764
2789
|
mode: spec.revision.mode,
|
|
2765
2790
|
from: spec.revision.sourceAssetId,
|
|
@@ -2941,6 +2966,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2941
2966
|
return dir;
|
|
2942
2967
|
}
|
|
2943
2968
|
estimate(spec) {
|
|
2969
|
+
if (spec.batch?.role === "member") {
|
|
2970
|
+
return { unit: "generations", amount: 0, candidates: 1 };
|
|
2971
|
+
}
|
|
2944
2972
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
2945
2973
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
2946
2974
|
}
|
|
@@ -3739,11 +3767,21 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3739
3767
|
return { jobId: res2.background_job_id };
|
|
3740
3768
|
}
|
|
3741
3769
|
if (spec.generator === "1dir") {
|
|
3770
|
+
if (spec.batch?.role === "member") {
|
|
3771
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: a batch member cannot submit on its own`);
|
|
3772
|
+
}
|
|
3742
3773
|
const res2 = await this.client.create1Direction({
|
|
3743
3774
|
description: spec.prompt,
|
|
3744
3775
|
size: spec.size,
|
|
3745
3776
|
view: spec.view === "sidescroller" ? "sidescroller" : "top-down",
|
|
3746
|
-
styleImages
|
|
3777
|
+
styleImages,
|
|
3778
|
+
// The leader's own subject is item_descriptions[0], so `description`
|
|
3779
|
+
// matches it exactly. Confirmed live: item_descriptions[0] owns
|
|
3780
|
+
// candidate slot 0 (a 3-item batch returned exactly the declared
|
|
3781
|
+
// items at frames 0/1/2, in order; docs/GENERATORS.md#1dir), so
|
|
3782
|
+
// sending the same text as `description` too is redundant but
|
|
3783
|
+
// harmless, not a hedge against ambiguity.
|
|
3784
|
+
itemDescriptions: spec.batch?.role === "leader" ? spec.batch.itemDescriptions : void 0
|
|
3747
3785
|
});
|
|
3748
3786
|
return { jobId: res2.object_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
3749
3787
|
}
|
|
@@ -7153,6 +7191,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7153
7191
|
};
|
|
7154
7192
|
styleSpecs.set(assetId, resolved);
|
|
7155
7193
|
}
|
|
7194
|
+
const batchMembersByLeader = /* @__PURE__ */ new Map();
|
|
7195
|
+
for (const id of styleSpecs.keys()) {
|
|
7196
|
+
const batch = manifest.assets[id].batch;
|
|
7197
|
+
if (!batch) continue;
|
|
7198
|
+
const members = batchMembersByLeader.get(batch.of) ?? [];
|
|
7199
|
+
members.push({ id, index: batch.index });
|
|
7200
|
+
batchMembersByLeader.set(batch.of, members);
|
|
7201
|
+
}
|
|
7156
7202
|
const finalized = /* @__PURE__ */ new Set();
|
|
7157
7203
|
const finalize = async (assetId) => {
|
|
7158
7204
|
const resolved = styleSpecs.get(assetId);
|
|
@@ -7311,6 +7357,56 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7311
7357
|
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
7312
7358
|
};
|
|
7313
7359
|
}
|
|
7360
|
+
if (asset.batch) {
|
|
7361
|
+
if (asset.batch.of === assetId) throw new Error(`assets.${assetId}: a batch cannot name itself as its own leader`);
|
|
7362
|
+
const leaderSpec = await finalize(asset.batch.of);
|
|
7363
|
+
if (!leaderSpec.batch || leaderSpec.batch.role !== "leader") {
|
|
7364
|
+
throw new Error(`assets.${assetId}: ${asset.batch.of} is not a 1dir batch leader`);
|
|
7365
|
+
}
|
|
7366
|
+
if (resolved.size !== leaderSpec.size) {
|
|
7367
|
+
throw new Error(
|
|
7368
|
+
`assets.${assetId}: batch members share the leader's canvas size; ${asset.batch.of} is ${leaderSpec.size}px, this is ${resolved.size}px`
|
|
7369
|
+
);
|
|
7370
|
+
}
|
|
7371
|
+
resolved.batch = {
|
|
7372
|
+
role: "member",
|
|
7373
|
+
itemDescriptions: leaderSpec.batch.itemDescriptions,
|
|
7374
|
+
index: asset.batch.index,
|
|
7375
|
+
leaderAssetId: asset.batch.of,
|
|
7376
|
+
leaderSpec
|
|
7377
|
+
};
|
|
7378
|
+
} else {
|
|
7379
|
+
const members = batchMembersByLeader.get(assetId);
|
|
7380
|
+
if (members) {
|
|
7381
|
+
if (style.generator !== "1dir") {
|
|
7382
|
+
throw new Error(`assets.${assetId}: only a 1dir asset can lead a batch`);
|
|
7383
|
+
}
|
|
7384
|
+
const sorted = [...members].sort((a, b) => a.index - b.index);
|
|
7385
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7386
|
+
for (const { index } of sorted) {
|
|
7387
|
+
if (seen.has(index)) throw new Error(`assets.${assetId}: two batch members both claim index ${index}`);
|
|
7388
|
+
seen.add(index);
|
|
7389
|
+
}
|
|
7390
|
+
const expectedIndices = sorted.map((_, i) => i + 1).join(",");
|
|
7391
|
+
if (sorted.map((m) => m.index).join(",") !== expectedIndices) {
|
|
7392
|
+
throw new Error(
|
|
7393
|
+
`assets.${assetId}: batch member indices must be 1..${sorted.length} with no gaps; got ${sorted.map((m) => m.index).join(",")}`
|
|
7394
|
+
);
|
|
7395
|
+
}
|
|
7396
|
+
const total = 1 + sorted.length;
|
|
7397
|
+
const limit = candidateCount(resolved.size);
|
|
7398
|
+
if (total > limit) {
|
|
7399
|
+
throw new Error(
|
|
7400
|
+
`assets.${assetId}: a ${resolved.size}px batch holds at most ${limit} items (1 leader + ${limit - 1} members); this one declares ${total}`
|
|
7401
|
+
);
|
|
7402
|
+
}
|
|
7403
|
+
resolved.batch = {
|
|
7404
|
+
role: "leader",
|
|
7405
|
+
itemDescriptions: [resolved.prompt, ...sorted.map(({ id }) => styleSpecs.get(id).prompt)],
|
|
7406
|
+
memberAssetIds: sorted.map((m) => m.id)
|
|
7407
|
+
};
|
|
7408
|
+
}
|
|
7409
|
+
}
|
|
7314
7410
|
resolved.specHash = specHash(
|
|
7315
7411
|
resolved,
|
|
7316
7412
|
styleImageHashes,
|
|
@@ -11696,6 +11792,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11696
11792
|
for (const styleId of new Set(items.map((i) => i.spec.styleId))) {
|
|
11697
11793
|
styleImages.set(styleId, await resolveStyleImages(loaded, styleId));
|
|
11698
11794
|
}
|
|
11795
|
+
const byKey = new Map(items.map((item) => [item.key, item]));
|
|
11699
11796
|
let submitted = 0;
|
|
11700
11797
|
let failed = 0;
|
|
11701
11798
|
let spent = 0;
|
|
@@ -11725,8 +11822,62 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11725
11822
|
await pruneInFlight();
|
|
11726
11823
|
}
|
|
11727
11824
|
}
|
|
11825
|
+
function writeBatchMembers(leaderSpec, leaderKey, error) {
|
|
11826
|
+
if (leaderSpec.batch?.role !== "leader") return;
|
|
11827
|
+
const leaderEntry = lock.entries[leaderKey];
|
|
11828
|
+
for (const memberAssetId of leaderSpec.batch.memberAssetIds ?? []) {
|
|
11829
|
+
const memberKey = lockKey(leaderSpec.styleId, memberAssetId);
|
|
11830
|
+
const memberItem = byKey.get(memberKey);
|
|
11831
|
+
if (!memberItem) {
|
|
11832
|
+
throw new Error(
|
|
11833
|
+
`${leaderKey}: batch member "${memberAssetId}" must be submitted in the same run as its leader; run without --only, or include every sibling`
|
|
11834
|
+
);
|
|
11835
|
+
}
|
|
11836
|
+
const memberSpec = memberItem.spec;
|
|
11837
|
+
const memberEstimate = estimates.get(memberKey);
|
|
11838
|
+
const previousMemberEntry = lock.entries[memberKey];
|
|
11839
|
+
const memberSubmittedAt = leaderEntry.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
11840
|
+
upsert(lock, memberKey, {
|
|
11841
|
+
styleId: memberSpec.styleId,
|
|
11842
|
+
assetId: memberSpec.assetId,
|
|
11843
|
+
specHash: memberSpec.specHash,
|
|
11844
|
+
generator: memberSpec.generator,
|
|
11845
|
+
prompt: memberSpec.prompt,
|
|
11846
|
+
width: memberSpec.width,
|
|
11847
|
+
height: memberSpec.height,
|
|
11848
|
+
batch: { role: "member", leaderAssetId: leaderSpec.assetId, index: memberSpec.batch.index },
|
|
11849
|
+
status: error ? "failed" : "processing",
|
|
11850
|
+
error,
|
|
11851
|
+
jobId: error ? null : leaderEntry.jobId,
|
|
11852
|
+
submissionComplete: error ? void 0 : true,
|
|
11853
|
+
reviewObjectId: error ? null : leaderEntry.reviewObjectId,
|
|
11854
|
+
objectId: null,
|
|
11855
|
+
candidateIndex: null,
|
|
11856
|
+
outputs: [],
|
|
11857
|
+
supersededOutputs: previousMemberEntry?.outputs.length ? previousMemberEntry.outputs : previousMemberEntry?.supersededOutputs ?? [],
|
|
11858
|
+
providerMetadata: leaderEntry.providerMetadata,
|
|
11859
|
+
sourceUrl: null,
|
|
11860
|
+
sourceUrls: [],
|
|
11861
|
+
submittedAt: memberSubmittedAt,
|
|
11862
|
+
history: historyAfterReplacing(previousMemberEntry, historyLimit(loaded.manifest), memberSubmittedAt),
|
|
11863
|
+
cost: memberEstimate.amount,
|
|
11864
|
+
costUnit: memberEstimate.unit,
|
|
11865
|
+
provider: provider.id,
|
|
11866
|
+
downloadedAt: null
|
|
11867
|
+
});
|
|
11868
|
+
if (error) failed++;
|
|
11869
|
+
else {
|
|
11870
|
+
submitted++;
|
|
11871
|
+
spent += memberEstimate.amount;
|
|
11872
|
+
log(` ${memberKey} \u2192 ${leaderEntry.jobId} (rides on ${leaderKey}'s batch)`);
|
|
11873
|
+
}
|
|
11874
|
+
}
|
|
11875
|
+
}
|
|
11728
11876
|
for (const { spec, key } of items) {
|
|
11729
11877
|
const estimate = estimates.get(key);
|
|
11878
|
+
if (spec.batch?.role === "member") {
|
|
11879
|
+
continue;
|
|
11880
|
+
}
|
|
11730
11881
|
if (spec.mirror) {
|
|
11731
11882
|
try {
|
|
11732
11883
|
await requireRevisionReady(spec, lock);
|
|
@@ -11759,6 +11910,14 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11759
11910
|
const since = Date.now() - lastSubmitAt;
|
|
11760
11911
|
if (since < spacing) await sleep(spacing - since);
|
|
11761
11912
|
await requireRevisionReady(spec, lock);
|
|
11913
|
+
if (spec.batch?.role === "leader") {
|
|
11914
|
+
const missing = (spec.batch.memberAssetIds ?? []).filter((id) => !byKey.has(lockKey(spec.styleId, id)));
|
|
11915
|
+
if (missing.length) {
|
|
11916
|
+
throw new Error(
|
|
11917
|
+
`${key}: batch member(s) ${missing.join(", ")} must be submitted in the same run as this leader; run without --only, or include every sibling`
|
|
11918
|
+
);
|
|
11919
|
+
}
|
|
11920
|
+
}
|
|
11762
11921
|
const previousEntry = lock.entries[key];
|
|
11763
11922
|
const resumesCheckpoint = Boolean(
|
|
11764
11923
|
previousEntry?.specHash === spec.specHash && previousEntry.provider === provider.id && previousEntry.generator === spec.generator && previousEntry.submissionComplete === false && previousEntry.jobId
|
|
@@ -11864,6 +12023,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11864
12023
|
inFlight.set(jobId, spec);
|
|
11865
12024
|
submitted++;
|
|
11866
12025
|
spent += estimate.amount;
|
|
12026
|
+
writeBatchMembers(spec, key, null);
|
|
11867
12027
|
log(
|
|
11868
12028
|
` ${key} \u2192 ${jobId} (${spec.width}x${spec.height}` + (estimate.candidates > 1 ? `, ${estimate.candidates} ${spec.tileFeature ? "outputs" : "candidates"}` : "") + `, ${estimate.amount})`
|
|
11869
12029
|
);
|
|
@@ -11875,6 +12035,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11875
12035
|
inFlight.set(entry.jobId, spec);
|
|
11876
12036
|
submitted++;
|
|
11877
12037
|
spent += estimate.amount;
|
|
12038
|
+
writeBatchMembers(spec, key, null);
|
|
11878
12039
|
log(` ${key} \u2192 ${entry.jobId} (recovered from completed checkpoint)`);
|
|
11879
12040
|
} else {
|
|
11880
12041
|
failed++;
|
|
@@ -11883,6 +12044,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11883
12044
|
error: message6,
|
|
11884
12045
|
cost: entry.jobId ? estimate.amount : 0
|
|
11885
12046
|
});
|
|
12047
|
+
writeBatchMembers(spec, key, message6);
|
|
11886
12048
|
log(` FAILED ${key}: ${message6}`);
|
|
11887
12049
|
}
|
|
11888
12050
|
}
|
|
@@ -12474,6 +12636,9 @@ function renderSheet(groups, options = {}) {
|
|
|
12474
12636
|
.cand.active { border-color:var(--dim); box-shadow:0 0 0 2px color-mix(in srgb, var(--dim) 20%, transparent); }
|
|
12475
12637
|
.cand.sel { border-color:var(--ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent); }
|
|
12476
12638
|
.cand.set-frame { cursor:pointer; }
|
|
12639
|
+
.cand.recommended:not(.sel) { border-color:var(--accent); }
|
|
12640
|
+
.cand.recommended .badge { position:absolute; top:-1px; left:-1px; font-size:9.5px; font-weight:650;
|
|
12641
|
+
text-transform:uppercase; letter-spacing:.03em; color:var(--bg); background:var(--accent); padding:1px 5px; }
|
|
12477
12642
|
.cand img { image-rendering:pixelated; display:block;
|
|
12478
12643
|
background-image:
|
|
12479
12644
|
linear-gradient(45deg,#0000 25%,#7f7f7f22 25%,#7f7f7f22 75%,#0000 75%),
|
|
@@ -12631,14 +12796,17 @@ GROUPS.forEach((g, gi) => {
|
|
|
12631
12796
|
}).observe(loop);
|
|
12632
12797
|
}
|
|
12633
12798
|
}
|
|
12799
|
+
const recommended = g.recommendedIndex ?? 0;
|
|
12634
12800
|
g.frameUrls.forEach((url, i) => {
|
|
12635
12801
|
const c = document.createElement('button');
|
|
12636
12802
|
c.type = 'button';
|
|
12637
|
-
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '')
|
|
12803
|
+
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '') +
|
|
12804
|
+
(g.recommendedIndex != null && i === recommended ? ' recommended' : '');
|
|
12638
12805
|
c.tabIndex = -1;
|
|
12639
|
-
c.setAttribute('aria-label', g.mode === 'frame-set'
|
|
12806
|
+
c.setAttribute('aria-label', (g.mode === 'frame-set'
|
|
12640
12807
|
? 'Accept ordered frame set from frame ' + (i + 1)
|
|
12641
|
-
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length)
|
|
12808
|
+
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length) +
|
|
12809
|
+
(g.recommendedIndex != null && i === recommended ? ' (this asset\u2019s declared slot)' : ''));
|
|
12642
12810
|
|
|
12643
12811
|
const preview = document.createElement('img');
|
|
12644
12812
|
preview.className = 'preview';
|
|
@@ -12651,6 +12819,12 @@ GROUPS.forEach((g, gi) => {
|
|
|
12651
12819
|
index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
|
|
12652
12820
|
g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
|
|
12653
12821
|
c.append(preview, index);
|
|
12822
|
+
if (g.recommendedIndex != null && i === recommended) {
|
|
12823
|
+
const badge = document.createElement('span');
|
|
12824
|
+
badge.className = 'badge';
|
|
12825
|
+
badge.textContent = 'declared';
|
|
12826
|
+
c.append(badge);
|
|
12827
|
+
}
|
|
12654
12828
|
if (displayScale > 1 && largestSide <= 96) {
|
|
12655
12829
|
const actual = document.createElement('img');
|
|
12656
12830
|
actual.className = 'actual';
|
|
@@ -12675,6 +12849,7 @@ GROUPS.forEach((g, gi) => {
|
|
|
12675
12849
|
});
|
|
12676
12850
|
frames.dataset.active = '0';
|
|
12677
12851
|
frames.querySelector('.cand')?.classList.add('active');
|
|
12852
|
+
if (recommended > 0) activate(recommended, frames);
|
|
12678
12853
|
root.appendChild(el);
|
|
12679
12854
|
});
|
|
12680
12855
|
|
|
@@ -12930,7 +13105,11 @@ async function prepareReview(provider, lock, opts = {}) {
|
|
|
12930
13105
|
height: spec.revision.sourceHeight
|
|
12931
13106
|
}
|
|
12932
13107
|
} : {},
|
|
12933
|
-
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {}
|
|
13108
|
+
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {},
|
|
13109
|
+
// A batch member's declared slot in the shared candidate set — the
|
|
13110
|
+
// leader's own slot 0 is already the sheet's ordinary default, so
|
|
13111
|
+
// only a member (whose slot is 1+) needs to say so explicitly.
|
|
13112
|
+
...entry.batch?.index != null ? { recommendedIndex: entry.batch.index } : {}
|
|
12934
13113
|
});
|
|
12935
13114
|
} catch (err) {
|
|
12936
13115
|
log(` could not load candidates for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -15977,6 +16156,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
15977
16156
|
};
|
|
15978
16157
|
}
|
|
15979
16158
|
export {
|
|
16159
|
+
AssetBatchSchema,
|
|
15980
16160
|
AssetSchema,
|
|
15981
16161
|
BudgetError,
|
|
15982
16162
|
CHARACTER_DIRECTIONS_4,
|