mirror of
https://github.com/hanzoai/social.git
synced 2026-10-17 11:18:21 +00:00
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]>
88 lines
3.5 KiB
TypeScript
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 },
|
|
},
|
|
},
|
|
}
|