Skip to main content
The frontend is React 19 with TypeScript, rendered through Inertia rather than fetched from an API.

Pages

Inertia resolves a page by name: a controller call like Inertia::render('Dashboard', [...]) matches resources/js/Pages/Dashboard.tsx. The array of props the controller passes in becomes that page component’s props — there’s no separate client-side data fetch for the page’s initial data. Orbit’s pages: Dashboard, Projects/Index, Projects/Show (the main project workspace — filter bar plus the List/Board/Calendar switcher), Issues/Show (a single issue’s detail page), Settings/Index (see Settings), and Auth/Login / Auth/Register.

Layouts

GuestLayout wraps the unauthenticated auth pages. MainLayout wraps everything else — sidebar, top navigation, and the current project/view context passed down to child pages.

Components: atomic design

Components are organized by composability, each in its own folder with a colocated test file (Button/Button.tsx, Button/Button.test.tsx):
  • Atoms — the smallest building blocks: Button, Input, Badge, Avatar, Icon, and so on
  • Molecules — small combinations of atoms: CommentForm, FilterDropdown, ProjectCard, NotificationItem, Breadcrumb
  • Organisms — larger, feature-level components: IssueBoard, IssueTable, Sidebar, NewIssueModal, DashboardVisuals, SettingsSidebar
The composition rule is one-directional: organisms use molecules, molecules use atoms — never the reverse.

Shared state and logic

  • Context (resources/js/context/) — AlertContext for toast messages (including flash messages from a redirect), ModalContext for orchestrating whichever modal is currently open, ShortcutContext for keyboard shortcuts, ThemeContext and AccentContext for the theme and accent color, both mounted at the root of app.tsx so they’re available everywhere
  • Hooks (resources/js/hooks/) — useSavedFilters wraps saved-filter CRUD and applying a filter’s query params; useTableResizing handles resizable columns in the List view
  • Types (resources/js/types/) — shared domain types (Issues.ts, Projects.ts, Users.ts, Notification.ts) and shared page-prop types
Some fields in types/Issues.ts (like milestone, sprint, or attachments_count) aren’t backed by any database column — they’re speculative additions to the type that don’t reflect a real, working feature. Don’t take the presence of a TypeScript field as confirmation a feature exists; check the issues table and IssueController before documenting or relying on it.

Styling

Tailwind CSS, with class-variance-authority for component variants and a cn() helper (clsx + tailwind-merge) for combining classes. Colors come from CSS custom properties (--bg-color, --accent-color, --border-color, and so on) consumed as Tailwind arbitrary values like bg-[var(--bg-color)], rather than Tailwind’s built-in color palette — see Appearance for the full token list and how Light/Dark/System theme switching works.

Path alias

@/ resolves to resources/js/ (configured in both tsconfig.json and the Vite config), so imports read as @/Components/Atoms/Button rather than long relative paths.
Last modified on August 17, 2026