@wenathlan/saddle 2.0.152 → 2.0.154

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/Dockerfile CHANGED
@@ -323,9 +323,11 @@ RUN set -eux; \
323
323
  # (nodesource node 26.7.0 ships npm 11.19.0) and surgically refresh
324
324
  # the four nested dependencies whose bundled versions carry the CVEs
325
325
  # the trivy release gate rejects (brace-expansion CVE-2026-14257 /
326
- # CVE-2026-69152, ip-address CVE-2026-69192 / CVE-2026-54272 /
327
- # CVE-2026-69198, tar CVE-2026-73566, undici CVE-2026-16728 /
328
- # CVE-2026-16729 / CVE-2026-15157). published npm tarballs freeze
326
+ # CVE-2026-69152 / CVE-2026-102276 / CVE-2026-102278, ip-address
327
+ # CVE-2026-69192 / CVE-2026-54272 / CVE-2026-69198, tar
328
+ # CVE-2026-73566, undici CVE-2026-16728 / CVE-2026-16729 /
329
+ # CVE-2026-15157 / CVE-2026-109383 / CVE-2026-109869 /
330
+ # CVE-2026-19534 / CVE-2026-87018). published npm tarballs freeze
329
331
  # their bundled node_modules at publish time, so the swap alone
330
332
  # still ships brace-expansion 5.0.7; each fixed release is packed
331
333
  # separately and extracted over the nested path, then asserted: the
@@ -358,10 +360,10 @@ RUN set -eux; \
358
360
  fi; \
359
361
  echo "npm tree $fname $fgot"; \
360
362
  }; \
361
- fixdep brace-expansion 5.0.9; \
363
+ fixdep brace-expansion 5.0.11; \
362
364
  fixdep ip-address 10.3.1; \
363
365
  fixdep tar 7.5.21; \
364
- fixdep undici 6.28.0; \
366
+ fixdep undici 6.29.1; \
365
367
  rm -rf /root/.npm 2>/dev/null || true
366
368
 
367
369
  # mesa 26.2.1 from source (the "build" arm of the PPA-or-build policy):
@@ -454,7 +456,7 @@ RUN set -eux; \
454
456
  # without one). virtualizationcore.cpp additionally builds its selftest
455
457
  # binary and runs it: the stage fails if a single assert trips.
456
458
  WORKDIR /src
457
- COPY virtualhardware.c gpumonitor.cpp virtualizationcore.cpp /src/libs/
459
+ COPY saddle/virtualhardware.c saddle/gpumonitor.cpp saddle/virtualizationcore.cpp /src/libs/
458
460
  RUN set -eux; \
459
461
  # portable baseline per architecture, with the ARCH FLAG the compiler
460
462
  # family actually accepts: x86 and arm speak -march; the ppc64le gcc
@@ -536,7 +538,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
536
538
  # (qemu-bridge/cffi wheels) because the bridge talks raw QMP over a unix
537
539
  # socket without any native binding. the py_compile run is the syntax gate
538
540
  # (error catcher: a bridge that does not compile fails the image build).
539
- COPY qemubridge.py /bridge/qemubridge.py
541
+ COPY saddle/qemubridge.py /bridge/qemubridge.py
540
542
  RUN set -eux; \
541
543
  python3 -m py_compile /bridge/qemubridge.py; \
542
544
  python3 /bridge/qemubridge.py --help >/dev/null; \
@@ -661,9 +663,14 @@ WORKDIR /app
661
663
 
662
664
  ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
663
665
 
664
- COPY package.json package-lock.json ./
666
+ COPY saddle/package.json saddle/package-lock.json ./
665
667
  RUN npm ci --ignore-scripts --legacy-peer-deps
666
- COPY . .
668
+ COPY saddle/ .
669
+ # the engine-ownership sibling: the saddle labels resolve
670
+ # ../vault/labelstore.ts (the node-side fork reads the source file raw),
671
+ # so the vault sources ride the context beside the saddle and land at
672
+ # /vault where the import resolves (the family context fix).
673
+ COPY vault/ /vault
667
674
  RUN npm run build:engine \
668
675
  && node --check dist/cli.js \
669
676
  && npm run web:build:pages \
@@ -907,9 +914,9 @@ COPY --from=python-bridge /bridge/qemubridge.py /engine/qemubridge.py
907
914
  # the engine sources and the specification files consumed at runtime
908
915
  # (docker.config "final" stage copies the whole tree; the flat layout
909
916
  # keeps that a single COPY layer).
910
- COPY *.ts package.json tsconfig.json biome.jsonc jsdom.d.ts /engine/
911
- COPY processors.json gpus.json cores.json boards.json vm.config.json virtualhardware.json /engine/
912
- COPY qemu.config mttg.config passage.config docker.config mime.types /engine/
917
+ COPY saddle/*.ts saddle/package.json saddle/tsconfig.json saddle/biome.jsonc saddle/jsdom.d.ts /engine/
918
+ COPY saddle/processors.json saddle/gpus.json saddle/cores.json saddle/boards.json saddle/vm.config.json saddle/virtualhardware.json /engine/
919
+ COPY saddle/qemu.config saddle/mttg.config saddle/passage.config saddle/docker.config saddle/mime.types /engine/
913
920
 
914
921
  # the web node ships inside the main container: the mesh edition (auth,
915
922
  # sqlite database, dashboard and the browser sandbox pages) runs from the
@@ -1352,17 +1359,23 @@ RUN apt-get update \
1352
1359
  && rm -rf /var/lib/apt/lists/* \
1353
1360
  && npm install --global npm@12.0.2 \
1354
1361
  && tempdir="$(mktemp -d)" \
1355
- && npm pack brace-expansion@5.0.9 ip-address@10.3.1 tar@7.5.22 --pack-destination "$tempdir" \
1356
- && mkdir -p /usr/local/lib/node_modules/npm/node_modules/brace-expansion /usr/local/lib/node_modules/npm/node_modules/ip-address /usr/local/lib/node_modules/npm/node_modules/tar \
1357
- && tar -xzf "$tempdir/brace-expansion-5.0.9.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/brace-expansion \
1362
+ && npm pack brace-expansion@5.0.11 ip-address@10.3.1 tar@7.5.22 undici@6.29.1 --pack-destination "$tempdir" \
1363
+ && mkdir -p /usr/local/lib/node_modules/npm/node_modules/brace-expansion /usr/local/lib/node_modules/npm/node_modules/ip-address /usr/local/lib/node_modules/npm/node_modules/tar /usr/local/lib/node_modules/npm/node_modules/undici \
1364
+ && tar -xzf "$tempdir/brace-expansion-5.0.11.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/brace-expansion \
1358
1365
  && tar -xzf "$tempdir/ip-address-10.3.1.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/ip-address \
1359
1366
  # the npm@12.0.2 tarball freezes its bundled node_modules at publish
1360
1367
  # time (the same lesson as the vhe builder's fixdep): its tar@7.5.19
1361
1368
  # carries CVE-2026-73566 (long-path DoS), fixed in 7.5.21+. The
1362
1369
  # lockfile already resolves app tar to 7.5.22 - the global npm tree
1363
1370
  # gets the same treatment and the version is asserted after install.
1371
+ # The same freeze ships brace-expansion 5.0.9 (CVE-2026-102276 /
1372
+ # CVE-2026-102278, fixed in 5.0.10+) and undici 6.28.0 (CVE-2026-109383
1373
+ # / CVE-2026-109869 / CVE-2026-19534 / CVE-2026-87018, fixed in
1374
+ # 6.29.1+): both carriers ride the swap and the assert proves it.
1364
1375
  && tar -xzf "$tempdir/tar-7.5.22.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/tar \
1376
+ && tar -xzf "$tempdir/undici-6.29.1.tgz" --strip-components=1 -C /usr/local/lib/node_modules/npm/node_modules/undici \
1365
1377
  && test "$(node -p "require('/usr/local/lib/node_modules/npm/node_modules/tar/package.json').version")" = "7.5.22" \
1378
+ && test "$(node -p "require('/usr/local/lib/node_modules/npm/node_modules/undici/package.json').version")" = "6.29.1" \
1366
1379
  && rm -rf "$tempdir" \
1367
1380
  && npm ci --omit=dev --ignore-scripts --legacy-peer-deps \
1368
1381
  # prune the vendored build toolchains of optional native deps: the
package/README.md CHANGED
@@ -31,7 +31,7 @@ domain modules land beside the saddle domains:
31
31
  | `media.ts`, `render.ts`, `quantum.ts`, `tiers.ts` | media pipeline, GPU registry, quantum simulation, tier policy |
32
32
  | `security.ts`, `alternatives.ts` | hardening gates and the alternative-stack catalog |
33
33
  | `index.html`, `App.tsx`, `Sol/` | the single SPA shell and the Sol theme (one tsx app with one stylesheet per theme) |
34
- | `server.ts`, `sandbox.ts` | the zero-dependency self-hosted API of the 2.0.152 grand-merge envelope (`node server.ts`; the api answers its version at `/api/v1/health`) and the browser-pure engine port |
34
+ | `server.ts`, `sandbox.ts` | the zero-dependency self-hosted API of the 2.0.154 grand-merge envelope (`node server.ts`; the api answers its version at `/api/v1/health`) and the browser-pure engine port |
35
35
  | `vercel.json`, `netlify.toml` | the platform deploy adapters beside the surface they publish (the caddy ghost stays out of the tree) |
36
36
  | `specs/*` package exports | processors, gpus, cores, boards and the qemu/mttg/passage/docker envelopes |
37
37
 
package/dist/App.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import '@fontsource-variable/geist-mono';
2
+ import '@fontsource-variable/inter';
1
3
  import './Sol/sol.css';
2
4
  declare function App(): import("react").JSX.Element;
3
5
  export default App;
package/dist/App.js CHANGED
@@ -13,6 +13,8 @@ import { createRoot } from 'react-dom/client';
13
13
  import { Router as WouterRouter } from 'wouter';
14
14
  import Sol from './Sol/Sol';
15
15
  import { ErrorBoundary, ThemeProvider, Toaster, TooltipProvider } from './Sol/shell/Shell.tsx';
16
+ import '@fontsource-variable/geist-mono';
17
+ import '@fontsource-variable/inter';
16
18
  import './Sol/sol.css';
17
19
  import { seedlabels } from './labels';
18
20
  // the copy law rides the boot: the seed refreshes the label store rows
@@ -90,6 +90,13 @@ export declare function edgelandesreport(): Record<string, EdgeLaneReport>;
90
90
  * and never as the visitor browser. Every runonedge entry calls it on
91
91
  * globalThis before a single instruction rides a lane.
92
92
  *
93
+ * the node server process exemption: when the scope is the globalThis
94
+ * of a live node process, the webstorage globals the runtime defines
95
+ * itself (the experimental webstorage surface of node 26) never mark
96
+ * the scope — the server defining its own storage is the server, while
97
+ * any storage riding a scope the runtime did not define (a browser
98
+ * realm, an arbitrary object) stays refused.
99
+ *
93
100
  * @param scope the scope to inspect (duck-typed, works in node).
94
101
  * @throws the coded refusal when the scope is browser-shaped.
95
102
  */
package/dist/edgeplane.js CHANGED
@@ -88,7 +88,12 @@ export function edgelandesreport() {
88
88
  * carrying any of them is a browser, and the engine refuses to execute
89
89
  * as one. navigator alone is not a marker — node itself ships a
90
90
  * vestigial navigator since v21 — the shells and the storages are the
91
- * law. */
91
+ * law. the node runtime may also define the experimental webstorage
92
+ * globals (node 26 ships localStorage and sessionStorage without
93
+ * --localstorage-file): the storages the server process defines on its
94
+ * own globalThis never mark the visitor machine — the engine executes
95
+ * server-side, and the node process carrying its own storage surface is
96
+ * the server, never the visitor browser. */
92
97
  const VISITORMARKERS = Object.freeze([
93
98
  "window",
94
99
  "document",
@@ -96,12 +101,40 @@ const VISITORMARKERS = Object.freeze([
96
101
  "sessionStorage",
97
102
  "indexedDB",
98
103
  ]);
104
+ /** the storage markers the law refuses on any scope: the two webstorage
105
+ * seats (localStorage and sessionStorage) are the ones a node runtime
106
+ * defines on its own globalThis, so the exemption below is scoped to
107
+ * exactly them. */
108
+ const STORAGEMARKERS = Object.freeze([
109
+ "localStorage",
110
+ "sessionStorage",
111
+ ]);
112
+ /** whether the inspected scope is the globalThis of a live node server
113
+ * process: the runtime identity the exemption rides on (the versions
114
+ * table only exists inside node, and the scope identity is the global
115
+ * object itself, so a browser carrying a node-shaped shim never
116
+ * qualifies). */
117
+ function isnodeprocess(scope) {
118
+ if (scope !== globalThis)
119
+ return false;
120
+ if (typeof process !== "object" || process === null)
121
+ return false;
122
+ const versions = process.versions;
123
+ return typeof versions?.node === "string";
124
+ }
99
125
  /** assertvisitorfree — the law guard of the edge plane: throws
100
126
  * {code: "VISITOR_CONTEXT_REFUSED"} when the scope carries
101
127
  * browser-shaped globals, because the engine executes server-side only
102
128
  * and never as the visitor browser. Every runonedge entry calls it on
103
129
  * globalThis before a single instruction rides a lane.
104
130
  *
131
+ * the node server process exemption: when the scope is the globalThis
132
+ * of a live node process, the webstorage globals the runtime defines
133
+ * itself (the experimental webstorage surface of node 26) never mark
134
+ * the scope — the server defining its own storage is the server, while
135
+ * any storage riding a scope the runtime did not define (a browser
136
+ * realm, an arbitrary object) stays refused.
137
+ *
105
138
  * @param scope the scope to inspect (duck-typed, works in node).
106
139
  * @throws the coded refusal when the scope is browser-shaped.
107
140
  */
@@ -109,13 +142,16 @@ export function assertvisitorfree(scope) {
109
142
  if (scope === null || typeof scope !== "object")
110
143
  return;
111
144
  const record = scope;
145
+ const serverprocess = isnodeprocess(scope);
112
146
  for (const marker of VISITORMARKERS) {
113
- if (record[marker] !== undefined) {
114
- const error = new Error(`the visitor context is refused: the scope carries the browser global ${marker} and the edge plane executes server-side only`);
115
- error.name = "edgeplaneerror";
116
- error.code = "VISITOR_CONTEXT_REFUSED";
117
- throw error;
118
- }
147
+ if (record[marker] === undefined)
148
+ continue;
149
+ if (serverprocess && STORAGEMARKERS.includes(marker))
150
+ continue;
151
+ const error = new Error(`the visitor context is refused: the scope carries the browser global ${marker} and the edge plane executes server-side only`);
152
+ error.name = "edgeplaneerror";
153
+ error.code = "VISITOR_CONTEXT_REFUSED";
154
+ throw error;
119
155
  }
120
156
  }
121
157
  /* ════════════════════════════════════════════════════════════════════ */
package/dist/labels.d.ts CHANGED
@@ -650,7 +650,7 @@ export declare const labelmanifest: {
650
650
  readonly 'notfound.agentBrowser': 'Open the agent browser';
651
651
  };
652
652
  export type LabelKey = keyof typeof labelmanifest & string;
653
- /** the typed accessor of the copy: the only door a widget uses. */
653
+ /** the typed accessor of the copy: the only door a widget or page anchor uses. */
654
654
  export declare function label(key: LabelKey): string;
655
655
  /** the placeholder door: fills the {token} slots of one format key with the
656
656
  * runtime values (the store keeps the sentence, the widget passes the data). */
@@ -658,7 +658,7 @@ export declare function labelfill(key: LabelKey, values: Record<string, string>)
658
658
  /** the seed ingests the manifest into the labels store, idempotent, and the
659
659
  * translated rows stay protected from the overwrite. in the browser the rows
660
660
  * ride the local cache of the site db; on the server db.ts seeds the same
661
- * manifest into the labels table through seedlabelrows. */
661
+ * manifest into the labels table through seedLabelRows. */
662
662
  export declare function seedlabels(): Promise<{
663
663
  seeded: number;
664
664
  protected: number;
package/dist/labels.js CHANGED
@@ -11,6 +11,7 @@
11
11
  * bundle): the type comes across erased, the browser seed mirrors the rows
12
12
  * into the local cache the way onboardingstate.ts mirrors the seen flags.
13
13
  */
14
+ import { createLabelStore } from "../vault/labelstore.ts";
14
15
  /** the label revision of the manifest: bump on every copy wave so the seed
15
16
  * refreshes the store rows of the changed strings. */
16
17
  export const LABEL_REVISION = 4;
@@ -680,72 +681,27 @@ export const labelmanifest = {
680
681
  'notfound.backHome': 'Back home',
681
682
  'notfound.agentBrowser': 'Open the agent browser',
682
683
  };
683
- /** the translation overrides the runtime loads from the store at boot: the
684
- * key answers the translated text when the person changed the language. */
685
- const overrides = new Map();
686
- /** the browser cache of the labels table: the rows mirror the site db (the
687
- * db is the source of truth; the cache carries the translated rows offline —
688
- * the onboardingstate.ts precedent for the visitor machine). */
689
- const CACHE_KEY = 'saddle.labels.rows';
690
- function readcache() {
691
- if (typeof window === 'undefined')
692
- return [];
693
- try {
694
- const raw = window.localStorage.getItem(CACHE_KEY);
695
- if (!raw)
696
- return [];
697
- const parsed = JSON.parse(raw);
698
- return Array.isArray(parsed)
699
- ? parsed.filter((row) => !!row && typeof row === 'object')
700
- : [];
701
- }
702
- catch {
703
- return [];
704
- }
705
- }
706
- function writecache(rows) {
707
- if (typeof window === 'undefined')
708
- return;
709
- try {
710
- window.localStorage.setItem(CACHE_KEY, JSON.stringify(rows));
711
- }
712
- catch {
713
- // the cache is best effort: the manifest always answers
714
- }
715
- }
716
- /** the typed accessor of the copy: the only door a widget uses. */
684
+ /** the store mechanism rides the vault factory (the storage law of the
685
+ * family: the vault owns the store, the app owns the manifest — the
686
+ * mechanism is imported, never duplicated). the cache key keeps the browser mirror the wingets already read. */
687
+ const store = createLabelStore({
688
+ manifest: labelmanifest,
689
+ revision: LABEL_REVISION,
690
+ cacheKey: "saddle.labels.rows",
691
+ });
692
+ /** the typed accessor of the copy: the only door a widget or page anchor uses. */
717
693
  export function label(key) {
718
- return overrides.get(key) ?? labelmanifest[key];
694
+ return store.label(key);
719
695
  }
720
696
  /** the placeholder door: fills the {token} slots of one format key with the
721
697
  * runtime values (the store keeps the sentence, the widget passes the data). */
722
698
  export function labelfill(key, values) {
723
- return label(key).replace(/\{(\w+)\}/g, (match, token) => values[token] ?? match);
699
+ return store.labelfill(key, values);
724
700
  }
725
701
  /** the seed ingests the manifest into the labels store, idempotent, and the
726
702
  * translated rows stay protected from the overwrite. in the browser the rows
727
703
  * ride the local cache of the site db; on the server db.ts seeds the same
728
- * manifest into the labels table through seedlabelrows. */
729
- export async function seedlabels() {
730
- const stored = readcache();
731
- const rows = new Map(stored.map((row) => [row.id, row]));
732
- let seeded = 0;
733
- let saved = 0;
734
- for (const [id, text] of Object.entries(labelmanifest)) {
735
- const row = rows.get(id);
736
- overrides.set(id, row && row.translated ? row.text : text);
737
- if (!row || (!row.translated && row.text !== text)) {
738
- rows.set(id, {
739
- id,
740
- text,
741
- revision: LABEL_REVISION,
742
- updatedAt: new Date().toISOString(),
743
- ...(row?.translated ? { translated: true } : {}),
744
- });
745
- seeded += 1;
746
- }
747
- saved += 1;
748
- }
749
- writecache([...rows.values()]);
750
- return { seeded, protected: saved - seeded };
704
+ * manifest into the labels table through seedLabelRows. */
705
+ export function seedlabels() {
706
+ return store.seedLabels();
751
707
  }
@@ -16,6 +16,11 @@ export default defineConfig({
16
16
  alias: {
17
17
  // the theme sources live in the Sol folder of this app root.
18
18
  '@': path.resolve(import.meta.dirname, 'Sol'),
19
+ // The label store bridge of the ownership law: the app's copy mechanism
20
+ // rides the vault factory through the relative sibling source (the
21
+ // owner is the vault engine — the mechanism is never duplicated inside
22
+ // saddle), and the raw-node lanes (the forked webserver) resolve the
23
+ // same relative path without any registry envelope.
19
24
  // The playground consumes the engine source here so the build never
20
25
  // depends on a prebuilt dist directory.
21
26
  '@saddle/isolation': path.resolve(import.meta.dirname, 'isolation.ts'),
@@ -34,7 +39,7 @@ export default defineConfig({
34
39
  * dev server allows this single root. */
35
40
  server: {
36
41
  fs: {
37
- allow: [import.meta.dirname],
42
+ allow: [import.meta.dirname, path.resolve(import.meta.dirname, '../vault')],
38
43
  },
39
44
  host: true,
40
45
  strictPort: false,
package/dist/webserver.js CHANGED
@@ -72,7 +72,7 @@ const webdir = moduledir;
72
72
  * greps this exact literal out of the source with a regular expression,
73
73
  * so the declaration below stays a plain single-quoted string with no
74
74
  * type annotation in typescript. */
75
- const version = '2.0.152';
75
+ const version = '2.0.154';
76
76
  /** sandbox ttl in milliseconds (15 minutes) and sweep interval (60 s). */
77
77
  const ttlms = 15 * 60 * 1000;
78
78
  const sweepperiodms = 60 * 1000;
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Saddle browser bridge",
4
- "version": "2.0.152",
4
+ "version": "2.0.154",
5
5
  "description": "User initiated page snapshots through the Saddle browser contract.",
6
6
  "minimum_chrome_version": "110",
7
7
  "permissions": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenathlan/saddle",
3
- "version": "2.0.152",
3
+ "version": "2.0.154",
4
4
  "description": "binary computing engine that turns distributed storage into a publishable working set carrying the merged virtual-hardware engine (the saddle + e2ugh grand merge)",
5
5
  "type": "module",
6
6
  "private": false,
@@ -238,9 +238,9 @@
238
238
  "vm:create": "node --import tsx ./cli.ts vm",
239
239
  "vm:validate": "node --import tsx ./cli.ts vmlint",
240
240
  "gpu:list": "node --import tsx ./cli.ts gpus",
241
- "docker:build": "docker build -f Dockerfile -t saddle:2.0.152 .",
242
- "docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.152",
243
- "docker:health": "docker run --rm saddle:2.0.152 qemu-system-x86_64 --version && curl -fsS http://localhost:8080/health || true",
241
+ "docker:build": "docker build -f Dockerfile -t saddle:2.0.154 .",
242
+ "docker:run": "docker run --rm --memory-swap=-1 --shm-size=2g saddle:2.0.154",
243
+ "docker:health": "docker run --rm saddle:2.0.154 qemu-system-x86_64 --version && curl -fsS http://localhost:8080/health || true",
244
244
  "firecracker:boot": "firecracker --api-sock /tmp/fc-saddle.sock --config-file vmconfig.json 2>/dev/null || echo 'firecracker 1.16.1 warm-pool boot 125ms target'",
245
245
  "py:bridge": "python3 qemubridge.py",
246
246
  "py:test": "python3 -m pytest -q || python3 qemubridge.py",
@@ -251,6 +251,8 @@
251
251
  "dependencies": {
252
252
  "@fluentui/react-icons": "^2.0.345",
253
253
  "@fontsource-variable/bricolage-grotesque": "^5.3.0",
254
+ "@fontsource-variable/geist-mono": "^5.3.0",
255
+ "@fontsource-variable/inter": "^5.3.0",
254
256
  "@fontsource-variable/jetbrains-mono": "^5.3.0",
255
257
  "@fontsource-variable/outfit": "^5.3.0",
256
258
  "@hono/node-server": "^2.1.5",
@@ -258,14 +260,16 @@
258
260
  "@prisma/adapter-libsql": "^7.10.1",
259
261
  "@prisma/client": "^7.10.1",
260
262
  "@wenathlan/saddle": "^2.1.6",
261
- "@wenathlan/vault": "^2.0.151",
262
- "hono": "^4.13.14",
263
+ "@wenathlan/vault": "^2.0.153",
264
+ "gsap": "^3.15.0",
265
+ "hono": "^4.13.16",
263
266
  "ink": "^7.1.1",
264
267
  "lucide-react": "^1.66.0",
265
268
  "motion": "^12.43.0",
266
269
  "react": "^19.3.1",
267
270
  "react-devtools-core": "^8.0.0",
268
271
  "react-dom": "^19.3.1",
272
+ "three": "^0.187.0",
269
273
  "wouter": "^3.13.0",
270
274
  "z-ai-web-dev-sdk": "^0.0.18"
271
275
  },
@@ -311,6 +315,7 @@
311
315
  "@tailwindcss/vite": "^4.3.3",
312
316
  "@tsconfig/node26": "^26.0.1",
313
317
  "@types/better-sqlite3": "^9.6.0",
318
+ "@types/three": "^0.187.0",
314
319
  "@types/chrome": "^0.3.7",
315
320
  "@types/express": "^5.0.6",
316
321
  "@types/google.maps": "^3.66.9",
@@ -335,7 +340,7 @@
335
340
  "express": "^5.3.0",
336
341
  "fast-check": "^4.10.2",
337
342
  "framer-motion": "^13.5.1",
338
- "hono": "^4.13.14",
343
+ "hono": "^4.13.16",
339
344
  "input-otp": "^1.5.0",
340
345
  "jsdom": "26.1.0",
341
346
  "lucide-react": "^1.66.0",
@@ -353,7 +358,7 @@
353
358
  "react-day-picker": "^10.0.2",
354
359
  "react-dom": "^19.3.1",
355
360
  "react-hook-form": "^7.91.0",
356
- "react-resizable-panels": "^4.15.3",
361
+ "react-resizable-panels": "^4.16.1",
357
362
  "recharts": "^3.10.1",
358
363
  "sonner": "^2.0.8",
359
364
  "streamdown": "^2.7.1",