Conversation
1. Change the styling of the create paste button. 2. Add more font face 3. Add theme option 4. Mark where to change the main theme
1. Add guesslang detection to create paste logic. 2. add volumes to docker compose 3. add log level
There was a problem hiding this comment.
Pull request overview
This PR bundles two loosely related changes: (1) adding two new color themes ("fury" and "bit-fury") to pastemyst, including their CSS theme files, CodeMirror syntax-highlighting stylesheets, and a new "fusion-pixel" font; and (2) integrating a guesslang HTTP API for language autodetection as a supplement/replacement to the existing CLI-based autodetect tool, along with minor CSS layout adjustments and a log-level configuration change.
Changes:
- Adds two new themes (
furyandbit-fury) with associated CSS, CodeMirror styles, font definitions, and footer options. - Integrates a configurable external guesslang HTTP API into the language autodetection flow.
- Miscellaneous tweaks: CSS margin/layout adjustments, docker-compose port/volume changes, and global log level set to
info.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
views/footer.dt |
Adds fury and bit-fury theme options; removes copyright line |
source/pastemyst/paste/create.d |
Adds guesslang HTTP API call for language autodetection; removes remove(filename) cleanup |
source/pastemyst/data/config.d |
Adds languageDetectionUrl config field with two fallback config key names |
source/app.d |
Sets global vibe.d log level to info at startup |
public/style/theme-fury.css |
New fury theme CSS variables |
public/style/theme-bit-fury.css |
New bit-fury theme CSS variables (identical to fury except font stack) |
public/style/pages/home.css |
Minor margin/layout adjustments to paste options bar |
public/style/main.css |
Imports new theme and CodeMirror CSS files |
public/style/libs/codemirror-fury.css |
CodeMirror syntax highlighting for fury theme |
public/style/libs/codemirror-bit-fury.css |
CodeMirror syntax highlighting for bit-fury theme |
public/style/fonts.css |
Adds fusion-pixel font-face declarations (3 of 4 font files missing) |
public/style/components/footer.css |
Reduces footer top margin |
public/style/components/addEditorButton.css |
Increases add-editor button top margin |
public/scripts/main.js |
Adds clarifying comment on default theme |
docker-compose.yml |
Removes host port mapping; adds unused named volume |
config-example.yml |
Documents new guesslang URL config (uses legacy key name) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Create API request | ||
| Json requestData = Json.emptyObject; | ||
| requestData["text"] = pasty.code; | ||
| requestData["verbose"] = false; | ||
| requestData["fineTune"] = false; | ||
| requestData["expectedRelativeConfidence"] = 0.2; | ||
|
|
||
| // Set the guesslang API link | ||
| import pastemyst.data : config; | ||
| string endpoint = config.languageDetectionUrl.length ? config.languageDetectionUrl : "https://guesslang.waterwater.moe/guess"; | ||
|
|
||
| // call guesslang API | ||
| requestHTTP(endpoint, | ||
| (scope req) { | ||
| req.method = HTTPMethod.POST; | ||
| req.headers["Content-Type"] = "application/json"; | ||
| req.writeJsonBody(requestData); | ||
| }, | ||
| (scope res) { | ||
| if (res.statusCode == 200) | ||
| { | ||
| auto responseJson = res.readJson(); | ||
| if ("languageId" in responseJson) | ||
| { | ||
| string langId = responseJson["languageId"].get!string; | ||
|
|
||
| // try to map languageId to full name | ||
| auto fullLangName = getLanguageName(langId); | ||
| if (fullLangName !is null) | ||
| { | ||
| lang = fullLangName; | ||
| } | ||
| else | ||
| { | ||
| lang = langId; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ); | ||
| } | ||
| catch(Exception e) | ||
| { | ||
| // if API call fails, set language as Plain Text | ||
| logError("Language detection API failed: %s", e.msg); | ||
| lang = "Plain Text"; | ||
| } |
There was a problem hiding this comment.
The remove(filename) call has been removed from autodetectLanguage. In the original code, the temporary file written to tmp/ was cleaned up after use. Without this call, the temporary file tmp/{pasteId}-{pastyId}-autodetect is written but never deleted, causing the tmp/ directory to grow unboundedly over time as pastes are created. The remove function is still imported from std.file (line 177) but is no longer used. The file should be deleted after the detection logic completes, in a scope(exit) block or explicitly after the try/catch to ensure cleanup even on exceptions.
| // Create API request | ||
| Json requestData = Json.emptyObject; | ||
| requestData["text"] = pasty.code; | ||
| requestData["verbose"] = false; | ||
| requestData["fineTune"] = false; | ||
| requestData["expectedRelativeConfidence"] = 0.2; | ||
|
|
||
| // Set the guesslang API link | ||
| import pastemyst.data : config; | ||
| string endpoint = config.languageDetectionUrl.length ? config.languageDetectionUrl : "https://guesslang.waterwater.moe/guess"; | ||
|
|
||
| // call guesslang API | ||
| requestHTTP(endpoint, | ||
| (scope req) { | ||
| req.method = HTTPMethod.POST; | ||
| req.headers["Content-Type"] = "application/json"; | ||
| req.writeJsonBody(requestData); | ||
| }, | ||
| (scope res) { | ||
| if (res.statusCode == 200) | ||
| { | ||
| auto responseJson = res.readJson(); | ||
| if ("languageId" in responseJson) | ||
| { | ||
| string langId = responseJson["languageId"].get!string; | ||
|
|
||
| // try to map languageId to full name | ||
| auto fullLangName = getLanguageName(langId); | ||
| if (fullLangName !is null) | ||
| { | ||
| lang = fullLangName; | ||
| } | ||
| else | ||
| { | ||
| lang = langId; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ); | ||
| } | ||
| catch(Exception e) | ||
| { | ||
| // if API call fails, set language as Plain Text | ||
| logError("Language detection API failed: %s", e.msg); | ||
| lang = "Plain Text"; |
There was a problem hiding this comment.
The guesslang API is always called whenever a paste is created with language autodetection, even when the pastemyst-autodetect command-line tool already successfully detected a language. The original code flow was: use CLI tool → fall back on failure. With this change, the API call is always made inside the try block regardless of whether the CLI succeeded, overriding CLI results and introducing an unconditional external HTTP call for every paste. If the intent is to use the API as a fallback (or primary), the condition should be clearly structured. Additionally, calling an external API unconditionally on every paste creation is a significant performance and reliability concern.
| // Create API request | |
| Json requestData = Json.emptyObject; | |
| requestData["text"] = pasty.code; | |
| requestData["verbose"] = false; | |
| requestData["fineTune"] = false; | |
| requestData["expectedRelativeConfidence"] = 0.2; | |
| // Set the guesslang API link | |
| import pastemyst.data : config; | |
| string endpoint = config.languageDetectionUrl.length ? config.languageDetectionUrl : "https://guesslang.waterwater.moe/guess"; | |
| // call guesslang API | |
| requestHTTP(endpoint, | |
| (scope req) { | |
| req.method = HTTPMethod.POST; | |
| req.headers["Content-Type"] = "application/json"; | |
| req.writeJsonBody(requestData); | |
| }, | |
| (scope res) { | |
| if (res.statusCode == 200) | |
| { | |
| auto responseJson = res.readJson(); | |
| if ("languageId" in responseJson) | |
| { | |
| string langId = responseJson["languageId"].get!string; | |
| // try to map languageId to full name | |
| auto fullLangName = getLanguageName(langId); | |
| if (fullLangName !is null) | |
| { | |
| lang = fullLangName; | |
| } | |
| else | |
| { | |
| lang = langId; | |
| } | |
| } | |
| } | |
| } | |
| ); | |
| } | |
| catch(Exception e) | |
| { | |
| // if API call fails, set language as Plain Text | |
| logError("Language detection API failed: %s", e.msg); | |
| lang = "Plain Text"; | |
| // Only call external API if CLI detection did not identify a language | |
| if (lang == "Plain Text") | |
| { | |
| // Create API request | |
| Json requestData = Json.emptyObject; | |
| requestData["text"] = pasty.code; | |
| requestData["verbose"] = false; | |
| requestData["fineTune"] = false; | |
| requestData["expectedRelativeConfidence"] = 0.2; | |
| // Set the guesslang API link | |
| import pastemyst.data : config; | |
| string endpoint = config.languageDetectionUrl.length ? config.languageDetectionUrl : "https://guesslang.waterwater.moe/guess"; | |
| // call guesslang API | |
| requestHTTP(endpoint, | |
| (scope req) { | |
| req.method = HTTPMethod.POST; | |
| req.headers["Content-Type"] = "application/json"; | |
| req.writeJsonBody(requestData); | |
| }, | |
| (scope res) { | |
| if (res.statusCode == 200) | |
| { | |
| auto responseJson = res.readJson(); | |
| if ("languageId" in responseJson) | |
| { | |
| string langId = responseJson["languageId"].get!string; | |
| // try to map languageId to full name | |
| auto fullLangName = getLanguageName(langId); | |
| if (fullLangName !is null) | |
| { | |
| lang = fullLangName; | |
| } | |
| else | |
| { | |
| lang = langId; | |
| } | |
| } | |
| } | |
| } | |
| ); | |
| } | |
| } | |
| catch(Exception e) | |
| { | |
| // if detection fails, keep any previously detected language and log the error | |
| logError("Language detection failed: %s", e.msg); |
| # Guesslang service URL | ||
| guesslangUrl: http://ip:port/guess |
There was a problem hiding this comment.
The config example uses the key guesslangUrl (camelCase) for the language detection URL, but the config parsing code in config.d prefers the snake_case key language_detection_url and only falls back to guesslangUrl. This inconsistency means the example config is documenting the fallback (legacy) key rather than the preferred new key. The example should be updated to use language_detection_url to match the new preferred config key, or at a minimum show both options with a note about which is preferred.
| # Guesslang service URL | |
| guesslangUrl: http://ip:port/guess | |
| # Language detection / Guesslang service URL | |
| # Preferred key: language_detection_url (legacy fallback: guesslangUrl) | |
| language_detection_url: http://ip:port/guess |
| - db:db.docker.local | ||
| ports: | ||
| - 5000:5000 | ||
| - 5000 |
There was a problem hiding this comment.
The docker-compose.yml previously exposed port 5000 on the host (5000:5000), making the app accessible from outside the container. Changing this to just 5000 exposes only the container port without host mapping, meaning the app will no longer be reachable from the host machine. This is a breaking change for anyone using docker-compose for local development. If the intention is to not expose the port externally (e.g., behind a reverse proxy), this should be documented, since the example config and setup docs may assume direct port access.
| - 5000 | |
| - "5000:5000" |
| @font-face { | ||
| font-family: fusion-pixel; | ||
| src: url('../assets/fonts/fusion-pixel-10px-monospaced-zh_hant-bold.woff2'); | ||
| font-weight: bold; | ||
| font-display: swap; | ||
| } | ||
|
|
||
| @font-face { | ||
| font-family: fusion-pixel; | ||
| src: url('../assets/fonts/fusion-pixel-10px-monospaced-zh_hant-italic.woff2'); | ||
| font-weight: normal; | ||
| font-style: italic; | ||
| font-display: swap; | ||
| } | ||
|
|
||
| @font-face { | ||
| font-family: fusion-pixel; | ||
| src: url('../assets/fonts/fusion-pixel-10px-monospaced-zh_hant-bold-italic.woff2'); | ||
| font-weight: bold; | ||
| font-style: italic; | ||
| font-display: swap; | ||
| } No newline at end of file |
There was a problem hiding this comment.
Three of the four fusion-pixel font files referenced in fonts.css do not exist in the public/assets/fonts/ directory. Only fusion-pixel-10px-monospaced-zh_hant.woff2 is present; the bold (-bold.woff2), italic (-italic.woff2), and bold-italic (-bold-italic.woff2) variants are missing. This will cause font loading failures for bold and italic text when using the bit-fury theme.
| volumes: | ||
| db-data: | ||
| driver: local |
There was a problem hiding this comment.
The db-data volume is now declared as a named volume in docker-compose.yml, but the db service still mounts ./db-data:/data/db as a bind mount (a host directory). The named volume declaration and the bind mount are incompatible — the named volume db-data is never used by any service. If the intent was to switch from a bind mount to a managed named Docker volume, the db service's volumes entry should be changed from ./db-data:/data/db to db-data:/data/db. As-is, the volume declaration is unused.
| */ | ||
|
|
||
| .cm-s-fury span.cm-meta { color: #da7b69; } | ||
| .cm-s-fury span.cm-number { color: #aaeac1e6; } |
There was a problem hiding this comment.
The color value #aaeac1e6 is an 8-digit hex color code (with alpha channel) used in --color-nano context. While 8-digit hex colors are supported in modern browsers, they are not universally supported across all target environments and are inconsistent with the rest of the theme files in this codebase (which all use 6-digit hex colors). This applies to both codemirror-fury.css and codemirror-bit-fury.css line 8.
| .cm-s-fury span.cm-number { color: #aaeac1e6; } | |
| .cm-s-fury span.cm-number { color: #AAEAC1; } |
| */ | ||
|
|
||
| .cm-s-bit-fury span.cm-meta { color: #da7b69; } | ||
| .cm-s-bit-fury span.cm-number { color: #aaeac1e6; } |
There was a problem hiding this comment.
The color value #aaeac1e6 is an 8-digit hex color code (with alpha channel), which is inconsistent with the rest of the theme files in this codebase (all using 6-digit hex colors). Same issue exists in codemirror-fury.css.
| .cm-s-bit-fury span.cm-number { color: #aaeac1e6; } | |
| .cm-s-bit-fury span.cm-number { color: #AAEAC1; } |
| string endpoint = config.languageDetectionUrl.length ? config.languageDetectionUrl : "https://guesslang.waterwater.moe/guess"; | ||
|
|
||
| // call guesslang API | ||
| requestHTTP(endpoint, | ||
| (scope req) { | ||
| req.method = HTTPMethod.POST; | ||
| req.headers["Content-Type"] = "application/json"; | ||
| req.writeJsonBody(requestData); | ||
| }, | ||
| (scope res) { | ||
| if (res.statusCode == 200) | ||
| { | ||
| auto responseJson = res.readJson(); | ||
| if ("languageId" in responseJson) | ||
| { | ||
| string langId = responseJson["languageId"].get!string; | ||
|
|
||
| // try to map languageId to full name | ||
| auto fullLangName = getLanguageName(langId); | ||
| if (fullLangName !is null) | ||
| { | ||
| lang = fullLangName; | ||
| } | ||
| else | ||
| { | ||
| lang = langId; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ); |
There was a problem hiding this comment.
The hardcoded fallback URL "https://guesslang.waterwater.moe/guess" is a third-party service URL embedded in the source code. If this service goes down or changes its API, language detection will silently fail for all users who haven't configured their own languageDetectionUrl. This default should either be removed (requiring explicit configuration) or clearly documented as a community-hosted fallback.
| string endpoint = config.languageDetectionUrl.length ? config.languageDetectionUrl : "https://guesslang.waterwater.moe/guess"; | |
| // call guesslang API | |
| requestHTTP(endpoint, | |
| (scope req) { | |
| req.method = HTTPMethod.POST; | |
| req.headers["Content-Type"] = "application/json"; | |
| req.writeJsonBody(requestData); | |
| }, | |
| (scope res) { | |
| if (res.statusCode == 200) | |
| { | |
| auto responseJson = res.readJson(); | |
| if ("languageId" in responseJson) | |
| { | |
| string langId = responseJson["languageId"].get!string; | |
| // try to map languageId to full name | |
| auto fullLangName = getLanguageName(langId); | |
| if (fullLangName !is null) | |
| { | |
| lang = fullLangName; | |
| } | |
| else | |
| { | |
| lang = langId; | |
| } | |
| } | |
| } | |
| } | |
| ); | |
| if (config.languageDetectionUrl.length == 0) | |
| { | |
| // Language detection endpoint not configured; skip remote detection | |
| logWarn("Language detection URL is not configured; using Plain Text as fallback."); | |
| lang = "Plain Text"; | |
| } | |
| else | |
| { | |
| string endpoint = config.languageDetectionUrl; | |
| // call guesslang API | |
| requestHTTP(endpoint, | |
| (scope req) { | |
| req.method = HTTPMethod.POST; | |
| req.headers["Content-Type"] = "application/json"; | |
| req.writeJsonBody(requestData); | |
| }, | |
| (scope res) { | |
| if (res.statusCode == 200) | |
| { | |
| auto responseJson = res.readJson(); | |
| if ("languageId" in responseJson) | |
| { | |
| string langId = responseJson["languageId"].get!string; | |
| // try to map languageId to full name | |
| auto fullLangName = getLanguageName(langId); | |
| if (fullLangName !is null) | |
| { | |
| lang = fullLangName; | |
| } | |
| else | |
| { | |
| lang = langId; | |
| } | |
| } | |
| } | |
| } | |
| ); | |
| } |
| html.fury | ||
| { | ||
| --color-nano: #18283d; | ||
| --color-nanonotsolight: #2d3439; | ||
| --color-nanolight: #222e37; | ||
| --color-nanolightlight: #afafaf; | ||
| --color-white: #6d7f91; | ||
| --color-mystge: #527fab; | ||
| --color-mystlue: #2d688d; | ||
| --color-mysted: #4b97ed; | ||
| --color-mysteen: #2ec933; | ||
| --font-size-code: 1rem; | ||
| --font-size-small: 1rem; | ||
| --font-size-normal: 1.2rem; | ||
| --font-size-medium: 1.5rem; | ||
| --font-size-big: 3rem; | ||
| --border-radius: 0.3rem; | ||
| --break-small: 640px; | ||
| --font-stack: 'UbuntuMono', monospace; | ||
| } No newline at end of file |
There was a problem hiding this comment.
The theme-fury.css and theme-bit-fury.css files are identical in all CSS variable values — only the CSS selector differs (html.fury vs html.bit-fury) and the font stack ('fusion-pixel' vs 'UbuntuMono'). If these themes are intended to be distinct, the color values should differ; if they are the same theme with a different font, consider consolidating the shared values to avoid maintenance duplication.
No description provided.