Vim's default p/P pastes from its internal unnamed register, which
is useless when the text to translate lives in another app's
clipboard. Override both in normal mode to read navigator.clipboard
.readText() and insert at (after) or before the cursor.
Caveats: requires browser clipboard permission (prompted on first
use), HTTPS or localhost. Both already satisfied — finrod is served
over the wildcard TLS cert.
The previous version only handled Shift-H/L when focus was outside
the editor; inside vim normal mode they were viewport top/bottom
and required Esc+Tab to escape. Now Vim.mapCommand overrides H/L
in normal-mode context to call the same moveFocus(±1) helper, so
the shortcut works uniformly regardless of focus position.
- .cm-editor pinned to font-size 1rem so the input matches the
output pane (CodeMirror 6 defaults smaller)
- refreshLoaded(isInitial=true) on first load picks the hot model
as the dropdown default — avoids the 5-15s disk reload on the
first translation. Subsequent refreshes don't override the user's
manual selection.
- document-level Shift-H / Shift-L cycle focus across controls
(source → target → model → editor → button), suppressed when
focus is inside CodeMirror so vim's viewport H/L still work.
- Replace the plain <textarea> with a CodeMirror 6 EditorView that
has @replit/codemirror-vim attached. Loaded from esm.sh, so no
build step — finrod stays no-bundler.
- vim() extension is conditionally omitted on coarse-pointer devices
(phones, tablets) where modal editing without a hardware Esc key
is unusable and IME composition fights the keymap.
- Ctrl-/Cmd-Enter shortcut and the typing-debounce that refreshes
the "Hot:" indicator both migrate to CodeMirror's keymap and
updateListener APIs respectively.
- Editor styled to match the existing .output pane.
CHANGELOG: note under [Unreleased].
- CHANGELOG.md following Keep a Changelog; 0.1.0 entry covers all
features shipped through the loaded-model indicator
- build.sh now also tags the image with the version read from
pyproject.toml, e.g. rg.nl-ams.scw.cloud/valinor/finrod:0.1.0
- README documents the release workflow (edit CHANGELOG, bump
pyproject version, merge, tag vX.Y.Z, build, bump valinor)
- /api/loaded backend endpoint proxies Ollama /api/ps and returns
{models: [{name, expires_at}]}
- Status line under the controls shows "Hot: gemma2:9b — unloads in 4m"
with a glowing orange dot, or "No model loaded" in muted style
- Loaded models in the dropdown get a "● " prefix
- Refreshes on page load, after every translation, and 500ms after the
user pauses typing in the input box
uv.lock was originally resolved with the workstation's globally-configured
dexterenergy private artifact registry as the default index, baking that
URL into every `source = { registry = ... }` entry. Inside Docker that
401s.
- pyproject.toml: declare https://pypi.org/simple as the project's
default uv index (overrideable, but pins intent)
- uv.lock: regenerated with UV_DEFAULT_INDEX=https://pypi.org/simple and
no UV_INDEX so every source now points at public PyPI
Reproduce: `UV_DEFAULT_INDEX=https://pypi.org/simple UV_INDEX= uv lock`
- app.py: /api/models filtered to gemma+aya, /api/translate streams
NDJSON from Ollama with stream:true, /healthz for k8s probes
- static/index.html: dark-mode UI, three dropdowns (source/target/model),
live token streaming with spinner+pulse+timer indicators
- Dockerfile: two-stage uv build, slim Python 3.13 runtime
- .gitea/workflows/build.yml: multi-arch (amd64+arm64) build, push to
Scaleway registry on push to main
- build.sh: manual fallback if Gitea Actions isn't available