Commit Graph
3516 Commits
Author SHA1 Message Date
Dmitry VerkhoturovandUmputun 5c0798fe10 docs(claude): document milestone + issue-label conventions
Milestones: one vX.Y.Z per release; decide a PR's release by whether its merge
commit is contained in a release tag (git tag --contains), not by dates; issues
get a milestone only when closed by a code change. Plus the issue-label taxonomy
(type / area / priority / contribution / resolution).
2026-07-11 02:08:40 -05:00
Umputun 76d0cc2cf6 fix(ci): pin go-version to 1.25.12 for GO-2026-5856
setup-go with go-version "1.25" resolved to 1.25.11, which govulncheck flags for
GO-2026-5856 (ECH privacy leak in crypto/tls, fixed in go1.25.12). Pin the exact
patch in ci-backend.yml and release.yml so the vuln scan passes and release
binaries build on the fixed toolchain.
2026-07-11 02:05:58 -05:00
Dmitry VerkhoturovandUmputun 51b6a7e890 Modernise Docker build workflows
ci-build.yml used the legacy actions/cache + /tmp/.buildx-cache local
cache with a manual rotate step. Switch it to buildx type=gha cache
(separate scopes for the main and example images), dropping the
actions/cache and rotate-cache steps.

docker.yml built each image twice per platform — one build-push-action
call per registry. Build once and push the same content-addressed image
to both ghcr.io and DockerHub via multiple outputs; the single build
digest is identical for both registries, so digest export is simplified
accordingly. The multi-arch manifest merge is unchanged.
2026-07-11 02:00:52 -05:00
Dmitry VerkhoturovandUmputun 8c5e82bd16 Drop armv7 build target and remove dead golangci settings
The published multi-arch Docker manifest is amd64+arm64 only, but
GoReleaser and the Makefile dockerx target still built linux/arm/v7
binaries with no matching image. Drop the armv7 target (goarch arm +
goarm 7) from .goreleaser.yml and linux/arm/v7 from the Makefile so
shipped binaries match the images; other platforms are unchanged.

Also remove the goconst and lll settings blocks from
backend/.golangci.yml — neither linter is in the enable list, so the
settings were inert.
2026-07-11 01:57:54 -05:00
Dmitry VerkhoturovandUmputun 95f59213e5 Make notify drop tests deterministic
TestService_WithDrops and TestService_SubmitVerificationWithDrops
submitted three items into a size-1 queue and asserted at least one was
dropped, relying on the single consumer not draining the queue between
submits. synctest does not fully pin this because the MockDest send path
does real logging I/O outside the bubble, so under CI's -race scheduling
the consumer occasionally drained all three, delivering everything and
failing the "<= 2" assertion (~0.2% of CI runs).

Add an optional gate channel to MockDest so a destination blocks in
Send/SendVerification until released. The tests now submit one item (the
consumer picks it up and blocks on the gate), fill the size-1 queue,
submit an overflow item that is dropped, then release the gate — the
drop is deterministic regardless of scheduling. Assert exactly two
delivered instead of "at most two".
2026-07-11 01:56:10 -05:00
Dmitry VerkhoturovandUmputun 503f5cacb0 Add workflow to validate compose files
Compose files were not covered by any CI workflow, so a malformed change
to docker-compose.yml or a compose-*.yml could merge unnoticed. Add a
workflow that runs docker compose config on every tracked compose file
(vendored ones excluded) on changes to any of them.
2026-07-11 01:54:21 -05:00
Dmitry VerkhoturovandUmputun d1f8cf412b Add govulncheck scan to backend CI
Nothing in CI guarded against known vulnerabilities in the Go
dependency tree. Add a vulncheck job that runs govulncheck over the
backend module on every backend change. The version is pinned rather
than tracking latest for reproducible runs. Current tree scans clean.
2026-07-11 01:52:13 -05:00
Dmitry VerkhoturovandUmputun db9d8703ef Fix flaky TestPublic_FindCommentsCtrl_ConsistentCount
The test decided the expected HTTP status with strings.Contains(tc.params,
"=bad"), but comment IDs are random UUIDs. When one started with "bad"
(e.g. offset_id=bad49e60-...), the param string contained "=bad" and the
case was wrongly expected to return 400 while the handler correctly
returned 200, failing the run about 0.2% of the time.

Identify bad-request cases by their error response body instead, which
is deterministic per case and independent of the generated IDs.
2026-07-11 01:50:36 -05:00
Dmitry VerkhoturovandUmputun 8f61ec691b Fix comment-tree pagination under-filling exact-fit subtrees
The limit() boundary used >=, so a subtree that fit the page exactly was
treated as overflow and dropped — under-filling the final page (e.g.
limit=5 over subtrees 3,2 returned only the first subtree, 3 comments,
instead of both). Change it to > so an exact-fit subtree is included;
the first node is still always returned in full and larger subtrees
still overflow to the next page.

Adds table tests for MakeTree's limit/offset pagination and countReplies,
and updates the /find consistent-count expectations for the corrected
boundary.
2026-07-11 01:48:45 -05:00
Dmitry VerkhoturovandUmputun 07f6b9a0a0 Remove obsolete version key from compose files
The top-level version: "2" field is ignored by modern Docker Compose,
which warns about it on every invocation. Drop it from docker-compose.yml
and the compose-dev-backend, compose-dev-frontend and compose-e2e-test
files.
2026-07-11 01:33:06 -05:00
Dmitry VerkhoturovandUmputun 98e4f03091 Run backend CI on PR updates and freeze frontend lockfile installs
ci-backend.yml had pull_request: types: [opened, reopened], which
excludes synchronize, so pushes to an open PR branch did not re-run
backend tests, lint or coverage and a broken follow-up commit could land
after the first green run. Drop the types filter so all default
pull_request events trigger the workflow.

ci-frontend.yml and ci-frontend-api.yml now install with
pnpm install --frozen-lockfile instead of pnpm i, matching release.yml
and preventing silent lockfile drift in CI.
2026-07-11 01:31:14 -05:00
Dmitry VerkhoturovandUmputun f8f2becb4b Fix dropped notification errors and switch to errors.Join
notify/email.go accumulated multi-recipient errors with
multierror.Append(fmt.Errorf(...)) instead of
multierror.Append(result, ...), so the accumulator was overwritten each
iteration and only the last failing recipient's error survived; earlier
failures were silently dropped. The telegram notifier did it correctly.

Replace hashicorp/go-multierror with the stdlib errors.Join everywhere
it was used (notify/email.go, notify/telegram.go, rest/api/rest_private.go,
store/service/service.go, store/image/image.go and store/engine/bolt.go),
which fixes the bug and drops the direct dependency. It stays indirect
because go-pkgz/lcw/v2 still imports it. A regression test in
email_test.go now sends two failing recipients and asserts both errors
are reported.
2026-07-11 01:28:31 -05:00
Umputun 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.
backend/v1.16.4 v1.16.4
2026-07-09 22:10:48 -05:00
Umputun 3e63d72852 fix(ci): build docker images on frontend-only master pushes
the docker workflow chained off the backend workflow only, and backend has a
backend/** path filter. master pushes touching just frontend/apps or the docker
files never triggered docker.yml, so no master image was published and
remark42.com was not redeployed. broken since the build workflow was split in
#1977.

listen to workflow_run from both backend and frontend, and add Dockerfile,
docker-init.sh and .dockerignore to the backend workflow paths to restore the
path coverage the old build workflow had.
2026-07-09 20:03:26 -05:00
Dmitry VerkhoturovandUmputun 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.
backend/v1.16.3 v1.16.3
2026-07-09 17:28:40 -05:00
Dmitry VerkhoturovandUmputun e62b3c830d fix(trusted-proxy): warn on catch-all, cover more cases, trim wording
Follow-up to the review notes on #2116:
- warn at startup when --trusted-proxy contains a catch-all (0.0.0.0/0 or ::/0),
  which trusts every peer and re-opens the bypass - mirrors the unset-case warning
- realIPMiddleware tests: cover the unparseable-peer and trusted-peer-without-header
  branches, and make the observed values per-call so subtests don't share closure locals
- trim the flag description and shorten the startup warning to the terse [WARN] style
backend/v1.16.2 v1.16.2
2026-07-09 15:05:05 -05:00
Dmitry VerkhoturovandUmputun b1502801fa fix: add --trusted-proxy to gate client-IP forwarding headers
Rate limiting and (with --votes-ip) vote de-duplication key on the client IP,
recovered from forwarding headers (X-Real-IP / X-Forwarded-For / CF-Connecting-IP)
when behind a reverse proxy. Those headers were accepted from any client, so a
caller could set them to change its apparent IP.

Add --trusted-proxy / TRUSTED_PROXY (comma-separated CIDR/IP): forwarding headers
are honored only when the direct peer is a trusted proxy; other peers keep their
real socket address. Unset preserves the previous trust-all behavior (with a
startup warning) so existing deployments keep working on upgrade.

Docs: a 'Trusted proxies and client IP' section with per-topology guidance, plus a
note in the nginx manual.
2026-07-05 17:47:19 -05:00
Dmitry VerkhoturovandUmputun 2e3a680ca4 fix(deleteme): surface real avatar-store errors, tolerate only not-found
Bumps go-pkgz/auth to v2.1.5, which adds avatar.ErrNotFound. deleteMeRequestCtrl's
avatar removal was best-effort (log and continue on any error) because before the
sentinel there was no portable way to tell an already-removed avatar from a genuine
failure. It now tolerates only errors.Is(err, avatar.ErrNotFound) - keeping the
repeated-request idempotency - and surfaces any other store failure as 500.
2026-07-05 17:28:01 -05:00
Dmitry VerkhoturovandUmputun d8b7f7530c fix: remove user avatar on deleteme request
The delete_me token built by deleteMeCtrl omitted the user's Picture, so the
avatar-removal branch in deleteMeRequestCtrl never ran for real requests and
avatars survived account deletion. Carry Picture in the token so the stored
avatar is removed when the request is processed.

Make the removal best-effort: the avatar stores report an already-missing
avatar as an error with no distinguishable sentinel, and the user's data is
already deleted at that point, so a missing avatar (e.g. a repeated request)
no longer fails the whole deletion with a 400.

Only remove a well-formed avatar id ("<hash>.image") so a malformed picture
can't make a filesystem-backed store target an unexpected path.
2026-07-03 15:40:31 -05:00
Dmitry VerkhoturovandUmputun b33025a76f feat(api): adopt enforcing rest.Timeout, drop local cooperative timeout
go-pkgz/rest v1.22.0 ships an enforcing Timeout middleware (net/http.TimeoutHandler
style): it runs the handler with a deadline and returns 504 at the deadline even if
the handler ignores the context - unlike the local cooperative timeout, which only
cancelled the context and never actually stopped a stuck handler.

Replace the local timeout with rest.Timeout on every route with a bounded response.
The streaming and long-polling routes are deliberately left without it, since the
enforcing timeout buffers the whole response in memory and aborts at the deadline:
- GET /api/v1/userdata and GET /api/v1/admin/export stream gzipped exports
- GET /api/v1/admin/wait long-polls for up to 15m
- POST /api/v1/admin/import[/form] and /remap ingest large uploads

Delete the local timeout middleware and its test; the enforcing behaviour is covered
by go-pkgz/rest. TestRouteTimeout locks the enforcing-vs-exempt contract in this build.
2026-07-03 15:40:10 -05:00
Dmitry VerkhoturovandUmputun c48254a994 chore(deps): bump go-pkgz/rest to v1.22.0, drop local CORS Vary workaround
v1.22.0 includes the preflight Vary fix (https://github.com/go-pkgz/rest/pull/44):
rest.CORS now adds Vary: Access-Control-Request-Method and
Access-Control-Request-Headers on preflight itself, making the local wrapper
that added them redundant. corsMiddleware now returns rest.CORS directly;
TestCorsMiddleware still asserts those preflight Vary headers, now supplied
upstream.

Also tidies the _example/memory_store module for the new version.
2026-07-03 15:40:10 -05:00
Dmitry VerkhoturovandUmputun 3fc5d6b970 fix: make user deletion idempotent for users without comments
deleteUser now succeeds for a user who has no comments (e.g. one who only logged
in) instead of failing on the missing per-user bucket. In hard mode the per-user
bucket is deleted, tolerating bbolt's ErrBucketNotFound so a bucket left behind by
an earlier partial removal is still removed; the comment-deletion failure path now
wraps the actual error.

Because the engine cannot distinguish a valid login-only user from a never-existed
one, deletion is idempotent: /admin/deleteme returns 200 for an unknown (but validly
signed) token rather than 400. The deleteme test is updated to this contract, engine
tests cover hard and soft deletion of login-only and unknown users, and the API docs
note the idempotent behaviour.
2026-07-01 15:05:27 -05:00
Fredrik AppelrosandUmputun 380aa3c828 Allow deleteUser to be called on users with no comments 2026-07-01 15:05:27 -05:00
Fredrik AppelrosandUmputun b6bc8ba675 Fix error handling in deleteUser function to return the correct error when deleting a user bucket. 2026-07-01 15:05:27 -05:00
Dmitry VerkhoturovandGitHub c5121fd402 refactor(api): replace go-chi/chi router with go-pkgz/routegroup (#2103)
Migrate the REST router off go-chi/chi onto go-pkgz/routegroup (backed by the
stdlib http.ServeMux), removing the last use of go-chi from the backend:

- rest.go routes() builds the tree with routegroup (Mount/Group/Route/With) and
  net/http method+path patterns instead of chi's Get/Post/Route/Mount helpers
- chi.URLParam(...) -> r.PathValue(...) in the admin, public and private handlers
- rest_public_test.go loadPictureCtrl test uses routegroup + http.ServeMux
- rest_test.go: add TestRest_FileServerStaticAssets (bare /web -> /web/ redirect,
  cache headers, 404, directory-listing block) and update the path-traversal test
  for ServeMux normalising a literal ".." (encoded traversal is still rejected
  by the handler)
- drop go-chi/chi from go.mod, go.sum and vendor; update the CLAUDE.md reference
2026-07-01 15:04:34 -05:00
Dmitry VerkhoturovandUmputun 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.
2026-06-30 18:23:21 -05:00
Eugene OrlovandUmputun 406df022ba fix: ui error when no auth providers configured 2026-06-30 18:23:21 -05:00
Dmitry VerkhoturovandUmputun 6840a46ac9 Replace go-chi/cors with go-pkgz/rest CORS
Swap the go-chi/cors middleware for rest.CORS (already a dependency),
removing the go-chi/cors module entirely. Behaviour-preserving:
- with AllowedOrigins "*" and credentials enabled, both reflect the request
  Origin into Access-Control-Allow-Origin (a literal "*" is invalid with
  credentials)
- preflight responses also vary on Access-Control-Request-Method/-Headers, not
  just Origin, matching go-chi/cors so caches don't reuse a preflight response
  across different requests

Extract the config into corsMiddleware() in middleware.go and add
TestCorsMiddleware covering origin reflection, credentials, preflight
methods/headers/max-age/Vary, and the no-Origin case. go-chi/cors dropped
from go.mod; the chi router stays until the router migration. go test -race,
vet, golangci-lint, govulncheck clean.
2026-06-30 17:40:03 -05:00
Dmitry VerkhoturovandUmputun 6a50ffd88a Use stdlib http.ServeMux instead of chi in cleanup_test
The cleanup command test builds a self-contained mock HTTP server with
only static routes (and {id} patterns read via r.URL.Path, not URLParam),
so chi.NewRouter is unnecessary — http.NewServeMux (Go 1.22 routing) covers
it. Independent of the main router; drops the chi import from app/cmd.

go test -race and golangci-lint clean.
2026-06-30 17:39:37 -05:00
Dmitry VerkhoturovandUmputun f7dbdae26c Consolidate request middlewares into middleware.go
Pure relocation, no behaviour change: gather all request-scoped middlewares
and their tests into dedicated files instead of scattering them across
rest.go and ssl.go.

  funcs -> app/rest/api/middleware.go:
    timeout (from ssl.go); rejectAnonUser, matchSiteID, cacheControl,
    apiCSPMiddleware, securityHeadersMiddleware, subscribersOnly,
    validEmailAuth, rateLimiter (from rest.go)
  tests -> app/rest/api/middleware_test.go:
    TestTimeout (from ssl_test.go); TestRest_rejectAnonUser,
    TestRest_cacheControl, TestRest_apiCSP, TestRest_securityHeaders,
    TestRest_subscribersOnly, Test_validEmailAuth, TestRest_matchSiteID
    (from rest_test.go)

go test -race, vet, golangci-lint and govulncheck clean; example builds.
2026-06-30 17:15:39 -05:00
Dmitry VerkhoturovandUmputun f4b236c66a Replace chi middleware.Timeout with the timeout helper, drop chi/middleware
middleware.Timeout was the last use of go-chi/chi/v5/middleware (RealIP, the
other user, landed in #2099). Swap it for the local timeout helper (context
deadline + 504 on deadline, matching chi exactly; covered by TestTimeout),
which removes the go-chi/chi/v5/middleware package from the vendor tree.

The go-chi/chi module stays in go.mod — the router (chi.NewRouter etc.) still
uses it, so go.mod only shrinks after the router migration. Build, vet, race
tests and golangci-lint clean.
2026-06-30 17:15:04 -05:00
Dmitry VerkhoturovandUmputun 17365f4304 Replace chi middleware.RealIP with rest.RealIP on the main router
Behaviour-preserving swap: rest.RealIP sets r.RemoteAddr from
X-Real-IP / X-Forwarded-For like chi's middleware.RealIP, removing chi from
the RealIP path without changing the trust model (GHSA-56x6-q882-mf27 stays
present, to be fixed separately). chi/middleware stays imported for Timeout;
whichever of this PR and the Timeout PR (#2097) merges last drops the import.
Drop-in to a tested rest middleware; covered by existing api router tests.
2026-06-30 16:38:12 -05:00
Dmitry VerkhoturovandUmputun 0b6eea68a1 Replace chi middleware.NoCache with rest.NoCache on the main router
go-pkgz/rest.NoCache is borrowed from chi's middleware.NoCache and behaves
identically: same no-cache response headers and the same stripping of
conditional request headers (If-None-Match etc.), which the image-proxy
etag logic relies on. Drop-in swap, chi router left in place.

chi/middleware stays imported for RealIP and Timeout. Build, vet, race
tests and golangci-lint clean.
2026-06-30 16:27:01 -05:00
Dmitry VerkhoturovandUmputun b19e6269c1 Replace chi middleware.Throttle with rest.Throttle on the main router
Drop-in swap of the global concurrency limiter (go-pkgz/rest.Throttle has
the same signature and semantics as chi's middleware.Throttle), with the
chi router left in place. First of the per-middleware swaps that chip away
at go-chi/chi/v5/middleware before the router itself is migrated.

chi/middleware is still imported for RealIP/Timeout/NoCache; build, vet,
race tests and golangci-lint clean.
2026-06-30 16:26:28 -05:00
Dmitry VerkhoturovandUmputun bb6d1450f1 Migrate ssl.go TLS routers from chi to routegroup
First step of the go-chi -> go-pkgz/routegroup migration. The HTTP->HTTPS
redirect and ACME http-01 challenge routers are small, self-contained
http.Handlers separate from the main API router, so they move cleanly:

- chi.NewRouter() -> routegroup.New(http.NewServeMux())
- middleware.Throttle -> rest.Throttle (same concurrency-limit semantics)
- middleware.Timeout -> local timeout helper (context deadline, mirrors chi)
- drop middleware.RealIP: these routers do redirect/challenge only, with no
  per-IP logic, so the spoofable header trust is simply removed here
- return http.Handler instead of chi.Router (callers already take http.Handler)

chi stays a dependency (still used by the main API router); this only removes
its use from ssl.go. go test -race, vet, golangci-lint and govulncheck clean.
2026-06-30 16:26:03 -05:00
dependabot[bot]andUmputun 8318f89dde chore(deps): bump the github-actions-updates group across 1 directory with 4 updates
Bumps the github-actions-updates group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/cache](https://github.com/actions/cache), [pnpm/action-setup](https://github.com/pnpm/action-setup) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `actions/cache` from 5 to 6
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

Updates `pnpm/action-setup` from 6.0.4 to 6.0.9
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v6.0.4...v6.0.9)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-updates
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-updates
- dependency-name: pnpm/action-setup
  dependency-version: 6.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-updates
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-30 15:57:05 -05:00
Dmitry VerkhoturovandUmputun 3e18681ca7 Sanitize comment text in email notifications (GHSA-74pc-3r2m-ppx3)
Email notification templates rendered the comment HTML via text/template,
so the store-level UGC sanitizer's permitted <a> and <img> tags reached
the email body verbatim. An authenticated user could plant phishing links
and remote tracking pixels in notification emails sent from the legitimate
remark42 address.

Switch notify to html/template (auto-escaping every non-HTML field) and
add a stricter email-only bluemonday policy that drops <a> and <img> while
keeping basic text formatting; the sanitized comment HTML is passed as
template.HTML. Add regression tests asserting links and images are stripped
while anchor text and formatting survive.
2026-06-30 15:56:36 -05:00
UmputunandGitHub 11d8a978a2 Merge pull request #2094 from umputun/fix/eleventy-outputpath-guard
Guard against falsy outputPath in eleventy htmlmin transform
2026-06-30 15:56:08 -05:00
Dmitry Verkhoturov d7fe27cb97 Guard against falsy outputPath in eleventy htmlmin transform
Eleventy passes a falsy outputPath to transforms for templates rendered
without a written file (e.g. permalink: false); calling .endsWith on it
would throw. Skip minification in that case instead. Pre-existing latent
issue surfaced by Copilot review on #2091.
2026-06-30 20:59:57 +01:00
UmputunandGitHub 7fee12a978 Merge pull request #2091 from umputun/deps/update-frontend
Update frontend and site dependencies to latest, bump pnpm to 10, clear audit alerts
2026-06-30 14:23:48 -05:00
Dmitry Verkhoturov 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.
2026-06-30 20:11:53 +01:00
Dmitry Verkhoturov 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.
2026-06-30 20:10:43 +01:00
Dmitry Verkhoturov 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.
2026-06-30 20:05:42 +01:00
Dmitry Verkhoturov 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'.
2026-06-30 19:53:10 +01:00
Dmitry Verkhoturov d274724c08 Update site dependencies and clear all yarn audit alerts
yarn audit: 0 vulnerabilities (was 52 findings). Site builds via eleventy +
tailwind on node 20.

Direct bumps: markdown-it 14.2, cross-env 10, date-fns 4.4, prettier 3.9,
@tailwindcss/typography 0.5.20, @11ty/eleventy-plugin-syntaxhighlight 5.0.2.
Replaced abandoned html-minifier (unpatched ReDoS, no fix released) with the
maintained html-minifier-terser fork; the .eleventy.js htmlmin transform is now
async. Transitive vulns patched via yarn resolutions. js-yaml resolves to 3.15.0
(3.x backport) which keeps gray-matter working.

Held: tailwindcss 3.4 (tailwind 4 is a config rewrite) and @11ty/eleventy 2
(eleventy 3 is an ESM migration) - both invasive.

Build output verified against a clean master build: every HTML page differs only
by the build-time ?v= cache-bust query; style.css differs only by an equivalent
refactor of @tailwindcss/typography's prose kbd-shadow variables (same rendered
result). Functionally identical.
2026-06-30 19:47:25 +01:00
Dmitry Verkhoturov 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).
2026-06-30 19:47:25 +01:00
UmputunandGitHub c8832e708c Merge pull request #2088 from umputun/deps/update-backend
Update backend dependencies to latest
2026-06-30 12:41:13 -05:00
Dmitry Verkhoturov 07c7926453 Update backend dependencies to latest
Update all Go modules in backend/ and backend/_example/memory_store/ to
their latest versions (chroma 2.27, go-redis 9.21, bbolt 1.5, slack 0.27,
golang.org/x/* and others); re-tidy and re-vendor, keep the example module
in sync.

Hold github.com/go-chi/chi/v5 at v5.2.5: v5.3.0 deprecates
middleware.RealIP (IP-spoofing advisories). Switching off RealIP changes
how the client IP is derived for rate limiting and votes, which is a
security decision better made on its own rather than inside a dependency
bump.

go test -race, go vet, golangci-lint and govulncheck all clean on both
modules.
2026-06-30 18:35:31 +01:00
UmputunandGitHub 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
2026-06-01 22:10:26 -05:00
UmputunandGitHub 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
2026-06-01 22:10:21 -05:00