Auto-update could not work at all: the installer was 189 MB because it carried
the local speech engine and ffmpeg, and the download feed rejects uploads over
about 100 MiB, so update metadata could never be published.
The installer now leaves those components out and the app fetches them the first
time they are needed, verifying every part and the joined archive before
installing. The installer is 90.6 MiB, the update feed is published again, and
updates stay small because the engine is not re-sent on every release.
The fetch is visible and recoverable: the download runs with progress, a failed
install cleans up after itself, and Settings > STT shows the runtime status with
a manual download action for when the automatic one cannot run.
The manual install path still needed 7-Zip, which the target machine does not
have, so "installable without a certificate" was not yet true.
The channel now also publishes the app as byte-split zip parts, and the install
script joins them and extracts with the built-in Windows Expand-Archive after
verifying every part and the joined archive. Version 1.3.1 republishes the
channel from a single build, because a version's artifacts can only match one
build and published volumes are never overwritten.
Installers could not be published at all: the signing certificate does not
exist yet, and the release pipelines stop at their signing guard. Users had no
way to install a fixed build, so the product was effectively stuck behind a
certificate that takes weeks to obtain.
There is also a second, independent blocker: the download feed sits behind
Cloudflare, which rejects any upload body over about 100 MiB, and the app with
its speech engine exceeds that even when signed.
A portable channel now publishes what can actually be delivered today: the app
compressed into 95 MiB 7z volumes (162 MiB total instead of 243 MiB), a Scoop
bucket for a normal install and uninstall experience, and a verifiable manual
installer script. It is deliberately separate from the auto-update feed, needs
no certificate, and refuses to overwrite an already published version.
Two tagged release pipelines failed and no installer has been published since
1.1.0. The cause is not in the build: the release repository has no Actions
secrets at all, so every run stops at the signing guard.
A helper now reports which release secrets are missing and registers them once
the signing material exists, and the release guide documents the required
values, the Forgejo-side check, and how to re-run a pipeline for an existing
tag without recreating it.
Packaging silently tolerates a missing resource directory: electron-builder
prints "file source doesn't exist" and continues, which is how installers that
could not transcribe were published.
Every pipeline that packages the desktop app now builds the sidecar and fails
when the engine or its VAD data is absent, so a release cannot ship without
local transcription.
Local dictation had never produced a transcript on an installed build. The
engine itself was healthy; every connection to it was broken.
Installed builds shipped no speech engine at all: the packaging config had no
entry for the faster-whisper sidecar and no pipeline step built one, so the app
always fell back to a system Python without the runtime. Development was broken
too, because the sidecar and SoX paths were resolved against the Vite output
directory instead of the app root, which also meant recording failed with a SoX
ENOENT. On hosts where localhost resolves only to IPv6, every local request was
refused outright, which silently disabled both local transcription and the local
LLM.
The sidecar is now built and bundled (including the Silero VAD data it needs),
gated by a packaging check that fails when the engine or its data is missing.
Paths are discovered from the app root and fail loudly when the engine is
absent. Local engine URLs are normalized to the IPv4 loopback, decoding is tuned
so repeated hallucinations cannot compound (the same transcript now takes about
a fifth of the time), the engine is warmed up at startup, and holding the hotkey
now shows the text forming live in the recording tip.
The changelog still described unreleased work under 1.1.0, which was already
published with its own notes. Those notes are restored verbatim for history,
and the new work has its own 1.2.0 section that the feed publisher will turn
into release notes.
The release guide, infrastructure map, and mobile SSOT now carry the 1.2.0
identity, state that installer binaries are distributed through the feed and
never committed, and record that the published 1.1.0 installer is unsigned and
is being superseded rather than rewritten. Backlog entries cover the remaining
external signing and token secrets.
Version 1.1.0 was published on 2026-09-15, so its tag is closed. Every commit
since then, the update-feed rework, dictionary import and export, the extra
push transports, and entitlement gating, needs its own immutable version.
Product version, Android version code, iOS build number, and all package and
store surfaces move to 1.2.0 / 1020001, with Korean and English store notes for
the new version code. The download centers read that version and its release
date from one place instead of repeating them inline.
The feed publisher overwrote whatever version-specific assets it found, so a
re-run of an old release tag could quietly replace the installer that
customers already downloaded under that version number.
Publication now compares the bytes already in the version-specific registry
path and stops when they differ, while still allowing an identical re-run to
finish. The metadata verifier gained a negative case so the guard cannot be
removed unnoticed.
Release notes for 1.1.0 were split between an Unreleased section and the
version section, so the published notes would have omitted the update-feed
and desktop changes. Everything shipping in this version now sits under one
`## [1.1.0]` heading.
`docs/map/` becomes the entry point for what infrastructure exists per
platform and how far each feature is developed, with a documented update
protocol so feature work and this map do not drift apart again. The release
guide now states that installer binaries live in the update feed rather than
the repository.
The download section described a release as pending while the product was
already installable, and its installer link pointed at a path the site build
never ships, so a visitor would have reached a missing file.
The section now states the released version and links the published update
feed that the desktop client itself uses, with the release notes and hash
metadata one click away. Repeated color literals behind the hero animation
moved into named tokens.
Team, meeting, memo, template, command, and dictionary screens drifted from
the server contract, and report submission could hang instead of confirming
to the user. The screens now use the server responses directly.
The retired Expo shell is removed; the React Native app is the mobile client.
Gradle-generated vector-icon drawables are ignored rather than committed.
Every notification depended on Firebase Cloud Messaging, so a missing
Firebase project, which is the current state, meant no notification could be
delivered on any platform. Web Push and token-based Apple Push are now first
class transports alongside FCM, chosen per registered device, and a scheduled
Cloudflare Worker drain retries an outbox so a provider outage delays rather
than drops a message.
Models, pipelines, users, subscriptions, usage, audit log, ads, and releases
still rendered placeholder or duplicated implementations from the earlier
admin split. They now read the live back-office API, share one sidebar and
console theme, and the license issuer dialog uses the rotated signing key.
The console could not create dictionary entries, and team pages showed a
static member list with no record of who changed what. Dictionary
management, a knowledge upload form, and a team activity feed are now
available, alongside a download center that links the published desktop
installer feed rather than repository-local paths that no deploy ships.
Red-team e2e coverage was added for the account and team flows touched here.
Desktop, web, mobile, and the API each decided locally what a tier could do,
so a plan change could unlock a feature on one surface and not another.
Entitlement checks now live in `@d3ro/core` and are exercised by tests.
The shared theme and design-system packages also gain the tokens the new
surfaces consume, and the api-client exposes the dictionary and team types
the clients now send.
Several desktop paths quietly substituted defaults or partial results: a
config write could fall back to a throwaway in-memory store, speech provider
errors were absorbed into empty transcriptions, and meeting exports built
file names from raw titles.
Writes now fail explicitly when the store is unavailable, provider and model
failures reach the UI as errors, and export names pass through one
sanitizer. Settings, license, ad, and support surfaces use the shared theme
tokens, unused hotkey helpers are gone, and the package gains strict
node/renderer typecheck configs plus red-team e2e scenarios for these flows.
When mediation had no programmatic fill, the banner and rewarded surfaces
collapsed to empty space. Direct house sponsors now serve their own copy and
click-through, with the same settlement accounting used by the mediated
network, and the mediation engine tests cover the added path.
Users could only rebuild their spoken-word dictionary entry by entry. Import
and export now round-trip the whole list, reporting duplicate and invalid
entries per row instead of failing the batch, so a dictionary survives a
reinstall or a move to another machine.
Desktop clients had two competing update sources: the runtime pointed at a
legacy GitLab registry while the Forgejo packages were filled in by
hardcoded, version-pinned scripts. Operators could not tell which feed was
authoritative, and no release could be reproduced from a tag.
Auto-update now reads a single canonical Forgejo registry feed, updated by
a version-agnostic publisher that runs from the tag on Forgejo, GitLab, and
GitHub CI alike. Channel, minimum supported version, forced install,
full-versus-delta thresholds, staged rollout, and a remote kill switch come
from one policy file the client fetches alongside the feed. Tag creation is
gated on a clean tree, matching version surfaces, and a changelog section.
Release packaging walked a working tree that still carried .NET build
output, Playwright run artifacts, and one-off browser automation scripts.
Those files are not source, could not be reproduced from the commit, and
made a clean-tree release gate impossible.
Build output and test run directories are now ignored and untracked, and
developer-only automation/scratch material moved under a single ignored
`scripts/local/` directory. A stale admin bundle that no HTML referenced
was dropped from the API server web root.