Form-less dialog
canonCreates or edits one record without form fields: an icon carries each row and the placeholder names it. Shape A writes text plus properties as chips in the body; shape B asks what · when · with whom · how · where.
Promoted to a rule by the product owner on 9 September 2026 ("promote form-less to a rule and review the other form dialogs with the aim of unifying them"); the pattern itself was approved by the AD on 7 September and shape B shipped to dev on 8 September (ACRM-132, MRs !3163–!3166). The code is the source of truth. THE RULE: form-less when the dialog creates or edits one record whose properties can be named by a placeholder; a form when it is data entry with many typed values and the label carries the meaning — the test is whether the label can be replaced by a placeholder without losing the field identity. Shape B (`.fl-*`) is the documented engine and the only one with a shared sheet. Shape A still runs as three private copies — `.tk-*` (task-dialog.css), `.dl-*` (deal-dialog.css), `.nd-*` + `.dlg-title` (note-dialog.css, dialog-doc.css) — with MetaChip declared twice in TSX; merging them onto `.fl-*` is wave 0 and until it lands shape A is a named shape, not a catalogued one. Adoption measured at main 06fb6b236: 2 dialogs on shape B, 3 on shape A, 43 forms — of which 12 are correctly forms (accounting document 13 fields, Spravovat segment 37, payroll relation, VAT period, mailbox) and 12 are conversion candidates with their questions listed in docs/REVIEW-form-dialogy.md. No stories yet — the pattern lives in apps/web, which packages/ui must not import, so they will be ported markup like Filter bar and Command palette. Three defects open in the shipped code (all in the review): the RSVP dot has no accessible name because aria-label sits on a role="generic" span (EntityPicker.tsx:191); --ic-amber 2.14:1 light and --ic-grey 2.97:1 dark fail the 3:1 non-text minimum while colour is the state’s only carrier; the availability day is mouse-only and role="presentation" (MeetingAvailability.tsx:233). A row may carry several values stacked in `.fl-vals` with the icon on the first: the video-call link sits under the meeting type rather than asking its own question (AD 9 September 2026), which also ended the `link` glyph naming two different rows; the in-person meeting type took `coffee` the same day, so `users` names only the attendees and no glyph in the dialog carries two meanings. The rule is one meaning per AXIS, not per app: the *Typy událostí* filter keeps `users` for its Schůzky row because it switches categories of event rather than describing one meeting (AD, 9 September 2026). The EDIT footer deliberately has no Zrušit — over an existing record the word reads as "cancel the meeting", and the close control already closes the window (product owner, 8 September 2026). The CREATE dialog still has it, because with nothing saved yet the word is unambiguous; whether create should drop it too is open. Delivered 13 Sep 2026 (FL-5; ACRM-223/224/226; acrm !3466–!3476): shape A in all of task, deal and note; header = primary record (EntityRef) then classification, separated by --sp-5 rhythm without a slash, classification and ID in --fg-muted (.head-muted); links chip names records instead of counting them and never repeats the primary; the title is a growing textarea. The note keeps its 1128 × 828 work window. Person dialog (shape B, on main since 15 September 2026, acrm !3561 — `apps/web/src/components/person/NewContactDialog.tsx`, channel editor draft mode in acrm !3548, wiring into the people list and palette in !3575): reuses the record rail's own multi-value channel editor in draft mode (`local-channels.ts`, `draft`/`plainValues`) for e-mail and phone, adds `.fl-dup`/`.fl-chan`/`.fl-chan-v`, and is the first shape-B dialog to use `masked-date-input` for a typed field. `.fl-dup`/`.fl-chan`/`.fl-chan-v` ship in `dialog-formless.css`. See the "person dialog" subsection in formless-dialog.usage.md. Person dialog, custom fields (acrm !3699, 16 Sep 2026, ACRM-277): behind the same "More details" chip, after the tag row, the create dialog draws the tenant's custom fields of a person from the SAME registry the record rail reads (`api.customFields('contact')`) — no drift, a deactivated definition is not drawn. The definition's type picks the input: date → dd.mm.rrrr mask, select and yes/no → PickerMenu (free text would put a value outside the code list into the jsonb, where no filter finds it), number → numeric keyboard but not type="number". Two items per row (like title prefix/suffix), leading glyph `clipboard-list` as in the rail. Values are written only after the record exists, via `setCustomValues` (the only path that merges jsonb; PATCH would overwrite keys from import, enrichment and the agent) and only the filled ones. Task dialog, suggestions from the typed title (acrm !3771, ACRM-213, approved 17 Sep 2026 — Linear "Apply all quick suggestions"): in a NEW task the existing `.flc-navrhy` group also offers what the title says — due date, priority, assignee only, only for an empty property (the default "Ty" assignee counts as empty), assignee only when the name fits exactly one colleague. Chip = canonical `.tk-chip` with the glyph of the property it sets (calendar · flag · user) and the sentence's own word ("zítra", "v pátek"); a quiet `IconButton` bolt (sm, 24px) BEFORE the chips applies all, drawn from two suggestions up. No new CSS. Computed in the browser (`lib/task-suggest.ts`), no model, no server call. ACRM-355 (acrm !3775): the note `.fl-note` is drawn by MentionInput (`.mi-editor.mi-multiline` = form-field frame, 88px); the form-less selectors carry `.mi-editor.mi-multiline.fl-note` so the note stays borderless at rest, on focus and while typing, whatever the CSS order. Relation dialog (shape B, acrm !4011, 27 Sep 2026 — `apps/web/src/components/relations/RelationDialog.tsx`, data/rules in `relations-data.ts`): the „New relation"/„Edit relation" dialog on a person or company card. The anchor (the card's own subject) is fixed as the `LeadEntity` in the header and is never repicked — a relation is an edge between two subjects, and `PATCH /relations/:id` does not accept `from`/`to` — so on edit the anchor never shows as an editable row. Icon+value rows carry no labels. The other side is a **generic entity** — company OR person, one `EntityPicker` (`types={['organization','contact']}`, `max={1}`) — because the product owner asked for the picker to stay general ("vztah můžu mít i s osobou", 27 Sep 2026) rather than splitting into a company dialog and a person dialog. Relation type is a `PickerMenu` grouped by the chosen entity's kind, with a `sep: true` separator row between the two groups; before an entity is picked, both groups show at once (the entity picked after the type discards a type that belongs to the other kind). "Valid from" is optional and typed into the `dd.mm.rrrr` mask (`MaskedDateInput`): the placeholder reads `field.validFromToday` while creating with nothing chosen (empty submits today) and `field.validFrom` on edit (empty keeps the stored date — clearing the field is never a silent delete of the start date). Position and share % rows draw only when the selected relation type's `allowsPosition`/`allowsShare` say so; a field outside the allowed set is always sent as `null` on submit, so switching type (e.g. „Společník" → „Zaměstnanec") clears it server-side instead of leaving a stale value. Footer: create = Cancel `Esc` + „Add relation" `⌘↵`; edit = „Save" only, no Cancel (the family rule — over an existing record `Zrušit` reads as "cancel the relation", and the close control already closes the window). Relationship type catalogue, person↔person (acrm !4010, migration `0416_relation_types_chot_prarodic.sql`, same 27 Sep 2026 change): „Manžel/ka" renamed to „Choť" (one word for both spouses), „Partner/ka" renamed to „Partner", and a new „Prarodič / Vnouče" (Grandparent/Grandchild) type was added after Parent/Child. The migration only rewrites rows still holding the old default name — an admin rename made in Nastavení → Data → Vztahy is left alone. Shape B — the add-to-list dialog (acrm !4019, 27 Sep 2026): `AddToListDialog` (`apps/web/src/components/segment/AddToListDialog.tsx`) is a multi-select-from-search instance of the pattern — "Multi-select from search in a dialog". The card's own record (company or person) is the `LeadEntity` anchor in the header, unpickable, same as the relation dialog's anchor. The body is ONE field, "Hledat nebo založit seznam" (search or create a list), filtering the tenant's manual lists (`segment kind='manual'`) by normalized, diacritic-insensitive match. Each list is a row with the anatomy of `check-menu` — `.acrm-popover-item` holding the list's own segment tile (`.sg`, 18px, reused from the left menu — see "Segment tile debt" below) then the name and member count, with `.acrm-checkbox` on the right (`aria-pressed` on the row) — and a click toggles membership immediately, so the footer carries only "Hotovo". A "Založit seznam „…"" row is appended only when nothing in the list matches the typed text exactly (no row for a name that already exists, so re-adding it is a toggle, not a duplicate). Enter is overloaded on the match count, not just an exact-match shortcut: with exactly one row still shown — even from a partial match like "fin" → "Fintech — sledovat" — Enter toggles that row; otherwise it creates the typed name (verified in `add-to-list-dialog.test.tsx`, case "Enter při jediné ČÁSTEČNÉ shodě list přepne"). Dynamic (filter) segments are excluded from this dialog on purpose — membership in them can't be set by hand, so an earlier version that listed them read-only was pure noise; the old `.atl-row`/`.atl-ic` row shape this dialog used before 27 Sep 2026 is now only used by the Trash page and the duplicates panel, not by any form-less dialog. "Change role" on the company card's person row (acrm !4020, 27 Sep 2026) now opens this same `RelationDialog` with `presetOther` set to the person — not a separate dialog: the older `PersonRoleDialog` component is REMOVED (`company/PersonRoleRelationDialog.tsx` is the new, thin wrapper that resolves the existing relation row, or passes `presetOther` when the person has no relation yet, and renders `RelationDialog`). `presetOther` (a `Mention`) fixes the picked-but-not-yet-saved other side of a NEW relation the same way `anchor` fixes the card's own side, so the entity picker does not show when the context already names both ends. Contact log dialog (shape A, `apps/web/src/components/timeline/ContactLogDialog.tsx`) — ONE dialog for „Zaznamenat kontakt“ on person, company, deal AND project (product owner 27 Sep 2026: „to je ten stejný dialog… použij ten samý prvek z DS“). Project as the main entity (acrm !4084, 28 Sep 2026): `entityType="project"` writes the 4th slot via `POST /timeline/entry` (`projectId`, from !4048) rather than a picked link; the old, project-only `ProjectEntryDialog` is REMOVED (279-line component + its test deleted, `ProjectMeetingsTab` now opens `ContactLogDialog`). Recording chip (acrm !4072, 28 Sep 2026, approved „doplň možnost přidání nahrávky všude“): a `MetaChip` (glyph `microphone`) driven by `PickerMenu` in the chip row — items „Z Plaudu“ (only when `plaudQ.data?.configured`) and „Nahrát soubor“, plus a clear row „Odebrat nahrávku“ once something is picked. The chip's VALUE is the SOURCE (`t('contactLog.recordingPlaud')` / „N z Plaudu“ / „Soubor“), never the file or dictaphone item's name — those run long and the chip must not truncate (source: `apps/web/src/components/meetings/recording-sources.ts`, `RECORDING_SOURCE_ICON`). A log with a recording is submitted as a proběhlá SCHŮZKA (only a meeting can hold a recording): `POST /timeline/entry` returns `meetingId`, then the binary/Plaud import goes as a second call; a failed second call closes the dialog like a success with a warning toast rather than reopening a half-filled form (review !4072). `.fl-note.fl-note-open`: the writing area is the dialog's CORE (REVIEW-form-dialogy.md §3.1) and must hold its three-line height even after focus leaves it for a chip — before this the area collapsed on every chip click and the dialog jumped 38 px (28 Sep 2026). Pitfall, already hit once: the fix must NOT be a bare `.open` class on `.fl-note` — `ds:sync-check` then reads `.x.open` elsewhere in the DS sheet as adopted-and-missing and broke the person-record golden; the CSS/TSX carry the dialog-specific `.fl-note-open` name instead. Header centring: `.acrm-dialog-lead .flc { align-items: center }` plus, in a row with more than the entity alone, `margin-top: -1px` on the wrapper around the entity's `.eref-chip` (its 1px dashed name underline extends the box 1px down, which without the correction put the entity's optical centre 2.8px above the classification chip and the × — product owner 28 Sep 2026: „položky v záhlaví nejsou zarovnané vertikálně na střed“). The single-entity header next to the title alone (ACRM-319) is untouched — the rule only fires with a second flex child. 14 Oct 2026 (owner "26 souhlas", proto/schuzka-ticho@19b777808, acrm !5041): Nová schůzka is the quiet reference — row placeholders are one or two words (Účastníci · Místo nebo odkaz · Cíl · Poznámka pro účastníky), a property of a row (internal goal) is an icon with tooltip, never a sentence in the placeholder. Dev check 14 Oct 1440 + 390: rows single-line (40 / 44 px). Registry repaired 17 Oct 2026 (review F2): removed classes with no CSS definition and no TSX use on main: .fl-copy, .tk-att-w, .tk-att-x. Registry repaired 17 Oct 2026 (review F4/F11): attached css share-dialog-formless.css.
Used in 29 files · 17 areas · 31× — components/communication, components/company, components/deal, components/meetings, components/notes, components/person …
apps/web <NewMeetingForm> <EditMeetingForm> <TaskDialog> <DealDialog> <NoteDialog> <LeadEntity> <RelationDialog> <AddToListDialog> <ContactLogDialog>.acrm-dialog.formless .fl-body .fl-stack .fl-title-row +40SourceStoryImport
Not exported from @acrm/ui — <NewMeetingForm>, <EditMeetingForm>, <TaskDialog>, <DealDialog>, <NoteDialog>, <LeadEntity>, <RelationDialog>, <AddToListDialog>, <ContactLogDialog> lives in the app (apps/web/src/components/meetings/NewMeetingForm.tsx). Reuse the CSS classes below, or move the component into the package first.
Props
NewMeetingForm
No props extracted (not in props.json).
EditMeetingForm
No props extracted (not in props.json).
TaskDialog
No props extracted (not in props.json).
DealDialog
No props extracted (not in props.json).
NoteDialog
No props extracted (not in props.json).
LeadEntity
No props extracted (not in props.json).
RelationDialog
No props extracted (not in props.json).
AddToListDialog
No props extracted (not in props.json).
ContactLogDialog
No props extracted (not in props.json).
CSS classes
Files
apps/web/src/components/meetings/NewMeetingForm.tsxapps/web/src/components/calendar/EditMeetingForm.tsxapps/web/src/components/meetings/MeetingAvailability.tsxapps/web/src/ds/dialog-formless.cssapps/web/src/components/tasks/TaskDialog.tsxapps/web/src/components/ChipDuo.tsxapps/web/src/ds/task-dialog.cssapps/web/src/components/person/NewContactDialog.tsxapps/web/src/components/tasks/TaskTitleSuggestions.tsxapps/web/src/lib/task-suggest.tsapps/web/src/components/relations/RelationDialog.tsxapps/web/src/components/relations/relations-data.tsapps/web/src/components/segment/AddToListDialog.tsxapps/web/src/components/timeline/ContactLogDialog.tsxapps/web/src/ds/dialog-formless.cssapps/web/src/ds/responsive.cssapps/web/src/ds/task-dialog.cssapps/web/src/ds.cssapps/web/src/ds/segment-screen.cssapps/web/src/ds/share-dialog-formless.css