)]}'
{
  "log": [
    {
      "commit": "6744295fa888e0621fc9f9a8ace04f1a4daf8554",
      "tree": "35103ef54f36df4c7c4f84de2c5e63fa64bb1221",
      "parents": [
        "c1669e17d2ad9ce6f7ddc5aacae2f3991f22a850"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 22 15:53:14 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 22 15:53:14 2026 +0800"
      },
      "message": "chore: update version to 2.0.3 and adjust prerequisites in README\n"
    },
    {
      "commit": "c1669e17d2ad9ce6f7ddc5aacae2f3991f22a850",
      "tree": "a0fae5d048408fcc35e438263acca0c3d771bc67",
      "parents": [
        "49c9bad8aa7c31712575ea06d6653eedffded7a7",
        "736dde0eed9b4067f0958fa7816bece99f7aa9cd"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 22 15:49:07 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 22 15:49:07 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/dev\u0027 into test\n"
    },
    {
      "commit": "49c9bad8aa7c31712575ea06d6653eedffded7a7",
      "tree": "e9806de26cc4abd94975b6c4ba163103b4a79b89",
      "parents": [
        "d70b5cd4647476e8a04b0ca8abbaaf5f802b57c7",
        "22867b411c1a802f0a8f9dae79a07ab0289c37fd"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 22 15:48:59 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 22 15:48:59 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/fix/2.0.3/user\u0027 into test\n"
    },
    {
      "commit": "736dde0eed9b4067f0958fa7816bece99f7aa9cd",
      "tree": "aaa4b93fadc4279e0f18310f6fe4bdc8d489c2a5",
      "parents": [
        "ace02c499a0ccf64f05e99185d7e6dcfd4aead06"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Fri Sep 18 17:48:25 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 18 17:48:25 2026 +0800"
      },
      "message": "build: replace Create React App with Vite (#1616)\n\nFixes #1580. Part of #1578. Step 2 of 3, atomic. Depends on\nhttps://github.com/apache/answer/pull/1582 (the Go asset contract, now\nmerged into `dev`), which is why the server-side parsing does not appear\nin this diff.\n\nReplaces `react-scripts` and `react-app-rewired` with Vite.\n\nThe configuration preserves the output contracts the server depends on:\nthe build directory stays at `ui/build`, which `ui/static.go` embeds,\nand emitted assets stay under `static/`, which `internal/router/ui.go`\nserves as a route. Files keep the `static/js`, `static/css` and\n`static/media` grouping, which the `analyze` script matches on; the\nignore rules for `ui/build`, previously pinned to that layout\u0027s\ndirectory depth, now ignore everything under `build` except the tracked\nfavicon. Production sourcemaps stay on, and the `REACT_APP_` prefix is\nretained so `ui/scripts/env.js` remains the single source of truth for\nconfiguration shared with the server. That same script\u0027s `public_url`\nvalue now also drives Vite\u0027s `base` configuration, and the manifest link\nin `ui/index.html` uses Vite\u0027s `%BASE_URL%` macro instead of a hardcoded\npath, so a non-root deployment keeps every asset and manifest reference\nprefixed the way it did under the previous toolchain\u0027s `PUBLIC_URL`\nwiring.\n\nAgentic tooling did the mechanical work in this series; every change was\nreviewed by a human before being committed.\n\n### The one server-side line\n\n`header.html` re-emitted the paths `GetStyle()` finds as classic\nscripts. An ES module loaded through a classic script tag fails on its\nfirst import, so the tag is now declared as a module and carries\n`crossorigin`, matching the tag Vite\u0027s own build emits for the\nclient-rendered entry point. Rewriting the built `index.html` instead\nwas not an option, because `internal/router/ui.go` serves that same file\nto boot the SPA and it cannot misdeclare its own script type. A\n`type\u003dmodule` script fetches in CORS mode, so any CDN origin serving\nthese files needs to send the matching CORS headers, with or without\n`crossorigin` present; default same-origin deployments are unaffected.\nThe note for CDN users is in the CDN plugin READMEs,\napache/answer-plugins#326, per the placement decided on #1567, and the\nrelease notes for the version that ships this should carry a short\npointer to it.\n\n### Route code splitting\n\nRoutes loaded pages with `` lazy(() \u003d\u003e import(`@/pages/${pagePath}`))\n``. That shape cannot be statically analyzed, so no page received its\nown chunk and the specifier reached the browser untransformed, leaving\nevery lazily routed page unable to load. Pages are now enumerated with a\nbounded glob covering the three directory depths routes actually use,\nexcluding component subtrees so their own index files do not become\nroute chunks. A page path with no matching module now rejects with the\nrequested path and the list of known keys, surfacing through the\nexisting route error boundary.\n\n### Behaviour changes, called out deliberately\n\n**The custom stylesheet link now derives its href from the build\u0027s own\nbase, not a separately computed value.** `PageTags` built the\n`/custom.css` link from `process.env.PUBLIC_URL`, which the previous\ntoolchain exposed with its trailing slash already stripped; at the\ndefault configuration that value was an empty string, resolving to\n`/custom.css`. `import.meta.env.BASE_URL`, the direct equivalent under\nthe new toolchain, keeps the trailing slash, so substituting it in the\nsame place would resolve the same default configuration to\n`//custom.css` instead. The trailing slash is stripped explicitly before\nthe substitution, reproducing the previous output at the default\nconfiguration and staying correct away from it. The output here is\nunchanged; only the mechanism it depends on is.\n\n**Two routes were dead ends and now fail loudly instead of silently.**\n`pages/403` and `pages/Admin/UserOverview` both referenced modules that\ndid not exist in the tree, and both failed the same way under the\nprevious toolchain, silently rendering blank. With the glob above, both\nnow report the missing path through the route error boundary. Step 3\nrepoints `pages/403` at `pages/404/403`, an existing component, so that\nroute renders; `pages/Admin/UserOverview` stays a visible gap because\ncreating the missing page is a content decision. Neither is a\nregression.\n\n**The markdown editor now renders its themed background.**\n`src/components/Editor/index.scss` read `var(-bs-body-bg)` with a single\nleading dash. That is not a valid custom property reference, so the\ndeclaration was discarded and the editor never received the background\nit asks for. The previous CSS minifier accepted the invalid value; the\ncurrent one rejects it outright and fails the build (`[lightningcss\nminify] Unexpected token Ident(\"-bs-body-bg\")`), which is how it\nsurfaced and why the one-line fix is part of this PR rather than a\nfollow-up. Fixing it changes rendering.\n\n### Dependency removals\n\n`react-scripts`, `react-app-rewired`, `customize-cra` and\n`config-overrides.js` are gone, and `yaml-loader` is replaced by the\nequivalent Vite plugin, pinned to the schema the previous loader used so\nbare dates and merge keys keep parsing the same way they did before. The\nscaffold test the old toolchain generated (`App.test.tsx`) and its jest\npackages go with it; this project has no test runner and no unit tests,\nbefore or after.\n\nThree removed packages were already inert before this migration. Both\npurgecss packages were declared but wired nowhere: no postcss config\nexists, the overrides file never referenced them, and no script invoked\nthem. `buffer` was aliased and provided as a global, but no application\nsource uses it, and the one dependency requiring it declares `buffer:\nfalse` in its own browser field.\n\n`sass` and `@types/node` are raised to the versions the toolchain\nrequires; the previous `sass` predates the async compiler API it now\ncalls. `sass` is pinned below the release that begins deprecating\n`@import`, which this project uses across 30 files. Migrating those to\n`@use` is a separate concern. Bootstrap\u0027s own Sass internals print\ndozens of dependency deprecation warnings on every build, unrelated to\nanything in this project\u0027s own styles; `vite.config.mts` sets\n`css.preprocessorOptions.scss.quietDeps: true` to silence those\nspecifically while still surfacing warnings from this project\u0027s own\nstylesheets. One such app-own warning remains in this PR\u0027s build output,\na mixed-declarations notice from `Comment/index.scss`; the one-line\nreorder that clears it is a step 3 nit.\n\nThe eslint config no longer extends `react-app/jest`, which shipped\ninside `react-scripts` and configured rules for a test suite this\nproject does not have.\n\n### A failure found only by running the built application\n\nWith the build green and the dev server working, the built application\ndid not boot. React never mounted, the page showed its loading spinner\nindefinitely, and the browser console was empty.\n\n`i18next` attaches its resource-store methods to the instance inside\n`init()`. The builtin plugins register their translations while their\nmodules are being evaluated. Whether that happens before or after `init`\ndepends on how the bundler groups and orders chunks, so the previously\nworking order was incidental rather than guaranteed. When it inverts,\nthe registration throws while the entry module is still evaluating,\nwhich takes the application down before it mounts and produces no\nconsole output.\n\nRegistration now happens immediately only when there is an initialised\ninstance to register into, and otherwise falls to the `initialized`\nhandler the code already installed, which is correct in either order.\nThe check that guards both orders is part of step 3.\n\n### Parity fixes found in review\n\n**Bootstrap icon fonts.** The bootstrap-icons stylesheet points at font\nfiles under a path the bundler could not resolve on the first migration\npass, so the build silently emitted zero font files and every icon\nrendered as a missing-glyph box. The stylesheet now overrides the\npackage\u0027s font-directory variable to a path Vite can resolve; the fonts\nare emitted, and the boot test below confirms they are served.\n\n**Type checking.** `pnpm build` now also runs `tsc --noEmit` before the\nbundler runs, and is clean today. The previous toolchain ran its checker\nin the dev server (blocking) and during builds (downgraded to warnings\nby this project\u0027s `TSC_COMPILE_ON_ERROR` setting); none of that carried\nover when the bundler changed, so until this fix a type error shipped\nwith no signal at all.\n\n**Yaml parsing.** The Vite yaml plugin defaults to js-yaml\u0027s more\npermissive schema, which resolves bare dates to JS `Date` objects and\nenables merge keys; the previous loader\u0027s schema kept both as plain\nstrings. The plugin is now pinned to that same schema.\n\n**Declared Node range.** The bundler\u0027s declared support range is\n`^20.19.0 || \u003e\u003d22.12.0`. `ui/package.json`\u0027s own `engines.node` allowed\n`\u003e\u003d20`, which admits versions below that floor, and now matches it\nexactly. The release workflow\u0027s pinned Node, which also sat below the\nfloor, is raised to `20.19.0` to match. That version bump is the only\nchange this PR makes anywhere under `.github/`; it does not add a job.\n\n**Typed environment variables.** `import.meta.env.REACT_APP_*`\npreviously typed as `any`, since no `ImportMetaEnv` augmentation\nexisted; a typo\u0027d key would compile clean and only surface as a missing\nvalue at runtime. The two keys the app reads this way,\n`REACT_APP_API_URL` and `REACT_APP_BASE_URL`, are now declared, with\n`strictImportMetaEnv` enabled so an undeclared key is a type error\ninstead of a silent `any`.\n\n### Verification\n\nOn `dev` at `ace02c49`, which includes step 1, plus these commits: `pnpm\ninstall --frozen-lockfile` under the pinned `pnpm@9.7.0` and `pnpm\nbuild` (which now includes `tsc --noEmit`) complete; the build prints\nthree warnings, each once: `front-matter`, a dependency unrelated to\nthis project\u0027s own pinned `js-yaml@^4.1.0`, pulls in a legacy\n`js-yaml@3.x` copy whose `buffer` import gets externalized for browser\ncompatibility; the Sass mixed-declarations notice from\n`Comment/index.scss` mentioned above; and one chunk over the default\nsize threshold, see the note on chunking below. `go build ./...` and `go\nvet ./...` pass; `TestGetStyleResolvesBuiltAssets` passes against the\nVite output; a search for `react-scripts`, `react-app-rewired`,\n`customize-cra` and `config-overrides` finds nothing outside the\nlockfile. The built binary was booted against sqlite3 and loaded in a\nbrowser: `/` renders with the client mounted (React\u0027s fiber container\npresent on the root element), the module entry script and both entry\nstylesheets are fetched, and the console shows no errors. The same holds\nfor `/tags`. The server-rendered `/` response itself carries the module\nentry script and both entry stylesheets, which is `GetStyle()` from step\n1 finding them in the Vite output and `header.html` re-emitting them.\n\nThe three `make check-ui` guards from #1567 (asset paths, non-default\nlocale, plugin i18n order) are not in this PR; they arrive as step 3 and\nwere run green against the equivalent tree there.\n\n### Measurements\n\nCaptured for #1567 at its head `d06b623`, against `main` at `3b9f137`,\nsame machine, same Node and package manager versions, clean tree and\nclean install on both sides, five runs per timing metric. They are\ncarried over rather than recaptured: the frontend build inputs here are\nthe same as at that head, minus the step 3 nits and plus the newer\nlocale files on `dev`.\n\n| Metric | Before | After |\n|---|---|---|\n| Cold production build | 19.87s | 4.77s |\n| Warm build | 8.75s | 4.87s |\n| Dev server time to ready | 7104ms | 359ms |\n| HMR latency | 421ms | 139ms |\n| Bundle JS, raw / gzip | 3477.60KB / 1198.75KB | 3010.49KB / 1035.57KB\n|\n| Bundle total, raw / gzip | 4290.39KB / 1652.24KB | 3763.97KB /\n1474.34KB |\n| Direct dependencies | 75 | 65 |\n| Packages installed | 1578 | 689 |\n| Audit findings, critical/high/moderate/low | 4/78/63/13 | 1/50/38/6 |\n\nFive things worth stating rather than leaving to be inferred:\n\n- Cold and warm builds are within noise of each other, because the new\nbuild has no meaningful persistent cache to warm and now also runs a\ntype check on every build, cold or warm alike. The old toolchain had a\nreal warm cache, which is why its two figures differ so much. The\ncomparison to draw is cold against cold.\n- Dev server time-to-ready is wall clock on both sides, including\nprocess spawn. The new tool self-reports a much smaller number that\nexcludes that, and using it would compare two different quantities.\n- Chunk boundaries differ structurally between the two bundlers, so the\nbundle rows compare total shipped bytes rather than like-for-like\nchunks.\n- HMR latency was measured once, at commit 7200ca3 on the #1567 branch,\non both toolchains, and carried forward from there: none of the commits\nsince touch the hot-update path.\n- The bundle totals include the bootstrap-icons font files the first\nmigration pass had silently dropped, and the cold build includes the\nrestored type check. Both are named costs of parity fixes, already\nfolded into the deltas.\n\n### Not included\n\nThis PR adds no CI job for the frontend. The project runs no frontend\njob today, and a build on every push is a cost a maintainer should\nchoose to take on, not one this migration should impose. One constraint\ncarries forward for whoever wires that job later: a bare `go test ./...`\nreports ok while asserting nothing about the built asset paths, because\n`TestGetStyleResolvesBuiltAssets` skips when no frontend build is\nembedded. Any future CI job needs to build the frontend first.\n\nThe dev server now binds to loopback only by default; reaching it from\nanother device on the network needs an explicit `--host` flag.\n\nCreate React App\u0027s SVG-as-component imports (`import { ReactComponent as\nX } from \u0027./x.svg\u0027`) are not carried over to this configuration. Nothing\nin this codebase used them.\n\nThe commits carry `(cherry picked from commit ...)` lines pointing at\nthe branch behind #1567, where these changes were first reviewed and\nwhere the regression matrix (subdirectory deploy, OAuth callbacks,\nabsolute CDN `public_url`) was run."
    },
    {
      "commit": "ace02c499a0ccf64f05e99185d7e6dcfd4aead06",
      "tree": "dcb4b4c8c70cfe6f8fc3c54ac78956aaf47dde5d",
      "parents": [
        "69fc3c96c3800f673f9ad1d2b0c7d32105c86b9f"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Wed Aug 19 11:59:30 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 15:29:37 2026 +0800"
      },
      "message": "chore: align asset-check comments with the DOM-tolerant parser\n\nThe GetStyle comment framed the DOM walk around attribute order,\nattribute set, and quoting, the same properties the old regex parser\ndepended on, without stating that the new parser ignores all of\nthem. check-built-assets.sh described its self-check as failing when\nasset tags change shape, but the fixtures test a missing script or\nstylesheet tag, and the parser accepts any shape as long as the tag\nis present.\n\nComments now state the actual constraint: tag shape does not affect\nparsing, and the guarding check fails when a build is present but a\nrequired script or stylesheet tag is missing from it.\n\n(cherry picked from commit 3bc12e80a659a08dbe72c840a60b25c8c0f90963, internal/controller/template_controller.go only)\n"
    },
    {
      "commit": "69fc3c96c3800f673f9ad1d2b0c7d32105c86b9f",
      "tree": "cad86c604a1bcfa3e32462e9f3551d111f7f62ff",
      "parents": [
        "c7b37a152b50c03836d533844c70be25a4c83ffe"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Sat Aug 01 21:09:52 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 15:29:37 2026 +0800"
      },
      "message": "test: assert every declared stylesheet is parsed, not just one\n\nAsserting that the parsed stylesheet list is non-empty leaves the exact\nregression this repository already hit uncovered: a parser that stops at\nthe first stylesheet returns a one element list, satisfies every existing\nassertion, and silently drops the rest of the page\u0027s CSS.\n\nCount the declarations again by a cruder method than the parser uses and\nrequire the two to agree, so the parser has to be checked against\nsomething other than itself. The count is a lower bound: a build that\nquotes attributes differently drives it to zero and it stops constraining,\nwhich is why it supplements the shape-independent assertions rather than\nreplacing them.\n\nVerified by reintroducing the truncation and watching this fail.\n\n(cherry picked from commit 9232cbd1fa7ef0a19e053474acf7c5468af43e74)\n"
    },
    {
      "commit": "c7b37a152b50c03836d533844c70be25a4c83ffe",
      "tree": "f0384e56f8f978217257f4679ae0fb06acb6d1e7",
      "parents": [
        "88c5ef666df0749b2c1a24d464047d48430c6a58"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Sat Aug 01 20:40:39 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 15:29:37 2026 +0800"
      },
      "message": "style: iterate the rel attribute tokens without building a slice\n\nThe linter configured for this repository flags the slice-building form,\nso make lint fails on it.\n\n(cherry picked from commit 1cfd5daf81bfe0823f497c27fea43297b15b18c9)\n"
    },
    {
      "commit": "88c5ef666df0749b2c1a24d464047d48430c6a58",
      "tree": "dfa16baf8faaea399783453c6650917bd77baa13",
      "parents": [
        "791b5f743f08a78cb1608c580c3266f7ba8fe059"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Sat Aug 01 19:44:13 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 15:29:37 2026 +0800"
      },
      "message": "fix: render every entry stylesheet, not only the first\n\nGetStyle returned a single stylesheet path because the previous build emitted\nexactly one. The current build emits two, so server-rendered pages loaded\npartially unstyled while every build step still reported success.\n\nThe stylesheet is now a list, mirroring how script paths are already collected\nand prefixed, and the template renders one link per entry.\n\nThis is the same assumption as the tag-shape one fixed earlier: the server\nencoded a property of one bundler\u0027s output, here that there is exactly one\nentry stylesheet.\n\n(cherry picked from commit d66e21b252752ef9d231b958e7452e66202ba0f3)\n"
    },
    {
      "commit": "791b5f743f08a78cb1608c580c3266f7ba8fe059",
      "tree": "af6a340b9a6d10091d7fe8ef69af2423a98d2772",
      "parents": [
        "d787464a637c840b88828bfdc9550040da094a18"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Wed Aug 19 11:58:34 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 15:29:37 2026 +0800"
      },
      "message": "fix: parse built asset tags without depending on the emitted format\n\nGetStyle scraped index.html with regexes matching one exact tag shape:\nclassic scripts with defer first, and stylesheet links with href before rel.\nAny bundler emitting a different shape returned nothing, and server-rendered\npages would load with no JavaScript and no stylesheet while every build step\nstill reported success.\n\nThe tags are now read from the parsed document, so attribute order, attribute\nset and quoting no longer matter.\n\nheader.html emits the scraped paths as script tags itself, and those were\nclassic scripts. A module bundle loaded that way fails on its first import, so\nfixing only the parsing would have left server-rendered pages broken; the tag\nis now declared as a module.\n\nThe self-check fixtures are replaced. The previous two asserted failure on\nmodule scripts and on rel-before-href, both of which the parser now accepts, so\nthey would have inverted into false alarms. The replacements cover a stylesheet\nwith no script, a script with no stylesheet, and an inline script with no src.\n\ngolang.org/x/net moves to a direct requirement, matching its use here.\n\n(cherry picked from commit eab6f9d80c084d8345a4c82821a64aaef512cce5, go.mod and internal/controller/template_controller.go only)\n"
    },
    {
      "commit": "d787464a637c840b88828bfdc9550040da094a18",
      "tree": "1245bdfa4f852774cb129ec32a86d6f4ce144051",
      "parents": [
        "7f226dcd096b3456760afee08b2337bc7d858e22"
      ],
      "author": {
        "name": "Xan Torres",
        "email": "xan.torres@gmail.com",
        "time": "Wed Aug 19 11:58:07 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 15:29:37 2026 +0800"
      },
      "message": "test: guard frontend behaviour a successful build does not demonstrate\n\nTwo things the server depends on are invisible to the build. Both fail\nsilently, so a green build and a working dev server actively disguise them.\n\nThe server parses the script and stylesheet paths out of the built\nindex.html and reuses them on every server-rendered page. That parse is\ncoupled to the exact attribute set and attribute order the frontend build\nwrites into those tags. A build that emits a different tag shape still\nsucceeds, the dev server still works, the binary still compiles, and the\npages simply render with no scripts and no stylesheet.\nTestGetStyleResolvesBuiltAssets asserts the parse still finds them.\n\nLanguages other than the default one are loaded with a template-literal\ndynamic import through an alias that points outside the frontend root. A\nbundler that cannot enumerate that pattern still builds and still serves a\nworking app; the resources never arrive, and only for non-default\nlanguages, so a smoke test in the default language misses it.\ncheck-locale-resolution.js bundles that same import with the project\u0027s own\nconfiguration, runs it, and requires two languages to resolve to distinct\ntranslated content.\n\nBoth run through make check-ui. check-built-assets.sh --self-check\nconfirms the asset check still fails on tag shapes the parser cannot read,\nso a check that quietly stopped asserting anything is distinguishable from\na passing one.\n\n(cherry picked from commit 71cd9246fedbf6430c6a7373759fdf0be4029e4a, internal/controller/template_controller_test.go only)\n"
    },
    {
      "commit": "22867b411c1a802f0a8f9dae79a07ab0289c37fd",
      "tree": "a503c4cd8a50716a88aaf812df95252448770a20",
      "parents": [
        "3985d20c7e5b85811eb5f009a4de7e14822f0e0e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 10:11:37 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 15 10:11:37 2026 +0800"
      },
      "message": "fix(security): enforce reaction content visibility\n"
    },
    {
      "commit": "7f226dcd096b3456760afee08b2337bc7d858e22",
      "tree": "c0b4a7e3fcc0488eeedb6f7e2506a11b3f80a31a",
      "parents": [
        "f4db9ec5275b3b2b3d925e35c7c6ce4966722483"
      ],
      "author": {
        "name": "Andy-Sverdlov-LucaNet",
        "email": "141342766+Andy-Sverdlov-LucaNet@users.noreply.github.com",
        "time": "Wed Sep 09 16:13:35 2026 +0200"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Thu Sep 10 17:41:00 2026 +0800"
      },
      "message": "fix: lowercase the email before hashing the Gravatar URL\n\nThe Gravatar specification hashes the trimmed, lowercased address.\nGetAvatarURL only trimmed it, so an account whose stored address contains\nan uppercase letter hashed to an address Gravatar does not know: the user\u0027s\navatar was never found and the identicon fallback was rendered instead.\n\nselectedAvatar recomputes this URL from the stored address on every\nresponse, so this affected both default_avatar: gravatar and an explicit\nper-user avatar.type: gravatar, and a user could not work around it by\nre-selecting Gravatar in their profile. Nothing in the backend normalises a\nstored address, and the external login path copies the provider\u0027s address\nverbatim, so accounts created through an OIDC/OAuth2 connector inherit\nwhatever casing the identity provider sends.\n\nThe web UI already lowercases before hashing, so the Settings -\u003e Profile\npreview showed the user\u0027s real avatar while every other surface showed an\nidenticon. This removes that disagreement.\n\nThe hash is computed on read, so existing accounts resolve correctly as soon\nas this ships. Stored addresses are left untouched.\n\nCo-Authored-By: Claude Opus 5 \u003cnoreply@anthropic.com\u003e\n"
    },
    {
      "commit": "3985d20c7e5b85811eb5f009a4de7e14822f0e0e",
      "tree": "6de944290a41f85f41bedfca8143bafb85a85852",
      "parents": [
        "7a7caac953cb1d980c2d496346421a059ca89e59"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 08 15:43:49 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 08 15:43:49 2026 +0800"
      },
      "message": "fix(security): preserve User Center session identity\n"
    },
    {
      "commit": "7a7caac953cb1d980c2d496346421a059ca89e59",
      "tree": "ed78640d017797b9c6875cef124410ca549180b7",
      "parents": [
        "76739fcfd790dbc79f7df19d7294f26015de4ea1"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 08 14:30:12 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 08 14:30:12 2026 +0800"
      },
      "message": "fix: normalize allowed email domains\n"
    },
    {
      "commit": "f4db9ec5275b3b2b3d925e35c7c6ce4966722483",
      "tree": "5790db21408efbf0f8b4a58042731d156d2df4e6",
      "parents": [
        "9c15df876e8d823d18b72782662e9c74c1018448"
      ],
      "author": {
        "name": "Steven Koo",
        "email": "steven.koo@emerson.com",
        "time": "Tue Sep 01 16:07:16 2026 -0500"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 08 12:13:57 2026 +0800"
      },
      "message": "charts: support using an existing PVC via persistence.existingClaim\n\nAdd a new `persistence.existingClaim` value that, when set, causes the\ndeployment to mount the specified PVC instead of the chart-managed one\nand skips creation of the PVC resource entirely.\n\nSigned-off-by: Steven Koo \u003csteven.koo@emerson.com\u003e\n"
    },
    {
      "commit": "9c15df876e8d823d18b72782662e9c74c1018448",
      "tree": "14cb11a48b15cbeb5090c2899b97250c0895fe8c",
      "parents": [
        "947a48b6437783c6bb0ec013ed32ec2366779e0f"
      ],
      "author": {
        "name": "Max Freedom Pollard",
        "email": "272618364+MaxFreedomPollard@users.noreply.github.com",
        "time": "Mon Sep 07 06:28:01 2026 -0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Sep 08 11:45:42 2026 +0800"
      },
      "message": "fix: comment url swaps title and answer id\n\nCommentURL called AnswerURL(permalink, siteUrl, questionID, answerID, title),\nbut AnswerURL takes (permalink, siteUrl, questionID, title, answerID), so the\nlast two arguments were reversed for every comment left on an answer.\n\nThe answer route is /questions/:id/:title/:answerid, so the answer id landed in\nthe title slot and the title landed in the answer id slot. Under the numeric\npermalink settings the title was run through uid.DeShortID and came out as an\nunrelated number. Under the short id settings uid.EnShortID returned it\nunchanged, so the raw title, spaces and all, was written into the path.\n\nThe broken link reaches the new comment notification email built in\ninternal/service/export/email_service.go and the CommentUrl handed to\nnotification plugins in internal/service/notification_common/notification.go.\nA comment on a question takes the other branch and was already correct.\n\nPass title and answerID in the order AnswerURL declares them, and add\npkg/display/url_test.go covering both branches across all four permalink\nsettings.\n"
    },
    {
      "commit": "947a48b6437783c6bb0ec013ed32ec2366779e0f",
      "tree": "f37b386b0429bd18e8de71eb008da4c869336288",
      "parents": [
        "dd4c6f3b53d1941b9a3a012fc01fc314536f83ca"
      ],
      "author": {
        "name": "LiuDag",
        "email": "1038678031@qq.com",
        "time": "Fri Aug 28 14:58:34 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Aug 28 14:58:34 2026 +0800"
      },
      "message": "fix(ai): stop advertising semantic_search when no vector search plugin is enabled (#1597)\n\nFixes #1532.\n\n### Problem\n\nThe `semantic_search` MCP tool was always advertised to the model, even\nwhen\nno VectorSearch plugin was enabled. The model could select it, the call\nreached `EmbeddingService.SearchSimilar`, and every such conversation\nlogged\n`semantic search failed: semantic search is not available: no vector\nsearch\nplugin is enabled` — polluting logs and wasting a tool-call round trip\non a\ncapability that does not exist in the deployment.\n\n### Fix\n\n- `EmbeddingService.Available()` reports whether a VectorSearch plugin\nis\n  currently enabled.\n- `MCPController.SemanticSearchAvailable()` exposes that to the AI chat.\n- `getMCPTools()` omits the `semantic_search` tool when unavailable; all\n  other MCP tools keep working exactly as before.\n- The default AI prompts (zh/en, and admin-custom prompts) drop the\n`semantic_search` instruction lines in the same situation, so the model\nis\n  no longer nudged toward a missing capability.\n\n### Testing\n\n- New unit tests: tool advertisement with/without the capability,\nprompt-line\nstripping, and prompt adaptation\n(`internal/controller/ai_tools_test.go`).\n- `go test ./internal/controller/` — all pass.\n\nCo-authored-by: LinkinStars \u003clinkinstar@foxmail.com\u003e"
    },
    {
      "commit": "dd4c6f3b53d1941b9a3a012fc01fc314536f83ca",
      "tree": "73a26c8d14f8434a4753046ccb1bdc4bd6e3a89d",
      "parents": [
        "548889e2e96310c4ca1d1ea4ffe41df0e5a00a0f"
      ],
      "author": {
        "name": "hgaol",
        "email": "dhangao@hotmail.com",
        "time": "Fri May 22 18:34:42 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 28 14:53:03 2026 +0800"
      },
      "message": "feat(plugin): add IsVectorSearchEnabled function to check plugin status\n"
    },
    {
      "commit": "d70b5cd4647476e8a04b0ca8abbaaf5f802b57c7",
      "tree": "34b317c40a2fa5c61d3c77e53b6f854d58364ab4",
      "parents": [
        "cb5d3bd567c3e8ac172ee5d8ad3a03c8eb285386",
        "548889e2e96310c4ca1d1ea4ffe41df0e5a00a0f"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 28 14:32:15 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 28 14:32:15 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/dev\u0027 into test\n"
    },
    {
      "commit": "cb5d3bd567c3e8ac172ee5d8ad3a03c8eb285386",
      "tree": "7c9744caff4b1d5b96d229ab20611fce4a3b8edd",
      "parents": [
        "65f0060888d7df1211701181310a569d5ff0022a",
        "76739fcfd790dbc79f7df19d7294f26015de4ea1"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 28 14:32:08 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 28 14:32:08 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/fix/2.0.3/user\u0027 into test\n"
    },
    {
      "commit": "548889e2e96310c4ca1d1ea4ffe41df0e5a00a0f",
      "tree": "e984e9f66c39fb362d8fca70194de2016eba54b9",
      "parents": [
        "7a2e2475f72cc8bc678fc38684eb93e206238b92"
      ],
      "author": {
        "name": "m8522s",
        "email": "43844394+m8522s@users.noreply.github.com",
        "time": "Fri Aug 21 21:05:55 2026 +0200"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Thu Aug 27 18:44:07 2026 +0800"
      },
      "message": "New and fixed translations"
    },
    {
      "commit": "7a2e2475f72cc8bc678fc38684eb93e206238b92",
      "tree": "c7b44a0ef4e8f1ac1dbbe6447ab910a77e0d1337",
      "parents": [
        "2c0ced322d17a822e82c9de624dd6c252e0ba40b"
      ],
      "author": {
        "name": "Andreas Polzer",
        "email": "culfin@me.com",
        "time": "Thu Aug 27 04:34:31 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 27 10:34:31 2026 +0800"
      },
      "message": "fix(ui): allow uppercase letters in username on profile settings (#1572)\n\nUsers whose username contains an uppercase letter cannot save their\nprofile at all — not even when they leave the username untouched,\nbecause the whole form is validated on submit.\n\nThe check in profile settings is the only one of four that rejects\nuppercase:\n\n| where | pattern | uppercase |\n|---|---|---|\n| `ui/src/pages/Users/Register/components/SignUpForm/index.tsx` |\n`/^[\\w.-\\s]{2,30}$/` | allowed |\n| `ui/src/pages/Install/components/FourthStep/index.tsx` |\n`/^[\\w.-\\s]{2,30}$/` | allowed |\n| `pkg/checker/username.go` | `^[\\w.\\- ]{2,30}$` | allowed |\n| `ui/src/pages/Users/Settings/Profile/index.tsx` | `/[^a-z0-9\\-._]/` |\n**rejected** |\n\nSo one can sign up as `MaxMustermann`, and from then on the profile page\nis locked. The error message points at the username field without saying\nwhy a name the server itself issued is suddenly invalid.\n\nI ran into this migrating a 26-year-old forum to Answer: 3368 of 5479\naccounts carry uppercase letters in names that have been in use for two\ndecades.\n\n## Proposed Changes\n\n- add the ignore-case flag to the username check in profile settings,\nbringing it in line with registration, installation and the server\n- permits nothing the server would reject; no data or API behaviour\nchanges\n\nAn alternative would be to reuse the exact pattern from `SignUpForm`,\nbut that also allows spaces, which felt like a larger change than this\nfix needs. Happy to switch if you prefer the patterns to be literally\nidentical.\n\nCo-authored-by: Besser Sehen Landshut \u003challo@bessersehen.la\u003e\nCo-authored-by: Claude Opus 5 (1M context) \u003cnoreply@anthropic.com\u003e"
    },
    {
      "commit": "76739fcfd790dbc79f7df19d7294f26015de4ea1",
      "tree": "3d321cd559a285d8fbbb28f4edd04162690be457",
      "parents": [
        "372f0776ae88603823ed57e8c808240f58ab948e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Aug 26 11:04:20 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Aug 26 11:04:20 2026 +0800"
      },
      "message": "fix(security): enforce mutation visibility checks\n"
    },
    {
      "commit": "372f0776ae88603823ed57e8c808240f58ab948e",
      "tree": "b15c4e8d73e7f7f107faa48972c8e519faf69eac",
      "parents": [
        "41a9d887d9361c8b4b70c50abfbb516ec09fe918"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 25 17:29:24 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 25 17:29:24 2026 +0800"
      },
      "message": "fix(security): revoke stale visit tokens\n"
    },
    {
      "commit": "41a9d887d9361c8b4b70c50abfbb516ec09fe918",
      "tree": "670d3594ee173941280d49a0a58f7499786e92ad",
      "parents": [
        "a52cc0aeb2b071ad73fc918c75a9b799b13352a2"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 25 16:36:04 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 25 16:36:04 2026 +0800"
      },
      "message": "fix(security): restrict similar question visibility\n"
    },
    {
      "commit": "a52cc0aeb2b071ad73fc918c75a9b799b13352a2",
      "tree": "79bbc6d1d3f15ae085f5fc327b6dae89e819181a",
      "parents": [
        "61ff3ea17e3307585304148977feb7dadd248ee7"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 25 16:15:06 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 25 16:15:06 2026 +0800"
      },
      "message": "fix(security): enforce personal comment visibility\n"
    },
    {
      "commit": "61ff3ea17e3307585304148977feb7dadd248ee7",
      "tree": "e49cd262376706fcef920928dc860c610ba2a1e4",
      "parents": [
        "a203e7f608f52205f567f27633cf7e9d6428a86e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Thu Aug 20 16:26:57 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Thu Aug 20 16:26:57 2026 +0800"
      },
      "message": "fix: hide hidden questions from public answer profiles\n"
    },
    {
      "commit": "65f0060888d7df1211701181310a569d5ff0022a",
      "tree": "3430c0f9f474ae0c387b6eba5ce89734f882f233",
      "parents": [
        "2c0ced322d17a822e82c9de624dd6c252e0ba40b",
        "a203e7f608f52205f567f27633cf7e9d6428a86e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 14:25:19 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 14:25:19 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/fix/2.0.3/user\u0027 into test\n"
    },
    {
      "commit": "2c0ced322d17a822e82c9de624dd6c252e0ba40b",
      "tree": "4d99a32d0f8f742725a73e133562d947cd4e0bd1",
      "parents": [
        "413bbd0c7f510db2eaf87b0b865633b00d72a353"
      ],
      "author": {
        "name": "Duansg",
        "email": "siguoduan@gmail.com",
        "time": "Sat Aug 15 22:50:33 2026 -0700"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 12:23:24 2026 +0800"
      },
      "message": "fix: use TagStatusDeleted instead of QuestionStatusDeleted in tag permission\n"
    },
    {
      "commit": "a203e7f608f52205f567f27633cf7e9d6428a86e",
      "tree": "447732a7bc381bdc0c21b44d38cd3bf13349ff1f",
      "parents": [
        "63a67542f4c23afcf3303b4a46e52bda60e31730"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 11:32:39 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 11:32:39 2026 +0800"
      },
      "message": "chore: satisfy static checks\n"
    },
    {
      "commit": "63a67542f4c23afcf3303b4a46e52bda60e31730",
      "tree": "deaa7075d8c8f7d95b5323f94048f68b7709c5fa",
      "parents": [
        "2e2c3019a257eddc88a083711307e0109b99a2fc"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 10:49:54 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 10:49:54 2026 +0800"
      },
      "message": "fix: constrain MCP content access\n"
    },
    {
      "commit": "2e2c3019a257eddc88a083711307e0109b99a2fc",
      "tree": "6011c0a1a21be9d8e65205b53eca5aabace5f671",
      "parents": [
        "e8ded23a4acb9868b4da004aa47427a90576495f"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 10:28:49 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Aug 17 10:28:49 2026 +0800"
      },
      "message": "fix: enforce question access controls\n"
    },
    {
      "commit": "e8ded23a4acb9868b4da004aa47427a90576495f",
      "tree": "942bfe50abdc4df14327fac0a0425c7a0c4280db",
      "parents": [
        "21e0714a0876c6b2e3e8eee44178d97846c6bcb3"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 19:17:10 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 19:17:10 2026 +0800"
      },
      "message": "fix: strengthen request safeguards\n"
    },
    {
      "commit": "21e0714a0876c6b2e3e8eee44178d97846c6bcb3",
      "tree": "3acf43db9da7f6f385edacb4e9390f46e931324f",
      "parents": [
        "32b451ce8758e04c1e45b0ed9712eb8fbc7b66cc"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 17:03:23 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 17:03:23 2026 +0800"
      },
      "message": "fix: align question operation permissions\n"
    },
    {
      "commit": "413bbd0c7f510db2eaf87b0b865633b00d72a353",
      "tree": "88473bdf333fd510c79ca7800f65ca1175157e84",
      "parents": [
        "1ccb4b7adcc3c23d218add997a3126e80a495574"
      ],
      "author": {
        "name": "Nikita Ermilov",
        "email": "nikita.ermilov@flant.com",
        "time": "Tue Jun 09 18:22:58 2026 +0300"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 17:00:03 2026 +0800"
      },
      "message": "i18n: complete Russian (ru_RU) translation\n"
    },
    {
      "commit": "32b451ce8758e04c1e45b0ed9712eb8fbc7b66cc",
      "tree": "81f1f5512cccb8e355c85af4de8c15f723af516f",
      "parents": [
        "b80ad0a892f6a597108dd3ca37dd299bd82682ad"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 16:48:53 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 16:48:53 2026 +0800"
      },
      "message": "fix: normalize imported content\n"
    },
    {
      "commit": "b80ad0a892f6a597108dd3ca37dd299bd82682ad",
      "tree": "4285b57b3e0b6721ea32139acffcdc497dc87480",
      "parents": [
        "1ccb4b7adcc3c23d218add997a3126e80a495574"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 16:34:33 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Aug 14 16:34:33 2026 +0800"
      },
      "message": "fix: avoid sensitive auth logging\n"
    },
    {
      "commit": "1ccb4b7adcc3c23d218add997a3126e80a495574",
      "tree": "b7c5f66a72c07fb2da75758eee54f1de12304778",
      "parents": [
        "da622a4927c172196301d86dbd49b35ef7a8f776"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 11 19:48:24 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 11 19:50:54 2026 +0800"
      },
      "message": "fix: add ASF header to tag search test\n\nSigned-off-by: LinkinStars \u003clinkinstar@foxmail.com\u003e\n"
    },
    {
      "commit": "da622a4927c172196301d86dbd49b35ef7a8f776",
      "tree": "eaccccab4e7390214dc119e0d0e6a9274b7d0e5c",
      "parents": [
        "4488ccc689559517d3c272ecd7a90ebdf6f7ecec"
      ],
      "author": {
        "name": "ferhat elmas",
        "email": "elmas.ferhat@gmail.com",
        "time": "Sun Aug 02 15:29:35 2026 +0200"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 11 19:34:58 2026 +0800"
      },
      "message": "test(converter): pin renderLinkIsUrl behavior\n\nAdd a table-driven test covering the markdown link destination check\nbefore replacing the govalidator dependency with stdlib logic.\n\nSigned-off-by: ferhat elmas \u003celmas.ferhat@gmail.com\u003e\n"
    },
    {
      "commit": "4488ccc689559517d3c272ecd7a90ebdf6f7ecec",
      "tree": "74357b80212f2bd1632da8b3318fc4baac4ddded",
      "parents": [
        "98329f3b05949246bddaa83dd5651f590e7afae7"
      ],
      "author": {
        "name": "Max Engine",
        "email": "ngineer420@gmail.com",
        "time": "Sun Jul 26 03:38:37 2026 -0600"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Aug 11 19:31:39 2026 +0800"
      },
      "message": "fix: tag search never matches on slug name\n\nThe search term was formatted into LOWER(%s) and passed as the *value* of the\nLIKE, so the function name ended up inside the pattern:\n\n    slug_name LIKE \u0027%LOWER(coco)%\u0027\n\nThat can never match. Only the display_name clause did any work, and LIKE is\ncase-sensitive on Postgres, so searching a tag by the name it is written in\nreturns nothing:\n\n    slug_name\u003dCoco  -\u003e matches\n    slug_name\u003dcoco  -\u003e no match\n\nTags are lower case by convention, so lower case is what users type, and the\nfilter appears to report that no such tag exists.\n\nLower both sides instead. The term normalisation is extracted so it can be\ncovered by a test without a database.\n"
    },
    {
      "commit": "98329f3b05949246bddaa83dd5651f590e7afae7",
      "tree": "64ba4c9b27cd36595cc452b473d8074163e9f04b",
      "parents": [
        "ecef4f11dce25b7785cfad41baccc12da030cbb5"
      ],
      "author": {
        "name": "ferhat elmas",
        "email": "elmas.ferhat@gmail.com",
        "time": "Sun Jul 26 04:04:08 2026 +0200"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 29 19:09:48 2026 +0800"
      },
      "message": "fix: advanced site settings setup from migration 30\n\nSigned-off-by: ferhat elmas \u003celmas.ferhat@gmail.com\u003e\n"
    },
    {
      "commit": "ecef4f11dce25b7785cfad41baccc12da030cbb5",
      "tree": "59d25d3b6c0e50e6b440910103c493fa8786fb8f",
      "parents": [
        "9df5853942f17fccc81d0c3a449cbb7fc51d4ff4",
        "3b9f1370612e690a0b7f230f05e688930db4c6d3"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 29 17:19:20 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 29 17:19:20 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/main\u0027 into dev\n\n# Conflicts:\n#\tdocs/release/LICENSE\n#\tinternal/service/notification/new_question_notification_test.go\n"
    },
    {
      "commit": "3b9f1370612e690a0b7f230f05e688930db4c6d3",
      "tree": "59d25d3b6c0e50e6b440910103c493fa8786fb8f",
      "parents": [
        "897fc8c4d4961863c8fde6331aa6ebee9e31244d"
      ],
      "author": {
        "name": "Luffy",
        "email": "52o@qq52o.cn",
        "time": "Fri Jul 17 22:38:41 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Jul 17 22:38:41 2026 +0800"
      },
      "message": "fix: edit question fails when short links enabled (#1554)\n\nClose #1553"
    },
    {
      "commit": "897fc8c4d4961863c8fde6331aa6ebee9e31244d",
      "tree": "b126559082063876158a51c597faedfa4247771e",
      "parents": [
        "2758e23381541ed8a2a92d2079c3f447277c4d73"
      ],
      "author": {
        "name": "Luffy",
        "email": "52o@qq52o.cn",
        "time": "Tue Jul 14 16:03:24 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jul 14 16:03:24 2026 +0800"
      },
      "message": "fix: add missing license entry for mozillazg-go-unidecode (#1552)"
    },
    {
      "commit": "2758e23381541ed8a2a92d2079c3f447277c4d73",
      "tree": "ccb806832d33f5978c2c8ed3f0e7634497a4e8d0",
      "parents": [
        "9d9f5d06a8a3d732b01458800fd9df9ca34aef6c"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Thu Jul 09 17:29:44 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: update version to 2.0.2 in Makefile and README\n"
    },
    {
      "commit": "9d9f5d06a8a3d732b01458800fd9df9ca34aef6c",
      "tree": "257535169feeba6ac84150340fa29eb9ad1b3b20",
      "parents": [
        "c589b59dd5f16d59b389ec4da8f8e1a97ac8ef04"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 08 16:34:37 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: enhance user admin service to manage API keys and filter answer visibility\n"
    },
    {
      "commit": "c589b59dd5f16d59b389ec4da8f8e1a97ac8ef04",
      "tree": "ce842076a5ed6d6426f2e0b4c48aadb0b698e023",
      "parents": [
        "dc8443176818cd91e33939de999b0ef9f610b38b"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 08 12:27:43 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: enhance answer visibility checks for user permissions\n"
    },
    {
      "commit": "dc8443176818cd91e33939de999b0ef9f610b38b",
      "tree": "54323c7c06f91eef2ce043ed6139099f79ae971c",
      "parents": [
        "0705ea6baeaf2fc3a2abdff8ef75b6b5a0b26e67"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 08 11:05:24 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(build): update Docker build process to use buildx and streamline image tagging\n"
    },
    {
      "commit": "0705ea6baeaf2fc3a2abdff8ef75b6b5a0b26e67",
      "tree": "c586a1f7b02ad3fe47e96c8b020226be1e400dcf",
      "parents": [
        "c52e2eeb1f3893eed898213c48198432addfec77"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 08 10:41:28 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(tests): add mock methods for cache OAuth state in new question notification tests\n"
    },
    {
      "commit": "c52e2eeb1f3893eed898213c48198432addfec77",
      "tree": "c307236ef870b76ff9d3586fd0633105b306ff29",
      "parents": [
        "58dbe2c02759d60a6bdab5fe3711e2eb2c5b295b"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 08 10:37:30 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(build): update Go version in Dockerfile to 1.25-alpine\n"
    },
    {
      "commit": "58dbe2c02759d60a6bdab5fe3711e2eb2c5b295b",
      "tree": "1ba3d1c74a81a39e328efdaf1c58aca32a023e80",
      "parents": [
        "88c288fdda2ccb4690f3bba3b6cc570389fe9e6b"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jul 08 10:31:16 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(build): streamline Docker image build process in CI configuration\n"
    },
    {
      "commit": "88c288fdda2ccb4690f3bba3b6cc570389fe9e6b",
      "tree": "d116f94f1c889e4b5a3e6607a23e39a30d403642",
      "parents": [
        "f5e25dcb8d7c793316161ce1a6925edb596e1d2e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 19:32:24 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(tests): update goroutine handling in new question email worker tests\n"
    },
    {
      "commit": "f5e25dcb8d7c793316161ce1a6925edb596e1d2e",
      "tree": "0c4920dba728a1475917f16e58741b166655f88a",
      "parents": [
        "e06311a51403224d9b9a7aace4d960c7960f6986"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 19:03:27 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(notification): remove buffer size parameter from new question email worker for test\n"
    },
    {
      "commit": "e06311a51403224d9b9a7aace4d960c7960f6986",
      "tree": "51af37da56b82f484e207301a6504758c89940f4",
      "parents": [
        "cb9739421154ad4e3c9d214ae5042343b10ef91c"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Wed Jul 01 13:30:44 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(notification): make new question email queue configurable\n"
    },
    {
      "commit": "cb9739421154ad4e3c9d214ae5042343b10ef91c",
      "tree": "a4daaa0d19dab66320be586b86b5541c259ab2b3",
      "parents": [
        "dda51232b9a61631dcb1b4e49bcefd31d83c8deb"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Sat Jun 27 23:35:28 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(notification): move new question email throttling to worker\n"
    },
    {
      "commit": "dda51232b9a61631dcb1b4e49bcefd31d83c8deb",
      "tree": "500cbaa611d0b2d30eaec1a64866b9d39c8822a5",
      "parents": [
        "a00cb2d38c018187ca73d409400f3c0c8f15294c"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Wed Jun 17 12:13:56 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "feat(notification): add interval for new question emails\n"
    },
    {
      "commit": "a00cb2d38c018187ca73d409400f3c0c8f15294c",
      "tree": "c069d6985ed470492fddedd0fb54f65bc156e444",
      "parents": [
        "f92a0fe140328bd180e46792557fd93f03e2e651"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 16:26:45 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: update goroutine handling and context imports for consistency\n"
    },
    {
      "commit": "f92a0fe140328bd180e46792557fd93f03e2e651",
      "tree": "33091ca5636e13cebcf1386fa9a62d27c55ec5d9",
      "parents": [
        "b575fe893bda63ede1112d8262fdef85893b4e1e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 15:05:47 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "Harden Accept-Language parsing\n"
    },
    {
      "commit": "b575fe893bda63ede1112d8262fdef85893b4e1e",
      "tree": "3a79281e29cb3344a72054fe18915c7699437e7b",
      "parents": [
        "54b1b35df5aec3b80c834c35546cf8a35d927395"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 14:33:21 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "Harden avatar cleanup ownership checks\n"
    },
    {
      "commit": "54b1b35df5aec3b80c834c35546cf8a35d927395",
      "tree": "ad74e1df8fc63069fb0cce06ff92e8163f045794",
      "parents": [
        "06a0f742f22cb309908ab19cd73657368d9a0726"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 12:18:55 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "Align revision audit permission checks\n"
    },
    {
      "commit": "06a0f742f22cb309908ab19cd73657368d9a0726",
      "tree": "5952df8853925c2095f4a2e118e7204091b9138c",
      "parents": [
        "3a5a15fe77319c62dd814fa07998f5bc53df35e1"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Thu Jul 02 17:41:16 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix external login account binding\n"
    },
    {
      "commit": "3a5a15fe77319c62dd814fa07998f5bc53df35e1",
      "tree": "7f1bf6d70ea78ebda6e9a3f002400267b937d44a",
      "parents": [
        "7123326515377a49b9f673dd8db6daa9c871ab47"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Tue Jun 09 01:39:27 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix(site): require explicit email verification setting\n\nReplace OptionalBool with an explicit require_email_verification value for the login settings save request while keeping legacy read defaults intact.\n\nUse positive RequireEmailVerification naming through the registration flow and inline the site setting mapping.\n\nAdd validation, save-path, and registration coverage for the explicit email verification setting.\n"
    },
    {
      "commit": "7123326515377a49b9f673dd8db6daa9c871ab47",
      "tree": "3fbef5733b50f79e0f8d53b0f1c044ffe9a45b90",
      "parents": [
        "4aa52a6b521c5088b5706b1500a5d27c02fa04de"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Fri Jun 05 00:42:00 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "feat(site): allow disabling email verification\n"
    },
    {
      "commit": "4aa52a6b521c5088b5706b1500a5d27c02fa04de",
      "tree": "17e111fad71e697f80ccb49989cacffa88a1c0c0",
      "parents": [
        "9234d8024630a41400282f6dab619f716551f815"
      ],
      "author": {
        "name": "hgaol",
        "email": "dhangao@hotmail.com",
        "time": "Tue Jun 09 15:26:11 2026 +0000"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: accept answer fails when short links enabled (#1541)\n\nThe ownership check added to AcceptAnswer compared the answer\u0027s QuestionID\nagainst the request\u0027s QuestionID directly. When short links are enabled,\nanswerRepo.GetByID re-encodes QuestionID to its short form while the\ncontroller de-shorts req.QuestionID to its long form, so the two encodings\nof the same question never matched and every accept returned \"Answer do not\nfound\". Normalize both ids via uid.DeShortID before comparing, preserving the\nprivilege-escalation guard for answers that truly belong to another question.\n"
    },
    {
      "commit": "9234d8024630a41400282f6dab619f716551f815",
      "tree": "f7aeeabcf2c58e1f5e8a654acf2051e616fd9582",
      "parents": [
        "10be96814ecf980d8993ef0ac23715f778945e63"
      ],
      "author": {
        "name": "hhc7",
        "email": "169754973+hhc7@users.noreply.github.com",
        "time": "Tue Jun 02 13:55:35 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: scope JSON 500 to API routes, skip rewriting already-flushed responses\n"
    },
    {
      "commit": "10be96814ecf980d8993ef0ac23715f778945e63",
      "tree": "a536dd4b6d1b2e50e2466db49978f477e75037db",
      "parents": [
        "a6d867e4ccc36b7519e65742b8039a9c685cd808"
      ],
      "author": {
        "name": "hhc7",
        "email": "169754973+hhc7@users.noreply.github.com",
        "time": "Thu May 28 18:04:27 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "feat: add recovery middleware to handle panic gracefully\n"
    },
    {
      "commit": "a6d867e4ccc36b7519e65742b8039a9c685cd808",
      "tree": "e429170c77cbfb2d55142b8fa0af580802192042",
      "parents": [
        "4b8de1897b34bb040ec955538a5377a041afffe0"
      ],
      "author": {
        "name": "Ahmed Qasid",
        "email": "ahmad.kassyd@gmail.com",
        "time": "Wed Jun 03 17:06:26 2026 +0300"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: avoid topic fallback for non-Latin titles via pragmatic ASCII transliteration (#1526)\n\n# fix: avoid `topic` fallback for non-Latin titles via pragmatic ASCII\ntransliteration\n\n\u003e **Scope update (in response to review):** this PR is intentionally\nbroader than its original \"Arabic-only\" framing. The implementation\nchanges URL slug generation for **every non-Latin, non-CJK script** that\n`slugify` previously stripped — see *Scope* below for the explicit list.\nThe goal is *not* linguistically correct romanization; it is \"avoid\ncollapsing to `/topic` by producing a usable ASCII slug.\"\n\n## What this PR is (and isn\u0027t)\n\n**Goal:** when a question title contains characters outside Basic Latin\n/ Latin Extended / CJK Han, generate a URL slug that is a deterministic\nASCII approximation instead of letting `slugify` strip everything and\nfalling back to the literal `\"topic\"`.\n\n**Non-goal:** this is *not* a linguistically correct multi-language\nromanizer. The output is a machine-acceptable ASCII slug, not what a\nnative speaker would choose. For example, `こんにちは` → `konnichiha` (not\nthe more natural `kon\u0027nichiwa`), `ไทย` → `aithy` (not `thai`). Treat the\nslug as an opaque, stable, indexable identifier — the\npath-after-`/questions/\u003cid\u003e/` is for SEO and shareability, the canonical\nreference is always the ID.\n\n## The bug\n\nPure non-Latin titles previously got stripped by `slugify.Slugify`, hit\nthe empty-result fallback in `htmltext.UrlTitle`, and collapsed to the\nliteral slug `\"topic\"`. On a live multilingual site, every Arabic / Thai\n/ Japanese-hiragana / Korean / Hebrew / Cyrillic question ended up at\n`/questions/\u003cid\u003e/topic`.\n\n## The fix\n\n`UrlTitle()` gets a `convertNonLatin` pre-step that mirrors the existing\n`convertChinese` pre-step pattern, using\n`github.com/mozillazg/go-unidecode` (same author as `go-pinyin` already\nin the repo, to minimise new-dep friction).\n\n```\nUrlTitle(title)\n  → convertChinese(title)        // pre-existing: Han-block → pinyin\n  → convertNonLatin(title)       // NEW: detect non-Latin letters → unidecode to ASCII\n  → clearEmoji / slugify / url.QueryEscape / cutLongTitle (unchanged)\n```\n\nThe non-Latin detector skips ASCII, Latin-1 Supplement, Latin\nExtended-A/B, and CJK Han. Inputs that hit none of those non-Latin\nletter categories short-circuit and return unchanged, so Latin-only and\nChinese-only inputs remain byte-identical (pinned by tests).\n\n## Scope — what scripts are affected\n\nThis PR changes behavior for **any** title containing letters in scripts\nthat `slugify` doesn\u0027t handle. Confirmed by tests in\n`pkg/htmltext/htmltext_test.go`:\n\n| Script | Example title | Before | After |\n| --- | --- | --- | --- |\n| Arabic | `كيف حالك` | `topic` | `kyf-hlk` |\n| Mixed Latin + Arabic | `مرحبا hello` | `hello` | `mrhb-hello` |\n| Thai | `ไทย ไทย` | `topic` | `aithy-aithy` |\n| Japanese hiragana | `こんにちは` | `topic` | `konnichiha` |\n| Korean | `안녕하세요` | `topic` | `annyeonghaseyo` |\n| Hebrew | `שלום עולם` | `topic` | `shlvm-vlm` |\n| Cyrillic | `Привет мир` | `topic` | `privet-mir` |\n\n**Unchanged:**\n\n| Case | Behavior |\n| --- | --- |\n| Pure Latin (`hello world`) | unchanged → `hello-world` |\n| Pure Chinese (`这是一个，标题，title`) | unchanged → `zhe-shi-yi-ge-biao-ti`\n(pinyin path) |\n| Japanese with Han-block kanji (`日本`) | unchanged → `ri-ben` (caught by\npre-existing pinyin path; treated as Chinese reading, not Japanese — a\npre-existing limitation, **not** introduced by this PR) |\n| Emoji only (`😂😂😂`) | unchanged → `topic` |\n| Empty / whitespace | unchanged → `topic` |\n\n## Transliteration quality — explicit acknowledgement\n\n`go-unidecode` is a generic Unicode → ASCII approximation. It is **not**\na per-language romanization library. Specifically:\n\n- It will pick *one* approximation per codepoint regardless of language\ncontext. `ใ` → `ai` (Thai romanization is `i` or `ai` depending on\nstandard), `한` → `han`, `語` → `Yu` (Chinese pinyin reading even when\nused in Japanese), etc.\n- The result is *good enough* to be a stable, URL-safe,\nhuman-recognizable handle, but speakers of the source language will not\nconsider it \"correct.\"\n- It is deterministic, so the same title always produces the same slug —\nimportant since `url_title` is recomputed on every request.\n\nIf maintainers prefer to scope this PR more narrowly (e.g. Arabic only,\nand reject Thai/Hebrew/Cyrillic/etc.), the detector in\n`containsNonLatin` can be tightened to specific Unicode blocks — but\nthat means the other scripts continue to collapse to `topic`, which is\nthe bug we\u0027re trying to fix. I\u0027d argue the broader fix is preferable to\na piecemeal one, but happy to narrow if you want.\n\n## Live deployment / real-world verification\n\nThis patch has been running in production on\n**[ask.namasoft.com](https://ask.namasoft.com)** (an Apache Answer\ninstance we operate) since deployment, built directly from this branch\nvia `docker compose build`. The site hosts Arabic-language questions, so\nthe fix exercises the affected code path on every page load.\n\nSample question URL on the deployed instance:\n\n\u003e `https://ask.namasoft.com/questions/10010000000000115`\n\nThe slug in the URL is the transliterated Arabic title rather than\n`topic`. No data migration was needed since `url_title` is computed on\nevery request from `Title` and never persisted (see *Why this is safe to\nship* below).\n\n## Admin-configurable\n\nThe transliteration is gated by a package-level `atomic.Bool` (default\n**on**, since the current behavior is objectively broken for affected\nusers):\n\n- `htmltext.SetTransliterateNonLatin(enabled bool)`\n- `htmltext.IsTransliterateNonLatinEnabled() bool`\n\nThis is deliberately the minimum surface needed to satisfy \"the setting\nmust be readable from `UrlTitle()`\". A follow-up PR can add an admin UI\nsection that calls `SetTransliterateNonLatin` on save and on startup,\nwithout having to re-plumb every `htmltext.UrlTitle` call site through\n`context.Context`.\n\n**Default choice — please confirm:** I picked **default-on** because the\nexisting `topic` behavior is a bug for affected users. If you\u0027d prefer\ndefault-off for strict backward compat on existing installs, flip the\n`init()` in `pkg/htmltext/htmltext.go` to `Store(false)` and surface the\ntoggle as opt-in.\n\n## Why this is safe to ship\n\n- `url_title` is **not** a persisted column. It\u0027s not on the `Question`\nentity in `internal/entity/question_entity.go`, no migration has ever\nadded/dropped it, and every call site (`question_service.go`,\n`revision_service.go`, `vote_service.go`,\nsearch/report/review/rank/comment services, controllers, repos)\nrecomputes it from `Title` at response-build time via\n`htmltext.UrlTitle(...)`.\n- That means the fix is read-only: existing rows light up with correct\nslugs on the next request, with no migration and no data rewrite.\n- Rollback is just redeploying the prior image; nothing on disk changes.\n\n## Test coverage\n\n`pkg/htmltext/htmltext_test.go`:\n\n- **`TestUrlTitleTable`** — table-driven, one case per affected script\n(the full matrix above), plus:\n  - `empty` → `topic`\n  - `pure latin unchanged` → byte-identical to pre-fix\n- `pure chinese unchanged` → byte-identical to pre-fix (pins existing\npinyin behavior)\n- `japanese kanji goes through pinyin path unchanged` → documents the\npre-existing Han-block limitation\n  - `emoji only falls back to topic` → unchanged\n- `long arabic truncates at cutLongTitle boundary` → exercises the\n150-byte cap and UTF-8 boundary safety\n- **`TestUrlTitleTransliterationToggle`** — with the toggle off,\nnon-Latin titles collapse to `topic` (pre-fix behavior); with it on,\nthey transliterate.\n- Existing `TestUrlTitle` left untouched.\n\nTest plan for reviewers:\n\n- [ ] `go test ./pkg/htmltext/...` — all pass\n- [ ] Visit the live sample URL above and confirm slug is\ntransliterated, not `topic`\n- [ ] Verify Chinese / Latin / emoji-only / empty behavior is\nbyte-identical to `main` (covered by table tests)\n\n## Out of scope (intentionally)\n\n- No admin UI / site setting plumbing in this PR — see\n*Admin-configurable* above. Happy to do the React `Non-Latin Languages\nHandling` admin page + `SiteType` + service / controller / migration in\na follow-up if maintainers want it.\n- No change to the `\"topic\"` empty-result fallback.\n- No plugin interface for slug generation — mirrored the existing\n`convertChinese` pre-step pattern instead.\n- No per-language romanization library — this is an explicit non-goal;\nsee *Transliteration quality* above.\n\n## Issues / discussion\n\nI didn\u0027t find an existing upstream issue covering this — happy to be\npointed at one if there is.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\n---------\n\nCo-authored-by: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nCo-authored-by: LinkinStars \u003clinkinstar@foxmail.com\u003e"
    },
    {
      "commit": "4b8de1897b34bb040ec955538a5377a041afffe0",
      "tree": "811a614e751a670be9068372153e4102e8bcd183",
      "parents": [
        "09eaa5b7e3acefcde37cf6cce8a8317ad24568cf"
      ],
      "author": {
        "name": "Luffy",
        "email": "52o@qq52o.cn",
        "time": "Tue Jun 02 11:50:54 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "fix: update license entries\n"
    },
    {
      "commit": "09eaa5b7e3acefcde37cf6cce8a8317ad24568cf",
      "tree": "f683171e4d7357c16d227c23199a9884775b8708",
      "parents": [
        "2746bf5b455fa67f1bec6178c75121397a3816f5"
      ],
      "author": {
        "name": "Luke Gao",
        "email": "dhangao@hotmail.com",
        "time": "Sat May 30 15:33:54 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jul 10 10:19:39 2026 +0800"
      },
      "message": "feat: add reasoning content to AI conversation records and update related components (#1530)\n\nFix #1524 \n\nRoot cause\n\nDeepSeek\u0027s reasoning models stream reasoning_content alongside content.\nAnswer ignored it, so follow-up requests failed with 400: The\nreasoning_content in the thinking mode must be passed back to the API,\nand the thinking text was never shown or saved.\n\nFix\n\n- Capture reasoning_content from the stream and pass it back to theAPI\non subsequent rounds.\n - Persist it with the conversation (new DB column via migrationv2.0.2).\n- Render it in the chat UI as a collapsible \"Thinking…/Thoughts\"panel\nabove the answer.\n\nCompatibility\n\nNullable column, omitempty field, UI hides the panel when empty — old\nconversations and non-reasoning models behave exactly as before.\n\nDemo\n\n\n\nhttps://github.com/user-attachments/assets/49b1a2a1-9133-4ac2-bbeb-860215a50285"
    },
    {
      "commit": "9df5853942f17fccc81d0c3a449cbb7fc51d4ff4",
      "tree": "e051a0be3e94c0395453856b364b2879e8c7f554",
      "parents": [
        "c17e0c94c85716b94aa831ed47c06e69250cd96b"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 19:32:24 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 19:32:24 2026 +0800"
      },
      "message": "fix(tests): update goroutine handling in new question email worker tests\n"
    },
    {
      "commit": "c17e0c94c85716b94aa831ed47c06e69250cd96b",
      "tree": "260d9603b7f436a7075ed4f738cde32fe7dca819",
      "parents": [
        "b70dda997a4e5d6c44559875c68c3ee49f5ef3e4"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 19:03:27 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Tue Jul 07 19:03:27 2026 +0800"
      },
      "message": "fix(notification): remove buffer size parameter from new question email worker for test\n"
    },
    {
      "commit": "b70dda997a4e5d6c44559875c68c3ee49f5ef3e4",
      "tree": "be2ce6b33a2d9466ae3f221bc8b003441041b870",
      "parents": [
        "d10e6aad701d857141da9fcebe35c99be5dc38ac"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Wed Jul 01 13:30:44 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Jul 06 22:31:16 2026 +0800"
      },
      "message": "fix(notification): make new question email queue configurable\n"
    },
    {
      "commit": "d10e6aad701d857141da9fcebe35c99be5dc38ac",
      "tree": "5613e401a828b9bdf868054aab073ae5f2b26aa6",
      "parents": [
        "e1d58ab6357766bbccb3da986bd6eabad8d5c924"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Sat Jun 27 23:35:28 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Jul 06 22:31:16 2026 +0800"
      },
      "message": "fix(notification): move new question email throttling to worker\n"
    },
    {
      "commit": "e1d58ab6357766bbccb3da986bd6eabad8d5c924",
      "tree": "8529df7598ae8f1b9e126ec02b41eaad999709d1",
      "parents": [
        "3b6f981b81ca4376138734e4039eab59b1fea63c"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Wed Jun 17 12:13:56 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Jul 06 22:31:16 2026 +0800"
      },
      "message": "feat(notification): add interval for new question emails\n"
    },
    {
      "commit": "3b6f981b81ca4376138734e4039eab59b1fea63c",
      "tree": "7f1bf6d70ea78ebda6e9a3f002400267b937d44a",
      "parents": [
        "d93e31e92afa500aec40090f2f15d0c0cf3c5639"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Tue Jun 09 01:39:27 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Jun 15 14:44:34 2026 +0800"
      },
      "message": "fix(site): require explicit email verification setting\n\nReplace OptionalBool with an explicit require_email_verification value for the login settings save request while keeping legacy read defaults intact.\n\nUse positive RequireEmailVerification naming through the registration flow and inline the site setting mapping.\n\nAdd validation, save-path, and registration coverage for the explicit email verification setting.\n"
    },
    {
      "commit": "d93e31e92afa500aec40090f2f15d0c0cf3c5639",
      "tree": "3fbef5733b50f79e0f8d53b0f1c044ffe9a45b90",
      "parents": [
        "43a91313d8b323b5f79a0e5e69b69a244a2468f3"
      ],
      "author": {
        "name": "Artur Iusupov",
        "email": "iusupov.aa@phystech.edu",
        "time": "Fri Jun 05 00:42:00 2026 +0400"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon Jun 15 14:44:34 2026 +0800"
      },
      "message": "feat(site): allow disabling email verification\n"
    },
    {
      "commit": "43a91313d8b323b5f79a0e5e69b69a244a2468f3",
      "tree": "17e111fad71e697f80ccb49989cacffa88a1c0c0",
      "parents": [
        "682811f769f4db6d24a0d1749901d95c2d93a7eb"
      ],
      "author": {
        "name": "hgaol",
        "email": "dhangao@hotmail.com",
        "time": "Tue Jun 09 15:26:11 2026 +0000"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jun 12 16:10:22 2026 +0800"
      },
      "message": "fix: accept answer fails when short links enabled (#1541)\n\nThe ownership check added to AcceptAnswer compared the answer\u0027s QuestionID\nagainst the request\u0027s QuestionID directly. When short links are enabled,\nanswerRepo.GetByID re-encodes QuestionID to its short form while the\ncontroller de-shorts req.QuestionID to its long form, so the two encodings\nof the same question never matched and every accept returned \"Answer do not\nfound\". Normalize both ids via uid.DeShortID before comparing, preserving the\nprivilege-escalation guard for answers that truly belong to another question.\n"
    },
    {
      "commit": "682811f769f4db6d24a0d1749901d95c2d93a7eb",
      "tree": "f7aeeabcf2c58e1f5e8a654acf2051e616fd9582",
      "parents": [
        "cece87f9dc6dc61eedc4157c514b14d4788cbc51"
      ],
      "author": {
        "name": "hhc7",
        "email": "169754973+hhc7@users.noreply.github.com",
        "time": "Tue Jun 02 13:55:35 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jun 05 11:26:55 2026 +0800"
      },
      "message": "fix: scope JSON 500 to API routes, skip rewriting already-flushed responses\n"
    },
    {
      "commit": "cece87f9dc6dc61eedc4157c514b14d4788cbc51",
      "tree": "a536dd4b6d1b2e50e2466db49978f477e75037db",
      "parents": [
        "e884bb61cb1b4cd43469bdd4688a948009fedb3d"
      ],
      "author": {
        "name": "hhc7",
        "email": "169754973+hhc7@users.noreply.github.com",
        "time": "Thu May 28 18:04:27 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri Jun 05 11:26:55 2026 +0800"
      },
      "message": "feat: add recovery middleware to handle panic gracefully\n"
    },
    {
      "commit": "e884bb61cb1b4cd43469bdd4688a948009fedb3d",
      "tree": "e429170c77cbfb2d55142b8fa0af580802192042",
      "parents": [
        "68085ab74277b399d3c6980a28d08eac1eef936f"
      ],
      "author": {
        "name": "Ahmed Qasid",
        "email": "ahmad.kassyd@gmail.com",
        "time": "Wed Jun 03 17:06:26 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Jun 03 22:06:26 2026 +0800"
      },
      "message": "fix: avoid topic fallback for non-Latin titles via pragmatic ASCII transliteration (#1526)\n\n# fix: avoid `topic` fallback for non-Latin titles via pragmatic ASCII\ntransliteration\n\n\u003e **Scope update (in response to review):** this PR is intentionally\nbroader than its original \"Arabic-only\" framing. The implementation\nchanges URL slug generation for **every non-Latin, non-CJK script** that\n`slugify` previously stripped — see *Scope* below for the explicit list.\nThe goal is *not* linguistically correct romanization; it is \"avoid\ncollapsing to `/topic` by producing a usable ASCII slug.\"\n\n## What this PR is (and isn\u0027t)\n\n**Goal:** when a question title contains characters outside Basic Latin\n/ Latin Extended / CJK Han, generate a URL slug that is a deterministic\nASCII approximation instead of letting `slugify` strip everything and\nfalling back to the literal `\"topic\"`.\n\n**Non-goal:** this is *not* a linguistically correct multi-language\nromanizer. The output is a machine-acceptable ASCII slug, not what a\nnative speaker would choose. For example, `こんにちは` → `konnichiha` (not\nthe more natural `kon\u0027nichiwa`), `ไทย` → `aithy` (not `thai`). Treat the\nslug as an opaque, stable, indexable identifier — the\npath-after-`/questions/\u003cid\u003e/` is for SEO and shareability, the canonical\nreference is always the ID.\n\n## The bug\n\nPure non-Latin titles previously got stripped by `slugify.Slugify`, hit\nthe empty-result fallback in `htmltext.UrlTitle`, and collapsed to the\nliteral slug `\"topic\"`. On a live multilingual site, every Arabic / Thai\n/ Japanese-hiragana / Korean / Hebrew / Cyrillic question ended up at\n`/questions/\u003cid\u003e/topic`.\n\n## The fix\n\n`UrlTitle()` gets a `convertNonLatin` pre-step that mirrors the existing\n`convertChinese` pre-step pattern, using\n`github.com/mozillazg/go-unidecode` (same author as `go-pinyin` already\nin the repo, to minimise new-dep friction).\n\n```\nUrlTitle(title)\n  → convertChinese(title)        // pre-existing: Han-block → pinyin\n  → convertNonLatin(title)       // NEW: detect non-Latin letters → unidecode to ASCII\n  → clearEmoji / slugify / url.QueryEscape / cutLongTitle (unchanged)\n```\n\nThe non-Latin detector skips ASCII, Latin-1 Supplement, Latin\nExtended-A/B, and CJK Han. Inputs that hit none of those non-Latin\nletter categories short-circuit and return unchanged, so Latin-only and\nChinese-only inputs remain byte-identical (pinned by tests).\n\n## Scope — what scripts are affected\n\nThis PR changes behavior for **any** title containing letters in scripts\nthat `slugify` doesn\u0027t handle. Confirmed by tests in\n`pkg/htmltext/htmltext_test.go`:\n\n| Script | Example title | Before | After |\n| --- | --- | --- | --- |\n| Arabic | `كيف حالك` | `topic` | `kyf-hlk` |\n| Mixed Latin + Arabic | `مرحبا hello` | `hello` | `mrhb-hello` |\n| Thai | `ไทย ไทย` | `topic` | `aithy-aithy` |\n| Japanese hiragana | `こんにちは` | `topic` | `konnichiha` |\n| Korean | `안녕하세요` | `topic` | `annyeonghaseyo` |\n| Hebrew | `שלום עולם` | `topic` | `shlvm-vlm` |\n| Cyrillic | `Привет мир` | `topic` | `privet-mir` |\n\n**Unchanged:**\n\n| Case | Behavior |\n| --- | --- |\n| Pure Latin (`hello world`) | unchanged → `hello-world` |\n| Pure Chinese (`这是一个，标题，title`) | unchanged → `zhe-shi-yi-ge-biao-ti`\n(pinyin path) |\n| Japanese with Han-block kanji (`日本`) | unchanged → `ri-ben` (caught by\npre-existing pinyin path; treated as Chinese reading, not Japanese — a\npre-existing limitation, **not** introduced by this PR) |\n| Emoji only (`😂😂😂`) | unchanged → `topic` |\n| Empty / whitespace | unchanged → `topic` |\n\n## Transliteration quality — explicit acknowledgement\n\n`go-unidecode` is a generic Unicode → ASCII approximation. It is **not**\na per-language romanization library. Specifically:\n\n- It will pick *one* approximation per codepoint regardless of language\ncontext. `ใ` → `ai` (Thai romanization is `i` or `ai` depending on\nstandard), `한` → `han`, `語` → `Yu` (Chinese pinyin reading even when\nused in Japanese), etc.\n- The result is *good enough* to be a stable, URL-safe,\nhuman-recognizable handle, but speakers of the source language will not\nconsider it \"correct.\"\n- It is deterministic, so the same title always produces the same slug —\nimportant since `url_title` is recomputed on every request.\n\nIf maintainers prefer to scope this PR more narrowly (e.g. Arabic only,\nand reject Thai/Hebrew/Cyrillic/etc.), the detector in\n`containsNonLatin` can be tightened to specific Unicode blocks — but\nthat means the other scripts continue to collapse to `topic`, which is\nthe bug we\u0027re trying to fix. I\u0027d argue the broader fix is preferable to\na piecemeal one, but happy to narrow if you want.\n\n## Live deployment / real-world verification\n\nThis patch has been running in production on\n**[ask.namasoft.com](https://ask.namasoft.com)** (an Apache Answer\ninstance we operate) since deployment, built directly from this branch\nvia `docker compose build`. The site hosts Arabic-language questions, so\nthe fix exercises the affected code path on every page load.\n\nSample question URL on the deployed instance:\n\n\u003e `https://ask.namasoft.com/questions/10010000000000115`\n\nThe slug in the URL is the transliterated Arabic title rather than\n`topic`. No data migration was needed since `url_title` is computed on\nevery request from `Title` and never persisted (see *Why this is safe to\nship* below).\n\n## Admin-configurable\n\nThe transliteration is gated by a package-level `atomic.Bool` (default\n**on**, since the current behavior is objectively broken for affected\nusers):\n\n- `htmltext.SetTransliterateNonLatin(enabled bool)`\n- `htmltext.IsTransliterateNonLatinEnabled() bool`\n\nThis is deliberately the minimum surface needed to satisfy \"the setting\nmust be readable from `UrlTitle()`\". A follow-up PR can add an admin UI\nsection that calls `SetTransliterateNonLatin` on save and on startup,\nwithout having to re-plumb every `htmltext.UrlTitle` call site through\n`context.Context`.\n\n**Default choice — please confirm:** I picked **default-on** because the\nexisting `topic` behavior is a bug for affected users. If you\u0027d prefer\ndefault-off for strict backward compat on existing installs, flip the\n`init()` in `pkg/htmltext/htmltext.go` to `Store(false)` and surface the\ntoggle as opt-in.\n\n## Why this is safe to ship\n\n- `url_title` is **not** a persisted column. It\u0027s not on the `Question`\nentity in `internal/entity/question_entity.go`, no migration has ever\nadded/dropped it, and every call site (`question_service.go`,\n`revision_service.go`, `vote_service.go`,\nsearch/report/review/rank/comment services, controllers, repos)\nrecomputes it from `Title` at response-build time via\n`htmltext.UrlTitle(...)`.\n- That means the fix is read-only: existing rows light up with correct\nslugs on the next request, with no migration and no data rewrite.\n- Rollback is just redeploying the prior image; nothing on disk changes.\n\n## Test coverage\n\n`pkg/htmltext/htmltext_test.go`:\n\n- **`TestUrlTitleTable`** — table-driven, one case per affected script\n(the full matrix above), plus:\n  - `empty` → `topic`\n  - `pure latin unchanged` → byte-identical to pre-fix\n- `pure chinese unchanged` → byte-identical to pre-fix (pins existing\npinyin behavior)\n- `japanese kanji goes through pinyin path unchanged` → documents the\npre-existing Han-block limitation\n  - `emoji only falls back to topic` → unchanged\n- `long arabic truncates at cutLongTitle boundary` → exercises the\n150-byte cap and UTF-8 boundary safety\n- **`TestUrlTitleTransliterationToggle`** — with the toggle off,\nnon-Latin titles collapse to `topic` (pre-fix behavior); with it on,\nthey transliterate.\n- Existing `TestUrlTitle` left untouched.\n\nTest plan for reviewers:\n\n- [ ] `go test ./pkg/htmltext/...` — all pass\n- [ ] Visit the live sample URL above and confirm slug is\ntransliterated, not `topic`\n- [ ] Verify Chinese / Latin / emoji-only / empty behavior is\nbyte-identical to `main` (covered by table tests)\n\n## Out of scope (intentionally)\n\n- No admin UI / site setting plumbing in this PR — see\n*Admin-configurable* above. Happy to do the React `Non-Latin Languages\nHandling` admin page + `SiteType` + service / controller / migration in\na follow-up if maintainers want it.\n- No change to the `\"topic\"` empty-result fallback.\n- No plugin interface for slug generation — mirrored the existing\n`convertChinese` pre-step pattern instead.\n- No per-language romanization library — this is an explicit non-goal;\nsee *Transliteration quality* above.\n\n## Issues / discussion\n\nI didn\u0027t find an existing upstream issue covering this — happy to be\npointed at one if there is.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\n---------\n\nCo-authored-by: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nCo-authored-by: LinkinStars \u003clinkinstar@foxmail.com\u003e"
    },
    {
      "commit": "68085ab74277b399d3c6980a28d08eac1eef936f",
      "tree": "811a614e751a670be9068372153e4102e8bcd183",
      "parents": [
        "7c210a4855270ef0b91d8e5471479b04f24c65ae"
      ],
      "author": {
        "name": "Luffy",
        "email": "52o@qq52o.cn",
        "time": "Tue Jun 02 11:50:54 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed Jun 03 22:03:58 2026 +0800"
      },
      "message": "fix: update license entries\n"
    },
    {
      "commit": "7c210a4855270ef0b91d8e5471479b04f24c65ae",
      "tree": "f683171e4d7357c16d227c23199a9884775b8708",
      "parents": [
        "2746bf5b455fa67f1bec6178c75121397a3816f5"
      ],
      "author": {
        "name": "Luke Gao",
        "email": "dhangao@hotmail.com",
        "time": "Sat May 30 15:33:54 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat May 30 15:33:54 2026 +0800"
      },
      "message": "feat: add reasoning content to AI conversation records and update related components (#1530)\n\nFix #1524 \n\nRoot cause\n\nDeepSeek\u0027s reasoning models stream reasoning_content alongside content.\nAnswer ignored it, so follow-up requests failed with 400: The\nreasoning_content in the thinking mode must be passed back to the API,\nand the thinking text was never shown or saved.\n\nFix\n\n- Capture reasoning_content from the stream and pass it back to theAPI\non subsequent rounds.\n - Persist it with the conversation (new DB column via migrationv2.0.2).\n- Render it in the chat UI as a collapsible \"Thinking…/Thoughts\"panel\nabove the answer.\n\nCompatibility\n\nNullable column, omitempty field, UI hides the panel when empty — old\nconversations and non-reasoning models behave exactly as before.\n\nDemo\n\n\n\nhttps://github.com/user-attachments/assets/49b1a2a1-9133-4ac2-bbeb-860215a50285"
    },
    {
      "commit": "2746bf5b455fa67f1bec6178c75121397a3816f5",
      "tree": "c5a4a5601496737906182383cdea79882b9b814a",
      "parents": [
        "f9941827b1dd19786e6d8f1e817add7593316114"
      ],
      "author": {
        "name": "robin",
        "email": "robin@rnode.me",
        "time": "Mon May 25 10:45:38 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 25 10:46:54 2026 +0800"
      },
      "message": "fix: update copyright year to 2026\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n"
    },
    {
      "commit": "f9941827b1dd19786e6d8f1e817add7593316114",
      "tree": "b5939bbf0817183595f7d8c50fa17f3f36467a54",
      "parents": [
        "48b4622aca60a8ddd230d5cca9304127cb3c0d9c"
      ],
      "author": {
        "name": "Luffy",
        "email": "52o@qq52o.cn",
        "time": "Fri May 22 16:18:15 2026 +0800"
      },
      "committer": {
        "name": "Robin",
        "email": "hello@robinren.me",
        "time": "Mon May 25 10:38:59 2026 +0800"
      },
      "message": "fix: missing uuid license\n"
    },
    {
      "commit": "48b4622aca60a8ddd230d5cca9304127cb3c0d9c",
      "tree": "8726b040a0022675d92c4bf2b214fc2451d77f1d",
      "parents": [
        "2a442d333cca27bd9a87601c927a8e3be7503987"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 12:09:59 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 14:27:00 2026 +0800"
      },
      "message": "chore: update Docker actions to specific versions for consistency\n"
    },
    {
      "commit": "2a442d333cca27bd9a87601c927a8e3be7503987",
      "tree": "808a0a244160629e52be92ce0a5fc08d453244ff",
      "parents": [
        "84204a718ae7e657aee0afc3013d5d9acd8806de"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 12:02:09 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 14:27:00 2026 +0800"
      },
      "message": "chore: update Docker actions to latest versions for improved stability\n"
    },
    {
      "commit": "84204a718ae7e657aee0afc3013d5d9acd8806de",
      "tree": "5817874eda5010090091b2b4e65fbdb9564c7db5",
      "parents": [
        "5be5c84a262e30535fbe00250aab14082ad035e3"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 11:42:35 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 14:27:00 2026 +0800"
      },
      "message": "chore: update GitHub Actions to use latest versions of setup actions\n"
    },
    {
      "commit": "5be5c84a262e30535fbe00250aab14082ad035e3",
      "tree": "655000fbd80f0d843b88eb6db519a2c0ab41bfcb",
      "parents": [
        "a218924ba0174fd4518b9b21ea36f7f63eddef7d"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 11:28:50 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 11:29:14 2026 +0800"
      },
      "message": "chore: update version to 2.0.1\n"
    },
    {
      "commit": "a218924ba0174fd4518b9b21ea36f7f63eddef7d",
      "tree": "00a62787c77b0b9728b5cc4a4a2f97307db6f14b",
      "parents": [
        "b1da65fbe49fa48a3e9bb626e20df667ee8d8bb8",
        "316417961ad03525d8f2b06263a3a900e1664aa3"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 11:20:08 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Wed May 20 11:20:08 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/dev\u0027 into test\n"
    },
    {
      "commit": "316417961ad03525d8f2b06263a3a900e1664aa3",
      "tree": "4a4c674385d6f9a7d05093a9122e15ae5c52a624",
      "parents": [
        "ab236f2d6a272c5dc6de313b83086e6392e089e3"
      ],
      "author": {
        "name": "robin",
        "email": "robin@rnode.me",
        "time": "Mon May 18 20:01:32 2026 +0800"
      },
      "committer": {
        "name": "Robin",
        "email": "hello@robinren.me",
        "time": "Tue May 19 10:15:36 2026 +0800"
      },
      "message": "fix: attachment upload broken after v2.0.0 upgrade (#1525)\n\n- base.ts: add replaceRange method to CodeMirror adapter\n- file.tsx: use replaceSelection to insert loading text (fixes RangeError)\n- file.tsx: use useState/useEffect for stable editorState reference in async callbacks\n\nCo-authored-by: Copilot \u003c223556219+Copilot@users.noreply.github.com\u003e\n"
    },
    {
      "commit": "b1da65fbe49fa48a3e9bb626e20df667ee8d8bb8",
      "tree": "a61f00a28d96a6c97e610bc82aec7734726b1012",
      "parents": [
        "0aca063077ccc6c41a514142713ee8b5bf06c991",
        "11091244f64e5a7e472edcd477c1ff4124eca7c3"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 11 12:16:58 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 11 12:16:58 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/fix/2.0.1/chat\u0027 into test\n"
    },
    {
      "commit": "0aca063077ccc6c41a514142713ee8b5bf06c991",
      "tree": "8f686bb207f3e9e0f1ad188da3d9daf56cf2cbad",
      "parents": [
        "97924574c4e92b46567ce866feb84f490f2195e4",
        "ab236f2d6a272c5dc6de313b83086e6392e089e3"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 11 12:16:49 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 11 12:16:49 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/dev\u0027 into test\n"
    },
    {
      "commit": "11091244f64e5a7e472edcd477c1ff4124eca7c3",
      "tree": "34160ab0bc5ad774db7ae5569030f4674163f6de",
      "parents": [
        "d1a4092c61ccd41988d1033fce47eb513adb433e"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 11 12:05:18 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Mon May 11 12:05:18 2026 +0800"
      },
      "message": "fix(user): enhance avatar validation by adding checks for custom avatars and file ownership\n"
    },
    {
      "commit": "d1a4092c61ccd41988d1033fce47eb513adb433e",
      "tree": "6e91a7d0ca8b6931eb2e29b73c7d3cb549bcf3f7",
      "parents": [
        "11c80384f13a27e27c871c05bb353489d1363ee2"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Sat May 09 16:07:09 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Sat May 09 16:07:09 2026 +0800"
      },
      "message": "fix(email): enhance email templates by escaping HTML characters in dynamic content\n"
    },
    {
      "commit": "11c80384f13a27e27c871c05bb353489d1363ee2",
      "tree": "cd0076065a41e7892be337ed4389fff7c88294f5",
      "parents": [
        "9da00a807db70ebd9033dc8298707d0a1e68ce52"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Sat May 09 15:13:50 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Sat May 09 15:13:50 2026 +0800"
      },
      "message": "fix(chat): enhance visibility checks by adding admin moderator support in answer and comment services\n"
    },
    {
      "commit": "ab236f2d6a272c5dc6de313b83086e6392e089e3",
      "tree": "07729ca5e7df3b0f727c253ac7122080ba8c64cb",
      "parents": [
        "e3831b6f525a17ce26e3a4d6c69076e96e2f9c59"
      ],
      "author": {
        "name": "Herrtian",
        "email": "70463940+Herrtian@users.noreply.github.com",
        "time": "Fri May 01 17:19:43 2026 +0200"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri May 08 20:09:04 2026 +0800"
      },
      "message": "fix(helm): keep install port aligned with service port\n\nSigned-off-by: Tian Teng \u003ctian.teng@emerson.com\u003e\n"
    },
    {
      "commit": "97924574c4e92b46567ce866feb84f490f2195e4",
      "tree": "228b8b74788aec73a1c7d6b6d95ac89010d98d44",
      "parents": [
        "2dbe594da270b533eff2d88ff7e62316078b14de",
        "e3831b6f525a17ce26e3a4d6c69076e96e2f9c59"
      ],
      "author": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri May 08 19:58:22 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri May 08 19:58:22 2026 +0800"
      },
      "message": "Merge remote-tracking branch \u0027origin/dev\u0027 into test\n"
    },
    {
      "commit": "e3831b6f525a17ce26e3a4d6c69076e96e2f9c59",
      "tree": "c9d2cce2d422e93b7307733d2888c169cbc240c9",
      "parents": [
        "c93f3bc5c354853f70aa78b9c53b4507585d809c"
      ],
      "author": {
        "name": "hgaol",
        "email": "dhangao@hotmail.com",
        "time": "Wed Apr 22 23:31:07 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri May 08 19:57:19 2026 +0800"
      },
      "message": "refactor: streamline plugin initialization by removing redundant search syncer registration\n"
    },
    {
      "commit": "c93f3bc5c354853f70aa78b9c53b4507585d809c",
      "tree": "e0ccfba422278ca5fbbb6dee6432ee2a297a9b32",
      "parents": [
        "278fdfd8c3bc53521b42ccf5b6d38d8203aa5c90"
      ],
      "author": {
        "name": "hgaol",
        "email": "dhangao@hotmail.com",
        "time": "Wed Apr 22 23:30:51 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri May 08 19:57:19 2026 +0800"
      },
      "message": "refactor: simplify SaveSiteAI method by removing unnecessary error handling\n"
    },
    {
      "commit": "278fdfd8c3bc53521b42ccf5b6d38d8203aa5c90",
      "tree": "9499f46ec2f55587f682fa8862ce8a133f778b29",
      "parents": [
        "2836d0ea819906fd08574261d4f3fd696f9967cb"
      ],
      "author": {
        "name": "hgaol",
        "email": "dhangao@hotmail.com",
        "time": "Wed Apr 22 22:58:58 2026 +0800"
      },
      "committer": {
        "name": "LinkinStars",
        "email": "linkinstar@foxmail.com",
        "time": "Fri May 08 19:57:19 2026 +0800"
      },
      "message": "refactor: rename UpdateSearch to updateSearch for consistency\n"
    }
  ],
  "next": "2836d0ea819906fd08574261d4f3fd696f9967cb"
}
