From 89958518998293e62121bf5e33b1d23eca99e35b Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Wed, 6 May 2026 16:44:37 +0200 Subject: [PATCH 1/2] feat(ui): redesign every module page to the SimpleModulePython HiFi system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the emerald + teal design system from the Claude Design HiFi mock across the full surface — public, auth, and authenticated pages — and adds a new admin Doctor page that mirrors `sm doctor`. Design system primitives in packages/ui: - StatCard, SectionTitle, FilterPills (new) - PageShell, AuthCardShell, ErrorScreen (refreshed) - TONE map at packages/ui/src/lib/tone.ts — single source for semantic badge / chrome class triplets - Globals: emerald primary scale (oklch hue 158), Sora display font Pages redesigned: - Landing — mesh blobs, gradient hero, terminal CTA, 6-feature grid, quickstart split with terminal, gradient CTA strip, light footer - Auth (Login / Register / Forgot / Reset / AcceptInvite / VerifyEmail) — light glass card on emerald mesh blobs, simple_module / python lockup - Dashboard Home — 4 stat cards, system module grid, demo activity panels gated to `import.meta.env.DEV` - Doctor (NEW, /dashboard/doctor) — admin-only page with stat cards, static checks, recent migrations, dev server, sm CLI runner, env vars, installed modules. Wired into AdminLayout sidebar - Users Index — workspace-wide aggregate stat cards (active/pending), search + filters, avatar table with status badges; Roles tab with role cards - Users Edit / Invite — section cards with gradient accent bars, role pill picker, alert-dialog confirmation on Disable + reset-link - Permissions UserEdit / RoleEdit — 3 stat cards, search, per-module switch grid with proper grid borders for odd-length lists - Settings Browse / Create / Edit / ModulesEdit — uniform PageShell + Card forms; module navigator with search - Profile — gradient avatar, verified badge, role pills - Error (403/404/500) — gradient HTTP numerals + accent badge per status Backend additions: - UserService.count_user_states() — workspace-wide active/unverified counts served as `aggregates` prop so stat cards reflect totals not the page slice - Dashboard module registers Doctor in ADMIN_SIDEBAR; doctor route slimmed to only system_info + module_count Notable fixes from the simplify pass: - Reset-link generation requires AlertDialog confirmation (was firing on click) - ForgotPassword keeps anti-enumeration WHY comment - useMemo Sets / reduce / filter in permission editors (was rebuilt per keystroke in the search box) - Lookup table replaces ternary chains in Doctor CheckRow - Grid border math corrected for odd-length permission lists - Manage permissions link from User Edit now goes to /edit (was 405) - Vite dev port reflected as 5050 in README --- README.md | 36 +- host/client_app/pages/Error.tsx | 19 +- host/client_app/pages/Landing.tsx | 379 ++++++++++-------- host/locales/en.json | 36 +- host/locales/es.json | 36 +- host/templates/index.html | 2 +- .../dashboard/dashboard/endpoints/views.py | 22 +- modules/dashboard/dashboard/module.py | 11 + modules/dashboard/dashboard/pages/Doctor.tsx | 272 +++++++++++++ modules/dashboard/dashboard/pages/Home.tsx | 170 +++----- .../pages/components/DemoPlaceholders.tsx | 142 +++++++ .../dashboard/pages/components/doctor-data.ts | 68 ++++ .../feature_flags/pages/Browse.tsx | 18 +- .../permissions/pages/RoleEdit.tsx | 198 +++++---- .../permissions/pages/UserEdit.tsx | 249 +++++++----- modules/settings/settings/pages/Browse.tsx | 181 ++++++--- modules/settings/settings/pages/Create.tsx | 210 ++++++---- modules/settings/settings/pages/Edit.tsx | 149 ++++--- .../settings/settings/pages/ModulesEdit.tsx | 82 ++-- modules/users/users/components/RolesTab.tsx | 111 ++--- modules/users/users/components/UserRow.tsx | 80 ++++ modules/users/users/endpoints/views.py | 2 + modules/users/users/pages/AcceptInvite.tsx | 92 +++-- modules/users/users/pages/ForgotPassword.tsx | 100 +++-- modules/users/users/pages/Login.tsx | 189 ++++----- modules/users/users/pages/Profile.tsx | 66 ++- modules/users/users/pages/Register.tsx | 178 ++++---- modules/users/users/pages/ResetPassword.tsx | 93 ++--- modules/users/users/pages/Users/Edit.tsx | 276 +++++-------- modules/users/users/pages/Users/Index.tsx | 149 +++---- modules/users/users/pages/Users/Invite.tsx | 78 ++-- .../Users/components/AccountStatusCard.tsx | 102 +++++ modules/users/users/pages/VerifyEmail.tsx | 48 ++- modules/users/users/service.py | 14 + packages/ui/src/components/ErrorScreen.tsx | 62 ++- packages/ui/src/components/FilterPills.tsx | 38 ++ packages/ui/src/components/PageShell.tsx | 25 +- packages/ui/src/components/SectionTitle.tsx | 38 ++ packages/ui/src/components/StatCard.tsx | 52 +++ packages/ui/src/index.ts | 4 + packages/ui/src/layouts/AuthCardShell.tsx | 30 +- packages/ui/src/layouts/PublicLayout.tsx | 203 +++++----- packages/ui/src/lib/tone.ts | 14 + packages/ui/src/styles/globals.css | 50 +-- 44 files changed, 2762 insertions(+), 1612 deletions(-) create mode 100644 modules/dashboard/dashboard/pages/Doctor.tsx create mode 100644 modules/dashboard/dashboard/pages/components/DemoPlaceholders.tsx create mode 100644 modules/dashboard/dashboard/pages/components/doctor-data.ts create mode 100644 modules/users/users/components/UserRow.tsx create mode 100644 modules/users/users/pages/Users/components/AccountStatusCard.tsx create mode 100644 packages/ui/src/components/FilterPills.tsx create mode 100644 packages/ui/src/components/SectionTitle.tsx create mode 100644 packages/ui/src/components/StatCard.tsx create mode 100644 packages/ui/src/lib/tone.ts 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" %}