Plateforme intranet CHU Guyane — Site de Kourou. Maquette de refonte numérique complète avec interface d administration, messagerie, annuaire, gardes, base documentaire.
Find a file
2026-05-22 13:11:06 +00:00
client Checkpoint: Ajout : logo CHU Guyane Kourou officiel, messagerie interne complète avec fils de conversation, Mon Espace personnalisable avec widgets modulaires, base documentaire avec recherche et filtres. Navigation mise à jour avec badges de notification. 2026-05-22 13:11:06 +00:00
nginx Checkpoint: Intégration des 17 applications réelles extraites du site intranet CHK existant (Outlook, PMSIpilot, Vidal, Dedalus/Clepsydre, GLPI, Antibiogarde, Inserm, Pharmacie, Annuaire, Mail in Black, Sesame, MFT Online, Identito Vigilance, Suspicion IAS/ISO, Telesante, Doc Utilisateurs, Intervention/Achat) avec leurs logos officiels téléchargés et hébergés sur le CDN webdev. Mise à jour de Applications.tsx et IntranetPublic.tsx. 2026-05-22 12:45:44 +00:00
patches Initial project bootstrap 2026-05-21 19:27:02 +00:00
server Initial project bootstrap 2026-05-21 19:27:02 +00:00
shared Initial project bootstrap 2026-05-21 19:27:02 +00:00
.gitignore Initial project bootstrap 2026-05-21 19:27:02 +00:00
.gitkeep Checkpoint: Maquette complète de l'interface d'administration de l'intranet CHK — 100 % en français, thème Centre de commande numérique (sidebar marine sombre + contenu clair), 10 pages fonctionnelles : Tableau de bord, Pages, Médias, Widgets, Applications, Utilisateurs, Statistiques, Notifications, Rôles et droits, Paramètres. 2026-05-21 19:42:05 +00:00
.prettierignore Initial project bootstrap 2026-05-21 19:27:02 +00:00
.prettierrc Initial project bootstrap 2026-05-21 19:27:02 +00:00
components.json Initial project bootstrap 2026-05-21 19:27:02 +00:00
DEPLOIEMENT_ONPREMISES.md Checkpoint: Intégration des 17 applications réelles extraites du site intranet CHK existant (Outlook, PMSIpilot, Vidal, Dedalus/Clepsydre, GLPI, Antibiogarde, Inserm, Pharmacie, Annuaire, Mail in Black, Sesame, MFT Online, Identito Vigilance, Suspicion IAS/ISO, Telesante, Doc Utilisateurs, Intervention/Achat) avec leurs logos officiels téléchargés et hébergés sur le CDN webdev. Mise à jour de Applications.tsx et IntranetPublic.tsx. 2026-05-22 12:45:44 +00:00
docker-compose.yml Checkpoint: Intégration des 17 applications réelles extraites du site intranet CHK existant (Outlook, PMSIpilot, Vidal, Dedalus/Clepsydre, GLPI, Antibiogarde, Inserm, Pharmacie, Annuaire, Mail in Black, Sesame, MFT Online, Identito Vigilance, Suspicion IAS/ISO, Telesante, Doc Utilisateurs, Intervention/Achat) avec leurs logos officiels téléchargés et hébergés sur le CDN webdev. Mise à jour de Applications.tsx et IntranetPublic.tsx. 2026-05-22 12:45:44 +00:00
Dockerfile Checkpoint: Intégration des 17 applications réelles extraites du site intranet CHK existant (Outlook, PMSIpilot, Vidal, Dedalus/Clepsydre, GLPI, Antibiogarde, Inserm, Pharmacie, Annuaire, Mail in Black, Sesame, MFT Online, Identito Vigilance, Suspicion IAS/ISO, Telesante, Doc Utilisateurs, Intervention/Achat) avec leurs logos officiels téléchargés et hébergés sur le CDN webdev. Mise à jour de Applications.tsx et IntranetPublic.tsx. 2026-05-22 12:45:44 +00:00
env-example.txt Checkpoint: Intégration des 17 applications réelles extraites du site intranet CHK existant (Outlook, PMSIpilot, Vidal, Dedalus/Clepsydre, GLPI, Antibiogarde, Inserm, Pharmacie, Annuaire, Mail in Black, Sesame, MFT Online, Identito Vigilance, Suspicion IAS/ISO, Telesante, Doc Utilisateurs, Intervention/Achat) avec leurs logos officiels téléchargés et hébergés sur le CDN webdev. Mise à jour de Applications.tsx et IntranetPublic.tsx. 2026-05-22 12:45:44 +00:00
ideas.md Checkpoint: Maquette complète de l'interface d'administration de l'intranet CHK — 100 % en français, thème Centre de commande numérique (sidebar marine sombre + contenu clair), 10 pages fonctionnelles : Tableau de bord, Pages, Médias, Widgets, Applications, Utilisateurs, Statistiques, Notifications, Rôles et droits, Paramètres. 2026-05-21 19:42:05 +00:00
package.json Initial project bootstrap 2026-05-21 19:27:02 +00:00
pnpm-lock.yaml Initial project bootstrap 2026-05-21 19:27:02 +00:00
README.md Initial project bootstrap 2026-05-21 19:27:02 +00:00
template.json Initial project bootstrap 2026-05-21 19:27:02 +00:00
tsconfig.json Initial project bootstrap 2026-05-21 19:27:02 +00:00
tsconfig.node.json Initial project bootstrap 2026-05-21 19:27:02 +00:00
vite.config.ts Initial project bootstrap 2026-05-21 19:27:02 +00:00

Web App Template (Static Frontend)

Pure React 19 + Tailwind 4 template with shadcn/ui baked in. Use this README as the checklist for shipping static experiences.

Note: This template includes a minimal shared/ and server/ directory with placeholder types to support imported templates. These are just compatibility placeholders - web-static remains a true static-only template without API functionality.


Stack Overview

  • Client-only routing powered by React + Wouter.
  • Design tokens live entirely in client/src/index.css—keep that file intact.

File Structure

client/
  public/       ← Small configuration files ONLY (favicon.ico, robots.txt). DO NOT put images/media here.
  src/
    pages/      ← Page-level components
    components/ ← Reusable UI & shadcn/ui
    contexts/   ← React contexts
    hooks/      ← Custom React hooks
    lib/        ← Utility helpers
    App.tsx     ← Routes & top-level layout
    main.tsx    ← React entry point
    index.css   ← global style
server/         ← Placeholder for imported template compatibility
shared/         ← Placeholder for imported template compatibility
  const.ts      ← Shared constants

⚠️ Handling Images & Media

DO NOT store images, videos, or large assets in client/public/ or client/src/assets/. Local media files will cause deployment timeouts.

Required workflow:

  1. Upload assets using the CLI: manus-upload-file --webdev path/to/image.png
  2. Use the returned storage path directly in your code: <img src="/manus-storage/image_a1b2c3d4.png" />
  3. Store the original local file in /home/ubuntu/webdev-static-assets/ (outside the project directory)

Only small configuration files like favicon.ico, robots.txt, and manifest.json belong in client/public/.

Files in client/public are available at the root of your site—reference them with absolute paths (/robots.txt, etc.) from HTML templates, JSX, or meta tags.


🎯 Development Workflow

  1. Choose a design style before you write any frontend code according to Design Guide (color, font, shadow, art style). Tell user what you chose. Remember to edit client/src/index.css for global theming and add needed font using google font cdn in client/index.html.
  2. Compose pages in client/src/pages/. Keep sections modular so they can be reused across routes.
  3. Share primitives via client/src/components/—extend shadcn/ui when needed instead of duplicating markup.
  4. Keep styling consistent by relying on existing Tailwind tokens (spacing, colors, typography).
  5. Fetch external data with useEffect if the site needs dynamic content from public APIs.

🎨 Frontend Development Guidelines

UI & Styling:

  • Prefer shadcn/ui components for interactions to keep a modern, consistent look; import from @/components/ui/* (e.g., button, card, dialog).
  • Compose Tailwind utilities with component variants for layout and states; avoid excessive custom CSS. Use built-in variant, size, etc. where available.
  • Preserve design tokens: keep the @layer base rules in client/src/index.css. Utilities like border-border and font-sans depend on them.
  • Consistent design language: use spacing, radius, shadows, and typography via tokens. Extract shared UI into components/ for reuse instead of copypaste.
  • Accessibility and responsiveness: keep visible focus rings and ensure keyboard reachability; design mobilefirst with thoughtful breakpoints.
  • Theming: Choose dark/light theme to start with for ThemeProvider according to your design style (dark or light bg), then manage colors pallette with CSS variables in client/src/index.css instead of hardcoding to keep global consistency.
  • Microinteractions and empty states: add motion, empty states, and icons tastefully to improve quality without distracting from content.
  • Navigation: For internal tools/admin panels, use persistent sidebar. For public-facing apps, design navigation based on content structure (top nav, side nav, or contextual)—ensure clear escape routes from all pages.
  • Placeholder UI elements: When adding structural placeholders (nav items, CTAs) for not-yet-implemented features, show toast on click ("Feature coming soon"). Inform user which elements are placeholders when presenting work.

React Best Practices:

  • Never call setState/navigation in render phase → wrap in useEffect

Customized Defaults: This template customizes some Tailwind/shadcn defaults for simplified usage:

  • .container is customized to auto-center and add responsive padding (see index.css). Use directly without mx-auto/px-*. For custom widths, use max-w-* with mx-auto px-4.
  • .flex is customized to have min-width:0 and min-height:0 by default
  • button variant outline uses transparent background (not bg-background). Add bg color class manually if needed.

🎨 Design Guide

When generating frontend UI, avoid generic patterns that lack visual distinction:

  • Avoid generic full-page centered layouts—prefer asymmetric/sidebar/grid structures for landing pages and dashboards
  • When user provides vague requirements, make creative design decisions (choose specific color palette, typography, layout approach)
  • Prioritize visual diversity: combine different design systems (e.g., one color scheme + different typography + another layout principle)
  • For landing pages: prefer asymmetric layouts, specific color values (not just "blue"), and textured backgrounds over flat colors
  • For dashboards: use defined spacing systems, soft shadows over borders, and accent colors for hierarchy

Animation Guide

Bake motion taste in from the first line of code. Snappy, physically intuitive interactions are not a polish pass — they are part of the initial build.

  • Decide whether to animate at all: keyboard-initiated actions (command palettes, shortcuts) must be instant — never animate them. High-frequency interactions (hover, list nav) should be minimal. Reserve richer motion for occasional events (modals, drawers, toasts) and rare delight moments (onboarding).
  • Keep UI animations under 300ms. A 180ms dropdown feels significantly better than a 400ms one. Typical ranges: button press 100160ms, tooltips 125200ms, dropdowns 150250ms, modals/drawers 200500ms.
  • Use strong custom easings, not the weak CSS defaults. Default to a snappy ease-out for entering/exiting UI: --ease-out: cubic-bezier(0.23, 1, 0.32, 1);. For moving/morphing use --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);. NEVER use ease-in for UI animations — it feels sluggish.
  • Buttons must feel responsive: add transform: scale(0.97) on :active with a ~160ms ease-out transition so the UI confirms it heard the user.
  • Never animate from scale(0) — nothing in the real world appears from nothing. Start from scale(0.95) combined with opacity: 0.
  • Origin-aware popovers/dropdowns: scale in from the trigger point (e.g. transform-origin: var(--radix-popover-content-transform-origin)). Modals are the exception and stay centered.
  • Prefer CSS transitions over @keyframes for dynamic UI state. Transitions can be interrupted and reversed smoothly mid-flight; keyframes restart from zero and feel broken when interrupted.
  • Only animate transform and opacity for motion — they run on the GPU and skip layout/paint. Avoid animating width, height, padding, margin, top/left unless absolutely necessary.
  • Stagger grouped entrances by 3080ms per item to create a cascading reveal instead of a wall of motion.
  • Asymmetric timing for deliberate actions: hold-to-confirm should be slow and linear on press (e.g. 2s linear), but release/cancel should snap back fast (~200ms ease-out).
  • Respect prefers-reduced-motion: gate non-essential motion behind @media (prefers-reduced-motion: no-preference).

Pre-built Components

Before implementing UI features, check if these components already exist:

Maps:

  • client/src/components/Map.tsx - Google Maps integration with proxy authentication. Provides MapView component with onMapReady callback for initializing Google Maps services (Places, Geocoder, Directions, Drawing, etc.). All map functionality works directly in the browser.

When implementing features that match these categories, MUST evaluate the component first to decide whether to use or customize it.


🗺️ Maps Integration

CRITICAL: The Manus proxy provides FULL access to ALL Google Maps features - including advanced drawing, heatmaps, Street View, all layers, Places API, etc. Do NOT ask users for Google Map API keys - authentication is automatic.

Implementation:

  • Frontend: Import MapView from client/src/components/Map.tsx and initialize ANY Google Maps service (geocoding, directions, places, drawing, visualization, geometry, etc.) in the onMapReady callback. ALL Google Maps JavaScript API features work directly in the browser.

NEVER use external map libraries or request API keys from users - the Manus proxy handles everything automatically with no feature limitations.


Launch Checklist

  • UI layout and navigation structure correct, all image src valid.
  • Success + error paths verified in the browser

Core File References

package.json

{
  "name": "chk-admin-intranet",
  "version": "1.0.0",
  "type": "module",
  "license": "MIT",
  "scripts": {
    "dev": "vite --host",
    "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
    "start": "NODE_ENV=production node dist/index.js",
    "preview": "vite preview --host",
    "check": "tsc --noEmit",
    "format": "prettier --write ."
  },
  "dependencies": {
    "@hookform/resolvers": "^5.2.2",
    "@radix-ui/react-accordion": "^1.2.12",
    "@radix-ui/react-alert-dialog": "^1.1.15",
    "@radix-ui/react-aspect-ratio": "^1.1.7",
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-checkbox": "^1.3.3",
    "@radix-ui/react-collapsible": "^1.1.12",
    "@radix-ui/react-context-menu": "^2.2.16",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.16",
    "@radix-ui/react-hover-card": "^1.1.15",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-menubar": "^1.1.16",
    "@radix-ui/react-navigation-menu": "^1.2.14",
    "@radix-ui/react-popover": "^1.1.15",
    "@radix-ui/react-progress": "^1.1.7",
    "@radix-ui/react-radio-group": "^1.3.8",
    "@radix-ui/react-scroll-area": "^1.2.10",
    "@radix-ui/react-select": "^2.2.6",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slider": "^1.3.6",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-switch": "^1.2.6",
    "@radix-ui/react-tabs": "^1.1.13",
    "@radix-ui/react-toggle": "^1.1.10",
    "@radix-ui/react-toggle-group": "^1.1.11",
    "@radix-ui/react-tooltip": "^1.2.8",
    "axios": "^1.12.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "embla-carousel-react": "^8.6.0",
    "express": "^4.21.2",
    "framer-motion": "^12.23.22",
    "input-otp": "^1.4.2",
    "lucide-react": "^0.453.0",
    "nanoid": "^5.1.5",
    "next-themes": "^0.4.6",
    "react": "^19.2.1",
    "react-day-picker": "^9.11.1",
    "react-dom": "^19.2.1",
    "react-hook-form": "^7.64.0",
    "react-resizable-panels": "^3.0.6",
    "recharts": "^2.15.2",
    "sonner": "^2.0.7",
    "streamdown": "^1.4.0",
    "tailwind-merge": "^3.3.1",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.2",
    "wouter": "^3.3.5",
    "zod": "^4.1.12"
  },
  "devDependencies": {
    "@builder.io/vite-plugin-jsx-loc": "^0.1.1",
    "@tailwindcss/typography": "^0.5.15",
    "@tailwindcss/vite": "^4.1.3",
    "@types/express": "4.17.21",
    "@types/google.maps": "^3.58.1",
    "@types/node": "^24.7.0",
    "@types/react": "^19.2.1",
    "@types/react-dom": "^19.2.1",
    "@vitejs/plugin-react": "^5.0.4",
    "add": "^2.0.6",
    "autoprefixer": "^10.4.20",
    "esbuild": "^0.25.0",
    "pnpm": "^10.15.1",
    "postcss": "^8.4.47",
    "prettier": "^3.6.2",
    "tailwindcss": "^4.1.14",
    "tsx": "^4.19.1",
    "tw-animate-css": "^1.4.0",
    "typescript": "5.6.3",
    "vite": "^7.1.7",
    "vite-plugin-manus-runtime": "^0.0.57",
    "vitest": "^2.1.4"
  },
  "packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af",
  "pnpm": {
    "patchedDependencies": {
      "wouter@3.7.1": "patches/wouter@3.7.1.patch"
    },
    "overrides": {
      "tailwindcss>nanoid": "3.3.7"
    }
  }
}

client/src/App.tsx

import { Toaster } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import NotFound from "@/pages/NotFound";
import { Route, Switch } from "wouter";
import ErrorBoundary from "./components/ErrorBoundary";
import { ThemeProvider } from "./contexts/ThemeContext";
import Home from "./pages/Home";


function Router() {
  return (
    <Switch>
      <Route path={"/"} component={Home} />
      <Route path={"/404"} component={NotFound} />
      {/* Final fallback route */}
      <Route component={NotFound} />
    </Switch>
  );
}

// NOTE: About Theme
// - First choose a default theme according to your design style (dark or light bg), than change color palette in index.css
//   to keep consistent foreground/background color across components
// - If you want to make theme switchable, pass `switchable` ThemeProvider and use `useTheme` hook

function App() {
  return (
    <ErrorBoundary>
      <ThemeProvider
        defaultTheme="light"
        // switchable
      >
        <TooltipProvider>
          <Toaster />
          <Router />
        </TooltipProvider>
      </ThemeProvider>
    </ErrorBoundary>
  );
}

export default App;

client/src/pages/Home.tsx

import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
import { Streamdown } from 'streamdown';

/**
 * All content in this page are only for example, replace with your own feature implementation
 * When building pages, remember your instructions in Frontend Best Practices, Design Guide and Common Pitfalls
 */
export default function Home() {
  // If theme is switchable in App.tsx, we can implement theme toggling like this:
  // const { theme, toggleTheme } = useTheme();

  return (
    <div className="min-h-screen flex flex-col">
      <main>
        {/* Example: lucide-react for icons */}
        <Loader2 className="animate-spin" />
        Example Page
        {/* Example: Streamdown for markdown rendering */}
        <Streamdown>Any **markdown** content</Streamdown>
        <Button variant="default">Example Button</Button>
      </main>
    </div>
  );
}

client/src/index.css

@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);
}

:root {
  --primary: var(--color-blue-700);
  --primary-foreground: var(--color-blue-50);
  --sidebar-primary: var(--color-blue-600);
  --sidebar-primary-foreground: var(--color-blue-50);
  --chart-1: var(--color-blue-300);
  --chart-2: var(--color-blue-500);
  --chart-3: var(--color-blue-600);
  --chart-4: var(--color-blue-700);
  --chart-5: var(--color-blue-800);
  --radius: 0.65rem;
  --background: oklch(1 0 0);
  --foreground: oklch(0.235 0.015 65);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.235 0.015 65);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.235 0.015 65);
  --secondary: oklch(0.98 0.001 286.375);
  --secondary-foreground: oklch(0.4 0.015 65);
  --muted: oklch(0.967 0.001 286.375);
  --muted-foreground: oklch(0.552 0.016 285.938);
  --accent: oklch(0.967 0.001 286.375);
  --accent-foreground: oklch(0.141 0.005 285.823);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(0.92 0.004 286.32);
  --input: oklch(0.92 0.004 286.32);
  --ring: oklch(0.623 0.214 259.815);
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.235 0.015 65);
  --sidebar-accent: oklch(0.967 0.001 286.375);
  --sidebar-accent-foreground: oklch(0.141 0.005 285.823);
  --sidebar-border: oklch(0.92 0.004 286.32);
  --sidebar-ring: oklch(0.623 0.214 259.815);
}

.dark {
  --primary: var(--color-blue-700);
  --primary-foreground: var(--color-blue-50);
  --sidebar-primary: var(--color-blue-500);
  --sidebar-primary-foreground: var(--color-blue-50);
  --background: oklch(0.141 0.005 285.823);
  --foreground: oklch(0.85 0.005 65);
  --card: oklch(0.21 0.006 285.885);
  --card-foreground: oklch(0.85 0.005 65);
  --popover: oklch(0.21 0.006 285.885);
  --popover-foreground: oklch(0.85 0.005 65);
  --secondary: oklch(0.24 0.006 286.033);
  --secondary-foreground: oklch(0.7 0.005 65);
  --muted: oklch(0.274 0.006 286.033);
  --muted-foreground: oklch(0.705 0.015 286.067);
  --accent: oklch(0.274 0.006 286.033);
  --accent-foreground:  oklch(0.92 0.005 65);
  --destructive: oklch(0.704 0.191 22.216);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.488 0.243 264.376);
  --chart-1: var(--color-blue-300);
  --chart-2: var(--color-blue-500);
  --chart-3: var(--color-blue-600);
  --chart-4: var(--color-blue-700);
  --chart-5: var(--color-blue-800);
  --sidebar: oklch(0.21 0.006 285.885);
  --sidebar-foreground: oklch(0.85 0.005 65);
  --sidebar-accent: oklch(0.274 0.006 286.033);
  --sidebar-accent-foreground:  oklch(0.985 0 0);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.488 0.243 264.376);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
  button:not(:disabled),
  [role="button"]:not([aria-disabled="true"]),
  [type="button"]:not(:disabled),
  [type="submit"]:not(:disabled),
  [type="reset"]:not(:disabled),
  a[href],
  select:not(:disabled),
  input[type="checkbox"]:not(:disabled),
  input[type="radio"]:not(:disabled) {
    @apply cursor-pointer;
  }
}

@layer components {
  /**
   * Custom container utility that centers content and adds responsive padding.
   *
   * This overrides Tailwind's default container behavior to:
   * - Auto-center content (mx-auto)
   * - Add responsive horizontal padding
   * - Set max-width for large screens
   *
   * Usage: <div className="container">...</div>
   *
   * For custom widths, use max-w-* utilities directly:
   * <div className="max-w-6xl mx-auto px-4">...</div>
   */
  .container {
    width: 100%;
    margin-left: auto;
    margin-right: auto;
    padding-left: 1rem; /* 16px - mobile padding */
    padding-right: 1rem;
  }

  .flex {
    min-height: 0;
    min-width: 0;
  }

  @media (min-width: 640px) {
    .container {
      padding-left: 1.5rem; /* 24px - tablet padding */
      padding-right: 1.5rem;
    }
  }

  @media (min-width: 1024px) {
    .container {
      padding-left: 2rem; /* 32px - desktop padding */
      padding-right: 2rem;
      max-width: 1280px; /* Standard content width */
    }
  }
}

client/index.html

<!doctype html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1" />
    <title>{{project_title}}</title>    
    <!-- THIS IS THE START OF A COMMENT BLOCK, BLOCK TO BE DELETED: Google Fonts here, example:
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
    THIS IS THE END OF A COMMENT BLOCK, BLOCK TO BE DELETED -->
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
    <script
      defer
      src="%VITE_ANALYTICS_ENDPOINT%/umami"
      data-website-id="%VITE_ANALYTICS_WEBSITE_ID%"></script>
  </body>

</html>

server/index.ts

import express from "express";
import { createServer } from "http";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

async function startServer() {
  const app = express();
  const server = createServer(app);

  // Serve static files from dist/public in production
  const staticPath =
    process.env.NODE_ENV === "production"
      ? path.resolve(__dirname, "public")
      : path.resolve(__dirname, "..", "dist", "public");

  app.use(express.static(staticPath));

  // Handle client-side routing - serve index.html for all routes
  app.get("*", (_req, res) => {
    res.sendFile(path.join(staticPath, "index.html"));
  });

  const port = process.env.PORT || 3000;

  server.listen(port, () => {
    console.log(`Server running on http://localhost:${port}/`);
  });
}

startServer().catch(console.error);

Common Pitfalls

Infinite loading loops from unstable references

Anti-pattern: Creating new objects/arrays in render that are used as query inputs

// ❌ Bad: New Date() creates new reference every render → infinite queries
const { data } = trpc.items.getByDate.useQuery({
  date: new Date(), // ← New object every render!
});

// ❌ Bad: Array/object literals in query input
const { data } = trpc.items.getByIds.useQuery({
  ids: [1, 2, 3], // ← New array reference every render!
});

Correct approach: Stabilize references with useState/useMemo

// ✅ Good: Initialize once with useState
const [date] = useState(() => new Date());
const { data } = trpc.items.getByDate.useQuery({ date });

// ✅ Good: Memoize complex inputs
const ids = useMemo(() => [1, 2, 3], []);
const { data } = trpc.items.getByIds.useQuery({ ids });

Why this happens: TRPC queries trigger when input references change. Objects/arrays created in render have new references each time, causing infinite re-fetches.

Navigation dead-ends in subpages

Problem: Creating nested routes without escape routes—no header nav, no sidebar, no back button.

Root cause: Implementing individual pages before establishing global layout structure.

Solution: Define layout wrapper in App.tsx first, then build pages inside it. For admin tools use DashboardLayout; for detail pages add back button with router.back().

Invisible text from theme/color mismatches

Root cause: Semantic colors (bg-background, text-foreground) are CSS variables that resolve based on ThemeProvider's active theme. Mismatches cause invisible text.

Two critical rules:

  1. Match theme to CSS variables: If defaultTheme="dark" in App.tsx, ensure .dark {} in index.css has dark background + light foreground values
  2. Always pair bg with text: When using bg-{semantic}, MUST also use text-{semantic}-foreground (not automatic - text inherits from parent otherwise)

Quick reference:

// ✅ Theme + CSS alignment
<ThemeProvider defaultTheme="dark">  {/* Must match .dark in index.css */}
  <div className="bg-background text-foreground">...</div>
</ThemeProvider>

// ✅ Required class pairs
<div className="bg-popover text-popover-foreground">...</div>
<div className="bg-card text-card-foreground">...</div>
<div className="bg-accent text-accent-foreground">...</div>

Problem: Wrapping <a> tags inside another <a> or wouter's <Link> creates nested anchors and runtime errors.

Solution: Pass children directly to Link—it already renders an <a> internally.

// ❌ Bad: <Link><a>...</a></Link> or <a><a>...</a></a>
// ✅ Good: <Link>...</Link> or just <a>...</a>

Empty Select.Item values

Rule: Every <Select.Item> must have a non-empty value prop—never "", undefined, or omitted.

Rule: Use sonner for toasts; do not add react-toastify or @radix-ui/react-toast

Rule: If you put placeholder components for App.tsx routes, you MUST replace them with actual components after your implementation.