feat: add initial webapp skeleton boilerplate

pull/715/head
Travis Fischer 2025-06-14 04:20:55 +07:00
rodzic 5a9c2af4d3
commit c785f8ca1a
22 zmienionych plików z 5451 dodań i 31 usunięć

1
.gitignore vendored
Wyświetl plik

@ -50,3 +50,4 @@ apps/api/auth-db-temp.json
.wrangler
.sentryclirc
.eslintcache
.nitro

Wyświetl plik

@ -1,3 +1,3 @@
# autogenerated file
# autogenerated files
packages/types/src/openapi.d.ts
packages/apps/gateway/src/worker.d.ts
apps/web/src/routeTree.gen.ts

Wyświetl plik

@ -30,7 +30,6 @@
},
"dependencies": {
"@agentic/json-schema": "workspace:*",
"@agentic/platform": "workspace:*",
"@agentic/platform-api-client": "workspace:*",
"@agentic/platform-core": "workspace:*",
"@agentic/platform-hono": "workspace:*",

Wyświetl plik

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"iconLibrary": "lucide",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

Wyświetl plik

@ -0,0 +1 @@
9

Wyświetl plik

@ -0,0 +1,51 @@
{
"name": "web",
"private": true,
"version": "0.1.0",
"description": "Agentic platform webapp.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "UNLICENSED",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git",
"directory": "apps/web"
},
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"clean": "del dist",
"test": "run-s test:*",
"test:typecheck": "tsc --noEmit",
"test:unit": "vitest run"
},
"dependencies": {
"@agentic/platform": "workspace:*",
"@agentic/platform-api-client": "workspace:*",
"@agentic/platform-core": "workspace:*",
"@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*",
"@tanstack/react-router": "^1.121.2",
"@tanstack/react-router-devtools": "^1.121.8",
"@tanstack/react-start": "^1.121.10",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.515.0",
"react": "catalog:",
"react-dom": "catalog:",
"stripe": "catalog:",
"tailwind-merge": "^3.3.1",
"type-fest": "catalog:",
"vite": "^6.3.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.5",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"vite-tsconfig-paths": "catalog:"
}
}

Wyświetl plik

@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {}
}
}

Wyświetl plik

@ -0,0 +1,55 @@
import {
ErrorComponent,
type ErrorComponentProps,
Link,
rootRouteId,
useMatch,
useRouter
} from '@tanstack/react-router'
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
const router = useRouter()
const isRoot = useMatch({
strict: false,
select: (state) => state.id === rootRouteId
})
console.error('DefaultCatchBoundary Error:', error)
return (
<div className='min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6'>
<ErrorComponent error={error} />
<div className='flex gap-2 items-center flex-wrap'>
<button
onClick={() => {
void router.invalidate()
}}
className='px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold'
>
Try Again
</button>
{isRoot ? (
<Link
to='/'
className='px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold'
>
Home
</Link>
) : (
<Link
to='/'
className='px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold'
onClick={(e) => {
e.preventDefault()
globalThis.history.back()
}}
>
Go Back
</Link>
)}
</div>
</div>
)
}

Wyświetl plik

@ -0,0 +1,27 @@
import { Link } from '@tanstack/react-router'
export function NotFound({ children }: { children?: any }) {
return (
<div className='space-y-2 p-2'>
<div className='text-gray-600 dark:text-gray-400'>
{children || <p>The page you are looking for does not exist.</p>}
</div>
<p className='flex items-center gap-2 flex-wrap'>
<button
onClick={() => globalThis.history.back()}
className='bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm'
>
Go back
</button>
<Link
to='/'
className='bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm'
>
Start Over
</Link>
</p>
</div>
)
}

Wyświetl plik

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

3
apps/web/src/reset.d.ts vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
/// <reference types="vite/client" />
import '@fisch0920/config/ts-reset'

Wyświetl plik

@ -0,0 +1,59 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

Wyświetl plik

@ -0,0 +1,20 @@
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { NotFound } from './components/not-found'
import { routeTree } from './routeTree.gen'
export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
defaultNotFoundComponent: () => <NotFound />
})
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}

Wyświetl plik

@ -0,0 +1,60 @@
import type { ReactNode } from 'react'
import {
createRootRoute,
HeadContent,
Outlet,
Scripts
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import globalStyles from '@/styles/global.css?url'
export const Route = createRootRoute({
head: () => ({
meta: [
{
// eslint-disable-next-line unicorn/text-encoding-identifier-case
charSet: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
title: 'Agentic'
}
],
links: [
{
rel: 'stylesheet',
href: globalStyles
}
]
}),
component: RootComponent
})
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang='en'>
<head>
<HeadContent />
</head>
<body>
{children}
<TanStackRouterDevtools position='bottom-right' />
<Scripts />
</body>
</html>
)
}

Wyświetl plik

@ -0,0 +1,46 @@
import * as fs from 'node:fs/promises'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const filePath = 'count.txt'
async function readCount() {
return Number.parseInt(await fs.readFile(filePath, 'utf8').catch(() => '0'))
}
const getCount = createServerFn({
method: 'GET'
}).handler(() => {
return readCount()
})
const updateCount = createServerFn({ method: 'POST' })
.validator((d: number) => d)
.handler(async ({ data }) => {
const count = await readCount()
await fs.writeFile(filePath, `${count + data}`)
})
export const Route = createFileRoute('/')({
component: Home,
loader: async () => getCount()
})
function Home() {
const router = useRouter()
const state = Route.useLoaderData()
return (
<button
type='button'
onClick={() => {
void updateCount({ data: 1 }).then(() => {
return router.invalidate()
})
}}
>
Add 1 to {state}?
</button>
)
}

Wyświetl plik

@ -0,0 +1,123 @@
@import 'tailwindcss' source('../');
@import 'tw-animate-css';
@import 'tailwindcss/preflight';
@import 'tailwindcss/utilities';
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

Wyświetl plik

@ -0,0 +1,5 @@
import type { Config } from 'tailwindcss'
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}']
} satisfies Config

Wyświetl plik

@ -0,0 +1,14 @@
{
"extends": "@fisch0920/config/tsconfig-react",
"compilerOptions": {
// https://tanstack.com/start/latest/docs/framework/react/build-from-scratch#typescript-configuration
"verbatimModuleSyntax": false,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src", "*.config.ts"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -0,0 +1,15 @@
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
server: {
port: 3001
},
plugins: [
tsConfigPaths(),
tanstackStart({
target: 'vercel'
})
]
})

Wyświetl plik

@ -9,7 +9,8 @@ export default [
'packages/types/src/openapi.d.ts',
'apps/gateway/src/worker.d.ts',
'packages/json-schema/test/json-schema-test-suite.ts',
'apps/gateway/.wrangler'
'apps/gateway/.wrangler',
'apps/web/src/*.gen.ts'
]
},
{
@ -39,5 +40,11 @@ export default [
rules: {
...drizzle.configs.recommended.rules
}
},
{
files: ['apps/web/src/**/*.{tsx,ts}'],
rules: {
'no-console': 'off'
}
}
]

Plik diff jest za duży Load Diff

Wyświetl plik

@ -93,6 +93,7 @@
- support multiple rate-limits by slug
- RateLimit-Policy: "burst";q=100;w=60,"daily";q=1000;w=86400
- https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/
- make `$schema` public for `agentic.config.json`
## License