Skip to the page
TACTILE UI

Kit part · Scroll areas

Loupe scroll

A scroll area whose scrollbar is a map of what it holds. A narrow machined rail runs beside the text with every line drawn small on its floor, headings darker and code as a box. A glass loupe rides over the part in view and shows it a little magnified. Drag the loupe to scroll, or press the rail to jump there; underneath it is a native scroll container.

Element
<tui-loupe-scroll>
Needs
kit.css + kit.mjs
Rules
loupe-scroll.rules.md · 5
Finishes
Aluminium · Graphite · Porcelain · Black
Volumes
Tactile · Quiet
On this page
  1. Variants
  2. Every state, at once
  3. API
  4. Markup
  5. Accessibility
  6. How it’s built
  7. Rules
  8. Related parts
01

Variants

4.2.1

Fixes the slow query on db-2 that took the first canary out. p95 is back under 200 ms.

4.2

Canary gates can hold on errors as well as latency. The deploy board shows every region on one line.

  • Signing keys can be revoked from the console.
  • The audit log exports to cold storage every night.

4.1

Release notes go to the team by email when a release is live everywhere.

4.2.1

Fixes the slow query on db-2 that took the first canary out. p95 is back under 200 ms.

4.2

Canary gates can hold on errors as well as latency. The deploy board shows every region on one line.

  • Signing keys can be revoked from the console.
  • The audit log exports to cold storage every night.

4.1

Release notes go to the team by email when a release is live everywhere.

Release notes
  • 09:02 Cut release/4.2 from main
  • 09:10 Build 412 passed
  • 09:18 Signed with lode-prod-3
  • 09:31 Staging took 4.2
  • 09:48 Canary at 5%
  • 10:21 p95 at 612 ms
  • 10:24 Canary rolled back
  • 10:40 Slow query found on db-2
  • 11:03 Cut 4.2.1
  • 11:40 Canary at 5%
  • 12:02 Canary at 25%
  • 12:15 eu-west at 100%
  • 12:40 us-east at 100%
  • 13:05 ap-south at 100%
  • 13:30 Errors flat at 0.02%
  • 14:10 4.2.1 live everywhere
A log, one line each

4.2.1

Fixes the slow query on db-2 that took the first canary out. p95 is back under 200 ms, and the canary went out in three steps with no errors.

Short: the loupe is most of the rail

When this runs

Lode takes a canary out by itself when p95 latency passes 800 ms for three minutes, or when errors pass one per cent of requests. This runbook is for the person on call when that happens.

The first five minutes

  1. Look at the deploy board. The canary shows red and the release is held.
  2. Open the canary’s dashboard and note the minute it rolled back.
  3. Say in the releases channel that the canary is out and that you are on it.

Find the cause

Most rollbacks in the last year came from one of three places: a slow query, a cache that went cold, or a service outside Lode that timed out.

Start with the database. On db-2, list the slowest queries of the last quarter of an hour:

lode db top --host db-2 --since 15m --by p95

If one query is far slower than the rest, it is almost always the cause. The release notes say which change added it.

A cold cache

A cold cache looks like a slow start that gets better by itself. If latency was already falling before the rollback, warm the cache and try the canary again:

lode cache warm --region eu-west

A service outside Lode

If the slow calls all go to one service that is not ours, read its status page before anything else. There is nothing to fix on our side until it is back.

Ship the fix

Cut a patch release from the release branch, not from main, so nothing else goes out with it. The canary takes five per cent of traffic, then 25, then all of it, one region at a time.

lode release cut --from release/4.2 --patch
lode canary start --steps 5,25,100

Afterwards

Write two lines in the incident log: what rolled back and what fixed it. If the same cause comes back within a month, it gets a ticket of its own.

A narrow rail · --ls-rail
02

Every state, at once

Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.

Aluminium

PartAt the topHalfwayAt the endHeld
Rail

Graphite

PartAt the topHalfwayAt the endHeld
Rail
03

API

NameKindValuesWhat it does
<tui-loupe-scroll>elementlabelThe scroll area and its rail. Give it a height; what goes in it is yours: headings, paragraphs, lists, code, pictures.
labelattributerequiredNames the region that scrolls, which is in the tab order so the keyboard can scroll it.
.tui-loupe-scroll__view · __pageclassbuiltThe native scroll container, role="region", and the page inside it, set on the kit’s type.
.tui-loupe-scroll__rail · __map · __barclassaria-hiddenThe rail, and the map on its floor: a bar for every line, with data-kind head, text, code or media.
.tui-loupe-scroll__loupe · __lensclassaria-hiddenThe glass over the part in view, and the map under it, magnified 1.4 times.
--ls-railcustom propertylengthThe rail’s width: 60px, and 48px on a phone.
refresh()methodnoneRedraw the map now. It redraws itself whenever the content or the box changes size, so this is rarely needed.
drag · presspointeron the railDrag the loupe and the text scrolls with it; press the rail and the text moves to put that point in the middle.
loupeWindow · loupeScrollFor · loupeDrag · loupeScale · loupeLines · loupeKindfunctionkit.mjsThe rules, pure: where the loupe sits, where a press on the rail scrolls to, what a drag does, the map’s scale, runs of text joined into lines, and what a block is on the map.
04

Markup

<tui-loupe-scroll label="Runbook: the canary rolled back" style="height: 380px">
  <h3>When this runs</h3>
  <p>Lode takes a canary out by itself when p95 latency passes 800 ms…</p>
  <h3>The first five minutes</h3>
  <ol><li>…</li></ol>
  <pre>lode db top --host db-2 --since 15m --by p95</pre>
  …
</tui-loupe-scroll>
import '/kit/kit.mjs';   // defines <tui-loupe-scroll>

// It keeps its own map up to date: new content, a new width, fonts arriving.
const doc = document.querySelector('tui-loupe-scroll');
doc.querySelector('.tui-loupe-scroll__page').insertAdjacentHTML('beforeend', '<h3>Handover</h3><p>…</p>');

// The scrolling is the browser's own, so everything you know still works.
doc.querySelector('.tui-loupe-scroll__view').scrollTo({ top: 0 });

Needs kit.css and kit.mjs. How to install the kit.

05

Accessibility

  • The text is in a native scroll container, a region named by its label and in the tab order, so the wheel, touch, the arrow keys, Page Up and Down, Space, Home and End scroll it as they do anywhere.
  • The rail is a picture and a pointer’s shortcut, as a scrollbar is: it is hidden from assistive technology and takes no focus, and there is nothing on it that scrolling does not reach.
  • The browser’s scrollbar is hidden only while the map stands in for it. Without the script it is back.
  • Headings on the map are heavier and darker than text, not another colour, in the finish’s own ink.
  • A press on the rail glides to its place; with reduced motion it jumps.
  • The loupe is never shorter than 18px, so it can still be caught in a long document.
06

How it’s built

The map is not a picture of the page but its line boxes. For each block the script puts a Range round its contents and asks for its client rects, one for every run of text on a line, and loupeLines joins the runs on each line into one bar from its first letter to its last. Every bar is an absolutely placed <i>, scaled to the rail: down its height to fit the whole document, across its width to fit the line. Headings are thicker and darker; a pre is a box with its lines inside it; a picture is a hatched box. There is no canvas. A ResizeObserver on the box, the page and the rail redraws the map whenever any of them changes size, which covers new content, a new width and fonts arriving. loupeWindow places the loupe; its lens holds a copy of the map, moved and scaled 1.4 times about the loupe’s middle, on a lighter floor under a bevel and a sheen.

07

Rules

loupe-scroll.rules.mdIn the brain

For a long document read in one box, where its shape helps you find your place.

5 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.

08
  1. HubReserve raised to 20%
  2. Meter 2291Missed three readings
  3. HubMeter 2291 is back
  4. BatteryFull at 13.4 kWh
  5. GridExporting 2.1 kW at 15p
  6. CarBoost charge started

Scroll area

A region that scrolls, with its scrollbar made as a machined rail: a groove cut into the plate and a thumb like a fader cap riding in it. Scrolling itself is the browser’s own.

  1. Build passed

  2. Canary at 5%

  3. Rolled back

Timeline

An event log like the roll of a chart recorder. The rail is engraved down the plate and each event is a tick stamped across it, with its time in tabular figures and its words beside it. A lamp on the rail is the only colour, and a marker for the present separates what has happened from what is still to come.

  • parts
  • calendar.mjs
  • carousel.mjs

File tree

Folders and files as a real tree, printed like a wiring diagram: traces with an elbow into every item, chevrons that turn, and the chosen file set in a recess with its lamp lit. Written as a plain nested list.

Docs
Finish
Volume