rastrillo / fiesta Public

Let no English through on its own say-so

Review fixes to the leak gate. It no longer excuses English on the
strength of what the catalogs happen to say.

The two inferred exemptions are gone. One excused a word in any
locale whose catalogs translated it as its English and where no draft
said so; the other excused a run that was any string's translation in
that locale. Either let a catalog entry put back to English excuse
itself. In their place are two lists written down beside leakExempt,
each checked by the gate so it cannot outlive its reason:

- leakUnchanged gains the sample words a language writes as English
  does, by locale, all carried from Rastrillo's tables: the cities of
  the select samples (Madrid in es, pt-PT and vi; Austin in es, ga,
  pt-PT and vi; and so on) and the product words Beta, Editor, Email,
  Menu, No, Plan, Pro, Slug and Token where the language borrows or
  shares them. The gate already checks that every id saying the word
  translates it as itself in each locale named.
- leakCollisions names a language's own word spelt like another
  string's English: Vietnamese calls the Tokens page "Token". The gate
  checks the word is the English of something a render says and, in
  each locale named, the translation of a different string.

Kept elements keep their content out of the sweep, as before, but no
longer their spoken attributes: the walker translates every opening
tag's, a <bdi>'s or a brand's included, so the sweep reads them.

Red first, by assertion. Three controls were added beside the four
existing ones, all in one scratch render, with the sweep built again
after the catalogs were changed:
- "← Requests" put back to English in the ja sample catalog (a short
  draft): "the sweep does not report it on day/ja/demo-request.html"
- "A summary needs at least twenty characters." put back to English in
  the es sample catalog (a carried sentence): "the sweep does not
  report it on day/es/frames/field-textarea/with-an-error.html"
- an aria-label on a <bdi> and a title on a shell brand: "the sweep
  does not read the spoken attribute "Help: orders" of a kept element"
Rebuilt after the mutation, the old exemptions also hid the existing
"Sign out", "Copy" and "Overview" controls. All seven are green now,
and the tree sweeps clean with the lists as written; reading kept
elements' attributes found no new English.

No translation, catalog or rendered byte changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 6678f50b6434eb7db0da24278c3ecba31d7bf238 parent a075589
2 files changed, +157 −94
  • internal/gallery/i18n.go +54 −2
  • internal/gallery/i18n_test.go +103 −92

CI failed — run details

CI log
=== gofmt ===
=== no-rastrillo ===
mkdir -p "/var/lib/amadan-runner/w4/repos/rastrillo__fiesta/wt/gallery/.build/tmp"
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
go: downloading go1.26.10 (linux/arm64)
ok  	amadan.net/rastrillo/fiesta	0.619s
=== size ===
go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$' -count=1 -v
=== RUN   TestTheOneLinkIsUnder25KiBGzipped
    build_test.go:138: fiesta-plain.css: 152856 bytes, 22303 bytes gzipped (21.8 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta-signal.css: 152951 bytes, 22414 bytes gzipped (21.9 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta.css: 155770 bytes, 23018 bytes gzipped (22.5 KiB of the 25.0 KiB limit)
--- PASS: TestTheOneLinkIsUnder25KiBGzipped (0.02s)
PASS
ok  	amadan.net/rastrillo/fiesta/internal/build	0.028s
=== suite ===
--- shard 0 of 15 ---
==> shard 0 of 15: make root
go build ./...
go vet ./...
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 go test -count=1 ./...
ok  	amadan.net/rastrillo/fiesta	0.747s
ok  	amadan.net/rastrillo/fiesta/check	5.823s
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	74.980s
ok  	amadan.net/rastrillo/fiesta/example	0.126s
?   	amadan.net/rastrillo/fiesta/hack/seed/english	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/axe	0.095s
ok  	amadan.net/rastrillo/fiesta/internal/build	1.282s
ok  	amadan.net/rastrillo/fiesta/internal/codeview	0.005s
ok  	amadan.net/rastrillo/fiesta/internal/gallery	101.660s
?   	amadan.net/rastrillo/fiesta/internal/harness	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/markup	0.011s
ok  	amadan.net/rastrillo/fiesta/internal/nodetest	0.295s
ok  	amadan.net/rastrillo/fiesta/internal/site	3.627s
ok  	amadan.net/rastrillo/fiesta/internal/tokens	0.022s
ok  	amadan.net/rastrillo/fiesta/internal/www	65.472s
--- shard 1 of 15 ---
==> shard 1 of 15: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
go: downloading honnef.co/go/tools v0.8.2
go: downloading golang.org/x/tools v0.51.0
go: downloading github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
go: downloading golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678
go: downloading golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
--- shard 2 of 15 ---
==> shard 2 of 15: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
go: downloading golang.org/x/vuln v1.8.0
go: downloading golang.org/x/telemetry v0.0.0-20260908163034-4bcc4b2ee518
go: downloading golang.org/x/tools v0.50.0
go: downloading golang.org/x/mod v0.41.0
go: downloading golang.org/x/sync v0.23.0
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 2 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
--- shard 3 of 15 ---
==> shard 3 of 15: make gitleaks
go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' .
go: downloading github.com/zricethezav/gitleaks/v8 v8.30.1
go: downloading github.com/Masterminds/sprig/v3 v3.3.0
go: downloading golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
go: downloading github.com/hashicorp/go-version v1.7.0
go: downloading github.com/charmbracelet/lipgloss v0.5.0
go: downloading github.com/rs/zerolog v1.33.0
go: downloading github.com/spf13/cobra v1.9.1
go: downloading github.com/spf13/viper v1.19.0
go: downloading github.com/BobuSumisu/aho-corasick v1.0.3
go: downloading github.com/gitleaks/go-gitdiff v0.9.1
go: downloading github.com/fatih/semgroup v1.2.0
go: downloading github.com/h2non/filetype v1.1.3
go: downloading github.com/mholt/archives v0.1.2
go: downloading github.com/muesli/termenv v0.15.1
go: downloading github.com/lucasb-eyer/go-colorful v1.2.0
go: downloading github.com/mattn/go-runewidth v0.0.14
go: downloading github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68
go: downloading dario.cat/mergo v1.0.1
go: downloading github.com/Masterminds/goutils v1.1.1
go: downloading github.com/Masterminds/semver/v3 v3.3.0
go: downloading github.com/spf13/cast v1.7.1
go: downloading github.com/shopspring/decimal v1.4.0
go: downloading golang.org/x/crypto v0.35.0
go: downloading github.com/google/uuid v1.6.0
go: downloading github.com/huandu/xstrings v1.5.0
go: downloading github.com/mitchellh/copystructure v1.2.0
go: downloading golang.org/x/sync v0.11.0
go: downloading github.com/klauspost/compress v1.17.11
go: downloading github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3
go: downloading github.com/STARRY-S/zip v0.2.1
go: downloading github.com/klauspost/pgzip v1.2.6
go: downloading github.com/bodgit/sevenzip v1.6.0
go: downloading github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
go: downloading github.com/minio/minlz v1.0.0
go: downloading github.com/nwaples/rardecode/v2 v2.1.0
go: downloading github.com/pierrec/lz4/v4 v4.1.21
go: downloading github.com/sorairolake/lzip-go v0.3.5
go: downloading github.com/therootcompany/xz v1.0.1
go: downloading github.com/ulikunitz/xz v0.5.12
go: downloading golang.org/x/text v0.22.0
go: downloading github.com/mattn/go-colorable v0.1.14
go: downloading github.com/spf13/pflag v1.0.6
go: downloading github.com/rivo/uniseg v0.2.0
go: downloading github.com/fsnotify/fsnotify v1.8.0
go: downloading github.com/mitchellh/mapstructure v1.5.0
go: downloading github.com/sagikazarmark/slog-shim v0.1.0
go: downloading github.com/spf13/afero v1.12.0
go: downloading github.com/aymanbagabas/go-osc52/v2 v2.0.1
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading golang.org/x/sys v0.30.0
go: downloading github.com/mitchellh/reflectwalk v1.0.2
go: downloading github.com/subosito/gotenv v1.6.0
go: downloading github.com/hashicorp/hcl v1.0.0
go: downloading gopkg.in/ini.v1 v1.67.0
go: downloading github.com/magiconair/properties v1.8.9
go: downloading github.com/pelletier/go-toml/v2 v2.2.3
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading github.com/bodgit/plumbing v1.3.0
go: downloading github.com/bodgit/windows v1.0.1
go: downloading github.com/hashicorp/go-multierror v1.1.1
go: downloading go4.org v0.0.0-20230225012048-214862532bf5
go: downloading github.com/hashicorp/golang-lru/v2 v2.0.7
go: downloading github.com/hashicorp/errwrap v1.1.0
5:41PM INF Unknown SCM platform. Use --platform to include links in findings. host=amadan.net
5:41PM INF 1 commits scanned.
5:41PM INF scanned ~7574703 bytes (7.57 MB) in 2.03s
5:41PM INF no leaks found
--- shard 4 of 15 ---
==> shard 4 of 15: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
ok  	amadan.net/rastrillo/fiesta/internal/harness	4.471s
--- shard 5 of 15 ---
==> shard 5 of 15: make browser.check~1of3
./hack/gotest.sh browser 'check~1of3'
gotest: on ip-172-31-19-105, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 1 of 3 of ./check/ runs 95 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	179.766s
--- shard 6 of 15 ---
==> shard 6 of 15: make browser.check~2of3
./hack/gotest.sh browser 'check~2of3'
gotest: on ip-172-31-19-105, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 2 of 3 of ./check/ runs 95 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	179.801s
--- shard 7 of 15 ---
==> shard 7 of 15: make browser.check~3of3
./hack/gotest.sh browser 'check~3of3'
gotest: on ip-172-31-19-105, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
gotest: slice 3 of 3 of ./check/ runs 96 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	189.840s
--- shard 8 of 15 ---
==> shard 8 of 15: make browser.internal.gallery~1of4
./hack/gotest.sh browser 'internal.gallery~1of4'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 4 of ./internal/gallery/ runs 1 of 163 tests
--- FAIL: TestPreviewFrameHeightsFitAtThePhonesNarrowest (309.45s)
    heights_browser_test.go:553: en/index.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/page-header.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/list-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/list-bar-search.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/list-search-submit.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/list-row-action.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/row-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/seg-tabs.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/dropdown.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/bulk-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/media-grid.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/pagination.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/empty-state.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/status-pill.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/badge.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/meter.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/stat.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: en/person.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/actor.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/callout.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: en/detail-list.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/notice.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/form-error.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/job-status.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/field.html at 320px: 7 frames settled; the page requested 7 frame documents
    heights_browser_test.go:553: en/field-text.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: en/field-url.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/field-money.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/field-textarea.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: en/field-select.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: en/field-check.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/choice-field.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/form-foot.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: en/confirm-form.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/field-date.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: en/field-time.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: en/field-datetime.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: en/field-daterange.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/error-page.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: en/back-nav.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/locale-menu.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: en/locale-menu.html at 320px locale-menu/twelve-languages-current-highlighted: 525px of frame for 76px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: en/scheme-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: en/api-tokens.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: en/box.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/button.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:594: en/button.html at 320px: self-check, --ds-hm 100px short reads need 385 in a box of 285
    heights_browser_test.go:553: en/form-layout.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/help.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/list-grid.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/modal.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: en/modal.html at 320px modal/default: 775px of frame for 155px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: en/selbox.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/shell-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/shell-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/stat-band.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/tblock.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-column.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-sidebar-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-console.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-console-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/layout-stage.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-dates.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-durations.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-numbers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-money.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-ratios.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-filesizes.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-identifiers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-people.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-addresses.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-abbr.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/format-output.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: en/signin.html at 320px: 12 frames settled; the page requested 12 frame documents
    heights_browser_test.go:553: ar/index.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/page-header.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/list-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/list-bar-search.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ar/list-search-submit.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/list-row-action.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/row-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/seg-tabs.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/dropdown.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/bulk-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/media-grid.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/pagination.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/empty-state.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/status-pill.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/badge.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/meter.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/stat.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: ar/person.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ar/actor.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/callout.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ar/detail-list.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/notice.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/form-error.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/job-status.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ar/field.html at 320px: 7 frames settled; the page requested 7 frame documents
    heights_browser_test.go:553: ar/field-text.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ar/field-url.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ar/field-money.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ar/field-textarea.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ar/field-select.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ar/field-check.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ar/choice-field.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/form-foot.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ar/confirm-form.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/field-date.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ar/field-time.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ar/field-datetime.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ar/field-daterange.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/error-page.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: ar/back-nav.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/locale-menu.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: ar/locale-menu.html at 320px locale-menu/twelve-languages-current-highlighted: 525px of frame for 76px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: ar/scheme-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ar/api-tokens.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:571: ar/api-tokens.html at 320px api-tokens/four-tokens-two-of-them-live: its document needs 1854px and its frame is 1850px; raise its height (previewHeights for Desktop, previewMobileHeights or previewMobileLocaleHeights for Mobile) to at least 1874
    heights_browser_test.go:580: ar/api-tokens.html at 320px with its menu (body) open api-tokens/four-tokens-two-of-them-live: its document needs 1854px and its frame is 1850px; raise its height (previewHeights for Desktop, previewMobileHeights or previewMobileLocaleHeights for Mobile) to at least 1874
    heights_browser_test.go:553: ar/box.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/button.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/form-layout.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/help.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/list-grid.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/modal.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: ar/modal.html at 320px modal/default: 775px of frame for 155px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: ar/selbox.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/shell-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/shell-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/stat-band.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/tblock.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-column.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-sidebar-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-console.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-console-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/layout-stage.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-dates.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-durations.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-numbers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-money.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-ratios.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-filesizes.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-identifiers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-people.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-addresses.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-abbr.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/format-output.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ar/signin.html at 320px: 12 frames settled; the page requested 12 frame documents
    heights_browser_test.go:553: ru/index.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/page-header.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/list-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/list-bar-search.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/list-search-submit.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/list-row-action.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/row-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/seg-tabs.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/dropdown.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/bulk-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/media-grid.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/pagination.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/empty-state.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/status-pill.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/badge.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/meter.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/stat.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: ru/person.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/actor.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/callout.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ru/detail-list.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/notice.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/form-error.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/job-status.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/field.html at 320px: 7 frames settled; the page requested 7 frame documents
    heights_browser_test.go:553: ru/field-text.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ru/field-url.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/field-money.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/field-textarea.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ru/field-select.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ru/field-check.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/choice-field.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/form-foot.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ru/confirm-form.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/field-date.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ru/field-time.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ru/field-datetime.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ru/field-daterange.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/error-page.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: ru/back-nav.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/locale-menu.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: ru/locale-menu.html at 320px locale-menu/twelve-languages-current-highlighted: 525px of frame for 76px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: ru/scheme-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ru/api-tokens.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ru/box.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/button.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/form-layout.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/help.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/list-grid.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/modal.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: ru/modal.html at 320px modal/default: 775px of frame for 155px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: ru/selbox.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/shell-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/shell-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/stat-band.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/tblock.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-column.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-sidebar-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-console.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-console-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/layout-stage.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-dates.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-durations.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-numbers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-money.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-ratios.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-filesizes.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-identifiers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-people.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-addresses.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-abbr.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/format-output.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ru/signin.html at 320px: 12 frames settled; the page requested 12 frame documents
    heights_browser_test.go:553: ga/index.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/page-header.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/list-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/list-bar-search.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/list-search-submit.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/list-row-action.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/row-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/seg-tabs.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/dropdown.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/bulk-bar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/media-grid.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/pagination.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/empty-state.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/status-pill.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/badge.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/meter.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/stat.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: ga/person.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/actor.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/callout.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ga/detail-list.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/notice.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/form-error.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/job-status.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/field.html at 320px: 7 frames settled; the page requested 7 frame documents
    heights_browser_test.go:553: ga/field-text.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ga/field-url.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/field-money.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/field-textarea.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ga/field-select.html at 320px: 5 frames settled; the page requested 5 frame documents
    heights_browser_test.go:553: ga/field-check.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/choice-field.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/form-foot.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ga/confirm-form.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/field-date.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ga/field-time.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ga/field-datetime.html at 320px: 4 frames settled; the page requested 4 frame documents
    heights_browser_test.go:553: ga/field-daterange.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/error-page.html at 320px: 6 frames settled; the page requested 6 frame documents
    heights_browser_test.go:553: ga/back-nav.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/locale-menu.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: ga/locale-menu.html at 320px locale-menu/twelve-languages-current-highlighted: 525px of frame for 76px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: ga/scheme-menu.html at 320px: 2 frames settled; the page requested 2 frame documents
    heights_browser_test.go:553: ga/api-tokens.html at 320px: 3 frames settled; the page requested 3 frame documents
    heights_browser_test.go:553: ga/box.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/button.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/form-layout.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/help.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/list-grid.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/modal.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:571: ga/modal.html at 320px modal/default: 775px of frame for 155px of document; deliberate headroom, or a number to bring down
    heights_browser_test.go:553: ga/selbox.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/shell-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/shell-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/stat-band.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/tblock.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-column.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-topbar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-sidebar.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-sidebar-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-console.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-console-page.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/layout-stage.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-dates.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-durations.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-numbers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-money.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-ratios.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-filesizes.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-identifiers.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-people.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-addresses.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-abbr.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/format-output.html at 320px: 1 frames settled; the page requested 1 frame documents
    heights_browser_test.go:553: ga/signin.html at 320px: 12 frames settled; the page requested 12 frame documents
    heights_browser_test.go:602: 612 frames measured and 116 opened at 320px over 4 locale(s), 16 sidebar shells given grace; 2 short (enforced: true)
    heights_browser_test.go:604: Mobile entries to raise, as locale key: height:
        	ar "api-tokens/four-tokens-two-of-them-live": 1880,
FAIL
FAIL	amadan.net/rastrillo/fiesta/internal/gallery	309.490s
FAIL
make[1]: *** [Makefile:190: browser.internal.gallery~1of4] Error 1
make: *** [Makefile:64: suite] Error 2

amadan: step suite shard 8 of 15 failed: exit status 2
--- shard 9 of 15 ---
==> shard 9 of 15: make browser.internal.gallery~2of4
./hack/gotest.sh browser 'internal.gallery~2of4'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 4 of ./internal/gallery/ runs 53 of 163 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	204.478s
--- shard 10 of 15 ---
==> shard 10 of 15: make browser.internal.gallery~3of4
./hack/gotest.sh browser 'internal.gallery~3of4'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 3 of 4 of ./internal/gallery/ runs 55 of 163 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	173.414s
--- shard 11 of 15 ---
==> shard 11 of 15: make browser.internal.gallery~4of4
./hack/gotest.sh browser 'internal.gallery~4of4'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 4 of 4 of ./internal/gallery/ runs 54 of 163 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	216.605s
--- shard 12 of 15 ---
==> shard 12 of 15: make browser.internal.gallery.history
./hack/gotest.sh browser 'internal.gallery.history'
gotest: on ip-172-31-19-105, driving /opt/amadan-browser/chromium-1234/chrome-linux/chrome
ok  	amadan.net/rastrillo/fiesta/internal/gallery/history	30.557s
--- shard 13 of 15 ---
==> shard 13 of 15: make browser.internal.www~1of2
./hack/gotest.sh browser 'internal.www~1of2'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 1 of 2 of ./internal/www/ runs 17 of 34 tests
ok  	amadan.net/rastrillo/fiesta/internal/www	114.080s
--- shard 14 of 15 ---
==> shard 14 of 15: make browser.internal.www~2of2
./hack/gotest.sh browser 'internal.www~2of2'
gotest: on ip-172-31-19-105, driving the harness's own choice (/usr/local/bin/chromium)
gotest: slice 2 of 2 of ./internal/www/ runs 17 of 34 tests
ok  	amadan.net/rastrillo/fiesta/internal/www	104.028s

amadan: job failed at step suite shard 8 of 15 — its output is above, not at the tail of this log
diff --git a/internal/gallery/i18n.go b/internal/gallery/i18n.go
index 7789940..be539dc 100644
--- a/internal/gallery/i18n.go
+++ b/internal/gallery/i18n.go
@@ -205,8 +205,12 @@ func leakExempt(en string) bool {
// leakUnchanged are the English strings a translation keeps exactly as
// they are, each with the locales that keep it (nil is every locale):
// the brand, the name of the HTML tab, and the title pattern, whose
-// only words are the brand's; and "Tokens", which Spanish and
-// Portuguese write as English does. Anything else a translated page
+// only words are the brand's; "Tokens", which Spanish and Portuguese
+// write as English does; and the sample words a language writes as
+// English does, carried from Rastrillo's tables: the cities of the
+// select samples where the language keeps the English name, and the
+// product words (Beta, Pro, Plan, Editor, Email, Menu, Slug, Token,
+// No) where it borrows or shares them. Anything else a translated page
// says in English is a leak, however short. Adding to this list is a
// claim that the translation is right as it stands.
var leakUnchanged = map[string][]string{
@@ -214,6 +218,37 @@ var leakUnchanged = map[string][]string{
"HTML": nil,
"{page} · Fiesta · {theme}": nil,
"Tokens": {"es", "pt-PT"},
+
+ "Accra": {"ga", "vi"},
+ "Asia": {"es"},
+ "Athens": {"vi"},
+ "Austin": {"es", "ga", "pt-PT", "vi"},
+ "Berlin": {"vi"},
+ "Dhaka": {"vi"},
+ "Dublin": {"pt-PT", "vi"},
+ "Hanoi": {"ga"},
+ "Helsinki": {"es", "vi"},
+ "Lima": {"es", "pt-PT", "vi"},
+ "Lisbon": {"vi"},
+ "Madrid": {"es", "pt-PT", "vi"},
+ "Montréal": {"ga", "vi"},
+ "Nairobi": {"es", "ga", "vi"},
+ "Osaka": {"es", "pt-PT", "vi"},
+ "Reykjavík": {"vi"},
+ "Tallinn": {"pt-PT", "vi"},
+ "Tunis": {"vi"},
+ "Valletta": {"vi"},
+ "Zagreb": {"es", "pt-PT", "vi"},
+
+ "Beta": {"es", "pt-PT"},
+ "Editor": {"es", "pt-PT"},
+ "Email": {"vi"},
+ "Menu": {"pt-PT"},
+ "No": {"es"},
+ "Plan": {"es"},
+ "Pro": {"es", "ga", "pt-PT"},
+ "Slug": {"es", "pt-PT"},
+ "Token": {"es", "pt-PT"},
}
// unchangedIn says whether locale keeps en as it is, by leakUnchanged.
@@ -225,6 +260,23 @@ func unchangedIn(locale, en string) bool {
return locales == nil || slices.Contains(locales, locale)
}
+// leakCollisions are the words a language uses for one string that
+// English spells like another string the gallery says, each with the
+// locales where that happens: Vietnamese names the Tokens page "Token",
+// which is also the English of a sample's column heading, translated
+// there. On a page in that locale the word is the language's own, not
+// English. Adding to this list is a claim about a word of that
+// language, and the leak gate checks the word is a translation there.
+var leakCollisions = map[string][]string{
+ "Token": {"vi"},
+}
+
+// collidesIn says whether run, on a page in locale, is that locale's
+// own word by leakCollisions.
+func collidesIn(locale, run string) bool {
+ return slices.Contains(leakCollisions[run], locale)
+}
+
// templateSources is every template the pages are drawn from, each
// registered by the file that owns it at init, so a page kind in a
// file of its own brings its body template with it and nothing here
diff --git a/internal/gallery/i18n_test.go b/internal/gallery/i18n_test.go
index 1d21ee1..1ddb04d 100644
--- a/internal/gallery/i18n_test.go
+++ b/internal/gallery/i18n_test.go
@@ -274,14 +274,6 @@ type leakSweep struct {
sentences []string
whole map[string]bool
patterns []leakPattern
- // same is, per locale, the English that locale says the same way
- // by the word of a carried translation (sameIn).
- same map[string]map[string]bool
- // own is, per locale, every translation that locale's catalogs
- // give a traced id, whole: a run that is one of them is the
- // locale's own word, even where English spells another word alike
- // (Vietnamese names the Tokens page "Token").
- own map[string]map[string]bool
}
// leakPattern is one string with placeholders: its English, the longest
@@ -324,68 +316,13 @@ func newLeakSweep(ids map[string]bool, english map[string]string) leakSweep {
return w
}
-// sameIn is, per locale, every traced English string that every id
-// saying it translates as the English itself, each of those
-// translations carried from Rastrillo rather than drafted: "Madrid" in
-// Spanish, the cities and product words Rastrillo's approved tables
-// keep as they are. Rastrillo's sweep never met these,
-// because it matched sentences only; this one matches every word whole,
-// so it takes Rastrillo's tables at their word. A draft that equals its
-// English is no such claim, and leakUnchanged has to name it.
-func sameIn(t *testing.T, ids map[string]bool, english map[string]string) map[string]map[string]bool {
- t.Helper()
- b, err := fs.ReadFile(catalogFiles, "i18n/drafts.json")
- if err != nil {
- t.Fatal(err)
- }
- var drafts map[string]struct {
- Locales []string `json:"locales"`
- }
- if err := json.Unmarshal(b, &drafts); err != nil {
- t.Fatal(err)
- }
- out := map[string]map[string]bool{}
- for _, l := range nonEnglish() {
- differs := map[string]bool{}
- said := map[string]bool{}
- for id := range ids {
- en := squash(english[id])
- said[en] = true
- if squash(translationOf(t, l, id)) != en || slices.Contains(drafts[id].Locales, l) {
- differs[en] = true
- }
- }
- out[l] = map[string]bool{}
- for en := range said {
- if !differs[en] {
- out[l][en] = true
- }
- }
- }
- return out
-}
-
-// ownIn is, per locale, the translation of every traced id, whitespace
-// collapsed.
-func ownIn(t *testing.T, ids map[string]bool) map[string]map[string]bool {
- t.Helper()
- out := map[string]map[string]bool{}
- for _, l := range nonEnglish() {
- out[l] = map[string]bool{}
- for id := range ids {
- out[l][squash(translationOf(t, l, id))] = true
- }
- }
- return out
-}
-
// leaks is the English a page in locale carries, given its runs, bar
// what leakUnchanged says the locale keeps.
func (w leakSweep) leaks(locale string, runs []string) []string {
var out []string
seen := map[string]bool{}
add := func(en string) {
- if !seen[en] && !unchangedIn(locale, en) && !w.same[locale][en] {
+ if !seen[en] && !unchangedIn(locale, en) {
seen[en] = true
out = append(out, en)
}
@@ -397,7 +334,7 @@ func (w leakSweep) leaks(locale string, runs []string) []string {
}
}
for _, r := range runs {
- if w.whole[r] && !w.own[locale][r] {
+ if w.whole[r] && !collidesIn(locale, r) {
add(r)
}
for _, p := range w.patterns {
@@ -421,15 +358,22 @@ var leakSpoken = map[string]bool{
}
// leakSkipped says whether the sweep leaves an element and everything
-// in it alone: script and style, which say nothing, and whatever the
-// sample walker keeps as written (keptElements and keptAttrs: code, a
-// drawing, a record's own words in <bdi>, a shell's brand and person,
-// and anything that says its own language), which may well be words a
-// sample elsewhere translates. The scheme menu and the HTML tab's
-// source are swept like the rest, since the samples are in the page's
-// language.
+// in it alone: script and style, which say nothing. The scheme menu
+// and the HTML tab's source are swept like the rest, since the samples
+// are in the page's language.
func leakSkipped(n *xhtml.Node) bool {
- if n.Data == "script" || n.Data == "style" || keptElements[n.Data] {
+ return n.Data == "script" || n.Data == "style"
+}
+
+// leakKept says whether the sweep reads an element's spoken attributes
+// and leaves its content alone: whatever the sample walker keeps as
+// written (keptElements and keptAttrs: code, a drawing, a record's own
+// words in <bdi>, a shell's brand and person, and anything that says
+// its own language), which may well be words a sample elsewhere
+// translates. The walker still translates the element's own spoken
+// attributes, as it does every opening tag's, so the sweep reads them.
+func leakKept(n *xhtml.Node) bool {
+ if keptElements[n.Data] {
return true
}
for _, a := range n.Attr {
@@ -474,8 +418,9 @@ func textContent(n *xhtml.Node) string {
// pageRuns is every run of text on a page and every spoken attribute
// value, as a reader gets them (entities decoded, whitespace
-// collapsed), outside what leakSkipped leaves alone, empty runs left
-// out; an HTML tab's source is read as the markup it shows.
+// collapsed), outside what leakSkipped leaves alone and the content
+// of what leakKept keeps, empty runs left out; an HTML tab's source is
+// read as the markup it shows.
func pageRuns(t *testing.T, doc []byte) []string {
t.Helper()
root, err := xhtml.Parse(strings.NewReader(string(doc)))
@@ -508,6 +453,9 @@ func pageRuns(t *testing.T, doc []byte) []string {
keep(a.Val)
}
}
+ if leakKept(n) {
+ return
+ }
case xhtml.TextNode:
keep(n.Data)
}
@@ -586,9 +534,7 @@ func TestNoEnglishProseReachesATranslatedPage(t *testing.T) {
files := render(t, "")
s := newTestSite(t, fiesta.FS(), "")
english := englishStrings(t)
- sweep := newLeakSweep(tracedIDs(t), english)
- sweep.same = sameIn(t, tracedIDs(t), english)
- sweep.own = ownIn(t, tracedIDs(t))
+ sweep := leakSweepFor(t, english)
if len(sweep.sentences) < 100 || len(sweep.whole) < 200 || len(sweep.patterns) < 5 {
t.Fatalf("sweeping for %d sentences, %d whole strings and %d patterns; the sweep has stopped reading the traced strings", len(sweep.sentences), len(sweep.whole), len(sweep.patterns))
}
@@ -623,6 +569,29 @@ func TestNoEnglishProseReachesATranslatedPage(t *testing.T) {
t.Errorf("leakUnchanged keeps %q, which no render says", en)
}
}
+ // Each word leakCollisions excuses is the English of a string a
+ // render says, and in each locale it names, the translation of
+ // another: the language's own word, spelt as English spells it.
+ for word, locales := range leakCollisions {
+ said := false
+ for id := range tracedIDs(t) {
+ said = said || squash(english[id]) == word
+ }
+ if !said {
+ t.Errorf("leakCollisions excuses %q, which is the English of nothing a render says", word)
+ }
+ for _, l := range locales {
+ own := false
+ for id := range tracedIDs(t) {
+ if squash(translationOf(t, l, id)) == word && squash(english[id]) != word {
+ own = true
+ }
+ }
+ if !own {
+ t.Errorf("leakCollisions excuses %q in %s, where it translates nothing; it is English there", word, l)
+ }
+ }
+ }
docs := translatedDocuments(s, files)
perDir := len(s.pageKinds()) + len(s.m.Examples) + len(s.extraDocumentNames())
for _, e := range s.m.Examples {
@@ -640,41 +609,83 @@ func TestNoEnglishProseReachesATranslatedPage(t *testing.T) {
// The controls: three strings put back to their English in the ja
// catalog, a sentence and two short words of the chrome (the Copy
// live region's word and the Overview's name), each of which the
- // sweep must report on the Japanese Overview; and a sample's word
- // put back in the ja sample catalog, which it must report in the
- // sidebar shell's frame.
+ // sweep must report on the Japanese Overview; and sample strings put
+ // back in their sample catalogs, each of which it must report on the
+ // document that shows it: a word of the sidebar shell, a short
+ // drafted link of the demo, and a carried sentence of a field's
+ // error. The sweep is built again with the catalogs changed, so no
+ // exemption it draws can excuse what a control put back.
cs := loadedCatalogs(t)
samples := loadedSamples(t)
controls := []string{"gallery.overview.intro", "gallery.copy", "gallery.kind.overview"}
- const sampleControl = "Sign out"
+ sampleControls := []struct{ locale, key, doc string }{
+ {"ja", "Sign out", "day/ja/frames/layout-sidebar/" + mustEntry(t, "layout-sidebar").Default + ".html"},
+ {"ja", "← Requests", "day/ja/demo-request.html"},
+ {"es", "A summary needs at least twenty characters.", "day/es/frames/field-textarea/with-an-error.html"},
+ }
orig := map[string]string{}
for _, id := range controls {
orig[id] = cs["ja"][id]
cs["ja"][id] = text[id]
}
- origSample := samples["ja"][sampleControl]
- samples["ja"][sampleControl] = sampleControl
+ origSamples := make([]string, len(sampleControls))
+ for i, c := range sampleControls {
+ origSamples[i] = samples[c.locale][c.key]
+ samples[c.locale][c.key] = c.key
+ }
scratch, err := Render(fiesta.FS(), Options{})
+ controlSweep := leakSweepFor(t, english)
for _, id := range controls {
cs["ja"][id] = orig[id]
}
- samples["ja"][sampleControl] = origSample
+ for i, c := range sampleControls {
+ samples[c.locale][c.key] = origSamples[i]
+ }
if err != nil {
t.Fatal(err)
}
- if origSample == "" || origSample == sampleControl {
- t.Errorf("the ja sample catalog says %q for %q; the sample control proves nothing", origSample, sampleControl)
- }
- found := sweep.leaks("ja", pageRuns(t, scratch["day/ja/index.html"]))
+ found := controlSweep.leaks("ja", pageRuns(t, scratch["day/ja/index.html"]))
for _, id := range controls {
if !slices.Contains(found, squash(text[id])) {
t.Errorf("with %s put back to its English in the ja catalog, the sweep does not report %q on day/ja/index.html; it reports %q", id, text[id], found)
}
}
- frame := "day/ja/frames/layout-sidebar/" + mustEntry(t, "layout-sidebar").Default + ".html"
- if found := sweep.leaks("ja", pageRuns(t, scratch[frame])); !slices.Contains(found, sampleControl) {
- t.Errorf("with %q put back to its English in the ja sample catalog, the sweep does not report it on %s; it reports %q", sampleControl, frame, found)
+ for i, c := range sampleControls {
+ if origSamples[i] == "" || origSamples[i] == c.key {
+ t.Errorf("the %s sample catalog says %q for %q; the control proves nothing", c.locale, origSamples[i], c.key)
+ }
+ if !strings.Contains(string(scratch[c.doc]), c.key) {
+ t.Errorf("%s does not show %q once it is put back; the control is on the wrong document", c.doc, c.key)
+ }
+ if found := controlSweep.leaks(c.locale, pageRuns(t, scratch[c.doc])); !slices.Contains(found, c.key) {
+ t.Errorf("with %q put back to its English in the %s sample catalog, the sweep does not report it on %s; it reports %q", c.key, c.locale, c.doc, found)
+ }
}
+
+ // A kept element's own spoken attributes are the walker's to
+ // translate, as it translates every opening tag's, so the sweep
+ // reads them while it leaves the element's content alone.
+ runs := pageRuns(t, []byte(`<p><bdi aria-label="Help: orders">Grace Hopper</bdi> <a rst-shell-brand href="#" title="Back to the start">Harbour</a></p>`))
+ for _, want := range []string{"Help: orders", "Back to the start"} {
+ if !slices.Contains(runs, want) {
+ t.Errorf("the sweep does not read the spoken attribute %q of a kept element; it reads %q", want, runs)
+ }
+ }
+ for _, kept := range []string{"Grace Hopper", "Harbour"} {
+ if slices.Contains(runs, kept) {
+ t.Errorf("the sweep reads %q, the content of a kept element", kept)
+ }
+ }
+}
+
+// leakSweepFor is the sweep over a full render's traced strings. Its
+// exemptions are leakExempt, leakUnchanged and leakCollisions, lists
+// written down, so nothing a catalog says can widen them; the controls
+// build it again after changing the catalogs all the same, so that
+// stays true if an exemption is ever drawn from them.
+func leakSweepFor(t *testing.T, english map[string]string) leakSweep {
+ t.Helper()
+ return newLeakSweep(tracedIDs(t), english)
}
func asRendered(s string) string {