Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ When creating branches or Pull Requests via the `gh` CLI:

## Known Issues & Fixes

### ⚠️ CRITICAL — Never Hardcode Colors in Portal UI
**Issue:** New portal pages render unthemed — wrong background, invisible text, borders that vanish — because the markup carries literal hex values (`bg-[#12151D]`, `border-[#1E232F]`, `text-[#D0F237]`, `bg-[#ccff00]`) or Tailwind's default palette (`text-zinc-400`, `text-white`, `text-black`, `bg-white/[0.04]`). These are frozen dark-theme values: they ignore `--accent`, do not flip under `.light` / `[data-theme="light"]`, and drift from the design system the moment a token changes.
**Cause:** Copying an existing page as a starting point. Several older files still contain hardcoded hex, so the wrong pattern looks like the house style. **A hex value in a neighbouring file is NOT precedent — it is unconverted debt.**
**Fix & Best Practices:**
1. **Always use the semantic utility classes.** The theme is defined in `packages/components/src/styles.css` as CSS variables; the Tailwind utilities built on them are the only supported way to colour portal UI:
- Surfaces: `bg-background`, `bg-background-secondary`, `bg-surface`, `bg-surface-secondary`, `bg-overlay`
- Text: `text-foreground`, `text-muted`, `text-muted-foreground`, `text-accent`, `text-accent-foreground`
- Lines & rings: `border-border`, `border-accent`, `ring-accent`, `ring-focus`
- Status: `text-danger`, `text-success`, `bg-warning` (and their `border-*` / `bg-*` forms)
2. **Never write `text-white` / `text-black` / `text-zinc-*` / `bg-white/[0.04]`.** Map them: primary text → `text-foreground`, secondary text → `text-muted`, hover wash → `hover:bg-surface-secondary`, selected wash → `bg-accent/10`, hairline ring → `ring-border`.
3. **The lime accent is `--accent`, never a literal.** `#ccff00` / `#D0F237` → `bg-accent` with `text-accent-foreground` (the accent needs dark text for contrast; `text-accent-foreground` already encodes that).
4. **Prefer the component over restyling a native element.** A lime CTA is `<Button variant="primary">`, not a `<button>` with an accent background pasted on — the variant already tracks the theme.
5. **Opacity modifiers on a token are fine** (`bg-accent/10`, `text-muted/50`); they stay theme-aware. Literal rgba/hex overlays are not.
6. **Check before committing any portal UI:**
`grep -nE "(bg|text|border|ring)-\[#|zinc-[0-9]|text-(white|black)" <changed files>` — this must return nothing.

### Monorepo Server to Frontend Package Bleed
**Issue:** "Module not found: Can't resolve 'child_process'" or similar Node.js built-in errors in Next.js client code.
**Cause:** Importing utilities (like `canAccess`) or types directly from the root of a server module (e.g., `@fluxify/server`) forces the Next.js bundler to evaluate the server's main barrel file (`index.ts`). This barrel file exports modules that rely on Node.js built-ins (like database schemas, ORMs, and `pg`), breaking the frontend build.
Expand Down
2 changes: 1 addition & 1 deletion apps/portal/src/components/common/RoleSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TbCheck } from "react-icons/tb";

const ROLES = [
export const ROLES = [
{ id: "viewer", title: "Viewer", description: "Can read and run" },
{ id: "creator", title: "Creator", description: "Can build and deploy" },
{ id: "project_admin", title: "Project Admin", description: "Full project control" },
Expand Down
90 changes: 7 additions & 83 deletions apps/portal/src/components/home/ProjectsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import { useState } from "react";
import {
Button,
Card,
Modal,
Spinner,
TextField,
Label,
Input,
FieldError,
toast,
} from "@fluxify/components";
import { Button, Spinner } from "@fluxify/components";
import { TbPlus } from "react-icons/tb";
import { useNavigate } from "@tanstack/react-router";
import { useQueryClient } from "@tanstack/react-query";
import { projectsQuery } from "@/query/projectsQuery";
import { projectsService } from "@/services/projects";
import { showErrorNotification } from "@/lib/errorNotifier";
import { useAuthStore } from "@/store/auth";
import { ProjectCard } from "./ProjectCard";

Expand Down Expand Up @@ -74,76 +62,12 @@ export function ProjectsTab() {
}

function NewProjectButton() {
const client = useQueryClient();
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
const [error, setError] = useState<string>();
const [saving, setSaving] = useState(false);

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setSaving(true);
setError(undefined);
try {
await projectsService.create({ name });
projectsQuery.invalidateAll(client);
setOpen(false);
setName("");
toast.success("Project created");
} catch (err) {
showErrorNotification(err as Error);
setError((err as Error).message);
} finally {
setSaving(false);
}
}
const navigate = useNavigate();

return (
<Modal isOpen={open} onOpenChange={setOpen}>
<Modal.Trigger>
<button className="flex items-center gap-2 rounded-md bg-[#ccff00] px-4 py-2 text-sm font-medium text-black transition-colors hover:bg-[#b3e600]">
<TbPlus className="h-4 w-4" />
New Project
</button>
</Modal.Trigger>
<Modal.Backdrop>
<Modal.Container placement="center" size="sm">
<Modal.Dialog>
<Modal.Header>
<Modal.Heading>Create a project</Modal.Heading>
<p className="mt-1 text-sm text-muted">
Give it a name to get started — you can rename it later.
</p>
</Modal.Header>
<form onSubmit={onSubmit}>
<Modal.Body>
<TextField
isRequired
value={name}
onChange={setName}
isInvalid={!!error}
>
<Label>Project name</Label>
<Input placeholder="My API project" autoFocus />
<FieldError>{error}</FieldError>
</TextField>
</Modal.Body>
<Modal.Footer>
<Button
type="button"
variant="ghost"
onPress={() => setOpen(false)}
>
Cancel
</Button>
<Button type="submit" variant="primary" isPending={saving}>
Create project
</Button>
</Modal.Footer>
</form>
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal>
<Button variant="primary" onPress={() => navigate({ to: "/projects/new" })}>
<TbPlus className="h-4 w-4" />
New Project
</Button>
);
}
19 changes: 18 additions & 1 deletion apps/portal/src/query/projectsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@ import {
type GetAllProjectsQueryParams,
projectsService,
} from "@/services/projects";
import { type QueryClient, useQuery } from "@tanstack/react-query";
import {
type QueryClient,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import type { z } from "zod";

export const projectsQuery = {
create: {
mutation() {
const qc = useQueryClient();
return useMutation({
mutationFn: (
body: z.infer<typeof projectsService.createRequestBodySchema>,
) => projectsService.create(body),
onSuccess: () => qc.invalidateQueries({ queryKey: ["projects", "list"] }),
});
},
},
getAll: {
useQuery(query: GetAllProjectsQueryParams) {
return useQuery({
Expand Down
Loading
Loading