chore: scaffold twitter lite
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
.superpowers/
|
.superpowers/
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
.output/
|
||||||
|
node_modules/
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
|
||||||
|
"files": {
|
||||||
|
"includes": ["**", "!src/routeTree.gen.ts", "!!**/.output"]
|
||||||
|
},
|
||||||
|
"formatter": { "enabled": true, "indentStyle": "space" },
|
||||||
|
"linter": { "enabled": true, "rules": { "recommended": true } },
|
||||||
|
"javascript": {
|
||||||
|
"formatter": { "quoteStyle": "single", "semicolons": "asNeeded" }
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783776592,
|
||||||
|
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
description = "Intentional X reader";
|
||||||
|
|
||||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
|
outputs = { nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
systems = [ "x86_64-linux" "aarch64-linux" ];
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||||
|
in {
|
||||||
|
devShells = forAllSystems (system:
|
||||||
|
let pkgs = import nixpkgs { inherit system; };
|
||||||
|
in {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [ nodejs_22 pnpm chromium ];
|
||||||
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
|
||||||
|
PLAYWRIGHT_CHROMIUM_EXECUTABLE =
|
||||||
|
"${pkgs.chromium}/bin/chromium";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "twitter-lite",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"packageManager": "[email protected]",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"#/*": "./src/*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev --host 127.0.0.1 --port 3000",
|
||||||
|
"dev:tailscale": "vite dev --host 0.0.0.0 --port 3000",
|
||||||
|
"generate-routes": "tsr generate",
|
||||||
|
"build": "vite build",
|
||||||
|
"start": "HOST=127.0.0.1 node .output/server/index.mjs",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "biome check .",
|
||||||
|
"format": "biome check --write .",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:live": "TWITTER_LITE_LIVE=1 vitest run tests/live/relay.test.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "5.101.2",
|
||||||
|
"@tanstack/react-router": "1.170.17",
|
||||||
|
"@tanstack/react-router-ssr-query": "1.167.1",
|
||||||
|
"@tanstack/react-start": "1.168.27",
|
||||||
|
"@yuta/bird": "link:../bird",
|
||||||
|
"nitro": "3.0.260610-beta",
|
||||||
|
"react": "19.2.7",
|
||||||
|
"react-dom": "19.2.7",
|
||||||
|
"zod": "4.4.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.5.3",
|
||||||
|
"@playwright/test": "1.61.1",
|
||||||
|
"@tanstack/router-cli": "1.167.18",
|
||||||
|
"@testing-library/dom": "10.4.1",
|
||||||
|
"@testing-library/jest-dom": "6.9.1",
|
||||||
|
"@testing-library/react": "16.3.2",
|
||||||
|
"@types/node": "26.1.1",
|
||||||
|
"@types/react": "19.2.17",
|
||||||
|
"@types/react-dom": "19.2.3",
|
||||||
|
"@vitejs/plugin-react": "6.0.3",
|
||||||
|
"jsdom": "29.1.1",
|
||||||
|
"typescript": "7.0.2",
|
||||||
|
"vite": "8.1.4",
|
||||||
|
"vitest": "4.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+3106
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/* 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>()
|
||||||
|
|
||||||
|
import type { getRouter } from './router.tsx'
|
||||||
|
import type { createStart } from '@tanstack/react-start'
|
||||||
|
declare module '@tanstack/react-start' {
|
||||||
|
interface Register {
|
||||||
|
ssr: true
|
||||||
|
router: Awaited<ReturnType<typeof getRouter>>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
|
import { createRouter } from '@tanstack/react-router'
|
||||||
|
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
||||||
|
import { routeTree } from './routeTree.gen'
|
||||||
|
|
||||||
|
export function getRouter() {
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: { retry: false, staleTime: 0, gcTime: 0 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const router = createRouter({
|
||||||
|
routeTree,
|
||||||
|
context: { queryClient },
|
||||||
|
scrollRestoration: true,
|
||||||
|
defaultPreload: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
setupRouterSsrQueryIntegration({ router, queryClient })
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: ReturnType<typeof getRouter>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import type { QueryClient } from '@tanstack/react-query'
|
||||||
|
import {
|
||||||
|
createRootRouteWithContext,
|
||||||
|
HeadContent,
|
||||||
|
Scripts,
|
||||||
|
} from '@tanstack/react-router'
|
||||||
|
import appCss from '../styles.css?url'
|
||||||
|
|
||||||
|
type RouterContext = { queryClient: QueryClient }
|
||||||
|
|
||||||
|
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||||
|
head: () => ({
|
||||||
|
meta: [
|
||||||
|
{ charSet: 'utf-8' },
|
||||||
|
{
|
||||||
|
name: 'viewport',
|
||||||
|
content: 'width=device-width, initial-scale=1',
|
||||||
|
},
|
||||||
|
{ title: 'Twitter Lite' },
|
||||||
|
],
|
||||||
|
links: [{ rel: 'stylesheet', href: appCss }],
|
||||||
|
}),
|
||||||
|
shellComponent: RootDocument,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="ja">
|
||||||
|
<head>
|
||||||
|
<HeadContent />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{children}
|
||||||
|
<Scripts />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { Home } from './index'
|
||||||
|
|
||||||
|
describe('Home', () => {
|
||||||
|
it('starts without ambient post content', () => {
|
||||||
|
render(<Home />)
|
||||||
|
|
||||||
|
expect(screen.getByText('目的を決めてから開く')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByRole('article')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/')({ component: Home })
|
||||||
|
|
||||||
|
export function Home() {
|
||||||
|
return (
|
||||||
|
<main className="landing">
|
||||||
|
<p className="brand">TWITTER LITE</p>
|
||||||
|
<h1>目的を決めてから開く</h1>
|
||||||
|
<p>ユーザー名か検索語を入力するまで、投稿は表示しません。</p>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
:root {
|
||||||
|
color: #1e3238;
|
||||||
|
background: #dfe7e9;
|
||||||
|
font-family: Inter, "Noto Sans JP", ui-sans-serif, system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing {
|
||||||
|
width: min(42rem, calc(100% - 2rem));
|
||||||
|
margin: 15vh auto;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid #c1cfd2;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: #f8faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
color: #70858a;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import '@testing-library/jest-dom/vitest'
|
||||||
|
import { cleanup } from '@testing-library/react'
|
||||||
|
import { afterEach } from 'vitest'
|
||||||
|
|
||||||
|
afterEach(cleanup)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["vite/client", "node"],
|
||||||
|
"paths": { "#/*": ["./src/*"] },
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
||||||
|
import viteReact from '@vitejs/plugin-react'
|
||||||
|
import { nitro } from 'nitro/vite'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [nitro(), tanstackStart(), viteReact()],
|
||||||
|
})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import { configDefaults, defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: { alias: { '#': path.resolve(import.meta.dirname, 'src') } },
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./tests/setup.ts'],
|
||||||
|
exclude: [...configDefaults.exclude, 'tests/e2e/**'],
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user