scenri 0.12.0 → 0.13.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/CHANGELOG.md +22 -0
- package/dist/serve.js +113 -16
- package/package.json +1 -1
- package/studio-dist/assets/first-shot-square-hTczF9bl.webp +0 -0
- package/studio-dist/assets/first-shot-wide-BSpJwjNx.webp +0 -0
- package/studio-dist/assets/{index-BIFWvZVh.css → index-BjmJCeT_.css} +1 -1
- package/studio-dist/assets/index-D8pLsQ5q.js +107 -0
- package/studio-dist/assets/presenter-square-BpqHv8Aq.webp +0 -0
- package/studio-dist/assets/presenter-wide-ecc4y7eJ.webp +0 -0
- package/studio-dist/assets/product-square-DPR77tr0.webp +0 -0
- package/studio-dist/assets/product-wide-DLSsO8IK.webp +0 -0
- package/studio-dist/assets/refine-square-CietHTPe.webp +0 -0
- package/studio-dist/assets/refine-wide-BM5sVq6w.webp +0 -0
- package/studio-dist/assets/reuse-square-CMLhjEwt.webp +0 -0
- package/studio-dist/assets/reuse-wide-DbbTeKsL.webp +0 -0
- package/studio-dist/assets/scene-square-CQBwEfXy.webp +0 -0
- package/studio-dist/assets/scene-wide-CJ9ENIrh.webp +0 -0
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-D-7D0wTu.js +0 -107
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0](https://github.com/tonygorb/Scenri/compare/v0.12.0...v0.13.0) (2026-09-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **studio:** rebuild transient alerts as a quiet toast stack ([7b44141](https://github.com/tonygorb/Scenri/commit/7b44141b57cc6748942d97bc6acc27a022260207))
|
|
9
|
+
* **studio:** turn first-use on, with a tutor that can go back ([df6b799](https://github.com/tonygorb/Scenri/commit/df6b799708d65cf31130233dabe2fd0ee1095a28))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **cli:** an install that has made a shot is not offered the first-use welcome ([4dae292](https://github.com/tonygorb/Scenri/commit/4dae292f3e833180448b2310a81573b641408bcc))
|
|
15
|
+
* **create:** keep the feed still when a shot archives or the rail toggles ([b3276cd](https://github.com/tonygorb/Scenri/commit/b3276cd1f68315455e8b29d7dfdf13c768a0438a))
|
|
16
|
+
* **studio:** a lesson on its first step has not begun, and Learn cannot take the app down ([cf0540b](https://github.com/tonygorb/Scenri/commit/cf0540b1ecd1e8fbdde2e3d203538f9e13f46d8c))
|
|
17
|
+
* **studio:** a lesson owns its progress, and only the screen is shared ([8c58bdb](https://github.com/tonygorb/Scenri/commit/8c58bdbc06124d2f9fda0775a41f84fa156e10ea))
|
|
18
|
+
* **studio:** a library lesson keeps its button in sight on narrow screens ([93869f4](https://github.com/tonygorb/Scenri/commit/93869f4f1f1fa8791c15ff893ef8ac536deda721))
|
|
19
|
+
* **studio:** a step scrolled out of sight is not gone ([49ceccb](https://github.com/tonygorb/Scenri/commit/49ceccb8d7dc41daf8327ecefb8353f98ed94e70))
|
|
20
|
+
* **studio:** alerts stand above a docked composer instead of over its buttons ([b6756c7](https://github.com/tonygorb/Scenri/commit/b6756c79c63419ca2ed146fc2e3c6d868eeb6f79))
|
|
21
|
+
* **studio:** Learn gives the keyboard back to whichever control opened it ([e9c5945](https://github.com/tonygorb/Scenri/commit/e9c5945d60e6ceb55704da1130fe23a988c6044f))
|
|
22
|
+
* **studio:** no empty alert on every page, and a held toast stays held ([3305f75](https://github.com/tonygorb/Scenri/commit/3305f7542fb38f604c9b4087c59aa7cfe0d4cf6b))
|
|
23
|
+
* **studio:** refining teaches how to find a shot, instead of picking one for you ([228938f](https://github.com/tonygorb/Scenri/commit/228938f2440e17b48d9bab836e7c4aeeb0a74cef))
|
|
24
|
+
|
|
3
25
|
## [0.12.0](https://github.com/tonygorb/Scenri/compare/v0.11.1...v0.12.0) (2026-09-20)
|
|
4
26
|
|
|
5
27
|
|
package/dist/serve.js
CHANGED
|
@@ -11716,6 +11716,25 @@ function registerImageRoutes(app, deps) {
|
|
|
11716
11716
|
|
|
11717
11717
|
// src/release/notes.data.ts
|
|
11718
11718
|
var RELEASES = [
|
|
11719
|
+
{
|
|
11720
|
+
version: "0.13.0",
|
|
11721
|
+
date: "2026-09-21",
|
|
11722
|
+
title: "Learn teaches Scenri one lesson at a time, alerts are quieter, and the Create feed holds still.",
|
|
11723
|
+
sections: [
|
|
11724
|
+
{
|
|
11725
|
+
heading: "Learn",
|
|
11726
|
+
body: "Learn sits in the top bar, or under Help on a smaller screen, and teaches Scenri in six short lessons: your first shot, adding a product, a presenter, a scene, using a product again, and refining a shot. Each lesson keeps its own place, so you can set one down, take up another and come back to it. A new install is offered a guided first shot, and the guide can step back when you change your mind."
|
|
11727
|
+
},
|
|
11728
|
+
{
|
|
11729
|
+
heading: "Alerts",
|
|
11730
|
+
body: "Alerts are smaller and stack at the bottom of the screen, above the composer rather than over it. The same event twice shares one card, an error stays until you close it, and an alert holds still while you point at it or tab into it."
|
|
11731
|
+
},
|
|
11732
|
+
{
|
|
11733
|
+
heading: "Create",
|
|
11734
|
+
body: "Archiving a shot no longer makes the pictures around it blink, and opening or closing the side panel rearranges the feed in one step."
|
|
11735
|
+
}
|
|
11736
|
+
]
|
|
11737
|
+
},
|
|
11719
11738
|
{
|
|
11720
11739
|
version: "0.12.0",
|
|
11721
11740
|
date: "2026-09-20",
|
|
@@ -12835,14 +12854,24 @@ function registerSystemRoutes(app, deps) {
|
|
|
12835
12854
|
}
|
|
12836
12855
|
|
|
12837
12856
|
// src/routes/guide.ts
|
|
12838
|
-
var TASKS = ["first-shot", "refine", "product", "presenter", "scene"];
|
|
12857
|
+
var TASKS = ["first-shot", "refine", "product", "presenter", "scene", "reuse"];
|
|
12839
12858
|
var MILESTONES = ["shot", "refine", "product", "presenter", "scene"];
|
|
12840
12859
|
var KEY = "guide";
|
|
12841
12860
|
var isTask = (t) => TASKS.includes(t);
|
|
12842
12861
|
var isMilestone = (m) => MILESTONES.includes(m);
|
|
12843
12862
|
var num2 = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : 0;
|
|
12844
12863
|
function blank(eligible) {
|
|
12845
|
-
return {
|
|
12864
|
+
return {
|
|
12865
|
+
v: 2,
|
|
12866
|
+
eligible,
|
|
12867
|
+
welcome: null,
|
|
12868
|
+
hidden: null,
|
|
12869
|
+
done: {},
|
|
12870
|
+
lessons: {},
|
|
12871
|
+
dismissed: [],
|
|
12872
|
+
active: null,
|
|
12873
|
+
progress: {}
|
|
12874
|
+
};
|
|
12846
12875
|
}
|
|
12847
12876
|
function parse2(raw) {
|
|
12848
12877
|
if (!raw) return null;
|
|
@@ -12854,9 +12883,47 @@ function parse2(raw) {
|
|
|
12854
12883
|
if (j.done && typeof j.done === "object") {
|
|
12855
12884
|
for (const [k, at] of Object.entries(j.done)) if (isMilestone(k) && typeof at === "string") done[k] = at;
|
|
12856
12885
|
}
|
|
12886
|
+
const lessons = {};
|
|
12887
|
+
if (j.lessons && typeof j.lessons === "object") {
|
|
12888
|
+
for (const [k, at] of Object.entries(j.lessons)) if (isTask(k) && typeof at === "string") lessons[k] = at;
|
|
12889
|
+
}
|
|
12890
|
+
const asTask = (a2) => {
|
|
12891
|
+
const b2 = a2?.baseline;
|
|
12892
|
+
return a2 && isTask(a2.task) && typeof a2.brandId === "string" && typeof a2.since === "string" ? {
|
|
12893
|
+
task: a2.task,
|
|
12894
|
+
brandId: a2.brandId,
|
|
12895
|
+
since: a2.since,
|
|
12896
|
+
baseline: { products: num2(b2?.products), presenters: num2(b2?.presenters), scenes: num2(b2?.scenes) },
|
|
12897
|
+
...a2.paused === true ? { paused: true } : {}
|
|
12898
|
+
} : null;
|
|
12899
|
+
};
|
|
12900
|
+
const asProgress = (v) => {
|
|
12901
|
+
const p = v;
|
|
12902
|
+
const b2 = p?.baseline;
|
|
12903
|
+
return p && typeof p.brandId === "string" && typeof p.since === "string" ? {
|
|
12904
|
+
brandId: p.brandId,
|
|
12905
|
+
since: p.since,
|
|
12906
|
+
baseline: { products: num2(b2?.products), presenters: num2(b2?.presenters), scenes: num2(b2?.scenes) },
|
|
12907
|
+
reached: Array.isArray(p.reached) ? p.reached.filter((m) => typeof m === "string") : [],
|
|
12908
|
+
...p.paused === true ? { paused: true } : {}
|
|
12909
|
+
} : null;
|
|
12910
|
+
};
|
|
12911
|
+
const progress = {};
|
|
12912
|
+
if (j.progress && typeof j.progress === "object") {
|
|
12913
|
+
for (const [k, v] of Object.entries(j.progress)) {
|
|
12914
|
+
const p = asProgress(v);
|
|
12915
|
+
if (isTask(k) && p) progress[k] = p;
|
|
12916
|
+
}
|
|
12917
|
+
}
|
|
12918
|
+
if (j.parked && typeof j.parked === "object") {
|
|
12919
|
+
for (const [k, v] of Object.entries(j.parked)) {
|
|
12920
|
+
const t = asTask(v);
|
|
12921
|
+
if (isTask(k) && t && !progress[k]) progress[k] = { ...t, reached: [], paused: true };
|
|
12922
|
+
}
|
|
12923
|
+
}
|
|
12857
12924
|
const a = j.active;
|
|
12858
12925
|
const b = a?.baseline;
|
|
12859
|
-
const
|
|
12926
|
+
const held = a && isTask(a.task) && typeof a.brandId === "string" && typeof a.since === "string" ? {
|
|
12860
12927
|
task: a.task,
|
|
12861
12928
|
brandId: a.brandId,
|
|
12862
12929
|
since: a.since,
|
|
@@ -12869,8 +12936,11 @@ function parse2(raw) {
|
|
|
12869
12936
|
welcome: j.welcome === "taken" || j.welcome === "declined" ? j.welcome : null,
|
|
12870
12937
|
hidden: typeof j.hidden === "boolean" ? j.hidden : null,
|
|
12871
12938
|
done,
|
|
12939
|
+
lessons,
|
|
12872
12940
|
dismissed: Array.isArray(j.dismissed) ? j.dismissed.filter(isTask) : [],
|
|
12873
|
-
|
|
12941
|
+
// an old record's one task in hand is that lesson's progress now
|
|
12942
|
+
active: typeof j.active === "string" && isTask(j.active) ? j.active : held?.task ?? null,
|
|
12943
|
+
progress: held && !progress[held.task] ? { ...progress, [held.task]: { ...held, reached: [] } } : progress
|
|
12874
12944
|
};
|
|
12875
12945
|
} catch {
|
|
12876
12946
|
return null;
|
|
@@ -12929,21 +12999,33 @@ function readGuide(core, env) {
|
|
|
12929
12999
|
changed = true;
|
|
12930
13000
|
}
|
|
12931
13001
|
}
|
|
12932
|
-
|
|
12933
|
-
|
|
13002
|
+
for (const [t, p] of Object.entries(r.progress)) {
|
|
13003
|
+
if (core.store.getBrand(p.brandId)) continue;
|
|
13004
|
+
delete r.progress[t];
|
|
13005
|
+
if (r.active === t) r.active = null;
|
|
12934
13006
|
changed = true;
|
|
12935
13007
|
}
|
|
12936
13008
|
if (changed) write(core.store, r);
|
|
12937
|
-
const
|
|
12938
|
-
const
|
|
12939
|
-
|
|
13009
|
+
const on = r.active ? r.progress[r.active] : null;
|
|
13010
|
+
const a = r.active && on ? {
|
|
13011
|
+
task: r.active,
|
|
13012
|
+
brandId: on.brandId,
|
|
13013
|
+
since: on.since,
|
|
13014
|
+
baseline: on.baseline,
|
|
13015
|
+
...on.paused ? { paused: true } : {}
|
|
13016
|
+
} : null;
|
|
13017
|
+
const nodeKind = a?.task === "first-shot" || a?.task === "reuse" ? "generation" : a?.task === "refine" ? "edit" : null;
|
|
13018
|
+
const settled = r.welcome === null && !!r.done.shot;
|
|
13019
|
+
const eligible = r.eligible && !settled && env.SCENRI_NO_GUIDE !== "1";
|
|
12940
13020
|
return {
|
|
12941
13021
|
eligible,
|
|
12942
13022
|
welcome: r.welcome,
|
|
12943
13023
|
hidden: r.hidden ?? !eligible,
|
|
12944
13024
|
done: r.done,
|
|
13025
|
+
lessons: r.lessons,
|
|
12945
13026
|
dismissed: r.dismissed,
|
|
12946
13027
|
active: a,
|
|
13028
|
+
progress: r.progress,
|
|
12947
13029
|
activeNodes: a && nodeKind ? core.store.nodesSince(a.brandId, nodeKind, a.since) : [],
|
|
12948
13030
|
activeDraftId: a?.task === "presenter" ? core.store.presenterDraftSince(a.brandId, a.since) : null,
|
|
12949
13031
|
counts: a ? countsOf(core, a.brandId) : null
|
|
@@ -12961,19 +13043,34 @@ function applyIntent(core, body) {
|
|
|
12961
13043
|
if (typeof s.brandId !== "string") return "brandId required";
|
|
12962
13044
|
const baseline = countsOf(core, s.brandId);
|
|
12963
13045
|
if (!baseline) return "brand not found";
|
|
12964
|
-
const
|
|
12965
|
-
if (
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
13046
|
+
const guiding = r.active ? r.progress[r.active] : null;
|
|
13047
|
+
if (r.active && r.active !== s.task && guiding) r.progress[r.active] = { ...guiding, paused: true };
|
|
13048
|
+
const mine = r.progress[s.task];
|
|
13049
|
+
if (mine && mine.brandId === s.brandId) {
|
|
13050
|
+
const { paused: _, ...going } = mine;
|
|
13051
|
+
r.progress[s.task] = going;
|
|
13052
|
+
} else r.progress[s.task] = { brandId: s.brandId, since: core.store.now(), baseline, reached: [] };
|
|
13053
|
+
r.active = s.task;
|
|
12969
13054
|
r.dismissed = r.dismissed.filter((t) => t !== s.task);
|
|
12970
13055
|
} else if ("finish" in b) {
|
|
12971
13056
|
if (!isTask(b.finish)) return "unknown task";
|
|
12972
|
-
if (r.
|
|
13057
|
+
if (!r.lessons[b.finish]) r.lessons[b.finish] = (/* @__PURE__ */ new Date()).toISOString();
|
|
13058
|
+
if (r.active === b.finish) r.active = null;
|
|
13059
|
+
delete r.progress[b.finish];
|
|
12973
13060
|
} else if ("dismiss" in b) {
|
|
12974
13061
|
if (!isTask(b.dismiss)) return "unknown task";
|
|
12975
13062
|
if (!r.dismissed.includes(b.dismiss)) r.dismissed.push(b.dismiss);
|
|
12976
|
-
|
|
13063
|
+
const shut = r.progress[b.dismiss];
|
|
13064
|
+
if (shut) r.progress[b.dismiss] = { ...shut, paused: true };
|
|
13065
|
+
if (r.active === b.dismiss) r.active = null;
|
|
13066
|
+
} else if ("reached" in b) {
|
|
13067
|
+
const w = b.reached ?? {};
|
|
13068
|
+
if (!isTask(w.task)) return "unknown task";
|
|
13069
|
+
if (typeof w.moment !== "string" || !w.moment) return "moment required";
|
|
13070
|
+
const p = r.progress[w.task];
|
|
13071
|
+
if (!p) return null;
|
|
13072
|
+
if (p.reached.includes(w.moment)) return null;
|
|
13073
|
+
r.progress[w.task] = { ...p, reached: [...p.reached, w.moment].slice(-24) };
|
|
12977
13074
|
} else if ("hidden" in b) {
|
|
12978
13075
|
if (typeof b.hidden !== "boolean") return "hidden is true or false";
|
|
12979
13076
|
r.hidden = b.hidden;
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|