# Status states How the extension communicates per-tab state, in two layers: the toolbar badge (at-a-glance) and the popup's status pills (in words). Two rules organize the badge vocabulary: - A badge appears only when there is something worth noticing, so its mere presence is the first signal. Ordinary pages carry no badge at all. - The channels split: the **color** says what the page is (green: verified publication, amber: needs a look, red: an account you blocked), the **glyph** says your relationship to it (`+` can subscribe, `?` unknown, `✓` subscribed, `X` blocked). No state is carried by color alone, so the vocabulary survives color blindness — under red-green deficiency the green drifts to a neutral grey, but presence and glyph still carry every state. - The two are independent, so they combine. A trailing `*` is the one modifier: your relationship to the publication is unchanged, but this page is not its verified home. It rides with the amber so the caveat is not color alone, and it reads as what it is — a footnote, whose text is in the popup. Every state also sets the toolbar icon's tooltip title, so hover and screen readers get the state in words. ## Toolbar badge | | State | When | Tooltip | |---|---|---|---| | ![no badge](img/badge-none.png) | no badge | Non-http pages (no tooltip), pages with no publication, or the browser is offline. Being offline is not a property of the page, so it never gets an amber badge. | `substandard — no publication on this page` / `substandard — offline; publication lookups need a connection` | | ![checking](img/badge-checking.png) | `checking` | Navigation started or a re-check is running; the result is not in yet. Transient. | `substandard — checking this page` | | ![detected](img/badge-detected.png) | `detected` | A verified publication; you are signed in and not subscribed. The `+` marks the available action. | `substandard — publication detected` | | ![signedout](img/badge-signedout.png) | `signedout` | A verified publication, but your subscription state is unknown: you are signed out, your session expired, or the subscription list could not load. Kept separate from `detected` so publications you already follow are not offered a second subscribe. | `substandard — publication detected; subscription state unknown` | | ![subscribed](img/badge-subscribed.png) | `subscribed` | You are subscribed to this publication. | `substandard — subscribed to this publication` | | ![detected-unverified](img/badge-detected-unverified.png) | `detected-unverified` | `detected`, but the page is not the publication's verified home — most often a third-party reader showing somebody else's publication. The glyph and the offer are unchanged; the `*` is the caveat, and the popup names the real home and links it. | `substandard — publication detected, but this site is not the verified publisher` | | ![signedout-unverified](img/badge-signedout-unverified.png) | `signedout-unverified` | `signedout` on such a page: neither the publisher nor your relationship to it is settled. | `substandard — publication detected, subscription state unknown; this site is not the verified publisher` | | ![subscribed-unverified](img/badge-subscribed-unverified.png) | `subscribed-unverified` | `subscribed` on such a page. A subscription is a definite relationship and it keeps its `✓`, but it no longer suppresses the caveat — the badge used to show a plain green `✓` here while the popup called the same page unverified. | `substandard — subscribed, but this site is not the verified publisher` | | ![failed](img/badge-failed.png) | `failed` | Detection could not run for this page, so nothing is known about it — a record fetch that failed, a resolver that did not answer. Distinct from a `*` state: there, the extension knows something and is qualifying it. | `substandard — could not check this page` | | ![blocked](img/badge-blocked.png) | `blocked` | The publication belongs to an account you have blocked. The only red in the vocabulary, and the only state that outranks every other — a block you made contradicting a subscription you made is exactly what wants surfacing. | `substandard — you have blocked the account behind this publication` | The mapping lives in `src/lib/icon.ts` (`iconStateFor`, `badgeFor`, `titleFor`); the worker applies it in `src/background.ts`. ## Popup status pills The popup shows every applicable status at once, weighted by severity: errors (red, circle icon) on top, then alerts (red, diamond icon), then warnings (amber, triangle icon), with informational lines (grey, no banner) at the bottom. The two reds mean different things — an error is the extension failing at its job, an alert is the extension working and the answer being serious, like a block you made — so each keeps its own shape, and severity reads without color. The mapping lives in `src/lib/status.ts` (`statusMessagesFor`). A blocked account is also not shown on its own card: no avatar, no display name, no bio, just "Blocked user" and the handle (`blockedIdentity` in `src/lib/profile.ts`). The publication keeps its own name, icon and description — the block is on the account, not on what it published. Moderation labels are chips on the card rather than status pills, and they use the same two loud shapes: a label its labeler calls an alert gets the red diamond, anything milder the amber triangle. A label that asks for the content to be put behind a click covers the card until the reader asks for it, and that cover is red when the label is an alert. | Screenshot | Scenario | |---|---| | ![no publication](img/popup-none.png) | No publication on the page: a single informational line. | | ![publication](img/popup-publication.png) | A verified publication, signed out, after clicking the greyed-out Subscribe: the click raises a warning pill pointing at Atmosphere sign-in. Before the click there are no pills. Captured on standard.site, which carries no link-tag hint — the origin well-known probe found it. | | ![loading](img/popup-loading.png) | The same publication as the row above, caught mid-load: the account card and the subscriber row stand placeholders at the height their content will take, while the publication itself is already drawn. Placeholders only appear once a lookup has run past `SHOW_AFTER_MS` (`src/popup/cards/loading.ts`), so a cache hit never shows one. Captured with those two lookups held open — a slow network, not a failed one. | | ![subscribed](img/popup-subscribed.png) | Signed in and subscribed: no pills, the card speaks for itself. Captured on permadeath.com, the author's own publication, which emits the link hint and answers the origin well-known both. | | ![unverified](img/popup-unverified.png) | The page shows a publication but is not its verified home; the warning links the real one. Captured on Standard Reader's view of the AT Protocol blog. | | ![blocked](img/popup-blocked.png) | The publication belongs to an account you have blocked: the alert pill names it and links its profile, the account card shows "Blocked user" and the handle instead of their avatar, name and bio, and Subscribe has been clicked once, so it is armed as the red "Subscribe anyway?" that the second click would go through with. | | ![labeled](img/popup-labeled.png) | Everything at once: an account you blocked, publishing something two labelers you listen to have both labeled, with Subscribe armed. Shown after clicking "Show anyway" — both labels blur content, so the popup covers the card until the reader asks for it. The only fully invented card in the set (see below). | | ![stacked pills](img/popup-stack.png) | Two facts at once, ranked: record fetching failed (error) and the stored session expired (informational). | | ![offline](img/popup-offline.png) | The browser is offline; the toolbar shows no badge and the popup explains why. | ## Regenerating the images The screenshots are produced by a real headless Chrome running the built extension: ```sh npm run capture # build, then re-shoot docs/img/ npm run recapture # the above, then every asset derived from the shots ``` `capture` builds first on purpose: the capture loads the extension from `dist/`, so an unbuilt change screenshots the *previous* popup. Running the script directly against a stale `dist/` is refused with a message saying so, rather than timing out on a state the built popup does not have. `recapture` follows with `npm run assets`, which is what puts the new shots into the store tiles (`npm run cws`) and the frontpage (`npm run shots`). It runs Chrome twice. The failure states (no publication, fetch failed, offline) come from an offline run with all external DNS mapped to localhost, so they are real failures and nothing leaves the machine. The states that show a publication card come from an online run against real standard.site publications — listed as `REAL` in the script, one per detection route — so the card, the icon, and the wording are a publisher's own data rather than a fixture. Only the account mirror, the subscription record and the block are fixtures; the capture never signs in. The blocked capture also rewrites the publication's owner to the fixture DID, so the docs never show a real account as one you blocked. The moderated capture (`popup-labeled`) is the exception, and is a fixture from top to bottom: no real publisher is going to model "blocked, and labeled by two labelers", so the publication, its account and its page are invented (`BAD_BLOG` in the script, on `example.com`, with placeholder copy). Its picture is two emoji in an inline SVG, drawn by the capture machine's own color-emoji font, so a machine without one shoots boxes there. The account card is seeded into the popup's own cache and the capture checks that the seeded handle is the handle that rendered — an unresolvable fixture DID otherwise degrades quietly to its bare DID, which is how a cache-key change once reached these images. The labelers are real — Bluesky's own moderation service and skywatch.blue — and the only thing stubbed about them is their answer about this fixture: `com.atproto.label.queryLabels` is served from the script, while resolving each labeler, reading its service record and interpreting its label value definitions all happen for real. So the pill names, their severity and the cover are the labelers' own, and a labeler that stops declaring one of these values fails the capture. There is no local stand-in for a labeler to use instead: a labeler is addressed by DID and the extension refuses a cleartext labeler endpoint. These captures are also what the public sees. `scripts/shots.mjs` names the ones the Chrome Web Store listing and the substandard.blog homepage share, and which of those get their own listing screenshot; `npm run recapture` covers the downstream work, and on its own it is `npm run cws` to recompose the listing screenshots and `npm run shots` to copy the captures into the frontpage (the prek hooks do both, and `scripts/deploy-site.sh` re-runs the copy so a deploy cannot ship a popup the extension no longer has). The images are clipped to the popup's own measured size, so widening `body` in `popup/popup.css` re-crops them instead of cutting them off, and each `brand/cws/screenshot-*.png` follows its capture's aspect ratio. Headless Chrome has no toolbar, so the badge tiles are drawn from the running worker's own `badgeFor` specs on a toolbar-like ground — colors and glyphs are authoritative, the tile framing is illustrative. A publication that moves or goes away fails the capture loudly (the script requires each `REAL` page to verify) rather than quietly changing the docs. ## What counts as a finished popup Every capture but `popup-loading` waits for the whole of this, as one question asked of the popup at one moment: no lookup still out (the card drops `aria-busy`), no placeholder still standing, and every `` the popup has given a `src` finished. `popup-loading` waits for the opposite, since the placeholders are what it is a picture of. The three used to be waited for one after another, which does not hold, because each of them *creates* the next: the account lookup is what gives the avatar its `src`, so an image check that ran before that lookup landed was a check of a popup that had no avatar yet. It passed, `aria-busy` cleared at the instant the lookup landed, and the shot went out a fifth of a second later with a grey circle where the account's picture goes. The question is also asked again after the hold, immediately before the shutter. Everything the capture waits for is a sample of a popup that is still changing, and the screenshot is the far side of a wait and several round trips.