@hanzo/rooms 0.1.34 → 0.1.35

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/README.md CHANGED
@@ -11,13 +11,13 @@ import { CalScheduler } from '@hanzo/rooms/CalScheduler'
11
11
 
12
12
  configure({ site: '', home: '/home', signUp: '/signup' })
13
13
 
14
- <Rooms router={router} search={search} route={route} Link={Link}>
14
+ <Rooms router={router} search={search} path={pathname} route={route} Link={Link}>
15
15
  <Room mode="cal"><CalScheduler /></Room>
16
16
  </Rooms>
17
17
  ```
18
18
 
19
19
  `configure()` names the host's addresses (where its own pages live, the
20
20
  gateway and IAM, the plan catalogue to paint first). `<Rooms>` carries what
21
- moves: the router, the query, the route and the link component. The rooms
21
+ moves: the router, the query, the path, the route and the link component. The rooms
22
22
  import nothing from a host, and their look comes from `@hanzo/appearance` and
23
23
  `@hanzo/design` through `@hanzo/ui`.
package/dist/dev.js CHANGED
@@ -7,12 +7,12 @@
7
7
  // to leave. The name drawn at the top left is Hanzo Dev: this host is the
8
8
  // dev agent. Chat, on the rooms shell, is Hanzo AI.
9
9
  //
10
- // THE ADDRESS IS THE QUERY. The app is `/` (app/page.tsx), and what it shows
11
- // rides `?at=` in the builder's own grammar (lib/host.ts `AT`), so a run, a
10
+ // THE ADDRESS IS THE PATH. Dev is `/dev`, and what it shows is the path under
11
+ // it in the builder's own grammar (lib/host.ts `app`, `under`), so a run, a
12
12
  // screen, a Settings section, a Customize tab, the plans and a project all
13
13
  // survive a reload, and Back steps through them. A move is a native history
14
- // entry, which the Next router folds into `useSearchParams`, so reading the
15
- // address and moving it are one query.
14
+ // entry, which the Next router folds into `usePathname`, so reading the address
15
+ // and moving it are one path.
16
16
  import { useMemo } from 'react';
17
17
  import { useLook, useRooms } from './host.js';
18
18
  import { useIam } from '@hanzo/iam/react';
@@ -21,7 +21,7 @@ import { administers, path, route } from '@hanzo/build';
21
21
  import { bearer, named, org, orgs, pick } from './lib/session.js';
22
22
  import { enter } from './lib/destination.js';
23
23
  import { api } from './lib/api.js';
24
- import { app, AT } from './lib/host.js';
24
+ import { app, under } from './lib/host.js';
25
25
  import { link } from './lib/tags.js';
26
26
  /** Where the builder links out: every one of them an address of this site. */
27
27
  const LINKS = {
@@ -32,14 +32,14 @@ const LINKS = {
32
32
  };
33
33
  /** The person's theme, when they have chosen one the builder can name. */
34
34
  const theme = (t) => (t === 'light' || t === 'dark' || t === 'system' ? t : undefined);
35
- /** The app address a query value names, canonical; '' for anything that is not one. */
35
+ /** The app address a builder path names, canonical; '' for anything that is not one. */
36
36
  const canonical = (at) => path(route(at ?? ''));
37
37
  /** The host for a signed-in reader. */
38
38
  export function useDevHost() {
39
39
  const { user, accessToken } = useIam();
40
40
  const logout = useSignOut();
41
- const { router, search } = useRooms();
42
- const here = canonical(search?.get(AT) ?? null);
41
+ const { router, path: pathname } = useRooms();
42
+ const here = canonical(under(pathname));
43
43
  const look = useLook();
44
44
  // The token's claims change only when the token does: a sign-in, a refresh.
45
45
  const memberships = useMemo(() => orgs(), [accessToken]); // eslint-disable-line react-hooks/exhaustive-deps
@@ -70,7 +70,7 @@ export function useDevHost() {
70
70
  path: here,
71
71
  go: (to, how) => {
72
72
  const next = app(canonical(to));
73
- if (`${window.location.pathname}${window.location.search}` === next)
73
+ if (window.location.pathname === next && !window.location.search)
74
74
  return;
75
75
  if (how?.replace)
76
76
  window.history.replaceState(null, '', next);
package/dist/host.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * configure({ site: 'https://hanzo.ai', home: '/', signUp: '/start' })
9
9
  *
10
- * <Rooms router={useRouter()} search={useSearchParams()} route={route} Link={Link}>
10
+ * <Rooms router={useRouter()} search={useSearchParams()} path={usePathname()} route={route} Link={Link}>
11
11
  * <Room mode="cal" />
12
12
  * </Rooms>
13
13
  *
@@ -17,7 +17,7 @@
17
17
  * any component and on pages the rooms never draw. So the host states them once
18
18
  * with `configure()`, in a module its root imports, before anything renders.
19
19
  *
20
- * The ROUTER, the query and the route change as the page moves, so they ride
20
+ * The ROUTER, the query, the path and the route change as the page moves, so they ride
21
21
  * React context, and a room reads them with `useRooms()`.
22
22
  */
23
23
  import { type AnchorHTMLAttributes, type ComponentType, type ReactNode } from 'react';
@@ -34,14 +34,18 @@ export interface Router {
34
34
  }
35
35
  interface Moving {
36
36
  router: Router;
37
- /** The address's query, for the rooms that read one (Dev's `?at=`). */
37
+ /** The address's query, for the rooms that read one (Chat's `?chat=`). */
38
38
  search: URLSearchParams | null;
39
+ /** The address's path, for the rooms that read one (Dev's, under `/dev`). */
40
+ path: string | null;
39
41
  route: string;
40
42
  Link: Link;
41
43
  }
42
- export interface RoomsProps extends Omit<Moving, 'search'> {
44
+ export interface RoomsProps extends Omit<Moving, 'search' | 'path'> {
43
45
  /** Absent where no mounted room reads the query, so a static export needs no Suspense for it. */
44
46
  search?: URLSearchParams | null;
47
+ /** Absent where no mounted room reads the path. */
48
+ path?: string | null;
45
49
  children?: ReactNode;
46
50
  }
47
51
  /** The router, the query, the route and the link the host mounted the rooms with. */
@@ -72,5 +76,5 @@ export declare function useLook(): {
72
76
  * keeps their choice in step with IAM.
73
77
  */
74
78
  export declare function Look(): any;
75
- export declare function Rooms({ router, search, route, Link, children }: RoomsProps): import("react").JSX.Element;
79
+ export declare function Rooms({ router, search, path, route, Link, children }: RoomsProps): import("react").JSX.Element;
76
80
  export {};
package/dist/host.js CHANGED
@@ -9,7 +9,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
9
9
  *
10
10
  * configure({ site: 'https://hanzo.ai', home: '/', signUp: '/start' })
11
11
  *
12
- * <Rooms router={useRouter()} search={useSearchParams()} route={route} Link={Link}>
12
+ * <Rooms router={useRouter()} search={useSearchParams()} path={usePathname()} route={route} Link={Link}>
13
13
  * <Room mode="cal" />
14
14
  * </Rooms>
15
15
  *
@@ -19,7 +19,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
19
19
  * any component and on pages the rooms never draw. So the host states them once
20
20
  * with `configure()`, in a module its root imports, before anything renders.
21
21
  *
22
- * The ROUTER, the query and the route change as the page moves, so they ride
22
+ * The ROUTER, the query, the path and the route change as the page moves, so they ride
23
23
  * React context, and a room reads them with `useRooms()`.
24
24
  */
25
25
  import { createContext, useContext } from 'react';
@@ -35,6 +35,7 @@ const Context = createContext({
35
35
  forward: () => window.history.forward(),
36
36
  },
37
37
  search: null,
38
+ path: null,
38
39
  route: '/',
39
40
  Link: Plain,
40
41
  });
@@ -71,6 +72,6 @@ export function Look() {
71
72
  useLook();
72
73
  return null;
73
74
  }
74
- export function Rooms({ router, search = null, route, Link, children }) {
75
- return _jsx(Context.Provider, { value: { router, search, route, Link }, children: children });
75
+ export function Rooms({ router, search = null, path = null, route, Link, children }) {
76
+ return _jsx(Context.Provider, { value: { router, search, path, route, Link }, children: children });
76
77
  }
@@ -30,30 +30,35 @@ export declare const ENTRY = "https://hanzo.team/?signin";
30
30
  */
31
31
  export declare const START = "https://hanzo.team/start";
32
32
  /**
33
- * The web app, at `/` for a signed-in reader, in two modes. Chat is the default.
34
- * Dev is @hanzo/build's `<Builder>`, and what it shows rides ONE query, `?at=`,
35
- * in the builder's own grammar (`route` in @hanzo/build): '' New, `sess_<id>` a
36
- * run, `-/<screen>`, `-/settings[/<section>]`, `-/customize[/<tab>]`,
37
- * `-/plans`, `<slug>` a project. A query and not a path, because a static
38
- * export has no server to answer a path under `/`.
33
+ * The web app, for a signed-in reader, in two modes. Chat is the default, at
34
+ * `/`. Dev is @hanzo/build's `<Builder>` at `/dev`, and what it shows is the
35
+ * PATH under it, in the builder's own grammar (`route` in @hanzo/build): `/dev`
36
+ * New, `/dev/sess_<id>` a run, `/dev/<org>/<repo>` a project,
37
+ * `/dev/-/<screen>`, `/dev/-/settings[/<section>]`, `/dev/-/customize[/<tab>]`,
38
+ * `/dev/<slug>` a deployed site. The export holds one page for all of them,
39
+ * `/dev`, and the edge serves it for every path under it (hanzo.ai's
40
+ * lib/edge.ts `APP`).
39
41
  */
40
- export declare const AT = "at";
41
- /** `/` in Dev at an app address; '' is Dev's New, `/?at=`. The key is the mode. */
42
+ export declare const DEV = "/dev";
43
+ /** Dev at an app address; '' is Dev's New, `/dev` itself. */
42
44
  export declare const app: (path: string) => string;
45
+ /** The app address a pathname names under `/dev`, or null for a pathname that is not Dev's. */
46
+ export declare const under: (pathname: string | null | undefined) => string | null;
43
47
  /**
44
48
  * Chat, the app's default mode: `/` is a new conversation and `?chat=<id>` one
45
- * of the reader's. So the address says which mode the app is in — `at` for Dev,
46
- * anything else Chat — and a reload keeps it.
49
+ * of the reader's. So the address says which mode the app is in — a path under
50
+ * `/dev` for Dev, anything else Chat — and a reload keeps it.
47
51
  */
48
52
  export declare const TALK = "chat";
49
53
  /** `/` in Chat, on a conversation, or `/` itself for a new one. */
50
54
  export declare const talk: (id?: string | null) => string;
51
55
  /**
52
- * The door opened on one run or project: `?run=` or `?project=`, the query the
53
- * door turns into `?at=` — and the one the console and shared links already
54
- * carry. Only a value that is exactly a session id or a project slug is
55
- * carried; anything else opens /dev itself, so no caller can make this name
56
- * another address.
56
+ * Dev opened on one run, project or site, from anywhere: every "Try Hanzo Dev"
57
+ * link, a room's Dev, the console's "open in the builder". It signs a stranger
58
+ * in on the way (app/(app)/dev/_door.tsx), and is reached through `site()`:
59
+ * here on hanzo.ai, across to hanzo.ai from hanzo.team. Only a value that is
60
+ * exactly a session id, a repository or a site's slug is carried; anything else
61
+ * opens /dev itself, so no caller can make this name another address.
57
62
  */
58
63
  export declare const dev: (ref?: string | null) => string;
59
64
  /**
package/dist/lib/host.js CHANGED
@@ -32,44 +32,59 @@ export const ENTRY = `${WORKSPACE}/?signin`;
32
32
  */
33
33
  export const START = `${WORKSPACE}/start`;
34
34
  /**
35
- * The web app, at `/` for a signed-in reader, in two modes. Chat is the default.
36
- * Dev is @hanzo/build's `<Builder>`, and what it shows rides ONE query, `?at=`,
37
- * in the builder's own grammar (`route` in @hanzo/build): '' New, `sess_<id>` a
38
- * run, `-/<screen>`, `-/settings[/<section>]`, `-/customize[/<tab>]`,
39
- * `-/plans`, `<slug>` a project. A query and not a path, because a static
40
- * export has no server to answer a path under `/`.
35
+ * The web app, for a signed-in reader, in two modes. Chat is the default, at
36
+ * `/`. Dev is @hanzo/build's `<Builder>` at `/dev`, and what it shows is the
37
+ * PATH under it, in the builder's own grammar (`route` in @hanzo/build): `/dev`
38
+ * New, `/dev/sess_<id>` a run, `/dev/<org>/<repo>` a project,
39
+ * `/dev/-/<screen>`, `/dev/-/settings[/<section>]`, `/dev/-/customize[/<tab>]`,
40
+ * `/dev/<slug>` a deployed site. The export holds one page for all of them,
41
+ * `/dev`, and the edge serves it for every path under it (hanzo.ai's
42
+ * lib/edge.ts `APP`).
41
43
  */
42
- export const AT = 'at';
43
- /** `/` in Dev at an app address; '' is Dev's New, `/?at=`. The key is the mode. */
44
- export const app = (path) => `/?${AT}=${path}`;
44
+ export const DEV = '/dev';
45
+ /** Dev at an app address; '' is Dev's New, `/dev` itself. */
46
+ export const app = (path) => {
47
+ const p = path.replace(/^\/+|\/+$/g, '');
48
+ return p ? `${DEV}/${p}` : DEV;
49
+ };
50
+ /** The app address a pathname names under `/dev`, or null for a pathname that is not Dev's. */
51
+ export const under = (pathname) => {
52
+ if (!pathname)
53
+ return null;
54
+ if (pathname === DEV || pathname === `${DEV}/`)
55
+ return '';
56
+ if (!pathname.startsWith(`${DEV}/`))
57
+ return null;
58
+ try {
59
+ return decodeURIComponent(pathname.slice(DEV.length + 1));
60
+ }
61
+ catch {
62
+ return '';
63
+ }
64
+ };
45
65
  /**
46
66
  * Chat, the app's default mode: `/` is a new conversation and `?chat=<id>` one
47
- * of the reader's. So the address says which mode the app is in — `at` for Dev,
48
- * anything else Chat — and a reload keeps it.
67
+ * of the reader's. So the address says which mode the app is in — a path under
68
+ * `/dev` for Dev, anything else Chat — and a reload keeps it.
49
69
  */
50
70
  export const TALK = 'chat';
51
71
  /** `/` in Chat, on a conversation, or `/` itself for a new one. */
52
72
  export const talk = (id) => (id ? `/?${TALK}=${encodeURIComponent(id)}` : '/');
53
- /**
54
- * The door into the app: every "Try Hanzo Dev" link, the rail's Dev, the
55
- * console's "open in the builder", dev.hanzo.ai. It signs a stranger in and
56
- * forwards a member to Dev in the app, `/?at=` (app/(app)/dev/_door.tsx). It is
57
- * the app's page, so it is reached through `site()`: here on hanzo.ai, across
58
- * to hanzo.ai from hanzo.team.
59
- */
60
- const DEV = '/dev';
61
73
  /** A run, as the platform mints its id. */
62
74
  const SESSION = /^sess_[0-9a-f]{32}$/;
63
- /** A project, as the Sites plane slugs it. */
75
+ /** A deployed site, as the Sites plane slugs it. */
64
76
  const SLUG = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;
77
+ /** A repository on the forge, `<org>/<name>`. */
78
+ const REPO = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
65
79
  /**
66
- * The door opened on one run or project: `?run=` or `?project=`, the query the
67
- * door turns into `?at=` — and the one the console and shared links already
68
- * carry. Only a value that is exactly a session id or a project slug is
69
- * carried; anything else opens /dev itself, so no caller can make this name
70
- * another address.
80
+ * Dev opened on one run, project or site, from anywhere: every "Try Hanzo Dev"
81
+ * link, a room's Dev, the console's "open in the builder". It signs a stranger
82
+ * in on the way (app/(app)/dev/_door.tsx), and is reached through `site()`:
83
+ * here on hanzo.ai, across to hanzo.ai from hanzo.team. Only a value that is
84
+ * exactly a session id, a repository or a site's slug is carried; anything else
85
+ * opens /dev itself, so no caller can make this name another address.
71
86
  */
72
- export const dev = (ref) => site(!ref ? DEV : SESSION.test(ref) ? `${DEV}?run=${ref}` : SLUG.test(ref) ? `${DEV}?project=${ref}` : DEV);
87
+ export const dev = (ref) => site(ref && (SESSION.test(ref) || REPO.test(ref) || SLUG.test(ref)) ? app(ref) : DEV);
73
88
  /** The routes under app/(app) the apex keeps: the app — /chat, a chat shared by link, and /dev. */
74
89
  const KEPT = new Set(['/chat', '/chat/shared', '/dev']);
75
90
  /** Settings, and every page under it: the person's and the organization's, served wherever the app is. */
@@ -14,7 +14,7 @@
14
14
  * who was signed in, which reads as a permissions problem rather than a typo.
15
15
  * Two derivations of one name never agree; this is the one derivation.
16
16
  */
17
- import { AT, TALK } from './host.js';
17
+ import { TALK } from './host.js';
18
18
  import { api } from './api.js';
19
19
  /** The SDK's own keys, `hanzo_iam_`-prefixed. The far side of a boundary. */
20
20
  const PREFIX = 'hanzo_iam_';
@@ -59,12 +59,13 @@ export function live() {
59
59
  /** The attribute on `<html>` that says `/` is showing the app, not the landing. */
60
60
  export const APP = 'data-app';
61
61
  /**
62
- * An address on `/` that names a place in the app — `?at=` in Dev, `?chat` in
63
- * Chat (lib/host.ts `AT`, `TALK`). The app is signed-in only, so a reader
64
- * nobody has signed in is sent to hanzo.id with it rather than shown the
65
- * landing, and it comes back with them.
62
+ * An address on `/` that names a place in the app — `?chat` in Chat
63
+ * (lib/host.ts `TALK`; Dev's places are paths under `/dev`, whose own page
64
+ * signs a stranger in). The app is signed-in only, so a reader nobody has
65
+ * signed in is sent to hanzo.id with it rather than shown the landing, and it
66
+ * comes back with them.
66
67
  */
67
- const PLACE = new RegExp(`[?&](?:${AT}|${TALK})(?:[=&]|$)`);
68
+ const PLACE = new RegExp(`[?&]${TALK}(?:[=&]|$)`);
68
69
  /** Whether `search` names a place in the app. */
69
70
  export const place = (search) => PLACE.test(search);
70
71
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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",