diff --git a/docker-compose.yml b/docker-compose.yml index 739343b..9d715bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,9 @@ services: MINIO_ROOT_USER: ${MINIO_ROOT_USER:?} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?} command: server /data --console-address ":9001" + ports: + - '9000:9000' + - '9001:9001' volumes: - ex0-minio-data:/data healthcheck: diff --git a/src/.ruler/tanstack-db-rules.md b/src/.ruler/tanstack-db-rules.md index e0e07d1..5d6176b 100644 --- a/src/.ruler/tanstack-db-rules.md +++ b/src/.ruler/tanstack-db-rules.md @@ -1,5 +1,3 @@ -TanStack DB in 60 seconds (token-tight) - What it is * Reactive client store extending TanStack Query with collections, live queries, optimistic mutations; uni-directional data flow; live queries via d2ts (incremental, sub-ms). diff --git a/src/components/data-table.tsx b/src/components/data-table.tsx index 5d4f29b..d82a1fe 100644 --- a/src/components/data-table.tsx +++ b/src/components/data-table.tsx @@ -1,800 +1,740 @@ import { - DndContext, - type DragEndEvent, - KeyboardSensor, - MouseSensor, - TouchSensor, - type UniqueIdentifier, - closestCenter, - useSensor, - useSensors, -} from "@dnd-kit/core"; -import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; + DndContext, + type DragEndEvent, + KeyboardSensor, + MouseSensor, + TouchSensor, + type UniqueIdentifier, + closestCenter, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { - SortableContext, - arrayMove, - useSortable, - verticalListSortingStrategy, -} from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; + SortableContext, + arrayMove, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import { - type ColumnDef, - type ColumnFiltersState, - type Row, - type SortingState, - type VisibilityState, - flexRender, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; + type ColumnDef, + type ColumnFiltersState, + type Row, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table'; import { - ChevronDown, - ChevronLeft, - ChevronRight, - ChevronsLeft, - ChevronsRight, - CircleCheck, - Columns2, - EllipsisVertical, - GripVertical, - Loader, - Plus, - TrendingUp, -} from "lucide-react"; -import * as React from "react"; -import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"; -import { toast } from "sonner"; -import { z } from "zod"; + ChevronDown, + ChevronLeft, + ChevronRight, + ChevronsLeft, + ChevronsRight, + CircleCheck, + Columns2, + EllipsisVertical, + GripVertical, + Loader, + Plus, + TrendingUp, +} from 'lucide-react'; +import * as React from 'react'; +import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'; +import { toast } from 'sonner'; +import { z } from 'zod'; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; import { - type ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "@/components/ui/chart"; -import { Checkbox } from "@/components/ui/checkbox"; + type ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { Checkbox } from '@/components/ui/checkbox'; import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "@/components/ui/drawer"; + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from '@/components/ui/drawer'; import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Separator } from '@/components/ui/separator'; import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useIsMobile } from "@/hooks/use-mobile"; + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { useIsMobile } from '@/hooks/use-mobile'; export const schema = z.object({ - id: z.number(), - header: z.string(), - type: z.string(), - status: z.string(), - target: z.string(), - limit: z.string(), - reviewer: z.string(), + id: z.number(), + header: z.string(), + type: z.string(), + status: z.string(), + target: z.string(), + limit: z.string(), + reviewer: z.string(), }); // Create a separate component for the drag handle function DragHandle({ id }: { id: number }) { - const { attributes, listeners } = useSortable({ - id, - }); + const { attributes, listeners } = useSortable({ + id, + }); - return ( - - ); + return ( + + ); } const columns: ColumnDef>[] = [ - { - id: "drag", - header: () => null, - cell: ({ row }) => , - }, - { - id: "select", - header: ({ table }) => ( -
- table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> -
- ), - cell: ({ row }) => ( -
- row.toggleSelected(!!value)} - aria-label="Select row" - /> -
- ), - enableSorting: false, - enableHiding: false, - }, - { - accessorKey: "header", - header: "Header", - cell: ({ row }) => { - return ; - }, - enableHiding: false, - }, - { - accessorKey: "type", - header: "Section Type", - cell: ({ row }) => ( -
- - {row.original.type} - -
- ), - }, - { - accessorKey: "status", - header: "Status", - cell: ({ row }) => ( - - {row.original.status === "Done" ? ( - - ) : ( - - )} - {row.original.status} - - ), - }, - { - accessorKey: "target", - header: () =>
Target
, - cell: ({ row }) => ( -
{ - e.preventDefault(); - toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { - loading: `Saving ${row.original.header}`, - success: "Done", - error: "Error", - }); - }} - > - - -
- ), - }, - { - accessorKey: "limit", - header: () =>
Limit
, - cell: ({ row }) => ( -
{ - e.preventDefault(); - toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { - loading: `Saving ${row.original.header}`, - success: "Done", - error: "Error", - }); - }} - > - - -
- ), - }, - { - accessorKey: "reviewer", - header: "Reviewer", - cell: ({ row }) => { - const isAssigned = row.original.reviewer !== "Assign reviewer"; + { + id: 'drag', + header: () => null, + cell: ({ row }) => , + }, + { + id: 'select', + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label="Select row" + /> +
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: 'header', + header: 'Header', + cell: ({ row }) => { + return ; + }, + enableHiding: false, + }, + { + accessorKey: 'type', + header: 'Section Type', + cell: ({ row }) => ( +
+ + {row.original.type} + +
+ ), + }, + { + accessorKey: 'status', + header: 'Status', + cell: ({ row }) => ( + + {row.original.status === 'Done' ? ( + + ) : ( + + )} + {row.original.status} + + ), + }, + { + accessorKey: 'target', + header: () =>
Target
, + cell: ({ row }) => ( +
{ + e.preventDefault(); + toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: 'Done', + error: 'Error', + }); + }} + > + + +
+ ), + }, + { + accessorKey: 'limit', + header: () =>
Limit
, + cell: ({ row }) => ( +
{ + e.preventDefault(); + toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { + loading: `Saving ${row.original.header}`, + success: 'Done', + error: 'Error', + }); + }} + > + + +
+ ), + }, + { + accessorKey: 'reviewer', + header: 'Reviewer', + cell: ({ row }) => { + const isAssigned = row.original.reviewer !== 'Assign reviewer'; - if (isAssigned) { - return row.original.reviewer; - } + if (isAssigned) { + return row.original.reviewer; + } - return ( - <> - - - - ); - }, - }, - { - id: "actions", - cell: () => ( - - - - - - Edit - Make a copy - Favorite - - Delete - - - ), - }, + return ( + <> + + + + ); + }, + }, + { + id: 'actions', + cell: () => ( + + + + + + Edit + Make a copy + Favorite + + Delete + + + ), + }, ]; function DraggableRow({ row }: { row: Row> }) { - const { transform, transition, setNodeRef, isDragging } = useSortable({ - id: row.original.id, - }); + const { transform, transition, setNodeRef, isDragging } = useSortable({ + id: row.original.id, + }); - return ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ); + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); } -export function DataTable({ - data: initialData, -}: { - data: z.infer[]; -}) { - const [data, setData] = React.useState(() => initialData); - const [rowSelection, setRowSelection] = React.useState({}); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [sorting, setSorting] = React.useState([]); - const [pagination, setPagination] = React.useState({ - pageIndex: 0, - pageSize: 10, - }); - const sortableId = React.useId(); - const sensors = useSensors( - useSensor(MouseSensor, {}), - useSensor(TouchSensor, {}), - useSensor(KeyboardSensor, {}), - ); +export function DataTable({ data: initialData }: { data: z.infer[] }) { + const [data, setData] = React.useState(() => initialData); + const [rowSelection, setRowSelection] = React.useState({}); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [columnFilters, setColumnFilters] = React.useState([]); + const [sorting, setSorting] = React.useState([]); + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }); + const sortableId = React.useId(); + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ); - const dataIds = React.useMemo( - () => data?.map(({ id }) => id) || [], - [data], - ); + const dataIds = React.useMemo(() => data?.map(({ id }) => id) || [], [data]); - const table = useReactTable({ - data, - columns, - state: { - sorting, - columnVisibility, - rowSelection, - columnFilters, - pagination, - }, - getRowId: (row) => row.id.toString(), - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - onPaginationChange: setPagination, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - }); + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + pagination, + }, + getRowId: (row) => row.id.toString(), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }); - function handleDragEnd(event: DragEndEvent) { - const { active, over } = event; - if (active && over && active.id !== over.id) { - setData((data) => { - const oldIndex = dataIds.indexOf(active.id); - const newIndex = dataIds.indexOf(over.id); - return arrayMove(data, oldIndex, newIndex); - }); - } - } + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event; + if (active && over && active.id !== over.id) { + setData((data) => { + const oldIndex = dataIds.indexOf(active.id); + const newIndex = dataIds.indexOf(over.id); + return arrayMove(data, oldIndex, newIndex); + }); + } + } - return ( - -
- - - - Outline - - Past Performance 3 - - - Key Personnel 2 - - Focus Documents - -
- - - - - - {table - .getAllColumns() - .filter( - (column) => - typeof column.accessorFn !== "undefined" && - column.getCanHide(), - ) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - - -
-
- -
- - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - - {table.getRowModel().rows.map((row) => ( - - ))} - - ) : ( - - - No results. - - - )} - -
-
-
-
-
- {table.getFilteredSelectedRowModel().rows.length} of{" "} - {table.getFilteredRowModel().rows.length} row(s) selected. -
-
-
- - -
-
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} -
-
- - - - -
-
-
-
- -
-
- -
-
- -
-
-
- ); + return ( + +
+ + + + Outline + + Past Performance 3 + + + Key Personnel 2 + + Focus Documents + +
+ + + + + + {table + .getAllColumns() + .filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide()) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ); + })} + + + +
+
+ +
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + + {table.getRowModel().rows.map((row) => ( + + ))} + + ) : ( + + + No results. + + + )} + +
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{' '} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+
+ + +
+
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ ); } const chartData = [ - { month: "January", desktop: 186, mobile: 80 }, - { month: "February", desktop: 305, mobile: 200 }, - { month: "March", desktop: 237, mobile: 120 }, - { month: "April", desktop: 73, mobile: 190 }, - { month: "May", desktop: 209, mobile: 130 }, - { month: "June", desktop: 214, mobile: 140 }, + { month: 'January', desktop: 186, mobile: 80 }, + { month: 'February', desktop: 305, mobile: 200 }, + { month: 'March', desktop: 237, mobile: 120 }, + { month: 'April', desktop: 73, mobile: 190 }, + { month: 'May', desktop: 209, mobile: 130 }, + { month: 'June', desktop: 214, mobile: 140 }, ]; const chartConfig = { - desktop: { - label: "Desktop", - color: "var(--primary)", - }, - mobile: { - label: "Mobile", - color: "var(--primary)", - }, + desktop: { + label: 'Desktop', + color: 'var(--primary)', + }, + mobile: { + label: 'Mobile', + color: 'var(--primary)', + }, } satisfies ChartConfig; function TableCellViewer({ item }: { item: z.infer }) { - const isMobile = useIsMobile(); + const isMobile = useIsMobile(); - return ( - - - - - - - {item.header} - - Showing total visitors for the last 6 months - - -
- {!isMobile && ( - <> - - - - value.slice(0, 3)} - hide - /> - } - /> - - - - - -
-
- Trending up by 5.2% this month{" "} - -
-
- Showing total visitors for the last 6 months. This is just - some random text to test the layout. It spans multiple lines - and should wrap around. -
-
- - - )} -
-
- - -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
-
- - - - - - -
-
- ); + return ( + + + + + + + {item.header} + Showing total visitors for the last 6 months + +
+ {!isMobile && ( + <> + + + + value.slice(0, 3)} + hide + /> + } /> + + + + + +
+
+ Trending up by 5.2% this month +
+
+ Showing total visitors for the last 6 months. This is just some random text to + test the layout. It spans multiple lines and should wrap around. +
+
+ + + )} +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + + + + + +
+
+ ); } diff --git a/src/router.tsx b/src/router.tsx index e919552..a8cea84 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -6,9 +6,9 @@ import { NotFound } from './components/NotFound'; import { routeTree } from './routeTree.gen'; // Initialize browser-echo for TanStack Start (manual import required) -// if (import.meta.env.DEV && typeof window !== 'undefined') { -// void import('virtual:browser-echo'); -// } +if (import.meta.env.DEV && typeof window !== 'undefined') { + void import('virtual:browser-echo'); +} export function getRouter() { const queryClient = new QueryClient(); diff --git a/src/routes/dashboard/chat/route.tsx b/src/routes/dashboard/chat/route.tsx index 20047d2..03d462b 100644 --- a/src/routes/dashboard/chat/route.tsx +++ b/src/routes/dashboard/chat/route.tsx @@ -36,7 +36,7 @@ function RouteComponent() {
-

Chat Assistant

+

Coding Assistant

Ask the repository-aware assistant for help with code or docs.

@@ -81,15 +81,13 @@ function AssistantChatSurface({ files }: { files: ChatLoaderData['files'] }) {
- +

Ask the assistant about any file stored in your MinIO bucket.

- Tip: mention paths like src/lib/api.ts to fetch exact files. + Tip: mention paths like src/lib/api.ts{' '} + to fetch exact files.

@@ -197,7 +195,8 @@ function AvailableFiles({ files }: { files: ChatLoaderData['files'] }) { ) : null}
- Tip: tag a file by mentioning its key (for example, {displayFiles[0]?.key ?? 'src/example.ts'}). + Tip: tag a file by mentioning its key (for example,{' '} + {displayFiles[0]?.key ?? 'src/example.ts'}).
);