@hanzo/rooms 0.1.73 → 0.1.74

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 (108) hide show
  1. package/assets/personas/ada.md +187 -0
  2. package/assets/personas/adi_shamir.md +155 -0
  3. package/assets/personas/al_khwarizmi.md +168 -0
  4. package/assets/personas/al_kindi.md +172 -0
  5. package/assets/personas/alan_kay.md +154 -0
  6. package/assets/personas/analyst.md +138 -0
  7. package/assets/personas/andrew_yao.md +155 -0
  8. package/assets/personas/auguste_kerckhoffs.md +168 -0
  9. package/assets/personas/barbara_liskov.md +154 -0
  10. package/assets/personas/butler_lampson.md +156 -0
  11. package/assets/personas/charles_babbage.md +175 -0
  12. package/assets/personas/creative.md +141 -0
  13. package/assets/personas/curry.md +169 -0
  14. package/assets/personas/cynthia_dwork.md +158 -0
  15. package/assets/personas/david_chaum.md +157 -0
  16. package/assets/personas/david_parnas.md +153 -0
  17. package/assets/personas/dennis_ritchie.md +153 -0
  18. package/assets/personas/dijkstra.md +187 -0
  19. package/assets/personas/donald_knuth.md +156 -0
  20. package/assets/personas/douglas_engelbart.md +175 -0
  21. package/assets/personas/edgar_codd.md +151 -0
  22. package/assets/personas/engineer.md +140 -0
  23. package/assets/personas/feynman.md +178 -0
  24. package/assets/personas/finney.md +187 -0
  25. package/assets/personas/fred_brooks.md +152 -0
  26. package/assets/personas/geoffrey_hinton.md +194 -0
  27. package/assets/personas/herbert_simon.md +190 -0
  28. package/assets/personas/hickey.md +169 -0
  29. package/assets/personas/hopper.md +191 -0
  30. package/assets/personas/hubert_dreyfus.md +184 -0
  31. package/assets/personas/humberto_maturana.md +156 -0
  32. package/assets/personas/index.json +1 -0
  33. package/assets/personas/jcr_licklider.md +178 -0
  34. package/assets/personas/jobs.md +177 -0
  35. package/assets/personas/joe_armstrong.md +155 -0
  36. package/assets/personas/john_backus.md +179 -0
  37. package/assets/personas/john_mccarthy.md +177 -0
  38. package/assets/personas/jon_postel.md +149 -0
  39. package/assets/personas/joseph_weizenbaum.md +185 -0
  40. package/assets/personas/judea_pearl.md +192 -0
  41. package/assets/personas/karen_sparck_jones.md +154 -0
  42. package/assets/personas/konrad_zuse.md +175 -0
  43. package/assets/personas/kunihiko_fukushima.md +152 -0
  44. package/assets/personas/leslie_lamport.md +156 -0
  45. package/assets/personas/markov.md +171 -0
  46. package/assets/personas/mentor.md +141 -0
  47. package/assets/personas/nancy_lynch.md +154 -0
  48. package/assets/personas/niklaus_wirth.md +154 -0
  49. package/assets/personas/norbert_wiener.md +179 -0
  50. package/assets/personas/north-star.md +138 -0
  51. package/assets/personas/peter_naur.md +151 -0
  52. package/assets/personas/pike.md +165 -0
  53. package/assets/personas/radia_perlman.md +156 -0
  54. package/assets/personas/raj_reddy.md +154 -0
  55. package/assets/personas/ralph_merkle.md +152 -0
  56. package/assets/personas/richard_sutton.md +156 -0
  57. package/assets/personas/robin_milner.md +149 -0
  58. package/assets/personas/shafi_goldwasser.md +156 -0
  59. package/assets/personas/shannon.md +184 -0
  60. package/assets/personas/stafford_beer.md +157 -0
  61. package/assets/personas/taher_elgamal.md +180 -0
  62. package/assets/personas/tesla.md +181 -0
  63. package/assets/personas/tony_hoare.md +153 -0
  64. package/assets/personas/torvalds.md +188 -0
  65. package/assets/personas/tpope.md +163 -0
  66. package/assets/personas/turing.md +188 -0
  67. package/assets/personas/whitfield_diffie.md +159 -0
  68. package/assets/personas/zeekay.md +151 -0
  69. package/dist/App.js +26 -22
  70. package/dist/Chat.js +51 -38
  71. package/dist/Column.d.ts +3 -3
  72. package/dist/Column.js +5 -27
  73. package/dist/Me.d.ts +8 -3
  74. package/dist/Me.js +79 -108
  75. package/dist/Palette.d.ts +18 -0
  76. package/dist/Palette.js +131 -0
  77. package/dist/Rail.js +3 -15
  78. package/dist/Recents.d.ts +1 -2
  79. package/dist/Recents.js +9 -9
  80. package/dist/Shell.d.ts +7 -0
  81. package/dist/Shell.js +65 -46
  82. package/dist/Top.d.ts +17 -0
  83. package/dist/Top.js +58 -0
  84. package/dist/agents.d.ts +64 -0
  85. package/dist/agents.js +62 -0
  86. package/dist/crew.d.ts +26 -5
  87. package/dist/crew.js +208 -83
  88. package/dist/help.d.ts +43 -0
  89. package/dist/help.js +58 -0
  90. package/dist/lib/web.d.ts +6 -0
  91. package/dist/lib/web.js +39 -7
  92. package/dist/recent.d.ts +2 -2
  93. package/dist/recent.js +3 -5
  94. package/dist/seek.d.ts +37 -0
  95. package/dist/seek.js +55 -0
  96. package/dist/team.js +3 -11
  97. package/dist/thread.js +7 -2
  98. package/dist/view.d.ts +44 -0
  99. package/dist/view.js +98 -0
  100. package/package.json +5 -5
  101. package/dist/Account.d.ts +0 -12
  102. package/dist/Account.js +0 -65
  103. package/dist/Find.d.ts +0 -7
  104. package/dist/Find.js +0 -82
  105. package/dist/Profile.d.ts +0 -4
  106. package/dist/Profile.js +0 -67
  107. package/dist/Standing.d.ts +0 -4
  108. package/dist/Standing.js +0 -16
package/dist/recent.d.ts CHANGED
@@ -56,12 +56,12 @@ export declare function runs(sessions: {
56
56
  updatedAt: string;
57
57
  }[]): Recent[];
58
58
  /**
59
- * The list as drawn: the rows the view and the words admit, sorted, and cut into
59
+ * The list as drawn: the rows the view admits, sorted, and cut into
60
60
  * headed groups in the order they read. Pinned rows come first, under Pinned,
61
61
  * whatever the grouping; a view grouped by None holds the rest in one group with
62
62
  * no heading.
63
63
  */
64
- export declare function arrange(rows: Recent[], view: View, words: string, now?: Date): {
64
+ export declare function arrange(rows: Recent[], view: View, now?: Date): {
65
65
  title: string;
66
66
  rows: Recent[];
67
67
  }[];
package/dist/recent.js CHANGED
@@ -106,15 +106,13 @@ function day(at, now) {
106
106
  }
107
107
  const newest = (a, b) => (a < b ? 1 : a > b ? -1 : 0);
108
108
  /**
109
- * The list as drawn: the rows the view and the words admit, sorted, and cut into
109
+ * The list as drawn: the rows the view admits, sorted, and cut into
110
110
  * headed groups in the order they read. Pinned rows come first, under Pinned,
111
111
  * whatever the grouping; a view grouped by None holds the rest in one group with
112
112
  * no heading.
113
113
  */
114
- export function arrange(rows, view, words, now = new Date()) {
115
- const w = words.trim().toLowerCase();
116
- const kept = rows.filter((r) => (!w || r.title.toLowerCase().includes(w)) &&
117
- (view.status === 'all' || r.status === view.status) &&
114
+ export function arrange(rows, view, now = new Date()) {
115
+ const kept = rows.filter((r) => (view.status === 'all' || r.status === view.status) &&
118
116
  (view.since === 'any' || now.getTime() - new Date(r.at).getTime() <= SPAN[view.since]));
119
117
  kept.sort((a, b) => view.sort === 'title' ? a.title.localeCompare(b.title) : view.sort === 'created' ? newest(a.began, b.began) : newest(a.at, b.at));
120
118
  const headOf = (r) => {
package/dist/seek.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { Press } from './chord.js';
2
+ /** A row the palette can list. */
3
+ export interface Hit {
4
+ id: string;
5
+ label: string;
6
+ /** Other words it is found by: a project's slug and repository, a skill's description. */
7
+ keywords?: string[];
8
+ /** When it last changed, ISO: the newest lead Recent chats. */
9
+ at?: string;
10
+ }
11
+ export interface Group<T extends Hit = Hit> {
12
+ id: string;
13
+ label: string;
14
+ items: T[];
15
+ }
16
+ /** The most rows one group shows for a query. */
17
+ export declare const CAP = 5;
18
+ /** The chats an empty field lists. */
19
+ export declare const RECENT = 8;
20
+ /**
21
+ * How well a row answers a query: 0 drops it, more ranks it higher. Every word
22
+ * of the query must be found, in the label or the row's other words; each counts
23
+ * most where the label starts with it, then where a word of the label does, then
24
+ * anywhere in the label, then in the other words alone. The label read whole
25
+ * outranks everything.
26
+ */
27
+ export declare function fit(row: Hit, query: string): number;
28
+ /** The rows a query keeps, best first, ties in the order they came, at most `cap`. */
29
+ export declare function rank<T extends Hit>(rows: T[], query: string, cap?: number): T[];
30
+ /**
31
+ * What the palette lists. An empty field: the newest chats, under Recent chats.
32
+ * A query: Chats, then each other group in its order, each cut to its best
33
+ * `CAP`, a group with nothing left out.
34
+ */
35
+ export declare function find<T extends Hit>(query: string, chats: T[], groups: Group<T>[]): Group<T>[];
36
+ /** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
37
+ export declare const summons: (e: Press) => boolean;
package/dist/seek.js ADDED
@@ -0,0 +1,55 @@
1
+ // The palette's decisions (Palette.tsx draws them): which rows a query keeps,
2
+ // in what order, under which heading, and which key press opens it. No React,
3
+ // so a spec reads them as they are.
4
+ /** The most rows one group shows for a query. */
5
+ export const CAP = 5;
6
+ /** The chats an empty field lists. */
7
+ export const RECENT = 8;
8
+ /**
9
+ * How well a row answers a query: 0 drops it, more ranks it higher. Every word
10
+ * of the query must be found, in the label or the row's other words; each counts
11
+ * most where the label starts with it, then where a word of the label does, then
12
+ * anywhere in the label, then in the other words alone. The label read whole
13
+ * outranks everything.
14
+ */
15
+ export function fit(row, query) {
16
+ const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
17
+ if (!terms.length)
18
+ return 1;
19
+ const label = row.label.toLowerCase();
20
+ const words = label.split(/[^\p{L}\p{N}]+/u);
21
+ const more = (row.keywords ?? []).join(' ').toLowerCase();
22
+ let total = label === terms.join(' ') ? 10 : 0;
23
+ for (const t of terms) {
24
+ const s = label.startsWith(t) ? 4 : words.some((w) => w.startsWith(t)) ? 3 : label.includes(t) ? 2 : more.includes(t) ? 1 : 0;
25
+ if (!s)
26
+ return 0;
27
+ total += s;
28
+ }
29
+ return total;
30
+ }
31
+ /** The rows a query keeps, best first, ties in the order they came, at most `cap`. */
32
+ export function rank(rows, query, cap = CAP) {
33
+ return rows
34
+ .map((row, i) => ({ row, i, score: fit(row, query) }))
35
+ .filter((x) => x.score > 0)
36
+ .sort((a, b) => b.score - a.score || a.i - b.i)
37
+ .slice(0, cap)
38
+ .map((x) => x.row);
39
+ }
40
+ /**
41
+ * What the palette lists. An empty field: the newest chats, under Recent chats.
42
+ * A query: Chats, then each other group in its order, each cut to its best
43
+ * `CAP`, a group with nothing left out.
44
+ */
45
+ export function find(query, chats, groups) {
46
+ if (!query.trim()) {
47
+ const newest = [...chats].sort((a, b) => (b.at ?? '').localeCompare(a.at ?? '')).slice(0, RECENT);
48
+ return newest.length ? [{ id: 'recent', label: 'Recent chats', items: newest }] : [];
49
+ }
50
+ return [{ id: 'chats', label: 'Chats', items: chats }, ...groups]
51
+ .map((g) => ({ ...g, items: rank(g.items, query) }))
52
+ .filter((g) => g.items.length);
53
+ }
54
+ /** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
55
+ export const summons = (e) => (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && !e.repeat && !e.isComposing && (e.key ?? '').toLowerCase() === 'k';
package/dist/team.js CHANGED
@@ -6,19 +6,11 @@
6
6
  // build (scripts/team.mjs), so the rooms keep no copy of a persona: moving the
7
7
  // pin is how a persona changes.
8
8
  //
9
- // The order is the order the crew is drawn in.
9
+ // Every member the package publishes, in the order the crew is drawn in
10
+ // (scripts/team.mjs `ORDER`): this is the default cast.
10
11
  import { parse } from '@hanzo/personas/team.js';
11
12
  import { FILES } from './team.gen.js';
12
- export const TEAM = [
13
- parse('dev', FILES.dev),
14
- parse('des', FILES.des),
15
- parse('vi', FILES.vi),
16
- parse('feynman', FILES.feynman),
17
- parse('einstein', FILES.einstein),
18
- parse('nora', FILES.nora),
19
- parse('maya', FILES.maya),
20
- parse('leo', FILES.leo),
21
- ];
13
+ export const TEAM = Object.entries(FILES).map(([id, text]) => parse(id, text));
22
14
  const BY_ID = new Map(TEAM.map((one) => [one.id, one]));
23
15
  /** The core member an agent name refers to, whatever its case, or undefined. */
24
16
  export const member = (name) => BY_ID.get(name?.toLowerCase() ?? '');
package/dist/thread.js CHANGED
@@ -33,6 +33,8 @@ import { attempt, bind, enlist, reread, snapshot, subscribe, sweep, tick, view }
33
33
  import { chord, named, said } from './chord.js';
34
34
  import { carry } from './stars.js';
35
35
  import { ShareDialog } from './Share.js';
36
+ /** Each client's last read of each shelf. */
37
+ const last = new WeakMap();
36
38
  /**
37
39
  * The member's conversations: pinned first, then most recently spoken in, the
38
40
  * archived ones left out — or, with `archived`, those alone — with every change
@@ -43,7 +45,8 @@ import { ShareDialog } from './Share.js';
43
45
  export function useThreadList(archived = false, seek = null) {
44
46
  const ai = useAi();
45
47
  // What was read, when, and from which shelf: the other shelf's rows are never drawn as this one's.
46
- const [read, setRead] = useState({ threads: [], at: 0, archived });
48
+ // A list mounted later (the palette) draws the client's last read of its shelf at once.
49
+ const [read, setRead] = useState(() => last.get(ai)?.[`${archived}`] ?? { threads: [], at: 0, archived });
47
50
  const [loading, setLoading] = useState(true);
48
51
  const [error, setError] = useState(null);
49
52
  const [asks, ask] = useState(0);
@@ -59,7 +62,9 @@ export function useThreadList(archived = false, seek = null) {
59
62
  if (control.signal.aborted)
60
63
  return;
61
64
  me.current.at = at;
62
- setRead({ threads, at, archived });
65
+ const got = { threads, at, archived };
66
+ last.set(ai, { ...last.get(ai), [`${archived}`]: got });
67
+ setRead(got);
63
68
  setError(null);
64
69
  setLoading(false);
65
70
  sweep();
package/dist/view.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /** The visual viewport, as `window.visualViewport` reports it. */
2
+ export interface Viewport {
3
+ height: number;
4
+ offsetTop: number;
5
+ scale: number;
6
+ }
7
+ /** Where the window stands, px. */
8
+ export interface Fit {
9
+ top: number;
10
+ height: number;
11
+ keyboard: number;
12
+ }
13
+ /**
14
+ * Where the window stands in a layout viewport `layout` px tall: null while the
15
+ * visual viewport is all of it, or zoomed in.
16
+ */
17
+ export declare function fit(layout: number, view: Viewport): Fit | null;
18
+ /** The window's top, for a style: the visual viewport's while a keyboard is up. */
19
+ export declare const TOP = "var(--view-top, 0px)";
20
+ /** The window's height, for a style: the visual viewport's while a keyboard is up. */
21
+ export declare const HEIGHT = "var(--view-height, 100dvh)";
22
+ /** The home indicator's inset, which the keyboard covers while it is up. */
23
+ export declare const FOOT = "max(0px, calc(var(--safe-bottom, 0px) - var(--keyboard, 0px)))";
24
+ interface Style {
25
+ setProperty(name: string, value: string): void;
26
+ removeProperty(name: string): string;
27
+ }
28
+ interface Source extends Viewport {
29
+ addEventListener(type: 'resize' | 'scroll', fn: () => void): void;
30
+ removeEventListener(type: 'resize' | 'scroll', fn: () => void): void;
31
+ }
32
+ interface Frame {
33
+ innerHeight: number;
34
+ requestAnimationFrame(fn: () => void): number;
35
+ cancelAnimationFrame(id: number): void;
36
+ }
37
+ /** Writes the window's place on `style` as `view` moves, once a frame; returns the stop. */
38
+ export declare function follow(style: Style, view: Source, frame: Frame): () => void;
39
+ /**
40
+ * The ref for the window: it follows the visual viewport while mounted. A
41
+ * browser without one leaves the window at 100dvh.
42
+ */
43
+ export declare function useView(): (node: unknown) => void;
44
+ export {};
package/dist/view.js ADDED
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+ // The window kept inside what the reader can see, so a phone's keyboard never
3
+ // covers the composer.
4
+ //
5
+ // A phone's keyboard does not shrink the page: the layout viewport keeps its
6
+ // height and only the VISUAL viewport (`window.visualViewport`) loses the
7
+ // keyboard's height, and the browser pans it down the layout to show the field.
8
+ // So the app reads the visual viewport and stands the window in it: its top at
9
+ // the visual viewport's top, its height the visual viewport's. The bar stays at
10
+ // the top of what shows, the composer sits on the keyboard, and the transcript
11
+ // scrolls in what is left between them.
12
+ //
13
+ // const view = useView()
14
+ // <YStack ref={view} $max-md={{ position: 'fixed', t: TOP, height: HEIGHT }}>
15
+ //
16
+ // It writes three custom properties on the element, which every descendant
17
+ // reads, and nothing else: no state, so a keyboard opening re-renders nothing.
18
+ //
19
+ // --view-top px from the layout viewport's top to the visual one's
20
+ // --view-height px of the visual viewport
21
+ // --keyboard px the keyboard takes from the view; 0 when it is down
22
+ //
23
+ // While the visual viewport is the layout one, the first two are unset (the
24
+ // window keeps 100dvh) and --keyboard is 0. A pinch-zoom shrinks the visual
25
+ // viewport too, and the window does not chase it. Events are folded into one
26
+ // write per frame.
27
+ import { useCallback, useRef } from 'react';
28
+ /** A zoom past this is the reader's pinch, not a keyboard. */
29
+ const ZOOM = 1.01;
30
+ /**
31
+ * Where the window stands in a layout viewport `layout` px tall: null while the
32
+ * visual viewport is all of it, or zoomed in.
33
+ */
34
+ export function fit(layout, view) {
35
+ if (view.scale > ZOOM)
36
+ return null;
37
+ const height = Math.round(view.height);
38
+ const keyboard = Math.round(layout) - height;
39
+ if (keyboard < 1)
40
+ return null;
41
+ return { top: Math.max(0, Math.round(view.offsetTop)), height, keyboard };
42
+ }
43
+ /** The window's top, for a style: the visual viewport's while a keyboard is up. */
44
+ export const TOP = 'var(--view-top, 0px)';
45
+ /** The window's height, for a style: the visual viewport's while a keyboard is up. */
46
+ export const HEIGHT = 'var(--view-height, 100dvh)';
47
+ /** The home indicator's inset, which the keyboard covers while it is up. */
48
+ export const FOOT = 'max(0px, calc(var(--safe-bottom, 0px) - var(--keyboard, 0px)))';
49
+ /** Writes the window's place on `style` as `view` moves, once a frame; returns the stop. */
50
+ export function follow(style, view, frame) {
51
+ let queued = 0;
52
+ let last = null;
53
+ const write = () => {
54
+ queued = 0;
55
+ const at = fit(frame.innerHeight, view);
56
+ const key = at ? `${at.top}/${at.height}/${at.keyboard}` : '';
57
+ if (key === last)
58
+ return;
59
+ last = key;
60
+ if (at) {
61
+ style.setProperty('--view-top', `${at.top}px`);
62
+ style.setProperty('--view-height', `${at.height}px`);
63
+ style.setProperty('--keyboard', `${at.keyboard}px`);
64
+ }
65
+ else {
66
+ style.removeProperty('--view-top');
67
+ style.removeProperty('--view-height');
68
+ style.setProperty('--keyboard', '0px');
69
+ }
70
+ };
71
+ const queue = () => {
72
+ if (!queued)
73
+ queued = frame.requestAnimationFrame(write);
74
+ };
75
+ view.addEventListener('resize', queue);
76
+ view.addEventListener('scroll', queue);
77
+ write();
78
+ return () => {
79
+ view.removeEventListener('resize', queue);
80
+ view.removeEventListener('scroll', queue);
81
+ if (queued)
82
+ frame.cancelAnimationFrame(queued);
83
+ };
84
+ }
85
+ /**
86
+ * The ref for the window: it follows the visual viewport while mounted. A
87
+ * browser without one leaves the window at 100dvh.
88
+ */
89
+ export function useView() {
90
+ const stop = useRef(null);
91
+ return useCallback((node) => {
92
+ stop.current?.();
93
+ stop.current = null;
94
+ // A gui stack hands its host element through a ref typed for native too; on the web it is the element.
95
+ if (node instanceof HTMLElement && window.visualViewport)
96
+ stop.current = follow(node.style, window.visualViewport, window);
97
+ }, []);
98
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,14 +49,14 @@
49
49
  "peerDependencies": {
50
50
  "@hanzo/ai": ">=0.6.23",
51
51
  "@hanzo/appearance": ">=0.2.6",
52
- "@hanzo/build": ">=0.2.57",
52
+ "@hanzo/build": ">=0.2.61",
53
53
  "@hanzo/composer": ">=0.2.0",
54
54
  "@hanzo/event": ">=0.3.56",
55
55
  "@hanzo/gui": "^8.3.5",
56
56
  "@hanzo/iam": ">=0.21.33",
57
57
  "@hanzo/logo": ">=1.0.26",
58
58
  "@hanzo/studio": ">=0.1.0",
59
- "@hanzo/ui": ">=8.27.67",
59
+ "@hanzo/ui": ">=8.29.0",
60
60
  "@hanzo/usage": ">=0.1.18",
61
61
  "@hanzo/voice": ">=0.1.16",
62
62
  "@hanzogui/shell": "^8.3.20",
@@ -67,7 +67,7 @@
67
67
  "devDependencies": {
68
68
  "@hanzo/ai": "0.6.23",
69
69
  "@hanzo/appearance": "0.2.8",
70
- "@hanzo/build": "0.2.57",
70
+ "@hanzo/build": "0.2.61",
71
71
  "@hanzo/composer": "0.2.0",
72
72
  "@hanzo/design": "0.5.34",
73
73
  "@hanzo/event": "0.3.56",
@@ -75,7 +75,7 @@
75
75
  "@hanzo/iam": "0.21.33",
76
76
  "@hanzo/logo": "1.0.26",
77
77
  "@hanzo/studio": "0.1.0",
78
- "@hanzo/ui": "8.27.67",
78
+ "@hanzo/ui": "8.29.0",
79
79
  "@hanzo/usage": "0.1.18",
80
80
  "@hanzo/voice": "0.1.16",
81
81
  "@hanzogui/config": "8.3.5",
package/dist/Account.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /** The block at the foot of the sidebar: press it for the menu. */
2
- export declare function Account({ onToggleCollapse, onProfile, onAppearance, onUsage, onKeys, }?: {
3
- onToggleCollapse?: () => void;
4
- /** Opens the profile beside the room. Absent, the row goes to hanzo.id. */
5
- onProfile?: () => void;
6
- /** Opens the appearance settings. Absent, the row is not drawn. */
7
- onAppearance?: () => void;
8
- /** Opens the plan's usage settings. Absent, the row is not drawn. */
9
- onUsage?: () => void;
10
- /** Opens the API keys settings. Absent, the row is not drawn. */
11
- onKeys?: () => void;
12
- }): import("react").JSX.Element;
package/dist/Account.js DELETED
@@ -1,65 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- /**
4
- * WHO YOU ARE, at the foot of the sidebar: the person, and the person's menu.
5
- *
6
- * The plan leads it — @hanzo/build's `Meter` (Standing.tsx, loaded with the
7
- * menu), the block every account menu opens with: the plan by its family and its
8
- * windows as shares, the free allowance on Free, the balance only with no plan. Then profile, appearance, usage, billing,
9
- * API keys, security and sign out — the account, and nothing that belongs to the
10
- * workspace. Which workspace you stand in is the switcher at the TOP of the
11
- * sidebar, and what it may spend is its Billing: a plan never sits beside a
12
- * balance here.
13
- */
14
- import { lazy, Suspense, useEffect, useRef, useState } from 'react';
15
- import { Activity, ChevronsUpDown, CreditCard, KeyRound, LogOut, PanelLeftClose, Palette, Shield, UserRound } from 'lucide-react';
16
- import { XStack, YStack, Text, Box } from '@hanzo/ui';
17
- import { sheet } from '@hanzo/ui/glass';
18
- import { useIam } from '@hanzo/iam/react';
19
- import { useSignOut } from './lib/signout.js';
20
- import { useAccount } from './lib/account.js';
21
- import { Face } from './cast.js';
22
- import { iam } from './lib/api.js';
23
- import { link } from './lib/tags.js';
24
- import { showSettings } from './open.js';
25
- import { site } from './where.js';
26
- /** The plan at the menu's head, loaded with the menu. */
27
- const Standing = lazy(() => import('./Standing.js').then((m) => ({ default: m.Standing })));
28
- function Row({ icon, onPress, children, }) {
29
- return (_jsx(Box, { render: "button", onClick: onPress, width: "100%", px: "$3", py: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", children: [icon, _jsx(Text, { fontSize: "$2", color: "$ink", children: children })] }) }));
30
- }
31
- /** The block at the foot of the sidebar: press it for the menu. */
32
- export function Account({ onToggleCollapse, onProfile, onAppearance, onUsage, onKeys, } = {}) {
33
- const { user } = useIam();
34
- const logout = useSignOut();
35
- const { user: account } = useAccount();
36
- const [open, setOpen] = useState(false);
37
- const holder = useRef(null);
38
- useEffect(() => {
39
- if (!open)
40
- return;
41
- const away = (e) => {
42
- if (holder.current && !holder.current.contains(e.target))
43
- setOpen(false);
44
- };
45
- const esc = (e) => e.key === 'Escape' && setOpen(false);
46
- document.addEventListener('mousedown', away);
47
- document.addEventListener('keydown', esc);
48
- return () => {
49
- document.removeEventListener('mousedown', away);
50
- document.removeEventListener('keydown', esc);
51
- };
52
- }, [open]);
53
- const called = account?.name || user?.displayName || user?.name || user?.email || 'You';
54
- const email = user?.email || '';
55
- const face = account?.avatar ?? user?.avatar;
56
- const act = (fn) => () => {
57
- setOpen(false);
58
- fn();
59
- };
60
- return (_jsxs(YStack
61
- // @ts-expect-error — ref is forwarded to the DOM node by the primitive.
62
- , {
63
- // @ts-expect-error — ref is forwarded to the DOM node by the primitive.
64
- ref: holder, position: "relative", borderTopWidth: 1, borderColor: "$borderColor", children: [open ? (_jsxs(YStack, { role: "menu", "aria-label": "Account", position: "absolute", b: "calc(100% + 6px)", l: "$2", r: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", ...sheet(2), py: "$2", gap: "$1", z: "var(--z-modal)", children: [_jsx(Suspense, { fallback: null, children: _jsx(Standing, { onPlans: act(() => window.location.assign(site('/pricing'))), onBilling: act(() => showSettings('billing')) }) }), _jsx(Box, { height: 1, bg: "$borderColor", my: "$1" }), _jsx(Row, { icon: _jsx(UserRound, { size: 15, "aria-hidden": true }), onPress: act(() => (onProfile ? onProfile() : window.location.assign(link(`${iam()}/account`)))), children: "Profile" }), onAppearance ? (_jsx(Row, { icon: _jsx(Palette, { size: 15, "aria-hidden": true }), onPress: act(onAppearance), children: "Appearance" })) : null, onUsage ? (_jsx(Row, { icon: _jsx(Activity, { size: 15, "aria-hidden": true }), onPress: act(onUsage), children: "Usage" })) : null, _jsx(Row, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), onPress: act(() => showSettings('billing')), children: "Billing" }), onKeys ? (_jsx(Row, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), onPress: act(onKeys), children: "API keys" })) : null, _jsx(Row, { icon: _jsx(Shield, { size: 15, "aria-hidden": true }), onPress: act(() => window.location.assign(link(`${iam()}/account/security`))), children: "Security" }), _jsx(Box, { height: 1, bg: "$borderColor", my: "$1" }), _jsx(Row, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), onPress: act(() => void logout()), children: "Sign out" })] })) : null, _jsxs(XStack, { items: "center", width: "100%", children: [_jsx(Box, { render: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, "aria-haspopup": "menu", "aria-label": "Account", flex: 1, minW: 0, px: "$3", py: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(Face, { src: face, name: called, size: 22 }), _jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, children: called }), email && email !== called ? (_jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, children: email })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true })] }) }), onToggleCollapse ? (_jsx(Box, { render: "button", onClick: onToggleCollapse, "aria-label": "Collapse sidebar", p: "$2", mr: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(PanelLeftClose, { size: 16, "aria-hidden": true }) })) : null] })] }));
65
- }
package/dist/Find.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { type OrgCommandItem } from "@hanzogui/shell";
2
- export declare function Find({ doors, onClose, navigate, }: {
3
- /** Where the shell can go without asking anyone: its rooms and its panes. */
4
- doors: OrgCommandItem[];
5
- onClose: () => void;
6
- navigate?: (route: string) => void;
7
- }): import("react").JSX.Element;
package/dist/Find.js DELETED
@@ -1,82 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- /**
4
- * ⌘K, in the workspace.
5
- *
6
- * The estate's palette — `OrgCommandPalette`, the same frame the marketing
7
- * pages open — so the chord means one thing wherever a reader presses it. Here
8
- * it used to focus the column's filter box, which left the shortcut that finds
9
- * anything on every other page of this site narrowing one list in the app: a
10
- * conversation in the room next door was unreachable from a control offering to
11
- * search the workspace.
12
- *
13
- * MOUNTED ONLY WHILE OPEN. Its rows are read from the platform, and a palette
14
- * that stayed mounted would fetch every conversation and every project on each
15
- * load of a room that draws neither.
16
- */
17
- import { dev } from "./lib/host.js";
18
- import { useEffect, useState } from "react";
19
- import { OrgCommandPalette } from "@hanzogui/shell";
20
- import { useThreads } from "@hanzo/ai/react";
21
- import { useAi } from "./lib/ai.js";
22
- import { useOpen } from "./open.js";
23
- export function Find({ doors, onClose, navigate, }) {
24
- const { threads } = useThreads();
25
- const { client } = useAi();
26
- const { open } = useOpen();
27
- const [projects, setProjects] = useState([]);
28
- useEffect(() => {
29
- if (!client)
30
- return;
31
- let live = true;
32
- client.http
33
- .json({ path: "/v1/projects" })
34
- .then((found) => live && setProjects(found))
35
- .catch(() => {
36
- // A palette that cannot read the projects still finds the rooms.
37
- });
38
- return () => {
39
- live = false;
40
- };
41
- }, [client]);
42
- const rows = [
43
- ...doors,
44
- // A conversation and a project are OPENED, not navigated to: the address is
45
- // the room, and which one is open belongs to the store — so a href would
46
- // land the reader in /chat with whatever they had before still on screen.
47
- ...threads.map((t) => ({
48
- id: `thread-${t.id}`,
49
- title: t.title || "Untitled",
50
- category: "Conversations",
51
- action: () => {
52
- open(t.id);
53
- navigate?.("/chat");
54
- },
55
- })),
56
- ...projects.map((p) => ({
57
- id: `project-${p.id}`,
58
- title: p.name,
59
- category: "Projects",
60
- action: () => {
61
- navigate?.(dev(p.name));
62
- },
63
- })),
64
- ];
65
- return (_jsx(OrgCommandPalette, { open: true, currentAppId: "chat", commands: rows, onOpenChange: (next) => {
66
- if (!next)
67
- onClose();
68
- }, onNavigate: (href, external) => {
69
- if (external)
70
- window.open(href, "_blank", "noopener");
71
- else
72
- navigate?.(href);
73
- onClose();
74
- },
75
- // THIS ROOM IS THE AI. A question nobody indexed opens a conversation
76
- // here, rather than a second copy of the product the reader is in.
77
- onAsk: (question) => {
78
- open(null);
79
- navigate?.(`/chat?q=${encodeURIComponent(question)}`);
80
- onClose();
81
- } }));
82
- }
package/dist/Profile.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export declare function Profile({ open, onClose }: {
2
- open: boolean;
3
- onClose: () => void;
4
- }): import("react").JSX.Element;
package/dist/Profile.js DELETED
@@ -1,67 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- // The reader's own profile, in the column beside the room.
4
- //
5
- // A WORKSPACE OPENS A PROFILE IN A PANEL, not a page. The page exists — it is
6
- // Settings (/settings), where the whole record is edited — but reaching it from a
7
- // conversation costs the conversation: the room, the sidebar and the rail all
8
- // go, and coming back is a navigation rather than a close. Slack, Teams and
9
- // every app shaped like them put this in the right column for that reason, and
10
- // so does the thread view that will share this slot.
11
- //
12
- // EVERY CONTROL DOES ITS VERB. Slack's panel carries "Set a status" and "View
13
- // as" and this one does not, because IAM stores no status and there is nobody
14
- // to view as. What is here is what there is: the picture can be changed, the
15
- // record can be edited, and the rest is read.
16
- import { useRooms } from './host.js';
17
- import { site } from './where.js';
18
- import { useRef, useState } from 'react';
19
- import { Camera, Mail, Phone, X } from 'lucide-react';
20
- import { Box, Text, View, XStack, YStack } from '@hanzo/ui';
21
- import { sheet } from '@hanzo/ui/glass';
22
- import { pane } from './ground.js';
23
- import { useAccount } from './lib/account.js';
24
- import { reach } from './lib/reach.js';
25
- import { mark } from './lib/mark.js';
26
- import { BAD } from './lib/mix.js';
27
- /** Slack's is 380 and the thread panel that shares this slot is the same. */
28
- const WIDTH = 380;
29
- const ACCOUNT = '/settings';
30
- export function Profile({ open, onClose }) {
31
- const { Link } = useRooms();
32
- const { user, updateUserProfile } = useAccount();
33
- const picker = useRef(null);
34
- const [busy, setBusy] = useState(false);
35
- const [failed, setFailed] = useState(null);
36
- if (!open || !user)
37
- return null;
38
- const pick = async (file) => {
39
- if (!file)
40
- return;
41
- setBusy(true);
42
- setFailed(null);
43
- try {
44
- await updateUserProfile({ avatar: await mark(file) });
45
- }
46
- catch (e) {
47
- // In the panel rather than a toast: the picture is here, so the reason it
48
- // did not change belongs here too.
49
- setFailed(reach(e));
50
- }
51
- finally {
52
- setBusy(false);
53
- }
54
- };
55
- return (_jsxs(YStack, { ...pane(sheet(2).backgroundColor), width: WIDTH, maxW: "100%", shrink: 0, minH: 0,
56
- // A column on a laptop. On a phone the room is already the whole screen,
57
- // so this covers it rather than squeezing it to nothing.
58
- position: "absolute", t: 0, r: 0, b: 0, l: 0, z: "var(--z-panel)", $lg: { position: 'relative', z: 'var(--z-base)', l: 'auto' }, children: [_jsxs(XStack, { items: "center", px: "$4", py: "$3", gap: "$2", children: [_jsx(Text, { flex: 1, fontSize: "$5", fontWeight: "600", color: "$ink", children: "Profile" }), _jsx(Box, { render: "button", onClick: onClose, "aria-label": "Close profile", p: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflowY: "auto", overflowX: "hidden", px: "$4", pb: "$5", gap: "$4", children: [_jsx("input", { ref: picker, type: "file", accept: "image/png,image/jpeg,image/gif,image/webp", hidden: true, onChange: (e) => {
59
- const file = e.target.files?.[0];
60
- e.target.value = '';
61
- void pick(file);
62
- } }), _jsxs(Box, { render: "button", onClick: () => {
63
- if (busy)
64
- return;
65
- picker.current?.click();
66
- }, "aria-label": "Change your picture", "aria-disabled": busy, width: "100%", aspectRatio: 1, rounded: "$5", overflow: "hidden", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", position: "relative", children: [user.avatar ? (_jsx(View, { render: _jsx("img", { src: user.avatar, alt: "" }), width: "100%", height: "100%", objectFit: "cover", display: "block" })) : (_jsx(YStack, { width: "100%", height: "100%", items: "center", justify: "center", children: _jsx(Text, { fontSize: 72, fontWeight: "600", color: "$soft", children: (user.name?.trim()[0] ?? '?').toUpperCase() }) })), _jsxs(XStack, { position: "absolute", l: 0, r: 0, b: 0, items: "center", justify: "center", gap: "$2", py: "$2", bg: "$background", opacity: 0.85, children: [_jsx(Camera, { size: 14, "aria-hidden": true }), _jsx(Text, { fontSize: "$2", color: "$ink", children: busy ? 'Saving…' : user.avatar ? 'Change picture' : 'Add a picture' })] })] }), failed ? (_jsx(Text, { fontSize: "$2", color: BAD, children: failed })) : null, _jsxs(XStack, { items: "baseline", gap: "$3", children: [_jsx(Text, { flex: 1, fontSize: "$6", fontWeight: "600", color: "$ink", numberOfLines: 2, children: user.name }), _jsx(Link, { href: site(ACCOUNT), children: _jsx(Text, { fontSize: "$2", color: "$quiet", children: "Edit" }) })] }), user.bio ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: user.bio })) : null, _jsxs(YStack, { gap: "$2", pt: "$3", borderTopWidth: 1, borderColor: "$edge", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Contact information" }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Mail, { size: 14, "aria-hidden": true }), _jsx(Text, { fontSize: "$3", color: "$soft", children: user.email })] }), user.phone ? (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Phone, { size: 14, "aria-hidden": true }), _jsx(Text, { fontSize: "$3", color: "$soft", children: user.phone })] })) : null] })] })] }));
67
- }
@@ -1,4 +0,0 @@
1
- export declare function Standing({ onPlans, onBilling }: {
2
- onPlans: () => void;
3
- onBilling: () => void;
4
- }): import("react").JSX.Element;
package/dist/Standing.js DELETED
@@ -1,16 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- // The plan at the head of the account menu (Account.tsx): @hanzo/build's
4
- // `Meter` over the organization's standing, read when the menu opens. Its own
5
- // module so the menu's foot row costs a room's first screen nothing: it loads
6
- // with the menu.
7
- import { useMemo } from 'react';
8
- import { Meter, useStanding } from '@hanzo/build';
9
- import { api } from './lib/api.js';
10
- import { bearer, org } from './lib/session.js';
11
- export function Standing({ onPlans, onBilling }) {
12
- const scoped = org();
13
- const target = useMemo(() => ({ api: api(), token: bearer, org: scoped }), [scoped]);
14
- const plan = useStanding(target, true);
15
- return _jsx(Meter, { read: plan, onPlans: onPlans, onBilling: onBilling });
16
- }