Skip to content
Open
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
2 changes: 1 addition & 1 deletion .specify/memory/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The landing page MUST load within 3 seconds on standard broadband. Transitions b
- **Maps**: `@vis.gl/react-google-maps` for all Google Maps integration. `APIProvider` MUST wrap the map tree. Raw `gmp-*` web components are acceptable ONLY for elements without React bindings (e.g., `gmp-polyline-3d`).
- **AI**: Vercel AI SDK with Google Gemini. Tool definitions in `src/lib/ai/tools.ts`.
- **Voice**: Web Speech API wrappers in `src/lib/voice/`.
- **Icons**: Inline SVG or Lucide React. No icon font libraries.
- **Icons**: doo-iconik bridge (`src/lib/icons/`) using `<DooIcon name="..." />`. Icon data sourced from [ajentik/doo-iconik](https://github.com/ajentik/doo-iconik) (MIT). No icon font libraries.
- **Deploy**: Railway auto-deploy from `main` branch. GitHub Actions CI runs lint + build on all PRs.

## Development Workflow
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ The project follows five core principles that govern all development decisions:
- **Styling**: Tailwind CSS 4 with OKLCH color tokens in `globals.css`. No inline style objects except for dynamic values.
- **State**: Zustand 5 as the single global store at `src/store/app-store.ts`. No prop drilling beyond one level.
- **Maps**: `APIProvider` wraps the map tree. Raw `gmp-*` web components only for elements without React bindings (e.g. `gmp-polyline-3d`).
- **Icons**: Inline SVG or Lucide React. No icon font libraries.
- **Icons**: doo-iconik bridge (`src/lib/icons/`) using `<DooIcon name="..." />`. Icon data sourced from [ajentik/doo-iconik](https://github.com/ajentik/doo-iconik) (MIT). No icon font libraries.

### Store Shape

Expand Down
10 changes: 0 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"focus-trap-react": "^12.0.0",
"lucide-react": "^0.577.0",
"next": "16.1.6",
"next-themes": "^0.4.6",
"react": "19.2.3",
Expand Down
6 changes: 3 additions & 3 deletions src/components/TranscriptionConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { Check, RefreshCw } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import { cn } from "@/lib/utils";
import type { QualityResult } from "@/utils/transcriptionQuality";

Expand Down Expand Up @@ -89,7 +89,7 @@ export default function TranscriptionConfirmation({
)}
aria-label="Confirm transcription"
>
<Check size={14} />
<DooIcon name="tick" size={14} />
Correct
</button>
<button
Expand All @@ -102,7 +102,7 @@ export default function TranscriptionConfirmation({
)}
aria-label="Try transcription again"
>
<RefreshCw size={14} />
<DooIcon name="sync" size={14} />
Try again
</button>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/components/aac/AACResultCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import type { POI } from "@/types";
import { MapPin, Footprints, Loader2, Phone } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import { useWalkingRoute } from "@/hooks/useWalkingRoute";

interface AACResultCardProps {
Expand All @@ -28,7 +28,7 @@ export default function AACResultCard({ poi, distanceKm, onSelect }: AACResultCa
>
<div className="flex items-start gap-2.5">
<div className="mt-0.5 flex-shrink-0 w-7 h-7 rounded-full bg-poi-aac/15 flex items-center justify-center">
<MapPin size={14} className="text-poi-aac" />
<DooIcon name="location-pin" size={14} className="text-poi-aac" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium leading-snug text-foreground group-hover:text-primary transition-colors truncate pr-8">
Expand Down Expand Up @@ -62,9 +62,9 @@ export default function AACResultCard({ poi, distanceKm, onSelect }: AACResultCa
className="absolute top-2 right-2 flex items-center justify-center w-7 h-7 rounded-full bg-primary/10 hover:bg-primary/20 active:scale-90 transition-all disabled:opacity-60"
>
{isWalking ? (
<Loader2 size={14} className="animate-spin text-primary" aria-hidden="true" />
<DooIcon name="loader" size={14} className="animate-spin text-primary" />
) : (
<Footprints size={14} className="text-primary" aria-hidden="true" />
<DooIcon name="navigation2" size={14} className="text-primary" />
)}
</button>
</button>
Expand All @@ -74,7 +74,7 @@ export default function AACResultCard({ poi, distanceKm, onSelect }: AACResultCa
aria-label={`Call ${poi.name}`}
className="flex items-center justify-center gap-2.5 w-full h-14 bg-green-600 hover:bg-green-700 active:bg-green-800 text-white font-semibold text-base transition-colors"
>
<Phone size={22} aria-hidden="true" />
<DooIcon name="phone" size={22} />
📞 Call This Centre
</a>
)}
Expand Down
12 changes: 6 additions & 6 deletions src/components/aac/AACSearchPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState, useMemo, useEffect } from "react";
import { Search, MapPin, Navigation, Loader2 } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import { ScrollArea } from "@/components/ui/scroll-area";
import { EmptyState } from "@/components/ui/empty-state";
import { useAppStore } from "@/store/app-store";
Expand Down Expand Up @@ -73,10 +73,10 @@ export default function AACSearchPanel() {
<div className="flex flex-col h-full">
<div className="px-3 pt-3 pb-2 space-y-2 shrink-0">
<div className="relative">
<Search
<DooIcon
name="search"
size={15}
className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none"
aria-hidden="true"
/>
<input
type="text"
Expand All @@ -97,9 +97,9 @@ export default function AACSearchPanel() {
className="w-full flex items-center justify-center gap-2 px-3 py-2.5 text-sm font-medium rounded-lg border border-border bg-card hover:bg-accent/50 active:bg-accent transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === "loading" ? (
<Loader2 size={15} className="animate-spin" aria-hidden="true" />
<DooIcon name="loader" size={15} className="animate-spin" />
) : (
<Navigation size={15} aria-hidden="true" />
<DooIcon name="navigation" size={15} />
)}
{status === "loading" ? "Getting location…" : "Use My Location"}
</button>
Expand All @@ -120,7 +120,7 @@ export default function AACSearchPanel() {
<EmptyState
icon={
<div className="w-16 h-16 rounded-2xl bg-muted/60 flex items-center justify-center">
<MapPin className="h-8 w-8 text-muted-foreground/50" />
<DooIcon name="location-pin" size={32} className="text-muted-foreground/50" />
</div>
}
title="No centres found"
Expand Down
6 changes: 3 additions & 3 deletions src/components/chat/ChatInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState, useRef, useEffect, useCallback } from "react";
import { ArrowUp } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import { cn } from "@/lib/utils";
import VoiceButton from "./VoiceButton";

Expand Down Expand Up @@ -126,9 +126,9 @@ export default function ChatInput({ onSend, isLoading }: ChatInputProps) {
: "bg-muted/60 text-muted-foreground/40"
)}
>
<ArrowUp
<DooIcon
name="send"
size={20}
strokeWidth={2.5}
className={cn(
"transition-all duration-250",
canSend ? "opacity-100 translate-y-0" : "opacity-50 translate-y-0.5"
Expand Down
8 changes: 4 additions & 4 deletions src/components/chat/ChatPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useChat } from "@ai-sdk/react";
import { useEffect, useRef, useState, type ReactNode } from "react";
import Image from "next/image";
import { AlertCircle, RotateCcw, ChevronDown } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import type { TextUIPart, DynamicToolUIPart } from "ai";
import { toast } from "sonner";
import ChatMessage from "./ChatMessage";
Expand Down Expand Up @@ -371,7 +371,7 @@ export default function ChatPanel() {
<div className="mx-2 mb-3 p-3.5 rounded-2xl bg-destructive/8 border border-destructive/15 text-sm animate-error-shake">
<div className="flex items-start gap-2.5">
<div className="shrink-0 mt-0.5 flex items-center justify-center w-7 h-7 rounded-lg bg-destructive/10">
<AlertCircle className="w-3.5 h-3.5 text-destructive" />
<DooIcon name="caution" size={14} className="text-destructive" />
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-destructive">
Expand All @@ -389,7 +389,7 @@ export default function ChatPanel() {
disabled={isActive}
className="text-destructive border-destructive/30 hover:bg-destructive/10"
>
<RotateCcw className="w-3.5 h-3.5 mr-1.5" />
<DooIcon name="sync" size={14} className="mr-1.5" />
Retry
</Button>
</div>
Expand Down Expand Up @@ -437,7 +437,7 @@ export default function ChatPanel() {
)}
>
New messages
<ChevronDown className="w-3.5 h-3.5" />
<DooIcon name="chevron-down" size={14} />
</button>
)}

Expand Down
24 changes: 12 additions & 12 deletions src/components/chat/ToolResultCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { memo } from "react";
import { MapPin, Calendar, Info, Star, Clock, Navigation } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
Expand Down Expand Up @@ -75,7 +75,7 @@ function LocationCard({ output }: { output: NavigateOutput }) {
return (
<Card size="sm" className="bg-destructive/5 ring-destructive/20 animate-tool-card-enter">
<CardContent className="flex items-start gap-2.5">
<MapPin className="w-4 h-4 text-destructive shrink-0 mt-0.5" />
<DooIcon name="location-pin" size={16} className="text-destructive shrink-0 mt-0.5" />
<p className="text-sm text-destructive">{output.message}</p>
</CardContent>
</Card>
Expand All @@ -86,7 +86,7 @@ function LocationCard({ output }: { output: NavigateOutput }) {
<Card size="sm" className="bg-surface-brand/[0.03] ring-primary/15 animate-tool-card-enter shadow-sm overflow-hidden">
<CardContent className="flex items-start gap-3">
<div className="shrink-0 mt-0.5 flex items-center justify-center w-9 h-9 rounded-xl bg-primary/10">
<MapPin className="w-[18px] h-[18px] text-primary" />
<DooIcon name="location-pin" size={18} className="text-primary" />
</div>
<div className="flex-1 min-w-0 space-y-1.5">
<div className="flex items-center gap-2">
Expand All @@ -101,19 +101,19 @@ function LocationCard({ output }: { output: NavigateOutput }) {
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground pt-0.5">
{poi.address && (
<span className="flex items-center gap-1">
<Navigation className="w-3 h-3" />
<DooIcon name="navigation" size={12} />
{poi.address}
</span>
)}
{poi.hours && (
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<DooIcon name="clock" size={12} />
{poi.hours}
</span>
)}
{poi.rating && (
<span className="flex items-center gap-1">
<Star className="w-3 h-3 fill-amber-400 text-amber-400" />
<DooIcon name="star" size={12} className="text-amber-400" />
{poi.rating}/5
</span>
)}
Expand All @@ -139,7 +139,7 @@ function EventListCard({ output }: { output: ShowEventsOutput }) {
return (
<Card size="sm" className="bg-muted/20 animate-tool-card-enter">
<CardContent className="flex items-start gap-2.5">
<Calendar className="w-4 h-4 text-muted-foreground shrink-0 mt-0.5" />
<DooIcon name="calendar" size={16} className="text-muted-foreground shrink-0 mt-0.5" />
<p className="text-sm text-muted-foreground">{message}</p>
</CardContent>
</Card>
Expand All @@ -154,7 +154,7 @@ function EventListCard({ output }: { output: ShowEventsOutput }) {
<CardContent className="space-y-2.5">
<div className="flex items-center gap-2">
<div className="flex items-center justify-center w-7 h-7 rounded-lg bg-primary/10 shrink-0">
<Calendar className="w-3.5 h-3.5 text-primary" />
<DooIcon name="calendar" size={14} className="text-primary" />
</div>
<p className="text-xs font-medium text-muted-foreground">{message}</p>
</div>
Expand Down Expand Up @@ -224,7 +224,7 @@ function CampusInfoCard({ output }: { output: CampusInfoOutput }) {
<CardContent className="space-y-2.5">
<div className="flex items-start gap-2.5">
<div className="shrink-0 mt-0.5 flex items-center justify-center w-7 h-7 rounded-lg bg-primary/10">
<Info className="w-3.5 h-3.5 text-primary" />
<DooIcon name="info" size={14} className="text-primary" />
</div>
<p className="text-sm leading-relaxed whitespace-pre-line">
{output.answer}
Expand All @@ -238,7 +238,7 @@ function CampusInfoCard({ output }: { output: CampusInfoOutput }) {
className="flex items-start gap-2 rounded-xl border border-border/50 p-2.5 text-xs
transition-colors duration-150 active:bg-muted/30"
>
<MapPin className="w-3 h-3 text-primary shrink-0 mt-0.5" />
<DooIcon name="location-pin" size={12} className="text-primary shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<span className="font-medium">{venue.name}</span>
{venue.address && (
Expand All @@ -250,13 +250,13 @@ function CampusInfoCard({ output }: { output: CampusInfoOutput }) {
<div className="flex items-center gap-2 text-muted-foreground mt-0.5">
{venue.hours && (
<span className="flex items-center gap-0.5">
<Clock className="w-2.5 h-2.5" />
<DooIcon name="clock" size={10} />
{venue.hours}
</span>
)}
{venue.rating && (
<span className="flex items-center gap-0.5">
<Star className="w-2.5 h-2.5 fill-amber-400 text-amber-400" />
<DooIcon name="star" size={10} className="text-amber-400" />
{venue.rating}
</span>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat/VoiceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { toast } from "sonner";
import { useSpeechRecognition } from "@/lib/voice/speech-recognition";
import { Mic } from "lucide-react";
import { DooIcon } from "@/lib/icons";
import { cn } from "@/lib/utils";

interface VoiceButtonProps {
Expand Down Expand Up @@ -36,7 +36,7 @@ export default function VoiceButton({ onTranscript }: VoiceButtonProps) {
title={isListening ? "Stop recording" : "Start voice input"}
aria-label={isListening ? "Stop recording" : "Start voice input"}
>
<Mic size={18} />
<DooIcon name="mic" size={18} />
</button>
);
}
Loading
Loading