Skip to content

Add WordLex (offline dictionary) extension - #279

Open
vedesh-padal wants to merge 13 commits into
vicinaehq:mainfrom
vedesh-padal:add-wordlex-extension
Open

Add WordLex (offline dictionary) extension#279
vedesh-padal wants to merge 13 commits into
vicinaehq:mainfrom
vedesh-padal:add-wordlex-extension

Conversation

@vedesh-padal

@vedesh-padal vedesh-padal commented May 24, 2026

Copy link
Copy Markdown

This Pull Request introduces the WordLex offline dictionary extension to the Vicinae ecosystem.

WordLex is a blisteringly fast, offline-first English dictionary and thesaurus powered by the comprehensive Open English WordNet (OEWN) 2025 database (~150,000+ words, 120,000+ synsets).
WordLex Desktop App

This extension brings instant dictionary lookups, synonyms, antonyms, and lexical relations directly to your keyboard launcher without any external API calls or internet dependencies.


🌟 Features Included

  • Prefix Search (search-dictionary): Lightning-fast type-ahead search through 150k+ words. It loads the top 5 results' full detail inline, while seamlessly falling back to a compact search definition for lower results to completely bypass UI blocking.
  • Define Clipboard Word (define-clipboard): Instantly look up the word in your clipboard or active text selection and display its detailed entry in a full-screen Detail view.
  • Random Word (random-word): Fetch a random interesting word from the database—perfect for vocabulary building.
  • Full Markdown Detail Support: Renders pronunciation, part-of-speech groupings, definitions, real-world examples, and semantic relations (Hypernyms, Hyponyms, Meronyms, Holonyms) inside Vicinae's native markdown widget.
  • Command Actions:
    • Enter: Navigate to a dedicated full-screen markdown view.
    • Cmd+C: Copy the clean formatted definition.
    • Cmd+Shift+P: Paste the looked-up word directly into your frontmost active window.
    • Cmd+W: Cross-reference the word instantly on Wiktionary.
    • Cmd+O: Launch/Focus the native WordLex GUI desktop app with the searched word pre-focused.

🔌 Prerequisites & Integration Details

  • Requires the WordLex desktop app (v2.0.0+) to be installed, with the wordlex binary on your $PATH.
    Download it from the GitHub Releases page.
  • To keep a zero-duplication database model, the extension shells out to the installed wordlex binary and reuses its bundled OEWN 2025 SQLite database — no data is copied into the extension.
  • Lookups run as parameterized process arguments (--cli-json, --search-json, --random-json) — no shell interpolation — and are executed asynchronously with debounced input, so the launcher UI never blocks while the local database is queried.
  • No external API calls and no internet connection are required; everything runs against the local database.

📸 Screenshots & Showcase

1. Dictionary Search & Autocomplete

Dictionary Search & Autocomplete

2. Full Definition View

image

3. Commands Available

image

4. Random Word Explorer

image

🧪 How to Test Locally

  1. Ensure you have WordLex v2.0.0+ installed:
    wordlex --version
    # Output should show v2.0.0 or higher
  2. Install extension dependencies:
    npm install
  3. Run the development loop:
    npm run dev
  4. Test the following paths:
    • Type a word (e.g. ephemeral) in the launcher to verify prefix matching.
    • Use arrow keys to navigate results to ensure smooth cache-populated detail changes.
    • Press Enter to drill into the full view, then try pressing Cmd+W to verify browser redirection.
    • Copy a word to your clipboard and trigger the Define Clipboard Word command.
    • Trigger the Random Word command.

🏁 Submission Checklist

  • Tested locally on Linux/Vicinae
  • Ran npm run lint and resolved all warnings (0 errors)
  • Ran npm run typecheck to confirm complete TypeScript coverage
  • Checked package.json manifest matches naming, author format, and category standards
  • High-res extension_icon.png included under /assets

- Add dynamically detected cmdModifier constant ('cmd' on macOS/Raycast, 'ctrl' on Linux/Vicinae/GNOME) to prevent Super key hijack
- Apply cmdModifier to all keyboard shortcut modifiers throughout search-dictionary, define-clipboard, and random-word commands
- Re-formatted and validated types
@vedesh-padal
vedesh-padal marked this pull request as ready for review May 24, 2026 19:13
@vedesh-padal vedesh-padal changed the title Add wordlex extension Add WordLex (offline dictionary) extension May 24, 2026
@vedesh-padal

vedesh-padal commented May 24, 2026

Copy link
Copy Markdown
Author

Hey @aurelleb,

I've been using Vicinae on Linux for a while now (switched from Windows about a year ago, missed having Raycast, and Vicinae has been a lifesaver). I wanted to experiment with building my first extension for it, so I put this together.

It's an offline dictionary extension that queries my app, WordLex (which uses the offline OEWN 2025 SQLite database). I've tested it locally to make sure the building (vici build), typechecking, and linting all pass completely clean.

Since it's my first PR here. Whenever you get a chance, take a look and approve...

Really appreciate all the work you've put into Vicinae!

@aurelleb

aurelleb commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@clankus-aurelius review

@clankus-aurelius

clankus-aurelius commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Thanks for contributing an extension to Vicinae! 👋

Before publication, this pull request receives two reviews:

  1. An automated review for extension guidelines, safety, error handling, and likely correctness issues.
  2. A final review from a Vicinae maintainer.

Ready for human review. The automated reviewer approved the latest commit and a maintainer has been notified.

No blocking findings remain on the latest commit.

The automated reviewer examines only the current commit. New commits invalidate its previous decision and start another review.

@clankus-aurelius clankus-aurelius added the ai-reviewing Automated extension review is running label Aug 2, 2026

@clankus-aurelius clankus-aurelius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension has one publication-blocking command-injection path plus several runtime, manifest, dependency, and quality issues.


Automated review found 1 publication-blocking issue.

Comment thread extensions/wordlex/src/lib/wordlex.ts Outdated
Comment thread extensions/wordlex/src/search-dictionary.tsx Outdated
Comment thread extensions/wordlex/src/random-word.tsx Outdated
Comment thread extensions/wordlex/src/lib/wordlex.ts Outdated
Comment thread extensions/wordlex/package.json Outdated
Comment thread extensions/wordlex/package.json Outdated
Comment thread extensions/wordlex/src/lib/formatter.ts Outdated
@clankus-aurelius clankus-aurelius added ai-changes-requested Automated review found blocking issues and removed ai-reviewing Automated extension review is running labels Aug 2, 2026

@aurelleb aurelleb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the nice words, please address @clankus-aurelius 's findings and we are good

Addresses SECURITY-002 (blocking): search text and clipboard-derived words
were interpolated into shell strings passed to execSync, e.g.
'wordlex --cli-json "<word>"', so interior shell syntax could escape the
quoted argument and execute unintended commands.

Switch lookupWord, searchWords, and randomWord to execFileSync with
argument arrays (no shell involved). No API or behavior change; all
callers are unaffected.
Addresses CORRECTNESS-001: onSearchTextChange ran one synchronous search
plus up to five synchronous detail lookups per keystroke. With the declared
three-second timeout, the handler could block Vicinae for up to 18 seconds
and prevent the loading state from rendering.

- wordlex.ts: add async lookupWordAsync/searchWordsAsync built on execFile,
  sharing a single error mapper that also detects ENOENT (missing binary)
- search-dictionary.tsx: debounced input, stale-response guard via a
  monotonic sequence number, and asynchronous parallel detail preloads
  before items mount; WordDetailView loads its detail asynchronously with
  loading/error states instead of blocking during render
- remove the now-unused sync searchWords
Addresses UX-001: randomWord() was called synchronously during render, so
the throttled CLI call could block the UI for up to three seconds and a
missing binary was silently swallowed (execFileSync ENOENT) with only the
canned 'Is WordLex installed?' fallback.

- random-word.tsx: load the random word in an effect with explicit
  loading/error/success states; fire a failure toast and show the
  underlying error message when the binary is missing or a lookup fails
- define-clipboard.tsx: use lookupWordAsync so the lookup is non-blocking
- wordlex.ts: drop the now-unused sync lookupWord/randomWord and add an
  async randomWordAsync; all shell-out calls are now async
Addresses UX-001: spawn reports a missing binary asynchronously via the
'error' event, so the try/catch around spawn never fired and the toast
could not appear when launching the WordLex GUI failed.

Attach an 'error' listener on the child process to show an actionable
failure toast (mentioning PATH) whenever the binary is unavailable.
- package.json: describe the WordLex desktop app prerequisite (MANIFEST-001)
- remove the unused eslint-plugin-react-hooks devDependency from
  package.json and package-lock.json (DEPENDENCY-001)
- formatter.ts: delete the unused formatShortDefinition export (QUALITY-001)
Wrap the part-of-speech list in parentheses and drop the truncated
short-definition accessory from each result row; the definition is already
shown in the inline detail pane.
@clankus-aurelius clankus-aurelius added ai-reviewing Automated extension review is running and removed ai-changes-requested Automated review found blocking issues labels Aug 6, 2026

@clankus-aurelius clankus-aurelius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search command does not distinguish a completed zero-result search from its initial empty state.


Automated extension review passed. A maintainer review is still required.

This is an AI-generated first pass and may be mistaken. If a finding is unclear or incorrect, reply in the relevant thread and mention @aurelleb.

Comment thread extensions/wordlex/src/search-dictionary.tsx
@clankus-aurelius clankus-aurelius added human-reviewable Automated review passed; ready for maintainer review and removed ai-reviewing Automated extension review is running labels Aug 6, 2026
@clankus-aurelius

Copy link
Copy Markdown
Collaborator

@aurelleb automated review passed for 86991a3; this extension is ready for your review.

Addresses UX-002: a completed search that returned no matches rendered the
same 'Search the Dictionary' empty view as the untouched initial state, so
users could not tell the two apart.

Track the current query in state and render a dedicated 'No Results Found'
empty view (with the query echoed back) once a search finishes with zero
matches. The initial prompt is only shown when nothing has been typed yet,
and loading is never mistaken for an empty result.
@clankus-aurelius clankus-aurelius added ai-reviewing Automated extension review is running and removed human-reviewable Automated review passed; ready for maintainer review labels Aug 6, 2026

@clankus-aurelius clankus-aurelius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search response guard can still publish stale results while the user continues typing.


Automated extension review passed. A maintainer review is still required.

This is an AI-generated first pass and may be mistaken. If a finding is unclear or incorrect, reply in the relevant thread and mention @aurelleb.

Comment thread extensions/wordlex/src/search-dictionary.tsx Outdated
@clankus-aurelius clankus-aurelius added human-reviewable Automated review passed; ready for maintainer review and removed ai-reviewing Automated extension review is running labels Aug 6, 2026
@clankus-aurelius

Copy link
Copy Markdown
Collaborator

@aurelleb automated review passed for bd2966f; this extension is ready for your review.

Addresses CORRECTNESS-001 (re-review): the search generation counter
advanced only inside the debounce callback, so a search already in flight
from a previous keystroke could commit its results after the query had
changed, as long as no newer debounce timer had fired yet. During
continuous typing this window let stale results briefly replace the
current query's view.

Advance the generation synchronously on every text change and capture it
for the debounced request; the existing generation checks are retained.
@clankus-aurelius clankus-aurelius added ai-reviewing Automated extension review is running and removed human-reviewable Automated review passed; ready for maintainer review labels Aug 6, 2026

@clankus-aurelius clankus-aurelius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension has one stale-results bug and two smaller dependency/documentation issues.


Automated extension review passed. A maintainer review is still required.

This is an AI-generated first pass and may be mistaken. If a finding is unclear or incorrect, reply in the relevant thread and mention @aurelleb.

Comment thread extensions/wordlex/src/search-dictionary.tsx
Comment thread extensions/wordlex/package.json Outdated
Comment thread extensions/wordlex/README.md Outdated
@clankus-aurelius clankus-aurelius added human-reviewable Automated review passed; ready for maintainer review and removed ai-reviewing Automated extension review is running labels Aug 6, 2026
@clankus-aurelius

Copy link
Copy Markdown
Collaborator

@aurelleb automated review passed for fe9ca46; this extension is ready for your review.

Addresses CORRECTNESS-001 (re-review): the error path set hasError but kept
the previous results. Since the error empty view is only rendered when
results.length === 0, a failed later query could keep showing matches from
an earlier query. Clear the results in the error path so the failure state
is actually displayed.
Addresses DEPENDENCY-001 (re-review): eslint.config.mjs imports only the
typescript-eslint aggregate package, which already provides the parser and
plugin. The separately declared @typescript-eslint/eslint-plugin and
@typescript-eslint/parser devDependencies were unused and redundant.
Addresses MANIFEST-001 (re-review): cmdModifier is ctrl on every non-macOS
platform, but the README documented the shortcuts as Cmd-only. Document
both variants (Cmd on macOS, Ctrl on Linux/Windows) for all four actions.
@clankus-aurelius clankus-aurelius added ai-reviewing Automated extension review is running and removed human-reviewable Automated review passed; ready for maintainer review labels Aug 6, 2026

@clankus-aurelius clankus-aurelius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One native-process lifecycle issue should be addressed before publication.


Automated extension review passed. A maintainer review is still required.

This is an AI-generated first pass and may be mistaken. If a finding is unclear or incorrect, reply in the relevant thread and mention @aurelleb.

Comment on lines +28 to +43
const child = spawn("wordlex", ["--search", word], {
detached: true,
stdio: "ignore",
});
// spawn reports a missing binary asynchronously via the 'error' event, so a
// try/catch around spawn never fires. Surface the failure to the user here.
child.on("error", () => {
showToast({
style: Toast.Style.Failure,
title: "Could not open WordLex",
message:
"Is WordLex installed? Make sure the 'wordlex' command is available on your PATH.",
});
});
child.unref();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Warning — Detached WordLex process can outlive Vicinae

Rule: PROCESS-001

The GUI launch uses detached: true and immediately calls unref(), deliberately releasing the child from Vicinae’s process lifecycle.

Suggested resolution: Keep the WordLex process attached and lifecycle-managed; remove the detached/unref behavior and handle its completion and errors normally.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clankus-aurelius thanks for the careful review — but I believe this one is a false positive, and the suggested resolution would introduce a regression.

openInWordLex launches a GUI desktop application from a short-lived extension command process. Each command invocation runs in its own short-lived Node process, and the semantics here are intentional:

  1. unref() is required. Without it, the command host keeps its event loop alive while the child runs, so the "Open in WordLex" command would block until the user closes the WordLex window. With unref(), the command returns immediately and the GUI app keeps running — the same fire-and-forget behavior as Raycast's open / macOS open when launching an app.
  2. detached: true ensures the GUI app is not tied to the launcher's process group/session, so it outlives the launcher process — which is the desired behavior for a desktop app launched from a launcher. (A GUI app that dies with the launcher would be unusable.)
  3. Errors are already lifecycle-handled. Spawn failures (e.g., a missing wordlex binary) surface asynchronously via the child's error event, and we attach a listener that shows an actionable failure toast (this was addressed per the earlier UX-001 finding). The execFile-based lookup paths separately convert ENOENT/127/timeouts into actionable Error messages.

I completely agree the process should not silently leak without diagnostics, so I'm happy to add any concrete lifecycle handling you'd suggest — e.g. logging the child exit code, or re-checking single-instance behavior on the desktop-app side. But removing detached/unref would make the command hang, so I'd rather not do that.

@aurelleb happy to make changes if you disagree.

@clankus-aurelius clankus-aurelius added human-reviewable Automated review passed; ready for maintainer review and removed ai-reviewing Automated extension review is running labels Aug 6, 2026
@clankus-aurelius

Copy link
Copy Markdown
Collaborator

@aurelleb automated review passed for bf2169b; this extension is ready for your review.

@vedesh-padal
vedesh-padal requested a review from aurelleb August 9, 2026 14:50
@aurelleb

aurelleb commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Not sure if I did something wrong here, but I can't run wordlex. Looks like the path was hardcoded in the runner or something.

./WordLex_1.6.0_amd64.AppImage

thread 'main' (180917) panicked at /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.2/src/app.rs:1417:11:
Failed to setup app: error encountered during setup hook: file is not a database
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@vedesh-padal

vedesh-padal commented Aug 11, 2026

Copy link
Copy Markdown
Author

Not sure if I did something wrong here, but I can't run wordlex. Looks like the path was hardcoded in the runner or something.

./WordLex_1.6.0_amd64.AppImage

thread 'main' (180917) panicked at /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.2/src/app.rs:1417:11:
Failed to setup app: error encountered during setup hook: file is not a database
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

the .AppImage packaging had an issue, and linking, pointing to the oewn db file.
i was testing with .deb installation and it worked fine, missed checking the .appimage after few commits.

fixed that now and other hardening for the desktop app, and curl based installer option, with updated README, clarifications.

you can now download the latest v2.0.0 installer (whatever way you prefer!)

Note that:

if you are going with .AppImage option, it will launch fine now, additionally with .AppImage based WordLex app, this vicinae extension will look for wordlex command in the PATH.
so you have to restart(*) vicinae for it to pick the wordlex command, so for that:

first register the WordLex app as a .desktop, so that it shows up in app menu and also a symlink is pointed to the .AppImage:
-> run the following commands:

./WordLex_<version>_amd64.AppImage --install-cli

# if you want to remove the symlink and the .desktop files that make it show in the app menu:
./WordLex_<version>_amd64.AppImage --uninstall-cli

then you can launch either from app menu or run the AppImage :)


*: it worked for me, only when i ran vicinae server --replace command (just quit the app and launch from app menu didn't work) or quit the existing and run the cli command to start -- but logs kept showing, I tried vicinae server & after quitting the existing vicinae, but even then logs kept showing in the terminal after 1-2 secs, and when i close the terminal, vicinae quit.
I had to run this command, to restart vicinae and run in background: nohup vicinae server > /dev/null 2>&1 & disown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-reviewable Automated review passed; ready for maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants