Instruction file imported from davideagosti-dev/CodeFoundex (
.cursor/rules/frontend.mdc). Copyright stays with the author.
CodeFoundex — FRONTEND RULES
#####################################################################
TECH STACK (DO NOT CHANGE without explicit approval)
#####################################################################
- Next.js 15 (App Router, RSC + Client Components)
- React 19, TypeScript (strict mode)
- TailwindCSS + Shadcn/UI component library
- Zustand for client state
- Native fetch via httpGet / httpJson (src/lib/http.ts) — NO axios, NO raw fetch()
- WebSocket client for streaming events
- authStorage (src/lib/auth/storage.ts) for token + tenant in sessionStorage
#####################################################################
FILE STRUCTURE
#####################################################################
frontend/src/
├── app/
│ ├── (auth)/ # Public auth pages (login, callback)
│ ├── (app)/ # Protected pages (wrapped in AuthGuard); code in app/code/
│ └── (marketing)/ # Public marketing pages
├── components/
│ ├── ui/ # Shadcn/UI primitives (DO NOT modify these)
│ ├── auth/ # LoginCard, EmailPasswordForm, OAuthButtons, TenantPickerDialog
│ └── [feature]/ # Feature-grouped components
├── hooks/ # Custom hooks (use-*.ts)
├── lib/
│ ├── auth/
│ │ ├── guards.tsx # AuthGuard component
│ │ └── storage.ts # authStorage: getToken, setToken, getTenantId, setTenantId
│ ├── env.ts # Environment config
│ ├── http.ts # httpGet<T>, httpJson<T> — auto-injects Auth + X-Tenant-Id
│ └── utils.ts # cn() for Tailwind class merging
└── types/ # Shared TypeScript type definitions
#####################################################################
COMPONENT PATTERN
#####################################################################
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { httpJson, HttpError } from "@/lib/http";
interface MyComponentProps {
title: string;
onSuccess: (id: string) => void;
}
export function MyComponent({ title, onSuccess }: MyComponentProps) {
const [loading, setLoading] = useState(false);
const { toast } = useToast();
const handleAction = async () => {
setLoading(true);
try {
const result = await httpJson<{ id: string }>({
path: "/api/example",
method: "POST",
body: { name: title },
});
onSuccess(result.id);
} catch (err) {
if (err instanceof HttpError) {
toast({ title: "Error", description: err.bodyText, variant: "destructive" });
}
} finally {
setLoading(false);
}
};
return (
<Button onClick={handleAction} disabled={loading}>
{loading ? "Working..." : title}
</Button>
);
}
#####################################################################
API CALLS RULES
#####################################################################
- ALWAYS use httpGet / httpJson from
@/lib/http.ts - NEVER use fetch() directly
- NEVER install axios
- httpJson auto-injects Authorization + X-Tenant-Id headers from authStorage
- Handle HttpError for user-facing error display
#####################################################################
AUTH STORAGE RULES
#####################################################################
- Token: sessionStorage via authStorage.setToken() / getToken()
- Tenant: sessionStorage via authStorage.setTenantId() / getTenantId()
- authStorage.clearAll() on logout
- NEVER store tokens in localStorage in production
#####################################################################
MARKETING vs CONSOLE RULES
#####################################################################
- Marketing UI is public, fixed theme, does NOT react to console light/dark toggle
- Console UI is protected (AuthGuard) and can toggle theme
- NEVER mix marketing assets/logic into console routes and vice versa
- Marketing routes MUST NOT attach tenant/token headers
#####################################################################
ROLE-BASED UI VISIBILITY
#####################################################################
- Hide 'Delete' buttons for viewers and developers
- Disable 'Execute' for viewers
- Hide 'Settings' / 'Members' tabs for non-admins
- Show read-only indicators for restricted content
- Grey out actions with tooltip: "Requires {role} role"
- Always close modals on success
- Provide toasts on error
#####################################################################
SSO FRONTEND RULES (Phase 13)
#####################################################################
- Provide
/auth/callbackroute (src/app/(auth)/callback/page.tsx) - On callback success:
- extract token from URL fragment (NOT query param)
- store JWT via authStorage.setToken()
- single tenant → setTenantId() and redirect to dashboard
- multiple tenants → show TenantPickerDialog
- OAuthButtons.tsx: redirect to /api/auth/sso/{provider}/start
- Marketing remains public; console remains protected by AuthGuard
#####################################################################
ROI — NORMALIZZAZIONE E FORMATTAZIONE
#####################################################################
frontend/src/features/stream/reducer.ts: i blocchiROI_EVALUATION_COMPLETEDeROI_EVALUATED_COMPATapplicanonormalizeRoiPayload()prima di impostarestate.roi.evaluation— stesso mapping del backendfrontend/src/lib/formatGbp.ts: helperformatGbp(value: number | null | undefined): string- Se
0 < |value| < 0.01→toFixed(4)(es. £0.0020) - Altrimenti →
toFixed(2)(es. £160.00) - REGOLA: usare SEMPRE
formatGbp()per valori GBP. MAI.toFixed(2)inline su valori monetari — i costi API possono essere frazioni di penny
- Se
ROIBreakdown.tsxeROISummaryStrip.tsxusanoformatGbp()per tutti i valori
#####################################################################
METRICS — CHIAMATA SEMPRE SENZA BLOCCO SU REPOID
#####################################################################
frontend/src/api/metrics.ts:getOverview(repoId: string | undefined, range: string)egetCosts—repo_idaggiunto ai query params conURLSearchParamssolo se presentefrontend/src/app/(app)/app/metrics/page.tsxecosts/page.tsx: chiamano SEMPRE l'API anche quandorepoIdènull/undefined- REGOLA: MAI fare early return
if (!repoId) returnnelle pagine metrics — senzarepo_idil backend aggrega tenant-wide e mostra i dati storici
#####################################################################
SHELL TAB
#####################################################################
frontend/src/components/session/ShellLogPanel.tsx: pannello terminale monospace con colori per nodo LangGraph (init_run=blue, execute_step=green, verify_step=yellow, human_review=amber, error/escalate=red, finalize=muted), auto-scroll, pulsante clearfrontend/src/features/stream/reducer.ts:shell_log→ appende astate.shellLines; resetshellLines: []suagent.run.started(nuovo run = shell pulita)frontend/src/features/stream/protocol.ts: tiposhell_logcon{ line, run_id?, ts? }
#####################################################################
ROUTING E LAYOUT
#####################################################################
- Le pagine Code e Code Insights sono in
frontend/src/app/(app)/app/code/(NON in(code)/code/— route group eliminato) frontend/src/app/(app)/app/code/layout.tsx:h-[calc(100vh-57px)]+-m-6per Monaco editor full-height (cancella padding di AppShell da 57px)- Monaco editor options standard: sempre
scrollBeyondLastLine: trueepadding: { bottom: 40 }
#####################################################################
SESSION STATE PERSISTENZA
#####################################################################
step_resultspersistiti insessionStoragechiavecodefoundex.session.stepResultsdopo ogni polling — ripristinati al remount per evitare reset a "pending" dopo navigazionecodefoundex.session.activeRepoIdscritto solo dasession/page.tsxdurante sessione attiva — non disponibile navigando direttamente a /metrics
#####################################################################
DEFAULT COMMANDS
#####################################################################
npm run lint→ MUST pass (0 errors)npm run build→ MUST pass