scenri 0.6.2 → 0.6.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.3](https://github.com/tonygorb/Scenri/compare/v0.6.2...v0.6.3) (2026-08-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * brand mark carriage is consistent and honest end to end ([3ebd733](https://github.com/tonygorb/Scenri/commit/3ebd7337615a390215c3da297ecfb6f21074acdf))
9
+ * brand mark carriage is consistent and honest end to end ([bf1927e](https://github.com/tonygorb/Scenri/commit/bf1927e0ae95a86cf1666c4efb70306b7c4e2d6b))
10
+ * route-navigation surfaces are real links ([1c8c331](https://github.com/tonygorb/Scenri/commit/1c8c3311c7e7caa88bdbb2190f1bd37445d44444))
11
+ * route-navigation surfaces are real links ([816cf8c](https://github.com/tonygorb/Scenri/commit/816cf8c1485879c50fb4b7e9a85cf67aced152b1))
12
+
3
13
  ## [0.6.2](https://github.com/tonygorb/Scenri/compare/v0.6.1...v0.6.2) (2026-08-28)
4
14
 
5
15
 
package/dist/serve.js CHANGED
@@ -3104,7 +3104,12 @@ function sceneFigureDirectives(opts) {
3104
3104
  if (treatment) {
3105
3105
  const who = opts.hasPerson ? "The face and body underneath are still exactly theirs - same structure, same proportions, same build - and any earlier instruction that their features must survive unchanged is a rule about who they are, which this does not alter. " : "";
3106
3106
  out.push(
3107
- `The art direction of this world is what has been done to that figure: ${treatment}. Render it as a real physical treatment, following the shape of the face and body it sits on rather than floating in the frame. Spread it across the whole form the way the reference does, reaching every part it covers there - brow, forehead, nose, both cheeks, jaw - instead of massing it in one area and leaving the rest untouched. Reaching wide is not the same as covering more: keep the number of pieces and the bare surface between them exactly as the description says, so a sparse treatment stays sparse while still touching every part of the form. Each piece sits on the plane beneath it, curving and catching light with the surface it is stuck to. ${who}The figure is bodily present and in shot: where the treatment covers or hides them, that is the photograph working as intended and never a reason to leave them out, crop them away, or reduce them to a shadow. If no person appears in this shot, the treatment does not go with them: it is what this world looks like, so it applies to whatever the frame does hold - the product, the surfaces, the set - as real pieces resting on those things. Applied on top, never redesigning them: the product keeps the exact form, colour, material and its own printed label that its reference shows, with the treatment sitting over it. Where the treatment carries printing, render it as genuinely designed print: real letterforms, readable words, numerals, illustration and colour, at the quality of commercial label artwork. Invent the companies - every name, logotype and piece of packaging artwork must be plausible but fictional, resembling no existing brand. That includes near-misses: do not borrow, extend or re-spell a name that appears in any attached reference, and use ordinary words for the produce itself rather than any company that sells it.`
3107
+ `The art direction of this world is what has been done to that figure: ${treatment}. Render it as a real physical treatment, following the shape of the face and body it sits on rather than floating in the frame. Spread it across the whole form the way the reference does, reaching every part it covers there - brow, forehead, nose, both cheeks, jaw - instead of massing it in one area and leaving the rest untouched. Reaching wide is not the same as covering more: keep the number of pieces and the bare surface between them exactly as the description says, so a sparse treatment stays sparse while still touching every part of the form. Each piece sits on the plane beneath it, curving and catching light with the surface it is stuck to. ${who}The figure is bodily present and in shot: where the treatment covers or hides them, that is the photograph working as intended and never a reason to leave them out, crop them away, or reduce them to a shadow. If no person appears in this shot, the treatment does not go with them: it is what this world looks like, so it applies to whatever the frame does hold - the product, the surfaces, the set - as real pieces resting on those things. Applied on top, never redesigning them: the product keeps the exact form, colour, material and its own printed label that its reference shows, with the treatment sitting over it. Where the treatment carries printing, render it as genuinely designed print: real letterforms, readable words, numerals, illustration and colour, at the quality of commercial label artwork. Invent the companies - every name, logotype and piece of packaging artwork must be plausible but fictional, resembling no existing brand. That includes near-misses: do not borrow, extend or re-spell a name that appears in any attached reference, and use ordinary words for the produce itself rather than any company that sells it.` + // The fictional-brands rule and an attached brand mark are in direct
3108
+ // conflict without this: "resembling no existing brand" reads as an
3109
+ // instruction to mutate the one real mark the user deliberately
3110
+ // attached. Same shape as pairDirectives' packshot override - name the
3111
+ // scope of the earlier rule, carry the exception in the same breath.
3112
+ (opts.hasMark ? " The one exception is the attached brand mark: it is this brand's own real mark, deliberately attached, so the fictional-brands rule above does not apply to it. Where the direction asks for that mark to appear, it appears exactly as drawn - never redrawn, re-lettered or fictionalised." : "")
3108
3113
  );
3109
3114
  }
3110
3115
  return out;
@@ -3394,7 +3399,14 @@ function compileBrief(brief, ctx) {
3394
3399
  // attached one is deliberate.
3395
3400
  "Any earlier instruction that bans props, hands, people, or a presenter from the frame is a solo-packshot rule for this product and does not apply to this shot: the attached presenter is deliberate and must appear as directed."
3396
3401
  ] : [];
3397
- const figureDirectives = scene?.figure ? sceneFigureDirectives({ figure: scene.figure, treatment: scene.figureTreatment, hasPerson }) : [];
3402
+ const figureDirectives = scene?.figure ? sceneFigureDirectives({
3403
+ figure: scene.figure,
3404
+ treatment: scene.figureTreatment,
3405
+ hasPerson,
3406
+ // The treatment's fictional-brands rule needs to know a real mark is
3407
+ // deliberately in play; attachments are fully collected by this point.
3408
+ hasMark: attachments.some((a) => a.role === "brand")
3409
+ }) : [];
3398
3410
  const closeUpDirectives = hasPerson && /\bclose[- ]?up\b|\bmacro\b|\bzoom(?:ed)?\b|\bDOF\b|\bdepth of field\b/i.test(sentence) ? [
3399
3411
  "The tight framing includes the presenter: keep at least their hand in genuine contact with the product, and as much more of them as the crop allows. A close-up is never a reason to leave the person out of the photograph."
3400
3412
  ] : [];
@@ -3426,10 +3438,9 @@ function compileBrief(brief, ctx) {
3426
3438
  const { kept, dropped } = allocateAttachments(attachments, max);
3427
3439
  if (dropped.some((d) => d.role !== "scene")) {
3428
3440
  const names = [...new Set(dropped.filter((d) => d.role !== "scene").map((d) => d.label))];
3441
+ const reads = max === 0 ? "reads no reference images" : `reads ${max} reference image${max === 1 ? "" : "s"}`;
3429
3442
  warnings.push(
3430
- `${ctx.engineCaps.displayName} reads ${max} reference image${max === 1 ? "" : "s"}, so ${names.join(
3431
- " and "
3432
- )} ${names.length === 1 ? "was" : "were"} left out.`
3443
+ `${ctx.engineCaps.displayName} ${reads}, so ${names.join(" and ")} ${names.length === 1 ? "was" : "were"} left out.`
3433
3444
  );
3434
3445
  }
3435
3446
  return {
@@ -4115,7 +4126,12 @@ async function buildFromUrl(url, opts = {}) {
4115
4126
  const palette = pickPalette(colorSources);
4116
4127
  if (!palette.primary) warnings.push("No confident palette found. Set colors manually.");
4117
4128
  let logoRef;
4118
- const iconHref = $('link[rel="apple-touch-icon"]').attr("href") || $('link[rel~="icon"]').attr("href") || $('meta[property="og:image"]').attr("content");
4129
+ let logoFromOg = false;
4130
+ let iconHref = $('link[rel="apple-touch-icon"]').attr("href") || $('link[rel~="icon"]').attr("href");
4131
+ if (!iconHref) {
4132
+ iconHref = $('meta[property="og:image"]').attr("content");
4133
+ logoFromOg = Boolean(iconHref);
4134
+ }
4119
4135
  if (iconHref && opts.saveAsset) {
4120
4136
  try {
4121
4137
  const iconRes = await fetchImpl(new URL(iconHref, origin).toString(), { redirect: "follow" });
@@ -4128,6 +4144,10 @@ async function buildFromUrl(url, opts = {}) {
4128
4144
  }
4129
4145
  }
4130
4146
  if (!logoRef) warnings.push("No logo captured. Add one manually.");
4147
+ else if (logoFromOg)
4148
+ warnings.push(
4149
+ "No site icon was found; the social share image was saved as an alternate mark. Check it before treating it as the logo."
4150
+ );
4131
4151
  const brand = {
4132
4152
  specVersion: "0.1",
4133
4153
  meta: {
@@ -4146,7 +4166,7 @@ async function buildFromUrl(url, opts = {}) {
4146
4166
  ...palette.neutrals.length ? { neutrals: palette.neutrals.map((hex) => ({ hex })) } : {}
4147
4167
  }
4148
4168
  } : {},
4149
- ...logoRef ? { logos: [{ role: "primary", file: logoRef }] } : {}
4169
+ ...logoRef ? { logos: [{ role: logoFromOg ? "alternate" : "primary", file: logoRef }] } : {}
4150
4170
  };
4151
4171
  return { brand, warnings };
4152
4172
  }
@@ -4180,7 +4200,16 @@ function mergeScrape(existing, scraped) {
4180
4200
  if (scrapedLogos.length) {
4181
4201
  const have = new Set(arr(cur.logos).map((l) => hashOf(l?.file)));
4182
4202
  const added = scrapedLogos.filter((l) => !have.has(hashOf(l?.file)));
4183
- if (added.length) brand.logos = [...arr(cur.logos), ...added];
4203
+ if (added.length) {
4204
+ let hasPrimary = arr(cur.logos).some((l) => l?.role === "primary");
4205
+ const demoted = added.map((l) => {
4206
+ if (l?.role !== "primary") return l;
4207
+ if (hasPrimary) return { ...l, role: "alternate" };
4208
+ hasPrimary = true;
4209
+ return l;
4210
+ });
4211
+ brand.logos = [...arr(cur.logos), ...demoted];
4212
+ }
4184
4213
  }
4185
4214
  return { brand, suggestions: { palette: suggestions } };
4186
4215
  }
@@ -6983,7 +7012,7 @@ function registerLogoRoutes(app, deps) {
6983
7012
  const entry = { role, file: `asset:${part.hash}`, background };
6984
7013
  if (existing !== -1) logos[existing] = entry;
6985
7014
  else logos.push(entry);
6986
- json.logos = logos;
7015
+ json.logos = role === "primary" ? logos.map((l) => l !== entry && l?.role === "primary" ? { ...l, role: "alternate" } : l) : logos;
6987
7016
  const v = validateBrand(json);
6988
7017
  if (!v.valid) return reply.status(400).send({ error: "brand became invalid", details: v.errors });
6989
7018
  return core.store.updateBrand(brand.id, json);
@@ -7011,7 +7040,7 @@ function registerLogoRoutes(app, deps) {
7011
7040
  if (cs) patch2.clearSpace = cs;
7012
7041
  }
7013
7042
  json.logos = readLogos(json).map((l, i) => {
7014
- if (i !== idx) return l;
7043
+ if (i !== idx) return patch2.role === "primary" && l?.role === "primary" ? { ...l, role: "alternate" } : l;
7015
7044
  const next = { ...l, ...patch2 };
7016
7045
  if ("clearSpace" in body && !patch2.clearSpace) delete next.clearSpace;
7017
7046
  return next;
@@ -7944,6 +7973,21 @@ function registerImageRoutes(app, deps) {
7944
7973
 
7945
7974
  // src/release/notes.data.ts
7946
7975
  var RELEASES = [
7976
+ {
7977
+ version: "0.6.3",
7978
+ date: "2026-08-28",
7979
+ title: "Your logo stays your logo, and every page opens like a page.",
7980
+ sections: [
7981
+ {
7982
+ heading: "Brand",
7983
+ body: "A brand mark attached to a shot is carried faithfully everywhere the shot goes: a refinement keeps it, Reuse setup rebuilds the brief with it, and when the logo has since left the kit, Scenri says so instead of quietly dropping it. The kit holds one primary logo at a time, every screen agrees which one it is, and the composer says before sending when an engine cannot carry the mark as an image."
7984
+ },
7985
+ {
7986
+ heading: "Fixes",
7987
+ body: "Anything that opens a page is a real link now. Middle click, Cmd or Ctrl click and copy link work on the navigation, cards, shots, crumbs and notifications, each opening in its own tab, while a plain click stays as fast as before."
7988
+ }
7989
+ ]
7990
+ },
7947
7991
  {
7948
7992
  version: "0.6.2",
7949
7993
  date: "2026-08-28",
@@ -8821,16 +8865,18 @@ function buildServer(opts) {
8821
8865
  ...opts2?.reshape === "extend" ? { editReshape: "extend" } : {}
8822
8866
  });
8823
8867
  let inheritedAttachments = [];
8868
+ let identityWarnings = [];
8824
8869
  if (inheritedTokens.length) {
8825
8870
  const identity = compileBrief(
8826
8871
  { tokens: inheritedTokens },
8827
8872
  { brand: brandJson, images: core.images, engineCaps: uncapped, templateById: sceneById }
8828
8873
  );
8874
+ identityWarnings = identity.warnings;
8829
8875
  inheritedAttachments = identity.attachments.filter((a) => a.essential || a.role === "brand" || a.role === "reference").map((a) => ({ ...a, inherited: true }));
8830
8876
  }
8831
8877
  const cap2 = Math.max(0, engineCaps.maxReferenceImages - 1);
8832
8878
  const merged = mergeEditAttachments(compiled2.attachments, inheritedAttachments, cap2);
8833
- const warnings = [...compiled2.warnings];
8879
+ const warnings = [...compiled2.warnings, ...identityWarnings.filter((w) => !compiled2.warnings.includes(w))];
8834
8880
  if (merged.dropped.length) {
8835
8881
  if (engineCaps.maxReferenceImages <= 1) {
8836
8882
  warnings.push(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenri",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "author": "Tony Gorb <hello@scenri.co>",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",