独家

Unsourced <mv-unsourced>

针对生成文本或长文本(AI 回答、客服回复、研究摘要、报告)的逐条论断溯源:每条论断都会在原位单独显示是否有依据支撑。

文化出处

《木偶奇遇记》,卡洛·科洛迪(1883 年,小说)。木头男孩每说一次谎鼻子就会变长,于是谎言会自己显形,就长在说谎者身上,无需任何人指出。这里,每个缺乏依据的论断都会在末尾长出一条安静的尾巴,越缺乏依据就越长,一旦附上证据,它就会缩回成一个带编号的来源标签。

工作原理

面向生成式或长篇文本(AI 回答、客服回复、研究摘要、报告)的论断级溯源:每个论断都在原位单独显示是否有依据支撑。包裹用 <mark data-claim> 标记论断的文本(或者提供文本以及按偏移量或精确匹配给出的论断片段,即溯源 API 返回的格式)和一个来源库;每个论断的状态为 sourced、weak、contradicted、unsourced、opinion 或 checking,可由其证据(source、quote、stance supports | partial | contradicts、score)推导,也可显式设置。有依据的论断会带上像脚注一样编号的小来源标签;无依据的论断会从最后一个词延伸出一条安静的细线尾巴,越缺乏依据就越长(论断长度 × 缺失的支撑),每种状态有不同形状(空心圆环 = 无来源,截止条 = 被反驳,点线 = 依据薄弱),悬停或聚焦时显示 “No source” 标签,一旦附上来源,尾巴就会缩回成标签。悬停、聚焦或点击标签会打开证据:每个来源及其匹配的引文,与论断相同的词会高亮;无依据的论断提供 Find a source(可取消的 mv-find-source,其 waitUntil(promise) 让应用执行检索并附上找到的内容)、Mark as opinion 和 Remove claim(可在原位撤销,toText() 返回不含该论断的文本)。汇总栏在覆盖率进度条上方显示 “8 claims · 3 sourced · 1 weak · 2 unsourced”,可逐个跳转到需要来源的论断([ 和 ] 键),并可将其他内容调暗;可选的编号 Sources 列表放在文本末尾。

分类数据展示
类型Web Component(<mv-unsourced>)
状态稳定版
套件可核查的 AI
同时安装button
Keywordsexclusive, culture, ai, llm, grounding, citations, sources, claims, fact-check, hallucination, verification, evidence, provenance, footnotes, review, support-reply, rag, annotation

When to use

  • An AI-drafted support reply or email must be checked for promises and facts that no policy or record backs before it is sent
  • An AI answer or research summary should show which statements are cited, weakly supported, contradicted or unsupported
  • A grounding or fact-check service returns claim spans with evidence and the text needs inline citations plus a review pass
  • Reviewers must step through unsupported claims one by one and fix, soften or remove each without leaving the text

Avoid when

  • The model returns a confidence score per passage rather than sources to check → use Certainty instead
  • Several accounts of the same events must be reconciled fact by fact on a timeline → use Accounts instead
  • The need is to show how a single figure, chart or score was computed → use Curtain instead

安装

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

使用 Marvelous UI MCP 服务器的 AI 智能体:install_components({ slugs: ["unsourced"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

复制的文件(含依赖):tokens/tokens.css, core/base.css, components/button/button.css, core/dismiss.js, core/dom.js, core/element.js, core/motion.js, core/position.js, components/unsourced/unsourced.js, components/unsourced/unsourced.css。

用法

快速开始,最小可运行标记:

<mv-unsourced>
  <script type="application/json" data-sources>[{ "id": "returns", "title": "Returns policy" }]</script>
  <p><mark data-claim data-cites="returns">Damaged items are refunded within 30 days</mark>, and <mark data-claim>we'll waive the $12 restocking fee</mark>.</p>
</mv-unsourced>

标准标记,可在此基础上通过属性、data-* 和 CSS 变量进行定制:

<div id="us-demo" style="width:min(100%,50rem);margin-inline:auto">
  <style>
    #us-demo { display:grid; gap:2rem }
    #us-demo .us-card { display:grid; gap:1rem; padding:1.25rem 1.5rem 1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-xs) }
    #us-demo .us-head { display:flex; align-items:flex-start; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap }
    #us-demo .us-head h3 { margin:0; font-size:1rem; letter-spacing:-.01em }
    #us-demo .us-head p { margin:.125rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem }
    #us-demo .us-ask { margin:0; padding:.7rem .9rem; border-radius:var(--mv-radius-lg); background:var(--mv-bg-muted); color:var(--mv-fg); font-size:.875rem; line-height:1.5 }
    #us-demo .us-ask b { display:block; margin-bottom:.15rem; color:var(--mv-fg-muted); font-size:.75rem; font-weight:600 }
    #us-demo mv-unsourced { font-size:.975rem }
    #us-demo mv-unsourced p { margin:0 0 .75rem }
    #us-demo .us-foot { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding-top:1rem; border-top:1px solid var(--mv-border) }
    #us-demo .us-state { display:flex; align-items:center; gap:.5rem; color:var(--mv-fg-muted); font-size:.8125rem }
    #us-demo .us-actions { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap }
    #us-demo .us-foot .mv-choice { font-size:.8125rem }
    #us-demo details { font-size:.8125rem; color:var(--mv-fg-muted) }
    #us-demo summary { cursor:pointer; width:max-content; border-radius:var(--mv-radius-sm) }
    #us-demo summary:focus-visible { outline:none; box-shadow:var(--mv-focus-ring) }
    #us-demo pre { margin:.5rem 0 0; padding:.75rem .9rem; border-radius:var(--mv-radius-md); background:var(--mv-bg-subtle); color:var(--mv-fg); font:.8125rem/1.55 var(--mv-font-sans); white-space:pre-wrap }
    #us-demo .us-hint { margin:-.75rem 0 0; color:var(--mv-fg-muted); font-size:.75rem; text-align:center }
    #us-demo kbd { padding:0 .3em; border:1px solid var(--mv-border); border-bottom-width:2px; border-radius:4px; font:600 .6875rem/1.4 var(--mv-font-mono); color:var(--mv-fg) }
  </style>

  <!-- 1 · AI-drafted support reply, annotated markup -->
  <section class="us-card" aria-labelledby="us-t1">
    <header class="us-head">
      <div>
        <h3 id="us-t1">Reply draft</h3>
        <p>Ticket #58214 · Ana Oliveira · Order NW-48213</p>
      </div>
      <span class="mv-badge" data-variant="secondary" data-shape="pill">AI draft</span>
    </header>
    <p class="us-ask"><b>Ana Oliveira · 9:12 AM</b>Hi, my Brewline French Press arrived with a cracked lid. Can I get a refund, and do I have to pay for return shipping?</p>

    <mv-unsourced id="us-reply" label="Reply grounding">
      <script type="application/json" data-sources>[
        { "id": "order", "title": "Order NW-48213", "publisher": "Order history", "date": "2026-09-18", "snippet": "Delivered Sep 18, 2026, 2:14 PM · FedEx Ground · signed by A. Oliveira." },
        { "id": "returns", "title": "Returns & refunds policy", "url": "https://help.example.com/returns", "publisher": "Help Center", "date": "2026-03-04" },
        { "id": "shipping", "title": "Return shipping FAQ", "url": "https://help.example.com/return-shipping", "publisher": "Help Center", "date": "2026-05-12" },
        { "id": "warranty", "title": "Warranty terms", "url": "https://help.example.com/warranty", "publisher": "Help Center", "date": "2025-11-20" }
      ]</script>
      <script type="application/json" data-claims>[
        { "id": "c2", "evidence": [{ "source": "returns", "quote": "Items that arrive damaged or defective can be refunded or replaced free of charge within 30 days of delivery." }] },
        { "id": "c3", "evidence": [
          { "source": "returns", "quote": "For damaged items, we cover return shipping and email a prepaid label within one business day." },
          { "source": "shipping", "quote": "Prepaid return labels are sent by email and work at any FedEx or UPS drop-off point." }
        ] },
        { "id": "c4", "evidence": [{ "source": "returns", "stance": "contradicts", "quote": "Refunds are issued to the original payment method 5 to 10 business days after the carrier scans the return." }] },
        { "id": "c7", "evidence": [{ "source": "warranty", "stance": "partial", "score": 0.35, "quote": "Brewline accessories are covered by our standard limited warranty." }] }
      ]</script>
      <p>Hi Ana, thanks for reaching out, and I'm sorry the lid arrived cracked.</p>
      <p><mark data-claim="c1" data-cites="order">Your order was delivered on September 18</mark>, and <mark data-claim="c2">damaged items can be refunded or replaced free of charge within 30 days of delivery</mark>, so you're well within the window. <mark data-claim="c3">Return shipping is on us: we'll email you a prepaid label</mark>.</p>
      <p><mark data-claim="c4">Once the carrier scans the package, your refund will post within 2 business days</mark>. <mark data-claim="c5">We'll also waive the $12 restocking fee as a courtesy</mark>.</p>
      <p>If you'd rather keep the press, <mark data-claim="c6">replacement lids ship within 24 hours</mark>, and <mark data-claim="c7">the glass carafe is covered by a 2-year warranty</mark>. <mark data-claim="c8" data-support="opinion">A replacement is probably the faster option for you</mark>.</p>
      <p>Best,<br>Jordan, Customer Care</p>
    </mv-unsourced>

    <details id="us-plain">
      <summary>Plain text that will be sent</summary>
      <pre id="us-plain-text"></pre>
    </details>

    <footer class="us-foot">
      <label class="mv-choice">
        <input type="checkbox" role="switch" class="mv-switch" id="us-chips" checked>
        <span class="mv-choice-text"><span class="mv-choice-title">Source numbers</span></span>
      </label>
      <div class="us-actions">
        <span class="us-state" id="us-state" aria-live="polite"></span>
        <button class="mv-button" type="button" data-size="sm" id="us-send">Send reply</button>
      </div>
    </footer>
  </section>

  <!-- 2 · Research summary from data: text + claim spans, references list -->
  <section class="us-card" aria-labelledby="us-t2">
    <header class="us-head">
      <div>
        <h3 id="us-t2">Churn analysis summary</h3>
        <p>Generated from five internal sources · rendered from text and claim spans</p>
      </div>
      <button class="mv-button" type="button" data-variant="outline" data-size="sm" id="us-recheck">Re-check sources</button>
    </header>
    <mv-unsourced id="us-brief" references attention="unsourced contradicted weak" filter-label="Show only claims to review" label="Summary grounding"></mv-unsourced>
  </section>

  <p class="us-hint">Hover or Tab to a claim to see its evidence · <kbd>[</kbd> <kbd>]</kbd> step through the claims that need a source</p>

  <script type="module">
    await customElements.whenDefined("mv-unsourced");
    const $ = (id) => document.getElementById(id);
    const wait = (ms) => new Promise((r) => setTimeout(r, ms));

    /* ── 1 · Reply draft ─────────────────────────────── */
    const reply = $("us-reply");
    const send = $("us-send");
    const state = $("us-state");
    const plain = $("us-plain-text");

    // What the app's retrieval finds (a real app would query its knowledge base here).
    const found = {
      c6: [{ source: { id: "parts", title: "Replacement parts inventory", publisher: "Inventory · live", date: "2026-09-24" }, quote: "Brewline French Press lids: 148 in stock, ship within 24 hours of order." }],
    };
    reply.addEventListener("mv-find-source", (e) => {
      e.detail.waitUntil(wait(1100).then(() => found[e.detail.id] ?? []));
    });

    const paint = () => {
      if (!reply.isConnected) return;
      const s = reply.stats;
      state.textContent = s.attention
        ? `${s.attention} claim${s.attention > 1 ? "s" : ""} to fix before sending`
        : "Every claim is sourced or marked as opinion";
      send.dataset.variant = s.attention ? "outline" : "";
      if (!s.attention) send.removeAttribute("data-variant");
      plain.textContent = reply.toText();
    };
    reply.addEventListener("mv-claim-change", () => queueMicrotask(paint));
    send.addEventListener("click", () => {
      const s = reply.stats;
      if (s.attention) { reply.next(); return; }
      send.textContent = "Sent";
      send.disabled = true;
    });
    $("us-chips").addEventListener("change", (e) => {
      if (e.target.checked) reply.removeAttribute("citations");
      else reply.setAttribute("citations", "hidden");
    });
    requestAnimationFrame(paint);

    /* ── 2 · Summary from data ───────────────────────── */
    const brief = $("us-brief");
    brief.sources = [
      { id: "dashboard", title: "Churn dashboard", url: "https://metrics.example.com/churn", publisher: "Metrics", date: "2026-09-01" },
      { id: "survey", title: "Cancellation exit survey, August", url: "https://wiki.example.com/research/exit-survey-aug", publisher: "Research ops", date: "2026-09-03" },
      { id: "interviews", title: "Customer interviews, round 3 (n=24)", publisher: "Research ops", date: "2026-08" },
      { id: "experiment", title: "Pricing experiment #14 readout", url: "https://wiki.example.com/growth/exp-14", publisher: "Growth team", date: "2026-08-27" },
      { id: "tickets", title: "Support tickets export", publisher: "Support", date: "2026-09-02" },
    ];
    const claims = [
      { id: "b1", match: "Monthly churn rose to 4.1% in August, up from 3.2% in June", evidence: [{ source: "dashboard", quote: "Monthly plan churn: Jun 3.2% · Jul 3.6% · Aug 4.1%." }] },
      { id: "b2", match: "most customers who left named price as the main reason", evidence: [{ source: "survey", quote: "Price was the top reason for cancelling: 58% of 212 responses in August." }] },
      { id: "b3", match: "the July price increase is the likely trigger", evidence: [{ source: "interviews", stance: "partial", score: 0.3, quote: "Several customers mentioned the new price, but half of them had downgraded before July." }] },
      { id: "b4", match: "Annual plans were largely unaffected", evidence: [{ source: "dashboard", quote: "Annual plan churn: Jun 0.9% · Aug 1.0%." }] },
      { id: "b5", match: "Two competitors now bundle onboarding for free" },
      { id: "b6", match: "the $29 tier lifted trial-to-paid conversion by 9%", evidence: [{ source: "experiment", stance: "contradicts", quote: "Trial-to-paid conversion for the $29 tier: +4.2% (not statistically significant)." }] },
      { id: "b7", match: "Billing tickets doubled over the same period", evidence: [{ source: "tickets", quote: "Billing tickets: 312 in June, 655 in August." }] },
      { id: "b8", match: "Reverting monthly pricing before Q4 would recover most of the lost revenue" },
    ];
    brief.text = "Monthly churn rose to 4.1% in August, up from 3.2% in June, and most customers who left named price as the main reason: the July price increase is the likely trigger. Annual plans were largely unaffected. Two competitors now bundle onboarding for free.\n\nIn the pricing experiment, the $29 tier lifted trial-to-paid conversion by 9%. Billing tickets doubled over the same period. Reverting monthly pricing before Q4 would recover most of the lost revenue.";
    brief.claims = claims;

    const teardown = [{ source: { id: "teardown", title: "Competitive teardown, Q3", publisher: "Product marketing", date: "2026-09-10" }, quote: "Two of our three main competitors now include guided onboarding in every paid plan at no extra cost." }];
    brief.addEventListener("mv-find-source", (e) => {
      e.detail.waitUntil(wait(1200).then(() => (e.detail.id === "b5" ? teardown : [])));
    });

    // Re-check: every claim goes back to “checking”, then resolves one by one.
    let run = 0;
    $("us-recheck").addEventListener("click", async () => {
      const id = ++run;
      brief.close();
      brief.claims = claims.map((c) => ({ ...c, support: "pending" }));
      await wait(700);
      for (const c of claims) {
        if (id !== run || !brief.isConnected) return;
        brief.updateClaim(c.id, { support: null });
        await wait(380);
      }
    });
  </script>
</div>

API

Attributes

Name类型DefaultDescription
attentionspace-separated statusesunsourced contradictedWhich statuses need attention: they are counted in stats.attention, reached by the stepper and the [ ] keys, and stay lit when the filter is on. Add weak for a stricter review.
filterbooleanDims everything except the claims that need attention (ink fades, their text gets an underline in its status color). Reflects the bar's toggle.
filter-labelstringShow only unsourcedText of the filter toggle (e.g. “Show only claims to review” when attention includes weak).
summary"true" | "false"truefalse hides the summary bar (counts, coverage meter, stepper, filter). The [ ] keys and the live announcements keep working.
citations"hidden"Hides the numbered source chips in the text (tails, lens and references are unchanged), for a cleaner reading view.
referencesbooleanAppends a numbered Sources list after the text: title (linked when http/https), publisher, en-US date, and how many claims each source backs or contradicts.
labelstringClaim groundingAccessible name of the summary bar.
data-claimstring (id)On any inline element (<mark> recommended) inside the component: makes it a claim. The value is its id (auto-generated when empty) used by the claims data, events and methods. Nested claims are ignored; claims added later (streaming) are picked up.
data-cites / data-contradictsspace-separated source idsOn a claim: sources that support it / contradict it. The quote defaults to the source's snippet.
data-supportsourced | weak | contradicted | unsourced | opinion | pendingOn a claim: explicit status, overriding the one derived from evidence (claims data with a support key wins over it).
data-quote / data-notestringOn a claim: quote for its first cited source / note shown in the lens instead of the default status sentence.
data-status / data-attention / data-active / data-current / data-searchingset by the componentOn claims: computed status (or removed), needs attention, lens open, reached by the stepper, retrieval running. The host gets data-state="review | clear | empty", data-filter and data-inspecting.

Properties

Name类型Description
sourcesArray<{ id, title, url?, publisher?, date?, snippet? }>Source library. Also read from a <script type="application/json" data-sources> child. Only http(s) URLs become links (new tab); ISO dates are shown as “Mar 4, 2026”.
claimsArray<{ id, support?, note?, evidence?, start?, end?, match? }>Claim data, merged with the markup by id (also read from a <script type="application/json" data-claims> child). evidence: [{ source: id | {…source}, quote?, stance?: "supports" | "partial" | "contradicts", score?: 0..1 }]. With text set, start/end (UTF-16 offsets) or match (exact substring, searched in order) place each claim. Reading returns a snapshot per claim: { id, index, text, status, label, attention, note, evidence (with number), opinion, removed, searching, element }.
textstring | nullData mode: the component renders this plain text itself (text nodes only, never HTML) into a [data-unsourced-body] container, one <p> per blank-line-separated paragraph, with a <mark data-claim> per claim span. A span never crosses a paragraph break; overlapping spans are dropped.
stats{ total, sourced, weak, contradicted, unsourced, opinion, pending, removed, attention, coverage }Read-only tally. total excludes removed claims; coverage = sourced / (total − opinion − pending), null when nothing is factual.
attention / filter / filterLabel / summary / citations / references / labelreflectedMirror the attributes.

Methods

NameDescription
select(id | element)Moves focus to a claim, scrolls it into view and opens its evidence. Returns false for unknown or removed claims.
next() / previous()Steps to the next / previous claim that needs attention (wrapping), like the stepper and the ] and [ keys. Returns false when none is left.
updateClaim(id, patch)Merges { support, evidence, note } into a claim's data (support: null returns to the derived status), e.g. to resolve a pending check. Emits mv-claim-change when the status changes.
addEvidence(id, evidence | evidence[])Attaches evidence found later (when mv-find-source was handled without waitUntil). A supporting source makes an unsourced or weak claim sourced; the tail retracts into a chip.
removeClaim(id) / restoreClaim(id) / markOpinion(id, on = true)Programmatic versions of the lens actions. Return false when nothing changed or the change was canceled.
toText()The current text as plain text (blank line between blocks), without removed claims and without the component's own marks; punctuation orphaned by a removed sentence is dropped.
close()Closes the evidence lens.

Events

NameDescription
mv-claim-selectA claim was opened by click, keyboard focus, a chip, the stepper or select() (not by hover). detail: the claim snapshot plus trigger: "click" | "focus" | "chip" | "step" | "api".
mv-find-sourceFind a source was pressed. Cancelable (preventDefault: nothing happens). detail: { id, text, claim, waitUntil(promise) }. Resolve the promise with evidence (an array, { evidence }, or a single item; inline source objects are added to the library) and it is attached; resolve with nothing for “No source found”, reject for “The search failed”. Without waitUntil the lens says “Search requested” and the app calls addEvidence() later.
mv-claim-changeA claim's status is changing. detail: { id, action: "remove" | "restore" | "opinion" | "claim" | "evidence" | "update", from, to, user, claim }. Cancelable when the user pressed Remove claim, Undo, Mark as opinion or Treat as a claim (user: true): preventDefault() vetoes it, e.g. a claim that legal requires.

Content structure

NameDescription
(content)Any text (paragraphs, lists, headings) containing [data-claim] elements, plus optional JSON <script data-sources> / <script data-claims> children. The summary bar is prepended; the Sources list, the lens (a popover) and a visually hidden live region are appended. Claim text is moved into a .mv-unsourced-text wrapper, never rewritten.

CSS classes

NameDescription
mv-unsourced-claimEvery claim (data-status, data-attention…). Inside: .mv-unsourced-text (the author's text), .mv-unsourced-marks (aria-hidden: .mv-unsourced-chips > .mv-unsourced-chip[data-n][data-stance] and .mv-unsourced-tail > .mv-unsourced-tail-line + .mv-unsourced-tail-label) and a visually hidden status suffix.
mv-unsourced-stub“Claim removed · Undo” button inserted after a removed claim.
mv-unsourced-barSummary bar: .mv-unsourced-counts (per-status .mv-unsourced-count with a .mv-unsourced-glyph), .mv-unsourced-meter (.mv-unsourced-seg[data-status]), .mv-unsourced-stepper (.mv-unsourced-step, .mv-unsourced-pos) and .mv-unsourced-filter[aria-pressed].
mv-unsourced-lensEvidence popover (data-status): .mv-unsourced-lens-head, -note, .mv-unsourced-evidence > .mv-unsourced-ev[data-stance] (number, title link, .mv-unsourced-stance, meta, .mv-unsourced-quote with .mv-unsourced-hit words), .mv-unsourced-lens-find and .mv-unsourced-actions (.mv-button).
mv-unsourced-refsOptional numbered Sources list (.mv-unsourced-ref-list > .mv-unsourced-ref).

CSS variables

NameDefaultDescription
--mv-unsourced-colorwarning mixed with fgTone of unsourced claims: tail, tag, meter segment, active filter.
--mv-unsourced-contradicteddanger mixed with fgTone of contradicted claims and contradicting evidence.
--mv-unsourced-weakwarning mixed with fg-mutedTone of weakly supported claims (dotted tail, dashed chips).
--mv-unsourced-groundedvar(--mv-accent)Tone of sourced claims (chip hover, meter segment).
--mv-unsourced-tail-min / --mv-unsourced-tail-max0.75em / 2.75emShortest and longest tail; the length in between follows --_g, set per claim (0..1, output).
--mv-unsourced-leading1.75Line height of the text (leaves room for the chips and the status tag).

Accessibility

Status never relies on color: each claim is a keyboard stop (role=button, aria-haspopup="dialog", aria-expanded) whose accessible name is its own text plus a visually hidden suffix (“(no source)”, “(sourced, 2 sources)”, “(weak support, 1 source)”, “(contradicted by 1 source)”, “(marked as opinion)”, “(checking sources)”), while the chips and tails are aria-hidden and drawn with distinct shapes (numbered chip, open ring, stop bar, dotted line, dashed line). Keyboard focus opens the evidence without moving focus; Enter or Space moves into it (source links, then the actions), Tab past its last control resumes reading right after the claim, Shift+Tab goes back to the claim and Escape closes it and returns focus. Arrow keys, Home and End move between claims; ] and [ jump to the next and previous claim that needs a source from anywhere inside the component (never while typing in a field), as do the stepper buttons (aria-keyshortcuts, “2 of 3”). The filter is a real toggle button (aria-pressed). The lens is a non-modal role="dialog" labelled by its status line, placed with a manual popover and also closed by an outside click; touch taps open and close it. Removing a claim moves focus to its “Claim removed · Undo” button, whose name quotes the removed text, and Undo returns focus to the claim. A polite live region announces each action and search result followed by the new tally (“Source attached: Replacement parts inventory. Claim is now sourced. 8 claims · 4 sourced · …”), debounced, and never on page load. Source links open in a new tab and say so. Reduced motion (OS setting or data-motion="reduce"): tails and chips appear and retract instantly, the checking pulse stops. Forced colors: claims keep CanvasText on Canvas, tails, chips and tags use CanvasText with borders, highlighted quote words use Mark, focus uses Highlight. Known limits: the component checks nothing by itself; support comes from the sources and data-cites / data-contradicts links you provide (or your mv-find-source handler), and the quote highlight matches words, not meaning. Visible texts are English only for now (no strings property yet).

本页面由 AI 翻译。报告翻译问题