Files
social/vite.config.ts
zeekayandHanzo Dev 50aa91efd4 Hanzo Social — publish to every account an org owns
A social publishing surface on @hanzo/gui + @hanzo/ui, written against the
platform's own /v1/social and /v1/integration. Six screens: the org roll-up,
a composer, the posts in every state, a month calendar, the networks with
their consent and their handles, and the delivery record.

Two facts shape it. A post carries one channel, so the composer stores one
post per network — which is also what makes writing separately for a single
network free, since it is a different content on that row. Media is a list of
addresses, so a video is ordinary content rather than a second kind of post,
and each network's own rules are checked before the post is stored rather
than discovered at a publish hours later.

State is drawn in luminance on a palette with no hues, and the inversion is
spent once per screen on the thing a person has to act on. Refusals are the
platform's own sentence, read off the RFC 7807 detail the SDK's extractor
drops — a 503 that names the missing credentials is the whole of what anyone
can act on.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-15 06:40:41 -07:00

88 lines
3.5 KiB
TypeScript

import { hanzo } from '@hanzo/vite'
import react from '@vitejs/plugin-react'
/**
* The bundler. Resolving the Hanzo runtime — `~/…`, react-native-web, web-first
* extensions, one copy of gui — comes from `hanzo()`, stated once for every
* Hanzo app. What is left is this app's own.
*
* There is no CSS pipeline, and none is needed: @hanzo/ui ships one generated
* stylesheet that `<Hanzo>` imports, and gui inserts the rules for props a
* package could not have known about at first render.
*/
const API = 'https://api.hanzo.ai'
/**
* Same-origin in front of the gateway, on the dev server and on the preview of
* a build. api.hanzo.ai admits an origin by allowlist and by an https, portless
* DNS proof; a localhost port satisfies neither, so a credentialed read to the
* absolute address fails its preflight and every screen draws as though the org
* had never connected an account. One statement, both servers — a proxy that
* exists only under `vite dev` means the built output is never exercised
* against real data before it ships.
*/
const proxy = { '/v1': { target: API, changeOrigin: true } }
/**
* The port is pinned because it is half of a redirect. IAM returns a browser to
* the `redirect_uri` the client registered, and that URI carries the port — so a
* port the dev server picked because 3300 was busy is a port the issuer refuses
* to return to, and the sign-in ends on an error page instead of this app.
*/
const PORT = 3300
/**
* A single-page app has one document and many addresses, so every address that
* is not a file has to arrive at that document. `ghcr.io/hanzoai/spa` answers
* index.html for any route; a static plane resolves an object key and otherwise
* answers the site's own `404.html`, so emitting the document under that name
* is the whole fix and every static host already implements it.
*
* The status stays 404: the browser runs the bundle regardless and the router
* takes the address from there, while a crawler reads "not a page I publish",
* which is true of every address the router invents.
*/
const spaFallback = {
name: 'spa-fallback',
async writeBundle(options: { dir?: string }) {
const { copyFile } = await import('node:fs/promises')
const { join } = await import('node:path')
const dir = options.dir ?? 'dist'
await copyFile(join(dir, 'index.html'), join(dir, '404.html'))
},
}
const config = hanzo(
{
plugins: [react(), spaFallback],
server: { port: PORT, allowedHosts: true, proxy },
preview: { port: PORT, allowedHosts: true, proxy },
},
{
root: import.meta.dirname,
// ONE COPY OF THE THEME. `hanzo()` dedupes `@hanzo/gui` and `@hanzo/ui`, but
// the theme and config contexts live a layer below them in `@hanzogui/core`.
// A component rendered under one context reading the other throws
// "Missing theme."
dedupe: ['@hanzogui/core', '@hanzogui/web', '@hanzogui/portal', '@hanzogui/toast'],
},
)
/**
* Dependency optimization is a SECOND resolution pass and inherits none of the
* first, so it is told the same thing: on the web, a react-native package's
* `.web.js` sibling comes first. Left out, the optimizer follows react-native
* into its Flow source and the dev server dies at startup, while `vite build`
* uses the resolver above and succeeds.
*
* The list is read back from the config rather than restated.
*/
export default {
...config,
optimizeDeps: {
rollupOptions: {
resolve: { extensions: (config.resolve as { extensions: string[] }).extensions },
},
},
}