load() chose the parser from the extension alone, so a .txt that is
really FDX was parsed as Fountain. The .fdx extension still wins, and
other names are sniffed for the FinalDraft root element.
A path that did not exist was silently ignored: plotzilla
new-script.fountain opened an untitled document instead of a new
script saved to that file on the first save.
The remembered answers are keyed on user input and never evicted: a
long session could grow the map without bound. Clear it at 10000 words
and let it refill - a script repeats the same words, so re-checks are
cheap.
Every model load read the full French and English Hunspell
dictionaries from disk again, even when the spell checker already held
the same dictionary in memory. App now caches one Dictionary per
language; a Dictionary is read-only once loaded, so the checker and
the language detection share it.
marian.Installed and marian.Files were only used by the package's own
test, while the app has its own check in translation.go - with a
different rule (pinned size there, size > 0 here), so the two
definitions of 'installed' could disagree. The app's check is the
canonical one (it owns the pins); Load already fails on an incomplete
model, which the test keeps asserting.
A from-scratch CPU inference engine is the largest, riskiest part of
the codebase and the least connected to screenwriting, so it deserves a
written decision. It stays: the alternative (a ctranslate2/argos
dependency, a plugin or a separate binary) buys a single menu item at
the cost of cgo and per-distribution packaging, while the in-tree
engine keeps the one-binary, offline, GPL-clean build. The package doc
now says so, with the condition that should reopen the question
(more language pairs).
Translate runs NumCPU/2 sentence workers and each of them started
GOMAXPROCS goroutines for the logit projection of every step: on 16
cores, 128 goroutines fighting for 16 cores. The model now caps the
goroutines of a step (SetThreads, called once after Load) so that
workers x threads stays within the machine.
Each beam-search step deep-cloned the keys and values of every layer
for every candidate it kept, so the cost grew quadratically with the
sentence length and produced garbage faster than the unrolled dot()
could win. Survivors are now chosen first; a beam whose parent has a
single survivor takes its slices over (the parent is dead), and only a
shared cache is cloned. The step's other buffers (query/key/value,
hidden, logits) are reused across layers, beams and steps instead of
being allocated afresh each time. TestRealModel confirms the output is
unchanged.
The hand-written PDF writer uses the standard Courier fonts
(WinAnsiEncoding), so any character outside CP1252 - a Polish, Greek,
Cyrillic or CJK script - was silently written as '?'. Render now
collects the characters it had to replace and returns them, and the
export dialog lists them in a warning once the file is written.
Embedding a TrueType font (e.g. Courier Prime) with a CID/ToUnicode
map would fix the root cause; the warning keeps the zero-dependency
trade-off honest in the meantime.
writeAtomic renamed the temporary file over the target without syncing
either: on ext4 with delayed allocation a power cut just after a save
could leave a zero-length script, the exact failure autosave exists to
prevent. Sync the temp file before the rename and the directory after
it. While here, keep an existing file's permissions instead of forcing
0o644, and resolve symlinks first so saving through a link replaces
the file it points to, not the link itself (Dropbox/dotfile setups).
SetDirty runs on a Wails call goroutine while the GTK close handler
reads the flag, with no synchronisation: an edit landing while a save
finished could make the close confirmation unreliable. The spellMu
comment already documents the goroutine-per-call convention; the flag
that guards against losing work deserves the same care.
GHSA-68fv-2mgg-jv7q: event-loop denial of service through indexed
source-map section offsets. Build-time dependency only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Downloading the 300 MB model restarted from zero after any failure and
hung forever on a stalled connection. Download into a .partial file
that survives interruptions, resume it with a Range request (falling
back to a fresh download when the server cannot), skip files already
verified, and abort when nothing arrives for 30 s. A model directory
only counts as installed when every file has its pinned size, so a
truncated model is offered for download again instead of failing to
load.
The model load held the translation mutex, so Cancel did nothing for
the seconds it takes to read 300 MB. Load without holding the lock,
make marian.Load context-aware, and let CancelTranslation wait for the
task to wind down so a new one can start at once. The idle release
timer re-arms when it fires while a download holds the task slot,
instead of never freeing the model.