HQ optimizes for fast, low-ceremony record manipulation. The default is to edit in place; the second-best is aDocumentation Index
Fetch the complete documentation index at: https://docs.yourhq.ai/llms.txt
Use this file to discover all available pages before exploring further.
SidePanel alongside the list; modal dialogs are reserved for focused decisions, and wizards for genuine multi-step flows. This page is the rule set.
Choosing a creation surface
Three options, picked by user mode — not by field count:| Surface | When | Reference |
|---|---|---|
SidePanel | Sustained record editing. The user is browsing a list and adding or editing one of its rows. The panel staying alongside the list reinforces “you’re still in the list, just editing one thing in it.” Contacts, organizations, knowledge items, collection records. | shared/side-panel.tsx, crm/contact-form.tsx |
Dialog | Quick capture and focused decisions. The user is mid-flow — possibly on an unrelated screen — and wants to dump intent into the system and get back to what they were doing. Tasks (regardless of field count: a task is tactical capture, not record management), confirmations, two-step OAuth handoffs. | ui/dialog.tsx, tasks/task-form.tsx |
| Wizard | Multi-step provisioning where steps depend on each other or async work happens between steps. | agents/agent-create-wizard.tsx, onboarding/wizard/onboarding-wizard.tsx |
SidePanel shape
Width presets: md (480px), lg (560px, the default), xl (720px). Sticky footer carries the primary “Save” / “Create” button on the right and a ghost-variant “Cancel” on its left. Body uses the PropertyRow layout — 28px label column, flex-1 control. Inline Input, Select, TagInput, DynamicField, and Textarea primitives for the controls.
Dialog shape
Square modal overlay (bg-black/40 light, bg-black/60 dark). Title at .text-title, optional description at .text-body muted. The form body should fit without scrolling. Two buttons in the footer.
Wizard shape
Multi-step modal. Step indicator in the header (e.g. “2 of 5”). Steps that perform async work display aSpinner with a status line — the user sees the system thinking. Back/Next buttons; Back is disabled or hidden on irreversible steps (after a network call has succeeded). Status polling drives forward progress automatically when ready.
Inline editing
Editing in place is the default for any property already visible. Users should not have to open a form to change one value.PropertyRow layout
The standard “label : value” row, used in detail right rails and inline-edit forms. 28px label column on the left (.text-label style), flex-1 control on the right. Borders and padding fade until hover, where the control reveals an accent-colored fill cue.
Inline text, tags, and links
Three small primitives composed inside detail views:InlineText— single-line text. Click to edit, Enter or blur saves, Escape cancels. Optimistic UI. Used for names, emails, phone numbers, anything single-line.InlineTags— multi-value chip input. Comma or Enter commits a tag. Click an X on a chip to remove. Used for tags, multi-select properties.InlineLink— URL with an external-link icon. Clicking the icon opens the link; clicking the text enters edit mode.
crm/contact-detail-view.tsx. When a new module needs the same primitives, lift them into components/shared/ — they are about to become composites.
Type-dispatched cell editing
apps/ui/src/components/collections/collection-cell.tsx is the canonical pattern for inline edit across many field types. One entry point dispatches by field_type:
| Type | Editor |
|---|---|
text, email, phone | Inline text input |
number | Inline number input with parse / format |
date, datetime | DatePickerButton popover |
boolean | Checkbox |
select | Select |
multi_select | Combobox (multi) |
url | Inline link with external-link icon |
rich_text | Textarea (or Novel where rendered in detail) |
relation | EntityLinkPicker |
collection-cell.tsx rather than building a parallel cell renderer.
Rich text vs plaintext
Two surfaces; pick by what the user is writing. Novel (TipTap wrapper) is for documents — surfaces where structure adds value. Headings, lists, task lists, code blocks, embeds, links. Long-form. The user is composing something they’ll come back to and read.apps/ui/src/components/knowledge/novel-editor.tsx
The Novel setup at HQ:
- Extensions — StarterKit, TaskList, TaskItem, HorizontalRule, Link, Image, Underline, CodeBlockLowlight.
- Slash commands —
/opens the command palette inside the editor. - Bubble menu — surfaces formatting (bold, italic, underline, code, link) on selection.
- Syntax highlighting —
lowlightwith the common language set. - Auto-save — on blur. The hosting component owns the persistence call.
globals.css under .ProseMirror (lines ~265-450). The editor uses a slightly larger type scale than the app body — H1 is 30px, body line-height is 1.7 — because long-form reading benefits from more vertical rhythm.
Knowledge pages and skills are the canonical Novel surfaces in HQ today.
Textarea is for short-form notes — surfaces where the user is dumping a sentence or a paragraph and getting on with their day. Task descriptions, contact notes, comment bodies, agent prompts, slug fields. The user is not composing a document; they are jotting.
A surface should stay on textarea unless three things are true:
- The content is genuinely document-shaped — multiple paragraphs, lists, sections.
- Downstream consumers (search indexing, agent context, list previews) can handle structured content cleanly, or have been updated to.
- The user mode is composition, not capture — they’re not entering this from a quick-create dialog.
Forms
When you do need a form (validation, multi-field saves, server actions), use React Hook Form + Zod through theForm primitives.
apps/ui/src/components/ui/form.tsx
The stack:
Form— wrapsFormProvider. Pass the RHFuseFormreturn.FormField—Controllerwrapper for a single field. Passcontrolandname.FormItem— auto-IDs the field and binds label/control/description/message.FormLabel— uses RadixLabel; auto-togglesdata-error="true"on invalid.FormControl— slot wrapper; wiresaria-describedbyandaria-invalid.FormDescription— helper text below the input.FormMessage— auto-extracts the field’s error message from RHF state.
FormLabel turns destructive on data-error, FormControl border and ring shift to destructive on aria-invalid. Don’t hand-roll error styles per form.
Schema lives next to the component (const schema = z.object({ ... })). Keep it simple — Zod is the validation engine, not a domain modeling tool. Database types are imported from lib/<module>/types.ts.
Custom fields
For any record with user-defined fields (contacts, organizations, collections, tasks), useDynamicField and DynamicFieldGroup from shared/. They take a FieldDefinition and render the right editor. Don’t branch on field type inside feature components.
Save semantics
- Inline edits save on blur, optimistic UI, toast on error. No explicit Save button.
SidePanelforms save on the explicit Save button. Disable the button during the async call; show a spinner inside it.- Wizards save per-step. The Next button is the save action.
- Cell edits in tables save on blur or Enter. Escape reverts.
audit_log table). Feature code does not need to log inline edits — the database trigger does.
Cancel and dismiss
- Inline edits: Escape reverts and closes the editor.
SidePanel/Dialog: Escape closes; clicking outside closes; explicit Cancel button closes. If there are unsaved changes, ask once viaConfirmDialog(warning tone).- Wizards: dismissal mid-flow is destructive on irreversible steps — disable outside-click close and prompt the user via
ConfirmDialog(destructive tone) for explicit X-button closes.
When to add to shared/
If a form pattern is reused in two modules, lift it. Three places to look first when adding:
- A new typed field editor → extend
collection-cell.tsxandDynamicField. - A new inline-edit primitive (e.g. inline date) → add to
components/shared/next toinline-edit.tsx. - A new wizard pattern → start inside the feature module; promote to
shared/only on the second use.

