Customization Guide
How to rebrand, retheme, extend, and customize the app.
Branding
App name
Set NEXT_PUBLIC_SITE_NAME to change the name everywhere: the sidebar, browser tab title, PWA manifest, and meta tags. Defaults to Daily Agent.
App description
Set NEXT_PUBLIC_SITE_DESCRIPTION to customize the meta description used for SEO and the PWA install prompt.
Icons
Replace the following files in /public/ with your own images:
icon-192.pngicon-512.pngapple-touch-icon.pngicon-maskable-512.png
Favicon
Replace /public/favicon.ico with your own favicon.
Theming
Built-in themes
Dark (default), Light, and System. Users can toggle in the sidebar footer or Settings.
Modifying colors
All colors are CSS variables defined in src/app/globals.css. Edit the variable values inside the :root (light) and [data-theme="dark"] (dark) selectors.
/* globals.css */
:root {
--color-bg: #ffffff;
--color-bg-secondary: #f5f5f5;
--color-bg-card: #fafafa;
--color-text: #111111;
--color-text-secondary: #555555;
--color-accent: #6366f1;
--color-accent-hover: #4f46e5;
--color-accent-dim: #eef2ff;
--color-border: #e5e7eb;
}
[data-theme="dark"] {
--color-bg: #0f0f0f;
--color-bg-secondary: #1a1a1a;
--color-bg-card: #161616;
--color-text: #f5f5f5;
--color-text-secondary: #a3a3a3;
--color-accent: #818cf8;
--color-accent-hover: #6366f1;
--color-accent-dim: #1e1b4b;
--color-border: #2a2a2a;
}Changing the accent color
Update --color-accent and --color-accent-hover in both the light and dark theme blocks. All buttons, links, and highlights reference these variables — no component changes needed.
System Prompt
Default behavior
The default system prompt produces a direct, opinionated AI with no filler phrases. It includes tool instructions and context injection when those features are enabled.
Customization levels
Prompts are layered in priority order (highest to lowest):
- Conversation-level prompt
- Project prompt
- User profile prompt
- Default system prompt
Memory notes from the user profile are always appended regardless of which level is active.
Editing the default
Edit src/lib/system-prompt.ts. Changes require redeployment.
Adding Tools
Tools run in Agent Mode. Each tool needs an entry in the definitions list, an executor implementation, and registration in the API route.
1. Define the tool
Add to the PRODUCTIVITY_TOOLS array in src/lib/tools/definitions.ts:
{
name: "get_my_data",
description: "Retrieves the user's custom data.",
input_schema: {
type: "object",
properties: {
limit: {
type: "number",
description: "Maximum number of records to return.",
},
},
required: [],
},
}2. Mark as read-only (if applicable)
Add the tool name to the READ_ONLY_TOOLS set in src/lib/tools/definitions.ts if it never writes data. Read-only tools execute automatically without user approval.
export const READ_ONLY_TOOLS = new Set([
// existing tools...
"get_my_data",
]);3. Implement the executor
Add a case to the executor switch in src/lib/tools/executor.ts:
case "get_my_data": {
const { limit = 10 } = input as { limit?: number };
const { data, error } = await supabase
.from("my_table")
.select("*")
.eq("user_id", userId)
.limit(limit);
if (error) throw error;
return data;
}4. Register in the API route
Add the tool name to the ALLOWED_TOOLS array in src/app/api/chat/tool-execute/route.ts:
const ALLOWED_TOOLS = [
// existing tools...
"get_my_data",
];5. Update tool instructions (optional)
Add a description of your tool to TOOL_SYSTEM_INSTRUCTIONS in src/lib/tools/definitions.ts so the AI knows when to use it.
Database Schema
Adding tables
- Write your SQL in
supabase/migrations/schema.sql - Run it in the Supabase SQL Editor
- Add the corresponding TypeScript types to
src/types/database.ts
RLS policy pattern
Every user-owned table should follow this standard RLS pattern:
-- Enable RLS
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
-- Users can only see their own rows
CREATE POLICY "Users can view own data"
ON my_table FOR SELECT
USING (auth.uid() = user_id);
-- Users can only insert their own rows
CREATE POLICY "Users can insert own data"
ON my_table FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Users can only update their own rows
CREATE POLICY "Users can update own data"
ON my_table FOR UPDATE
USING (auth.uid() = user_id);
-- Users can only delete their own rows
CREATE POLICY "Users can delete own data"
ON my_table FOR DELETE
USING (auth.uid() = user_id);Adding Pages
Protected pages
Create your page at src/app/(protected)/your-page/page.tsx. The (protected) route group is handled by middleware — any page inside it automatically requires authentication.
Sidebar
Add navigation links by editing src/components/Sidebar.tsx. Match the existing link pattern for consistent styling.
Mobile nav
Add items to the mobile bottom navigation by editing src/components/BottomNav.tsx. Keep the item count low — the bar has limited space on small screens.
API Routes
Standard route pattern
All API routes follow this pattern: authenticate the user, query Supabase, return the result.
// src/app/api/my-feature/route.ts
import { createServerClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
export async function GET() {
const supabase = createServerClient();
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { data, error } = await supabase
.from("my_table")
.select("*")
.eq("user_id", user.id);
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json(data);
}Auth check
Always use supabase.auth.getUser() with the server-side client (initialized with cookies). Never trust client-supplied user IDs.
Rate limiting
Import and call checkRateLimit at the top of any route that calls AI providers or performs expensive operations:
import { checkRateLimit } from "@/lib/rate-limit";
const rateLimitResult = await checkRateLimit(user.id, "chat");
if (!rateLimitResult.allowed) {
return NextResponse.json(
{ error: "Rate limit exceeded. Please wait before trying again." },
{ status: 429 }
);
}