Template
Seekroom
A search modal in an AI chat assistant that lets people find past conversations and projects by title or content. With an empty query it doubles as a quick switcher showing the most recent items.
AI chat app search modal for past chats and projects · App screen: search · Small tools and apps · full-stack app (auth + DB)
A mock-up of the screen, drawn from its layout, palette and typefaces. A build follows the full prompt below.
Start from this screenRead the build prompt
Typefaces
The catalog's own faces. A screen composed into a template is drawn in that template's typefaces.
- InterHeadings: Inter 400, 16px input text
- InterBody: Inter 400 16px row titles; 13px relative times (muted)
Patterns
- centered search modal on warm off-white canvas
- list of recent chats with relative time on the right
- highlighted first row with Enter key hint
- minimal icon rail (new, search, chats, projects, artifacts, code)
- close x in input row
- type icon per row (chat bubble vs project box)
- avatar at rail bottom
States it is designed for
- Empty query: recents
- Searching (subtle spinner in input)
- Results with snippets
- No results ("No chats match. Try fewer words.")
- Search unavailable (error line with retry)
- Very long titles truncated with ellipsis
- Brand-new account (no chats yet)
Who it is for
- Heavy users of an AI assistant with many chats
- Knowledge workers organising work into projects
- Developers returning to earlier coding sessions
Layout
- Thin icon rail (~28-56px): sidebar toggle, new chat (+), search, chats, projects, artifacts, code; download and avatar at bottom
- Canvas: warm off-white, empty chat behind
- Modal (~415px wide, vertically centered): search icon + input "Search chats and projects" + close x; list of up to 8 rows (icon, title, relative time or ↵ on active)
- Mobile: full-screen sheet with input at top
Palette
warm, quiet, literate, unhurried. Like flipping through a notebook rather than a database.
- app background
#edece8 - modal background
#ffffff - highlighted row
#f5f0e6 - row titles
#1f1e1b - relative times, placeholder
#6b6a64 - rail icon colour
#3d3c38 - focus ring, active bar
#b8572f - modal hairline (decorative)
#dedcd4 - avatar circle
#3d3c38
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | row title on modal | 16.67:1 | 4.5:1 |
| Aa | muted time on modal | 5.43:1 | 4.5:1 |
| Aa | muted time on active row | 4.78:1 | 4.5:1 |
| Aa | title on active row | 14.68:1 | 4.5:1 |
| rail icons on canvas | 9.34:1 | 3:1 | |
| focus ring on modal | 4.73:1 | 3:1 | |
| Aa | text on canvas | 14.10:1 | 4.5:1 |
As vibld’s tokens
The palette on the fifteen colour tokens vibld styles a project with, each text colour on the fill it is read on. Marked tokens are solved from the palette, because no swatch held that role at 4.5:1.
- background
- card
- muted
- primary
- secondary
- accent
- destructive *
Type scale
- Display
- Inter 400, 16px input text
- Body
- Inter 400 16px row titles; 13px relative times (muted)
Similar to the observed soft grotesk. No headings in the modal; hierarchy comes from weight and colour only.
Spacing and imagery
Compact but airy: 4px base; input row 48px; list rows 36px with 12px side padding and 4px inset radius; modal radius 12px, padding 8px; shadow 0 12px 40px rgba(40,30,10,.12).
None. 16px outline icons: chat bubble for chats, box for projects.
Components
- Icon rail
- Search modal
- Search input with close
- Result row (icon, title, time)
- Active row with Enter hint
- Match highlight in titles and snippets
- Section divider for Projects vs Chats (on query)
Interactions
- Cmd/Ctrl+K opens; Esc or x closes
- Empty query shows recent items; typing searches titles and message content (debounced 200ms)
- Arrow keys move the active row; Enter opens; mouse hover sets active
- When content matches, a one-line snippet appears under the title with the match bolded
- Modal fades and scales 0.98→1 in 120ms
Data
Conversation{id, user_id, title, project_id?, updated_at}Message{id, conversation_id, role, content, created_at}Project{id, user_id, name, updated_at}SearchHit{kind (chat|project), id, title, snippet?, updated_at}
Guardrails
Experience
- With no query, act as a quick switcher of recent items
- Show relative times in plain words (Just now, Past hour, Past week, Past month)
- Distinguish chats and projects with icons and, when searching, section labels
- Keep the modal compact (max 8 visible rows, scroll the rest)
- Enter hint shown only on the active row
Accessibility
- Combobox + listbox; active row via aria-activedescendant
- Row accessible name includes type and time ("Chat: Offsite ideas, past month")
- Close button labelled; Esc works
- Matches highlighted with <mark> styled bold, not colour only
- Focus ring visible on the warm canvas
Security
- Search strictly scoped to the signed-in user (RLS on conversations, messages, projects)
- Snippets generated server-side and HTML-escaped
- Do not index or return deleted chats; honour retention settings
Build prompt
The baseline every prompt in the catalog assumes, then this design’s own ten sections, from goal to guardrails.
The baseline
### How to use these prompts Paste an entry's build prompt into your coding agent as the first message. Each prompt names its own stack, tokens and acceptance criteria; the rules below apply to all of them and can be prepended once per project. ### Engineering baseline - TypeScript strict mode, no `any`, small typed components, feature folders, and one source of truth for design tokens (CSS variables consumed by Tailwind). - Validate every input with a shared zod schema on the client and again on the server or edge function. Never trust client-side checks alone. - Show loading, empty and error states for every async view. Surface errors in plain language with a retry, and log details to the console in development only. - Keep secrets out of the bundle. Only publishable keys (for example a Supabase anon key) belong in client code; service-role keys, API keys and webhooks live in server or edge-function environment variables. ### Data and auth baseline (full-stack entries) - Enable Row Level Security on every table before inserting data. Default-deny, then add owner-scoped policies (`auth.uid() = user_id`) and explicit role checks for admin views. - Store roles in a separate table checked by a security-definer function, never in a user-editable profile field. - Upload files to private storage buckets with size and MIME limits, and serve them through signed URLs. - Rate-limit public endpoints (forms, auth, AI calls) and add a honeypot field or captcha to anonymous forms. - Take payments through a hosted checkout and verify webhooks by signature. Never handle raw card data. ### Accessibility and UX baseline - Target WCAG 2.2 AA: 4.5:1 contrast for normal text and 3:1 for large text, input borders, focus rings and meaningful icons or chart lines. Every palette in this catalog lists its verified pairs; re-check with a contrast tool after any colour change. - Keep body text at 16px or larger with 1.5 line height, nothing below 12px, no light weights under 24px, and uppercase only for short labels. - Give every interactive element a visible focus ring, full keyboard support, semantic landmarks, labelled form fields, and alt text on meaningful images. - Respect `prefers-reduced-motion` for every animation. Give drag-and-drop and carousels keyboard and button alternatives. - Build mobile-first and test at 375px, 768px and 1280px. ### Content guardrails - Use original copy, fictional sample data and placeholder or licensed imagery. Do not reuse another product's name, logo, screenshots or marketing text. - Label demo testimonials and metrics as samples. Collect the minimum personal data the feature needs. ### SaaS screen baseline - Design every screen for its full set of states: first-run empty, loading skeleton, partial data, error with retry, permission-denied, and success feedback. Each entry lists the states its screen needs. - Keep destructive actions (delete, revoke, downgrade, remove member) behind a confirmation that names the object, and prefer undo over a second dialog where the action is reversible. - Enforce authorisation on the server for every action a screen exposes. Hiding a button is not access control; check the role again in the API or RLS policy. - Never show secrets (API keys, tokens) in full after creation. Show them once, then mask them, and offer rotate and revoke. - Keep the app shell (navigation, workspace switcher, account menu) consistent across screens, and preserve filters, sort and scroll position when the user navigates back.
### Goal
Build **Seekroom**, the search modal of an AI chat assistant. From anywhere, Cmd/Ctrl+K opens a compact, centred modal on a warm off-white canvas. With an empty query it lists recent chats and projects with relative times; typing searches titles and message text and shows snippets.
### Stack
React + TypeScript + Vite, Tailwind CSS, shadcn/ui Command (cmdk) and Dialog on Radix, lucide-react, TanStack Query, date-fns for relative times. Supabase Postgres full-text search on conversations and messages through an RPC.
### Pages & layout
1. **Icon rail** (56px): sidebar toggle, new chat (+), search, chats, projects, artifacts, code; download and avatar at the bottom.
2. **Canvas**: warm off-white with an empty chat composer behind the modal.
3. **Search modal** (440px, centred): search icon, input "Search chats and projects", close x; rows with a type icon, title and relative time on the right; the active row has a warm tint and a ↵ hint in place of the time. Example rows: "Getting started guide" (project), "Quarterly planning notes", "Image-to-text automation", "Spreadsheet integration setup", "Accessibility audit fixes", "Plan a memorable team offsite".
4. With a query: two small sections ("Projects", "Chats"), and snippets under titles when the match is in message content.
5. Mobile: full-screen sheet.
### Design system
- Colors: `--canvas: #edece8` (app background), `--bg: #ffffff` (modal background), `--active: #f5f0e6` (highlighted row), `--fg: #1f1e1b` (row titles), `--muted: #6b6a64` (relative times, placeholder), `--rail-icon: #3d3c38` (rail icon colour), `--accent: #b8572f` (focus ring, active bar), `--border: #dedcd4` (modal hairline (decorative)), `--avatar: #3d3c38` (avatar circle).
- Fonts: Inter (similar to the observed soft grotesk): input 16px, row titles 16px/400, times 13px muted; line-height 1.5.
- Spacing: 4px base; input row 48px; rows 36px with 12px padding and 4px inset from the modal edge.
- Radius: modal 12px; rows 6px.
- Shadows: `0 12px 40px rgba(40,30,10,.12)`; scrim 20% warm grey.
- Motion: 120ms fade and scale; none under reduced motion.
### Components & interactions
IconRail, SearchDialog, SearchInput (spinner while fetching, close button), ResultList (virtualised after 50), ResultRow (icon, title, optional snippet with bold matches, time or ↵), SectionLabel, EmptyRecents, NoResults, ErrorLine with Retry. Hover and arrows share the active state; Enter opens the item and closes the modal.
### Data & state
Tables: `conversations(id, user_id, title, project_id, updated_at)`, `messages(id, conversation_id, role, content, created_at, tsv)`, `projects(id, user_id, name, updated_at)`. RPC `search_history(q)` returns `{kind, id, title, snippet, updated_at}`. Seed 20 invented chats and 3 projects. Handle states: recents, searching, results with snippets, no results, error, long titles, new account.
### Accessibility
Use combobox and listbox semantics with aria-activedescendant. Row names include the type and time. Use `<mark>` for matches, styled bold. The close button has a label; Esc closes; focus returns to the trigger. Focus ring: 2px terracotta.
Verified contrast: row title on modal: #1f1e1b on #ffffff = 16.67:1; muted time on modal: #6b6a64 on #ffffff = 5.43:1; muted time on active row: #6b6a64 on #f5f0e6 = 4.78:1; title on active row: #1f1e1b on #f5f0e6 = 14.68:1; rail icons on canvas: #3d3c38 on #edece8 = 9.34:1; focus ring on modal: #b8572f on #ffffff = 4.73:1; text on canvas: #1f1e1b on #edece8 = 14.10:1.
### Security
RLS on conversations, messages and projects: `user_id = auth.uid()`. The RPC is `security invoker` so RLS applies. Generate snippets server-side with `ts_headline` and escape them before rendering. Exclude soft-deleted chats and honour the user's retention setting.
### Performance & SEO
Recents come from a cached query and open instantly. Debounce search to 200ms and cancel stale requests. Use a GIN index on the tsvector. The app is noindex.
### Guardrails
- Invented chat titles; no real product names in sample data.
- Keep the modal compact and never cover the whole canvas on desktop.
- Acceptance criteria: (1) Cmd/Ctrl+K opens and Esc closes; (2) the empty query shows recents; (3) content matches show snippets; (4) keyboard navigation is complete; (5) all states render; (6) RLS prevents cross-user results.