Files
hanzo.ai/app/dev/_door.tsx
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

91 lines
3.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
// /dev, and by the edge every path under it (lib/edge.ts `APP`): Dev. Its
// address is the path — `/dev/run/<id>` a run, `/dev/projects/<org>/<repo>` a
// project, `/dev/<place>` every other place (@hanzo/app lib/host `app`,
// `place`).
//
// signed in the builder: the app (app/_web.tsx) in Dev, in the rooms' frame
// (components/rooms `Room`). A retired address is replaced by
// its path first, once, so Back does not land on it (`moved`);
// one whose page is not the app's (a builder Settings section is
// the site's /settings, @hanzo/app `settings`) is loaded instead.
// signed out bare `/dev` is Dev's page, the landing the export holds for
// crawlers and strangers; its Open Dev signs in and comes back.
// Any other address (./_away `away`) signs in first
// (destination.ts `enter`): the address is written down on the
// way out, so the sign-in returns to it and the first case takes
// over.
//
// The deciding is `Apex`'s (app/_apex.tsx), the door `/` is: the landing until
// hydration, then IAM's answer, the marks before paint both ways.
import { useEffect, useState, type ReactNode } from 'react'
import dynamic from 'next/dynamic'
import { useIam } from '@hanzo/iam/react'
import { path, route } from '@hanzo/build'
import { app, DEV, place } from '@hanzo/app/lib/host'
import { Room } from '@/components/app'
import { Apex } from '../_apex'
import { away } from './_away'
/** The app, fetched only for a signed-in reader. */
const App = dynamic(() => import('../_web'), { ssr: false })
/**
* The path a retired Dev address names, canonical; null when it is current.
*
* Retired: a query from before Dev had paths — `?at=` in the builder's grammar
* (what hanzo.build's addresses arrive as), or `?run=`, `?project=` or `?view=`
* (the console's and shared links') — and the builder's own grammar that
* @hanzo/app 0.1.35–0.1.38 wrote under /dev: `/dev/sess_<id>`,
* `/dev/-/<place>`, `/dev/<org>/<repo>`, `/dev/<slug>`. /dev/settings is not
* one: the edge answers it with a 301 to /settings (lib/publish.ts `MOVED`).
*/
export function moved(pathname: string, q: URLSearchParams): string | null {
const at = q.get('at')
if (at !== null) return app(path(route(at)))
const view = q.get('view')
const ref = q.get('run') ?? q.get('project') ?? (view ? `-/${view}` : null)
if (ref !== null) return app(path(route(ref)))
const rest = pathname.slice(DEV.length + 1)
const now = place(pathname)
if (!rest || (now?.mode === 'dev' && app(now.at) === pathname)) return null
return route(rest).kind === 'new' ? null : app(rest)
}
/** Signed in: the builder once IAM has settled, an old query replaced by its path first. */
function Builder() {
const { isLoading } = useIam()
const [ready, setReady] = useState(false)
useEffect(() => {
if (isLoading) return
const to = moved(window.location.pathname, new URLSearchParams(window.location.search))
if (to && !place(to)) {
window.location.replace(to)
return
}
if (to) window.history.replaceState(null, '', to)
setReady(true)
}, [isLoading])
return ready ? <App /> : null
}
/** Whether this address is a place in the builder, read once the browser has one. */
const placed = (): boolean => typeof window !== 'undefined' && away(window.location)
export function Door({ children }: { children: ReactNode }) {
return (
<Apex
placed={placed}
app={
<Room>
<Builder />
</Room>
}
>
{children}
</Apex>
)
}