Reword "May be" to "Maybe" in the auth.no-providers message and run
translation:generate to register the key in every locale dictionary, then
replace the English placeholders with proper translations for each locale.
Add a test asserting the error is hidden when providers are configured.
Captures what isn't obvious from the diff alone: the ten places a
node/pnpm version is pinned and must move together (including .nvmrc,
which CI never reads and is how the node-16 drift in this PR's first
push went unnoticed), the pnpm-10 layout pins, the msw 1->2 migration,
the deliberately held-back majors, and the abandoned html-minifier
replacement. Written so the next dependency bump doesn't repeat the
same gaps.
- frontend/.nvmrc was still pinned to 16, left behind by the node 16->20
bump everywhere else (Dockerfile, CI matrices). A contributor running
'nvm use' in frontend/ would land on node 16, which cannot even run
pnpm 10 (requires node >=18) -- CI never reads .nvmrc, so this was
invisible to every check.
- pnpm/action-setup 'version: 10' floated the patch release in CI,
inconsistent with the exact 10.10.0 pin now used in Dockerfile,
Dockerfile.e2e and packageManager. Pinned all ten occurrences across
ci-frontend.yml, ci-frontend-api.yml and release.yml to 10.10.0.
- Pin pnpm to the exact version (10.10.0) when installing it in the
production Dockerfile, matching packageManager and Dockerfile.e2e,
instead of a floating major that can drift the lockfile behaviour.
- Fix mockEndpoint's array header handling in the api test utility:
append each value instead of joining with a comma, which is how
multi-value headers (e.g. set-cookie) are actually represented.
- Update apps/remark42's engines to node >=18 / pnpm >=10, matching
the pnpm 10 requirement instead of the stale node 16 / pnpm 8 range.
- frontend/Dockerfile.e2e: bump base image to mcr.microsoft.com/playwright:
v1.61.1-noble to match the Playwright 1.61.1 npm bump (browser revision
mismatch was failing all e2e specs), and corepack pnpm@8 -> pnpm@10.10.0 to
match the pnpm bump and the v9 lockfile.
- release.yml validate: pnpm 10 forwards 'test -- --runInBand' literally as
'jest -- --runInBand' (treated as a path pattern, 0 tests). Drop the extra
separator: 'pnpm test --runInBand'.
pnpm 8.15.9 -> 10.10.0 (packageManager + lockfile regenerated to v9). Frontend
CI (ci-frontend.yml, ci-frontend-api.yml, release.yml) and the production
Dockerfile bumped from node 16 + pnpm 8 to node 20 + pnpm 10 (pnpm 10 requires
node 18+). pnpm audit: no known vulnerabilities (was 63 alerts).
packages/api: bumped to latest including the major test stack - vitest 4, jsdom
29, @vitest/coverage-v8 4, @typescript-eslint 8.62, typescript 5.9, prettier
3.9, @types/node 26, and msw 1 -> 2. Migrated tests/test-utils.ts to the msw 2
http/HttpResponse API (capturing a compatible request shape) and made test base
URLs absolute so node 20's native fetch is intercepted; added the jsdom base
URL. type-check:api, lint:api and coverage:api (45 tests) all pass.
apps/remark42: safe in-major bumps (webpack 5.108, postcss, mini-css-extract,
html-webpack-plugin, ts-loader, webpack-dev-server 5.2.5, core-js, clsx 2,
lodash-es 4.18, dotenv 17, @types/*). Transitive vulns patched via
pnpm.overrides. type-check, lint, build, jest coverage (299 tests) and
translations all pass.
pnpm 10's stricter layout required a few pins to keep the app's preact-compat
setup compiling: preact 10.6.2 (override), react-intl 6.0.5 and
@testing-library/preact 3.2.2 (newer types break the build), tsconfig paths for
preact, @types/minimatch 5.1.2 (6.x is an empty stub) and cheerio 1.0.0-rc.12
(1.2 is ESM and breaks jest 28). Held: react/react-dom (preact compat alias),
babel 7, eslint 8, stylelint 14, jest 28, typescript 4.7 (app),
redux/react-redux - majors that change the bundle or need a config migration.
Build output verified against a clean master build: apps/remark42 output is
functionally identical (the only diffs are webpack module-id numbering and
css-module class tokens from the webpack/css-loader bump; all HTML, CSS values
and translations byte-identical).
* fix(frontend): no_footer scrollbar regression introduced in v1.16.0
Two unrelated changes in v1.16.0 combined to surface a scrollbar in
no_footer=true mode:
1. c26f45e5 removed the deprecated `scrolling="no"` iframe attribute
on the grounds that "overflow is already hidden via CSS". That CSS
(`overflow: hidden` in createIframe styles) is on the iframe ELEMENT
in the parent page; it has no effect on the iframe DOCUMENT's own
scrollbars. The spec-correct replacement is `overflow: hidden` on
the iframe document's body — added here to global.css.
2. The negative `margin-bottom: -24px` on `.thread:last-child` was a
trick to tighten the gap to the footer (combined with the footer's
`margin-top: 48px` it collapsed to a 24px net gap). With no_footer
the negative margin had no positive-margin sibling to collapse
against and instead propagated up through .root, leaving body
~24px shorter than the visual content. The iframe height calc
(`body.offsetHeight + 12`) then sized the iframe below the visible
bottom of the last thread → scrollbar.
Replace the negative-margin trick with a straight `margin-top: 24px`
on `.copyright`. Same 24px visual gap when the footer is shown, no
propagation when it isn't. The mix={styles.thread} on Thread becomes
a dead reference and is dropped.
Closes#2073
* fix(frontend): drop dead Thread.mix prop after root.tsx removed its only caller
Both Copilot and umputun flagged this in PR review: after the parent
commit on this branch dropped `mix={styles.thread}` from root.tsx, the
`mix?: string` prop and the corresponding entry in the clsx() call in
thread.tsx are dead code — no caller passes it (the recursive Thread
render in thread.tsx:82 never did either). Remove the prop, the
destructure, and the clsx entry.
* Probe /auth/status from frontend to avoid 401 console noise on /user
GET /api/v1/user requires auth and returns 401 for anonymous visitors,
which the browser logs to console even when JS catches it. Probe
/auth/status first (always 200), then fetch /user only when logged in.
Stale auth cookies are cleared when status reports "not logged in" to
preserve the cleanup-on-probe behaviour previously triggered by /user 401.
Closes#1188.
* Don't clear auth cookies when /auth/status probe itself fails
A transient network/5xx on the /auth/status probe used to fall through
into the cookie-clear branch and silently log the user out on the next
page load. Distinguish "probe failed" (null) from explicit "not logged
in"; only the latter clears JWT/XSRF cookies. Lock the distinction with
a negative assertion in the probe-failure test.
Also align packages/api prettier config with apps/remark42 (trailingComma: 'es5')
so future edits don't sweep unrelated trailing commas into the diff.
* fix(embed): set color-scheme on iframe to fix Firefox dark mode
Firefox renders a white background in dark mode when color-scheme is 'none' on the iframe. Set color-scheme to match the active theme on both the outer iframe element and the inner document root, so Firefox uses the correct rendering mode from the start and on theme changes.
* fix(embed): default iframe color-scheme to light when no theme set
Changes the fallback from 'light dark' to 'light' to match the inner document's default behavior, which always defaults to light when no theme is specified.
The edit textarea was running `data.orig` through the browser's HTML
parser via a detached `<span>.innerHTML` to "decode entities", which
turned user-typed `<`/`>` into real `<`/`>`. On save, blackfriday
then saw a real `<script>` tag, bluemonday stripped it, and the comment
body collapsed to an empty string.
The decode block predates commit 243c835 (2022) which stopped the
backend from sanitising `orig` with bluemonday. Before 243c835, orig
came back HTML-escaped from the API and the frontend compensated.
After 243c835 the backend stores and returns orig byte-for-byte, but
the frontend decode was never removed — so it has been silently
corrupting user input containing entities for ~3.5 years.
The backend contract is clear: `orig` is the raw user input, never
rendered as HTML. The frontend should echo it back into the textarea
unchanged. This change removes the decode and adds 45 table-driven
regression tests covering entity round-trips, unicode edge cases,
and markdown constructs.
Consolidate legacy BEM CSS files into CSS Modules for 4 components:
- dropdown/__item: 1 CSS file → dropdown-item.module.css
- list-comments: 1 CSS file → list-comments.module.css (removed unused
comments-list class that had no CSS rules)
- comment-form/__subscribe-by-rss: 1 CSS file → subscribe-by-rss.module.css,
removed dead titleClass prop and dead __rss-link directory
- settings: 10 CSS files → settings.module.css, removed dead
.settings__blocked-users-username CSS rule
Built artefact comparison (master vs branch):
- 83 of 89 files in /srv/web/ are byte-identical (all locale bundles,
SVGs, HTML pages unchanged)
- 6 files differ: remark.css/js/mjs and last-comments.css/js/mjs
- CSS changes are class name hash shifts (e.g. G_A → H_A) caused by
webpack's module ordering, plus 3 new var() fallback values added
by the CSS modules build; all property:value pairs are preserved
- JS changes are minified variable name shifts (O ↔ A, I ↔ L) from
changed import order; no logic changes
- Visual comparison (pixel-by-pixel screenshots of both light and dark
themes on the demo page) shows 0 different pixels
- Bundle sizes: remark.css -626 bytes, remark.js -512 bytes,
last-comments.css -16 bytes (dead CSS removed)
* frontend: remove deprecated iframe attrs and non-standard CSS
Three separate cleanups:
1. remove deprecated HTML attributes from iframe creation (create-iframe.ts)
- frameborder="0": deprecated since HTML5; border is already set to none via CSS
- allowtransparency="true": non-standard Microsoft attribute never in any spec;
transparency is handled by body { background: transparent } in CSS instead
- scrolling="no": deprecated since HTML5; overflow is already hidden via CSS
- horizontalscrolling/verticalscrolling: non-standard IE-era attributes with
no effect in modern browsers; remove without replacement
2. replace allowtransparency with explicit CSS (global.css)
- add background: transparent to body; this is the spec-correct way to make
an iframe document transparent, as documented by MDN
3. drop -moz-touch-enabled media query prefix (5 comment CSS files)
- -moz-touch-enabled was a Firefox-only non-standard media feature removed
in Firefox 58 (2018); pointer: coarse is the standard equivalent and was
already present as the second condition in every query, so removing the
dead -moz prefix reduces the media query to just (pointer: coarse)
note: colorScheme: 'none' in create-iframe.ts is intentionally left unchanged;
it is tracked by #1430 and requires a broader color-scheme implementation
* frontend: fix CSS bugs and replace deprecated properties
Bugs fixed:
- comment-votes.module.css: add missing comma between transition values;
without it the shorthand was invalid and colour transitions on vote
buttons were silently ignored
- icon-button.module.css: fix "transfrom" typo (should be "transform");
the misspelling made the transition declaration a no-op, so the hover
scale animation jumped instantly instead of easing
- auth.module.css: remove doubly-nested rgb(rgb(var(…))) call; the outer
rgb() rejected the inner rgb() result, so the .title element's colour
fell back to inherited instead of the intended --secondary-text-color
Deprecated properties replaced:
- comment-form__markdown-toolbar.css: replace deprecated clip: rect()
with clip-path: inset(50%); clip was deprecated in CSS Masking Level 1
- raw-content.css: replace word-wrap with overflow-wrap; word-wrap was
renamed in CSS Text Level 3, all current browsers support overflow-wrap
- global.css: remove redundant literal-colour fallback lines before
var() declarations in .preloader and .preloader_view_iframe; the var()
calls already have inline fallback values (e.g. var(--color6, #fff)),
making the preceding duplicate property and its stylelint-disable
comment unnecessary since IE11 EOL
* move border:none from inline style to widget__comments-frame class
Add skipLibCheck to skip type checking of .d.ts files in node_modules,
matching the setting already used by the main remark42 app. Fixes
@types/eslint-scope vs @types/eslint type incompatibility.
Remove non-iframe child nodes from the root element once the
iframe signals it has initialised, allowing users to add
loading placeholders that get cleaned up automatically. Fixes#1990
Add admin_edit field to frontend Config types and use it in
comment component to give admins unlimited edit time and allow
editing comments with replies. Hide countdown timer when
editDeadline is Infinity. Fixes#1986
The last step of the data deletion feature–when the admin user visits the link sent by the user requesting to delete their data–was broken due to the deleteme.js script not being loaded.
* 1833 - Toolbar buttons are stuck to the main comment form
* Add readonly and JSDoc to CommentForm textareaId properties
Improve code quality based on review feedback: mark textareaId as
readonly since it should never change after construction, and add
JSDoc to static textareaCounter explaining its purpose.
---------
Co-authored-by: Dmitry Verkhoturov <paskal.07@gmail.com>
- Update Google logo to use gradient version per new brand guidelines
- Replace Twitter bird logo with X logo
- Add light/dark variants for X logo (like Apple and GitHub)
- Update oauth.consts.ts to use new X logo variants
Fixes#1957
With AUTH_SEND_JWT_HEADER=true, frontend now properly handles JWT authentication:
- Store JWT token in client-side cookie named 'JWT'
- Extract and store XSRF token from JWT payload
- Set Secure flag automatically when on HTTPS connection
- Update documentation to clarify this behavior
This fixes an issue where login state would be lost after page reload
when using header-based JWT authentication.