scenri 0.11.1 → 0.12.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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.0](https://github.com/tonygorb/Scenri/compare/v0.11.1...v0.12.0) (2026-09-20)
4
+
5
+
6
+ ### Features
7
+
8
+ * archive a selection, and make the selection dock a toolbar ([a2697a2](https://github.com/tonygorb/Scenri/commit/a2697a26201314185e609125d04a2bf6391c4e6e))
9
+ * **create:** archive a whole selection from the dock ([d5c2181](https://github.com/tonygorb/Scenri/commit/d5c2181210f0f92b1df528b0113a773a049e916d))
10
+ * **studio:** make the selection dock a toolbar ([362112e](https://github.com/tonygorb/Scenri/commit/362112e9d1bc2be6bf171f0b0702e61ca8845720))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * ask the browser whether the page is still here ([2800f8d](https://github.com/tonygorb/Scenri/commit/2800f8d72feb294b7a54da090242ecb2a66e6929))
16
+ * **brand:** a deleted brand does not reopen its dialogs on the next one ([dbe69eb](https://github.com/tonygorb/Scenri/commit/dbe69eb2e6679dc068c426721a0a0ebbfd39c3ca))
17
+ * **brand:** brand saves, renames, deletes and shot wipes reach the screen ([020c905](https://github.com/tonygorb/Scenri/commit/020c905c6700d8e909cef025d437bc7d4566b663))
18
+ * **create:** a shot restored from the archive comes back to the feed ([131d132](https://github.com/tonygorb/Scenri/commit/131d1329dac03a4d43299636ecf94cb90a26c0e1))
19
+ * **create:** keep a phone's toast clear of the tab bar ([c42f947](https://github.com/tonygorb/Scenri/commit/c42f94791f226879d15f460f2e054ddd79529a9b))
20
+ * **create:** the selection dock is a shelf on a phone, not the pill squeezed ([ffac597](https://github.com/tonygorb/Scenri/commit/ffac5970779d28dfabd52d7655d7469a993417e6))
21
+ * make every saved change reach every surface without a reload ([fb1e517](https://github.com/tonygorb/Scenri/commit/fb1e517170d1327eb4b1ce8af5a6e93d42c9ed7e))
22
+ * **presenters:** a saved person is on every surface the moment they land ([dd6ed16](https://github.com/tonygorb/Scenri/commit/dd6ed16ba1e687c1bf8848bb1f070349663ddb81))
23
+ * **products:** a product edit or delete reaches every surface at once ([2e34a76](https://github.com/tonygorb/Scenri/commit/2e34a765fe038e26d440a2ee586ea7844b6c35b9))
24
+ * **scenes:** a deleted scene leaves every surface at once ([1d132cf](https://github.com/tonygorb/Scenri/commit/1d132cff749437f1ed6ec5ea7635eaa92125763e))
25
+ * **studio:** ask the browser, not the last render, whether the page is still here ([0c4d874](https://github.com/tonygorb/Scenri/commit/0c4d8742a7358dad11fc5d6a2d137f8bb9d11448))
26
+ * **studio:** hand focus on after a delete, and write the rule down ([07efb5c](https://github.com/tonygorb/Scenri/commit/07efb5c653af482e4725587a731dceb1701de130))
27
+ * **studio:** let the selection bar undo Select all ([da21207](https://github.com/tonygorb/Scenri/commit/da21207c9d1215e85b10670b7af8c3b5631462b3))
28
+ * **studio:** order brand answers so a late read never undoes a newer write ([abac146](https://github.com/tonygorb/Scenri/commit/abac146d5fa8ff559bb1105589d2c4418c5df94b))
29
+
3
30
  ## [0.11.1](https://github.com/tonygorb/Scenri/compare/v0.11.0...v0.11.1) (2026-09-18)
4
31
 
5
32
 
@@ -865,7 +865,7 @@ function createStore(db) {
865
865
  }));
866
866
  },
867
867
  updateBrand(id, json) {
868
- db.prepare("UPDATE brands SET json=?, slug=?, updated_at=datetime('now') WHERE id=?").run(
868
+ db.prepare("UPDATE brands SET json=?, slug=?, updated_at=strftime('%Y-%m-%d %H:%M:%f','now') WHERE id=?").run(
869
869
  JSON.stringify(json),
870
870
  uniqueSlug(db, json.meta.name, id),
871
871
  id
@@ -2224,5 +2224,5 @@ function createCore(homeDir = defaultHome()) {
2224
2224
  }
2225
2225
 
2226
2226
  export { ASPECT_TOLERANCE, BUDGET_EXHAUSTED, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, SCHEMA_VERSION, STEM_MIN, SchemaTooNewError, SpendCapError, TRIGRAM_MIN, budgetSize, createCatalogStore, createCore, createStore, defaultHome, fold, ftsMatch, matchesQuery, ratioLabel, searchTerms, termMatches, uniqueProjectSlug, uniqueSetSlug, uniqueSlug };
2227
- //# sourceMappingURL=chunk-K6IELFL3.js.map
2228
- //# sourceMappingURL=chunk-K6IELFL3.js.map
2227
+ //# sourceMappingURL=chunk-MHTA6O3A.js.map
2228
+ //# sourceMappingURL=chunk-MHTA6O3A.js.map
@@ -463,7 +463,7 @@ async function addOrRepair(ownEntry, say, over) {
463
463
  }
464
464
  async function rememberDecline(home) {
465
465
  try {
466
- const { createCore } = await import('./src-TC4UQ7H6.js');
466
+ const { createCore } = await import('./src-INKSTJNC.js');
467
467
  const core = createCore(home);
468
468
  try {
469
469
  core.store.setSetting("desktop.prompt", "declined");
@@ -530,5 +530,5 @@ function tail(path, bytes = 4096) {
530
530
  }
531
531
 
532
532
  export { addToDesktop, assetsDirFor, installDeps, runDesktopCommand, runExecFile, runOpenCommand };
533
- //# sourceMappingURL=cli-GMRPOISO.js.map
534
- //# sourceMappingURL=cli-GMRPOISO.js.map
533
+ //# sourceMappingURL=cli-4A3QWRSC.js.map
534
+ //# sourceMappingURL=cli-4A3QWRSC.js.map
package/dist/index.js CHANGED
@@ -86,12 +86,12 @@ try {
86
86
  break;
87
87
  }
88
88
  case "desktop": {
89
- const { runDesktopCommand } = await import('./cli-GMRPOISO.js');
89
+ const { runDesktopCommand } = await import('./cli-4A3QWRSC.js');
90
90
  process.exit(await runDesktopCommand(command, fileURLToPath(import.meta.url)));
91
91
  break;
92
92
  }
93
93
  case "open": {
94
- const { runOpenCommand } = await import('./cli-GMRPOISO.js');
94
+ const { runOpenCommand } = await import('./cli-4A3QWRSC.js');
95
95
  process.exit(await runOpenCommand(fileURLToPath(import.meta.url)));
96
96
  break;
97
97
  }
package/dist/serve.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createUpdateChecker, classify, findNpm, isReleaseTriplet, stageVersion } from './chunk-YK37H6DM.js';
2
2
  import { shouldAdoptRunning, anotherScenriLines, portBusyLines } from './chunk-CMLJGPYC.js';
3
- import { SchemaTooNewError, createCore, BUDGET_EXHAUSTED, SpendCapError, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, ratioLabel, searchTerms, termMatches, budgetSize, SCHEMA_VERSION, ASPECT_TOLERANCE } from './chunk-K6IELFL3.js';
3
+ import { SchemaTooNewError, createCore, BUDGET_EXHAUSTED, SpendCapError, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, ratioLabel, searchTerms, termMatches, budgetSize, SCHEMA_VERSION, ASPECT_TOLERANCE } from './chunk-MHTA6O3A.js';
4
4
  import { detectInstallKind } from './chunk-PAIAXRAC.js';
5
5
  export { detectInstallKind } from './chunk-PAIAXRAC.js';
6
6
  import { readMeta, repoSlug } from './chunk-Y3ZPBPLP.js';
@@ -8007,6 +8007,7 @@ async function runSceneBuild(deps, job, hashes, instruction, signal) {
8007
8007
  commit(core, job.brandId, (json) => {
8008
8008
  const rows = brandScenes(json);
8009
8009
  const at = rows.findIndex((s) => s?.id === scene.id);
8010
+ if (at < 0 && job.sceneId) throw new Error("This scene was deleted while it was being read again.");
8010
8011
  json.scenes = at >= 0 ? rows.map((s, i) => i === at ? scene : s) : [...rows, scene];
8011
8012
  });
8012
8013
  patch(job, {
@@ -8052,6 +8053,11 @@ function sceneBuildRunning(brandId, sceneId) {
8052
8053
  }
8053
8054
  return false;
8054
8055
  }
8056
+ function cancelSceneBuilds(brandId, sceneId) {
8057
+ for (const b of builds.values()) {
8058
+ if (b.brandId === brandId && b.sceneId === sceneId && !b.finished) cancelAssetBuild(b.id);
8059
+ }
8060
+ }
8055
8061
  var LOOPBACK = ["localhost", "127.0.0.1", "::1"];
8056
8062
  var ACCESS_COOKIE = "sc_access";
8057
8063
  var LEGACY_ACCESS_COOKIE = "bt_access";
@@ -10947,10 +10953,16 @@ function registerAssetBuildRoutes(app, deps) {
10947
10953
  if (!brand) return;
10948
10954
  const id = String(req.params.sceneId);
10949
10955
  if (!brandScenes(brand.json).some((s) => s.id === id)) return reply.status(404).send({ error: "scene not found" });
10950
- commit(core, brand.id, (json) => {
10951
- json.scenes = brandScenes(json).filter((s) => s.id !== id);
10952
- });
10953
- return { ok: true };
10956
+ cancelSceneBuilds(brand.id, id);
10957
+ let row;
10958
+ try {
10959
+ row = commit(core, brand.id, (json) => {
10960
+ json.scenes = brandScenes(json).filter((s) => s.id !== id);
10961
+ });
10962
+ } catch (err) {
10963
+ return reply.status(err.statusCode ?? 500).send({ error: err.message });
10964
+ }
10965
+ return { ok: true, brand: row };
10954
10966
  });
10955
10967
  app.post("/api/brands/:id/scenes/:sceneId/reread", async (req, reply) => {
10956
10968
  const brand = brandOr404(req, reply);
@@ -11704,6 +11716,25 @@ function registerImageRoutes(app, deps) {
11704
11716
 
11705
11717
  // src/release/notes.data.ts
11706
11718
  var RELEASES = [
11719
+ {
11720
+ version: "0.12.0",
11721
+ date: "2026-09-20",
11722
+ title: "Saved work shows up everywhere at once, and a selection has its own toolbar.",
11723
+ sections: [
11724
+ {
11725
+ heading: "Create",
11726
+ body: "Pick several shots and the dock can archive the lot in one go, with a single Undo that brings them all back. That dock is a toolbar now: how many are picked, Select all, and the verbs as tools that name themselves. It is the same bar on a phone, at a size a thumb can hit."
11727
+ },
11728
+ {
11729
+ heading: "Brand",
11730
+ body: "A brand you save, rename or delete, and a workspace you wipe, show up on every screen straight away. A brand you have just deleted no longer reopens its dialogs on the brand you land in next."
11731
+ },
11732
+ {
11733
+ heading: "Fixes",
11734
+ body: "A product edit, a new presenter and a deleted scene each reach every surface that shows them, without a reload, and a slow answer can no longer undo a newer change. Walking to another page while a delete is still out leaves you where you went, instead of pulling you back when it answers. A shot restored from the archive comes back to the feed where it belongs. After deleting a record, focus lands somewhere sensible instead of at the top of the page. On a phone a toast no longer sits on the tab bar."
11735
+ }
11736
+ ]
11737
+ },
11707
11738
  {
11708
11739
  version: "0.11.1",
11709
11740
  date: "2026-09-18",
@@ -12976,7 +13007,7 @@ function registerDesktopRoutes(app, deps) {
12976
13007
  record: null
12977
13008
  };
12978
13009
  }
12979
- const { installDeps } = await import('./cli-GMRPOISO.js');
13010
+ const { installDeps } = await import('./cli-4A3QWRSC.js');
12980
13011
  const { desktopStatus } = await import('./install-VXJPLYWM.js');
12981
13012
  return desktopStatus(installDeps(runtime.entry));
12982
13013
  });
@@ -12984,7 +13015,7 @@ function registerDesktopRoutes(app, deps) {
12984
13015
  if (!runtime.entry) {
12985
13016
  return { ok: false, reason: "unsupported", message: "Desktop shortcuts are not available on this system yet." };
12986
13017
  }
12987
- const { addToDesktop } = await import('./cli-GMRPOISO.js');
13018
+ const { addToDesktop } = await import('./cli-4A3QWRSC.js');
12988
13019
  return addToDesktop(runtime.entry);
12989
13020
  });
12990
13021
  app.get("/api/desktop", async () => {
@@ -13092,7 +13123,12 @@ function buildServer(opts) {
13092
13123
  return { ...row, warnings, report };
13093
13124
  });
13094
13125
  app.put("/api/brands/:id", async (req, reply) => {
13095
- const json = req.body?.brand;
13126
+ const body = req.body ?? {};
13127
+ let json = body.brand;
13128
+ if (body.keepAssets === true && json && typeof json === "object") {
13129
+ const stored = core.store.getBrand(req.params.id);
13130
+ if (stored) json = withStoredAssets(json, stored.json);
13131
+ }
13096
13132
  const v = validateBrand(json);
13097
13133
  if (!v.valid) return reply.status(400).send({ error: "invalid .brand", details: v.errors });
13098
13134
  const row = core.store.updateBrand(req.params.id, json);
@@ -14474,6 +14510,16 @@ function buildServer(opts) {
14474
14510
  }
14475
14511
  return app;
14476
14512
  }
14513
+ var ASSET_COLLECTIONS = ["products", "scenes", "characters"];
14514
+ function withStoredAssets(sent, stored) {
14515
+ const out = { ...sent };
14516
+ const held = stored ?? {};
14517
+ for (const key of ASSET_COLLECTIONS) {
14518
+ if (key in held) out[key] = held[key];
14519
+ else delete out[key];
14520
+ }
14521
+ return out;
14522
+ }
14477
14523
 
14478
14524
  // src/serve.ts
14479
14525
  var PORT = Number(process.env.SCENRI_PORT || 4747);
@@ -14625,7 +14671,7 @@ async function run() {
14625
14671
  }
14626
14672
  }
14627
14673
  const ownEntry = fileURLToPath(import.meta.url);
14628
- const { addToDesktop, installDeps } = await import('./cli-GMRPOISO.js');
14674
+ const { addToDesktop, installDeps } = await import('./cli-4A3QWRSC.js');
14629
14675
  const { refreshLauncher } = await import('./refresh-5HBDLLLB.js');
14630
14676
  const { askOnTerminal, offerDesktop, shouldOfferDesktop } = await import('./offer-W6NJXMWT.js');
14631
14677
  const { launcherInstalled } = await import('./paths-6ZXRZUHY.js');
@@ -1,3 +1,3 @@
1
- export { ASPECT_TOLERANCE, BUDGET_EXHAUSTED, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, SCHEMA_VERSION, STEM_MIN, SchemaTooNewError, SpendCapError, TRIGRAM_MIN, budgetSize, createCatalogStore, createCore, createStore, defaultHome, fold, ftsMatch, matchesQuery, ratioLabel, searchTerms, termMatches, uniqueProjectSlug, uniqueSetSlug, uniqueSlug } from './chunk-K6IELFL3.js';
2
- //# sourceMappingURL=src-TC4UQ7H6.js.map
3
- //# sourceMappingURL=src-TC4UQ7H6.js.map
1
+ export { ASPECT_TOLERANCE, BUDGET_EXHAUSTED, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, SCHEMA_VERSION, STEM_MIN, SchemaTooNewError, SpendCapError, TRIGRAM_MIN, budgetSize, createCatalogStore, createCore, createStore, defaultHome, fold, ftsMatch, matchesQuery, ratioLabel, searchTerms, termMatches, uniqueProjectSlug, uniqueSetSlug, uniqueSlug } from './chunk-MHTA6O3A.js';
2
+ //# sourceMappingURL=src-INKSTJNC.js.map
3
+ //# sourceMappingURL=src-INKSTJNC.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenri",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "author": "Tony Gorb <hello@scenri.co>",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",