Components › Identity › Identity marks › Entity mark

Entity mark

canon

The mark of any record: tone says which world the type belongs to, glyph says the exact type, shape says person / company / thing. One vocabulary for over twenty types.

Replaces seven parallel vocabularies (chip tile, palette glyph, listing tone, nav icons, ad-hoc header glyph, and the two picker maps in MentionInput and EntityPicker, which contradicted the canon — deal as businessplan, product rose in one picker and grey in the next, a tone on people and companies at all). Seven tones cannot cover twenty types, so the tone groups and the glyph identifies. Glyph size keeps the parity of the box — an odd difference puts the drawing on a half pixel.

Used in 21 files · 17 areas · 31× — components/assistant, components/deal, components/dialog, components/entity-preview, components/projects, components/search …

The identity mark of any record, not just a person or a company. React packages/ui/src/components/EntityMark.tsx, primitive .acrm-mark in apps/web/src/ds/entity-mark.css, vocabulary packages/ui/src/lib/entity-mark.ts.

It replaced seven parallel vocabularies the app kept for the same types: the tile inside the reference chip, the bare glyph in the ⌘K palette, the coloured glyph in listings, the navigation icons, whatever a detail header happened to draw, and — found during the cleanup — the pickers: MentionInput and EntityPicker each carried their own type→glyph/tone map, shared by the product picker, the record select and the link field.

They did not merely differ, they contradicted each other: a deal was a green coin in a chip, a grey briefcase in the palette and a businessplan glyph in the @ mention; a product was rose in one picker and grey in the next; and a person or a company was given a tone at all — which the system reserves for things, because a person's colour is the hue of their name. Now there is one answer and every surface reads it from the same place.

Two layers, read in this order. What is this? — shape, tone and glyph. Which one is it? — photo, logo or initials, and only a person or a company has that. The mark adds the first layer; avatar still owns the second.

When to use

  • Wherever a record is named: detail header, list row, ⌘K palette row, reference in text, preview card, rail item.
  • For a person or a company too — the mark delegates to <Avatar>, so one call site handles a mixed list without branching on the type.

When not to use

  • As a status or a priority marker. The mark says what a record is, never how it is doing.
  • As the whole click target — the mark is a mark, the name is the label.
  • For a UI action (filter, sort, settings). Those take a plain <Icon>; a coloured tile would promise a record that is not there.

Anatomy

  • span.acrm-mark plus the size class when it is not sm; inside, one <Icon> from the DS set.
  • Tone = world, glyph = type. Seven --ic-* tones cannot cover twenty-seven types, so the tone groups (money · work · time · content · catalogue · property · selection) and the glyph identifies. Someone who learns the worlds knows where they are; the glyph says exactly what.
  • Shape: circle = person, square = company, tile = thing. Nothing else is encoded in shape.
  • Sizes inline 16 · sm 24 (default) · lg 32 — the same ladder as the avatar, to the pixel, so a mixed row never jumps.
  • The glyph shrinks with the tile (12 · 16 · 20 px) and its stroke thins with it (1.9 · 1.7 · 1.5 in viewBox units = 0.95 · 1.13 · 1.25 px on screen). White drawing on a saturated ground gains weight optically; a constant stroke made the big tile clumsy and the small one mush.
  • Glyph size keeps the parity of the box. An odd difference puts the drawing on a half pixel and the stroke blurs asymmetrically — measured at 4.5 px offset before the fix.
  • Six Tabler glyphs are not centred inside their own viewBox; deviations of at least one unit are corrected in GLYPH_NUDGE (vehicle, budget). Smaller ones are left alone on purpose: the correction is expressed in viewBox units and would land off the pixel grid.

Best practices

Do
  • Add a new type to ENTITY_MARK — one row — before drawing it anywhere. Drawing a tile the vocabulary does not know is how the parallel vocabularies were born; a missing type is one line, a private drawing is forever.
  • Let the mark decide between avatar and tile; do not branch at the call site.
  • Pass seed (and photo) whenever the call site already holds the user row. The face of a colleague is stored (users.avatar_seed), not derived: without the seed the mark falls back to the hash of the NAME, so a regenerated face would show only to the person who pressed „Regenerate" and everyone else would keep seeing the old one.
  • Keep the mark and the name together; the mark alone is not a label.
Don’t
  • Re-implement a tile per screen. That is exactly what produced five vocabularies.
  • Read --av-hue in your own CSS rule. The hue derived from a name belongs to .acrm-avatar and nothing else; apps/web/src/ds/__tests__/identity-mark-kanon.test.ts fails the build otherwise. The recipe „circle + hsl(var(--av-hue) …) + initial" spread by copying between CSS files and neither lint nor the visual baseline caught it — a fresh copy looks almost like the canon.
  • Invent a tone for a new type. Put the type in an existing world, or bring a world to the AD.
  • Use a tone to mean anything but the world — not health, not state, not priority.

Content

  • An unknown type renders a visible grey question mark. It used to fall back to deal silently, which meant a wrong glyph rather than a missing one.
  • Historical names of the same thing resolve to one mark: contact = person, organization = company, location = place.
  • avatar — the identity layer for a person and a company.
  • user-avatar — our own person, drawn as a generated face so a colleague is not a contact.
  • entity-ref, item-list, section-top-bar — the usual hosts.
  • Stories: components--entity-mark--default, --variants, --sizes, --states, --composition.

Source: docs/avatar/entity-mark.usage.md · GitLab