feat(web): scaffold client-side rendered SPA foundation
- feat: initialize Vite project with React 19, React Router 8, TanStack Query, Ant Design 6, and Tailwind CSS 4 - feat: add Oxlint with React/TypeScript rules, strict TypeScript config, and layered CSS import order - docs: add decision record for client-rendered Web foundation with React, Ant Design, and Tailwind CSS - docs: create web roadmap defining architecture boundaries and deferred dependencies - build: pin Node.js 24 in mise.toml
This commit is contained in:
@@ -106,3 +106,21 @@
|
||||
- REST remains a handler/API concern, and future protocols can reuse services without HTTP leakage.
|
||||
- Error responses keep the same top-level shape, with optional `log_id` instead of embedding log references in the message.
|
||||
- Admin and auth middleware behavior is testable through service contracts rather than database access.
|
||||
|
||||
## 2026-07-14: Client-rendered Web Foundation
|
||||
|
||||
**Context**: MyGO needs a browser client now and native clients later. The Web application must share the versioned REST API instead of introducing browser-only server logic.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| Rendering | Pure client-side rendered SPA | Vite emits static assets; do not introduce SSR, React Server Components, or a Node API server. |
|
||||
| Application stack | React, strict TypeScript, React Router, and TanStack Query | Keep routing and remote-data state explicit and client-side. |
|
||||
| UI system | Ant Design plus Tailwind CSS 4 | Ant Design owns reusable controls and theme tokens; Tailwind initially owns layout, spacing, and responsive utilities. |
|
||||
| Dependency policy | Install capabilities when their feature starts | Keep API generation, transfer, virtualization, drag-and-drop, test, and preview libraries deferred in `docs/web-roadmap.md`. |
|
||||
|
||||
**Consequences**:
|
||||
- The Web and future native clients consume the same client-neutral API contracts.
|
||||
- MyGO or a reverse proxy may host `web/dist` with an SPA fallback without changing the rendering model.
|
||||
- MyGO domain components own file-browser behavior and must not depend on Ant Design request behavior for business logic.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Web Roadmap
|
||||
|
||||
## Product Boundary
|
||||
|
||||
- The Web client is a pure client-side rendered single-page application.
|
||||
- Vite produces static assets only. The project does not use SSR, React Server Components, a Node API server, or a browser-specific business backend.
|
||||
- The Web client consumes the same versioned REST API as future Android and other native clients.
|
||||
- Production may serve `web/dist` from MyGO or a reverse proxy.
|
||||
- Shared API contracts should remain client-neutral and eventually be described by OpenAPI.
|
||||
|
||||
## Foundation
|
||||
|
||||
The initial project contains only the framework and styling foundation:
|
||||
|
||||
- Node.js 24 and npm
|
||||
- Vite
|
||||
- React
|
||||
- TypeScript in strict mode
|
||||
- TanStack Query provider
|
||||
- Tailwind CSS 4 through its Vite plugin
|
||||
- Ant Design provider and components
|
||||
- Oxlint from the Vite template
|
||||
|
||||
Ant Design owns reusable UI components and theme tokens. Tailwind CSS is initially limited to application layout, spacing, and responsive utilities.
|
||||
|
||||
## Planned Structure
|
||||
|
||||
```text
|
||||
web/src/
|
||||
├── app/ # Router, providers, and application composition
|
||||
├── api/ # API client and generated contracts
|
||||
├── components/ # Shared presentation components
|
||||
├── features/ # Auth, files, account, and admin features
|
||||
├── pages/ # Route entry points
|
||||
├── lib/ # Framework-independent utilities
|
||||
└── test/ # Shared test setup and fixtures
|
||||
```
|
||||
|
||||
## Deferred Dependencies
|
||||
|
||||
Some dependencies are suggested for future implementation. Refer these only when the corresponding feature is implemented and propose better choices if any:
|
||||
|
||||
- `@ant-design/icons`: Add Ant Design-consistent application and action icons when real screens require them.
|
||||
- `openapi-typescript`: Generate TypeScript API types from the shared OpenAPI document.
|
||||
- `openapi-fetch`: Provide a small type-safe Fetch client based on generated OpenAPI types.
|
||||
- `openapi-react-query`: Connect generated OpenAPI operations to TanStack Query if handwritten query adapters become repetitive.
|
||||
- `zustand`: Manage a cross-route upload queue, bulk selection, or other complex client-only state if React state is insufficient.
|
||||
- `vitest`: Run unit and integration tests using the Vite toolchain.
|
||||
- `pdfjs-dist`: Preview PDF files in the browser when document preview is implemented.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["react", "typescript", "oxc"],
|
||||
"rules": {
|
||||
"react/rules-of-hooks": "error",
|
||||
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# MyGO Web
|
||||
|
||||
MyGO's browser client is a pure client-side rendered application built with React, TypeScript, and Vite.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Checks
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
|
||||
The production build is emitted to `dist/` as static assets. See `docs/web-roadmap.md` at the repository root for architecture boundaries and planned dependencies.
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="MyGO web client" />
|
||||
<title>MyGO</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2761
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "mygo-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=24 <25"
|
||||
},
|
||||
"packageManager": "npm@11.9.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "oxlint",
|
||||
"typecheck": "tsc -b",
|
||||
"check": "npm run lint && npm run build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"antd": "^6.5.1",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router": "^8.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@types/node": "^24.13.2",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.3",
|
||||
"oxlint": "^1.71.0",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { RouterProvider } from 'react-router'
|
||||
|
||||
import { router } from './app/router.tsx'
|
||||
|
||||
function App() {
|
||||
return <RouterProvider router={router} />
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { App as AntDesignApp, ConfigProvider } from 'antd'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
export function AppProviders({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ConfigProvider>
|
||||
<AntDesignApp>{children}</AntDesignApp>
|
||||
</ConfigProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createBrowserRouter } from 'react-router'
|
||||
|
||||
import { HomePage } from '../pages/HomePage.tsx'
|
||||
import { NotFoundPage } from '../pages/NotFoundPage.tsx'
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <HomePage />,
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <NotFoundPage />,
|
||||
},
|
||||
])
|
||||
@@ -0,0 +1,13 @@
|
||||
@layer theme, base, antd, components, utilities;
|
||||
@import 'tailwindcss';
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-width: 320px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { AppProviders } from './app/providers.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<AppProviders>
|
||||
<App />
|
||||
</AppProviders>
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Card, Space, Tag, Typography } from 'antd'
|
||||
|
||||
const { Paragraph, Title } = Typography
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<main className="grid min-h-screen place-items-center bg-slate-50 p-6">
|
||||
<Card className="w-full max-w-xl">
|
||||
<Space orientation="vertical" size="middle">
|
||||
<Tag color="blue">MyGO Web</Tag>
|
||||
<Title level={1}>MyGO</Title>
|
||||
<Paragraph>
|
||||
The client-side web application foundation is ready.
|
||||
</Paragraph>
|
||||
</Space>
|
||||
</Card>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Button, Result } from 'antd'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
export function NotFoundPage() {
|
||||
return (
|
||||
<Result
|
||||
status="404"
|
||||
title="Page not found"
|
||||
extra={
|
||||
<Button type="primary">
|
||||
<Link to="/">Back home</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023", "DOM"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"allowArbitraryExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"module": "nodenext",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
})
|
||||
Reference in New Issue
Block a user