Desktop transcript edits, auto-polish and speaker labels now reach the phone,
which draws meetings from transcript segments, and prompt edits of the four
shared preset commands are used by the phone's commands.
Bumps the product version to 1.9.0 (Android/iOS build 1090000).
Stripe is not used. Keeping its checkout, portal and webhook paths meant a
second payment provider, a second return-URL format and dead UI.
- Delete the stripe-checkout, stripe-portal and stripe-webhook functions and
their config; billing-catalog serves Payple prices only, and the web parser
rejects a catalog that still mixes in Stripe prices.
- Web: drop the Stripe checkout/portal buttons, provider toggle and return
notices; billing shows Payple only. Past rows with provider='stripe' are
still displayed ("Stripe (종료)") with a support contact instead of a portal.
- Desktop: delete the Stripe checkout modal, payment IPC channels, preload
namespace and their types; "Remove ads with Pro" opens the web billing page
via license.openBilling. Support/refund copy names Payple.
- billingUrl() loses the Stripe-only success/canceled result option; the
Deno contract is regenerated.
- Migrations and the DB's accepted provider values are untouched (history).
- Docs and the backlog record the removal (MON-04, EXT-STRIPE-01, GAP-BILL-03).
Verified: typecheck (desktop/web/admin/api-client/mobile), contract:check,
deno check all functions, deno test 80/80, desktop 1478/1480 on the Electron
runtime (2 known environment failures), web and admin builds, release
metadata and mobile boundary self-tests, eslint on changed files.
- .env.example lists the keys the scripts actually read (NAS_HOST/NAS_USER/
NAS_SSH_PORT/NAS_DEPLOY_PATH, web build args, Supabase access token and
project ref, CRON_SECRET, Cloudflare token/account/zone/tunnel) and drops
unused ones (DSM_HOST, NAS_DEPLOY_ROOT, NAS_ADMIN_PORT, NAS_LANDING_PORT,
APP_DOMAIN, ADMIN_DOMAIN).
- docs/map/02-infrastructure.md gains a credentials map (names only) with the
matching Forgejo/Supabase/NAS copies, plus the public routing summary.
- Deploy scripts no longer point to the removed /admin static page or portal.
- docs/REFACTOR_WAVE3_REPORT.md and the Wave 3 policy: canonical map,
production changes, verification and remaining external steps.
- Gap backlog: GAP-BILL-01 resolved; new GAP-BILL-02 (Payple renewal never ran,
Payple client key never set), GAP-WEB-01 (tunnel host for /app), GAP-OPS-01
(NAS compose/.env drift), GAP-CI-01, GAP-I18N-02, GAP-TEAM-02.
- design.md: hero loop decision (numbers taken from the app capsule), pricing
mismatch closed; feature catalog SHELL-11 updated.
- docs/map, release guide and mobile release docs no longer describe the
deleted wwwroot, binaries, Dockerfile.admin, NAS site copy or .github CI.
- docker-compose.nas.yml gains the d3ro-whisper service that only existed in
the NAS copy, so the repository file is the complete definition.
- refactor-wave skill: Wave 3 index and lessons P10-P12.
Next-sentence suggestions now arrive one at a time up to twelve, shown three
per page with Ctrl+Alt+Up/Down to move, Left/Right to page, Enter to accept
and Esc to close; old default bindings migrate and the panel guide follows the
live bindings. The overlay is redesigned, stays put while candidates stream
and sits outside the input box when no caret is reported.
The personal phrase memory stops learning from terminals, code editors and
the coding-agent hub, ignores symbol-heavy lines and empty-field placeholders,
and prunes existing entries that break those rules.
Fixes suggestion keys starting dictation, installs stuck on a pre-1.5.0
speech engine without the focus endpoint, Ollama runner windows flashing
while typing, the speech engine starting twice, and cold-model timeouts.
Live captions can be dragged to a remembered position and show a waiting
notice until the first line arrives.
Bumps the product version to 1.6.0 (Android/iOS build 1060000).
Adds next-sentence suggestions while typing, weekly input insights and a
personal phrase memory to the desktop app, and fixes custom instructions so
they process the text instead of inserting the instruction's own wording.
Local model requests are now bounded and individually cancellable.
Bumps the product version to 1.5.0 (Android/iOS build 1050000), refreshes the
landing and web download links, and records the new INPUT feature rows and the
open verification gaps in the infrastructure map.
Running a custom instruction (translate, summarise, rewrite, explain code,
free prompt) inserted the instruction's own wording instead of the result.
Two faults stacked:
The instruction was passed as the text to process, leaving the system-prompt
argument empty. `BASE_SYSTEM_PROMPTS` has no `custom` key, so resolution fell
back to `refine` without saying so, and the model dutifully polished the
instruction it had been handed. The transcript never reached it.
And only `{{text}}` was substituted, which none of the five built-in
instructions use — they carry `{{targetLanguage}}`, `{{userPrompt}}`, or no
placeholder at all. The substitution was a no-op from the day it was written:
the presets landed ten hours before the code that expected them.
- Instruction prompts now go to the system-prompt argument and the transcript
to the text argument. Instructions that spell out `{{text}}` keep their old
meaning, so hand-written ones still work.
- `renderInstructionPrompt` resolves `{{text}}`, `{{userPrompt}}` and
`{{targetLanguage}}` in one place, and warns by name when a placeholder is
left standing rather than letting it reach the model.
- `resolveSystemPrompt` no longer drops silently to `refine` for `custom`.
- Voice shortcuts no longer die at the `defaultLLMAction === 'none'` gate; an
explicitly named instruction outranks the default. Without one, `none` still
passes the transcript through untouched.
- `translate` receives its target language instead of relying on a default two
call frames away. It is still always English — `AppConfig` has no key for it,
and neither `language` (UI locale) nor `sttLanguage` (source language) can
stand in. Choosing a target language needs a setting and is not in this fix.
- Chains ran instructions with placeholders intact; they share the same
resolution now.
- The command screen's pipeline bench called `llm.generate`, which preload does
not expose, so every run threw and the catch showed the input back as if it
had succeeded. It uses `llm.process` now, over the same path production
takes, and a failure reads as a failure.
Present since the feature shipped: the custom-instruction path has never
worked. Plain actions (refine, summarise, grammar, expand) were unaffected and
are now covered by tests so they stay that way.
Shortcuts were defined in four places that drifted apart: per-action IPC channel
pairs, a hand-written VK table in the service, a second one in the renderer, and
three copies of the keycap styling. Adding an action meant editing all of them,
so two shortcuts stayed hardcoded in bootstrap and one had no settings entry at
all.
packages/core/src/keybinding.ts is now the single source for the binding type,
the selectable key catalog, the action catalog, normalization, validation,
conflict detection, display labels, search and deserialization. Main, preload
and renderer all read from it; nothing redefines keys or rules locally.
- Each action holds a list of bindings instead of one. AppConfig's four
*Shortcut fields collapse into a single keyBindings map, migrated on launch.
- Mouse buttons can be bound. Left click is refused, right/middle need a
modifier, side buttons are free. uiohook cannot swallow events, so the
original click still fires and the UI says so.
- Keys can be picked from a grouped dropdown with a search box, not only by
recording a keypress.
- HOTKEY's 14 channels become KEYBINDING's 9, taking the action as a parameter,
so actions no longer multiply channels. The history and command popups moved
out of bootstrap into ordinary actions.
- displayLabel is gone; labels derive from the binding and follow the app
language and platform.
Fixes found on the way:
- Double-press hands-free was unreachable: lookup returned only the first
matching action, and dictation shares its default binding.
- Reserved-combination checks compared joined key names, so a different modifier
order let Ctrl+C through.
- Disabling shortcuts released every global registration in the process,
including the popup ones, and never restored them.
- Enabling shortcuts after starting disabled left nothing registered.
- The dashboard stored the caption event payload instead of the state in it.
d3ro.chanpaca.net answered an empty Cloudflare 404 because the account had no
Pages project for the landing site, and attaching the custom domain still needs
a DNS CNAME that the local Cloudflare credentials cannot create. Add a small
Workers route bridge that serves the Pages deployment on that hostname so the
domain works now and keeps following Pages deploys, and record the cleanup
left to do.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
The download contract on both the web console and the landing site still
advertised 1.2.0, so the install button pointed at
D3RO-Voice-Setup-1.2.0-x64.exe, which the feed answers with 404 while 1.3.7
is live. Wire both contract files into version:sync so the installer version
and release date follow the version SSOT, and set them to the published 1.3.7.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
The overlay fix shipped as 1.3.7 through the local updater publisher, so the
map now carries the published version, the per-channel evidence, and the
correction that 1.3.2-1.3.7 are live on the feed even though the CI signing
gate still blocks tag-driven publication.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
Popup pages loaded their scripts as classic <script src> tags, which the
renderer build never bundles, so an installed app rendered only the static
markup: the recording tip stayed at 0:00 with no wave bars and live captions
showed nothing.
- declare popup scripts as modules so the build emits them, and fail
packaging when a renderer page references an asset that was never produced
- hold popup IPC until the renderer has loaded and re-assert visibility on
every show, so a popup hidden once still appears next time
- surface popup renderer console and load failures in the main log
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
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.
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.
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.