Unobeyable · component reference

<trust-warning>

Overview

<trust-warning> is the one custom element behind every redesigned certificate and link warning in this project. It takes a connection's details, runs them through classify(), and renders whatever that situation calls for: nothing, a quiet indicator, or an interrupting panel. It never decides on its own what a situation means; that logic lives entirely in component/classify.js.

It renders into light DOM, inside a wrapper carrying the class tw, so the page's own token values (light or dark) apply without any shadow-DOM boundary to cross.

Anatomy

A caution or stop panel, top to bottom, with the real markup below it.

    Severity

    Four levels. Colour is never the only signal: each also has its own icon shape and text label.

    None

    No interrupt, no indicator. The connection needs no comment.

    Notice

    No interrupt. A quiet, persistent indicator pill (circle with a check).

    Caution

    Interrupts. Proceeding is a reasonable, remembered choice (triangle).

    Stop

    Interrupts. No proceed action on the first screen (octagon).

    States

    Every situation classify() can return, each rendered from a real scenario in prototypes/scenarios.js.

    API

    Property / attribute / eventTypeDescription
    contextproperty, object The classify() input, plus optional display vars org, orgTeam, renewalChanges. Setting it re-renders.
    openattribute, boolean Whether the panel or indicator is currently shown. Toggling it re-renders.
    resultproperty, object (read-only) The classify() output for the current context: situation, severity, actions, reportTo.
    tw-decisionevent, CustomEvent Fires on every action taken. detail: {action, situation, severity, view}, bubbles.

    Copy rules

    From the top of component/copy.js:

    • The title says what is happening, not how the reader should feel.
    • Say what the browser knows and what it cannot know, in that order.
    • Every action label is a verb phrase that describes its outcome.
    • "Secure" is never used. The browser only knows "verified" or "not verified".
    • No exclamation marks, no "Warning:", no "Your connection is not private".
    Do“{host} is a device on your own network”
    Don't“Your connection is not private”
    Do“You chose to trust {host}”
    Don't“This site is now secure”
    Do“{host} is presenting a different certificate”
    Don't“Warning: certificate mismatch”
    Do“This network is inspecting encrypted traffic”
    Don't“Your connection is not private”
    Do“You haven't signed in to {host} before”
    Don't“This link looks suspicious”

    Accessibility

    • Focus order. When the panel opens, or its view state changes (warning to reviewing, to reporting, and so on), focus moves to the <h2> title, which carries tabindex="-1".
    • Escape. Closes the report, review, or fingerprint-comparison sub-view back to the base warning. Escape never proceeds and never trusts a device: there is no path from Escape to a resolved, trusted state.
    • alertdialog role. Caution and stop panels are a <section role="alertdialog" aria-labelledby aria-describedby>, so assistive technology announces them as an interrupting dialog tied to the title and body.
    • Colour independence. Every severity also has a distinct icon shape (circle, triangle, octagon) and a text label, so nothing is communicated by colour alone.
    • Targets. Every interactive control has a minimum hit area of var(--target) (44px), including on narrow screens where buttons stack full-width.
    • Reduced motion. The panel's entry animation is skipped entirely under prefers-reduced-motion: reduce.

    Using it

    Import the element, set its context, and listen for decisions.

    import "./component/trust-warning.js";
    
    const warning = document.createElement("trust-warning");
    warning.context = {
      kind: "tls",
      host: "192.168.1.40",
      certProblem: "self-signed",
      fingerprint: "4F:9C:1E:...",
      publicKey: "spki:7d1f0a",
      issuer: "192.168.1.40",
      accepted: null,
    };
    warning.open = true;
    document.body.appendChild(warning);
    
    warning.addEventListener("tw-decision", (e) => {
      console.log(e.detail.action, e.detail.situation);
    });