ExklusivBeta
Accounts <mv-accounts>
Zeitleiste aus mehreren Quellen, die mehrere Berichte desselben Ereignisses nebeneinanderlegt und Fakt für Fakt zeigt, wo sie übereinstimmen, wo sie sich widersprechen und wo eine Quelle schweigt: Post-Mortems von Incidents (Gateway-Logs vs. Zahlungsanbieter vs. Client-Telemetrie vs. Notizen der Rufbereitschaft), Lieferstreitigkeiten (Kunde vs. Lager vs. Spediteur), Audit-Trails, Faktenchecks, Besprechungsnotizen mehrerer Teilnehmer. Jede Quelle ist ein einfaches <ol data-source> (oder die Property sources), dessen Einträge <li data-at data-key> mit Zeitstempel Fakten behaupten; Einträge mit demselben Key werden in einer Zeile ausgerichtet und bewertet: agree, conflict (ein abweichender data-value oder eine Zeit außerhalb der Toleranz, wobei die Mehrheit pro Fakt berechnet und ein Gleichstand durch eine optionale Referenzquelle entschieden wird) oder only-one-source; fehlt eine Quelle in einer Zeile, wird sie als stumm angezeigt. Drei Ansichten: Side by side (eine Spur pro Quelle, Zeilen nach Fakt ausgerichtet, mit Verbindungen über die Spuren hinweg und einer Randspalte für die Konsenszeit), Merged (eine Chronologie, in der jeder Fakt auflistet, wer ihn stützt und wer ihm widerspricht, dazu ein Konsensmesser) und One witness (die Geschichte so umgeordnet, wie eine einzelne Quelle sie erzählt, die anderen daneben abgedunkelt). Uhren gehen unterschiedlich, deshalb hat jede Quelle einen Offset, der sich in einem Clocks-Panel bearbeiten oder aus den Fakten schätzen lässt, die sie mit den anderen teilt (Match, Align all clocks); Zeilen ordnen sich per FLIP neu, und geänderte Urteile blinken auf. Eine Zusammenfassung („3 conflicts · 2 facts only one source mentions“) listet jeden Widerspruch als Sprunglink auf, die Auswahl eines Fakts öffnet ein ausformuliertes Urteil unter seiner Zeile, und jeder Status wird als Text ausgeschrieben und mit einer eigenen Form gezeichnet, nie nur über die Farbe.
| Kategorie | Datenanzeige |
|---|---|
| Typ | Web Component (<mv-accounts>) |
| Status | Beta |
| Keywords | exclusive, culture, timeline, multi-source, reconciliation, post-mortem, incident, audit-trail, dispute, fact-check, comparison, consensus, conflict, clock-skew, swimlanes, logs, provenance, keyboard |
When to use
- An incident post-mortem must reconcile logs, vendor reports, client telemetry and human notes into one agreed timeline
- A support or claims team compares a customer's story with carrier and warehouse records to settle a dispute
- Several sources report the same events with clocks that drift, and the skew must be corrected before judging who is right
- Fact-checkers or auditors need to see at a glance which claims are corroborated, contradicted or backed by a single source
Avoid when
- The documents are static forms or records compared field by field rather than timestamped narratives → use Papers instead
- There is a single source of events: a plain chronological list or activity feed is clearer → use Animated List instead
- Thousands of events must be reconciled in bulk; this is a review surface for tens of facts, use a data table with a match column → use Data table instead
Installation
node scripts/add.mjs accounts --out ./src/marvelousKI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["accounts"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/focus.js, core/motion.js, components/accounts/accounts.js, components/accounts/accounts.css.
Verwendung
Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:
<div id="acc-demo" style="width:min(100%,78rem);margin-inline:auto">
<style>
#acc-demo { display:grid; gap:2.25rem }
#acc-demo .acc-head { display:flex; align-items:flex-end; justify-content:space-between; gap:.75rem 1.5rem; flex-wrap:wrap; margin:0 0 .875rem }
#acc-demo .acc-kicker { display:flex; align-items:center; gap:.5rem; margin:0 0 .375rem; color:var(--mv-fg-muted); font-size:.75rem; font-variant-numeric:tabular-nums }
#acc-demo .acc-head h3 { margin:0; font-size:1.125rem; letter-spacing:-.01em }
#acc-demo .acc-sub { margin:.25rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem; max-width:62ch }
#acc-demo .acc-side { display:flex; align-items:center; gap:1rem; flex-wrap:wrap }
#acc-demo .acc-side .mv-choice { font-size:.8125rem }
#acc-demo .acc-log { margin:.625rem 0 0; min-height:1.25rem; color:var(--mv-fg-muted); font-size:.75rem }
#acc-demo .acc-log b { color:var(--mv-fg); font-weight:600 }
#acc-demo code { font-family:var(--mv-font-mono); font-size:.92em }
</style>
<section aria-labelledby="acc-t1">
<div class="acc-head">
<div>
<p class="acc-kicker"><span class="mv-badge" data-variant="danger" data-shape="pill">SEV-2</span> INC-2291 · Sep 14, 2026 · post-mortem draft</p>
<h3 id="acc-t1">Checkout payments failing for 12 minutes</h3>
<p class="acc-sub">Four sources, one outage. Rows line up the same fact across every account; select one to read the verdict, or switch to Merged and One witness.</p>
</div>
<div class="acc-side">
<label class="mv-choice" data-control="end">
<input type="checkbox" role="switch" class="mv-switch" id="acc-skew" checked>
<span class="mv-choice-text"><span class="mv-choice-title">Correct the mobile app clock</span></span>
</label>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" id="acc-next">Next conflict</button>
</div>
</div>
<mv-accounts id="acc-incident" tolerance="1m" time-zone="UTC" label="Incident accounts" selected="degraded">
<ol data-source="API gateway" data-id="gateway" data-description="Edge access logs">
<li data-at="2026-09-14T14:01:05Z" data-key="deploy" data-label="Deploy v4.18.0">Config reload after deploy <code>v4.18.0</code></li>
<li data-at="2026-09-14T14:02:29Z" data-key="degraded" data-label="Payments start timing out">First upstream timeout to payments (30 s)</li>
<li data-at="2026-09-14T14:02:41Z" data-key="first-502" data-value="502" data-label="First 502 at checkout">on <code>POST /checkout</code></li>
<li data-at="2026-09-14T14:08:10Z" data-key="retries" data-label="Retry storm">Traffic on /checkout up 4×, mostly retries</li>
<li data-at="2026-09-14T14:12:20Z" data-key="rollback" data-value="v4.17.2" data-label="Rollback">Config reload after rollback</li>
<li data-at="2026-09-14T14:14:00Z" data-key="recovered" data-label="Checkout recovers">Error rate back under 0.5%</li>
</ol>
<ol data-source="Payment provider" data-id="provider" data-description="Webhook log and RCA">
<li data-at="2026-09-14T14:06:30Z" data-key="degraded">Incident start, per the provider’s RCA</li>
<li data-at="2026-09-14T14:13:30Z" data-key="charges" data-value="0 captured" data-label="Charges during the outage">No charges captured from 14:02 to 14:14</li>
<li data-at="2026-09-14T14:14:30Z" data-key="recovered">Status page: resolved</li>
</ol>
<ol data-source="Mobile app" data-id="app" data-offset="-2m40s" data-description="Client telemetry">
<li data-at="2026-09-14T14:05:11Z" data-key="degraded">Card payment spinner, then a timeout</li>
<li data-at="2026-09-14T14:05:24Z" data-key="first-502" data-value="502">“Something went wrong” shown to 1,204 users</li>
<li data-at="2026-09-14T14:10:53Z" data-key="retries">Auto-retry fires up to 3× per tap</li>
<li data-at="2026-09-14T14:16:05Z" data-key="charges" data-value="Charged twice">Support ticket: charged twice for order #88121</li>
<li data-at="2026-09-14T14:16:40Z" data-key="recovered">Checkout success rate back to 99%</li>
</ol>
<ol data-source="On-call notes" data-id="oncall" data-description="Incident channel">
<li data-at="2026-09-14T14:01:00Z" data-key="deploy">Maya Okafor ships v4.18.0</li>
<li data-at="2026-09-14T14:07:00Z" data-key="paged" data-label="On-call paged">Paged: checkout error rate at 6%</li>
<li data-at="2026-09-14T14:12:00Z" data-key="rollback" data-value="v4.17.2">Rolled back to v4.17.2</li>
<li data-at="2026-09-14T14:18:00Z" data-key="recovered">Declared resolved in #incidents</li>
<li data-at="2026-09-14T14:25:00Z" data-key="rca" data-label="Suspected cause">New connection-pool limit in v4.18.0</li>
</ol>
</mv-accounts>
<p class="acc-log" id="acc-log" aria-live="off">Tip: Alt + ←/→ jumps to the same fact in the next source, Alt + ↑/↓ to the next disagreement.</p>
</section>
<section aria-labelledby="acc-t2">
<div class="acc-head">
<div>
<p class="acc-kicker"><span class="mv-badge" data-variant="warning" data-shape="pill">Dispute</span> Order #88342 · Chicago time</p>
<h3 id="acc-t2">“My package never arrived”</h3>
<p class="acc-sub">Customer, warehouse and carrier merged into one chronology: each fact shows who backs it and who contradicts it. Weight readings are compared on value only.</p>
</div>
</div>
<mv-accounts id="acc-dispute" view="merged" tolerance="30m" time-zone="America/Chicago" label="Delivery dispute accounts">
<ol data-source="Customer" data-description="Support ticket #44710">
<li data-at="2026-09-08T09:12:00-05:00" data-key="ordered" data-label="Order placed">Placed order #88342 on the website</li>
<li data-at="2026-09-10T08:05:00-05:00" data-key="out" data-label="Out for delivery">Got the “out for delivery” text</li>
<li data-at="2026-09-10T18:30:00-05:00" data-key="delivered" data-value="Not received" data-label="Delivery">Nothing at the door when I got home</li>
</ol>
<ol data-source="Warehouse" data-description="Scan events">
<li data-at="2026-09-08T09:13:00-05:00" data-key="ordered">Order received by the warehouse system</li>
<li data-at="2026-09-08T14:40:00-05:00" data-key="packed" data-label="Packed">Packed and sealed, 2 items</li>
<li data-at="2026-09-08T14:41:00-05:00" data-key="weight" data-value="1.4 kg" data-tolerance="any" data-label="Parcel weight">Weighed at the packing station</li>
<li data-at="2026-09-08T17:05:00-05:00" data-key="handoff" data-label="Handed to carrier">Handed over, manifest 7731</li>
</ol>
<ol data-source="Carrier" data-description="Tracking history">
<li data-at="2026-09-08T17:20:00-05:00" data-key="handoff">Picked up at origin</li>
<li data-at="2026-09-09T03:10:00-05:00" data-key="weight" data-value="0.6 kg" data-tolerance="any">Re-weighed at the regional hub</li>
<li data-at="2026-09-10T08:02:00-05:00" data-key="out">Out for delivery</li>
<li data-at="2026-09-10T11:47:00-05:00" data-key="delivered" data-value="Delivered">Left at front door, photo on file</li>
</ol>
</mv-accounts>
</section>
<script type="module">
const inc = document.getElementById("acc-incident");
await customElements.whenDefined("mv-accounts");
const log = document.getElementById("acc-log");
// Toggle the known clock drift of the mobile app: uncorrected, its skew fakes conflicts.
document.getElementById("acc-skew").addEventListener("change", (e) => {
inc.setOffset("app", e.target.checked ? "-2m40s" : 0);
});
inc.addEventListener("mv-offset-change", (e) => {
if (e.detail.source === "app") document.getElementById("acc-skew").checked = e.detail.offset !== 0;
});
document.getElementById("acc-next").addEventListener("click", () => {
const issues = inc.facts.filter((f) => f.status === "conflict");
if (!issues.length) return;
const i = issues.findIndex((f) => f.key === inc.selected);
inc.select(issues[(i + 1) % issues.length].key, { focus: true });
});
inc.addEventListener("mv-select-fact", (e) => {
const { fact } = e.detail;
log.replaceChildren("mv-select-fact → ", Object.assign(document.createElement("b"), { textContent: fact.label }), ` · ${fact.status} · ${fact.entries.length} of ${inc.sources.length} sources`);
});
inc.addEventListener("mv-view-change", (e) => {
log.replaceChildren("mv-view-change → ", Object.assign(document.createElement("b"), { textContent: e.detail.view }));
});
</script>
</div>Kulturelle Referenz
Rashomon, Akira Kurosawa (1950, Film). Ein Ereignis wird viermal von vier Zeugen erzählt, jeder Bericht plausibel und jeder im Widerspruch zu den anderen, sodass sich die Wahrheit nur triangulieren lässt. In der UI wird der Bericht jeder Quelle zu einem Incident zu einer Spur, dieselben Fakten werden über alle Spuren hinweg ausgerichtet und als übereinstimmend, widersprochen oder unbelegt bewertet, und die Geschichte lässt sich nebeneinander, zusammengeführt oder so lesen, wie ein einzelner Zeuge sie erzählt.
API
Attributes
| Name | Typ | Default | Description |
|---|---|---|---|
view | lanes | merged | witness | lanes | lanes: one column per source, rows aligned by fact. merged: one chronology of facts with their supporters, dissenters and a consensus meter. witness: lanes reordered by the witness source's own chronology, that lane first and wider, the others dimmed. |
witness | source id | Source focused by the witness view (default: the first source). | |
tolerance | duration | 1m | Times closer than this count as the same moment ("90s", "2m", "1h", "500ms" or ms). Per fact, data-tolerance on any entry (or factLabels[key].tolerance) overrides it; "any" means time is not compared for that fact (e.g. a measurement taken twice). |
reference | source id | System of record: wins value and time ties when no majority exists, and is the clock kept fixed by Align all clocks (default anchor: the first source). | |
selected | fact key | Selected fact: highlighted in every view, with a written verdict under its row in the lanes views. Reflects user selection. | |
filter | all | issues | all | issues hides facts on which every source agrees (the “Only disagreements” toggle). |
locale | BCP 47 | en-US | Locale for times, durations, plurals and name lists. |
time-zone | IANA zone | Zone used to display times (default: the viewer's; UTC when every time is wall-clock only). Shown in the gutter header. | |
precision | auto | seconds | minutes | auto | Time display precision; auto shows seconds when any entry has them. |
label | string | Accounts | Accessible name of the timeline group. |
heading-level | 1-6 | 3 | Level of the lane headings. |
controls | "true" | "false" | true | "false" hides the built-in toolbar (views, filter, Clocks) when the app drives them. |
data-source | string | On a child <ol>/<ul>: the source's display name. Makes it a lane. The list itself is hidden and re-read live when it changes. | |
data-id / data-offset / data-description | string / duration / string | On a source list: stable id (default: slug of the name), clock correction added to its times ("-2m40s" for a clock 2 min 40 s fast) and a short description under its heading. | |
data-at | ISO date-time | epoch ms | HH:MM[:SS] | On an entry: when the source says it happened. Wall-clock times are compared as is. Entries without a time are ordered after the previous timed entry of their source and never conflict on time. | |
data-key | string | On an entry: the fact it claims. Same key across sources = same fact. Entries without a key are facts only their source mentions. | |
data-value | string | On an entry: the comparable claim (status code, amount, outcome). Compared case-, accent- and space-insensitively (or with the compare property); entries without a value never conflict on value. | |
data-label / data-tolerance | string / duration | "any" | On an entry: the fact's title (first one found wins, default: the key humanized) and a per-fact tolerance. |
Properties
| Name | Typ | Description |
|---|---|---|
sources | Array<{ id?, name, offset?, description?, entries: Array<{ key?, at?, value?, text, label?, tolerance? }> }> | Data alternative to the markup (takes precedence over child lists). Reading it returns the current model with live offsets. |
facts | Array<Fact> | Read-only analysis in consensus order: { key, label, status: "agree" | "conflict" | "single", at (consensus ms), conflict: { value, time }, value (majority), entries: [{ source, key, at, reportedAt, value, text, status: "agree" | "dissent" | "split" | "single", delta, node }], silent: [source ids], verdict (sentence) }. |
summary | { facts, agreed, conflicts, single, silentGaps } | Counts behind the summary line (read-only). |
offsets | Record<sourceId, ms> | Current clock corrections (read-only; use setOffset). |
factLabels | Record<key, { label?, tolerance? }> | Titles and tolerances per fact, when the markup cannot carry them. |
compare | (a: string, b: string, key: string) => boolean | Custom value equality (numeric tolerance, synonyms…). Default: case-, accent- and whitespace-insensitive. |
strings | Partial<Record<string, string>> | Overrides for every visible label, verdict sentence and announcement (plural forms as "one|other", placeholders like {names}, {delta}). English defaults. |
view / witness / tolerance / reference / selected / filter / locale / timeZone / precision / label / headingLevel / controls | reflected | Mirror the attributes. |
Methods
| Name | Description |
|---|---|
select(key, { source?, focus? }) | Selects a fact (null clears). Emits the cancelable mv-select-fact; focus: true moves focus to its first dissenting entry (or the given source) and scrolls it into view. Returns false if unknown or vetoed. |
setOffset(sourceId, value) | Corrects a source's clock (ms or duration string). Emits the cancelable mv-offset-change, re-judges every fact and announces the new summary. |
suggestOffset(sourceId) | Estimated correction for a source: median gap between its times and the other sources' times on the facts they share, rounded to the second. null when it shares nothing. |
alignClocks() | Applies suggestOffset to every source except the reference (two passes) and returns the offsets. Real disagreements survive because the median ignores a minority of outliers. |
refresh() | Re-reads the markup (normally automatic through a MutationObserver). |
Events
| Name | Description |
|---|---|
mv-select-fact | Cancelable, before a fact is selected (click, Enter/Space, summary link, select()). detail: { key, fact, source (id of the entry clicked, or null), entry }. |
mv-view-change | Cancelable, before the view changes. detail: { view, previous, witness }. |
mv-offset-change | Cancelable, before a clock correction is applied. detail: { source, offset, previous, reason: "input" | "align" | "reset" | "api" }. |
mv-reconcile | After every analysis (data, tolerance or offset change). detail: the summary counts. |
Content structure
| Name | Description |
|---|---|
(sources) | Child <ol data-source> / <ul data-source> lists of <li> entries. Entry content (inline markup such as <code> or links) is cloned into the lanes; ids are stripped from the clones. |
CSS classes
| Name | Description |
|---|---|
mv-accounts-bar / -views / -view / -tool / -pick | Toolbar: view radio group, witness picker, “Only disagreements” and Clocks toggles. |
mv-accounts-clocks / -clock-row / -offset | Clock correction panel and its per-source offset inputs. |
mv-accounts-summary / -details / -jumps / -jump | Summary line with counts and the list of disagreements as jump links. |
mv-accounts-grid / -lane / -head / -list | Lanes layout (CSS grid with subgrid lanes so rows align across sources). The fact gutter is the lane with data-role="facts"; data-focused / data-dimmed in witness view. |
mv-accounts-entry / -fact / -link / -verdict | Entry cards (data-status: agree | dissent | split | single | silent, data-kind: time | value), gutter facts, links across lanes and the verdict row. data-hot / data-linked mark every piece of the hovered or focused fact. |
mv-accounts-merged / -card / -claims / -claim / -consensus / -dot | Merged view: fact cards on a rail, their claims per source and the consensus meter (circle = agrees, square = contradicts, dashed = silent). |
mv-accounts-glyph | Status shapes: check disc (agree), crossed diamond (conflict), half disc (single source), dashed ring (silent). |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-accounts-agree | var(--mv-success) | Agreement tone. |
--mv-accounts-conflict | var(--mv-danger) | Conflict tone. |
--mv-accounts-single | var(--mv-warning) | Tone of facts only one source mentions. |
--mv-accounts-silent | var(--mv-fg-subtle) | Tone of silent cells. |
--mv-accounts-gutter | 12.5rem | Width of the fact gutter (9.5rem in narrow containers). |
--mv-accounts-lane-min | 12.5rem | Minimum lane width; below it the lanes scroll horizontally with the gutter sticky. |
--mv-accounts-max-height | none | Caps the height of the lanes / merged list; the lane headers then stay sticky while scrolling. |
Accessibility
Every lane is a <section> with a real heading (level set by heading-level) and an ordered list, the fact gutter included, so screen reader users can browse each account by heading and list. Each entry is a list item that names its fact and ends with a full verdict sentence (“Places it 4 min later than API gateway and Mobile app (2:02:31 PM).”, “Silent here; API gateway and Mobile app mention it.”, the reported time and correction when a clock was adjusted); the short visible labels (Agrees, +4 min, Contradicts, Only source, Silent) are paired with distinct shapes (check disc, crossed diamond, half disc, dashed ring) and never rely on color. The body is one tab stop with a roving tabindex: arrows move within a lane and across lanes on the same fact row, Alt+Left/Right jumps to the same fact in the next source that mentions it (skipping silent ones), Alt+Up/Down to the previous or next disagreement, Home/End and Ctrl+Home/End to the ends, Enter or Space selects a fact and Escape clears it; these instructions are exposed as the group's description. Hovering or focusing any piece of a fact highlights its entries and link in every lane. The view switcher is a radio group (arrow keys), the filter a toggle button (aria-pressed), Clocks a disclosure (aria-expanded) whose offset inputs are labelled per source, accept durations like -2m40s and step with Up/Down (1 s, Shift 1 min) and PageUp/PageDown. The summary is a native <details> whose items are buttons that move focus to the disagreement. View changes, selections (with their verdict) and clock corrections (with the new summary) are announced in a polite live region; focus is kept on the same fact across re-renders. Reduced motion (OS or data-motion="reduce"): no FLIP reordering, no status flash, instant scrolling. Forced colors: shapes use CanvasText, selection and active controls use Highlight outlines, conflicts get a thicker border.