Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 15:35
· 28 commits to main since this release
9bfa542

Added

  • Sign-in provider adapters for Google (generic OpenID Connect) and GitHub (OAuth 2.0), not wired to any endpoint yet (D-023). Authorization Code with PKCE (S256); the ID token is verified with coreos/go-oidc (signature, algorithm allow-list, issuer, audience, expiry, authorized party) and our code checks the nonce; GitHub uses the primary address and only when GitHub marks it verified. New settings GOOGLE_CLIENT_ID/SECRET and GITHUB_CLIENT_ID/SECRET (passed through both compose files): a provider is off while its pair is empty, and half a pair stops the start. New dependencies: coreos/go-oidc/v3 and its go-jose/v4 (Apache-2.0) and golang.org/x/oauth2 (BSD-3-Clause).
  • Groundwork for social sign-in (D-023; no endpoint yet): migration 00008 adds user_identities and auth_oauth_flows and makes users.password_hash nullable, with Postgres repositories and the pure linking function domain/identity.Decide (never auto-links an unverified local email). A user without a password cannot sign in with one, the login answer and its timing match a wrong password, and changing a password is refused with a pointer to setting one.
  • Russian and Kazakh catalogs for the dashboard and the demo (frontend/messages/ru.json, kk.json, 687 keys each, ICU plurals per CLDR). Machine drafts for native review: neither locale is enabled yet (separate change), docs/copy/review/{ru,kk}.md list the terms and strings to check. The post-list row wraps its retry button and fits longer Edit labels at 320 px.
  • App translations for es, pt-BR, de, fr and id (D-021): complete catalogs in frontend/messages/<locale>.json (676 keys each, CLDR plural categories, glossary terms), machine drafts with the choices and open questions in docs/copy/review/<locale>.md (section "App"). The locales are not enabled yet, so the language switcher is unchanged.
  • Japanese (ja) and Simplified Chinese (zh-CN) catalogs for the dashboard and the demo: all 676 keys in frontend/messages/ja.json and zh-CN.json, machine drafts that follow docs/copy/glossary.csv and the landing translations (CJK punctuation, other-only plurals, no "Claude" claim in zh-CN copy). Neither locale is enabled yet (frontend/src/i18n/locales.ts is unchanged), so users still see English until a separate change enables them. docs/copy/review/ja.md and zh-CN.md have an "App" section with the terminology and the strings that need a native check. The audit-log filter buttons wrap on narrow screens.
  • Landing page in 9 languages (D-021): ru, es, pt-BR, de, fr, id, ja, zh-CN and ar, besides English, at /{locale}/ (English stays at /, /en/ forwards to it). site/build.mjs renders the page from site/i18n/landing.<locale>.json (ICU plurals and rich tags through intl-messageformat), with <html lang dir>, localized title, description and Open Graph tags, hreflang for every locale plus x-default, and the locales in the sitemap. Docs and the demo stay English and are linked with "(EN)". Every translation is a machine draft labelled "Beta translation" in the footer; docs/copy/review/<locale>.md lists what needs a native look. kk is built hidden (noindex, not linked) until a native reviewer signs it.
  • A compact language menu in the nav and a plain link list in the footer, both with endonyms, no flags, one line at 320 px. A first-visit suggestion on the English page follows navigator.languages (same matching as the app, uk and zh-Hant get English); it is a small dismissible card, never a redirect, and the choice is remembered in localStorage (steerpost_locale, shared with the demo).
  • Right-to-left layout for ar through logical CSS properties; CJK and Arabic use system fonts with phrase-aware line breaking and no negative tracking; Cyrillic uses Inter's own Cyrillic subsets (fetched only by pages that need them). "Pause motion" and reduced motion behave the same in every language.
  • npm run check verifies a page, a complete catalog and complete hreflang for every locale; npm run smoke loads each locale at 320, 390 and 1440 px and asserts no horizontal overflow, a one-line nav, the motion switch and the suggestion.
  • Internationalization infrastructure (D-021; no translated copy yet). A client-side locale provider that works the same in the server build and the static demo (no middleware, no locale routes): the locale is resolved as user setting (later users.locale) → localStorage (steerpost_locale) → navigator.languages → English, <html lang> and dir follow it (right-to-left only for ar), and uk, zh-TW, zh-HK and zh-Hant browsers get English. Message catalogs live in frontend/messages/<locale>.json with en as the source and empty files for the target locales; a missing key falls back to English. ICU MessageFormat comes from a small in-house runtime (src/i18n/icu.ts, plural, select, ordinal, rich tags) with typed keys; it is checked against FormatJS in tests. next-intl 4.14 supports Next 15 but added about 14 kB gzipped to every route (measured: first-load JS of / 114.8 kB → 128.7 kB), so it was not used; the runtime adds about 2 kB (114.8 kB → 116.9 kB, the Next build table stays at 103 kB shared).
  • Language switcher in Settings → Preferences and a compact one in the sidebar. It lists only enabled locales by their own name and marks machine-drafted ones "Beta translation". Only English is enabled so far; the pseudo-locale en-XA (accented, about 35 % longer) is listed in dev builds and the demo, to find hard-coded strings and overflow.
  • Intl formatting helpers for numbers, dates, times and relative times that honour the locale and the Settings timezone (src/i18n/format.ts, useFormat()). Arabic keeps Latin digits and the Gregorian calendar.
  • npm run i18n:check (part of npm run lint): every catalog key must exist in English, every message must be valid ICU with the same placeholders and tags, enabled locales must be complete with all plural categories, code may only use keys English defines; unused keys and missing meta.json descriptions are warnings.
  • Every user-visible string of the dashboard and the demo now comes from the English catalog (frontend/messages/en.json, 674 keys in 17 namespaces (nav, common, errors, shell, auth, dashboard, posts, composer, calendar, accounts, approvals, settings, developer, media, analytics, legal and language). The English text is unchanged. Counts and states are ICU plural and select messages (no more string concatenation or hand-made "s"), dates, numbers, sizes and relative times follow the locale and the Settings time zone, and every key has a note for translators in messages/meta.json, including the ambiguous ones (Draft, Schedule, Expired, Never, Cancel vs Cancel post vs Keep post, Retry vs Try again, Connect, Requested by, Approved: action, "Post this code there", "{n} min left" and others).
  • npm run i18n:literals (part of npm run lint, and tests/i18n-literals.test.ts) fails on hard-coded English in JSX text, in aria-label, title, placeholder, alt and the copy props of components, and in string literals that read like UI sentences. The allow-list (frontend/scripts/i18n-literals.allow.json) names a reason for every exception: the Terms and Privacy text, static metadata and the web manifest, the demo seed and engine, API fallback messages. tests/i18n-extract.test.tsx renders screens in en-XA and fails if plain English is left on screen.
  • <T k="..." /> for server components, useErrorText() and nodes() (rich messages with links) in src/i18n; lib/* functions take the translator as a parameter and stay pure.
  • The sidebar navigation labels are the first strings read from the catalog.
  • Reconnect button on accounts that need reconnecting (OAuth and token networks).
  • Glossary: approvals, deny, trusted key, main text, operator, open-source and the approval statuses; docs/copy/style-guide.md section 7 describes server-side approval.
  • App: a "Skip to content" link as the first tab stop and a focusable main landmark; failed posts on the dashboard get a Retry button; the calendar "+N more" and the mobile post count open that day.
  • Data export (D-018): Settings, "Your data", builds a ZIP with your profile, posts with targets and attempts, connected accounts (no credentials), API key names, approvals, the audit log and your media files. The worker streams it to storage within its memory cap; the download link works for 5 minutes, the file is deleted after EXPORT_RETENTION_DAYS (default 7). One export at a time, one per 24 hours. New endpoints POST/GET /account/exports and GET /account/exports/{id} (browser session only), migration 00005 (indexes).
  • Account deletion (D-019): Settings, "Your data", "Delete account". It asks for your password and your email typed out, signs out every session and revokes every API key and MCP connection at once, sets scheduled posts back to drafts, and deletes everything after ACCOUNT_DELETION_GRACE_DAYS (default 7). Until then you can sign in, see a banner and cancel. The worker removes posts, media (S3 objects too), connected accounts and their stored credentials, keys, approvals and the audit log in batches; only a record of counts without personal data stays. New endpoints POST /account/delete and POST /account/delete/cancel (browser session only), user.deletion_scheduled_at and deletion_grace_days in /me, an email when deletion is scheduled, migrations 00006 and 00007.
  • 429 RATE_LIMITED answers can carry a specific Retry-After.

Changed

  • The dashboard and the demo are now offered in Russian, Spanish, Brazilian Portuguese, German, French, Indonesian, Japanese and Simplified Chinese (D-021): the eight locales are enabled in frontend/src/i18n/locales.ts and listed by their own names in the language switcher, all labelled "Beta translation" until a native review. Kazakh stays hidden until it is reviewed and Arabic until the right-to-left work lands. The account deletion and data export strings are translated in every catalog (kk included).
  • Narrow-screen layout in translated UI: stacked table cards (API keys, audit log) give the column title room and wrap long values instead of overlapping; the calendar shows only the post count in a day cell below 480 px (the full text stays for screen readers); the post row's Edit and Retry buttons stay right-aligned when the row wraps; Japanese and Chinese use strict line breaking (phrase-aware in Japanese) and the dates in rows do not wrap mid-token (2026年10月10日 09:00); the day-cell count is its own full-width line.
  • Chinese landing and app copy use 审批, 批准, 拒绝 and 开发者 (glossary updated), the Japanese landing uses 承認画面 and 「開発者」ページ, and a few ja and zh-CN app strings were reworded (final 。, 「在几分钟内定时发布」, 「未能设为定时发布」).
  • The Privacy Policy describes self-service export and account deletion, and how long data stays.
  • Landing page v2: about half the words (hero: "AI agents draft posts. You stay in control."), a scroll-driven "how it works" route (the line draws as you scroll, the approval gate locks, posts fan out to the networks), word-by-word headline reveals, clip-path screen reveals, magnetic buttons, tilting screens and a cursor light (mouse only), and a slimmer mobile hero and story with 44 px tap targets and safe-area insets. Pause motion now stops scroll animations and reveals too and exposes aria-pressed. Fixes: the nav logo no longer shrinks at 320 px, the hero flow no longer runs under the text, smooth anchor scrolling.
  • Steerpost is now open-source under the AGPL-3.0 (LICENSE, D-022).
  • App copy follows the copy review (docs/copy): shorter and plainer text, one term per concept, no idioms, translator notes next to the 14 ambiguous strings. No API values changed.
  • Honesty fixes: the MCP panel and the "I understand" checkbox on API keys describe approvals (D-013), not a confirm flag; Publish now and Retry now count as irreversible in Approvals (danger-style Approve); each unavailable network shows its own reason from the capabilities (X, Medium and Hashnode no longer say "Requires platform approval"); the token form promises HTTPS only when the page is served over HTTPS; the media delete warning matches the backend (a file used in a post cannot be deleted); the Terms say API-key dangerous actions need approval unless the key is trusted, and name the AGPL-3.0.
  • Status names: the target status "Needs review" and the attempt status "Unknown outcome" are now "Unconfirmed" in the UI (API values needs_review and unknown are unchanged). "Cancelled" is "Canceled", "Expired" on an account is "Needs reconnecting" (with a Reconnect button), the test network is no longer called "Mock", and the post page says "Accounts" instead of "Targets".
  • Backend messages that reach the UI or agents are plain sentences with a next step (no raw ids, statuses or byte counts). Provider notes drop internal words; unavailable networks say "Not available yet: ".
  • Mail templates: one verb ("Verify"), "server admin" instead of "operator", and the export mail no longer points to a settings page that has no export.
  • MCP tool descriptions and error hints: no duplicate SENSITIVE/CRITICAL prefix, correct reconnect and scope guidance, needs_review explained, agents are told to show the final text, accounts and time before schedule_post.
  • Docs: D-021 (locale set and rollout order, uk waits, zh-CN joins), docs/copy/languages.md and translation-process.md updated, a release is no longer blocked by a locale's review status (machine-drafted locales ship as "Beta translation"), and the copy glossary has zh-CN, ar, fr and id columns.
  • All user-visible app strings now come from the message catalog, so the JavaScript that ships with the app grows: the English catalog (about 10.5 kB gzipped) lands in a shared chunk, so first-load JS on /login goes from 131 to 147 kB, /verify-email 125 to 140, /compose 155 to 168 and /accounts 149 to 162 (the shared baseline stays 103 kB). Splitting the catalog by namespace is a follow-up (D-021).
  • File sizes above 999 KB group thousands in English ("1,024 MB" instead of "1024 MB"), as the number follows the locale.
  • Migrations 00005 to 00008 run automatically (deploy.sh, and so autoupdate, applies them before the app containers are replaced); no manual step. 00005 and 00007 build indexes CONCURRENTLY, so they do not block writes, but they can take a while on a large database. 00008 is additive.
  • New optional settings GOOGLE_CLIENT_ID/SECRET and GITHUB_CLIENT_ID/SECRET. Nothing uses them until the sign-in endpoints ship; leave them empty (set only one half of a pair and the start is refused).
  • Privacy Policy text and legal version 2026-10-10: the version is recorded for accounts created from now on. Existing users are not asked to accept again (re-acceptance is a later decision, D-016), so nothing changes for them on sign-in.

Fixed

  • Worker shutdown: the publish and export servers stop taking tasks together and drain in parallel (the export server waits 5 s), so a stop fits the 45 s stop_grace_period. An export archive upload may now run as long as the build (90 minutes) instead of 30; a finished build can no longer overwrite an export the sweep already failed.
  • Landing hero video: the cursor no longer jerks. The dot used the CSS scale property together with transform: translate, so every click scaled its position towards the page origin and it flew across the screen for a moment. It now uses translate, and the video is rendered frame by frame (virtual time, 60 fps, cursor path computed per frame) instead of screen-recorded at about 25 fps with dropped frames. The video and posters were re-recorded; the poster shows the whole compose form.
  • i18n groundwork before the first real locale: sentences that were assembled from pieces are single ICU messages now (approvals badge "99+" with the real count for screen readers, "+N more on {date}" with a locale-formatted date, capability badges one sentence per state, the locked-post notice picked by status code); an empty network id reads "Unknown" everywhere (providerName, useProviderName) instead of "Text on " or an empty list; the dashboard no longer overflows 320 px in the pseudo-locale; the count-only approvals pill on mobile shows the Approvals icon. Dates in the approval summary follow the locale like everywhere else, the duplicate formatDateTime in lib/time is gone, and ESLint bans it, providerLabel in UI code, toLocale*String and hard-coded en-GB/en-US. The pseudo-locale test now covers the dashboard, composer, post detail, calendar, accounts, developer and settings screens.
  • Plural forms in the UI come from ICU messages: "1 requests per minute" for agents is now "1 request per minute", and the same rule covers attempts, accounts, attachments and characters.
  • A failed publishing attempt whose server message is only a code shows the sentence for the attempt's error code (before: the generic "Something went wrong").
  • Outside English the UI never shows the server's English error text; it shows the sentence for the error code from the catalog (field-level messages of the token form are mapped to catalog keys).
  • The site smoke test confirms the demo's Reset dialog (it had been failing since the reset confirmation landed).
  • Pages no longer scroll slightly on notched iPhones: the safe-area body padding is now subtracted from the min-h-screen and md:h-screen heights (dynamic viewport units, so the mobile browser bars are accounted for); desktop and Android, with zero insets, are unchanged.
  • The completed setup checklist collapses to a "Set up" line with a "Show steps" button; the Approvals tab is kept in the URL (?tab=history); stacked table rows (Developer on phones) are more compact.
  • Calendar month view no longer scrolls sideways at 768 to 1280 px; event chips are at least 24 px tall with a gap between them.
  • Approval cards no longer break labels on phones and show network and status names (LinkedIn, Telegram, Draft) instead of codes.
  • Settings Terms and Privacy links are underlined, not only coloured. The Escape key closes the mobile menu and returns focus to its button; the mobile header stays visible while scrolling.
  • Touch targets: buttons, nav links, pending pill, legal links, "View all" and the demo Reset reach 44 px (24 px for Reset) on phones while desktop stays compact. Toasts respect the bottom safe-area inset.
  • Demo banner is a labelled landmark, and Reset asks before wiping demo data. Loading states carry visible-to-screen-reader text, the email notice close button and the dashboard "View all" links have specific names, and the dashboard stats row has a heading.
  • "Canceled" is spelled the American way in the message about a post that cannot be edited (it said "cancelled"), matching the status name.
  • The "requests waiting" pill in the mobile header no longer wraps: on phones it shows only the count (screen readers still hear "N requests wait for you"), and the full text appears from 640 px.