Exclusivebeta

Inscribe — <mv-inscribe>

Critical notes that follow a record everywhere it appears in the app. Register notes per entity id (customer, account, device, patient, shipment, vendor) with a severity (info, caution, critical), an author, a date and an optional expiry; every element of the scope that carries data-entity="…" (table row, avatar, @mention, search result, order header) then gets a compact marker, a button whose shape gives the highest severity (circle, triangle, octagon) next to the count, solid while a critical note is still unacknowledged. Elements added later are picked up automatically, the same notes stay in sync across every place the record shows, and expired notes vanish on their own. Hovering or focusing a marker previews the notes and clicking pins them, with an optional inline editor (severity, expiry) whose additions stamp every marker of that record at once. Guarded actions (data-entity-guard on a button or link) are held until the critical notes have been read: the first click opens them, the second click (or “Acknowledge and continue”) records the acknowledgment and lets the original click through, with a cancelable mv-inscribe-guard event and an optional acknowledgment lifetime (ack-ttl, "0" = every time).

CategoryData display
TypeWeb Component (<mv-inscribe>)
Statusbeta
Keywordsexclusive, culture, notes, annotations, entity, crm, support, customer, warning, severity, acknowledgment, guard, confirmation, popover, badge, marker, compliance, handoff

When to use

Avoid when

Install

node scripts/add.mjs inscribe --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["inscribe"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/focus.js, core/observe.js, core/position.js, components/inscribe/inscribe.js, components/inscribe/inscribe.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="ins-demo" style="width:min(100%,76rem);margin-inline:auto">
  <style>
    #ins-demo .ins-console { border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-bg-subtle); overflow:hidden; box-shadow:var(--mv-shadow-sm) }
    #ins-demo .ins-bar { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1.5rem; flex-wrap:wrap; padding:.875rem 1.125rem; border-bottom:1px solid var(--mv-border); background:var(--mv-surface) }
    #ins-demo .ins-bar h3 { margin:0; font-size:1rem; letter-spacing:-.01em }
    #ins-demo .ins-bar p { margin:.125rem 0 0; color:var(--mv-fg-muted); font-size:.75rem }
    #ins-demo .ins-controls { display:flex; align-items:center; gap:.5rem 1.25rem; flex-wrap:wrap }
    #ins-demo .ins-controls .mv-choice { font-size:.8125rem }
    #ins-demo .ins-grid { display:grid; grid-template-columns:18.5rem minmax(0,1fr) 17.5rem; min-height:31rem }
    #ins-demo .ins-col { min-width:0; background:var(--mv-surface) }
    #ins-demo .ins-col + .ins-col { border-left:1px solid var(--mv-border) }
    #ins-demo .ins-h { display:flex; align-items:center; justify-content:space-between; gap:.5rem; margin:0; padding:.75rem 1rem; border-bottom:1px solid var(--mv-border); font-size:.75rem; font-weight:600; color:var(--mv-fg-muted); letter-spacing:.04em; text-transform:uppercase }
    #ins-demo .ins-queue ul { list-style:none; margin:0; padding:.375rem; display:grid; gap:.125rem }
    #ins-demo .ins-ticket { position:relative; display:grid; gap:.25rem; padding:.625rem .75rem; border-radius:var(--mv-radius-lg) }
    #ins-demo .ins-ticket:hover { background:var(--mv-bg-subtle) }
    #ins-demo .ins-ticket[aria-current="true"] { background:var(--mv-accent-subtle) }
    #ins-demo .ins-t-top { display:flex; align-items:center; justify-content:space-between; gap:.5rem; font-size:.8125rem; font-weight:600 }
    #ins-demo .ins-t-top time { color:var(--mv-fg-subtle); font-size:.6875rem; font-weight:500; white-space:nowrap }
    #ins-demo .ins-subj { color:var(--mv-fg-muted); font-size:.75rem; line-height:1.35; text-decoration:none; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
    #ins-demo .ins-subj::after { content:""; position:absolute; inset:0; border-radius:inherit }
    #ins-demo .ins-subj:focus-visible { outline:none }
    #ins-demo .ins-ticket:has(.ins-subj:focus-visible) { box-shadow:var(--mv-focus-ring) }
    #ins-demo .ins-ticket .mv-inscribe-marker { position:relative; z-index:1 }
    #ins-demo .ins-ticket[data-new] { animation:ins-in .5s var(--mv-ease-out) }
    @keyframes ins-in { from { background:color-mix(in oklab, var(--mv-accent) 14%, transparent) } }
    #ins-demo .ins-thread { display:grid; grid-template-rows:auto minmax(0,1fr) auto }
    #ins-demo .ins-who { display:flex; align-items:center; gap:.75rem; padding:.75rem 1.125rem; border-bottom:1px solid var(--mv-border) }
    #ins-demo .ins-who b { font-size:.9375rem }
    #ins-demo .ins-who small { display:block; color:var(--mv-fg-muted); font-size:.75rem; margin-top:.0625rem }
    #ins-demo .ins-who .mv-badge { margin-left:auto }
    #ins-demo .ins-msgs { display:grid; align-content:start; gap:.875rem; padding:1rem 1.125rem }
    #ins-demo .ins-msg { display:grid; grid-template-columns:auto minmax(0,1fr); gap:.625rem }
    #ins-demo .ins-msg-body { padding:.625rem .75rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-surface); font-size:.8125rem; line-height:1.5 }
    #ins-demo .ins-msg-body p { margin:0 }
    #ins-demo .ins-msg-meta { display:flex; gap:.5rem; align-items:baseline; margin-bottom:.25rem; font-size:.75rem }
    #ins-demo .ins-msg-meta b { font-weight:600 }
    #ins-demo .ins-msg-meta span { color:var(--mv-fg-subtle) }
    #ins-demo .ins-msg[data-internal] .ins-msg-body { border-style:dashed; background:color-mix(in oklab, var(--mv-warning) 6%, var(--mv-surface)) }
    #ins-demo .ins-mention { color:var(--mv-accent); font-weight:600 }
    #ins-demo .ins-compose { display:grid; gap:.5rem; padding:.75rem 1.125rem 1rem; border-top:1px solid var(--mv-border) }
    #ins-demo .ins-compose textarea { min-height:3.5rem; resize:none }
    #ins-demo .ins-compose div { display:flex; justify-content:flex-end; gap:.5rem }
    #ins-demo .ins-order { display:grid; align-content:start }
    #ins-demo .ins-sec { display:grid; gap:.5rem; padding:.875rem 1rem; border-bottom:1px solid var(--mv-border) }
    #ins-demo .ins-sec:last-child { border-bottom:0 }
    #ins-demo .ins-row { display:flex; align-items:center; justify-content:space-between; gap:.75rem; font-size:.8125rem }
    #ins-demo .ins-row > span:first-child { color:var(--mv-fg-muted) }
    #ins-demo .ins-row b { font-weight:600; font-variant-numeric:tabular-nums }
    #ins-demo .ins-row a { color:var(--mv-fg); font-weight:500; text-underline-offset:2px }
    #ins-demo .ins-oid { font-size:.9375rem; font-weight:650; letter-spacing:-.01em }
    #ins-demo .ins-oid small { display:block; color:var(--mv-fg-muted); font-size:.75rem; font-weight:500; letter-spacing:0; margin-top:.125rem }
    #ins-demo .ins-total { padding-top:.5rem; border-top:1px dashed var(--mv-border) }
    #ins-demo .ins-actions { display:grid; gap:.5rem }
    #ins-demo .ins-actions .mv-button { width:100% }
    #ins-demo [data-entity-guarded]::before { content:""; width:.75rem; height:.75rem; margin-right:.125rem; background:currentColor; opacity:.9; -webkit-mask:var(--ins-lock) center/contain no-repeat; mask:var(--ins-lock) center/contain no-repeat }
    #ins-demo { --ins-lock:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 7V5a3.5 3.5 0 0 1 7 0v2h.5a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1zm1.6 0h3.8V5a1.9 1.9 0 0 0-3.8 0z'/%3E%3C/svg%3E") }
    #ins-demo .ins-foot { display:flex; align-items:center; gap:.5rem; padding:.5rem 1.125rem; border-top:1px solid var(--mv-border); background:var(--mv-surface); color:var(--mv-fg-muted); font-size:.75rem; min-height:2.25rem }
    #ins-demo .ins-foot b { color:var(--mv-fg); font-weight:600 }
    #ins-demo .ins-dot { width:.5rem; height:.5rem; border-radius:50%; background:var(--mv-success); flex:none }
    #ins-demo .ins-foot[data-tone="block"] .ins-dot { background:var(--mv-danger) }
    #ins-demo .ins-foot[data-tone="note"] .ins-dot { background:var(--mv-info) }
    #ins-demo .ins-hint { margin:.75rem 0 0; color:var(--mv-fg-muted); font-size:.75rem; text-align:center }
    @media (max-width:64rem) {
      #ins-demo .ins-grid { grid-template-columns:16rem minmax(0,1fr) }
      #ins-demo .ins-order { grid-column:1 / -1; border-left:0; border-top:1px solid var(--mv-border) }
    }
    @media (max-width:42rem) {
      #ins-demo .ins-grid { grid-template-columns:minmax(0,1fr) }
      #ins-demo .ins-col + .ins-col { border-left:0; border-top:1px solid var(--mv-border) }
    }
  </style>

  <mv-inscribe id="ins" editable author="Sam Patel">
    <div class="ins-console">
      <header class="ins-bar">
        <div>
          <h3>Support console</h3>
          <p>Billing &amp; accounts queue · signed in as Sam Patel</p>
        </div>
        <div class="ins-controls">
          <label class="mv-choice" data-control="end">
            <input type="checkbox" role="switch" class="mv-switch" id="ins-edit" checked>
            <span class="mv-choice-text"><span class="mv-choice-title">Inline editor</span></span>
          </label>
          <label class="mv-choice" data-control="end">
            <input type="checkbox" role="switch" class="mv-switch" id="ins-every">
            <span class="mv-choice-text"><span class="mv-choice-title">Acknowledge every time</span></span>
          </label>
          <button class="mv-button" data-variant="outline" data-size="sm" type="button" id="ins-new">Simulate incoming ticket</button>
        </div>
      </header>

      <div class="ins-grid">
        <section class="ins-col ins-queue" aria-labelledby="ins-q-h">
          <h4 class="ins-h" id="ins-q-h">Open tickets <span class="mv-badge" data-variant="secondary" data-shape="pill" id="ins-count">5</span></h4>
          <ul id="ins-list">
            <li class="ins-ticket" aria-current="true">
              <span class="ins-t-top"><span data-entity="cust-4821">Diana Okafor</span><time>9:42 AM</time></span>
              <a class="ins-subj" href="#ticket-4410">Refund for duplicate charge on INV-20931</a>
            </li>
            <li class="ins-ticket">
              <span class="ins-t-top"><span data-entity="cust-1377">Mateo Álvarez</span><time>9:15 AM</time></span>
              <a class="ins-subj" href="#ticket-4407">Can’t update the card on file</a>
            </li>
            <li class="ins-ticket">
              <span class="ins-t-top"><span data-entity="cust-2290">Aiko Tanaka</span><time>8:58 AM</time></span>
              <a class="ins-subj" href="#ticket-4402">CSV export drops the last column</a>
            </li>
            <li class="ins-ticket">
              <span class="ins-t-top"><span data-entity="cust-4821">Diana Okafor</span><time>Yesterday</time></span>
              <a class="ins-subj" href="#ticket-4388">Please remove our old pilot workspace</a>
            </li>
            <li class="ins-ticket">
              <span class="ins-t-top"><span data-entity="cust-3054">Lars Nygaard</span><time>Yesterday</time></span>
              <a class="ins-subj" href="#ticket-4381">Change the billing address on invoices</a>
            </li>
          </ul>
        </section>

        <section class="ins-col ins-thread" aria-label="Conversation">
          <div class="ins-who">
            <span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:20" role="img" aria-label="Diana Okafor"><span class="mv-avatar-fallback" aria-hidden="true">DO</span></span>
            <div>
              <b data-entity="cust-4821">Diana Okafor</b>
              <small>[email protected] · Ticket #4410</small>
            </div>
            <span class="mv-badge" data-variant="danger" data-shape="pill">Urgent</span>
          </div>
          <div class="ins-msgs">
            <div class="ins-msg">
              <span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:20" aria-hidden="true"><span class="mv-avatar-fallback">DO</span></span>
              <div class="ins-msg-body">
                <div class="ins-msg-meta"><b>Diana Okafor</b><span>9:42 AM</span></div>
                <p>Hi! We were charged $1,249.00 twice for our annual renewal (INV-20931). Could you refund the duplicate? And while you’re at it, please delete all the data from our old pilot workspace.</p>
              </div>
            </div>
            <div class="ins-msg" data-internal>
              <span class="mv-avatar" data-size="sm" data-variant="tinted" style="--mv-avatar-hue:260" aria-hidden="true"><span class="mv-avatar-fallback">PR</span></span>
              <div class="ins-msg-body">
                <div class="ins-msg-meta"><b>Priya Raman</b><span>Internal note · 9:50 AM</span></div>
                <p>Refund is fine. Before anything irreversible, read the notes on <span class="ins-mention" data-entity="cust-4821">@Diana Okafor</span>. Legal still has a hold on this account.</p>
              </div>
            </div>
          </div>
          <div class="ins-compose">
            <textarea class="mv-textarea" aria-label="Reply to Diana Okafor" placeholder="Reply to Diana…"></textarea>
            <div><button class="mv-button" data-size="sm" type="button">Send reply</button></div>
          </div>
        </section>

        <aside class="ins-col ins-order" aria-label="Order details">
          <div class="ins-sec">
            <div class="ins-oid">Order A-10492<small>Paid Sep 1, 2026 · Visa •••• 4417</small></div>
            <div class="ins-row"><span>Customer</span><span data-entity="cust-4821">Diana Okafor</span></div>
            <div class="ins-row"><span>Account</span><a href="#account-lumen" data-entity="acct-lumen">Lumen Health</a></div>
          </div>
          <div class="ins-sec">
            <div class="ins-row"><span>Team plan · 25 seats, annual</span><b>$1,249.00</b></div>
            <div class="ins-row"><span>Duplicate charge</span><b>$1,249.00</b></div>
            <div class="ins-row ins-total"><span>Total charged</span><b>$2,498.00</b></div>
          </div>
          <div class="ins-sec ins-actions">
            <button class="mv-button" data-variant="outline" type="button" id="ins-refund">Refund $1,249.00</button>
            <button class="mv-button" data-variant="destructive" type="button" id="ins-delete" data-entity-guard="cust-4821">Delete customer data</button>
          </div>
        </aside>
      </div>

      <div class="ins-foot" id="ins-status" role="status"><span class="ins-dot" aria-hidden="true"></span><span id="ins-status-text">Every place that shows a customer carries that customer’s notes.</span></div>
    </div>
  </mv-inscribe>
  <p class="ins-hint">Hover or Tab to a marker to read the notes · Click to pin and add one · Try “Delete customer data” · Simulate a ticket: its marker attaches by itself</p>

  <script type="module">
    const ins = document.getElementById("ins");
    await customElements.whenDefined("mv-inscribe");
    const $ = (id) => document.getElementById(id);
    const now = Date.now(), hour = 36e5, day = 864e5;

    ins.entities = {
      "cust-4821": { name: "Diana Okafor", kind: "Customer" },
      "cust-1377": { name: "Mateo Álvarez", kind: "Customer" },
      "cust-2290": { name: "Aiko Tanaka", kind: "Customer" },
      "cust-3054": { name: "Lars Nygaard", kind: "Customer" },
      "acct-lumen": { name: "Lumen Health", kind: "Account" },
    };
    ins.notes = [
      { entity: "cust-4821", severity: "critical", author: "Hannah Weiss, Legal", date: now - 21 * day,
        text: "Legal hold LH-2291: do not delete, anonymize or export any of this customer’s data until Legal lifts the hold." },
      { entity: "cust-4821", severity: "caution", author: "Marcus Bell", date: now - 36 * day,
        text: "Severe peanut allergy: never include food in goodwill gift boxes." },
      { entity: "cust-4821", severity: "info", author: "Priya Raman", date: now - 3 * day, expires: now + 68 * day,
        text: "Executive sponsor at Lumen Health. Escalate billing questions to Priya Raman." },
      { entity: "cust-1377", severity: "caution", author: "Payments team", date: now - 5 * day, expires: now + 20 * day,
        text: "Open chargeback on the August invoice: route refund requests to Payments before replying." },
      { entity: "cust-2290", severity: "info", author: "Leila Haddad", date: now - 50 * day,
        text: "Prefers written follow-ups to calls; usually replies within a day." },
      { entity: "acct-lumen", severity: "caution", author: "Priya Raman", date: now - 2 * hour, expires: now + 40 * day,
        text: "Renewal under negotiation: no discounts or credits without Priya’s approval." },
    ];

    const status = (text, tone = "ok") => {
      $("ins-status-text").textContent = text;
      $("ins-status").dataset.tone = tone;
    };
    const name = (id) => ins.entities[id]?.name ?? id;

    $("ins-edit").addEventListener("change", (e) => { ins.editable = e.target.checked; });
    $("ins-every").addEventListener("change", (e) => { ins.ackTtl = e.target.checked ? "0" : null; });

    ins.addEventListener("mv-inscribe-guard", (e) => {
      const n = e.detail.notes.length;
      status(`Held “${e.detail.trigger.textContent.trim()}”: ${n} critical note${n > 1 ? "s" : ""} on ${name(e.detail.entity)} to acknowledge first.`, "block");
    });
    ins.addEventListener("mv-note-ack", (e) => {
      status(`${e.detail.by} acknowledged a ${e.detail.note.severity} note on ${name(e.detail.entity)}.`, "note");
    });
    ins.addEventListener("mv-note-add", (e) => {
      status(`${e.detail.note.author} added a ${e.detail.note.severity} note on ${name(e.detail.entity)}: it now shows everywhere this record appears.`, "note");
    });
    $("ins-delete").addEventListener("click", () => status("Deletion request queued for Diana Okafor and routed to Legal for review."));
    $("ins-refund").addEventListener("click", () => status("Refund of $1,249.00 issued to Diana Okafor’s Visa •••• 4417."));
    $("ins-list").addEventListener("click", (e) => { if (e.target.closest("a[href^='#']")) e.preventDefault(); });

    const incoming = [
      ["cust-4821", "Diana Okafor", "Follow-up: still seeing the duplicate charge"],
      ["cust-1377", "Mateo Álvarez", "Refund status for order A-10311?"],
      ["cust-6120", "Kofi Mensah", "How do I add a second admin?"],
    ];
    let next = 0;
    $("ins-new").addEventListener("click", () => {
      const [id, who, subject] = incoming[next++ % incoming.length];
      const li = document.createElement("li");
      li.className = "ins-ticket";
      li.dataset.new = "";
      const top = document.createElement("span");
      top.className = "ins-t-top";
      const person = document.createElement("span");
      person.dataset.entity = id;
      person.textContent = who;
      const time = document.createElement("time");
      time.textContent = "Just now";
      top.append(person, time);
      const a = document.createElement("a");
      a.className = "ins-subj";
      a.href = `#ticket-${4411 + next}`;
      a.textContent = subject;
      li.append(top, a);
      $("ins-list").prepend(li);
      $("ins-count").textContent = String($("ins-list").children.length);
      status(`New ticket from ${who}${ins.notesFor(id).length ? ": their notes came with it" : ""}.`, "note");
    });
  </script>
</div>

Cultural reference

Memento — Christopher Nolan (2000, film). A man who cannot form new memories tattoos the facts that must never be forgotten on his own body and writes on every photograph, so critical knowledge travels with the thing itself where he cannot miss it. In the UI, short notes are attached to a record’s id and appear as a marker on every element that shows that record, and critical ones must be read and acknowledged before any irreversible action on it.

API

Attributes

NameTypeDefaultDescription
scope"self" | "document" | CSS selectorselfWhere data-entity and data-entity-guard elements are looked for: inside the element (self), the whole document, or the first element matching the selector. Elements added later are picked up automatically.
editablebooleanfalseShows the inline editor (“Add note”: text, severity, expiry) at the bottom of the notes card, and lets data-entity-marker="always" show an empty “+” marker on records without notes.
authorstring"You"Name of the current user, used as the author of notes added in the editor and in acknowledgments.
ack-ttlduration ("0", "30m", "8h", "7d")(none)How long an acknowledgment lasts before guarded actions are held again. Empty = for the life of the page, "0" = every guarded action needs a fresh acknowledgment. Markers turn solid again when an acknowledgment lapses.
placement"bottom-start" | "top" | "right" | …bottom-startPreferred side of the notes card relative to the marker (flipped and shifted to stay on screen). Guard cards open above the action.
localeBCP 47 tagen-USLocale of dates, relative times and acknowledgment times.
data-entitystringOn any element of the scope: the entity id it represents. A marker is attached when that entity has active notes; the element also receives data-inscribed="info | caution | critical" (highest severity) for your own styling.
data-entity-namestringOn a data-entity element: human name used in labels when the entities property doesn’t provide one (falls back to the id).
data-entity-marker"none" | "always" | "before"On a data-entity element: no marker (the element still gets data-inscribed and guards still work), a marker even without notes (with editable, to add the first one), or the marker placed before the content instead of after.
data-entity-slotbooleanOn a descendant of a data-entity element: the marker is placed inside it. Otherwise the marker goes after an interactive or void host (link, button, img), into the first cell of a <tr>, or at the end of the host.
data-entity-guardentity id | emptyOn a button or link: the action is held while that entity (or, when empty, the closest data-entity ancestor’s) has unacknowledged guarding notes. While armed it carries data-entity-guard-state="armed"; while guarded, data-entity-guarded.

Properties

NameTypeDescription
notesNote[] | { [entityId]: Note[] }All notes. Note = { id?, entity, text, severity: "info" | "caution" | "critical", author?, date? (Date | ISO | ms, default now), expires? (idem), guard? (default: severity === "critical"), acknowledged? { by, at } }. Setting replaces everything; reading returns copies with acknowledged and expired filled in.
entities{ [entityId]: { name, kind? } | string }Names (for marker labels and the card header) and kinds (shown under the name, e.g. “Customer”).
scope / editable / author / ackTtl / placement / localereflectedMirror the attributes.

Methods

NameDescription
inscribe(entityId, note | text)Adds a note to an entity and returns it (with its generated id); every marker of that entity updates and stamps. No event (the app made the change).
erase(noteId)Removes a note. Returns false if it doesn’t exist.
notesFor(entityId, { includeExpired? })Active notes of an entity, most severe then newest first.
acknowledge(entityId, noteId?)Records an acknowledgment by author for one note, or for every guarding note of the entity. Returns how many were acknowledged. No event.
isGuarded(entityId)True while a guarded action on this entity would be held.
setEntity(entityId, { name, kind } | name)Adds or updates one entry of entities.
open(entityId, anchor?) / close()Opens the pinned notes card for an entity (anchored to its first marker by default) and moves focus into it; closes it.
refresh()Re-resolves the scope and re-scans it.

Events

NameDescription
mv-inscribe-guardA guarded action was clicked while notes still need acknowledging. detail: { entity, notes, trigger }. Cancelable: preventDefault() lets the click through untouched (e.g. for a role that is allowed to bypass).
mv-note-ackA note is being acknowledged from the card or a guarded action. detail: { entity, note, by, at, source: "card" | "guard" }. Cancelable: preventDefault() refuses the acknowledgment and the action stays held. Persist acknowledgments here.
mv-note-addA note is being added from the inline editor. detail: { entity, note, source: "editor" }. Cancelable: preventDefault() discards it (e.g. to save it server-side and call inscribe() afterwards). Notes you write count as acknowledged by you.
mv-inscribe-openThe notes card opened. detail: { entity, mode: "peek" | "pin" | "guard", anchor, notes }.
mv-inscribe-closeThe notes card closed. detail: { entity, reason }.

Content structure

NameDescription
(content)With scope="self" (default), the region whose data-entity elements are decorated. The element is display: contents; with scope="document" it can sit anywhere, empty.

CSS classes

NameDescription
mv-inscribe-markerThe marker: a <button> (or, inside another link or button, a non-focusable role="img" with data-passive). data-severity="info | caution | critical | none", data-pending while a guarding note is unacknowledged, data-inscribe-open while its card is open.
mv-inscribe-cardThe notes card (role="dialog", Popover API top layer). data-severity (highest), data-mode="peek | pin | guard". Parts: -head, -title, -sub, -guardmsg, -list, -note (data-severity, data-pending), -sev, -text, -meta, -acked, -foot, -form, -btn.

CSS variables

NameDefaultDescription
--mv-inscribe-infovar(--mv-info)Tint of info notes (circle).
--mv-inscribe-cautionvar(--mv-warning)Tint of caution notes (triangle).
--mv-inscribe-criticalvar(--mv-danger)Tint of critical notes (octagon).
--mv-inscribe-marker-size1.25remMarker height.
--mv-inscribe-width22remWidth of the notes card.

Accessibility

Each marker is a real <button type="button"> with aria-haspopup="dialog", aria-expanded and aria-controls, named in full (“3 notes on Diana Okafor, 1 critical, 1 caution, needs acknowledgment”); its visible count and shape icon are aria-hidden. Severity never relies on color: the icon shape differs (circle / triangle / octagon), the card spells it out (“Critical”), an unacknowledged critical note makes the marker solid, and forced-colors mode keeps shapes and borders with system colors. Keyboard: focusing a marker with the keyboard previews its notes without moving focus; Enter, Space or ArrowDown pins the card and moves focus into it, Tab from the marker enters the card, Tab past its last control continues to whatever follows the marker in the page, Shift+Tab returns to the marker, and Escape closes the card and returns focus. The card is a labelled non-modal dialog (Popover API top layer, no focus trap) dismissed by Escape or an outside click. Markers are never nested inside other interactive content: inside a link or button the marker becomes a non-focusable role="img" whose label joins the control’s name, and Alt+ArrowDown on that control opens the notes (announced through aria-keyshortcuts). Guarded actions get aria-describedby pointing to a hidden description (“Requires acknowledging 1 critical note on Diana Okafor first: …”) and data-entity-guarded while held; a held click opens the card with focus on “Acknowledge and continue”, the requirement is described by aria-describedby, and Escape or Cancel returns focus to the action. Additions and acknowledgments are announced in a polite live region. Reduced motion (prefers-reduced-motion or data-motion="reduce") removes the stamp, the marker entrance and the card scale; nothing loops.