diff --git a/README.md b/README.md index ae52a606..7a6dab28 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A modular-monolith framework for Python. Each feature lives in its own self-cont - **Backend:** Python 3.12, FastAPI, SQLModel (SQLAlchemy async + Pydantic), Alembic - **Frontend:** Inertia.js + React + Tailwind CSS 4, Vite HMR +- **UI:** shadcn/ui primitives + emerald/teal design tokens, Sora display font, DM Sans body, JetBrains Mono code - **Auth:** Local user management (email+password, cookie-based sessions) via fastapi-users - **Tooling:** uv workspaces, Ruff, ty, Biome, pytest @@ -44,7 +45,7 @@ make migrate make dev ``` -Hit `http://localhost:8000` — you land on the public page. `/users/login` is the email+password login, `/dashboard` is the authenticated home. +Hit `http://localhost:8000` — you land on the public page. `/users/login` is the email+password login, `/dashboard/` is the authenticated home, and `/dashboard/doctor` is the admin-only "sm doctor" panel (static checks, migrations, dev server, modules). ## Create a new module @@ -80,7 +81,8 @@ host/ client_app/ # Vite + React client app migrations/ # Alembic migrations packages/ - ui/ # shared shadcn/ui components & layouts + ui/ # shared shadcn/ui components, layouts, and design-system primitives + i18n/ # generated i18n keys + translation runtime scripts/ new_module.py # module scaffolder (called by `make new-module`) docs/ @@ -100,7 +102,7 @@ docs/ | `make migrate` | Apply pending Alembic migrations | | `make migration msg="..."` | Autogenerate a new migration | | `make new-module name=` | Scaffold a new module | -| `make kill` | Stop any running dev servers (ports 8000, 5173) | +| `make kill` | Stop any running dev servers (ports 8000, 5050, 5173) | | `make docker-up` / `docker-down` | Manage the Postgres container (SQLite needs no Docker) | ## Configuration @@ -112,7 +114,7 @@ Local deployments only need one env var — everything else has sensible default | `SM_DATABASE_URL` | `sqlite+aiosqlite:///./app.db` | Yes — async URL. Postgres: `postgresql+asyncpg://...` | | `SM_ENVIRONMENT` | `development` | No — any value other than `development`, `test`, `testing` triggers strict discovery and placeholder-secret checks | | `SM_SECRET_KEY` | `change-me-in-production` | No in dev; **must** be overridden in production | -| `SM_VITE_DEV_URL` | `http://localhost:5050` | Dev only | +| `SM_VITE_DEV_URL` | `http://localhost:5050` | Dev only — Vite HMR origin | Power users can still override the following bootstrap knobs via env if needed: `SM_DB_POOL_SIZE`, `SM_DB_MAX_OVERFLOW`, `SM_DB_POOL_PRE_PING`, `SM_DB_POOL_RECYCLE`, `SM_DEBUG`, `SM_LOG_LEVEL`, `SM_LOG_FORMAT`, `SM_MODULES_ENABLED`. These are needed before the DB connection is open. @@ -128,6 +130,32 @@ to seed DB overrides from the current `SM_*` environment. See `framework-conventions.md` for the settings-per-module convention. +## UI & design system + +The frontend uses an emerald + teal design system mirrored as Tailwind 4 tokens. Module pages should compose from a small set of shared primitives so they stay visually consistent without duplication. + +**Shared primitives** (in `packages/ui/src/`): + +| Component | When to use | +|---|---| +| `PageShell` | Every authenticated page. Wraps title + description + actions header and a max-width content area. | +| `StatCard` | Top-of-page KPI tiles — icon, value, label, optional delta badge. Used on Dashboard, Users, Doctor. | +| `SectionTitle` | Card section headings with the gradient accent bar. | +| `FilterPills` | Segmented filter chips for status/tab-style toggles. | +| `AuthCardShell` | Login / register / forgot / accept-invite / verify — light glass card on emerald mesh blobs. | +| `ErrorScreen` | 403 / 404 / 500 — gradient HTTP numerals + accent badge per status. | + +**Design tokens** live in `packages/ui/src/styles/globals.css` under the `@theme` block — primary emerald scale (`--color-primary-50…900`), display/sans/mono families, semantic shadcn tokens. Override the CSS variables to rebrand without touching component code. + +**Module pages** should: + +- Wrap in `PageShell` with `title`, optional `description`, and `actions`. +- Use `Card` + `CardContent` from `@simple-module-py/ui/components/ui/card` for content blocks. +- Reach for `StatCard` / `SectionTitle` / `FilterPills` before rolling new layouts. +- Use lucide-react icons (already a dependency) and the existing `Badge` / `Button` variants — emerald primary for the main CTA, outline / ghost for secondary actions. + +The 300-line file cap (enforced by CI) usually pushes you to factor row-level components into `pages/components/` — see `modules/users/users/components/UserRow.tsx` and `modules/dashboard/dashboard/pages/components/doctor-data.ts` for the pattern. + ## User management ### Creating the first admin diff --git a/host/client_app/pages/Error.tsx b/host/client_app/pages/Error.tsx index 7996e761..c1531af7 100644 --- a/host/client_app/pages/Error.tsx +++ b/host/client_app/pages/Error.tsx @@ -2,6 +2,7 @@ import { Link } from '@inertiajs/react'; import { keys, useT } from '@simple-module-py/i18n'; import { ErrorScreen } from '@simple-module-py/ui/components/ErrorScreen'; import { Button } from '@simple-module-py/ui/components/ui/button'; +import { Home, LifeBuoy } from 'lucide-react'; interface Props { status: number; @@ -23,15 +24,25 @@ function ErrorPage({ status, message }: Props) { 500: t(keys.host.error.server_error_description), }; + const accents: Record = { + 403: 'warning', + 404: 'primary', + 500: 'destructive', + }; + const title = titles[status] || t(keys.host.error.generic_title); const description = message || descriptions[status] || t(keys.host.error.generic_description); return ( - - - diff --git a/host/client_app/pages/Landing.tsx b/host/client_app/pages/Landing.tsx index bbbfcd6f..245e46f4 100644 --- a/host/client_app/pages/Landing.tsx +++ b/host/client_app/pages/Landing.tsx @@ -1,208 +1,245 @@ -import { usePage } from '@inertiajs/react'; import { keys, useT } from '@simple-module-py/i18n'; -import { Badge } from '@simple-module-py/ui/components/ui/badge'; import { Button } from '@simple-module-py/ui/components/ui/button'; import { Card, CardContent } from '@simple-module-py/ui/components/ui/card'; -import { Separator } from '@simple-module-py/ui/components/ui/separator'; import { PublicLayout } from '@simple-module-py/ui/layouts/PublicLayout'; +import { + BookOpen, + Copy, + Database, + LayoutTemplate, + Package, + Rocket, + Route, + ShieldCheck, + Sparkles, + Stethoscope, +} from 'lucide-react'; -interface Props { - isAuthenticated: boolean; -} +const QUICKSTART = `# 1. install python and js deps +$ sm install + +# 2. copy env template +$ cp .env.example .env + +# 3. run migrations +$ sm migrate + +# 4. start API + Vite in parallel +$ sm dev +`; function Landing() { - const { isAuthenticated } = usePage<{ props: Props }>().props as unknown as Props; const { t } = useT(); const features = [ { - icon: ( - - ), - title: t(keys.host.landing.features.module_system_title), - description: t(keys.host.landing.features.module_system_description), + icon: Package, + title: keys.host.landing.features.schema_title, + desc: keys.host.landing.features.schema_description, }, { - icon: ( - - ), - title: t(keys.host.landing.features.auth_title), - description: t(keys.host.landing.features.auth_description), + icon: Route, + title: keys.host.landing.features.module_system_title, + desc: keys.host.landing.features.module_system_description, }, { - icon: ( - - ), - title: t(keys.host.landing.features.schema_title), - description: t(keys.host.landing.features.schema_description), + icon: LayoutTemplate, + title: keys.host.landing.features.inertia_title, + desc: keys.host.landing.features.inertia_description, }, { - icon: ( - - ), - title: t(keys.host.landing.features.inertia_title), - description: t(keys.host.landing.features.inertia_description), + icon: Database, + title: keys.host.landing.features.devtools_title, + desc: keys.host.landing.features.devtools_description, }, { - icon: ( - - ), - title: t(keys.host.landing.features.diagnostics_title), - description: t(keys.host.landing.features.diagnostics_description), + icon: ShieldCheck, + title: keys.host.landing.features.auth_title, + desc: keys.host.landing.features.auth_description, }, { - icon: ( - - ), - title: t(keys.host.landing.features.devtools_title), - description: t(keys.host.landing.features.devtools_description), + icon: Stethoscope, + title: keys.host.landing.features.diagnostics_title, + desc: keys.host.landing.features.diagnostics_description, }, ]; return ( <> - {/* Hero */} -
- + +
-
- - + {/* Features */} +
+
+
+ + How it works + +

+ One process · many modules · zero glue. +

+
+
+ {features.map((f) => ( + + + + +

+ {t(f.title)} +

+

{t(f.desc)}

+
+
+ ))} +
- + {/* Quickstart split */} +
+
+
+ + Quickstart + +

+ Working app in five commands. +

+

+ Land on{' '} + + http://localhost:8000 + {' '} + with users, dashboard, and permissions pre-wired. Sign in with the admin account you + bootstrap and go from there. +

+
+ {[ + ['users', 'Email + cookie sessions via fastapi-users'], + ['dashboard', 'Authenticated home with module tiles'], + ['permissions', 'Per-module permission registry'], + ].map(([n, d]) => ( +
+ + + + {n} + {d} +
+ ))} +
+
+
+
+ + + + ~/my-app — bash +
+
+              {QUICKSTART}
+              ✓ ready on http://localhost:8000
+              {'\n\n# 5. scaffold a new module\n$ sm new module orders\n'}
+              ✓ scaffolded modules/orders/
+            
+
+
+
- {/* Features */} -
-
- {features.map((feature) => ( - +
+
+

+ Ready to ship modules? +

+

+ Sign up for the admin UI, or hack on the framework directly. +

+
+
+ + +
diff --git a/host/locales/en.json b/host/locales/en.json index 6a4ea77c..e206fb8c 100644 --- a/host/locales/en.json +++ b/host/locales/en.json @@ -1,25 +1,25 @@ { "landing": { - "badge": "Built with FastAPI + Inertia.js + React", - "hero_title_line1": "Modular Monolith", - "hero_title_line2": "Framework for Python", - "hero_subtitle": "Build scalable applications with independent modules, each with its own database schema, API endpoints, and React pages — all in one deployable unit.", + "badge": "v0.1 · Python 3.12 · experimental", + "hero_title_line1": "Modular monoliths for Python —", + "hero_title_line2": "without the boilerplate.", + "hero_subtitle": "Each feature ships its own SQLModel tables, FastAPI endpoints, and React pages. Plugin modules compose at boot — no microservice tax, no API-client glue.", "cta_dashboard": "Open Dashboard", - "cta_get_started": "Get Started", - "cta_docs": "Documentation", + "cta_get_started": "Start your project", + "cta_docs": "Read the docs", "features": { - "module_system_title": "Module System", - "module_system_description": "Each module is a self-contained package with its own models, services, API endpoints, and React pages. Discovered automatically via Python entry_points.", - "auth_title": "Local Auth", - "auth_description": "Cookie-based email+password authentication with local user management. Server-side sessions, permission-based access control, and role-filtered menus.", - "schema_title": "Schema Isolation", - "schema_description": "Each module gets its own database schema on PostgreSQL or table prefix on SQLite. Full audit trails, soft deletes, and multi-tenancy built in.", - "inertia_title": "Inertia.js + React", - "inertia_description": "Server-driven SPA — FastAPI renders props, React renders the UI. No separate API client, no state duplication, full-stack type safety.", - "diagnostics_title": "Diagnostics", - "diagnostics_description": "Built-in module validator catches orphan pages, phantom renders, unguarded endpoints, and circular dependencies at startup.", - "devtools_title": "Developer Tools", - "devtools_description": "uv workspaces, Tailwind CSS 4, Vite HMR, auto-discovered pages, 97 tests in 0.4s, and a CLI scaffolding tool." + "module_system_title": "Discovered at boot", + "module_system_description": "Python entry points register every ModuleBase subclass. No reflection, no manual wiring.", + "auth_title": "Built-in auth", + "auth_description": "Email + cookie sessions via fastapi-users. Roles & permissions register with the module.", + "schema_title": "Per-module schema", + "schema_description": "PostgreSQL → schema per module. SQLite → prefixed tables. Each module owns its data.", + "inertia_title": "Inertia + React", + "inertia_description": "Browse / Create / Edit pages ship inside each module. Vite HMR for the frontend, async FastAPI for the backend.", + "diagnostics_title": "sm doctor", + "diagnostics_description": "Static analyzer catches orphan pages, framework coupling, and migration drift before boot.", + "devtools_title": "Async SQLModel", + "devtools_description": "SQLAlchemy async + Pydantic + Alembic. Generate migrations per module." } }, "error": { diff --git a/host/locales/es.json b/host/locales/es.json index 88e56392..72b1500d 100644 --- a/host/locales/es.json +++ b/host/locales/es.json @@ -1,25 +1,25 @@ { "landing": { - "badge": "Hecho con FastAPI + Inertia.js + React", - "hero_title_line1": "Monolito modular", - "hero_title_line2": "Framework para Python", - "hero_subtitle": "Construye aplicaciones escalables con módulos independientes, cada uno con su propio esquema de base de datos, endpoints de API y páginas de React — todo en una sola unidad desplegable.", + "badge": "v0.1 · Python 3.12 · experimental", + "hero_title_line1": "Monolitos modulares para Python —", + "hero_title_line2": "sin el código repetitivo.", + "hero_subtitle": "Cada función incluye sus propias tablas SQLModel, endpoints FastAPI y páginas React. Los módulos plugin se componen al iniciar — sin coste de microservicios, sin pegamento de cliente API.", "cta_dashboard": "Abrir panel", - "cta_get_started": "Comenzar", - "cta_docs": "Documentación", + "cta_get_started": "Empieza tu proyecto", + "cta_docs": "Lee los docs", "features": { - "module_system_title": "Sistema de módulos", - "module_system_description": "Cada módulo es un paquete autocontenido con sus propios modelos, servicios, endpoints de API y páginas de React. Descubierto automáticamente vía entry_points de Python.", - "auth_title": "Autenticación local", - "auth_description": "Autenticación por correo y contraseña basada en cookies con gestión local de usuarios. Sesiones del lado del servidor, control de acceso por permisos y menús filtrados por rol.", - "schema_title": "Aislamiento de esquema", - "schema_description": "Cada módulo obtiene su propio esquema de base de datos en PostgreSQL o prefijo de tabla en SQLite. Auditoría completa, borrado lógico y multi-tenancy incorporados.", - "inertia_title": "Inertia.js + React", - "inertia_description": "SPA dirigida por el servidor — FastAPI renderiza props, React renderiza la UI. Sin cliente API separado, sin duplicación de estado, seguridad de tipos de punta a punta.", - "diagnostics_title": "Diagnósticos", - "diagnostics_description": "El validador de módulos incorporado detecta páginas huérfanas, renders fantasma, endpoints sin protección y dependencias circulares al inicio.", - "devtools_title": "Herramientas de desarrollo", - "devtools_description": "Workspaces de uv, Tailwind CSS 4, Vite HMR, páginas auto-descubiertas, 97 pruebas en 0.4s y una herramienta CLI de andamiaje." + "module_system_title": "Descubierto al iniciar", + "module_system_description": "Los entry points de Python registran cada subclase de ModuleBase. Sin reflexión, sin cableado manual.", + "auth_title": "Autenticación incluida", + "auth_description": "Email + sesiones por cookie vía fastapi-users. Roles y permisos se registran con el módulo.", + "schema_title": "Esquema por módulo", + "schema_description": "PostgreSQL → un esquema por módulo. SQLite → tablas con prefijo. Cada módulo posee sus datos.", + "inertia_title": "Inertia + React", + "inertia_description": "Páginas Browse / Create / Edit incluidas en cada módulo. Vite HMR para el frontend, FastAPI async para el backend.", + "diagnostics_title": "sm doctor", + "diagnostics_description": "El analizador estático detecta páginas huérfanas, acoplamiento del framework y desviaciones de migración antes del arranque.", + "devtools_title": "SQLModel async", + "devtools_description": "SQLAlchemy async + Pydantic + Alembic. Genera migraciones por módulo." } }, "error": { diff --git a/host/templates/index.html b/host/templates/index.html index ea8da7ea..5078d26e 100644 --- a/host/templates/index.html +++ b/host/templates/index.html @@ -6,7 +6,7 @@ SimpleModule - + {% inertia_head %} {% if request.app.state.sm.inertia_config.environment == "development" %}