Maintainer repair for the #167 re-review of 25d9375f.
Blocking: "Load older messages" prepended the older page to a newest-first
list that MessagePreview reverses, so it showed under the newest message.
It is appended now.
Live media:
- LiveMedia probes each candidate key with a one-byte read and returns
ErrLiveMediaTooLarge for an object over 16 MiB before reading it. The
admin API maps it to 413 and the panel answers 413 with a plain-text
reason and the same nosniff / CSP sandbox headers.
- Responses are Cache-Control: no-store again, at both layers.
- parseMedia reads the top-level kind and the id of the right object, so
documents get their download link. Number literals are quoted before
JSON.parse, so 64-bit ids never become JS numbers.
- Tests: LiveMedia over the real files service on LocalFS (sniffing,
allowlist, key checks, the cap and the probe), /v1/media and panel proxy
headers, a messages.read gate test for /api/live/*, and a DSN-gated
dialog paging test with tied and zero dates.
Messenger:
- The dialog cursor is keyed on before_peer_id, since top_message_date is
0 for dialogs created by a scheduled message, a TTL or a chat theme.
- The dialog list has its own stale-response guard for owner switches and
"Load more chats".
- Bubbles show the box id that "Delete selected" takes.
- Detail pages preview the 50 messages ending at the opened one and keep
it in view.
- ?focus=env jumps once, so Status can be opened afterwards.
- The shell title and the lit tab use the longest matching tab path, so
group pages say Groups and detail routes light their tab; /premium is
an alias of the Premium tab.
- The remaining hardcoded strings go through i18n (en, zh, ru).
Docs: /api/live/* rows document the cursor, has_more, media types, status
codes, headers and messages.read; "Adding a page" says the shell renders
the tab bar, and the generator now fails on a <SectionTabs> in a page.
AGENTS.md, .env.example and comments use the current section names.
web/dist is rebuilt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Translate all natural-language comments, log/diagnostic messages, SQL comments and config/doc prose from Chinese (and Russian) to English across the codebase (~840 files). Rebased on upstream main; upstream's newly added files are translated too.
Locale resources and functional non-English data are left intact: language packs, admin/grammystore i18n bundles, localized docs, the officialBrandRE regex, seeded Russian premium-bot text, user-facing Russian bot strings and asserted Unicode test fixtures. A new internal/translation guard test fails if CJK/Cyrillic reappears in source outside that allowlist.
No code, identifiers, struct tags or behavior changed (verified with an AST-level comment/string audit). go build ./..., go vet ./... and the guard pass.
Sync private telesrv 8f8d15b6. start-docker always ran
`docker compose pull` unless --build was given, so restarting a stack
built locally under a custom TELESRV_IMAGE_TAG failed with "manifest
unknown" or was silently replaced by an older published image.
--no-pull (-NoPull in PowerShell) skips the pull and starts with
`up --pull never`, so a missing image fails fast instead of being
fetched. With --build it builds without refreshing base images. Both
scripts keep their public "gramsrv" ready message.
Refs #136
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Giveaway was a tab of its own holding one field and one button, which is
not a job a tab bar should carry. The button now sits at the bottom of the
Stars & Premium page, next to the per-user grants it belongs with, inside
a frame with a warned border: it is the only write on the page that
touches every account at once, and it keeps the same reason -> dry run ->
confirm flow as every other write.
Commerce drops to three tabs -- Stars & Premium, Prices, Stars balances --
and the last one is renamed, because "Stars" and "Stars & Premium" in the
same tab bar read as the same thing. The old /star-issue URL is an alias
of the Stars & Premium tab, so a bookmark opens the same place and the
sidebar row stays lit.
The doc generator now prints the alias paths instead of "the bare alias",
which was written for /messages and said nothing about /star-issue.
Seven pages rendered their section sub-menu into PageFrame's `actions` prop
instead of among its children, and actions goes into the header's
right-aligned action row. Three of them -- Storage, Stars and Account
ratings -- nested the tab bar inside the Refresh <button>, which is not
valid HTML and looked worse still. So the sub-menu selector appeared
alongside the page's buttons rather than under its title, on all four
Accounts tabs and both Stars pages. Moving the seven to be PageFrame's
first child is the same one-line position the other seventeen already use.
Nothing caught it because the component renders either way, so the check
that already exists for the generated doc tables now also asserts that no
<SectionTabs> sits inside an actions prop, and names the file and line.
The pinned-shell layout is the other half. .content is a grid, and inside
the viewport-height media query it gets a definite height from flex, so
its single implicit row absorbed all the leftover space and every page --
each one a single .page-frame -- was pulled to the full viewport with its
own rows smeared out to match. align-content: start puts that back. The
overview is unaffected: it already opts in with an explicit 1fr row.
In the overview's top band, the three summary panels are equal thirds, and
auto-fit resolves that width to three columns -- right for the three
health figures, wrong for the other two. The Stars economy rendered its
four figures 3+1 with a hole beside them, and Needs attention took two
tiles across two thirds of its panel while the shared stretch rule
inflated both to the full band height. Each panel now pins the count its
own figures need: 2x2 for the economy, two centred columns for the
queues, hugging their content instead of stretching.
People and chats was the worst of them. Nine counters in the tall band's
left cell, auto-fit found five columns and rendered 5+4, so the figures
used two rows of the four the panel had -- half the panel empty and every
tile a cramped 124px. It is a fixed 3x3 that fills the panel and makes
each tile about twice as wide.
Finally, the server icon in Server Settings > Server identity rendered
through .avatar-photo-img, which is a 50% circle, while the sidebar and
the login screen show the same uploaded file as a 30% squircle. Scoping
the radius to the edit slot makes all three agree and leaves the account
avatars in tables circular.
Co-Authored-By: Claude Fable 5.5 (1M context) <noreply@anthropic.com>
The route and permission tables in docs/admin-panel-api.{en,ru}.md had
drifted a long way out of date and nothing noticed: permissions.tsx
exported a dozen section rights the tables never mentioned, the SPA table
listed routes that no longer existed, and several pages and their rights
were missing entirely. They are generated from nav.ts, permissions.tsx and
security.go now, so editing the source and rerunning is the only way they
move -- and CI runs --check so they cannot rot unnoticed again.
The generator also documents what the panel does now, since the rules it
follows were only ever in the diff: the sidebar, the tab bars and the
route table are three views of one declaration in nav.ts, a sibling page
is a tab rather than a second sidebar row, a right outside
assignablePermissions() cannot be granted to a named operator, and the
//go:embed web/dist bundle has to be rebuilt and committed or a src change
does not ship. AGENTS.md and both API docs carry that, plus a section on
adding a page in the order that avoids repeating this drift.
docs/giftfetch.md is new, and local-setup.md points at it. Official
Star-gifts are only reachable with an authorized user session, which is the
part that actually stops people: the fetch is written up around the QR
login, the snapshot layout, the validation it performs, and the fact that
a .session file is a live account credential and belongs outside the
repository.
One fix to the generator itself. It read nav.ts's tab arrays with a
non-greedy (?P<tabs>.*?)\]\s*\}, which stops at the first ] followed by a
} -- the closing bracket of a tab's own aliases: [...] -- so the messages
section parsed to zero tabs and dropped out of the table with no error
where all the others were fine. The tabs array is now read by bracket
matching, and a section that parses to zero tabs raises instead of
vanishing, because a silently missing section is exactly the failure this
commit exists to stop.
Operators had no supported way to move a deployment or to recover one. This
adds cmd/gramsrv-backup, which captures the PostgreSQL databases, the media and
key tree, Redis and the configuration into a single verifiable bundle, and
restores it on another host.
backup / restore / verify, with MANIFEST.json recording the source commit, the
schema version and exact row counts, and SHA256SUMS in sha256sum(1) format.
Components are captured independently (server, grammystore) since each has its
own database and usually its own container; pg_dump and redis-cli can come from
the host or from docker exec.
Portability is the point of the dump format:
- plain SQL rather than the custom format, so any psql can replay it and the
client need not match the server
- --clean --if-exists so a bundle can be replayed over a populated database
- --no-owner --no-privileges, and no --create, so the target role and database
name are not baked into the artifact
- restored with ON_ERROR_STOP=1 and --single-transaction, so a failure cannot
leave a half-applied schema
- \restrict, \unrestrict and SET transaction_timeout are stripped on the way
in; they are PostgreSQL 17 specific and break older clients and servers
- the target major version is checked before the restore starts
Freezing (--freeze-units, --freeze-containers) is optional because pg_dump is
already MVCC-consistent; it exists so the media tree is byte consistent, and
everything stopped is restarted from a defer even when the capture fails.
Verification re-hashes every artifact, decompresses it, walks tar members, and
cross-checks file_blobs against the media tree, reporting missing and corrupt
objects. check-media.sh runs the live form of that check.
Also included: shell and PowerShell wrappers, a migration runbook in
docs/backup-restore.md, and internal/doclint, which fails the build when tracked
markdown leaks a key, a token or a credential so documentation stays safe to
publish.