Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.yourhq.ai/llms.txt

Use this file to discover all available pages before exploring further.

HQ renders the same record in multiple ways depending on what the user is doing. This page documents the two halves of that system: how lists are built, and how a record opens.

Lists: the multi-view orchestrator

Every module that shows a list of structured records follows the same pattern. One hook owns the state. Many view components consume it. Switching between views never reloads data, never loses filters, never re-fetches. Canonical references: The hook returns:
  • Datacontacts (filtered), allContacts, supporting collections (campaigns, pipelineStages).
  • Stateloading, viewMode, sorting, filters, selection.
  • PersistenceviewMode to localStorage, filters and sorting to URL search params.
  • Actionsactions.archive, actions.delete, actions.changeStatus, etc.
  • Formform.open, form.close, form.editing for the create/edit SidePanel.
  • Realtime — subscribed via use-realtime-sync.
The orchestrator component renders a FilterBar, a view-mode ToggleGroup, and the active view component. It does not own state — it passes hook outputs straight through.

The four view shapes

Pick view shapes by data shape, not by aesthetic preference. Modules support whichever shapes their data justifies.

Table

The default. Built on DataTable (TanStack React Table). Sortable headers, sticky-header support, three row-height variants (compact / normal / comfortable), row click handlers, loading-skeleton fallback, empty-state slot. Column visibility is owned by the ColumnToggle composite, persisted via use-column-visibility to localStorage. Title fields stay clickable and open the detail surface; secondary cells with mutations (status, priority) use inline edit through their type-specific control. Reach for table when the data is wide and users compare across rows.

Cards

Responsive grid (grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4). Each card is a richer preview — avatar or icon, title, metadata row, badges. Click opens the detail surface. Reach for cards when records have an identity (a person, an agent, an organization) that benefits from visual presence.

Kanban

Status-grouped columns. Each column is a status value (todo / in_progress / blocked / done; or a select field’s options for collections). Drag a card between columns to change its status. An InlineCreateRow sits at the bottom of each column for fast adds. Drag-and-drop must use @dnd-kit (see interaction — that is the canonical drag-drop library across the app). Reach for kanban when the central question is “what state is this record in?” — a tasks board, a deals pipeline, a content calendar by stage.

Calendar

Month grid driven by a date-typed field on the record. Each date cell shows record indicators; click a date to create. Built with date-fns (startOfMonth, eachDayOfInterval, etc.). References: apps/ui/src/components/tasks/task-calendar-view.tsx, apps/ui/src/components/collections/collection-calendar-view.tsx. Reach for calendar when records have a meaningful date field and users need to see them on a timeline.

State persistence

StatePersisted toWhy
viewMode (table / cards / kanban / calendar)localStorageUser preference, survives reloads
filters (search, status, priority, archived)URL search paramsShareable, browser back works
sortingURL search paramsSame
Column visibilitylocalStorage per modulePer-user table layout
SelectionIn-memory onlyCleared on navigation
URL-first for anything a user might link to or share; localStorage for personal preferences.

Detail surfaces

Two shapes, picked by record weight:

Full page

Used for heavy entities — records with timelines, multiple tabs, related records, sidebars worth of metadata. Examples: a contact (notes + interactions + draft sets + linked tasks), an agent (overview + files + usage + knowledge + routines + inbox), a knowledge page (rich editor + agent assignments + embedding status), an organization. Layout convention:
  • A DetailHeader at the top: back link, breadcrumb, title with inline edit, action buttons (copy, archive, delete) on the right.
  • A two-column content area below: main content on the left, a sticky DetailSidebar (280px) on the right with status, priority, relationships, metadata timestamps.
  • On tablet/mobile, the sidebar folds into a “Details” button that opens a Sheet.
Routes for full-page detail: /dashboard/<module>/[id]. Server-side fetch in page.tsx, hydrate the client component with full data.

Slide-over (SidePanel)

Used for light records — collection records, ad-hoc edits, anything that fits comfortably in 560px and benefits from staying alongside the list the user came from. The body is a label-and-control grid. Inline edits commit on blur; explicit Save buttons are reserved for the rare case where validation can’t run inline (a multi-field form needing cross-field checks). Used for capture-style entities — records the user creates and edits in quick-capture mode rather than from a list-management workflow. Tasks are the canonical example: their form serves as both create and detail surface, and a centered Dialog matches the tactical “I just thought of this” mode better than a slide-over would. When a record is opened from a list, prefer the slide-over. When the same form is also the create surface invoked from ”+ Task” anywhere in the app, use the dialog for both — consistency between create and detail beats consistency with other modules’ detail shape (see creation and editing).

Filter bar

apps/ui/src/components/shared/filter-bar.tsx Universal pattern across modules: search input on the left, filter controls in the middle, action buttons (column toggle, archive toggle, view-mode switch, primary ”+ Create” button) on the right, item count on the far right. Modules pass their own filter controls in as children — the bar provides the layout, not the controls.

Empty states for lists

  • No data at all — welcoming EmptyState with the module’s icon, an explanation, a primary “Create your first X” action.
  • Data exists but filters return nothingEmptyState variant filtered with a “Clear filters” secondary action.
  • Loading first paintLoadingSkeleton matching the active view (table, cards, list, feed, or detail).
Never show a generic spinner for the first load of a list — match the skeleton to the layout it replaces.

Realtime

Every list subscribes to Supabase Realtime via use-realtime-sync (or the simpler use-realtime for single-table cases). Inserts, updates, and deletes from any session — including from agents acting on the workspace — flow through within a few hundred milliseconds. The hook handles the subscription lifecycle, RLS-scoped event filtering, and reconciliation with optimistic local mutations. Lists should not poll. Users should never need to refresh.