Create directory structure for frontend with requisite justfile changes. (#6)

Reviewed-on: #6
Co-authored-by: Drew Galbraith <drew@tiramisu.one>
Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
Drew 2025-09-22 08:58:25 +00:00 committed by Drew
parent 6bf4a037f3
commit 7d2b7fc90c
27 changed files with 8806 additions and 46 deletions

65
backend/justfile Normal file
View file

@ -0,0 +1,65 @@
# Backend development commands for Captain's Log
export DATABASE_URL :="sqlite://local.db"
[working-directory: 'backend']
dev-backend:
cargo run
[working-directory: 'backend']
build-backend:
cargo build
[working-directory: 'backend']
test-unit-backend:
cargo test
[working-directory: 'backend']
fmt-backend:
cargo fmt
[working-directory: 'backend']
fmt-check-backend:
cargo fmt --check
[working-directory: 'backend']
lint-backend:
cargo clippy
[working-directory: 'backend']
clean-backend:
cargo clean
[working-directory: 'backend']
reset-db:
sqlx database drop
sqlx database create
sqlx migrate run
[working-directory: 'backend']
migrate:
sqlx migrate run
[working-directory: 'backend']
migrate-revert:
sqlx migrate revert
[working-directory: 'backend']
test-integration:
#!/usr/bin/env bash
set -e
cargo run &
SERVER_PID=$!
trap 'echo "Stopping server..."; kill -TERM $SERVER_PID 2>/dev/null || true; wait $SERVER_PID 2>/dev/null || true' EXIT
echo "Waiting for server to start..."
printf 'GET http://localhost:3000/health\nHTTP 200' | hurl --retry 30 > /dev/null
echo "Running integration tests..."
hurl --test --error-format long --variable host=http://localhost:3000 tests/api/*.hurl
[working-directory: 'backend']
test-coverage:
cargo tarpaulin --out Html --output-dir coverage

4
frontend/.dockerignore Normal file
View file

@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md

6
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.DS_Store
/node_modules/
# React Router
/.react-router/
/build/

9
frontend/.prettierrc Normal file
View file

@ -0,0 +1,9 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"bracketSpacing": true,
"arrowParens": "avoid"
}

22
frontend/Dockerfile Normal file
View file

@ -0,0 +1,22 @@
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci
FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev
FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build
FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]

87
frontend/README.md Normal file
View file

@ -0,0 +1,87 @@
# Welcome to React Router!
A modern, production-ready template for building full-stack React applications using React Router.
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)
## Features
- 🚀 Server-side rendering
- ⚡️ Hot Module Replacement (HMR)
- 📦 Asset bundling and optimization
- 🔄 Data loading and mutations
- 🔒 TypeScript by default
- 🎉 TailwindCSS for styling
- 📖 [React Router docs](https://reactrouter.com/)
## Getting Started
### Installation
Install the dependencies:
```bash
npm install
```
### Development
Start the development server with HMR:
```bash
npm run dev
```
Your application will be available at `http://localhost:5173`.
## Building for Production
Create a production build:
```bash
npm run build
```
## Deployment
### Docker Deployment
To build and run using Docker:
```bash
docker build -t my-app .
# Run the container
docker run -p 3000:3000 my-app
```
The containerized application can be deployed to any platform that supports Docker, including:
- AWS ECS
- Google Cloud Run
- Azure Container Apps
- Digital Ocean App Platform
- Fly.io
- Railway
### DIY Deployment
If you're familiar with deploying Node applications, the built-in app server is production-ready.
Make sure to deploy the output of `npm run build`
```
├── package.json
├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)
├── build/
│ ├── client/ # Static assets
│ └── server/ # Server-side code
```
## Styling
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.
---
Built with ❤️ using React Router.

59
frontend/app/app.css Normal file
View file

@ -0,0 +1,59 @@
@import 'tailwindcss';
@theme {
--font-sans:
'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* Captain's Log Design Tokens */
--color-primary-50: #eff6ff;
--color-primary-100: #dbeafe;
--color-primary-500: #3b82f6;
--color-primary-950: #172554;
--color-task-todo: #3b82f6;
--color-task-done: #22c55e;
--color-task-backlog: #6b7280;
}
html,
body {
@apply bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100;
@media (prefers-color-scheme: dark) {
color-scheme: dark;
}
}
/* Captain's Log Component Styles */
.task-card {
@apply bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-4 transition-all duration-200;
}
.task-card:hover {
@apply shadow-md border-gray-300 dark:border-gray-600;
}
.quick-capture {
@apply bg-white dark:bg-gray-800 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-xl p-4 transition-colors duration-200;
}
.quick-capture:focus-within {
@apply border-blue-500 bg-blue-50 dark:bg-blue-950;
}
.status-badge {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
}
.status-todo {
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200;
}
.status-done {
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200;
}
.status-backlog {
@apply bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200;
}

75
frontend/app/root.tsx Normal file
View file

@ -0,0 +1,75 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from 'react-router'
import type { Route } from './+types/root'
import './app.css'
export const links: Route.LinksFunction = () => [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'anonymous',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
},
]
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
export default function App() {
return <Outlet />
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = 'Oops!'
let details = 'An unexpected error occurred.'
let stack: string | undefined
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? '404' : 'Error'
details =
error.status === 404
? 'The requested page could not be found.'
: error.statusText || details
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message
stack = error.stack
}
return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
)
}

3
frontend/app/routes.ts Normal file
View file

@ -0,0 +1,3 @@
import { type RouteConfig, index } from '@react-router/dev/routes'
export default [index('routes/home.tsx')] satisfies RouteConfig

View file

@ -0,0 +1,10 @@
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Home from './home'
describe('Home component', () => {
it('should render welcome component', () => {
render(<Home />)
expect(screen.getByText(/React Router/i)).toBeInTheDocument()
})
})

View file

@ -0,0 +1,13 @@
import type { Route } from './+types/home'
import { Welcome } from '../welcome/welcome'
export function meta(_: Route.MetaArgs) {
return [
{ title: 'New React Router App' },
{ name: 'description', content: 'Welcome to React Router!' },
]
}
export default function Home() {
return <Welcome />
}

View file

@ -0,0 +1,23 @@
<svg width="1080" height="174" viewBox="0 0 1080 174" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M231.527 86.9999C231.527 94.9642 228.297 102.173 223.067 107.387C217.837 112.606 210.614 115.835 202.634 115.835C194.654 115.835 187.43 119.059 182.206 124.278C176.977 129.498 173.741 136.707 173.741 144.671C173.741 152.635 170.51 159.844 165.281 165.058C160.051 170.277 152.828 173.507 144.847 173.507C136.867 173.507 129.644 170.277 124.42 165.058C119.19 159.844 115.954 152.635 115.954 144.671C115.954 136.707 119.19 129.498 124.42 124.278C129.644 119.059 136.867 115.835 144.847 115.835C152.828 115.835 160.051 112.606 165.281 107.387C170.51 102.173 173.741 94.9642 173.741 86.9999C173.741 71.0711 160.808 58.1643 144.847 58.1643C136.867 58.1643 129.644 54.9347 124.42 49.7155C119.19 44.502 115.954 37.2931 115.954 29.3287C115.954 21.3643 119.19 14.1555 124.42 8.93622C129.644 3.71698 136.867 0.493164 144.847 0.493164C160.808 0.493164 173.741 13.4 173.741 29.3287C173.741 37.2931 176.977 44.502 182.206 49.7155C187.43 54.9347 194.654 58.1643 202.634 58.1643C218.594 58.1643 231.527 71.0711 231.527 86.9999Z" fill="#F44250"/>
<path d="M115.954 86.9996C115.954 71.0742 103.018 58.1641 87.061 58.1641C71.1037 58.1641 58.1677 71.0742 58.1677 86.9996C58.1677 102.925 71.1037 115.835 87.061 115.835C103.018 115.835 115.954 102.925 115.954 86.9996Z" fill="white"/>
<path d="M58.1676 144.671C58.1676 128.745 45.2316 115.835 29.2743 115.835C13.317 115.835 0.381104 128.745 0.381104 144.671C0.381104 160.596 13.317 173.506 29.2743 173.506C45.2316 173.506 58.1676 160.596 58.1676 144.671Z" fill="white"/>
<path d="M289.314 144.671C289.314 128.745 276.378 115.835 260.42 115.835C244.463 115.835 231.527 128.745 231.527 144.671C231.527 160.596 244.463 173.506 260.42 173.506C276.378 173.506 289.314 160.596 289.314 144.671Z" fill="white"/>
<g clip-path="url(#clip0_202_2131)">
<path d="M562.482 173.247C524.388 173.247 498.363 147.49 498.363 110.468C498.363 73.4455 524.388 47.6885 562.482 47.6885C600.576 47.6885 626.869 73.7135 626.869 110.468C626.869 147.222 600.576 173.247 562.482 173.247ZM562.482 144.007C579.385 144.007 587.703 130.319 587.703 110.468C587.703 90.6168 579.385 76.9289 562.482 76.9289C545.579 76.9289 537.529 90.6168 537.529 110.468C537.529 130.319 545.311 144.007 562.482 144.007Z" fill="white"/>
<path d="M833.64 141.116C824.217 141.116 819.237 136.684 819.237 126.156V74.8983H851.928V47.7792H819.237V1.15527H791.75L786.1 26.1978C783.343 36.4805 780.82 42.822 773.897 46.0821C773.105 46.4506 771.129 46.9976 769.409 47.3884C768.014 47.701 766.596 47.8573 765.167 47.8573H752.338V47.9243H734.832C723.578 47.9243 714.445 57.0459 714.445 68.3111V111.552C714.445 130.599 707.199 142.668 692.719 142.668C678.238 142.668 672.868 133.279 672.868 116.375V47.9243H634.249V125.765C634.249 151.254 644.442 173.248 676.63 173.248C691.915 173.248 703.895 167.231 711.096 157.182C712.145 155.72 714.445 156.49 714.445 158.276V170.022H753.332V83.8412C753.332 78.8953 757.34 74.8871 762.286 74.8871H779.882V136.952C779.882 164.663 797.89 173.248 817.842 173.248C833.908 173.248 844.436 169.374 853.58 162.441V136.126C846.1 139.453 839.725 141.116 833.629 141.116H833.64Z" fill="white"/>
<path d="M981.561 130.865C975.387 157.962 954.197 173.258 923.07 173.258C885.243 173.258 858.415 150.18 858.415 112.354C858.415 74.5281 885.779 47.6992 922.266 47.6992C961.699 47.6992 982.365 74.796 982.365 107.263V113.884H896.509C894.555 135.711 909.382 144.017 924.409 144.017C937.829 144.017 946.136 138.915 950.434 127.918L981.561 130.865ZM945.075 94.9372C944.271 83.1361 936.757 75.8567 921.998 75.8567C906.434 75.8567 899.188 82.321 897.045 94.9372H945.064H945.075Z" fill="white"/>
<path d="M1076.24 85.7486C1070.06 82.2652 1064.17 80.9142 1055.85 80.9142C1039.75 80.9142 1029.02 90.0358 1029.02 110.691V170.02H990.393V47.9225H1029.02V64.3235C1029.02 65.4623 1030.54 65.8195 1031.05 64.8035C1036.68 53.5718 1047.91 44.707 1062.03 44.707C1069.27 44.707 1075.45 46.8507 1078.66 49.5414L1076.25 85.7597L1076.24 85.7486Z" fill="white"/>
<path d="M547.32 31.5345V23.9983H522.457V31.5345H515.378V2.23828H542.14C553.562 2.23828 554.365 2.95282 554.365 13.1239C554.365 17.4111 553.472 18.5611 551.329 19.6553L549.408 20.6378L551.317 21.6426C553.595 22.8372 554.365 23.2391 554.365 30.0273V31.5345H547.332H547.32ZM522.457 18.3601H547.32V7.88763H522.457V18.349V18.3601Z" fill="white"/>
<path d="M578.493 2.23828H610.826V7.90996H580.067V14.5083H610.011V19.2868H580.067V25.8963H610.837V31.501L578.504 31.5345C575.344 31.5345 572.787 28.9778 572.787 25.8293V7.95462C572.787 4.80617 575.344 2.24945 578.493 2.24945V2.23828Z" fill="white"/>
<path d="M655.562 31.5345L653.151 26.3429H633.746L631.335 31.5345H624.58L637.006 4.75034C637.71 3.22078 639.262 2.23828 640.936 2.23828H645.927C647.613 2.23828 649.154 3.22078 649.857 4.75034L662.283 31.5345H655.529H655.562ZM643.46 8.06627C642.712 8.06627 642.053 8.49053 641.729 9.17158L635.968 21.5756H650.94L645.19 9.17158C644.878 8.49053 644.208 8.06627 643.46 8.06627Z" fill="white"/>
<path d="M694.862 32.4153C676.05 32.4153 675.313 32.4153 675.313 16.8852C675.313 1.35505 676.05 1.36621 694.862 1.36621C711.721 1.36621 713.764 2.06959 714.244 10.5325H707.333V7.01556H682.168V26.766H707.333V23.2714H714.244C713.775 31.7119 711.721 32.4153 694.862 32.4153Z" fill="white"/>
<path d="M745.282 31.5345V7.02795H729.16V2.23828H768.147V7.02795H752.025V31.5345H745.282Z" fill="white"/>
<path d="M454.419 169.819C450.935 165.264 448.792 154.814 447.452 137.397C446.112 118.104 437.806 113.817 422.532 113.817H392.254V169.83H347.494V0.986328H432.715C476.391 0.986328 498.106 21.6187 498.106 54.5882C498.106 79.2399 482.833 95.3171 462.201 98.0078C479.618 101.491 489.8 111.405 491.675 130.966C494.087 156.154 494.891 163.656 500.518 169.819H454.419ZM424.676 78.704C443.969 78.704 453.615 73.8808 453.615 58.3395C453.615 44.6739 443.969 37.4392 424.676 37.4392H392.254V78.7152H424.676V78.704Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_202_2131">
<rect width="731.156" height="172.261" fill="white" transform="translate(347.494 0.986328)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,23 @@
<svg width="1080" height="174" viewBox="0 0 1080 174" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M231.527 86.9999C231.527 94.9642 228.297 102.173 223.067 107.387C217.837 112.606 210.614 115.835 202.634 115.835C194.654 115.835 187.43 119.059 182.206 124.278C176.977 129.498 173.741 136.707 173.741 144.671C173.741 152.635 170.51 159.844 165.281 165.058C160.051 170.277 152.828 173.507 144.847 173.507C136.867 173.507 129.644 170.277 124.42 165.058C119.19 159.844 115.954 152.635 115.954 144.671C115.954 136.707 119.19 129.498 124.42 124.278C129.644 119.059 136.867 115.835 144.847 115.835C152.828 115.835 160.051 112.606 165.281 107.387C170.51 102.173 173.741 94.9642 173.741 86.9999C173.741 71.0711 160.808 58.1643 144.847 58.1643C136.867 58.1643 129.644 54.9347 124.42 49.7155C119.19 44.502 115.954 37.2931 115.954 29.3287C115.954 21.3643 119.19 14.1555 124.42 8.93622C129.644 3.71698 136.867 0.493164 144.847 0.493164C160.808 0.493164 173.741 13.4 173.741 29.3287C173.741 37.2931 176.977 44.502 182.206 49.7155C187.43 54.9347 194.654 58.1643 202.634 58.1643C218.594 58.1643 231.527 71.0711 231.527 86.9999Z" fill="#F44250"/>
<path d="M115.954 86.9996C115.954 71.0742 103.018 58.1641 87.0608 58.1641C71.1035 58.1641 58.1676 71.0742 58.1676 86.9996C58.1676 102.925 71.1035 115.835 87.0608 115.835C103.018 115.835 115.954 102.925 115.954 86.9996Z" fill="#121212"/>
<path d="M58.1676 144.671C58.1676 128.745 45.2316 115.835 29.2743 115.835C13.317 115.835 0.381104 128.745 0.381104 144.671C0.381104 160.596 13.317 173.506 29.2743 173.506C45.2316 173.506 58.1676 160.596 58.1676 144.671Z" fill="#121212"/>
<path d="M289.313 144.671C289.313 128.745 276.378 115.835 260.42 115.835C244.463 115.835 231.527 128.745 231.527 144.671C231.527 160.596 244.463 173.506 260.42 173.506C276.378 173.506 289.313 160.596 289.313 144.671Z" fill="#121212"/>
<g clip-path="url(#clip0_171_1761)">
<path d="M562.482 173.247C524.388 173.247 498.363 147.49 498.363 110.468C498.363 73.4455 524.388 47.6885 562.482 47.6885C600.576 47.6885 626.869 73.7135 626.869 110.468C626.869 147.222 600.576 173.247 562.482 173.247ZM562.482 144.007C579.386 144.007 587.703 130.319 587.703 110.468C587.703 90.6168 579.386 76.9289 562.482 76.9289C545.579 76.9289 537.529 90.6168 537.529 110.468C537.529 130.319 545.311 144.007 562.482 144.007Z" fill="#121212"/>
<path d="M833.64 141.116C824.217 141.116 819.237 136.684 819.237 126.156V74.8983H851.928V47.7792H819.237V1.15527H791.75L786.1 26.1978C783.343 36.4805 780.82 42.822 773.897 46.0821C773.105 46.4506 771.129 46.9976 769.409 47.3884C768.014 47.701 766.596 47.8573 765.167 47.8573H752.338V47.9243H734.832C723.578 47.9243 714.445 57.0459 714.445 68.3111V111.552C714.445 130.599 707.199 142.668 692.719 142.668C678.238 142.668 672.868 133.279 672.868 116.375V47.9243H634.249V125.765C634.249 151.254 644.442 173.248 676.63 173.248C691.915 173.248 703.895 167.231 711.096 157.182C712.145 155.72 714.445 156.49 714.445 158.276V170.022H753.332V83.8412C753.332 78.8953 757.34 74.8871 762.286 74.8871H779.882V136.952C779.882 164.663 797.89 173.248 817.842 173.248C833.908 173.248 844.436 169.374 853.58 162.441V136.126C846.1 139.453 839.725 141.116 833.629 141.116H833.64Z" fill="#121212"/>
<path d="M981.561 130.865C975.387 157.962 954.197 173.258 923.07 173.258C885.243 173.258 858.415 150.18 858.415 112.354C858.415 74.5281 885.779 47.6992 922.266 47.6992C961.699 47.6992 982.365 74.796 982.365 107.263V113.884H896.509C894.555 135.711 909.382 144.017 924.409 144.017C937.829 144.017 946.136 138.915 950.434 127.918L981.561 130.865ZM945.075 94.9372C944.271 83.1361 936.757 75.8567 921.998 75.8567C906.434 75.8567 899.188 82.321 897.045 94.9372H945.064H945.075Z" fill="#121212"/>
<path d="M1076.24 85.7486C1070.06 82.2652 1064.17 80.9142 1055.85 80.9142C1039.75 80.9142 1029.02 90.0358 1029.02 110.691V170.02H990.393V47.9225H1029.02V64.3235C1029.02 65.4623 1030.54 65.8195 1031.05 64.8035C1036.68 53.5718 1047.91 44.707 1062.03 44.707C1069.27 44.707 1075.45 46.8507 1078.66 49.5414L1076.25 85.7597L1076.24 85.7486Z" fill="#121212"/>
<path d="M547.321 31.5345V23.9983H522.457V31.5345H515.378V2.23828H542.14C553.562 2.23828 554.366 2.95282 554.366 13.1239C554.366 17.4111 553.472 18.5611 551.329 19.6553L549.408 20.6378L551.318 21.6426C553.595 22.8372 554.366 23.2391 554.366 30.0273V31.5345H547.332H547.321ZM522.457 18.3601H547.321V7.88763H522.457V18.349V18.3601Z" fill="#121212"/>
<path d="M578.493 2.23828H610.826V7.90996H580.067V14.5083H610.011V19.2868H580.067V25.8963H610.837V31.501L578.504 31.5345C575.344 31.5345 572.787 28.9778 572.787 25.8293V7.95462C572.787 4.80617 575.344 2.24945 578.493 2.24945V2.23828Z" fill="#121212"/>
<path d="M655.562 31.5345L653.151 26.3429H633.747L631.335 31.5345H624.58L637.007 4.75034C637.71 3.22078 639.262 2.23828 640.937 2.23828H645.927C647.613 2.23828 649.154 3.22078 649.857 4.75034L662.284 31.5345H655.529H655.562ZM643.46 8.06627C642.712 8.06627 642.053 8.49053 641.729 9.17158L635.968 21.5756H650.94L645.19 9.17158C644.878 8.49053 644.208 8.06627 643.46 8.06627Z" fill="#121212"/>
<path d="M694.862 32.4153C676.05 32.4153 675.313 32.4153 675.313 16.8852C675.313 1.35505 676.05 1.36621 694.862 1.36621C711.721 1.36621 713.764 2.06959 714.244 10.5325H707.333V7.01556H682.168V26.766H707.333V23.2714H714.244C713.775 31.7119 711.721 32.4153 694.862 32.4153Z" fill="#121212"/>
<path d="M745.282 31.5345V7.02795H729.16V2.23828H768.148V7.02795H752.026V31.5345H745.282Z" fill="#121212"/>
<path d="M454.419 169.819C450.935 165.264 448.792 154.814 447.452 137.397C446.112 118.104 437.806 113.817 422.532 113.817H392.254V169.83H347.494V0.986328H432.715C476.391 0.986328 498.106 21.6187 498.106 54.5882C498.106 79.2399 482.833 95.3171 462.201 98.0078C479.618 101.491 489.8 111.405 491.676 130.966C494.087 156.154 494.891 163.656 500.518 169.819H454.419ZM424.676 78.704C443.969 78.704 453.615 73.8808 453.615 58.3395C453.615 44.6739 443.969 37.4392 424.676 37.4392H392.254V78.7152H424.676V78.704Z" fill="#121212"/>
</g>
<defs>
<clipPath id="clip0_171_1761">
<rect width="731.156" height="172.261" fill="white" transform="translate(347.494 0.986328)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,89 @@
import logoDark from './logo-dark.svg'
import logoLight from './logo-light.svg'
export function Welcome() {
return (
<main className="flex items-center justify-center pt-16 pb-4">
<div className="flex-1 flex flex-col items-center gap-16 min-h-0">
<header className="flex flex-col items-center gap-9">
<div className="w-[500px] max-w-[100vw] p-4">
<img
src={logoLight}
alt="React Router"
className="block w-full dark:hidden"
/>
<img
src={logoDark}
alt="React Router"
className="hidden w-full dark:block"
/>
</div>
</header>
<div className="max-w-[300px] w-full space-y-6 px-4">
<nav className="rounded-3xl border border-gray-200 p-6 dark:border-gray-700 space-y-4">
<p className="leading-6 text-gray-700 dark:text-gray-200 text-center">
What&apos;s next?
</p>
<ul>
{resources.map(({ href, text, icon }) => (
<li key={href}>
<a
className="group flex items-center gap-3 self-stretch p-3 leading-normal text-blue-700 hover:underline dark:text-blue-500"
href={href}
target="_blank"
rel="noreferrer"
>
{icon}
{text}
</a>
</li>
))}
</ul>
</nav>
</div>
</div>
</main>
)
}
const resources = [
{
href: 'https://reactrouter.com/docs',
text: 'React Router Docs',
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="20"
viewBox="0 0 20 20"
fill="none"
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
>
<path
d="M9.99981 10.0751V9.99992M17.4688 17.4688C15.889 19.0485 11.2645 16.9853 7.13958 12.8604C3.01467 8.73546 0.951405 4.11091 2.53116 2.53116C4.11091 0.951405 8.73546 3.01467 12.8604 7.13958C16.9853 11.2645 19.0485 15.889 17.4688 17.4688ZM2.53132 17.4688C0.951566 15.8891 3.01483 11.2645 7.13974 7.13963C11.2647 3.01471 15.8892 0.951453 17.469 2.53121C19.0487 4.11096 16.9854 8.73551 12.8605 12.8604C8.73562 16.9853 4.11107 19.0486 2.53132 17.4688Z"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
),
},
{
href: 'https://rmx.as/discord',
text: 'Join Discord',
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="20"
viewBox="0 0 24 20"
fill="none"
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
>
<path
d="M15.0686 1.25995L14.5477 1.17423L14.2913 1.63578C14.1754 1.84439 14.0545 2.08275 13.9422 2.31963C12.6461 2.16488 11.3406 2.16505 10.0445 2.32014C9.92822 2.08178 9.80478 1.84975 9.67412 1.62413L9.41449 1.17584L8.90333 1.25995C7.33547 1.51794 5.80717 1.99419 4.37748 2.66939L4.19 2.75793L4.07461 2.93019C1.23864 7.16437 0.46302 11.3053 0.838165 15.3924L0.868838 15.7266L1.13844 15.9264C2.81818 17.1714 4.68053 18.1233 6.68582 18.719L7.18892 18.8684L7.50166 18.4469C7.96179 17.8268 8.36504 17.1824 8.709 16.4944L8.71099 16.4904C10.8645 17.0471 13.128 17.0485 15.2821 16.4947C15.6261 17.1826 16.0293 17.8269 16.4892 18.4469L16.805 18.8725L17.3116 18.717C19.3056 18.105 21.1876 17.1751 22.8559 15.9238L23.1224 15.724L23.1528 15.3923C23.5873 10.6524 22.3579 6.53306 19.8947 2.90714L19.7759 2.73227L19.5833 2.64518C18.1437 1.99439 16.6386 1.51826 15.0686 1.25995ZM16.6074 10.7755L16.6074 10.7756C16.5934 11.6409 16.0212 12.1444 15.4783 12.1444C14.9297 12.1444 14.3493 11.6173 14.3493 10.7877C14.3493 9.94885 14.9378 9.41192 15.4783 9.41192C16.0471 9.41192 16.6209 9.93851 16.6074 10.7755ZM8.49373 12.1444C7.94513 12.1444 7.36471 11.6173 7.36471 10.7877C7.36471 9.94885 7.95323 9.41192 8.49373 9.41192C9.06038 9.41192 9.63892 9.93712 9.6417 10.7815C9.62517 11.6239 9.05462 12.1444 8.49373 12.1444Z"
strokeWidth="1.5"
/>
</svg>
),
},
]

30
frontend/eslint.config.js Normal file
View file

@ -0,0 +1,30 @@
import js from '@eslint/js'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['build', '.react-router', 'node_modules'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
},
)

70
frontend/justfile Normal file
View file

@ -0,0 +1,70 @@
# Frontend development commands for Captain's Log
# Start development server with backend API proxy
[working-directory: 'frontend']
dev-frontend:
npm run dev
# Build frontend for production (includes SSR build)
[working-directory: 'frontend']
build-frontend:
npm run build
# Run all tests
[working-directory: 'frontend']
test-frontend:
npm run test:run
# Run tests with coverage reporting
[working-directory: 'frontend']
test-coverage-frontend:
npm run test:coverage
# Run tests in watch mode
[working-directory: 'frontend']
test-watch-frontend:
npm run test
# Run TypeScript type checking
[working-directory: 'frontend']
typecheck-frontend:
npm run typecheck
# Lint code with ESLint
[working-directory: 'frontend']
lint-frontend:
npm run lint
# Fix linting issues automatically
[working-directory: 'frontend']
lint-fix-frontend:
npm run lint:fix
# Format code with Prettier
[working-directory: 'frontend']
fmt-frontend:
npm run format
# Check if code is properly formatted
[working-directory: 'frontend']
fmt-check-frontend:
npm run format:check
# Install dependencies
[working-directory: 'frontend']
install-frontend:
npm install
# Clean build artifacts and dependencies
[working-directory: 'frontend']
clean-frontend:
rm -rf build .react-router node_modules
# Start production server
[working-directory: 'frontend']
start-frontend:
npm run start
# Run full quality checks (lint + format + typecheck + test)
[working-directory: 'frontend']
check-frontend: lint-frontend fmt-check-frontend typecheck-frontend test-frontend

7664
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

49
frontend/package.json Normal file
View file

@ -0,0 +1,49 @@
{
"name": "frontend",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"test": "vitest",
"test:coverage": "vitest --coverage",
"test:run": "vitest run",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write \"app/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"format:check": "prettier --check \"app/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"typecheck": "react-router typegen && tsc"
},
"dependencies": {
"@react-router/node": "^7.7.1",
"@react-router/serve": "^7.7.1",
"isbot": "^5.1.27",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router": "^7.7.1"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@react-router/dev": "^7.7.1",
"@tailwindcss/vite": "^4.1.4",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@types/eslint__js": "^8.42.3",
"@types/node": "^20",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^5.0.3",
"eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"jsdom": "^27.0.0",
"prettier": "^3.6.2",
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.44.0",
"vite": "^6.3.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
}
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,7 @@
import type { Config } from "@react-router/dev/config";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true,
} satisfies Config;

View file

@ -0,0 +1,76 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./app/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
fontFamily: {
sans: ["Inter", "ui-sans-serif", "system-ui", "sans-serif"],
},
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
success: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
},
warning: {
50: '#fffbeb',
100: '#fef3c7',
200: '#fde68a',
300: '#fcd34d',
400: '#fbbf24',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
800: '#92400e',
900: '#78350f',
},
danger: {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
},
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
borderRadius: {
'xl': '0.75rem',
'2xl': '1rem',
},
boxShadow: {
'card': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
'card-hover': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
},
},
},
plugins: [],
}

1
frontend/test-setup.ts Normal file
View file

@ -0,0 +1 @@
import '@testing-library/jest-dom'

27
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,27 @@
{
"include": [
"**/*",
"**/.server/**/*",
"**/.client/**/*",
".react-router/types/**/*"
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}

16
frontend/vite.config.ts Normal file
View file

@ -0,0 +1,16 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
},
},
},
});

39
frontend/vitest.config.ts Normal file
View file

@ -0,0 +1,39 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
environment: 'jsdom',
setupFiles: ['./test-setup.ts'],
globals: true,
css: true,
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'test-setup.ts',
'**/*.d.ts',
'build/',
'.react-router/',
],
thresholds: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
},
include: ['app/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
exclude: [
'node_modules/',
'build/',
'.react-router/',
'public/',
],
},
})

View file

@ -1,53 +1,29 @@
# Temporary until we have a frontend. # Root justfile for Captain's Log - coordinates both backend and frontend
set working-directory := 'backend' import 'backend/justfile'
import 'frontend/justfile'
export DATABASE_URL :="sqlite://local.db"
# Combined commands - run both backend and frontend
dev: dev:
cargo run
build:
cargo build
test-unit:
cargo test
fmt:
cargo fmt --check
lint:
cargo clippy
clean:
cargo clean
reset-db:
sqlx database drop
sqlx database create
sqlx migrate run
migrate:
sqlx migrate run
migrate-revert:
sqlx migrate revert
test-integration:
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
echo "Starting backend and frontend servers..."
just dev-backend &
BACKEND_PID=$!
just dev-frontend &
FRONTEND_PID=$!
cargo run & trap 'echo "Stopping servers..."; kill -TERM $BACKEND_PID $FRONTEND_PID 2>/dev/null || true; wait' EXIT
SERVER_PID=$! wait
trap 'echo "Stopping server..."; kill -TERM $SERVER_PID 2>/dev/null || true; wait $SERVER_PID 2>/dev/null || true' EXIT build: build-backend build-frontend
echo "Waiting for server to start..." test: test-unit-backend test-integration test-frontend
printf 'GET http://localhost:3000/health\nHTTP 200' | hurl --retry 30 > /dev/null
echo "Running integration tests..." fmt: fmt-backend fmt-frontend
hurl --test --error-format long --variable host=http://localhost:3000 tests/api/*.hurl
test-coverage: fmt-check: fmt-check-backend fmt-check-frontend
cargo tarpaulin --out Html --output-dir coverage
lint: lint-backend lint-frontend
clean: clean-backend clean-frontend

View file

@ -0,0 +1,317 @@
# Frontend MVP Implementation Plan
## Overview
This plan details the concrete implementation steps for the Captain's Log frontend MVP using Vite + React + TypeScript + Tailwind CSS with comprehensive testing infrastructure.
## Project Structure (Planned Implementation)
```
captains-log/
├── justfile # Root task runner with combined commands
├── backend/
│ ├── justfile # Backend-specific commands
│ └── ... (existing backend structure)
└── frontend/
├── justfile # Frontend-specific commands
├── package.json # Node.js project manifest
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
├── tailwind.config.js # Tailwind CSS configuration
├── index.html # Entry HTML file
├── src/
│ ├── main.tsx # Application entry point
│ ├── App.tsx # Root component with routing
│ ├── components/
│ │ ├── TaskCard.tsx # Individual task display
│ │ ├── TaskForm.tsx # Create/edit task form
│ │ ├── TaskList.tsx # Main task list view
│ │ ├── QuickCapture.tsx # Rapid task entry
│ │ └── StatusBadge.tsx # Task status visualization
│ ├── hooks/
│ │ ├── useTask.ts # Single task operations
│ │ ├── useTasks.ts # Multiple task operations
│ │ └── useApi.ts # Generic API utilities
│ ├── services/
│ │ └── api.ts # Backend API client
│ ├── types/
│ │ └── task.ts # TypeScript type definitions
│ └── styles/
│ └── index.css # Tailwind imports and custom styles
└── tests/
└── components/ # Component unit tests
```
## Phase 1: Project Foundation (Days 1-2)
### Task 1.1: Initialize Frontend Project
- [x] **File**: `frontend/package.json`
- Create new Vite + React + TypeScript project
- Add dependencies: react, react-dom, react-router-dom, tailwindcss, @types/*
- Add dev dependencies: vitest, @testing-library/react, @testing-library/jest-dom
- Configure scripts for dev, build, test, lint
- **Expected outcome**: `npm install` succeeds
### Task 1.2: Setup Build Tools and Configuration
- [x] **Files**: `frontend/vite.config.ts`, `frontend/tsconfig.json`, `frontend/tailwind.config.js`
- Configure Vite with React plugin and proxy to backend
- Setup TypeScript with strict mode and path aliases
- Configure Tailwind CSS with custom design tokens
- Setup Vitest for testing
- **Expected outcome**: `npm run dev` starts frontend development server
### Task 1.3: Create Frontend Justfile
- [x] **File**: `frontend/justfile`
- Commands: `dev-frontend`, `build-frontend`, `test-frontend`, `lint-frontend`, `fmt-frontend`
- Setup proxy configuration for backend API
- **Expected outcome**: `just --list` in frontend directory shows all commands
### Task 1.4: Update Root Justfile Structure
- [x] **Files**: `justfile`, `backend/justfile`
- Move existing backend commands to `backend/justfile`
- Create new root justfile with combined commands
- Add concurrent execution for `dev` command (both backend + frontend)
- Include commands: `dev`, `build`, `test`, `test-unit`, `fmt`, `lint`
- **Expected outcome**: `just dev` starts both backend and frontend
## Phase 2: Core API Integration (Days 3-4)
### Task 2.1: Define TypeScript Types
- [ ] **File**: `frontend/src/types/task.ts`
- Create Task interface matching backend TaskModel
- Add TaskStatus enum (Todo, Done, Backlog)
- Include API response types and error types
- **Expected outcome**: Type definitions compile without errors
### Task 2.2: Backend API Client
- [ ] **File**: `frontend/src/services/api.ts`
- Implement API client with fetch wrapper
- Add all task endpoints: GET, POST, PUT, DELETE /api/tasks
- Include error handling and response parsing
- Add request/response logging for development
- **Expected outcome**: API client can communicate with backend
### Task 2.3: Custom React Hooks for API
- [ ] **Files**: `frontend/src/hooks/useTask.ts`, `frontend/src/hooks/useTasks.ts`
- Create useTask hook for single task operations (get, update, delete)
- Create useTasks hook for task list operations (list, create)
- Include loading states, error handling, and optimistic updates
- Add data caching and synchronization
- **Expected outcome**: Hooks provide clean API for components
### Task 2.4: API Integration Tests
- [ ] **File**: `frontend/tests/api.test.ts`
- Test API client with mock responses
- Test custom hooks with mock API calls
- Test error handling scenarios
- Use vitest and testing library utilities
- **Expected outcome**: `just test-frontend` passes all API tests
## Phase 3: Core Components (Days 5-6)
### Task 3.1: Task Card Component
- [ ] **File**: `frontend/src/components/TaskCard.tsx`
- Display task with title, description, status, dates
- Implement inline editing for title and description
- Add status change buttons/dropdown
- Include delete confirmation
- Mobile-friendly touch interactions
- **Expected outcome**: TaskCard displays and edits tasks correctly
### Task 3.2: Task Form Component
- [ ] **File**: `frontend/src/components/TaskForm.tsx`
- Create/edit form with all task properties
- Form validation and error display
- Handle form submission with API calls
- Support both modal and inline modes
- **Expected outcome**: TaskForm creates and updates tasks
### Task 3.3: Quick Capture Component
- [ ] **File**: `frontend/src/components/QuickCapture.tsx`
- Minimal input for rapid task creation
- Auto-focus and keyboard shortcuts
- Optimistic UI updates
- Smart defaults for new tasks
- **Expected outcome**: QuickCapture enables fast task entry
### Task 3.4: Status Badge Component
- [ ] **File**: `frontend/src/components/StatusBadge.tsx`
- Visual status indicators with colors
- Consistent styling across components
- Accessible color schemes
- **Expected outcome**: StatusBadge provides clear status visualization
### Task 3.5: Component Unit Tests
- [ ] **Files**: `frontend/tests/components/*.test.tsx`
- Test all components with React Testing Library
- Test user interactions and state changes
- Test API integration through mocked hooks
- Test accessibility basics (aria labels, keyboard navigation)
- **Expected outcome**: All component tests pass
## Phase 4: Main Application (Days 7-8)
### Task 4.1: Task List Component
- [ ] **File**: `frontend/src/components/TaskList.tsx`
- Display tasks in organized list/grid layout
- Filter tasks by status (Todo, In Progress, Done, Someday)
- Sort tasks by created date, priority, due date
- Implement virtual scrolling for performance
- **Expected outcome**: TaskList displays tasks efficiently
### Task 4.2: Main App Component and Routing
- [ ] **Files**: `frontend/src/App.tsx`, `frontend/src/main.tsx`
- Setup React Router with basic navigation
- Create main layout with header and task area
- Implement responsive design with Tailwind
- Add loading states and error boundaries
- **Expected outcome**: Full application loads and navigates properly
### Task 4.3: State Management and Persistence
- [ ] **File**: `frontend/src/hooks/useApi.ts`
- Implement localStorage for offline task caching
- Add synchronization when coming back online
- Handle conflicting updates gracefully
- Maintain app state across page refreshes
- **Expected outcome**: App works offline and syncs when online
### Task 4.4: Mobile Responsiveness
- [ ] **Files**: Update all component styles
- Optimize layouts for mobile screens (320px+)
- Add touch gestures for common actions
- Ensure forms work well on mobile keyboards
- Test on various screen sizes
- **Expected outcome**: App is fully usable on mobile devices
## Phase 5: Polish and Testing (Day 9)
### Task 5.1: Error Handling and User Feedback
- [ ] **Files**: Update all components
- Add comprehensive error messages
- Implement toast notifications for actions
- Add loading spinners and skeleton screens
- Handle network failures gracefully
- **Expected outcome**: Users get clear feedback for all actions
### Task 5.2: Performance Optimization
- [ ] **Files**: Review and optimize all components
- Implement React.memo where appropriate
- Optimize re-renders with useCallback/useMemo
- Add code splitting for larger bundles
- Optimize images and assets
- **Expected outcome**: App loads and responds quickly
### Task 5.3: Comprehensive Testing
- [ ] **Files**: Expand test coverage
- Achieve >80% frontend code coverage
- Test integration between components
- Test error scenarios and edge cases
- Add performance tests if needed
- **Expected outcome**: `just test-frontend` passes with high coverage
### Task 5.4: Final Integration Testing
- [ ] **Manual testing of full application**
- Test complete user workflows
- Verify backend-frontend integration
- Test on different browsers and devices
- Validate all MVP requirements are met
- **Expected outcome**: Full application works end-to-end
## Dependencies and Key Technologies
### Core Dependencies (package.json)
```json
{
"dependencies": {
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-router-dom": "^6.26.0"
},
"devDependencies": {
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"vite": "^5.4.0",
"typescript": "^5.5.0",
"tailwindcss": "^3.4.0",
"vitest": "^2.0.0",
"@testing-library/react": "^16.0.0",
"@testing-library/jest-dom": "^6.5.0"
}
}
```
### Testing Tools
- **Unit Tests**: Vitest with React Testing Library
- **Component Tests**: User interaction testing
- **API Tests**: Mock-based testing of hooks and services
- **Coverage**: Vitest coverage reporting
## Justfile Commands Structure
### Root Justfile Commands
```just
# Combined commands
dev: dev-backend dev-frontend # Start both servers concurrently
build: build-backend build-frontend
test: test-backend test-frontend
test-unit: test-unit-backend test-unit-frontend
fmt: fmt-backend fmt-frontend
lint: lint-backend lint-frontend
# Individual service commands
dev-backend:
just backend/dev-backend
dev-frontend:
just frontend/dev-frontend
```
### Backend Justfile (migrated commands)
```just
set working-directory := '.'
dev-backend:
cargo run
build-backend:
cargo build
test-unit-backend:
cargo test
```
### Frontend Justfile (new commands)
```just
set working-directory := '.'
dev-frontend:
npm run dev
build-frontend:
npm run build
test-frontend:
npm test
lint-frontend:
npm run lint
```
## Success Criteria
- [ ] All CRUD operations work through the UI
- [ ] Mobile-responsive design functions well
- [ ] >80% frontend code coverage achieved
- [ ] Integration with backend API is seamless
- [ ] Quick capture enables rapid task entry
- [ ] Task list filtering and sorting work correctly
- [ ] Offline functionality with sync capabilities
- [ ] Error handling provides clear user feedback
- [ ] Performance is acceptable for MVP usage
## Testing Strategy Summary
1. **Unit Tests**: Component testing with React Testing Library and Vitest
2. **Integration Tests**: API hook testing with mock backend
3. **Manual Testing**: Cross-browser and mobile device validation
4. **Coverage**: Automated coverage reporting with Vitest
5. **Performance**: Basic performance validation during development
This plan provides a comprehensive roadmap for implementing a robust, well-tested frontend MVP that integrates seamlessly with the existing backend API and meets all requirements outlined in the original plan.md.