Overview
AGENT PROMPT SNIPPET app/components/ui/Avatars.tsx
CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
dùng component Avatars (variant="primary", size="md") từ app/components/ui/Avatars.tsx theo docs/ui/components/avatar.md
💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

Avatars

Load when: rendering a profile image, initials fallback, avatar size or accessible identity label.

Also read: Shared component guidelines.

Do not load for: generic icons or identity authorization policy.

BEM identity: crm-avatar and crm-initials-avatar are targets; size props map to --size-. No target block is currently emitted in rendered HTML.

Use Avatar, AvatarImage, and AvatarFallback from @/components/ui/avatar when an image can load progressively. Avatar sizes are sm, default, and lg; group utilities are also available.

Use InitialsAvatar from @/shared/ui/primitives/initials-avatar when no image is available and a deterministic text-derived color is useful. Its size is sm, md, or lg; it assigns role="img" and labels the result with the supplied name.

Image alt must identify the person when the image conveys identity. Do not use initials alone as reliable identity proof; the domain must supply the canonical display name and access policy.

Source: apps/frontend/src/components/ui/avatar.tsx and apps/frontend/src/shared/ui/primitives/initials-avatar.tsx.

AGENT PROMPT SNIPPET app/components/ui/Badge.tsx
CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
dùng component Badge (variant="primary", size="md") từ app/components/ui/Badge.tsx theo docs/ui/components/badge.md
💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

Badge

Load when: presenting compact status or metadata and choosing a semantic/tinted Badge variant.

Also read: Shared component guidelines and Tokens for semantic roles.

Do not load for: actionable buttons or full feedback/error states.

BEM identity: crm-badge is the target block; real variant values map to crm-badge--variant-. The block and modifiers are not currently emitted in rendered HTML.

Import Badge from @/components/ui/badge. Use for compact metadata or status, not as the only communication of a consequential state. The verified hook is data-slot="badge".

variant defaults to default. Use lightprimary, lightsecondary, lightsuccess, lightwarning, lighterror, or lightinfo for low-emphasis semantic status; use outline for neutral metadata. default, secondary, destructive, ghost, and link follow their matching semantic roles.

Badges keep one line and clip overflow. Make the text self-explanatory; an icon alone is insufficient for a status. The component can render another semantic tag through its Base UI render prop, but do not turn a non-actionable status into a button merely for styling.

Source: apps/frontend/src/components/ui/badge.tsx.

Contract previewLocal documentation state; not production runtime
QualifiedChanges were saved successfully.
AGENT PROMPT SNIPPET app/components/ui/Button.tsx
CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
dùng component Button (variant="primary", size="md") từ app/components/ui/Button.tsx theo docs/ui/components/button.md
💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

Button

Load when: implementing an action, choosing Button variant/size/loading or reviewing icon-only accessibility.

Also read: Shared component guidelines and Design guidelines for action hierarchy.

Do not load for: route navigation that belongs to a link.

BEM identity: crm-button is emitted by the shared root. Real variant values map to crm-button--variant- and loading={true} maps to crm-button--loading; size modifiers and crm-button__icon remain unassigned. The Deal Sheet browser check verified the base block.

Import Button from @/components/ui/button. Use it for an action; use a link component for navigation.

PropDefaultRule
variantdefaultChoose default for primary action, outline for secondary, ghost for low-emphasis, destructive for risk, link for inline navigation-style action, or secondary for the secondary brand role.
sizedefaultUse xs, sm, lg, or icon sizes only when adjacent controls use that density. Icon-only sizes require an accessible name.
loadingfalseSets disabled and aria-busy; callers still supply meaningful loading content if needed.
<Button type="button" loading={isSaving} onClick={save}>Lưu</Button>

Focus-visible and invalid states use the semantic ring/destructive roles. Disabled controls are non-interactive and dimmed. The component does not show a spinner, perform confirmation, or persist data.

Source: apps/frontend/src/components/ui/button.tsx.

Contract previewLocal documentation state; not production runtime
AGENT PROMPT SNIPPET app/components/ui/Cards.tsx
CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
dùng component Cards (variant="primary", size="md") từ app/components/ui/Cards.tsx theo docs/ui/components/cards.md
💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

Cards

Load when: grouping related information/actions in a Card or using the dashboard summary-card composition.

Also read: Shared component guidelines and the owning layout/pattern.

Do not load for: page grid/shell structure or automatically clickable containers.

BEM identity: crm-card is emitted on the shared root and verified in the Deal Sheet. Card elements and size modifiers remain unassigned until their owners emit them.

Import the Card family from @/components/ui/card. Use it to group related information or actions; do not use it as a substitute for page layout.

PartRole
CardSurface root; size is default or sm.
CardHeader, CardTitle, CardDescription, CardActionHeading hierarchy and a trailing header action.
CardContentMain padded content.
CardFooterFooter separated with a top border and muted background.

The root uses data-slot="card"; subparts use corresponding data-slot values. Card radius, surface, text, and border derive from semantic tokens. A card is not automatically clickable: wrap the whole card in a route link only when the whole summary navigates, as StatCard does.

Source: apps/frontend/src/components/ui/card.tsx and apps/frontend/src/modules/dashboard/components/stat-card.tsx.

Contract previewLocal documentation state; not production runtime
Account summaryUpdated just now
128Active records
+12% from last period
AGENT PROMPT SNIPPET app/components/ui/Checkbox.tsx
CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
dùng component Checkbox (variant="primary", size="md") từ app/components/ui/Checkbox.tsx theo docs/ui/components/checkbox.md
💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

Checkbox

Load when: a form needs a binary, labelled choice.

Import Checkbox from @/components/ui/checkbox. Its rendered root emits crm-checkbox; the caller owns the associated label, validation and persisted value. It does not replace a domain-specific multi-state control.

Evidence: apps/frontend/src/components/ui/checkbox.tsx and Deal commerce composition.

Contract previewLocal documentation state; not production runtime
Type here
Choose an option to continue.
AGENT PROMPT SNIPPET app/components/ui/Shared component guidelines.tsx
CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
dùng component Shared component guidelines (variant="primary", size="md") từ app/components/ui/Shared component guidelines.tsx theo docs/ui/components/component-guidelines.md
💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

Shared component guidelines

Load when: creating, extending or reviewing a reusable component or deciding whether feature UI belongs in a shared owner.

Also read: the named component contract, Naming and DOM identity when identity/aliases/variants change, applicable design guidelines, tokens, and governance for ownership gaps.

Owner and use decision

1. Reuse the existing component unchanged when its behavior fits.

2. Extend its shared owner when the difference is a compatible reusable prop/state; check existing consumers and defaults.

3. Compose a feature adapter when only copy, data, permissions, validation or persistence differ.

4. Create a new shared owner only for a concrete recurring behavior that existing owners cannot reasonably represent; document the comparison.

Required component contract

Each reusable owner documents its canonical name/aliases, import, BEM block/stable elements/real modifier mappings and adoption status, use/avoid, anatomy, variants/sizes, key props/defaults/events, token/style rules, applicable states, keyboard/focus/announcement behavior, responsive/overflow behavior, ownership boundaries, minimal usage and source/demo/test evidence.

Describe the delta for applicable default, hover, focus, active/selected, disabled, read-only, loading, empty, error and success states. Do not list unsupported states or confuse disabled with read-only.

Implementation rules

  • Use actual public imports and semantic tokens. Preserve current APIs unless an accepted feature intentionally changes them.
  • Keep native semantics and ARIA truthful. Icon-only actions need an accessible name; loading and errors need the relevant busy/status/alert behavior.
  • The shared owner renders presentation and local interaction. The feature retains domain data, authorization, validation, confirmation and persistence.
  • Update the production owner and its owning UI concept when public interactive behavior changes. The generated portal is documentation, not a substitute for runtime behavior.
  • Follow the selected crm-* grammar in Naming and DOM identity. Keep target mappings distinct from source and rendered evidence; only retrofit DOM classes in authorized implementation work.
  • Validation

    Use source inspection, focused tests and rendered product output for public API/state changes and BEM, focus, layout, wrapping or responsive claims. The generated portal documents the contract but does not prove runtime geometry or screen-reader output. Follow Validation; unit tests alone do not prove those conditions.

    AGENT PROMPT SNIPPET app/components/ui/Deal form.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Deal form (variant="primary", size="md") từ app/components/ui/Deal form.tsx theo docs/ui/components/deal-form.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Deal form

    Load when: creating or reviewing /deals create/edit Sheet.

    DealFormSheet owns Sheet presentation and selects the approved xlarge drawer target; its current 90vw implementation matches the preferred ratio but does not yet use a typed size API. DealForm owns React Hook Form state and Deal business orchestration; DealFormSection composes Card and FormSectionHeading; DealLeadPicker owns only Deal-specific Lead presentation/query mapping. The form emits crm-deal-form; sections emit crm-deal-form-section and the supporting owners use their respective blocks.

    Pipeline remains first. Customer/details and commerce/billing retain the existing desktop 60/40 grid and mobile stacking. Pricing, payment locks, association loading, nested drawers, validation and persistence remain domain behavior and are not owned by shared UI.

    For an existing Deal, the Sheet must receive the full Deal detail contract so saved Lead, Account, buyer and associated Contact names render without identifier fallbacks. An ADMIN may edit Pipeline/Stage, Lead-derived customer context, products, order discount and billing only while the Deal is OPEN and not PAID. Other states keep those controls disabled and show the resume, reopen, role or payment reason. A changed Stage is submitted together with its Pipeline; changing Pipeline clears an invalid Stage. The current inactive Pipeline remains visible for history but is not a selectable target after moving away.

    Lead replacement reuses the create picker and association drawers. The selected buyer is the primary Contact and remains included in the associated Contact IDs. A legacy Deal without a saved billing snapshot hydrates invoice fields from the current authorized Contact/Account details, with its reusable invoice profile taking precedence and Lead identity as fallback. An existing billing snapshot remains authoritative and unchanged; manual billing edits survive customer refreshes. Legacy amount-only Deals require an explicit commerce-adoption action and never receive a synthesized Product. Request-affecting edits on a PENDING or price-confirmed Deal expose the explicit supersede acknowledgement; the informational discount code alone does not alter pricing or supersede a payment request. Cancel and nested-drawer interaction retain draft-only behavior until the single update request succeeds.

    New commerce Deals and explicit legacy commerce adoption require payment-ready invoice identity before submit: legal name and address for every recipient, company tax code for companies, delivery email when delivery is enabled, and both personal identity fields when either is supplied. An explicit billing edit on an existing commerce Deal must also satisfy that contract. Unrelated edits preserve incomplete historical billing snapshots rather than forcing migration. DealBillingFields renders the applicable native required semantics and inline validation through the existing Deal form owner.

    Evidence: Deal component tests, particularly deal-form-commerce.test.tsx and deal-customer-links.test.tsx.

    Contract previewLocal documentation state; not production runtime
    Visible only when it helps completion.Enter a valid value.
    AGENT PROMPT SNIPPET app/components/ui/Deal invoice status.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Deal invoice status (variant="primary", size="md") từ app/components/ui/Deal invoice status.tsx theo docs/ui/components/deal-invoice-status.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Deal invoice status

    Load when: displaying or changing invoice issuance state for a Deal.

    DealInvoiceStatusBadge presents NOT_REQUESTED, PENDING, ISSUED, and VOIDED independently from pipeline stage and payment status. DealInvoiceStatusPanel emits crm-deal-invoice-status, composes the shared Badge, Select, and Button owners, and persists through the dedicated Deal invoice-status command.

    The UI does not offer ISSUED before full payment. The backend remains authoritative and rejects issuance unless paymentStatus is PAID. An issued invoice can only move to VOIDED; voided invoices can be queued or reissued. Existing Deals default to NOT_REQUESTED.

    Evidence: deal-invoice-status.test.tsx and backend deal-invoice-status.test.ts.

    Contract previewLocal documentation state; not production runtime
    Chờ xuất hóa đơnChanges were saved successfully.
    AGENT PROMPT SNIPPET app/components/ui/Deal payment.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Deal payment (variant="primary", size="md") từ app/components/ui/Deal payment.tsx theo docs/ui/components/deal-payment.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Deal payment

    Load when: creating or reviewing the payment area in Deal detail.

    DealPaymentPanel owns payment-state queries, QR request presentation, receipt history and role-based actions. It emits crm-deal-payment-panel; disabled state emits crm-deal-payment-panel--disabled, and the server-authorized ADMIN selector emits crm-deal-payment-panel__account-selector. The selector displays only masked labels supplied by the server, defaults to the configured account and locks to an existing immutable request. Non-Admins never receive or render the alternate-account catalog.

    When SePay is disabled, the panel hides selector/create/copy/download/open-QR actions and stored actionable instructions while retaining request history, receipt history and eligible audited manual confirmation. DealPaymentManualConfirmationDialog is the bounded domain confirmation owner and emits crm-deal-payment-manual-confirmation; it composes the shared Dialog, FormRow, Input, Textarea and Button owners.

    Manual confirmation is available only before payment on an eligible commerce Deal. The dialog requires the exact current VND total; confirmation note, bank transaction ID, and actual transfer content are optional evidence. Submission remains pending-safe and dismissible before submit. Backend permissions, row locking, Payment Receipt creation, actor audit, confirmation time and settlement are domain behavior rather than shared UI concerns.

    Evidence: deal-payment-panel.test.tsx and manual-payment-confirmation.test.ts.

    Contract previewLocal documentation state; not production runtime
    Visible only when it helps completion.Enter a valid value.
    AGENT PROMPT SNIPPET app/components/ui/DetailEditableField.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component DetailEditableField (variant="primary", size="md") từ app/components/ui/DetailEditableField.tsx theo docs/ui/components/detail-editable-field.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    DetailEditableField

    Load when: rendering a labeled value that swaps to a caller-owned editor in an entity detail panel.

    Import DetailEditableField from @/components/ui/detail-editable-field. It owns the 38% label/value layout and composes InlineEditableValue; callers retain editor controls, permissions, autosave, validation, and persistence.

    BEM identity: crm-detail-editable-field; __label and __value are its stable anatomy. Source verification only.

    AccountDetailEditableField remains a compatibility alias. New Lead and Contact callers must import the shared owner directly.

    Contract previewLocal documentation state; not production runtime
    Visible only when it helps completion.Enter a valid value.
    AGENT PROMPT SNIPPET app/components/ui/Entity references.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Entity references (variant="primary", size="md") từ app/components/ui/Entity references.tsx theo docs/ui/components/entity-reference.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Entity references

    Load when: implementing a picker or read/edit/clear association field while keeping detail navigation distinct.

    Also read: Editable association, Shared component guidelines and the domain form policy.

    Do not load for: deleting the referenced entity or selecting a simple enum.

    BEM identity: crm-entity-picker is emitted and verified in source; crm-entity-reference-field and crm-inline-editable-value remain targets. Applicable owner states map to --loading, --error, --editing and --disabled; stable elements remain unassigned until an authorized audit identifies owned anatomy.

    Use these owners for an association UI while retaining domain query, authorization, creation, and save behavior in the caller.

    OwnerUseImportant boundary
    EntityPickerControlled popover shell with injected search, result list, optional create action, and working-tree allowClear/onClear supportCaller owns search results, selected value, create route, and clear semantics.
    EntityReferenceFieldRead/edit shell for one associationonClear clears the caller's selection only; it never deletes the referenced entity.
    InlineEditableValueDisplay-to-editor transition for scalar or picker detail fieldsCaller owns commit/cancel and editor behavior.

    EntityReferenceField exposes loading, error, edit, open-detail, and clear affordances. It blocks edits while disabled or loading; when a value can open detail, navigation is separate from the edit affordance. InlineEditableValue focuses the first editor control, handles Escape, and only commits Enter for its scalar default (commitOnEnter=true).

    Use a specific editLabel and clearLabel for icon-only controls. Preserve unsaved draft state when an association opens a nested detail UI.

    Working-tree extension under validation

    The current uncommitted EntityPicker source can render an Xóa chọn action when allowClear, a selected label and onClear are present. It handles click, Enter and Space without opening the picker. This extension has not been accepted as a stable shared contract or verified with a focused rendered test in this initialization; inspect the pending source/test state before relying on disabled/loading or nested-trigger behavior.

    Source: apps/frontend/src/components/ui/entity-picker.tsx, apps/frontend/src/components/ui/entity-reference-field.tsx, and apps/frontend/src/components/ui/inline-editable-value.tsx.

    Contract previewLocal documentation state; not production runtime
    ACExample Company
    AGENT PROMPT SNIPPET app/components/ui/Feedback and status.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Feedback and status (variant="primary", size="md") từ app/components/ui/Feedback and status.tsx theo docs/ui/components/feedback.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Feedback and status

    Load when: choosing progress, skeleton, private-field, toast, loading, empty, error or success presentation.

    Also read: Shared component guidelines and the owning form/pattern for persistent recovery.

    Do not load for: semantic status labels that only need a Badge.

    BEM identity: crm-progress, crm-skeleton, crm-private-field and crm-toaster are target blocks. Stable element/modifier mappings remain unassigned until their shared owners are adopted and rendered output is inspected; no target block is currently emitted.

    OwnerUse
    ProgressQuantified task progress with a label/value when a percentage needs explanation.
    SkeletonTemporary structural placeholder while known content is loading.
    PrivateFieldPermission-limited field placeholder; do not replace it with an empty value that implies absence.
    ToasterApp-level Sonner host, already mounted by AppProviders at top center.

    Use a real empty state when no data exists, an error state when a request fails, and a disabled/loading action guard while an operation is pending. Toasts are supplemental feedback; do not rely on them as the only indication that a form field failed validation or a destructive operation completed.

    Source: apps/frontend/src/components/ui/progress.tsx, apps/frontend/src/components/ui/skeleton.tsx, apps/frontend/src/components/ui/private-field.tsx, apps/frontend/src/components/ui/sonner.tsx, and apps/frontend/src/app/providers/app-providers.tsx.

    Contract previewLocal documentation state; not production runtime
    Contact savedChanges were saved successfully.
    AGENT PROMPT SNIPPET app/components/ui/Form row.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Form row (variant="primary", size="md") từ app/components/ui/Form row.tsx theo docs/ui/components/form-row.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Form row

    Load when: laying out a label, control, helper and error or choosing responsive/top/left field alignment.

    Also read: Inputs and selection, Shared component guidelines and the owning form pattern.

    Do not load for: unlabeled decorative content or form submission logic.

    BEM identity: crm-form-row is emitted; __label and __control map to owned anatomy. __help, __error and layout modifiers remain unassigned until an owner emits them. Source verification covers the adopted classes.

    Import FormRow from @/components/ui/form-row for a labeled control or a small grouped control set. It does not own form submission or validation.

    PropDefaultRule
    labelrequiredVisible label text.
    requiredfalseAdds a visual asterisk and passes aria-required through the render function.
    error / helpabsentRenders helper and error text; error receives role="alert".
    layoutresponsiveChoose top for compact/narrow content; choose left for a proportional persistent column; choose fixed or fixed-top only when preserving the established 160px CRM entity-form geometry.

    Use the render-function child for exactly one associated control so id, aria-describedby, aria-invalid, and aria-required reach it:

    <FormRow label="Tên" required error={errors.name}>
      {(control) => <Input {...control} value={name} onChange={onChange} />}
    </FormRow>

    For grouped controls, use normal children; the group receives the label association. Current root hook: data-slot="form-row".

    Source: apps/frontend/src/components/ui/form-row.tsx.

    Contract previewLocal documentation state; not production runtime
    Visible only when it helps completion.Enter a valid value.
    AGENT PROMPT SNIPPET app/components/ui/FormSectionHeading.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component FormSectionHeading (variant="primary", size="md") từ app/components/ui/FormSectionHeading.tsx theo docs/ui/components/form-section-heading.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    FormSectionHeading

    Load when: composing a repeated titled form group.

    Import FormSectionHeading from @/components/ui/form-section-heading. It renders an h3 divider with the crm-form-section-heading block. Use it inside a feature adapter or Card; it owns no data, validation, save action, or responsive layout.

    Evidence: source apps/frontend/src/components/ui/form-section-heading.tsx; Deal adapter deal-form-sections.tsx.

    Components

    Use this index when selecting, creating or changing reusable UI. Read Shared component guidelines, then load only the named component contract. For identity changes, also read Naming and DOM identity.

    Actions

    Component familyCanonical ownerBEM target / rendered statusStatusContract
    Buttoncomponents/ui/button.tsxcrm-button; verified in rendered Deal SheetImplementedButton

    Inputs and associations

    Component familyCanonical ownerBEM target / rendered statusStatusContract
    Input, Textarea, Select, SearchableSelectshared UI ownerscrm-input, crm-textarea, crm-searchable-select verified in source; crm-select remains a targetImplementedInputs and selection
    FormRowcomponents/ui/form-row.tsxcrm-form-row; verified in sourceImplementedForm row
    FormSectionHeadingcomponents/ui/form-section-heading.tsxcrm-form-section-heading; verified in sourceImplementedFormSectionHeading
    DetailEditableFieldcomponents/ui/detail-editable-field.tsxcrm-detail-editable-field; verified in sourceImplementedDetailEditableField
    SearchableSelectshared/ui/primitives/searchable-select.tsxcrm-searchable-select; verified in sourceImplementedSearchableSelect
    Deal form adaptersmodules/deals/components/crm-deal-form and Deal blocks; full-detail edit, protected-state, customer-backed billing hydration and atomic Pipeline/Stage behavior verified in source/testsImplementedDeal form
    Deal payment adaptersmodules/deals/components/crm-deal-payment-panel, --disabled, __account-selector, crm-deal-payment-manual-confirmation; Admin-only masked selection and default-off behavior verified in source/testsImplementedDeal payment
    Deal invoice status adaptermodules/deals/components/deal-invoice-status.tsxcrm-deal-invoice-status; verified in sourceImplementedDeal invoice status
    Lead form and detail adaptersmodules/leads/components/crm-lead-* form/detail blocks; verified in sourceImplementedLead forms and detail adapters
    EntityPicker, EntityReferenceField, InlineEditableValueshared UI ownerscrm-entity-picker verified in source; remaining blocks are targetsImplementedEntity references

    Containment and navigation

    Component familyCanonical ownerBEM target / rendered statusStatusContract
    Cardcomponents/ui/card.tsxcrm-card; verified in rendered Deal SheetImplementedCards
    Sheet, Dialog, AlertDialog, Popovershared UI ownerscrm-sheet and crm-popover verified in source; Sheet size modifiers and Dialog/AlertDialog blocks remain targetsImplemented with drawer-size gapOverlays, Popover
    Sidebarcomponents/ui/sidebar.tsxcrm-sidebar target; missing in DOM, data-slot/data-sidebar verifiedDocumentedSidebar

    Data display and feedback

    Component familyCanonical ownerBEM target / rendered statusStatusContract
    Badgecomponents/ui/badge.tsxcrm-badge target; missing in DOM, data-slot="badge" verifiedDocumentedBadge
    Table and AG Grid themeshared UI owner and globalscrm-table, crm-data-grid targets; missing in DOMDocumentedTables
    Avatar and InitialsAvatarshared UI ownerscrm-avatar, crm-initials-avatar targets; missing in DOMDocumentedAvatars
    Progress, Skeleton, Sonner, PrivateFieldshared UI ownerscrm-progress, crm-skeleton, crm-toaster, crm-private-field targets; missing in DOMDocumentedFeedback and status

    Inventory requiring source inspection

    Task signalsOwnerStatus
    breadcrumb, collapsible, menu, label, separator, switch, tooltipcomponents/ui/Implemented primitives; dedicated public contracts missing
    Checkboxcomponents/ui/checkbox.tsxcrm-checkbox; verified in sourceImplementedCheckbox

    Naming and DOM identity

    The selected block targets and their non-adopted status are governed by Naming and DOM identity. Do not infer stable elements or modifiers from target names; the named component concept and rendered evidence must establish those mappings.

    Shared ownership

    Shared UI owners render presentation and local interaction. Feature modules own queries, permissions, API calls, persistence, confirmation requirements, and route effects. The global rule and required states live in Shared component guidelines.

    AGENT PROMPT SNIPPET app/components/ui/Inputs and selection.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Inputs and selection (variant="primary", size="md") từ app/components/ui/Inputs and selection.tsx theo docs/ui/components/inputs.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Inputs and selection

    Load when: choosing Input, Textarea, Select or SearchableSelect and defining label, invalid, disabled or filtering behavior.

    Also read: Form row, Shared component guidelines and the domain validation policy.

    Do not load for: remote entity search/creation that belongs to EntityPicker.

    BEM identity: crm-input, crm-textarea and crm-searchable-select are emitted and verified in source. crm-select remains a target. Owned Select/SearchableSelect parts may become BEM elements; invalid, disabled or open modifiers must map to their real native/ARIA/prop states.

    Use the owner that matches the interaction; the feature owns value state, validation schema, loading, and persistence.

    NeedImportVerified behavior
    Single-line value@/components/ui/inputNative/Base UI input, 32px height, invalid ring via aria-invalid, disabled state.
    Multiline value@/components/ui/textareaNative textarea, 80px minimum height; caller controls resizing and validation.
    Enumerated option@/components/ui/selectBase UI Select root plus Trigger/Content/Item parts; SelectTrigger size is default or sm.
    Filterable in-memory option list@/shared/ui/primitives/searchable-selectPopover list, arrow-key filtering, Enter select, Escape close, optional clear.

    Always provide a visible label through FormRow or an equivalent label. Placeholder text is not a label. Use disabled when the user cannot interact; use read-only display when the value needs to remain available. SearchableSelect accepts already loaded options only and does not own remote search, permissions, or saving.

    Source: apps/frontend/src/components/ui/input.tsx, apps/frontend/src/components/ui/textarea.tsx, apps/frontend/src/components/ui/select.tsx, and apps/frontend/src/shared/ui/primitives/searchable-select.tsx.

    Contract previewLocal documentation state; not production runtime
    AGENT PROMPT SNIPPET app/components/ui/Lead forms and detail adapters.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Lead forms and detail adapters (variant="primary", size="md") từ app/components/ui/Lead forms and detail adapters.tsx theo docs/ui/components/lead-form.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Lead forms and detail adapters

    Load when: changing a Lead create Sheet, inline detail edit panel, or read-only detail surface.

    LeadFormSheet owns the create/edit Sheet and LeadForm owns its form anatomy. LeadFormRow remains a Lead-local adapter root, while its fixed 160px label/control layout and alignTop behavior are emitted by shared FormRow. LeadDetailEditRow similarly retains its 38% detail-panel grid. Both forms use the shared semantic FormSectionHeading.

    LeadDetailSection is the reusable Lead-domain section boundary for read-only, edit metadata, and feed history. LeadDetailKeyValueRow is its named read-only value row. These remain module adapters: Lead queries, permissions, autosave, triage, drawer stack, and persistence are not shared UI responsibilities.

    ConditionalFormattingPopover owns the Lead-grid conditional-format editor. ColorPicker and RuleRow are private anatomy beneath it; its fixed palette is documented in Tokens and themes. Stored legacy hexadecimal rules remain renderable, while newly created rules use the documented palette variables.

    DOM identity

  • crm-lead-form-sheet, crm-lead-form, crm-lead-form-row
  • crm-lead-detail-edit-panel, crm-lead-detail-edit-fields, crm-lead-detail-edit-row
  • crm-lead-info-read-only, crm-lead-detail-section, crm-lead-detail-key-value-row
  • crm-lead-detail-sheet, crm-lead-detail-sheet-section, crm-lead-detail-sheet-info-row
  • crm-lead-conditional-formatting, crm-lead-format-rule-row, crm-lead-format-color-picker
  • The named Lead adapter roots map to the rendered form, section, and value anatomy. LeadFormRow wraps the shared FormRow root, which emits the fixed label/control elements. /leads browser review verified rendered LeadFormSheet, LeadForm, LeadFormRow, and shared section-heading roots without submitting data. The detail and conditional-formatting adapters remain source-verified until those states are opened in a browser review.

    Evidence: Lead form and detail-panel focused tests, plus the owning component files in apps/frontend/src/modules/leads/components/.

    Contract previewLocal documentation state; not production runtime
    Visible only when it helps completion.Enter a valid value.
    AGENT PROMPT SNIPPET app/components/ui/Popover.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Popover (variant="primary", size="md") từ app/components/ui/Popover.tsx theo docs/ui/components/popover.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Popover

    Load when: an anchored transient surface is needed without a route-level Sheet or Dialog.

    Import the Popover composition from @/components/ui/popover. PopoverContent emits crm-popover; triggers and content retain their Base UI semantics. Domain code owns loaded content, authorization and persistence.

    Evidence: apps/frontend/src/components/ui/popover.tsx.

    Contract previewLocal documentation state; not production runtime
    AGENT PROMPT SNIPPET app/components/ui/SearchableSelect.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component SearchableSelect (variant="primary", size="md") từ app/components/ui/SearchableSelect.tsx theo docs/ui/components/searchable-select.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    SearchableSelect

    Load when: a loaded, in-memory option set needs text filtering and keyboard selection.

    Import from @/shared/ui/primitives/searchable-select. Canonical props are options, value, onChange, and placeholder; it accepts FormRow association props, plus disabled, loading, clearable, and error. Its root emits crm-searchable-select; Input remains an independent crm-input block. Caller owns remote loading, authorization, options and persistence.

    Use with FormRow render props. Do not use it for remote entity search; use EntityPicker. Evidence: shared source and Deal fields/items tests.

    Contract previewLocal documentation state; not production runtime
    Select an owner
    Choose an option to continue.
    AGENT PROMPT SNIPPET app/components/ui/Sheets and dialogs.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Sheets and dialogs (variant="primary", size="md") từ app/components/ui/Sheets and dialogs.tsx theo docs/ui/components/sheet-dialog.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Sheets and dialogs

    Load when: choosing Sheet, Dialog or AlertDialog or reviewing overlay title, dismissal, focus and destructive confirmation.

    Also read: Shared component guidelines and Entity detail drawer for the drawer stack.

    Do not load for: non-modal page sections or caller-owned persistence.

    BEM identity: crm-sheet is emitted and its real side values map to crm-sheet--side-; the Deal Sheet browser check verified the base block. Approved size values target crm-sheet--size-small, --size-medium, --size-large, --size-xlarge, and --size-full; these size modifiers are not emitted yet. crm-dialog and crm-alert-dialog remain targets. Owned parts map to __backdrop, __content, __title, __description, __footer and __close only where that owner renders them.

    Use Sheet for side-attached work, Dialog for bounded modal work, and AlertDialog for a confirmation that requires deliberate acknowledgement. All are composed from Base UI owners.

    OwnerVerified contract
    SheetDefaults modal to trap-focus, offsetting its backdrop by --app-sidebar-offset; SheetContent supports four sides and an optional close control.
    DialogCentered popup with backdrop, close control, title/description, and responsive footer that stacks actions on narrow screens.
    AlertDialogCentered confirmation popup; use AlertDialogCancel for closing and AlertDialogAction for the confirmed action.

    Approved drawer size standard

    Use the canonical names small, medium, large, xlarge, and full. For a side Sheet, calculate width from the device's dynamic viewport, then cap it to the authenticated application's available surface: min(calc(100dvw - var(--app-sidebar-offset, 0px)), clamp(minimum, preferred-dvw, maximum)). On viewports narrower than 768px, every feature drawer becomes 100dvw so form controls and content do not collapse into a narrow strip.

    sizePreferred device widthMinimumMaximumChoose for
    small30dvw20rem / 320px30rem / 480pxShort utility flow, history, focused detail
    medium50dvw28rem / 448px48rem / 768pxNormal single-column form or standard detail
    large70dvw40rem / 640px70rem / 1120pxDense form, broad detail, or two related regions
    xlarge90dvw56rem / 896px90rem / 1440pxComplex multi-section or two-pane work surface such as Deal/Product
    full100dvwnone100dvwA deliberately full-screen task that covers the application shell

    For small through xlarge, the available-surface cap keeps the expanded sidebar visible and prevents the drawer crossing its interaction boundary. full is a separate fully blocking presentation and may cover the sidebar. Top and bottom Sheets use height, not this width scale. The mobile navigation Sheet continues to use --sidebar-width; it is not a feature drawer size.

    The size API and modifiers are an approved design target, not a current source claim. Until SheetContent implements size, feature width classes are implementation drift. Do not invent another size name or treat an arbitrary pixel/vw class as a new standard.

    Always provide a clear title and describe irreversible consequences. Put the safe escape action before or alongside the destructive action according to the existing footer order. Loading, API errors, and persistence remain caller-owned. Avoid nesting portaled overlays unless the shell explicitly guards that interaction.

    Source: apps/frontend/src/components/ui/sheet.tsx, apps/frontend/src/components/ui/dialog.tsx, and apps/frontend/src/components/ui/alert-dialog.tsx.

    Contract previewLocal documentation state; not production runtime
    AGENT PROMPT SNIPPET app/components/ui/Sidebar.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Sidebar (variant="primary", size="md") từ app/components/ui/Sidebar.tsx theo docs/ui/components/sidebar.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Sidebar

    Load when: changing the shared sidebar primitive, collapsed/mobile behavior, shortcut, navigation adapter or active state.

    Also read: Application shell and Layout.

    Do not load for: feature-page navigation actions or route authorization alone.

    BEM identity: crm-sidebar is the target block. Stable owner parts may use __header, __content, __footer, __menu and __menu-button; collapsed/mobile/active modifiers must map to the provider or item state that actually owns them. Exact modifier placement is unassigned and the target block is not currently emitted.

    Use the SidebarProvider and Sidebar composition from @/components/ui/sidebar only inside a shell. AppSidebar is the application adapter that supplies routes, role filtering, feedback, and session actions.

    The provider supports open state, a Ctrl/Cmd+B shortcut, desktop widths (16rem expanded, 3rem icon), and an off-canvas mobile Sheet. SidebarMenuButton accepts isActive, variant, size, and tooltip; tooltip content is visible only in the collapsed desktop state.

    Active navigation must derive from the router and its relevant search state. Role checks belong to AppSidebar or the feature's route policy, not the presentation primitive. The implementation exposes data-slot and data-sidebar hooks, not a verified project BEM identity.

    Source: apps/frontend/src/components/ui/sidebar.tsx and apps/frontend/src/shared/ui/navigation/app-sidebar.tsx.

    Contract previewLocal documentation state; not production runtime
    CRM workspace
    AGENT PROMPT SNIPPET app/components/ui/Tables and data grids.tsx
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    dùng component Tables and data grids (variant="primary", size="md") từ app/components/ui/Tables and data grids.tsx theo docs/ui/components/tables.md
    💡 Ví dụ ghép câu: "Ở trang chủ, đổi sang nút màu vàng + [Paste]"

    Tables and data grids

    Load when: implementing a semantic table or AG Grid presentation and reviewing row, column, overflow or grid-theme behavior.

    Also read: Data list and Shared component guidelines.

    Do not load for: feature query, filtering, pagination or authorization logic.

    BEM identity: crm-table targets the shared semantic table with __header, __body, __footer, __row, __head, __cell and __caption elements. crm-data-grid targets the AG Grid theme root. Neither target is currently emitted as the project BEM identity.

    Use the shared Table family for small semantic tables. It wraps horizontal overflow and exports TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, and TableCaption.

    Use AG Grid only when its grid capabilities are needed. Apply ag-theme-quartz; global CSS supplies semantic background, foreground, selection, border, field, typography, 48px-row, and 44px-header values. Keep grid-specific row semantics in the owning module rather than duplicating the global theme.

    Rows are not automatically actions. If a row opens detail, make its interaction and keyboard behavior explicit, preserve selection behavior, and provide an alternate actionable control where needed. Handle loading, empty, errors, pagination, sorting, and permission-based columns in the feature owner.

    Source: apps/frontend/src/components/ui/table.tsx and apps/frontend/src/styles/globals.css.

    Contract previewLocal documentation state; not production runtime
    Accounts3 records
    NameStatusExample recordActiveAnother recordPending
    Rows per page: 10 ‹ 1 ›

    Accessibility

    Load when: reviewing keyboard, focus, names/roles/states, error announcements, zoom/reflow, contrast or assistive-technology behavior.

    Also read: Design guidelines for cross-cutting rules and the named component/pattern for exceptions.

    Do not load for: token values or layout geometry without an accessibility decision.

    Use native or Base UI semantics first. Every UI change must preserve a logical keyboard path and a visible focus indicator.

    Verified behavior

  • AppLayout includes a skip link to #main; its content owner provides the main landmark.
  • Button, Input, Select, Badge, Sidebar, Dialog, Sheet, and Tooltip use Base UI or native controls with focus-visible ring styles.
  • FormRow links a render-function control to its label, helper text, and error; errors use role="alert".
  • EntityReferenceField exposes loading with aria-busy, loading text with role="status", and errors with role="alert".
  • InlineEditableValue moves focus into the editor and restores focus to the edit control when its picker-style edit session exits; Escape cancels or exits.
  • Dialog and Sheet wrap Base UI primitives. The Sheet default is trap-focus; the mobile sidebar explicitly uses a fully modal sheet.
  • Required checks

  • Give every icon-only control a concise aria-label; a tooltip is supplemental, not its accessible name.
  • Keep disabled distinct from read-only. Disabled controls block interaction; read-only values must remain understandable and selectable when that matters.
  • Verify tab order, focus entry/return, Escape, close behavior, error announcement, and 200% zoom/reflow for the affected flow.
  • Pair status color with text, icon labeling, or another non-color cue.
  • Gaps and limits

    No WCAG target, contrast audit, screen-reader run, automated accessibility suite, or reduced-motion implementation was verified in this initialization. These are required review work, not passed checks.

    Evidence

    Source: apps/frontend/src/shared/ui/layouts/app-layout.tsx, apps/frontend/src/components/ui/form-row.tsx, apps/frontend/src/components/ui/inline-editable-value.tsx, apps/frontend/src/components/ui/entity-reference-field.tsx, apps/frontend/src/components/ui/dialog.tsx, and apps/frontend/src/components/ui/sheet.tsx.

    Content and localization

    Load when: writing or reviewing Vietnamese labels, actions, helper/error text, formatting, truncation, translation or RTL behavior.

    Also read: Design guidelines and the named component/pattern when its content constraints differ.

    Do not load for: component APIs or visual token selection alone.

    The visible CRM UI is primarily Vietnamese, with a few existing English labels. Keep new copy in the language used by the owning screen unless a product decision changes it.

    Rules

  • Prefer concise action verbs: Lưu, Hủy, Đóng, Quay lại, and Xóa rather than vague labels.
  • Write field errors beside the field and describe the repair; FormRow renders helper text and an alert-compatible error slot.
  • Use a semantic empty state that explains both absence and the next action when the user can act.
  • Preserve truncation in navigation, table cells, and picker labels; provide a title only when source already uses it or the full value cannot otherwise be reached.
  • Use vi-VN for locale-formatted user-visible dates or amounts when a feature owns that formatting. Do not treat this as a globally audited formatting contract.
  • Not established

    There is no centralized translation framework, terminology glossary, RTL policy, or long-text translation test in the inspected source. RTL is configured as false in components.json; do not infer RTL support.

    Evidence

    Source: apps/frontend/src/components/ui/form-row.tsx, apps/frontend/src/shared/ui/navigation/app-sidebar.tsx, apps/frontend/src/shared/ui/primitives/searchable-select.tsx, and apps/frontend/components.json.

    Foundations

    Use this index for visual values and structural layout. Cross-cutting usage rules live under Design guidelines.

    Task signalsDecision ownerStatusConcept
    color, theme, typography, spacing, radius, elevation, iconsSemantic token roles and implementation mappingVerified source and custom manifest; no DTCG claimTokens and themes
    shell geometry, density, breakpoints, responsive form/drawer/table behaviorShared layout rules and current missing global decisionsVerified source with explicit gapsLayout and responsive behavior

    Layout and responsive behavior

    Load when: changing shell geometry, spacing/density, breakpoints, responsive forms, drawers or table overflow.

    Also read: Application shell or the named component/pattern for owned responsive behavior.

    Do not load for: color, copy or isolated component API decisions.

    Use the shared application shell for authenticated routes and the auth layout for login/register routes. Do not reproduce shell padding, sidebar offset, or overlay behavior inside a feature page.

    Verified rules

    ConcernCurrent contract
    Authenticated shellAppLayout provides a collapsible sidebar, sticky 48px header, skip link, main landmark, quick-log button, and feedback dialog.
    Main contentThe main landmark is flex-based with min-w-0, min-h-0, 5px padding, and bottom spacing for the floating action. Feature content must handle its own scroll region.
    Sidebar desktopThe shared provider uses 16rem expanded and 3rem icon widths; it mirrors the active width to --app-sidebar-offset.
    Sidebar mobileBelow the sidebar's md desktop boundary it becomes a modal Sheet; the mobile width is 18rem.
    Form rowsresponsive is one column until its container reaches @sm, then becomes label/control columns. top stays one column; left uses a proportional label column before sm.
    Drawer design targetFeature drawers use the approved device-relative small, medium, large, xlarge, or full scale in Sheets and dialogs. Below 768px every feature drawer is 100dvw; desktop values use dynamic-viewport percentages with min/max and available-shell caps.
    Drawer implementation statusSheetContent still defaults to three-quarters width and callers override it with ad hoc classes. Entity/Deal/Product 90vw implementations visually correspond to xlarge, but the typed size API and size BEM modifiers are not implemented.
    TablesThe shared HTML Table wraps horizontal overflow. Long tabular content must not force page-level overflow.

    No project-wide page grid, spacing scale, or touch target minimum is presently established in source. The drawer scale is an approved design rule with a recorded implementation gap, not verified runtime adoption.

    Evidence

    Source: apps/frontend/src/shared/ui/layouts/app-layout.tsx, apps/frontend/src/components/ui/sidebar.tsx, apps/frontend/src/components/ui/form-row.tsx, apps/frontend/src/components/ui/sheet.tsx, and apps/frontend/src/components/ui/table.tsx.

    Motion and interaction

    Load when: changing hover, pressed, cursor, transition, loading animation or reduced-motion behavior.

    Also read: Design guidelines and the affected component contract.

    Do not load for: static token values or feature persistence logic.

    Keep interaction feedback brief and functional. Existing controls use transition-colors, 100–300ms state transitions, and sheet/dialog entrance and exit animation classes.

    Verified rules

  • Interactive native and ARIA controls receive a pointer cursor unless disabled.
  • Clickable controls use a 0.97 active scale through global CSS; do not add a second competing press transform to shared controls.
  • Cards have card-hover; dashboard summary cards use stat-card-hover with a modest scale and shadow change.
  • Dialog, Sheet, Select, and Tooltip use short fade/zoom/slide transitions from tw-animate-css classes.
  • Skeleton uses animate-pulse; use it only while data is loading, then replace it with content, empty, or error state.
  • Gap

    No prefers-reduced-motion override is present in the inspected global stylesheet. Treat reduced-motion support as a follow-up requirement for any motion-heavy change.

    Evidence

    Source: apps/frontend/src/styles/globals.css, apps/frontend/src/components/ui/sheet.tsx, apps/frontend/src/components/ui/dialog.tsx, and apps/frontend/src/components/ui/skeleton.tsx.

    AGENT PROMPT SNIPPET app/styles/tokens.css
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET)
    dùng CSS design tokens từ app/styles/tokens.css theo docs/ui/foundations/tokens.md

    Tokens and themes

    Load when: selecting or changing semantic color, typography, spacing, radius, elevation, chart, sidebar or dark-theme values.

    Also read: the named component or design guideline only when it constrains token use.

    Do not load for: domain behavior, permissions or page actions.

    Use semantic Tailwind utilities such as bg-primary, text-muted-foreground, and border-border; do not introduce hard-coded color values in shared UI when an existing role fits.

    Verified implementation

    FamilyCanonical roles or valuesUse
    Brandprimary, secondary, plus *-foregroundPrimary actions and brand emphasis
    Feedbacksuccess, warning, info, destructive, plus foreground rolesStatus meaning; do not use color alone
    Surfacesbackground, card, popover, muted, accent, border, input, ringPage, container, overlay, field, and focus surfaces
    Tintslightprimary, lightsecondary, lightsuccess, lightwarning, lighterror, lightinfoLow-emphasis status/background treatment
    Lead conditional-format palette--lead-format-preset-* fixed paletteUser-selected Lead grid row/cell highlighting; preserves existing saved color appearance
    Emphasisprimary-emphasis, secondary-emphasisStronger brand interaction treatment
    TypographyDM Sans Variable, system-ui, -apple-system, sans-serifBody and heading fallback stack
    Radiusbase --radius: 0.625rem; derived sm through 4xlPrefer existing component radius before custom radius
    Elevation--shadow-sm, --shadow-md, --shadow-lg, --shadow-btnCard, popup, and overlay elevation
    Chartschart-1 through chart-5Ordered chart series only
    Sidebarsidebar-* semantic rolesNavigation shell only
    Machine-readable manifestapps/frontend/src/shared/constants/design-tokens-manifest.json generated by scripts/generate-tokens-manifest.mjsAgent/project token lookup; keep generated content synchronized with the script and CSS owners

    Light tokens use a 12% color mix; dark mode changes the semantic values and uses 20% tints. Dark mode is implemented by a .dark ancestor.

    The custom JSON manifest declares a Design Tokens Community Group schema URL but does not use DTCG token objects with $value/resolved $type, and no publish/import/translation boundary was verified. Treat it as a project lookup manifest, not proof of DTCG format conformance; DTCG interoperability validation remains not applicable until that boundary is intentionally adopted.

    Usage rules

  • ring is the focus treatment; do not substitute a shadow or color-only hover state for keyboard focus.
  • destructive communicates risk; pair it with text, an accessible name, or confirmation when the action is consequential.
  • bodytext exists in CSS but is not exported into Tailwind's @theme inline; use muted-foreground unless source inspection establishes a local need.
  • Icons come from Lucide and Iconify. Match adjacent source, use a meaningful accessible name for actionable icons, and keep decorative icons hidden from assistive technology when appropriate.
  • Lead conditional-format rules use a fixed presentational palette rather than semantic status tokens. Use only its documented variables: --lead-format-preset-red-50, --lead-format-preset-orange-50, --lead-format-preset-amber-50, --lead-format-preset-green-50, --lead-format-preset-cyan-50, --lead-format-preset-blue-50, --lead-format-preset-violet-50, --lead-format-preset-pink-50, --lead-format-preset-neutral-50, --lead-format-preset-white, --lead-format-preset-red-200, --lead-format-preset-orange-200, --lead-format-preset-amber-200, --lead-format-preset-green-200, --lead-format-preset-cyan-200, --lead-format-preset-blue-200, --lead-format-preset-violet-300, --lead-format-preset-pink-300, --lead-format-preset-neutral-200, --lead-format-preset-neutral-300, --lead-format-preset-red-500, --lead-format-preset-amber-500, --lead-format-preset-teal-500, --lead-format-preset-lime-500, --lead-format-preset-teal-700, --lead-format-preset-violet-500, --lead-format-preset-pink-500, --lead-format-preset-ink, --lead-format-preset-neutral-500, and --lead-format-preset-neutral-700. They are not Tailwind utility roles and must not communicate status by themselves.
  • Evidence

    Source: apps/frontend/src/styles/globals.css, apps/frontend/src/shared/constants/design-tokens-manifest.json, scripts/generate-tokens-manifest.mjs, apps/frontend/components.json, and apps/frontend/package.json.

    Visual Color Palette (Bảng màu trực quan)

    Click chip màu để copy biến CSS, hoặc bấm icon 🤖 để copy câu lệnh Prompt ghép câu cho Agent.

    --primary
    #1a9cc7
    --primary-foreground
    #ffffff
    --secondary
    #aed85a
    --secondary-foreground
    #1c2536
    --success
    #1ef5cf
    --success-foreground
    #1c2536
    --warning
    #f8c63e
    --warning-foreground
    #1c2536
    --info
    #a07ef2
    --info-foreground
    #ffffff
    --lead-format-preset-red-50
    #fef2f2
    --lead-format-preset-orange-50
    #fff7ed
    --lead-format-preset-amber-50
    #fefce8
    --lead-format-preset-green-50
    #f0fdf4
    --lead-format-preset-cyan-50
    #ecfeff
    --lead-format-preset-blue-50
    #eff6ff
    --lead-format-preset-violet-50
    #f5f3ff
    --lead-format-preset-pink-50
    #fdf2f8
    --lead-format-preset-neutral-50
    #fafafa
    --lead-format-preset-white
    #ffffff
    --lead-format-preset-red-200
    #fecaca
    --lead-format-preset-orange-200
    #fed7aa
    --lead-format-preset-amber-200
    #fde68a
    --lead-format-preset-green-200
    #bbf7d0
    --lead-format-preset-cyan-200
    #a5f3fc
    --lead-format-preset-blue-200
    #bfdbfe
    --lead-format-preset-violet-300
    #c4b5fd
    --lead-format-preset-pink-300
    #f9a8d4
    --lead-format-preset-neutral-200
    #e5e5e5
    --lead-format-preset-neutral-300
    #d4d4d4
    --lead-format-preset-red-500
    #ef4444
    --lead-format-preset-amber-500
    #f6b51e
    --lead-format-preset-teal-500
    #13deb9
    --lead-format-preset-lime-500
    #99ca3c
    --lead-format-preset-teal-700
    #087096
    --lead-format-preset-violet-500
    #8754ec
    --lead-format-preset-pink-500
    #ec4899
    --lead-format-preset-ink
    #1c2536
    --lead-format-preset-neutral-500
    #737373
    --lead-format-preset-neutral-700
    #404040
    --background
    #1c2536
    --foreground
    #f1f5f9
    --card
    #1e293b
    --card-foreground
    #f1f5f9
    --popover
    #333f55
    --popover-foreground
    #f1f5f9
    --muted
    #2a3851
    --muted-foreground
    #7c8fac
    --destructive
    #f87171
    --border
    #333f55
    --input
    #333f55
    --bodytext
    #7c8fac
    --sidebar
    #1c2536
    --sidebar-foreground
    #7c8fac
    --sidebar-primary-foreground
    #ffffff
    --sidebar-border
    #333f55

    Governance

    Use this index for ownership, coverage, lifecycle, implementation drift and evidence decisions.

    Task signalsDecision ownedConcept
    component/element name, BEM block, modifier, alias, rendered identityProject naming grammar, target mappings, adoption status and checksNaming and DOM identity
    reuse versus new owner, scope, lifecycle, adoption, page-contract gapDesign-system governance and adoptionOverview
    docs check, source proof, unit/browser/accessibility evidenceValidation levels and claim boundariesValidation

    Naming and DOM identity

    Load when: naming or renaming a reusable component, DOM block, stable element, variant/state modifier, prompt alias or framework symbol; also load when reviewing rendered component identity.

    Also read: the component index for target blocks and implementation status, then only the named component contract and source owner.

    Do not load for: token selection, page composition, copy changes or feature-only wrappers whose reusable owner and identity do not change.

    Project standard and implementation status

    Anphabe CRM uses crm as the project namespace for reusable rendered UI. Deal-flow owners now emit selected crm-* blocks: the create Sheet browser check verified crm-sheet, crm-card, crm-button, crm-deal-form and crm-deal-form-section; other affected blocks have source verification. Untouched owners remain targets in the component index.

    The approved Sheet size names are small, medium, large, xlarge, and full. Their target mapping is size="" to crm-sheet--size- alongside the base crm-sheet; no size modifier is verified in source or rendered HTML yet. compact, standard, wide, workspace, arbitrary viewport widths, and translated labels are not canonical aliases.

    BEM identity is a stable DOM inspection and lookup contract. Tailwind utilities continue to style the UI; data-slot may continue to support selectors. Neither one replaces the project BEM identity.

    Grammar

    UnitSyntaxExample
    Blockcrm-crm-button, crm-form-row
    Element__crm-button__icon, crm-form-row__label
    Block modifier--crm-button--loading, crm-button--variant-outline
    Element modifier__--crm-button__icon--trailing

    Use lowercase kebab words. Name by stable UI responsibility, not color, record ID, DOM position or an incidental wrapper: use crm-button--variant-outline, not crm-blue-button, crm-row-42 or crm-sidebar-left-div.

    A modifier maps to a real supported prop, variant or state and always coexists with the base block. It supplements rather than replaces native state, aria-*, data-* hooks or the component API.

    Elements and composition

  • Add an element only for a stable parent-owned part that is documented anatomy, a useful inspection target or a real variant/state owner. Do not name every wrapper.
  • Do not chain elements. Use crm-form-row__label, never crm-form-row__field__label.
  • A nested reusable component keeps its own block. A Button inside FormRow remains crm-button; it is not crm-form-row__button. A parent may name a wrapper such as crm-form-row__actions when that wrapper has a stable layout responsibility.
  • A multi-root component places its block on the semantic primary rendered root and records that inspection root in its component concept. If no stable primary root exists, document the chosen inspection root or mark the owner headless.
  • Hooks, providers and renderless/headless owners have no rendered-block requirement. Their consuming rendered component owns the block.
  • Component lookup contract

    Every reusable rendered component row and owning concept records:

    FieldRule
    Display name and prompt aliasesResolve human wording to one canonical owner; do not create translated duplicates
    Symbol and importUse the verified framework export and public import
    BEM targetRecord one crm-* block on the semantic rendered root
    Stable elementsRecord only actual owned anatomy that needs stable identity
    ModifiersMap each class to a real prop/value or state; omit unsupported mappings
    StatusDistinguish target, verified in source, and verified in rendered HTML
    EvidenceLink the owning concept, source, primary catalog demo and relevant test

    For example, Button emits crm-button; loading={true} maps to crm-button--loading and variant="outline" maps to crm-button--variant-outline. An owned icon part may use crm-button__icon when the owner explicitly renders it.

    Adoption and verification

  • Preserve verified legacy identities and public imports. Do not mass-rename exports, add empty CSS declarations or retrofit the whole application during documentation-only work.
  • Add or change BEM classes only in an authorized UI implementation scope. Update the component concept and compact index in the same change.
  • Verify that the semantic rendered root contains the base block in its actual class attribute. Source symbol names, CSS Module hashes, documentation or data-slot alone are insufficient.
  • Verify that elements follow ownership, nested blocks remain independent and each modifier corresponds to the real prop/state. Keep native semantics and ARIA truthful.
  • Record verified in rendered HTML only after an appropriate component, catalog or browser check; source inspection alone establishes at most verified in source.
  • UI design system governance

    Load when: deciding reuse versus a new owner, checking scope/coverage, resolving naming/BEM drift or assessing missing page contracts.

    Also read: Validation when making an evidence or completion claim.

    Do not load for: routine use of a known component with an unchanged contract.

    This bundle is the single documentation entry for UI decisions in this checkout. It covers the React frontend only and is initialized from current source; it does not migrate or rely on docs/ui_bk/.

    Design principles and status

    PrincipleStatus
    Use semantic tokens and shared UI owners before adding local presentationEstablished project rule and verified owner structure
    Keep data, permissions, validation and persistence in domain modulesEstablished project rule
    Specify applicable loading, empty, error, disabled and success behaviorDesign-system requirement; not audited across every feature
    Keep navigation, edit, clear and delete actions distinctVerified shared EntityReferenceField pattern and project rule
    Prefer accessible native/Base UI semantics and visible focusVerified in primary owners; no app-wide accessibility audit
    Route agents from indexes to the smallest sufficient concept setDocumented by the current initialization; behavioral retrieval checks completed manually

    Reuse and ownership

  • Reuse an owner under apps/frontend/src/components/ui/ or apps/frontend/src/shared/ui/ when its behavior matches.
  • Add a shared component only when the behavioral difference is real and recurring. A module-level adapter may own domain copy, data, permissions, and persistence while composing a shared presentation owner.
  • Update the owning concept and components index when a public UI contract, canonical name, or supported state changes.
  • The generated docs/ui/view.html portal is the sole catalog artifact. Rebuild it with okf-design-system view; it is supplementary evidence, not a replacement for the contracts.
  • Naming standard and adoption status

    The project grammar, lookup fields and verification rules live in Naming and DOM identity. The Deal audit verified crm-sheet, crm-card, crm-button and Deal form blocks in the rendered create Sheet; affected remaining blocks are verified in source. Untouched component families remain targets.

    Coverage gaps

  • Only the Deal create/edit page is documented; other pages still require source inspection.
  • Breadcrumb, Collapsible, DropdownMenu, Label, Separator, Switch and Tooltip remain inventory-only components without dedicated public contracts.
  • WCAG target, contrast results, screen-reader behavior, reduced motion and touch-target minimum remain unverified or undecided.
  • EntityPicker's independent clear action is covered by focused UI tests; its block is verified in source.
  • Compatibility

    Preserve public imports and current variant APIs unless an approved feature intentionally changes them. Docs describe verified implementation and explicit proposals separately; source tests and runtime checks decide behavioral compliance.

    UI documentation validation

    Load when: validating UI docs or deciding whether source, unit, rendered, browser or accessibility evidence supports a claim.

    Also read: only the changed concepts and their source/test owners.

    Do not load for: routine implementation under a known, already verified contract.

    Documentation-only change

    1. Verify changed statements against the relevant source owner.

    2. Check every local Markdown link and index reachability.

    3. Run strict OKF validation against docs/ui.

    4. Starting from the root index, test token, accessibility/content, component, pattern, page and reusable-component naming tasks; each must resolve to a bounded concept set.

    5. Record unexecuted browser, accessibility, and behavior checks as limits.

    For a naming task, the bounded route is Naming and DOM identity, the component index and only the named component contract/source owner.

    UI behavior change

    In addition to the documentation checks, run the narrowest relevant unit/integration test first, then typecheck/lint or a broader check when a shared contract changes. Inspect the affected UI in a real runtime when visual, focus, responsive, or rendered-DOM claims are made.

    For BEM adoption, verify that the semantic rendered root contains its base block, elements follow ownership, nested component blocks remain independent and each modifier corresponds to the real prop/state. Source inspection alone does not establish rendered-DOM adoption.

    For forms and association flows, verify initial, loading, empty, selected, editing, validation error, server error, disabled, success, cancel, and clear behavior that applies. Confirm clear does not delete the linked entity. For overlays, verify focus entry, focus return, Escape, outside-close behavior, and nesting safeguards.

    For a drawer size implementation, verify each supported size at a viewport below 768px and at representative desktop widths. Confirm small through xlarge use the documented percentage/min/max rule, do not cross the expanded-sidebar application surface, do not create horizontal overflow, and render the matching crm-sheet--size-* modifier. Verify full separately as an intentionally fully blocking viewport surface. Source utility classes alone do not prove geometry.

    Evidence boundary

    OKF validation proves bundle structure and internal links only. Source inspection proves the checked implementation, not desired design compliance. A successful automated test does not prove browser layout, authenticated behavior, or screen-reader output unless that exact evidence was executed.

    Uncommitted source may be documented as a working-tree extension only when that state is explicit. It does not become a stable public contract until the implementation and relevant checks are accepted.

    Anphabe CRM design guidelines

    Load when: making a cross-cutting UI decision, reviewing consistency, or choosing accessibility, feedback, content, hierarchy or semantic styling rules.

    Also read: Tokens and themes for values, Components for a reusable owner, and Patterns for composed behavior.

    Core rules

  • Use semantic tokens rather than hard-coded colors in shared UI. Use color with text, an icon label or another non-color cue for meaningful status.
  • Preserve a visible hierarchy: one high-emphasis action for the current decision, lower-emphasis actions through existing Button variants, and destructive styling only for consequential actions.
  • Use the shared component owner and its documented states. Feature modules own data, permission, validation and persistence behavior.
  • Keep navigation, editing, selection, clearing and deletion as distinct actions. Clearing an association never deletes its entity.
  • Provide loading, empty, error, disabled and success feedback when those states apply. A skeleton is loading only; a toast is supplemental and must not replace field or persistent error feedback.
  • Keep layouts responsive at the owning component/pattern boundary. Do not reproduce application-shell offsets, page chrome or shared overlay behavior inside a feature.
  • Accessibility and interaction

    Use native or Base UI semantics, a logical keyboard path, visible focus, accessible names for icon-only actions, associated labels/errors and focus entry/return for overlays. Apply the detailed Accessibility and Motion and interaction contracts.

    No project WCAG target, contrast audit, touch-target minimum, screen-reader run or reduced-motion implementation has been verified. These remain missing standards/checks rather than implied compliance.

    Content and localization

    Follow the owning screen's Vietnamese language, concise action verbs and repair-oriented errors. Use the detailed Content and localization contract. A translation framework, terminology glossary and RTL support are not established.

    Do and don't

    DoDon't
    Use semantic roles such as primary, muted-foreground, border and destructiveAdd a hard-coded shared color when an existing semantic role fits
    Use Button variants to express action hierarchyStyle a navigation link or neutral status as a primary action solely for appearance
    Use FormRow or an equivalent real label/error associationTreat placeholder text as a label or toast as the only validation error
    Keep selected entity navigation separate from edit and clear actionsMake one click ambiguously navigate, edit and clear, or treat clear as delete
    Use Card for a related information/action groupUse nested cards as a substitute for page layout or heading hierarchy
    Reuse Sheet, Dialog or AlertDialog according to the taskNest portaled overlays without the owning modal guard and focus behavior
    Replace loading with content, empty or error state when settledLeave a skeleton as an empty/error state or hide a failed operation
    Reuse existing short transitions and the global active treatmentAdd a competing press transform or claim reduced-motion support before it exists

    Evidence and limits

    These guidelines summarize verified source and current documented contracts. They are not proof of visual consistency, WCAG compliance, responsive behavior or screen-reader output without the checks in Validation.

    Design guidelines

    Use this index for cross-cutting usage rules. Load a named component or pattern only when it has an exception to the shared guideline.

    Task signalsDecision ownedStatusConcept
    overall UI decision, hierarchy, semantic styling, feedback, project do/don'tCross-cutting design guidance and unresolved standardsSource-derived with explicit gapsDesign guidelines
    keyboard, focus, names/roles/states, zoom, contrast, screen readerAccessibility behavior and required checksSemantics inspected; runtime audit pendingAccessibility
    motion, hover, pressed, cursor, loading transition, reduced motionInteraction feedback and motion boundariesCurrent implementation inspected; reduced-motion gapMotion and interaction
    Vietnamese copy, action labels, errors, formatting, localization, RTLContent and localization rulesCurrent usage inspected; i18n/RTL not establishedContent and localization

    Anphabe CRM UI Design System

    Start here for UI work. This bundle is a source-derived specification for the React frontend; it does not use docs/ui_bk/ as an authority.

    Start here

    Choose the task row and load only the linked concepts. Follow a conditional link only when the task actually needs that decision.

    Task or signalRead firstThen load only whenAvoid loading
    Color, typography, spacing, radius, elevation, icons or responsive valuesFoundationsA component or guideline constrains how the value is usedUnrelated components and pages
    Accessibility, keyboard, focus, content, localization, motion or do/don'tDesign guidelinesA named component or pattern has an exceptionUnrelated token details and pages
    Reuse, create or change a shared componentComponents, then the named contractThe contract links a foundation, guideline or composing patternOther component bodies and unrelated feature pages
    Compose a form, data list, drawer or application shellPatternsThe pattern names specific component contractsThe full component and foundation catalogs
    Choose or change a drawer/Sheet sizeSheets and dialogs and Layout and responsive behaviorLoad the named page/pattern only when its composition or modal behavior differsUnrelated components and every feature drawer
    Build or review a feature pageApplication shell and the closest page contractA named component/foundation is used; inspect the feature source for page-only data and authorizationUnrelated page modules and the whole UI bundle
    Name or rename a component, element, alias, variant/state or DOM identityNaming and DOM identity, then the named component contractCallers, catalog examples or migration evidence are affectedUnrelated foundations, patterns and pages
    Resolve ownership, implementation drift or adoptionGovernanceThe affected component/source and validation evidence are neededEvery concept body
    Validate docs or UI evidenceValidationOnly changed concepts and their source ownersEvery concept body for a format-only check

    Groups

    GroupDecision owned
    FoundationsSemantic visual values, themes, layout and responsive rules
    Design guidelinesAccessibility, interaction, content/localization and cross-cutting do/don't rules
    ComponentsShared imports, anatomy, variants, states, accessibility and ownership
    Patterns and layoutsRepeated workflows, application shell, drawers, lists and editable associations
    GovernanceCoverage, naming/DOM identity, reuse/new-owner policy and validation evidence

    Current status

    The current implementation uses React, Tailwind CSS 4, Base UI primitives, and CSS variables in apps/frontend/src/styles/globals.css. Generate the sole UI portal with okf-design-system view; its local artifact is docs/ui/view.html. docs/ui_bk/ was intentionally excluded.

    Coverage map

    AreaStatusOwner
    Purpose and principlesDocumented from source; product design goals remain implicitGovernance overview
    Foundations and token implementationDocumented from source and custom manifest; DTCG conformance is not claimedFoundations
    Design guidelinesDocumented from source; WCAG target, contrast, screen-reader and reduced-motion checks remain missingDesign guidelines
    Core shared componentsGlobal contract and primary owners documented; remaining primitives inventoriedComponents
    Patterns and layoutsShared shell and repeated interactions documented from sourcePatterns
    Page contractsDeal create/edit is documentedPages
    Content and localizationDocumented for current Vietnamese usage; glossary, i18n and RTL are not establishedContent and localization
    Governance and verificationDocumentedGovernance
    Agent routingDocumented in root/group indexes and concept loading notesThis index
    BEM naming and DOM identityProject crm-* grammar; Deal-flow adoption has rendered evidence while untouched owners remain targetsNaming and DOM identity

    Boundaries

    Documentation does not grant permission to retrofit DOM classes, revise variants, or alter persistence. Domain modules retain data loading, authorization, validation, and save/cancel/delete behavior.

    UI design-system log

    2026-09-16 — Payment-ready Deal billing

    The existing Deal form now requires payment-ready invoice identity when creating or adopting commerce and when explicitly editing commerce billing. Required semantics cover legal name, address, conditional company tax code, delivery email, and paired personal identity; unrelated edits preserve incomplete historical snapshots. The existing crm-deal-form owner and billing field controls are reused, with no new UI owner or DOM block.

    2026-09-16 — Admin-only SePay account selection

    Extended the existing crm-deal-payment-panel owner with a server-authorized masked account selector for ADMIN and a fail-closed disabled state. Non-Admins receive no alternate account catalog, disabled state removes actionable QR controls while retaining evidence/manual confirmation, and crm-deal-payment-panel--disabled plus crm-deal-payment-panel__account-selector map to the actual rendered states. No new UI owner or schema migration was introduced.

    2026-09-15 — Legacy Deal billing hydration

    Legacy Deal edit now hydrates a missing billing snapshot from the current authorized Contact and Account details, while a reusable invoice profile remains preferred and Lead identity remains the fallback. Existing billing snapshots and manual edits remain unchanged. The existing Deal form and crm-deal-form identity are reused; no new UI owner or DOM block was introduced.

    2026-09-15 — Optional manual-payment note

    Changed the existing Deal manual-payment confirmation dialog so its note field is optional. The shared Dialog, FormRow, Input, Textarea and Button owners and the existing crm-deal-payment-manual-confirmation identity remain unchanged; exact-amount validation, permissions, receipt creation and audit/settlement behavior stay enforced.

    2026-09-14 — Independent Deal invoice status

    Added an invoice lifecycle badge, Deal-table column and guarded detail control using the shared Badge, Select and Button owners. Invoice issuance remains independent from pipeline stage and is unavailable until the Deal is fully paid; the backend enforces the same invariant.

    2026-09-15 — Legacy Deal edit parity

    Extended the canonical Deal form so eligible ADMIN users can edit migration-era Pipeline/Stage, Lead-derived customer context, commerce and billing fields from full Deal detail data. Protected lifecycle/payment states remain disabled with an explanation, Pipeline and Stage submit atomically, PENDING request-affecting edits retain explicit supersession, and legacy commerce adoption remains intentional. No new UI owner or DOM block was introduced.

    2026-09-14 — Audited manual Deal payment confirmation

    Added a bounded Deal payment confirmation dialog using the shared Dialog, FormRow, Input, Textarea and Button owners. The Deal payment panel and dialog expose stable crm-* identities; exact-amount validation, role/ownership checks, Payment Receipt persistence, actor audit and confirmation time remain backend domain behavior.

    2026-09-14 — Component contract previews

    Added declarative preview coverage for every non-index concept in components/: 16 supported generic previews and three explicit documentation-only concepts. The generated portal now demonstrates contract states without executing CRM components, project data, or runtime behavior.

    2026-09-14 — Generated portal is the sole UI catalog

    Removed the authenticated /settings/ui-patterns route and its Settings link. The standalone docs/ui/view.html artifact, rebuilt with okf-design-system view, is now the single UI catalog surface; component contracts and product rendering remain the evidence for runtime behavior.

    2026-09-14 — Shared CRM form primitives

    Added fixed 160px FormRow layouts and the shared DetailEditableField owner. Account and Contact form headings now reuse FormSectionHeading; the former Account detail row remains a compatibility alias while cross-domain callers move to the shared owner. Domain schemas, autosave, permissions, and persistence are unchanged.

    2026-09-13 — Lead form and detail alignment

    Lead create/edit and detail surfaces now use explicit domain adapter names, shared FormSectionHeading, and source-verified crm-lead-* identity blocks. Conditional formatting uses a documented fixed CSS-variable palette while retaining compatibility with previously saved hexadecimal rules. The fixed Lead form/edit-row layouts remain domain adapters because they differ from the shared FormRow geometry. No Lead API, permissions, autosave, triage, association, or persistence behavior changed.

    2026-09-13 — Device-relative drawer size standard

    Approved five canonical feature-drawer sizes: small, medium, large, xlarge, and full. Sizes derive from dynamic device viewport width with minimum/maximum guardrails; feature drawers become full-width below 768px, and non-full sizes are capped to the authenticated application surface. The shared typed API and crm-sheet--size-* modifiers remain an explicit implementation gap; this documentation change did not migrate runtime callers.

    2026-09-13 — Deal create alignment

    Added source-level crm-* identity adoption for the shared controls used by Deal create/edit and for Deal-owned form regions. Introduced the shared FormSectionHeading, canonical DealForm, DealFormSection, DealLeadPicker, and shared SearchableSelect reuse. Focused regression tests verify Deal commerce and keyboard selection; browser-rendered evidence remains pending.

    2026-09-13 — Update

    Added the Naming and DOM identity contract with the crm-* namespace, block/element/modifier grammar, composition boundaries, component lookup targets, adoption status and rendered-output checks. Updated agent routes so naming guidance is loaded only for identity work. No UI runtime code or data changed.

    2026-09-13 — Update

    Re-ran okf-design-system init against the current frontend and repaired the root agent load map. Added the design-guideline route, shared component rules, task-oriented group lookups and per-concept loading directives. Updated the token contract for the custom manifest and recorded the working-tree EntityPicker clear extension as pending validation. No CRM runtime behavior or data was changed by this documentation initialization.

    2026-09-13 — Initialization

    Created the new UI design system from current frontend source. The legacy docs/ui_bk/ folder was deliberately excluded from discovery, migration, and editing.

    Deal create/edit

    Route: /deals Sheet create/edit flow. Load Deal form, then Sheets and dialogs only when changing its approved xlarge drawer size or overlay behavior.

    The UI has no catalog data or API side effects. It preserves Deal association, pricing, payment and draft behavior. Review desktop and narrow mobile rendering separately; source and unit tests do not prove geometry.

    Pages

  • Deal create/edit
  • Lead create and detail
  • Lead create and detail

    Route: /leads create/edit Sheet and Lead detail surfaces.

    Load Lead forms and detail adapters before changing presentation, then load shared Sheet, form, picker, or accessibility contracts only for the exact owner being changed. The Lead module owns service context, triage, permissions, autosave, association selection, drawer layering, and save behavior. Design-system adapters own only presentation and local interaction.

    Review create and detail rendering without submitting a Lead. Unit tests protect service selection and detail autosave transitions, but do not prove responsive geometry or browser-rendered BEM classes.

    AGENT PROMPT SNIPPET docs/ui/patterns/application-shell.md
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    áp dụng pattern Application shell theo docs/ui/patterns/application-shell.md (dùng shared components trong app/components/ui/)
    💡 Ví dụ: "Tạo trang login + [Paste]"

    Application shell

    Load when: building or changing authenticated/auth layouts, sidebar/header composition, main content boundaries or global overlays.

    Also read: Layout and responsive behavior and Sidebar when those owners change.

    Do not load for: feature-page data, authorization or local form composition.

    Use AppLayout for authenticated routes and AuthLayout for login/register route families. RootLayout only passes through the route outlet.

    The authenticated order is: skip link, AppSidebar, SidebarInset, sticky header/global search, main outlet, quick-log action/sheet, then feedback dialog. The quick-log action is global UI state, not page-owned behavior. Keep feature pages inside the main outlet; do not introduce a competing fixed header or sidebar.

    On mobile, Sidebar uses its Sheet behavior. The sidebar adapter filters role-gated navigation items, while route/API authorization remains authoritative. Test keyboard skip navigation and a collapsed sidebar whenever shell structure changes.

    Source: apps/frontend/src/shared/ui/layouts/app-layout.tsx, apps/frontend/src/shared/ui/layouts/auth-layout.tsx, and apps/frontend/src/shared/ui/navigation/app-sidebar.tsx.

    AGENT PROMPT SNIPPET docs/ui/patterns/data-list.md
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    áp dụng pattern Data list theo docs/ui/patterns/data-list.md (dùng shared components trong app/components/ui/)
    💡 Ví dụ: "Tạo trang login + [Paste]"

    Data list

    Load when: choosing Table versus AG Grid or defining collection loading, empty, error, row navigation and overflow behavior.

    Also read: Tables and data grids for the shared visual owner.

    Do not load for: unrelated forms, drawers or feature query semantics.

    Choose the shared HTML Table for bounded semantic data and AG Grid for column behavior that needs grid capabilities. The feature owns queries, filtering, sorting, pagination, selection, empty/loading/error states, and authorization-sensitive columns.

    Keep the column header visible, prevent long cells from widening the page, and distinguish row navigation from row selection. For an interactive row, ensure keyboard access and a visible focus treatment; the current generic Table only supplies visual row states. Use Tables and data grids for shared visual rules.

    An empty state should state that no results are present and, where permitted, offer the next action. A skeleton is loading only; it is not an empty or error state.

    AGENT PROMPT SNIPPET docs/ui/patterns/editable-association.md
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    áp dụng pattern Editable association theo docs/ui/patterns/editable-association.md (dùng shared components trong app/components/ui/)
    💡 Ví dụ: "Tạo trang login + [Paste]"

    Editable association

    Load when: selecting, creating, displaying, navigating, editing, clearing or saving an Account, Contact or other entity relationship.

    Also read: Entity references, the owning form component and its domain policy.

    Do not load for: deleting an entity or editing unrelated scalar fields.

    Use Entity references for the presentation owner. The domain owner follows this sequence:

    1. Render the selected entity label or an explicit empty label.

    2. Open edit using a specific accessible action; load/search choices under the caller's authorization.

    3. Select or create the intended entity, then preserve the originating draft until the association save succeeds.

    4. On clear, remove only the relationship after any required confirmation; never delete the referenced entity.

    5. Keep a failed save visible and recoverable; do not silently revert unrelated draft fields.

    The documented components expose loading, disabled, error, editing, selected, and empty presentation. They do not decide whether an association is required or whether its target can be created; that is domain policy.

    AGENT PROMPT SNIPPET docs/ui/patterns/entity-drawer.md
    CHỈ DẪN GHÉP CÂU (COMPOSABLE SNIPPET) Dán kèm vào bất kỳ câu chat nào của bạn
    áp dụng pattern Entity detail drawer theo docs/ui/patterns/entity-drawer.md (dùng shared components trong app/components/ui/)
    💡 Ví dụ: "Tạo trang login + [Paste]"

    Entity detail drawer

    Load when: opening entity detail from a list/relationship, navigating nested drawer layers or guarding a portaled child modal.

    Also read: Sheets and dialogs for overlay primitives and the named entity detail source.

    Do not load for: standalone form sheets or page-level data rules.

    Use EntityDrawerSheet for supported entity detail opened from a list or nested relationship. It currently displays one right Sheet at 90vw, which maps to the approved xlarge design target; the typed size contract is not implemented yet. It swaps the current stack layer in place and renders breadcrumbs with Back/Close actions.

    The stack owner supplies stack, isOpen, onClose, onPop, onPush, and optional refresh after mutation. The drawer shell owns keyboard Escape: it pops a nested layer or closes the root layer. The child DrawerModalGuardContext suppresses Escape/outside close while a portaled child modal is open.

    The current shell renders canonical Lead, Account, and Contact detail content; Campaign and Activity have explicit placeholder behavior. Do not claim they are full entity-detail contracts. Deletion callbacks close or pop the drawer and then invoke the caller's refresh hook.

    Source: apps/frontend/src/shared/drawer/entity-drawer-sheet.tsx.

    Patterns

    Use this index when composing multiple components into a repeated workflow or layout.

    Task signalsDecision ownedStatusConcept
    authenticated page, auth route, sidebar, header, global shellAuthenticated and unauthenticated layout compositionVerifiedApplication shell
    entity detail, nested relationship, URL-driven drawerLayered drawer navigation and modal guardVerified; Campaign/Activity detail remain placeholdersEntity detail drawer
    collection, table, grid, row navigation, loading/empty/errorData-list owner selection and state boundariesVerifiedData list
    select, create, clear or save Account/Contact/other associationAssociation lifecycle without deleting the entity or losing a draftVerified shared behavior; domain policy remains caller-ownedEditable association

    Standalone page contracts are not yet documented. For a feature-page task, start with the closest pattern and inspect the named route/module source; do not infer that this index defines page-specific authorization, data or actions.

    Đã sao chép!