e2e-reveal-timing
638
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6f40926241 |
Drop the origin-anchored public path from the delete-me bundle (#2219)
deleteme.ts set __webpack_public_path__ to window.location.origin plus /web/, which discards any path prefix the instance is served under. It is inert today because that bundle references no asset and loads no chunk, so the value is assigned and never read, but it is wrong by construction and would resolve at the domain root the moment anyone adds an image to that page. Removing it leaves webpack's own publicPath: 'auto', which derives the base from the script's URL and is right in both arrangements. |
||
|
|
250e8ad925 |
Report the widget height when the sign-in panel closes (#2213)
The sign-in panel is positioned absolutely, so it grows the iframe without growing the document: `useDropdown` measures the panel itself and posts the sum, and a ResizeObserver on the panel keeps that number current while it is open. Closing it resizes no box anything watches. The panel observer goes with the element, and the document observer in `Root` sees nothing, because the document height never changed in the first place. Nothing then tells the parent to come back down, so the iframe keeps the open panel's height and the embedding page carries a hole under the widget for as long as the reader stays on it. The effect's cleanup now reports the height, with no element, so the number is the document's own. That is the one place both close paths reach: the click inside the widget, and the clickOutside message the host page posts when the reader clicks anywhere else. TestGeometry_HeightFollowsTheAuthPanelAndTheTextarea covers this and has been intermittently green: whether the frame comes back down without the fix depends on timing, and it fails on every run here while CI has been passing. The unit test fails with the cleanup reverted. |
||
|
|
23be25d84a |
Fix seven widget defects, including the cookies the separate-domain setup needs (#2197)
* Drop the frontend workspace root and re-resolve the lockfile
`frontend/` carried a `package.json`, a `pnpm-workspace.yaml` and the lockfile
for a workspace of exactly one package. Two manifests meant two places to
declare a version, and the app pin was the one that did not win: `preact` and
`@babel/core` were each written twice, and a bump to the app manifest alone
would have been a silent no-op, since `pnpm.overrides` decides and it lived at
the root.
Everything pnpm reads now lives in `frontend/apps/remark42`: dependencies,
`packageManager`, `engines` and the overrides. `frontend/` keeps `.nvmrc`,
`.husky` and `CLAUDE.md`, none of which pnpm reads. The directory nesting
stays: every path in the repository points at `frontend/apps/remark42`,
including the published contributing docs, so moving the package up would have
rewritten 14 files to no benefit.
Moving the manifest kept the old resolutions verbatim, which left optional peer
subtrees the tree no longer reaches: `ts-node` under jest, `@swc/core` under
webpack, `vitest` under `@testing-library/jest-dom`, `tslib` under
`webpack-dev-server`. None is referenced by any config or source file here.
Re-resolving drops 137 packages and moves 59 to versions already permitted by
the ranges in the manifest, 1446 to 1308, with no direct dependency changing
version: the five that look changed differ only in their peer suffix. Every
file `pnpm build` produces is identical in size before and after.
The frontend-deps stage of the Dockerfile sets `CI=true` so the `prepare`
script skips husky, which has no git repository to install hooks into there.
* Stop markdown-only changes triggering heavy workflows, and check the documented versions
`ci-backend.yml`, `ci-build.yml` and `ci-frontend.yml` all end their path
filters with `!**.md`. The e2e workflow did not, so a change to any markdown
file under `frontend/` or `backend/` matched its `frontend/**` and `backend/**`
entries and started a docker build and the whole browser suite. The release
filter had the same hole and two of its own: it names `README.md` and `LICENSE`
on purpose, since `.goreleaser.yml` packages both, so it now excludes markdown
under `backend/` and `frontend/` only. `CLAUDE.md` and the installation page
were listed as well, and neither is packaged.
`ci-site.yml` goes on matching markdown, which is right, since the site is
built from it. It excludes `CLAUDE.md`, so a future `site/CLAUDE.md` cannot
start a site build, and `site/README.md`, which documents how to build the site
rather than being part of it.
The installation page tells a reader that a source build needs Go 1.25, Node
24+ and PNPM 10. Nothing kept those in step with `backend/go.mod`,
`engines.node`, `packageManager` and `.nvmrc`, and the drift is silent: a wrong
version in the docs builds and tests exactly as well as a right one. `.nvmrc`
is the pin with form here, having sat at 16 through the whole node 20 migration
because nothing red ever pointed at it. The check compares each stated version
against its source and holds `.nvmrc` to `engines.node`, and it fails when the
page states no version at all, so removing the claims cannot turn it into a
check that passes by comparing nothing.
Its own workflow rather than a step in an existing one, since the inputs span
the backend module, the frontend manifest and the site.
* Fix the cookie fallback page, asset path, message senders, auth teardown and cookies
Two defects with the same origin:
|
||
|
|
a82dc8d3f1 |
Restore the legacy /web/*.js URLs and fix iframe reuse (#2192)
* Serve the legacy /web/*.js names from their .mjs siblings The build emitted <name>.js alongside <name>.mjs until the two compilations were collapsed into one. Dropping the second compilation was right, but it removed URLs the project itself had published: the v1.16.4 SPA documentation named /web/embed.js directly and its loader snippet requested .js. Pages that hard-coded those names now 404 with no deprecation. webFiles.Open retries a missing .js against the .mjs sibling. The bundles contain no import or export, so the same bytes serve both names. The retry runs only once both sources report the name missing, so a real .js still wins, and an unreadable sibling reports its own error rather than being flattened into the requested file's 404. Related to #2178 * Reuse only the comments iframe embed created createInstance took root.firstElementChild as its iframe, so anything a page left inside #remark42 was adopted instead. A <noscript> fallback became the "iframe", createIframe never ran, and the height messages went to an element that cannot show comments. That also defeats the placeholder support, which promises content in the root is cleared once the iframe reports inited: a text placeholder works, but any element placeholder is mistaken for the iframe, so inited never arrives and the cleanup never runs. The iframe now carries data-remark42-iframe and the lookup is scoped to a direct child, so a second createInstance still reuses it while nothing else in the root can be adopted. Related to #1990 * Assert the backup contents rather than the compressed size TestBackup_MakeBackup and TestBackup_Do pinned the gzip output at 52 bytes, which ties them to the exact output of compress/flate. The same input encodes to 57 bytes on go 1.27, so both fail for anyone building on a toolchain newer than the one CI pins. They now read the backup back and compare it against what the exporter wrote, which is what the tests were reaching for and does not move with the compressor. The payload is a shared constant so the two cannot drift. |
||
|
|
49bf83b09c |
Address the review follow-ups from #2188, #2189 and #2190 (#2193)
* Read the collapsed-threads key through getJsonItem `getFromLocalStorage` parsed the stored string directly, so anything malformed under `__remarkCollapsed` threw out of `restoreCollapsedThreads`. That call sits in `remark.tsx` ahead of the `render`, so the throw took the whole widget with it: the reader was left on the preloader, over a view preference. `getJsonItem` in `common/local-storage.ts` already wraps a parse of a localStorage key and returns null on failure, and null is a shape the check below already reads as empty. The rest of that function is total against whatever the browser holds, and the bare parse was the one way in. * Stop retrying a failed e2e test in CI The suite went in with one gotestsum rerun. It has no failures on record to justify that: 31 CI runs since it landed, all green, and no rerun report has ever been produced. A retry is what turns an intermittent regression into a green build, and while the suite is this young its own failures are the evidence worth keeping. `E2E_RUN_ID` stays. It stamps the threads a run works on with the CI run id, so a thread url in a trace or a log names the run it came from. It carries no data across: the stack is disposable, and a local run under the same id gets those urls on an empty database. * Stop two chooseUnusedPort comments claiming collisions cannot happen All four copies listen on :0, read the assigned port, close the listener and bind later, so nothing holds the number across that gap and another binary can take it. The copies in app/cmd and app/rest/api call a collision very unlikely, which is accurate; the ones in app and the example module said binaries never land on the same number, which is not, and a comment ruling out a port collision is what would send the next person chasing one somewhere else. All four now read the same. Closing the window rather than describing it means the server binding :0 itself and reporting the address it got, which is a larger change. |
||
|
|
b6975af63c |
Fix collapsed threads not restoring, and the clock skew correction (#2188)
* Fix collapsed threads not restoring, and the clock skew correction
Collapse state was kept as a flat list of `siteID_url_commentID` strings
and read back by splitting on `_`. Any underscore in the url, the site id
or the comment id made the pieces impossible to tell apart, so a page
whose url contains one lost its collapsed threads on every reload, and one
page's entries could be read or deleted as another's: `/post` matched
everything stored for `/post_2`, and a site id of `blog` matched `blog_ru`.
No separator fixes that, since every candidate can occur inside the values,
so the ids are now nested under the site and the url instead. Anything
stored in the old shape reads as empty: collapsed threads are a view
preference, and re-expanding them once is not worth a migration.
The e2e suite had been stripping underscores out of its own thread urls to
work around this, which left its collapse test unable to fail on the bug it
covers. That workaround is gone, and the test now fails without this fix.
`serverClientTimeDiff` was written in seconds and added to an epoch in
milliseconds, so the correction it exists to apply was a thousandth of the
real skew. It is now milliseconds, and named for the unit.
A response with no usable `date` used to fall back to a zero timestamp,
which already made the "skew" about twenty days and would have made it
fifty-five years once the units were right. Nothing is stored now unless
the reading is plausible, since `Date.parse` is lenient enough to turn junk
into a date and let an absurd value through the branch that parses.
The score tooltip reports controversy again when there is any. It has been
dead since the vote component was rewritten in
|
||
|
|
7c312da199 |
Stop the Telegram paragraph rendering with spaces in Japanese and Chinese (#2187)
`telegram-link.tsx` assembles that paragraph from five separate messages with the anchor and the QR clause in the middle, joining them with a hardcoded space. Japanese and Chinese do not put spaces between words, so the assembled sentence carried them mid-clause: `通过 此链接 或扫描二维码 打开 Telegram,` separated a preposition from its object and an adverbial phrase from its verb. The separator now comes from the locale and is empty for `ja`, `zh` and `zh-tw`. Korean keeps its spaces, because Korean uses them, as do Thai's phrase boundaries. The locale is matched exactly as `loadLocale` matches it. Comparing case insensitively would have been worse than the bug: `remark_config.locale` is forwarded verbatim and `loadLocale` is case sensitive, so a conventional `zh-TW` loads the English catalogue, and a lowercased comparison would then join English words with nothing between them. The test covers that case alongside `ja` and `en`, and fails if either the comparison loosens or the separator stops depending on the locale. Macedonian labelled the replies feed as comments. `subscribeByRSS.replies` carried `Коментари`, the same value as `user.comments`, in a catalogue whose two reply strings are both `Одговори`. That option subscribes to `/rss/reply?user=`, which `UserReplies` documents as comments replied to that user, so the feed is replies. `auth.user-not-found` is removed. It reached every catalogue but could not render: the only dynamic path to it is `messages[invalidReason]`, and `invalidReason` comes from `getTokenInvalidReason`, which returns `expiredToken`, `invalidToken` or null, or from a backend error string, and the backend emits nothing matching. Catalogues go from 181 keys to 180. |
||
|
|
a5b2fe3cfc |
Consolidate the frontend toolchain onto babel, and ship one bundle (#2178)
Four upgrades that were finished but never merged, the compiler collapse
they enable, and the dependency sweep that follows. Direct
devDependencies go from 78 to 60 and dependencies from 10 to 9.
Three were doing the same job: `ts-loader` stripped types in webpack,
`babel-loader` did everything else, and `@swc/jest` repeated both for the
tests with its own copy of the JSX settings. Babel is the one that
survives, because the `data-testid` stripper has no equivalent elsewhere.
`ts-loader` ran `transpileOnly: true`, so it only stripped types, which
`@babel/preset-typescript` does; `fork-ts-checker-webpack-plugin` was
already what type-checks. Jest runs `babel-jest` against the same
`.babelrc.js` the bundle uses, passed as `configFile` because a
file-relative babel config does not reach the `node_modules` packages in
`transformIgnorePatterns`, and `jest.config.mjs` is plain ESM because a
`.ts` config is compiled against `tsconfig.json`, whose
`verbatimModuleSyntax` rejects ESM syntax in a file the package has not
declared as a module.
That removes `ts-loader`, `@swc/jest` and `@swc/core`. The last was
pinned to 1.2.205 from 2022 with no way forward, because newer builds
emit non-configurable exports and break `jest.spyOn` across 13 suites.
Babel compiles a file at a time with no type information, so it cannot
tell a type-only import from a real one and keeps the module. One line,
`import { boundActions } from './connected-comment'`, pulled the whole
redux store into `last-comments.mjs` and doubled it. `verbatimModuleSyntax`
and `@typescript-eslint/consistent-type-imports` mark them properly; the
statement has to be a separate `import type`, since verbatim semantics
keep an inline `import { type X }` and load the module anyway.
The legacy and modern compilations produced the same bytes. Both read the
same browserslist query, `defaults, not IE 11, not samsung 12` resolves to
chrome 109 and up, and nothing in the source needs transforming for that
set, so 28 of the 29 output pairs were byte-identical.
That made the module/nomodule switch worse than redundant: it served the
`.js` file to browsers with no ES module support, and those files carried
`??`, `?.` and class fields, so the fallback handed its own audience a
syntax error. There is now one bundle, always loaded as a module, in the
five templates and in the seven `site/` documents integrators copy from.
A production build emits 29 files rather than 58, in about 3 seconds
rather than 17. Two of those documents did not work at all beforehand:
the SPA snippet could not parse, and the subdomain example had an
unterminated string.
`@babel/core` 8 declares `^22.18 || >=24.11` and `size-limit` 13 declares
`^22.18 || ^24 || >=26`, so 20 was below the floor of two things installed
here; pnpm only warns, which is why every build passed. All seven places
the frontend pins it move together. `site/` is untouched: it builds with
yarn and eleventy and installs neither.
`eslint --print-config` before and after gives 173 active rules on an
application file against 172, and 172 on a spec file and a plain JS file
against 171. What is gone is three `flowtype` rules with no Flow here,
`no-new-object` and `no-new-symbol` whose upstream replacements are on,
`react/forbid-foreign-prop-types` with no propTypes anywhere, and, on TS
only, `no-useless-constructor`, whose typescript-eslint version is on at
error. `@babel/core` is pinned to 8 across the workspace because
`@jest/transform` and `istanbul-lib-instrument` depend on 7 outright; a
second scoped override holds `eslint-config-preact` on 7, since its
`@babel/eslint-parser` loads babel 7 syntax plugins.
`fast-async` rewrote every async function into nodent promise chains,
calls babel's `transform` synchronously, which babel 8 removed, and every
browser in the target list runs async natively. `prefresh` blew its stack
on `createContext` under babel 8 with no newer release to move to, which
compiled `intl.tsx` and `store/context.tsx` into throwing stubs, so
`pnpm dev:app` could not run the widget at all. `core-js` is not injected
now that `useBuiltIns` is gone, `postcss-custom-properties` was reached
directly although nothing declared it and resolved only through pnpm's
private hoist directory, and `cssnano` ran in both postcss chains although
`CssMinimizerPlugin` already uses it.
`pnpm lint`, `pnpm test` and `pnpm build` now work from `frontend/` as
`CLAUDE.md` and the contributing guide have always said they do; the
workspace root defined none of them.
|
||
|
|
fb7b6c2cdd |
Serve the build-independent web assets from the backend (#2181)
* Serve the build-independent web assets from the backend `privacy.html`, `markdown-help.html` and the `400x400.jpeg` it embeds carry no template variable, link no script or stylesheet, and are imported by nothing in the widget. They now live in `backend/app/webassets/assets`, embedded there, and are served under `/web` alongside the frontend build. `/web` reads the frontend build first and falls back to them, which is what lets an operator replace one by dropping a file into `--web-root`. That is what `privacy.html` needs: it describes remark42.com, while the authorization guide tells operators to hand its URL to Google and Facebook as their own application's privacy policy. Only a missing file falls through. An unreadable file in the web root keeps reporting as unreadable rather than being silently replaced by the embedded copy, and a name the filesystem rejects reports as missing rather than as a server error, both matching what `http.Dir` did. The dev server serves the same directory, so the Markdown help link in the comment form resolves on the dev port as well as in production. The two pages are served as they are written. `markdown-help.html` was minified before, and its formatted inline stylesheet is most of its 8.5 kB; that is 2.4 kB more over the wire, behind the hour-long cache header the file server already sets. Drops `copy-webpack-plugin`, which had no other pattern, and the stylelint entries that only ever matched these files. * Make pnpm dev:app start again The dev server has been failing to start on two counts, so the flow the contributing guide documents does not run at all. `webpack-cli` 4 drives `webpack-dev-server` 5 through the argument order of an older major, handing it the compiler where it expects the options object. It rejects that against its schema and exits, complaining about an unknown `_assetEmittingPreviousFiles` property, which is a field of the compiler. `webpack-cli` 7 is the release that declares `webpack-dev-server` 5 as a peer. Past that, `http-proxy-middleware` resolves to 4.1.1, which no longer accepts the two-argument call `webpack-dev-server` makes, so the `/api` and `/auth` proxies throw on startup. It is pulled in by the security override for CVE-2025-32996, the only override in the file with no upper bound: `>=2.0.10` matches every later major. Bounding it to the 2.x line keeps the fix and the API `webpack-dev-server` calls. With both in place `pnpm dev:app` serves the widget and the pages under `/web` on port 9000. |
||
|
|
4c9ef37cf1 |
Move the site from eleventy to hugo (#2179)
* Move the site from eleventy to hugo The site is built by a single static binary. No node, no package manager, no lockfile, and the toolchain it needed is gone: eleventy, tailwind, postcss, markdown-it and its three plugins, date-fns, prism, npm-run-all, cross-env and html-minifier-terser. Hugo covers most of that itself. Chroma replaces prism, goldmark replaces markdown-it, `--minify` replaces html-minifier-terser, and fingerprinted asset URLs replace the cache-busting `version` shortcode that stamped `Date.now()` into every stylesheet link. `assets/styles.css` is hand-written, since tailwind was the only reason left to keep a package manager. The palette and the light and dark values are custom properties at the top of the file; the minified stylesheet is 15 kB against tailwind's 46 kB, and the whole build 1.0 MB against 1.2 MB. It was matched to the old one by comparing computed styles rather than by eye, which is how the heading weights and line heights, the list marker colour, and the home page heading and sign-off were caught: the last of those had been carried by tailwind utilities written into the markup. The `::: note` container becomes a `note` shortcode taking the emoji to show. Its closer needs a blank line after it, because a shortcode is not a block rule the way `markdown-it-container` was, and without one goldmark keeps the callout inside the open paragraph. The `overflow-x` wrapper around tables and the heading anchors are goldmark render hooks. Syntax guessing is off. Chroma detected a systemd unit file as gdscript and a chat transcript as mysql, and colouring a snippet as the wrong language is worse than not colouring it. The two chroma themes are scoped to opposite sides of the theme switch rather than layered, because they do not declare the same properties on the same tokens: github gives Error a background github-dark never overrides, and styles Punctuation where github-dark leaves it alone. Layered, either leaves a light value applying on a dark page. `[frontmatter] lastmod` resolves through git, then front matter, then file modification time. Without that chain `.Lastmod` falls back to `.Date`, which is zero when a page carries no date, and every page reads `Jan 01, 0001`. `enableGitInfo` is off because the image build context is `site/` alone, where hugo fails hard rather than degrading; `HUGO_ENABLEGITINFO=true` gives real per-page commit dates locally. Three fixes fall out of the move rather than being sought: - `/docs/` redirected nowhere. The stub was a markdown file whose permalink was a template expression while `markdownTemplateEngine` was false, so it never rendered and the URL 404'd. It is an alias now - `/docs/contributing/` pointed at `/docs/contributing/development/`, which has never existed. It points at the backend page - the 404 page was built to `/404/` and nothing served it. Hugo writes it to `/404.html` and reproxy is told to use it The mobile documentation menu is a checkbox and label. `visibility: hidden` on the checkbox, which is what the old `invisible` utility set, takes it out of the tab order, and a label is not focusable on its own, so the menu could not be opened from the keyboard at all. The checkbox is clipped rather than hidden, and its label shows a focus ring. Content is unchanged. Every code block on every page is byte-identical to the eleventy output; the only prose difference is that two example values, `mysite.com` and a quoted `https://demo.remark42.com`, are no longer turned into links, goldmark's linkify being narrower than markdown-it's. `backend/README.md` and `frontend/apps/remark42/README.md` are symlinks into the docs tree and follow it to `site/content/`, as does the path `release.yml` watches. `frontend/CLAUDE.md` described the site as a node and yarn project in four places. * Keep the heading anchors markdown-it generated Goldmark strips punctuation markdown-it kept, so 22 headings holding a dot, slash, apostrophe, question mark, bracket or em dash would take a new id and any link into one from outside the repository would stop resolving. Those headings carry their previous id as well, as an empty target emitted ahead of the heading by the render hook, from a map of content path to old anchor in `data/anchor_aliases.json`. The map was built by matching heading text between the two builds rather than by position, so it survives a heading being added or moved. The hook rather than markdown, because goldmark's `{#id}` attribute syntax cannot express these: it accepts dots, apostrophes and em dashes but treats a slash, a question mark, a bracket or a percent sign as heading text, which is 11 of the 22. The ids are stored percent-decoded, since a browser decodes a fragment before matching, so `#children%E2%80%99s-privacy` finds `children’s-privacy`. Verified by navigating to the awkward ones against the built image and measuring where the page settles: each lands 112px down, which is the header offset the target carries. Three pages carried no title, so the docs template rendered an empty `<h1>` above the heading their markdown already had. They take their titles from that heading text, so neither the wording nor its anchor changes, and the template's `<h1>` carries an id. One in-page link pointed at an anchor goldmark no longer generates. The heading render hook emits no permalink anchor. The one it replaced was an empty `<a href>` with `pointer-events: none`, so it could not be clicked, and its only job was a `::before` spacer that `scroll-margin-top` on the heading already does. Being an `<a href>` it stayed in the tab order, so every heading was an unexplained keyboard stop: eight on the installation page alone. Fragment navigation still lands 112px down, clear of the fixed header. * Harden the site image build and its CI The architecture guard could not fire. `${TARGETARCH:-amd64}` defaulted before the `unsupported arch` branch was reachable, so a build without buildkit put an amd64 hugo inside an aarch64 image and ran only because Docker Desktop emulates it. Reproduced with `--build-arg TARGETARCH=`: `/etc/apk/arch` reported aarch64 and `hugo version` linux/amd64. An empty value is an error now. `Dockerfile.dev` had the same defect and no smoke step to catch it, so it would have failed at `compose up`. The hugo tarball is verified against the release's own `checksums.txt`, and the match is asserted present before it is used: piping grep straight into `sha256sum -c` left the guarantee resting on what the checker does with empty input. Busybox exits 1 there, so it did fail closed, but nothing in the line said so. Verified against a checksums file that does not list the tarball: the build stops before the install. Hugo exits 0 on an empty content tree and emits a two-page shell, which would have been copied, pushed and deployed. The build asserts the home page and a docs page exist. `site/**` pull requests were never built. The only building job is gated on `github.ref == 'refs/heads/master'`, so on a pull request every job skipped and rendered in the checks list the same way a pass does, and the image was first built on the run that also deploys it. A `validate` job builds it with `push: false`, needing no secrets so it works on a fork. `.github/dependabot.yml` watched `/site` for npm packages that are gone. That entry is a docker one, which tracks the alpine base. It does not track the hugo pin and cannot: the docker ecosystem reads `FROM` references, and `ARG HUGO_VERSION` is a bare string in a download URL, so that one is a manual bump and `site/README.md` says so. `Dockerfile.dev` carries a `COPY`, so the dev image works without the compose bind mount, and compose runs as the invoking user rather than root, which on linux left root-owned `public/` and `resources/` in the checkout. Recorded in the backlog: `master` has `required_status_checks` off with an empty check list, so the new job surfaces a red X and does not block a merge. That is a settings decision rather than a code fix. |
||
|
|
ff77f41a3a |
Move the e2e suite to Go and playwright-go (#2180)
* Move the e2e suite to Go and playwright-go The seven playwright tests in `frontend/e2e` become twenty in `e2e/`, a separate Go module driving the same browsers through playwright-go. The npm project, its lockfile entries, its prettier config and `Dockerfile.e2e` go with it, leaving `frontend/` a single-member workspace. The suite covers posting with markdown, replying and the nesting that implies, editing inside the deadline and the backend refusing one outside it, deleting, voting with the optimistic score observed mid-flight and rolled back on failure, changing the sort, collapse persistence across a reload, dev, anonymous and email sign-in end to end, the profile iframe, and the two scripts that render into the host page rather than the widget's own frame. The rendering tests run in chromium, firefox and webkit. The rest sign in, sign-in needs the dev oauth2 provider, and reaching that by name from the host is chromium-only, so they run there alone. `compose-e2e-test.yml` runs remark42, a second instance with a short edit window so that path does not need a five-minute test, and mailpit, which catches the email verification message the suite reads back. Everything binds to the loopback interface: the stack holds a known secret and an admin shared id, and `go test` can start it unattended. The tests run on the host rather than in a container. Three settings there exist for the tests rather than for realism. `REMARK_URL` uses a hostname because the dev oauth2 server binds whatever host it reads out of it, and a loopback bind inside a container cannot be published. `UPDATE_LIMIT` is raised because the default of 0.5/sec rejects any test posting twice in a row. The suite also paces its own `/auth/` calls, which are capped at 2/sec by a bare literal in `rest.go` rather than by a setting. Each test gets its own comment thread from a query string on the demo page, so nothing has to reset the database between runs. CI gains a vet and lint job for the module, since the build tag keeps it out of a plain `go test ./...`, and uploads a browser trace for any test that fails. `e2e/README.md` carries the rest: how to run it, what the stack is for, and the widget behaviour the assertions have to work around. * Update golangci-lint to 2.13.1 in the backend workflow The pin sat three minors behind what the linter installs locally, so CI checked the backend with an older set of rules than anyone running it by hand. 2.10.1 also fetches its config schema over the network on every `config verify`, which is a failure mode with no bearing on the code. Both targets are clean on 2.13.1, `backend/app` and the memory_store example. |
||
|
|
1bb002348a |
Complete and correct every translation catalogue (#2177)
* Fix wrong and missing translations across 17 locales
`errors.8` is `ErrReadOnly` (`backend/app/rest/httperrors.go:29`), but 13
catalogues carried a copy of `errors.7`, which is `ErrUserBlocked`. A
reader who simply hit a read-only thread was told they had been blocked,
in Belarusian, Bulgarian, Brazilian Portuguese, German, Finnish, French,
Japanese, Polish, Russian, Turkish, Ukrainian, Vietnamese and Simplified
Chinese. Each now says the page is read-only, in the terminology that
catalogue already uses for its read-only badge.
Czech had an off-by-one: `errors.19`, restricted words, carried the text
of `errors.18`, file not found, and `errors.18` was left in English. So a
comment caught by the word filter reported a missing file. Both rewritten.
Also corrected, all of the same class:
- `de` `vote.downvote` had a leading space
- `mk` had dropped `{shortcut}` from the bold, italic and link tooltips,
losing the keyboard hints
- `fi` `comment.pin` was "Sitoo", which means "it binds", and `comment.unpin`
followed from it; `errors.forbidden` was misspelled "Kieletty."
- `be` `errors.failed-fetch` ended in a stray "r"
- `zh-tw` `authPanel.read-only` wrote 唯獨 for 唯讀
- `ja` `errors.conflict` used 相衝, which is not Japanese usage
- `it` `auth.symbols-restriction` misspelled "numberi" and used "username"
where `auth.username` now says "Nome utente"
Nine Finnish strings, `it` `auth.username` and `zh-tw` `comment.time` were
left in English; `comment.time` is a format string and now matches the
other CJK locales at `{day} {time}`.
No English source string changes, and every runtime placeholder is
preserved. Reviewed by two independent passes, which between them reworded
five of these and found four of the pre-existing defects above.
* Complete every translation catalogue
No locale carries English text any more, and the Telegram authorisation
paragraph now reads as a sentence in all 23 of them.
That paragraph is assembled in `telegram-link.tsx` from five separate
keys with the link and the QR clause in the middle, so a catalogue that
translates each key in isolation produces word salad in any language
whose verb does not sit where English puts it. Japanese rendered as
"テレグラムを開く リンクで または QR コードをスキャン そこで..." and Korean, Persian,
Traditional Chinese and Czech had the same break. Each of those now
splits the sentence at the point its own grammar wants, and every
catalogue was checked by rendering both the wide and the narrow layout,
since the QR clause only appears above 768px.
Ten catalogues also had `auth.telegram-link`, which is the anchor text
and reads "by the link", left as "Telegram bot" from an older source
wording, so the paragraph named the bot twice and never said what the
link was for. Six of the block's keys had never been translated at all
in the 16 locales that ship it, and `auth.telegram-message-1/2/3` were
English in nine. None of these were visible to a check for "value equals
the English string", because none of them equalled it.
Two of the six untranslated keys are word for word the English of their
`subscribeByEmail` siblings, so each catalogue's own existing wording was
reused rather than a second phrasing invented for the same sentence.
Quotation marks in that paragraph now follow each language rather than
the English source: «» for be, ua, ru, fa, fr and ar, „“ for bg, cs, de
and mk, „” for pl and ro, ”” for fi, 「」 for ja and zh-tw.
The button the paragraph tells the reader to press is Telegram's own, and
Telegram ships no interface translation for Japanese, Thai or Vietnamese,
so those three now name it the way Russian and Traditional Chinese
already did, with the Latin label alongside the translated one.
Also swept and fixed: Vietnamese "Bằng đã huỷ đăng kí" for "Bạn", Spanish
"ó" for "o", Thai "คลิ๊ก" for "คลิก", an unclosed quotation mark in Arabic
`commentForm.upload-file-fail`, German alternating between tippen and
klicken for the same action, Czech infinitive "Otevřít" where the rest of
the paragraph is imperative, Macedonian "СО ЛИНК" in caps, French
"Sélectionner" on a button the text calls "Vérifier", a missing space
after a full stop in `ua` `errors.9`, and double spaces in `mk`, `pl` and
`vi`.
Left identical to English on purpose, because the word is the same in
that language: `RSS` and `Telegram` everywhere, "Email" in be, it, pl,
ro, ua and vi, "Site" in bp, fr and ro, and "Conflict." in ro. German and
Turkish do not use bare "Site" and say "Website" and "Web sitesi".
|
||
|
|
fc4e10573c |
Replace react-intl and remove React from the widget (#2176)
Second and final step of #2166. `react-intl` is replaced by `app/common/intl.tsx`, a small i18n binding over Preact context, and with `react-redux` already gone nothing holds the React compatibility alias. React is now absent from the lockfile, the installed tree, the config and the bundles: `react`, `react-dom`, `react-intl`, `@types/react`, `@preact/compat`, `use-sync-external-store` and `intl-messageformat` are all gone, along with the `paths` entries in `tsconfig.json` and three babel-loader excludes. Runtime dependencies go from 15 to 10. `preact/compat` goes too, which matters more than its 3.8 kB. Importing it anywhere installs hooks on preact's shared `options` that remap `onFocus`/`onBlur` to `focusin`/`focusout` for every element and make `@testing-library/preact` rewrite `change` to `input`, the two bugs behind #2166, still live until now. `Button` was wrapped in `forwardRef` with no caller passing one, and `TextareaAutosize` now takes its ref as an ordinary prop. The workaround in `sort-picker.spec.tsx` is gone with them, since `fireEvent.change` reaches a `<select>` again. Gzipped, against master: `remark.mjs` 76.17 kB to 56.47, `last-comments.mjs` 37.97 to 18.26, `deleteme.mjs` 14.51 to 8.42. The limits move with them and keep more relative headroom than master shipped. ### The binding `IntlProvider`, `useIntl`, `createIntl`, `defineMessages`, `FormattedMessage` and `IntlShape`. 32 files change only their import. The export names copy react-intl's deliberately: `formatjs extract` finds messages by recognising `defineMessages`, `FormattedMessage` and `intl.formatMessage` in the AST rather than by import source, so renaming one silently empties the catalogue. `frontend/CLAUDE.md` records that, along with the destructive part: `translation:generate` would then strip the unextracted keys from all 24 catalogues and the check would pass. A message the binding cannot parse falls back to the message in the source: a broken, unhandled or nested tag, a brace that is not a well-formed placeholder, and a placeholder naming a value the caller did not supply. `mk.json` and `th.json` carried broken markup and rendered in English; both are repaired, so a catalogue sweep over every locale can now require well-formed markup with no exceptions listed. `translation:check` gained the validation that would have caught them when they were proposed: a translation's tags have to be well-formed pairs of the names the English string uses, with no attributes, and its placeholders have to be ones the English string provides. Leaving a tag or a placeholder out stays allowed. Run against master's catalogues it reports both. ### enzyme `@types/enzyme` was the last thing pulling `@types/react`, so React could not leave while enzyme stayed. Its three test files move to `@testing-library/preact`, which now has no rival: `@testing-library/preact-hooks` had one import left and its own unmet peer warning. `intersection-observer` was a runtime dependency nothing imported, and the `cheerio` override lost its last dependent with enzyme. Enzyme's `.find(X).prop()` threw unless exactly one node matched, so the converted tests assert node counts explicitly to keep that. ### Verified All 181 message ids formatted across all 24 catalogues through both real react-intl and this binding: 4344 comparisons, no differences. From a wiped `node_modules`: `pnpm install --frozen-lockfile`, `pnpm lint`, `pnpm type-check`, `pnpm test` (392 tests, 42 suites), `pnpm build`, `pnpm size-check`, `pnpm translation-check`. |
||
|
|
a91e322d5c |
Replace react-redux with a preact context binding (#2175)
* Replace react-redux with a preact context binding One of the two packages holding the @preact/compat alias in place, and the contained one: the store is plain redux, and the only react-redux import inside it was a single line re-exporting typed hooks. * Drop the now-unused react-redux types * Subscribe before paint and check once on subscribe Previously, useSelector subscribed to the store inside useEffect, which runs after paint. A dispatch landing between render and that effect was never delivered, since the listener did not exist yet, so the component kept rendering a stale value until some later unrelated dispatch happened to differ from the stale ref. Subscribing in useLayoutEffect narrows the window to before paint, and running the check once immediately on subscribe closes it, which is what react-redux does for the same reason. Adds the first tests for the binding, one of which fails without this change: the store holds 1 while the DOM still shows 0. * Only re-check on subscribe when the state actually moved The subscribe-time check ran unconditionally, so it re-ran the selector at mount. A selector building a fresh object fails Object.is against the value the render already computed, which forced a second render of every connected component: ConnectedRoot and every ConnectedComment, so around 201 extra renders for a 200-comment thread. Reducers return a new root object on every change, so an unchanged state reference means no dispatch was missed and the check has nothing to find. Comparing against the state the render used keeps the property the check exists for while dropping the extra render. The race test still exercises the guarded path, since its dispatch produces a new state object, and a new test pins the mount case: it fails without the guard. Raised by umputun in review. |
||
|
|
931f2db4e3 |
Drop turbo
CI never invoked it, and after #2172 removed the four api scripts its only remaining job was orchestrating one script in one package. |
||
|
|
b8f6dc5f91 |
Require node 20 and record every place the version is pinned (#2168)
* Require node 20 and record every place the version is pinned The declared floor was >=18 while CI, Docker and both .nvmrc files had been on 20 since the pnpm 8 to 10 migration, and transitive dependencies now require 20.18.1. The docs had drifted further still, telling contributors to install Node 16 and PNPM 8. * Set the node floor to the strictest dependency and keep one checklist undici needs >=20.18.1, so a bare >=20 advertised support for 20.0 to 20.18.0, which fail dependency engine checks. frontend/CLAUDE.md already carried a pinning checklist, so the new entries fold into it rather than starting a rival list in the root file. * Keep the node floor at the major, not a patch version engines.node states the major we support. Individual dev dependencies can be stricter within it, and chasing those patch floors into engines and the docs would turn every lockfile refresh into a docs change. |
||
|
|
b03dc366f9 |
Update preact to 10.29.8 (#2163)
* Update preact to 10.29.8 Also moves TypeScript to 5.9, which preact 10.29 typings require, and the compat and testing library pins that go with it. Type checking resolves JSX from preact via the automatic runtime; the bundle keeps the classic transform so babel still strips test ids. * Move babel to the automatic JSX runtime and refresh frontend notes Leaving babel on the classic h pragma while tsconfig used the automatic runtime meant a tsx file without an h import would type-check and lint clean, then throw at runtime, since eslint-config-preact disables react/react-in-jsx-scope and no-undef is off. * Address review findings on the preact upgrade Forward the textarea ref with useImperativeHandle so it clears on unmount and lands during commit rather than after paint. Pair typescript-eslint with the TypeScript it now has to parse. Use the preact namespace types rather than the deprecated JSX aliases, and drop the redundant type re-declarations the element-specific interfaces already provide. * Drive the focus tests through real DOM focus and blur Dispatching a synthetic focusin hard-coded preact/compat's internal alias for onFocus. Calling focus() and blur() exercises the sequence a browser produces and stays correct if that mapping changes. * Raise the two bundle limits the preact upgrade pushes past CI measures remark.mjs at 78024 bytes against a limit size-limit reads as 78000, so it failed by 24. last-comments.mjs had 36 bytes of headroom and would have tripped on the next change. * Regenerate the lockfile after the rebase The rebase resolution left it missing the @typescript-eslint entries, so every CI job failed at pnpm install --frozen-lockfile. |
||
|
|
d370b78613 |
Drop the @remark42/api package (#2172)
* Drop the @remark42/api package It cannot authenticate anyone: clients/auth.ts exposes only anonymous, email and telegram, with no OAuth method, and the fetcher never sets credentials so its cookie auth cannot work cross-origin. Nothing in the repo consumes it, no third-party consumer exists, and npm has served an alpha from July 2022 that CI never publishes. * Drop the removed workflow from the pnpm pinning checklist frontend/CLAUDE.md still counted ci-frontend-api.yml among the places the pnpm version is pinned, and stated a fixed total that no longer holds. |
||
|
|
164eb89c60 |
Raise pnpm override floors to clear all frontend advisories
All 23 open Dependabot alerts against frontend/pnpm-lock.yaml resolve to packages whose override floor sat below the patched release. Every floor now carries an explicit upper bound, as an open-ended floor lets pnpm resolve across a major version. |
||
|
|
fdfce6495c |
Remove the widget body padding and the surplus reported height
Previously the widget document had `padding: 6px` on the body, so every embedded widget sat 6px inside its container and could not align flush with the host layout. `updateIframeHeight` then reported `document.body.offsetHeight + 12`, but the body is `box-sizing: border-box` and `offsetHeight` already includes padding, so the addition double-counted it. Measured against the deployed widget: the content needs 20610px, the body reported 20622px with the padding, and the parent was told 20634px, leaving 24px of empty space below every embed on top of the horizontal inset. Removing the padding does not clip anything. With it at zero, offsetHeight, body scrollHeight and documentElement scrollHeight all agree, and the last child carries no bottom margin, so no margin collapses through the body edge. Resolves #1487. |
||
|
|
8801903d01 |
Derive host from the page URL on self-served pages
Previously the pages Remark42 serves from /web/ carried a build-time host.
The `{% REMARK_URL %}` placeholder is substituted during the image and
release-asset builds, both of which write `http://127.0.0.1:8080`. Docker
rewrites it again at container start from REMARK_URL, but a release binary
has no equivalent step, so it serves demo, counter, last-comments and
deleteme pages pointing at the visitor's own loopback address. `counter.ejs`
additionally had that address hardcoded in two "note" links, which no
substitution touched.
These pages are served by Remark42 itself, so the host is whatever origin and
path prefix delivered them. Deriving it from `location` is correct at the root
and under a path prefix alike, and needs no build-time value. Sibling links
are now relative for the same reason.
`site_id` is left as a literal so the startup substitution in docker-init.sh
keeps matching it.
Reported by @andreas-hempel.
Resolves #1996.
|
||
|
|
8bcfd9e456 |
Close the login dropdown only on a genuine outside click
Previously any message reaching the widget closed the Sign In dropdown, because the handler returned early only for a clickOutside payload while closing was disabled and fell through to closing in every other case. The embedding page posts hash, title and theme messages of its own, and `embed.ts` installs a MutationObserver on the host page title that posts on every mutation, so a host page whose title changes closes an open login form and discards whatever was typed into it. Browser extensions that post into the page have the same effect. After this change the dropdown closes only for a clickOutside payload from `window.parent`. Reproduced against the deployed demo: with the form open and filled, a single `document.title` assignment on the host page removed it, while three seconds of inactivity did not. Resolves #2139. |
||
|
|
6e7820d2b7 |
fix(frontend): remove white flash on comments iframe load
on dark host pages the widget flashed an opaque white rectangle while loading. the iframe element carries color-scheme from the theme param, but its document had none until remark.tsx ran, and a mismatched color-scheme makes the embedded canvas opaque instead of transparent. broken since #2023 added the element-side color-scheme to fix a firefox dark-mode bug. set the document's color-scheme from the theme param in an inline head script, before first paint, using the same rule as create-iframe.ts. that closes the long window but not the surface browsers paint before the document is parsed, which webkit renders white and chromium hides behind paint holding. so also create the iframe hidden and reveal it when the document posts inited, with a timeout fallback so a failed bootstrap cannot leave the widget invisible. the reveal lives in createIframe rather than embed.ts so the profile modal, the other caller, gets it too. that modal focuses its iframe on open, and a hidden element cannot take focus, so focus now fires from the reveal instead of a timer. covered by a unit test for the reveal paths and the event.source guard, and by e2e for the document's color-scheme and the iframe's visibility before inited, after inited, and after the fallback. |
||
|
|
a8dd527c45 |
Fix comments iframe collapsing to preloader height on load
On mount ConnectedRoot immediately reported the iframe height to the parent page while the app was still showing the global preloader, so the parent shrank the iframe from its initial size to ~63px and then grew it back step by step as content rendered. On pages with many comments this reads as the widget blinking several times before loading (reported for radio-t.com). The June frontend dependency refresh (#2091) shifted render/effect timing enough to make the premature measurement happen on every load rather than only on slow connections. Move the height reporting into Root and start it in the setState callback that replaces the preloader with real content: the first height message now always describes rendered content, the iframe never shrinks below it, and subsequent ResizeObserver updates only grow the frame as comments arrive. Also adds the previously missing observer disconnect on unmount. Verified by instrumenting the embed with a height-message listener: master sent 63px then 316px on an empty test page (v1.16.1 sent a single 316px); with this fix the first message is 316px again. |
||
|
|
fff9127976 |
fix: correct no-providers message grammar, translate it, and cover both branches
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. |
||
|
|
406df022ba | fix: ui error when no auth providers configured | ||
|
|
fc3d93c398 |
Add frontend/CLAUDE.md documenting dependency-update gotchas
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. |
||
|
|
4baf0f4260 |
Close remaining node/pnpm version drift after the pnpm 10 bump
- 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. |
||
|
|
b72030114c |
Address Copilot review feedback on #2091
- 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. |
||
|
|
8626e4181f |
Fix CI for node 20 / pnpm 10: e2e Playwright image and jest arg forwarding
- 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'. |
||
|
|
f5ccfaa0e1 |
Update frontend dependencies to latest, bump pnpm to 10, clear all npm audit alerts
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). |
||
|
|
34ed97b7a6 |
Merge pull request #2056 from umputun/dependabot/npm_and_yarn/frontend/postcss-8.5.10
chore(deps-dev): bump postcss from 8.4.14 to 8.5.10 in /frontend |
||
|
|
0868b70fa9 |
Merge pull request #2063 from umputun/dependabot/npm_and_yarn/frontend/webpack-dev-server-5.2.4
chore(deps-dev): bump webpack-dev-server from 4.9.3 to 5.2.4 in /frontend |
||
|
|
a21044738d | fix typo in file name | ||
|
|
198efddb54 |
fix(frontend): no_footer scrollbar regression introduced in v1.16.0 (#2076)
* 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.
|
||
|
|
e8c106f06b |
chore(deps-dev): bump webpack-dev-server in /frontend
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.9.3 to 5.2.4. - [Release notes](https://github.com/webpack/webpack-dev-server/releases) - [Changelog](https://github.com/webpack/webpack-dev-server/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.9.3...v5.2.4) --- updated-dependencies: - dependency-name: webpack-dev-server dependency-version: 5.2.4 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
54b7b3fdd4 |
chore(deps-dev): bump postcss from 8.4.14 to 8.5.10 in /frontend
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.14 to 8.5.10. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.5.10) --- updated-dependencies: - dependency-name: postcss dependency-version: 8.5.10 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
a4c5e17bbb |
Probe /auth/status from frontend to avoid 401 on /user (closes #1188) (#1763)
* 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. |
||
|
|
94d1f6e224 |
feat: custom oauth2 provider (#2006)
* feat: add configurable custom OAuth2 provider and icons * fix: reserve built-in custom provider names * fix: add nolint directive for sha1 import * fix: harden custom oauth provider validation |
||
|
|
ba3df171d1 | #2025 Fix typo in Spanish localization for sort-by | ||
|
|
7ec5af8068 |
Fix Firefox dark mode white background on comment iframe (#2023)
* 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. |
||
|
|
fc6f15534e |
fix(frontend): preserve orig verbatim in edit textarea (#2040)
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 |
||
|
|
06436ff9b0 |
Migrate batch 1 components from BEM to CSS Modules (#2014)
* feat: migrate batch 1 components from BEM to CSS Modules Migrate 8 components from BEM to CSS Modules: - button (7 BEM files -> 1 module) - dropdown (7 BEM files -> 1 module) - thread (3 BEM files -> 1 module) - auth-panel (2 BEM files -> 1 module) - dropdown-item, list-comments, subscribe-by-rss, settings (from batch 0 PR #2013) Consolidates 19 BEM CSS files into 8 CSS Module files. Uses clsx for conditional class composition, replacing bem-react-helper's b() calls. Class naming follows the established convention: BEM block = .root, elements = camelCase, modifiers = camelCase. Visual regression verification on built artefacts: - remark.css: 43,779 -> 43,299 bytes (480 bytes smaller) - last-comments.css: 18,792 -> 18,776 bytes (16 bytes smaller) - remark.js: 256,709 -> 304,837 bytes (48KB larger, expected: CSS Module classname mappings now live in JS instead of plain strings) - Dark theme: pixel-identical (zero difference) - Light theme: pixel-identical (0.21% diff is the native demo page "Toggle theme" button, not any remark42 widget element) Also updates CLAUDE.md CSS guideline to reflect the migration status. * Migrate remaining BEM components to CSS Modules (final batch) Migrate the last 4 BEM components to CSS Modules, completing the migration and removing bem-react-helper from the project entirely. Components migrated: - subscribe-by-email (1 BEM CSS file -> 1 module) - comment-form + markdown-toolbar (20 BEM CSS files -> 2 modules) - comment (19 BEM CSS files -> expanded existing module) - root (10 BEM CSS files -> expanded existing module) Consolidates ~50 BEM CSS files into 4 new + 2 expanded CSS Module files. Removes bem-react-helper dependency — all components now use clsx for conditional class composition. Dead CSS cleanup during migration: - Orphaned comment-actions selectors in comment theme CSS (already migrated) - Dead BEM modifiers: comment_disabled, comment_pinned, comment_guest - Dead element: comment__user-id (CSS existed but never used in TSX) - Dead button type classes: comment-form__button_type_preview/_send - Dead mix values: auth-email-login-form__back-button, comment-form__email-dropdown Key implementation details: - comment_highlighting stays global via :global() (imperatively added by classList) - Bare .dark/.light theme class preserved on root wrapper (8+ modules depend on it) - raw-content.css kept as global utility CSS (syntax highlighting) Visual regression verification on built artefacts: - remark.css: 43,779 -> 36,106 bytes (-17.5%) - last-comments.css: 18,792 -> 13,955 bytes (-25.7%) - remark.js: 256,709 -> 253,637 bytes (-1.2%) - last-comments.js: 121,726 -> 120,795 bytes (-0.8%) - Total: 441,006 -> 424,493 bytes (-3.7%) - Screenshot comparison: pixel-identical across light/dark themes |
||
|
|
b888a53759 |
Migrate dropdown-item, list-comments, subscribe-by-rss, and settings from BEM to CSS Modules (#2013)
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) |
||
|
|
c26f45e55e |
Clean up deprecated CSS and fix silent CSS bugs in frontend (#2012)
* 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
|
||
|
|
ab9e6675cf |
fix type check failure in @remark42/api package
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. |
||
|
|
b38d91cb0f |
Merge pull request #2000 from umputun/fix/quick-fixes-1946-1991-1996
Fix email encoding, image cleanup CPU spin, and demo template paths |
||
|
|
d6d53ff2e0 |
Merge pull request #2001 from umputun/fix/admin-edit-frontend-1986
Fix frontend not respecting ADMIN_EDIT config |
||
|
|
195becc6ee |
Merge pull request #2002 from umputun/fix/placeholder-clearing-1990
Clear user placeholder content when comments iframe loads |
||
|
|
283e2c19c7 |
Merge pull request #1994 from umputun/dependabot/npm_and_yarn/frontend/lodash-es-4.17.23
Bump lodash-es from 4.17.21 to 4.17.23 in /frontend |