@hanzo/build 0.2.67 → 0.2.68

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.
Files changed (2) hide show
  1. package/lib/gear.js +41 -11
  2. package/package.json +1 -1
package/lib/gear.js CHANGED
@@ -9,9 +9,13 @@
9
9
  *
10
10
  * Both live in the account, so a choice follows the person to every device. A
11
11
  * conversation that has none of its own reads the default; changing one writes
12
- * that conversation's, and "Keep as default" writes the default.
12
+ * that conversation's. A conversation not begun holds its choice on the page
13
+ * (`draft`) and hands it to the conversation once it has an id. What carries
14
+ * over to the next new conversation is only the switches (`STICKY`: Research,
15
+ * Memory, the mode); a skill or a plugin is picked for the conversation it was
16
+ * picked in.
13
17
  */
14
- import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
18
+ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
15
19
  import { call } from './api/call.js';
16
20
  /**
17
21
  * The modes the platform honours. Manual and Accept edits are not here: a cloud
@@ -32,6 +36,9 @@ export const BARE = {
32
36
  memory: true,
33
37
  mode: 'auto',
34
38
  };
39
+ /** What a new conversation carries over from the last: the switches, never a pick. */
40
+ const STICKY = ['research', 'memory', 'mode'];
41
+ const pick = (g) => ({ research: g.research, memory: g.memory, mode: g.mode });
35
42
  /** How many conversations keep a choice of their own: the document holds 16 KiB in all. */
36
43
  export const KEEP = 30;
37
44
  const list = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string' && x !== '') : []);
@@ -62,7 +69,7 @@ export function shelve(gears, key, g) {
62
69
  }
63
70
  return out;
64
71
  }
65
- let doc = { gear: BARE, gears: {}, language: null };
72
+ let doc = { gear: BARE, gears: {}, draft: null, language: null };
66
73
  let loaded = null;
67
74
  const listeners = new Set();
68
75
  const emit = () => listeners.forEach((f) => f());
@@ -81,7 +88,7 @@ function load(t) {
81
88
  const gear = gearOf(raw.gear);
82
89
  const all = raw.gears && typeof raw.gears === 'object' ? raw.gears : {};
83
90
  const language = typeof raw.language === 'string' && raw.language ? raw.language : null;
84
- doc = { gear, gears: Object.fromEntries(Object.entries(all).map(([k, v]) => [k, gearOf(v, gear)])), language };
91
+ doc = { gear, gears: Object.fromEntries(Object.entries(all).map(([k, v]) => [k, gearOf(v, gear)])), draft: doc.draft, language };
85
92
  emit();
86
93
  })
87
94
  .catch(() => {
@@ -96,16 +103,39 @@ export function useGear(t, key = '') {
96
103
  useEffect(() => load(t), [t]);
97
104
  const now = useSyncExternalStore(subscribe, () => doc, () => doc);
98
105
  const [error, setError] = useState(null);
99
- const gear = key ? (now.gears[key] ?? now.gear) : now.gear;
100
- const write = useCallback((g) => {
101
- const fresh = key ? { ...doc, gears: shelve(doc.gears, key, g) } : { ...doc, gear: g };
102
- doc = fresh;
103
- emit();
106
+ // A conversation with no choice of its own starts from the switches alone.
107
+ const fresh = useMemo(() => ({ ...BARE, ...pick(now.gear) }), [now.gear]);
108
+ const gear = key ? (now.gears[key] ?? fresh) : (now.draft ?? fresh);
109
+ const save = useCallback((patch) => {
104
110
  if (!t.token())
105
111
  return;
106
- const patch = key ? { gears: fresh.gears } : { gear: g };
107
112
  call(t, 'PATCH', '/v1/pref', patch).then(() => setError(null), (e) => setError(e instanceof Error ? e.message : 'Your choice was not saved.'));
108
- }, [key, t]);
113
+ }, [t]);
114
+ // The conversation just begun takes what was picked before it had an id.
115
+ const was = useRef(key);
116
+ useEffect(() => {
117
+ const from = was.current;
118
+ was.current = key;
119
+ if (from || !key || !doc.draft || doc.gears[key])
120
+ return;
121
+ doc = { ...doc, gears: shelve(doc.gears, key, doc.draft), draft: null };
122
+ emit();
123
+ save({ gears: doc.gears });
124
+ }, [key, save]);
125
+ const write = useCallback((g) => {
126
+ if (key) {
127
+ doc = { ...doc, gears: shelve(doc.gears, key, g) };
128
+ emit();
129
+ return save({ gears: doc.gears });
130
+ }
131
+ // Not begun: the page holds it; the switches become the default too.
132
+ const sticky = { ...doc.gear, ...pick(g) };
133
+ const moved = STICKY.some((k) => sticky[k] !== doc.gear[k]);
134
+ doc = { ...doc, draft: g, ...(moved ? { gear: sticky } : null) };
135
+ emit();
136
+ if (moved)
137
+ save({ gear: sticky });
138
+ }, [key, save]);
109
139
  return useMemo(() => ({
110
140
  gear,
111
141
  error,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.67",
3
+ "version": "0.2.68",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",