Commit graph

323 commits

Author SHA1 Message Date
Yun Chan
9a8f7e6aa6 feat(desktop): sync knowledge, recordings and shared settings; play any recording
Knowledge documents travel as source-text chunks; each surface embeds them
with its own model, the server index is requested through embed-chunks, and
documents from the phone are stored without a file and indexed from their
chunks. Chunk text is now kept when local embedding fails, so reindexing no
longer needs the original file.

Recordings upload to the mobile storage contract (audio bucket under the
user's folder plus an audio_files row, 50 MiB cap, a Settings > Cloud
toggle) and are removed with their record. The history card gains a play
button that uses the local file or, for phone recordings, a signed URL.

Language (ko/en), system/light/dark theme, auto-polish and the active user
command follow the phone's user_settings with its revision rule; changes that
arrive from the phone reach the open window.
2026-09-27 14:44:56 +09:00
Yun Chan
cee4ab9317 feat(supabase): sync knowledge documents and prune tombstones daily
knowledge_documents deletions now leave a sync tombstone and have a cursor
index and Realtime, so the desktop mirror can follow them. pg_cron runs
prune_sync_tombstones_v1 every day at 03:17 UTC with the 180-day window.
2026-09-27 14:44:56 +09:00
Yun Chan
bde8565fd2 chore(release): point the Scoop manifest at the 1.7.0 portable build 2026-09-27 14:44:56 +09:00
Yun Chan
3d9faedf7d release: ship v1.7.0 with two-way cloud sync and streaming captions
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 52s
ci / 모바일 린트·타입·Jest (push) Successful in 41s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 24s
ci / .NET API 서버 테스트 (push) Successful in 11s
deploy-site / deploy (push) Successful in 44s
ci / 워크스페이스 빌드 검증 (push) Successful in 35s
release / release-windows (push) Failing after 6m37s
portable-unsigned / portable-windows (push) Failing after 21m42s
Signing in now keeps history, dictionary, meetings with memos and documents,
memo tags, user commands and templates the same on desktop, phone and web,
with offline changes queued, deletions shared and phone edits arriving live.
The desktop registers in the phone's device list and signs out when
disconnected there; local-mode records move into the first account.

Live captions stream about a second behind speech, finish lines on pauses,
are polished in context by the local model and can use their own speech
model (the speech engine is downloaded again once, minimum 1.7.0).

Fixes suggestion paste on Ctrl+Alt+Enter, click acceptance, typing detection
in growing chat boxes, one-way cloud sync and failing account exports.

Bumps the product version to 1.7.0 (Android/iOS build 1070000).
2026-09-27 14:08:18 +09:00
Yun Chan
6b06442412 docs(map): cross-device sync rewrite, fixed defects and what is still local
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 50s
ci / 모바일 린트·타입·Jest (push) Successful in 38s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 21s
ci / .NET API 서버 테스트 (push) Successful in 11s
deploy-site / deploy (push) Successful in 33s
ci / 워크스페이스 빌드 검증 (push) Has been cancelled
2026-09-27 14:04:49 +09:00
Yun Chan
53fcdf6305 fix(mobile): list desktop and web devices instead of failing the screen
normalizeRegisteredDevice accepted only android/ios, so one desktop row made
the whole Devices screen fail. Accept the server platform list and show the
platform on each card.
2026-09-27 14:04:49 +09:00
Yun Chan
0a4f5aee64 feat(desktop): two-way cloud sync with mobile and web
Rewrites the desktop mirror as services/sync/SyncEngine: a persistent
outbox, per-account server-clock keyset cursors with paging, pulls that never
overwrite unsent local edits, deletions both ways through sync_tombstones and
per-row failure isolation. It now covers history titles and favorites,
dictionary, every meeting's memos and documents, memo tags, user commands and
dictation/meeting templates, and registers the desktop as a device that the
phone can disconnect.

Fixes shipped defects: the first pull after sign-in fetched nothing, only
the first meeting's children were pushed, team meetings leaked into the
personal database and lost team_id on re-push, and Realtime never connected
because Electron's Node 20 has no global WebSocket (ws is now the transport).
Anonymous local-mode records are imported into the first account that signs
in. The settings sync section is translated and shows pending/rejected
changes; synced screens reload on app:dataChanged.
2026-09-27 14:04:49 +09:00
Yun Chan
b5c9ff9f31 feat(supabase): record deletions, move history revision on any write, keep portability export on v1 keys
sync_tombstones logs every owner-scoped delete on the synced tables so an
offline device can apply it later; filtered Realtime channels never deliver
DELETE, so the tombstone INSERT is also the live deletion signal.
history.revision now follows content changes made without a revision (the
desktop mirror), meeting_memos gains updated_at, and cursor indexes back
keyset pulls. Templates get client-keyed upsert/delete wrappers over the
revision-checked mobile RPCs.

export_account_portability serialised whole rows while the v1 archive is an
exact key set; columns added later made every account with meetings fail
export. Each dataset is projected back onto its v1 keys.
2026-09-27 14:04:49 +09:00
Yun Chan
0d66e4df1a ci(deploy-site): check the live structured data from a file so curl is not cut off by grep -q
All checks were successful
ci / 정본·보안·린트·타입·테스트 (push) Successful in 55s
ci / 모바일 린트·타입·Jest (push) Successful in 37s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 23s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 28s
ci / 워크스페이스 빌드 검증 (push) Successful in 40s
2026-09-27 10:49:54 +09:00
Yun Chan
a0abda7a5c feat(site): prerender every locale and publish structured data, llms.txt and sitemaps
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 53s
ci / 모바일 린트·타입·Jest (push) Successful in 45s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 21s
ci / .NET API 서버 테스트 (push) Successful in 13s
deploy-site / deploy (push) Failing after 27s
ci / 워크스페이스 빌드 검증 (push) Successful in 36s
Crawlers received an empty client-rendered shell (107 characters of text);
most AI crawlers do not run JavaScript, so the product was invisible to them.

- Build renders each locale to static HTML (/ for Korean, /en/ … /vi/) with
  src/entry-server.tsx + scripts/prerender.mjs; the browser hydrates the same
  locale. The language menu links to those pages instead of switching in place.
- Per-locale head: title, description, canonical, hreflang (+ x-default /en/),
  Open Graph and X cards with a per-locale 1200x630 image.
- JSON-LD graph: Organization, WebSite, WebPage, SoftwareApplication (KRW
  offers, version, release date, download URL from the canonical sources),
  HowTo and FAQPage. No invented ratings.
- robots.txt (search, ai-input and ai-train allowed; /app, /api, invites
  excluded), sitemap.xml with language alternates, llms.txt and llms-full.txt
  generated from the same translations and canonical values.
- IndexNow key and a post-deploy ping (Bing, Naver, Yandex).
- A factual "at a glance" section and two FAQ answers (recognised languages,
  where data is stored) in all 10 languages.
- Icons, apple-touch-icon and web manifest; asset base is now absolute so
  sub-path pages load the same bundle.

Verified: 3.6k-7.7k characters of text per page, no hydration warnings,
no overflow at 320/1440 in six locales, axe 0, Lighthouse mobile SEO,
accessibility and best practices 100.
2026-09-27 10:45:05 +09:00
Yun Chan
52e364e578 docs(backlog): Payple web checkout waits for a Payple contract
All checks were successful
ci / 정본·보안·린트·타입·테스트 (push) Successful in 52s
ci / 모바일 린트·타입·Jest (push) Successful in 40s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 24s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 워크스페이스 빌드 검증 (push) Successful in 34s
2026-09-27 00:02:12 +09:00
Yun Chan
e7a29a735e copy(site): tighten the Korean landing copy 2026-09-27 00:02:12 +09:00
Yun Chan
082018ecd9 chore(ops): the admin console lives only at d3ro-admin.chanpaca.net; release admin.chanpaca.net
All checks were successful
ci / 정본·보안·린트·타입·테스트 (push) Successful in 49s
ci / 모바일 린트·타입·Jest (push) Successful in 45s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 22s
ci / .NET API 서버 테스트 (push) Successful in 14s
deploy-site / deploy (push) Successful in 22s
ci / 워크스페이스 빌드 검증 (push) Successful in 31s
2026-09-26 23:11:46 +09:00
Yun Chan
6c12da54a6 docs(backlog): record the admin hostname decision (no ACM for now)
All checks were successful
ci / 정본·보안·린트·타입·테스트 (push) Successful in 50s
ci / 모바일 린트·타입·Jest (push) Successful in 39s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 25s
ci / .NET API 서버 테스트 (push) Successful in 11s
deploy-site / deploy (push) Successful in 22s
ci / 워크스페이스 빌드 검증 (push) Successful in 34s
2026-09-26 22:32:27 +09:00
Yun Chan
79fa5d66f4 ci(deploy-site): wait for the new commit to reach the edge before verifying the public identity
All checks were successful
ci / 정본·보안·린트·타입·테스트 (push) Successful in 54s
ci / 모바일 린트·타입·Jest (push) Successful in 43s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 23s
ci / .NET API 서버 테스트 (push) Successful in 10s
deploy-site / deploy (push) Successful in 19s
ci / 워크스페이스 빌드 검증 (push) Successful in 35s
2026-09-26 21:26:19 +09:00
Yun Chan
0d92a4a853 fix(rag): do not mark a document indexed when no chunk could be embedded
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 51s
ci / 모바일 린트·타입·Jest (push) Successful in 37s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 26s
ci / .NET API 서버 테스트 (push) Successful in 14s
deploy-site / deploy (push) Failing after 15s
ci / 워크스페이스 빌드 검증 (push) Failing after 11m7s
With the embedding server unavailable every chunk failed, yet the document
was stored as indexed=true with 0 chunks, so the knowledge base listed it as
searchable while queries could never match it. The red use-case test caught
this; it had been written off as an environment failure.

Now a run with zero embedded chunks leaves indexed=false and throws
RAGEmbeddingFailed (surfaced by reindex, logged by addDocument).

Tests that only hold on the Windows developer machine now declare it: the
bundled SoX binary and PowerShell device discovery run on win32 only, and
the sidecar venv test runs only when sidecar/.venv exists. The Linux Forgejo
runner skips them instead of failing.
2026-09-26 21:10:40 +09:00
Yun Chan
b8a09c0333 ci(forgejo): install the .nvmrc Node before npm on the Linux runner
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 59s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / 모바일 린트·타입·Jest (push) Successful in 41s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 21s
ci / .NET API 서버 테스트 (push) Successful in 16s
deploy-site / deploy (push) Successful in 21s
The first real Forgejo CI run failed because the linux-builder runner ships
Node 18.19: scripts using import.meta.dirname threw, and its older npm
reported the mobile lock as out of sync with the file: dependency on
packages/core (the lock is in sync under Node 24's npm).

bootstrap-linux-toolchain.sh gains a checksum-verified `node` mode
(24.19.0, the .nvmrc version); ci.yml (quality, build-validation,
mobile-quality) and deploy-site.yml add it to GITHUB_PATH before npm runs.
2026-09-26 21:05:34 +09:00
Yun Chan
eedd127ea7 refactor(billing): remove Stripe; payments are Payple (web) and Google Play (mobile)
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 1m13s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / 모바일 린트·타입·Jest (push) Failing after 1m4s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 37s
ci / .NET API 서버 테스트 (push) Successful in 27s
deploy-site / deploy (push) Failing after 20s
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.
2026-09-26 20:56:18 +09:00
Yun Chan
7224e43bfb chore(ops): keep every operations credential name in .env.example and map where each lives
- .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.
2026-09-26 20:35:06 +09:00
Yun Chan
96272de2dc docs(backlog): close GAP-WEB-01 and GAP-OPS-01, record Payple/Stripe/Play decisions 2026-09-26 20:22:26 +09:00
Yun Chan
4aadee8264 fix(edge): route /app and /api on d3ro.chanpaca.net through the NAS tunnel again
Since 2026-09-19 the site bridge worker served every path from Pages, so the
API server on the NAS had no public route: admin login (API_SERVER_URL)
and stt-proxy (D3RO_API_URL) called https://d3ro.chanpaca.net/api/... and
got the landing page's 405.

- The worker passes /app, /api and /health through to the domain origin
  (the kd-nas tunnel) and serves everything else from Pages.
- The kd-nas tunnel sends path ^/app on d3ro.chanpaca.net to the web app
  (NAS 3002); other paths keep going to the API (NAS 5050). No extra
  hostname or DNS record is needed, so WEB_APP_ORIGIN is removed.
- API_PATH_PREFIXES joins WEB_APP_BASE_PATH in packages/core/src/web-urls.ts
  (contract regenerated).

Verified live: /app/login 200, /health and /api/health 200, API login 401 for
an unknown account (was 405), landing/legal/404 unchanged, git/sso/admin
hosts unaffected.
2026-09-26 20:19:27 +09:00
Yun Chan
e87ce63440 docs: record Wave 3 surface consolidation and fold NAS-only whisper into compose
- 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.
2026-09-26 16:02:44 +09:00
Yun Chan
92978607da feat(site): loop a faithful copy of the app's recording capsule in the hero
Replace the press-to-play demo and the "see how it works" button with an
example that keeps playing on the right: hold Right Alt, the capsule appears
with live wave bars, elapsed time and the partial transcript, releasing shows
the processing bar, and the cleaned sentence is pasted at the cursor of a
notes window. It cycles through two samples per language.

- Same numbers as apps/desktop recording-tip: 9 bars with cos weights, 100 ms
  updates, 0.5 smoothing, +/-35% jitter, 2-28 px, per-bar colours, 120 px
  progress bar min(95, (1 - 1/(1 + 1.5t)) * 100)%.
- WCAG 2.2.2: a pause button; stops when off screen or the tab is hidden.
  Reduced motion shows one still frame. The animation is aria-hidden with a
  text description for screen readers. Chinese and Japanese transcripts
  appear three characters at a time.
- Add 404.html so unknown paths stop falling back to the landing page, delete
  the duplicate accept-invite.html (/accept-invite/ is canonical) and link it
  straight to /#download, and move the legal pages off the retired orange
  accent (primary button contrast 5.2:1).
2026-09-26 15:49:08 +09:00
Yun Chan
dc43884e3e ci: run CI only on Forgejo and delete the never-run GitHub workflows (WS-D)
The repository has Forgejo and GitLab remotes but no GitHub remote, so
nothing under .github/workflows ever ran - including the daily Payple
renewal job, which means Payple subscriptions were not being renewed.

- Port payple-renew (daily cron + manual dispatch) to Forgejo. Requires the
  Forgejo secrets SUPABASE_URL and CRON_SECRET.
- Port the CI checks (quality incl. version:check and contract:check, build,
  mobile quality, edge functions, API tests) to .forgejo/workflows/ci.yml.
  macOS and Android emulator jobs are dropped: no macOS runner here, and the
  signed Android release stays on GitLab mobile-production-release.
- Keep one site deploy workflow (Linux, Cloudflare Pages); remove the
  manual Windows duplicate. The mobile release boundary self-test runs there.
- Release verifiers read the Forgejo/GitLab workflows, fail if .github
  workflows come back, and check the rewritten site offers no mobile package.

Policy: docs/REFACTOR_POLICY.md Wave 3, W3-7 and W3-8.
2026-09-26 15:49:00 +09:00
Yun Chan
cd9d199dbf chore: remove duplicate copies of the site, download center and installers (WS-C)
The landing page, download pages, invite page, assetlinks and installers
existed in two or three places; only site/ and the Forgejo feed are served.

- apps/api-server/wwwroot: delete the stale site build, download/invite
  pages, .well-known copy, legacy static admin and 1.0.0 binaries. The API
  no longer serves static files (UseStaticFiles/fallbacks and the apk/zip
  blocker removed); the Next admin is the only admin UI.
- Delete 19 tracked installers/packages (~568 MiB) under site/public/releases,
  apps/web/public/releases and wwwroot/releases; .gitignore blocks them.
- apps/web: delete the download/releases pages, desktop-release.ts, the
  download.html and assetlinks copies, and the accept-invite page (invites
  are only issued to the site's /accept-invite/). e2e specs call the /app
  base path and check the /download redirect instead.
- scripts: delete the retired release/NAS site scripts, drop the web target
  from sync-version, and check assetlinks in site/public only.
- Delete the unused Dockerfile.admin (apps/admin/Dockerfile is used).

Policy: docs/REFACTOR_POLICY.md Wave 3, W3-5 and W3-6.
2026-09-26 15:48:52 +09:00
Yun Chan
b6fe588a7c feat(web): serve the web app under /app and send every billing link there (WS-B)
apps/web was never deployed, so /billing on the public domain returned the
landing page and d3ro.dev (desktop "upgrade") did not resolve.

- apps/web runs with basePath /app and output standalone; /download and
  /releases redirect to the site's #download. A Dockerfile and a d3ro-web
  compose service (port 3002) deploy it to the NAS with the other images.
- The site bridge worker forwards /app/* to WEB_APP_ORIGIN (the tunnel host)
  and rewrites upstream redirects; everything else still goes to Pages.
  With no origin configured /app answers 503 instead of the landing page.
- Desktop upgrade, desktop Stripe return, mobile subscription management,
  the web checkout/portal returns and the site all use billingUrl(); the
  return query is success=1 / canceled=1, which the billing page reads.
  The billing page highlights ?tier=pro|pro_plus, and signing in from a
  billing link returns to the same plan.
- auth/callback pins the redirect origin in production and rejects
  protocol-relative next= values (open redirect).
- Mobile legal links use SITE_URLS (fixes the missing slash on /terms).
- Compose drops the unused NEXT_PUBLIC_API_URL and the dead wwwroot legal
  mounts; deploy scripts add the web image and the SUPABASE_* values the NAS
  compose already required; .dockerignore keeps app .env files out of images.
- Supabase auth redirects allow /app/** (remote dashboard must match).

Policy: docs/REFACTOR_POLICY.md Wave 3, W3-3 and W3-4.
2026-09-26 15:48:30 +09:00
Yun Chan
88f24d84a1 refactor(core): keep plan prices, cloud quotas and public URLs in one contract (WS-A)
Prices, quotas and site URLs were copied by hand into the edge functions,
admin, desktop and the landing site, and the copies disagreed (Payple billed
9,900/29,900 KRW, admin labels said 12,900/24,900 KRW and $9.9/$19.9, the
site said 2,900/8,900 KRW).

- packages/core/src/plan-catalog.ts is the single source for PLAN_PRICE_KRW
  (Free 0 / Pro 2,900 / Pro+ 8,900 a month) and PLAN_QUOTA.
- packages/core/src/web-urls.ts is the single source for the public origin,
  the /app web-app base path, SITE_URLS and billingUrl().
- Deno cannot bundle packages/core, so scripts/ci/sync-core-contract.mjs
  generates _shared/core-contract.generated.ts; `npm run contract:check`
  fails on drift (same pattern as version:sync).
- Payple checkout, renewal and webhook amount checks now bill the catalog
  price, so existing subscribers move to the new price at their next renewal.
  quota.ts, team-contract.ts and the tests read the generated values.
- Admin MRR/ARR is computed in KRW from the catalog; license labels, the
  release link and desktop PREMIUM_LLM limits derive from core; the site
  imports prices and quotas directly.

Policy: docs/REFACTOR_POLICY.md Wave 3, W3-1 and W3-2.
2026-09-26 15:48:18 +09:00
Yun Chan
e689683b72 feat(site): rewrite the landing page around verified facts and accessible controls
- Replace claims that contradicted the app: the default hotkey is Right Alt
  (hold to talk), local features are free with no daily cap, there is no
  14-day trial, Team/SSO/SCIM/ZDR are not offered, and the repository is not
  public. Remove invented metrics, status badges, the competitor table, the
  ad-mediation changelog, the hash calculator and the duplicate demo.
- Seven sections: hero with one labelled example, features, how it works,
  privacy, pricing (Free / Pro 2,900 / Pro+ 8,900 KRW a month), download, FAQ.
  Footer links the privacy policy, terms and account deletion pages.
- All copy moves into i18n and every one of the 10 locales is translated.
  Pricing and cloud quotas come from site/src/pricing.ts.
- Accessibility: skip link, labelled sections, aria-expanded with Esc and
  focus return for the menu, language list and FAQ, live status for the
  example, reduced-motion support, 44px targets, AA contrast on the primary
  button. Korean keep-all line breaking is scoped to :lang(ko) because
  Tailwind's break-keep blocked wrapping in Japanese and Chinese.
- JS 111 -> 98 KB gzip, CSS 7.8 -> 5.2 KB gzip.
2026-09-26 15:04:43 +09:00
Yun Chan
ad6bb70c20 fix(settings): show the live caption model under the dictation model
The caption model selector now sits directly under the dictation model in
the STT tab's Local Whisper card, where people look for it, and stays in the
General tab's caption section as well; both use one shared component. The
first selector is renamed to 'Dictation model' so the two are told apart.
2026-09-24 22:08:05 +09:00
Yun Chan
da0da98285 fix(suggestion): paste after the shortcut keys are released and keep typing detection stable
Ctrl+Alt+Enter pasted while Ctrl+Alt were still down, so the target app got
Ctrl+Alt+V; accepting now closes the panel and waits for the modifiers to be
released. Candidates are accepted on pointer press because the list is
redrawn as new candidates stream in, which swallowed clicks.

The typing gate identified the focused field by its bounds, so chat boxes
that grow while typing looked like a new field on every keystroke and were
reported as "not typing". Fields are now keyed by window, control type and
name, and a mouse click re-baselines the text instead. The decision log
includes both gate values.

The live-caption model selector moves to the caption section of the General
tab, next to the other caption settings.
2026-09-24 22:01:10 +09:00
Yun Chan
4b0f685941 feat(caption): let live captions use their own speech model
The speech engine now keeps an auxiliary model next to the dictation model
and transcribes with whichever the request names, reloading it once if the
engine restarted. Settings > STT gains a live-caption model so captions can
run on large-v3-turbo while dictation keeps its own model. The runtime
minimum rises to 1.7.0 because older engines would silently ignore the
model choice.

Suggestion paging moves to Up/Down: the page follows the selection and the
last item waits while more candidates are being generated. The Left/Right
page shortcuts are removed; they did nothing until a page had filled and
clash with Intel's display-rotation hotkeys.
2026-09-24 21:46:54 +09:00
Yun Chan
39b8e7448e feat(caption): stream live captions and polish finished lines in context
Replaces the fixed six-second batches with a streaming track per audio
source: the uncommitted audio is re-recognised every second and sent as a
partial with its agreed (stable) prefix, a short pause finalises the line,
and long unbroken speech is committed at Whisper segment boundaries. Idle
audio is trimmed so silence cannot produce invented sentences.

Finished lines are corrected by the local model against the previous lines
and replaced in place; edits that change too much are rejected. The
behaviour can be switched off in Settings.
2026-09-24 20:22:21 +09:00
Yun Chan
db8d9448a3 docs(release): record the v1.6.0 publish
Some checks failed
deploy-site / deploy (push) Failing after 28s
Notes the canonical feed, runtime and portable aliases serving 1.6.0 with the
installer hash verified, the site reporting commit 2fe20fa / 1.6.0, the new
minimum-runtime rule that spares 1.5.0 engines a re-download, and the local
packaging trap where a Node-ABI better-sqlite3 ships an installer without a
database.
2026-09-24 20:12:04 +09:00
Yun Chan
2fe20fa7b5 release: ship v1.6.0 with paged suggestions and a cleaner phrase memory
Some checks failed
deploy-site / deploy (push) Failing after 39s
release / release-windows (push) Failing after 3m41s
portable-unsigned / portable-windows (push) Failing after 12m23s
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).
2026-09-24 19:56:28 +09:00
Yun Chan
856e375f3e docs(release): record the v1.5.0 publish and download refresh
Some checks failed
deploy-site / deploy (push) Failing after 30s
Notes the canonical feed now serving 1.5.0 (installer hash verified), the
runtime and portable aliases pointing at the same version, and the landing
site reporting commit 5c11ee2 / 1.5.0. Also records that the site's CI
deployment still fails without a Cloudflare API token, so this refresh went
out through an authenticated local deploy, and that the stale-alias bug found
while publishing is fixed and closed.
2026-09-23 16:39:48 +09:00
Yun Chan
0e4f2de4d0 fix(release): stop portable aliases from mixing old and new bytes
The portable publisher decided a remote file was already up to date by
comparing size alone for anything over 1 MiB. A rebuilt runtime sidecar
produced a first split volume with exactly the same byte length as the
previous one, so the stale volume was skipped and the `runtime-latest`
alias ended up as an old first part next to a new second part. Downloading
that alias would produce a corrupt archive.

The remote hash now comes from the package file-list API, and when any file
in a version differs the whole alias version is deleted and republished, so
an alias can never hold a mix of old and new bytes.
2026-09-23 16:39:38 +09:00
Yun Chan
5c11ee2fde release: ship v1.5.0 with on-device writing suggestions
Some checks failed
deploy-site / deploy (push) Failing after 33s
portable-unsigned / portable-windows (push) Failing after 4m7s
release / release-windows (push) Failing after 3m16s
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.
2026-09-23 16:04:27 +09:00
Yun Chan
99f06c253c fix(llm): stop leaking instruction prompts instead of processed text
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.
2026-09-21 14:39:26 +09:00
Yun Chan
30d51c952f chore(release): v1.4.0
Shortcuts gain several bindings per action, mouse buttons and a searchable
picker, and the stored shortcut schema changes shape, so this is a minor bump.
Existing shortcuts migrate on first launch.

Play release notes state that this release carries no mobile feature changes;
the work is desktop-only.
2026-09-21 13:41:59 +09:00
Yun Chan
4ad1ae6ed4 feat(keybinding): several shortcuts per action, mouse buttons, searchable picker
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.
2026-09-21 13:41:47 +09:00
Yun Chan
0ca9e242fa fix(site): serve the landing domain from the Pages deployment
Some checks failed
deploy-site / deploy (push) Failing after 1m26s
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
2026-09-19 09:21:27 +09:00
Yun Chan
79ecdc89c4 docs(map): record why the landing domain answers 404
Some checks failed
deploy-site / deploy (push) Failing after 1m29s
The Cloudflare account had no Pages project for the landing site, so the custom
domain had nothing to bind to. The project and a production deployment now exist
and the domain is attached; only the DNS CNAME is missing because the local
Cloudflare credentials cannot edit DNS.
2026-09-19 09:12:22 +09:00
Yun Chan
2407f5a1c1 docs(map): record where the release pipelines actually stop
Some checks failed
deploy-site / deploy (push) Failing after 1m4s
The tag pipeline dies at the sidecar step before the signing guard because the
release runner cannot see Python 3.11, the site deploy now builds and fails only
for the missing Cloudflare token, and the portable channel published 1.3.7 with
only its evidence-upload step failing.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
2026-09-19 08:53:11 +09:00
Yun Chan
4b40b25e53 fix(site): stop the landing build from depending on hoisted Node types
Some checks failed
deploy-site / deploy (push) Failing after 1m4s
The hero typed its timer ref with NodeJS.Timeout, which only resolves when
@types/node is visible from an ancestor node_modules. The deploy job installs
site dependencies alone, so tsc -b failed with TS2503 and the landing site has
not been redeployed since. Use ReturnType<typeof setTimeout> and record the
measured deploy blocker plus the runner's missing Python in the map.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
2026-09-19 08:47:33 +09:00
Yun Chan
ee1deb64cf fix(release): point the download centers at the published installer
Some checks failed
deploy-site / deploy (push) Failing after 52s
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
2026-09-19 08:41:37 +09:00
Yun Chan
f741999859 docs(map): record the 1.3.7 release
Some checks failed
deploy-site / deploy (push) Failing after 1m12s
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
2026-09-19 08:30:29 +09:00
Yun Chan
05f0aaa660 chore(release): 1.3.7
Some checks failed
release / release-windows (push) Failing after 6m51s
portable-unsigned / portable-windows (push) Failing after 16m51s
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
2026-09-19 08:24:13 +09:00
Yun Chan
ae7efb6acf fix(desktop): render popup overlays in packaged builds
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
2026-09-19 08:24:03 +09:00
Yun Chan
74cbc8f6ae docs(map): record the 1.3.6 release
Some checks failed
deploy-site / deploy (push) Failing after 12m28s
2026-09-18 21:07:00 +09:00
Yun Chan
6766cb8c8e chore(release): 1.3.6
Some checks failed
deploy-site / deploy (push) Failing after 1m37s
release / release-windows (push) Failing after 6m6s
portable-unsigned / portable-windows (push) Failing after 18m52s
2026-09-18 20:58:52 +09:00