Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
76d8057
feat(scribe): core seams for a pro editor + settings sections
Jul 4, 2026
154b777
feat(scribe): add Scribe to the pro catalog + nav
Jul 4, 2026
bcb69a0
test(scribe): pro E2E — squiggle on typo, valid words untouched
Jul 4, 2026
4608108
docs(scribe): research findings + desktop implementation plan
Jul 4, 2026
39681b1
test(scribe): exercise the document review panel
Jul 4, 2026
ff09cd1
test(scribe): refresh squiggle screenshot for the two-column layout
Jul 4, 2026
d804c24
docs(scribe): roadmap — import→transform→edit, section feedback, lear…
Jul 4, 2026
f74d40e
test(scribe): settings expose style guides + learning loop
Jul 4, 2026
7526470
test(scribe): assert the import menu + refresh aligned-squiggle scree…
Jul 4, 2026
6481d8e
test(scribe): refresh review + settings screenshots
Jul 4, 2026
57cc8c5
feat(scribe): seed the chat composer from Scribe (continue-in-chat)
Jul 4, 2026
9480f78
test(scribe): refresh screenshots (readability report + hotkey settings)
Jul 4, 2026
e9719b4
feat(crm): preload bindings for create-entity + link-observation
Jul 4, 2026
71c76c4
test: fix stale clipboard placeholder assertion
Jul 4, 2026
0f18656
test(scribe): refresh screenshots
Jul 4, 2026
d0a315b
feat(stt): Parakeet transcription engine (additive, beside whisper)
Jul 4, 2026
1b89e7c
feat(stt): engine selector with whisper fallback
Jul 4, 2026
0e930a3
build(stt): CI staging for the Parakeet runtime
Jul 4, 2026
9b638d2
feat(models): Parakeet STT models in the catalog (downloadable)
Jul 4, 2026
d16b95f
feat(stt): use downloaded Parakeet models; keep whisper isolated
Jul 4, 2026
c011389
test(stt): e2e for the Parakeet model picker in Voice settings
Jul 4, 2026
3f15803
fix(stt): honor active whisper model by catalog id; de-em-dash catalo…
Jul 5, 2026
c42de2e
test(stt): point the model-picker e2e at the generalized component te…
Jul 5, 2026
ca7aa35
fix(stt): match active Parakeet model by catalog id; refresh screenshots
Jul 5, 2026
52e33dc
build(stt): stage Parakeet engine dylibs with the 0.0.28 gates
Jul 5, 2026
4b32e86
feat(onboarding): showcase Scribe in the Pro step
Jul 5, 2026
5417f05
fix(stt): correct sherpa-onnx flags + slim/robust staging (verified o…
Jul 5, 2026
fb1a0fd
test(stt): verify parser on real Parakeet output; gitignore the runtime
Jul 5, 2026
9c7c400
refactor(open-core): drop crm preload bindings for pro entity create/…
Jul 5, 2026
c71a807
fix(stt): honor Parakeet model pick when active slot stores a filename
Jul 5, 2026
705a1c5
fix(stt): route core transcription paths through the active engine
Jul 5, 2026
05576ea
fix(build): verify + harden Parakeet tarball staging (supply chain)
Jul 5, 2026
5e7a2ac
feat(stt): effectiveEngine() — single source of truth for engine labe…
Jul 5, 2026
034021d
fix(build): archive traversal check no longer fails open under pipefail
Jul 5, 2026
7076858
docs(scribe): system-wide inline overlay plan + AX feasibility probe
Jul 5, 2026
ed56b9b
feat(scribe): P1 overlay checkpoint — squiggles over native apps (ver…
Jul 5, 2026
e41d184
fix(scribe): overlay viewport culling + squiggle cap
Jul 5, 2026
708ac89
feat(scribe): overlay binary IPC mode (mechanism, engine-driven)
Jul 5, 2026
8533d51
build(scribe): compile + stage the overlay helper in CI
Jul 5, 2026
44e8d4c
perf(scribe): cache overlay bounds + gate selection measurement to ty…
Jul 5, 2026
035e986
feat(scribe): hover-to-fix — native menu at squiggles + AX apply
Jul 5, 2026
a31a271
docs(scribe): overlay build status (v1 implemented, on-device verific…
Jul 5, 2026
cff760a
feat(scribe): branded hover card (replaces the native NSMenu)
Jul 5, 2026
e9d040f
fix(scribe): correction card renders above the squiggle overlay
Jul 5, 2026
02f0f09
feat(scribe): respect macOS learned words (coexist, don't suppress)
Jul 5, 2026
c6214d2
fix(scribe): stop cursor disruption in Electron/browser + brand card …
Jul 5, 2026
c1811fc
fix(scribe): card clicks apply the fix + design-token card colors
Jul 5, 2026
ccd1944
feat(scribe): card category badge + settings gear
Jul 5, 2026
5dd4d20
feat(scribe): system-wide rewrite menu (Improve/Shorten/Tone/Translate)
Jul 5, 2026
258e446
feat(scribe): actionable AI rewrite on the card + on-brand styling
Jul 5, 2026
d07ae72
feat(scribe): theme-aware card + fix Rephrase not replacing text
Jul 5, 2026
481702d
fix(scribe): Rephrase rewrites the whole sentence (verified on-device)
Jul 5, 2026
8c8275c
fix(scribe): make Slack/Electron work + conservative live-underline +…
Jul 6, 2026
937d42c
build(scribe): compile the overlay binary automatically on npm run dev
Jul 6, 2026
732d041
docs(scribe): start gaps backlog (design-audit + functional + verific…
Jul 6, 2026
d764277
docs(scribe): mark G-1 (light-mode hex) resolved
Jul 6, 2026
92c1d98
feat(scribe): overlay squiggles use brand colors (red correctness / e…
Jul 6, 2026
c2bff00
docs(scribe): mark G-2 (color-coding) resolved - brand-aligned
Jul 6, 2026
c86aca2
feat(scribe): selection affordance — select a passage in any app → Re…
Jul 6, 2026
5014b68
fix(scribe): anchor the Rephrase pill to the selection, not the mouse
Jul 6, 2026
30cff1a
feat(a11y): honor prefers-reduced-motion app-wide (G-4)
Jul 6, 2026
b7e005f
docs(scribe): provit E2E findings writeup (G-9) + backlog G-3/G-4/G-9…
Jul 6, 2026
7ea9e85
docs(scribe): log G-11/12/13 (spelling false-positive fixes) in backlog
Jul 6, 2026
28dd46b
fix(scribe): don't offer Rephrase / squiggles on URL bars & non-prose
Jul 7, 2026
b24e31d
fix(scribe): style the markdown preview (Tailwind Preflight stripped …
Jul 7, 2026
6efe70f
feat(scribe): add TipTap for real rich-text mode + editor CSS
Jul 7, 2026
1f8ffd4
docs(scribe): log editor/rich-text items (G-14..G-17)
Jul 7, 2026
b3b0200
fix(scribe-overlay): never offer rephrase in the address bar
Jul 7, 2026
e3565ac
chore(scribe): bump pro (SOLID/DRY audit sweep) + log G-17..G-19
Jul 7, 2026
5a3aa2b
fix: remove posthog-js telemetry dep; fix onboarding Pro grid overflow
Jul 7, 2026
c977ec3
test: add jsdom component-integration test env for Scribe editors
Jul 7, 2026
01f43b3
docs: inherit shared design philosophy + add multi-agent operating model
Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ jobs:
mkdir -p resources/bin
cp scripts/dictation-hotkey/dictation-hotkey resources/bin/dictation-hotkey
chmod +x resources/bin/dictation-hotkey
# Stage the Parakeet STT runtime (sherpa-onnx CLI + ONNX model) into
# resources/bin/parakeet. Additive: with SHERPA_ONNX_URL / PARAKEET_MODEL_URL
# unset this is a no-op and transcription stays on whisper, so it can't break a
# release. Set those vars once the exact sherpa-onnx build + Parakeet export are pinned.
- name: Stage Parakeet STT runtime (optional)
env:
SHERPA_ONNX_URL: ${{ vars.SHERPA_ONNX_URL }}
PARAKEET_MODEL_URL: ${{ vars.PARAKEET_MODEL_URL }}
run: bash scripts/fetch-parakeet.sh
# Private pro source into pro/ → __OFFGRID_PRO__ true. The checkout action
# injects the token as an http.extraheader (never in a clone URL) and scrubs
# it afterwards — safer than embedding ${TOKEN} in a git URL.
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ electron/accessibility/ocr
# them. Only the heavy optional runtimes we don't ship are excluded.
resources/bin/coreml-sd/
resources/bin/mflux/
# Parakeet STT runtime — CI-staged via scripts/fetch-parakeet.sh (like mflux/coreml-sd),
# not committed. Devs stage it locally with the same script.
resources/bin/parakeet/
# Gateway probe scratch output
.gateway-probe/
# TS build cache
Expand All @@ -50,3 +53,6 @@ scripts/dictation-hotkey/dictation-hotkey

# Local demo profile — synthetic-data run target for `npm run demo` (never real userData)
.demo-profile/

# local PR-campaign target list (not for commit)
pr-targets.local.md
250 changes: 250 additions & 0 deletions docs/WRITING_ASSISTANT_DESKTOP_IMPLEMENTATION.md

Large diffs are not rendered by default.

197 changes: 197 additions & 0 deletions docs/WRITING_ASSISTANT_PLAN.md

Large diffs are not rendered by default.

370 changes: 243 additions & 127 deletions e2e/pro.spec.ts

Large diffs are not rendered by default.

Binary file added e2e/screenshots/parakeet-models.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/screenshots/scribe-review.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/screenshots/scribe-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/screenshots/scribe-squiggle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 2 additions & 5 deletions packages/models/dist/adapters/node.d.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { D as DownloadBridge } from '../types-C3JiELTS.mjs';

declare class NodeDownloadBridge implements DownloadBridge {
import type { DownloadBridge } from '../types';
export declare class NodeDownloadBridge implements DownloadBridge {
private readonly modelsDir;
constructor(modelsDir: string);
pathFor(fileName: string): string;
Expand All @@ -10,5 +9,3 @@ declare class NodeDownloadBridge implements DownloadBridge {
signal?: AbortSignal;
}): Promise<number>;
}

export { NodeDownloadBridge };
7 changes: 2 additions & 5 deletions packages/models/dist/adapters/node.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { D as DownloadBridge } from '../types-C3JiELTS.js';

declare class NodeDownloadBridge implements DownloadBridge {
import type { DownloadBridge } from '../types';
export declare class NodeDownloadBridge implements DownloadBridge {
private readonly modelsDir;
constructor(modelsDir: string);
pathFor(fileName: string): string;
Expand All @@ -10,5 +9,3 @@ declare class NodeDownloadBridge implements DownloadBridge {
signal?: AbortSignal;
}): Promise<number>;
}

export { NodeDownloadBridge };
4 changes: 1 addition & 3 deletions packages/models/dist/adapters/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var NodeDownloadBridge = class {
this.modelsDir = modelsDir;
import_fs.default.mkdirSync(modelsDir, { recursive: true });
}
modelsDir;
pathFor(fileName) {
return import_path.default.join(this.modelsDir, fileName);
}
Expand All @@ -52,7 +51,6 @@ var NodeDownloadBridge = class {
}
}
async download(url, destPath, opts) {
var _a;
const tmp = `${destPath}.part`;
let start = 0;
try {
Expand All @@ -78,7 +76,7 @@ var NodeDownloadBridge = class {
if (done) break;
out.write(Buffer.from(value));
written += value.length;
(_a = opts.onProgress) == null ? void 0 : _a.call(opts, written, total || written);
opts.onProgress?.(written, total || written);
}
} finally {
out.end();
Expand Down
4 changes: 1 addition & 3 deletions packages/models/dist/adapters/node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ var NodeDownloadBridge = class {
this.modelsDir = modelsDir;
fs.mkdirSync(modelsDir, { recursive: true });
}
modelsDir;
pathFor(fileName) {
return path.join(this.modelsDir, fileName);
}
Expand All @@ -19,7 +18,6 @@ var NodeDownloadBridge = class {
}
}
async download(url, destPath, opts) {
var _a;
const tmp = `${destPath}.part`;
let start = 0;
try {
Expand All @@ -45,7 +43,7 @@ var NodeDownloadBridge = class {
if (done) break;
out.write(Buffer.from(value));
written += value.length;
(_a = opts.onProgress) == null ? void 0 : _a.call(opts, written, total || written);
opts.onProgress?.(written, total || written);
}
} finally {
out.end();
Expand Down
6 changes: 6 additions & 0 deletions packages/models/dist/catalog.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ModelEntry, ModelKind, ModelRecommendationTier } from './types';
export declare const RECOMMENDATION_TIERS: ModelRecommendationTier[];
export declare function recommendForRam(ramGb: number): ModelRecommendationTier;
export declare const CATALOG: ModelEntry[];
export declare function modelsByKind(kind: ModelKind): ModelEntry[];
export declare const MODEL_KINDS: ModelKind[];
6 changes: 6 additions & 0 deletions packages/models/dist/catalog.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ModelEntry, ModelKind, ModelRecommendationTier } from './types';
export declare const RECOMMENDATION_TIERS: ModelRecommendationTier[];
export declare function recommendForRam(ramGb: number): ModelRecommendationTier;
export declare const CATALOG: ModelEntry[];
export declare function modelsByKind(kind: ModelKind): ModelEntry[];
export declare const MODEL_KINDS: ModelKind[];
11 changes: 11 additions & 0 deletions packages/models/dist/credibility.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Credibility = 'offgrid' | 'official' | 'verified-quantizer' | 'community';
export declare const OFFGRID_AUTHORS: string[];
export declare const OFFICIAL_MODEL_AUTHORS: Record<string, string>;
export declare const VERIFIED_QUANTIZERS: Record<string, string>;
export declare const CREDIBILITY_LABELS: Record<Credibility, {
label: string;
description: string;
color: string;
}>;
/** Classify a HF author into a credibility tier. */
export declare function determineCredibility(author: string): Credibility;
11 changes: 11 additions & 0 deletions packages/models/dist/credibility.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Credibility = 'offgrid' | 'official' | 'verified-quantizer' | 'community';
export declare const OFFGRID_AUTHORS: string[];
export declare const OFFICIAL_MODEL_AUTHORS: Record<string, string>;
export declare const VERIFIED_QUANTIZERS: Record<string, string>;
export declare const CREDIBILITY_LABELS: Record<Credibility, {
label: string;
description: string;
color: string;
}>;
/** Classify a HF author into a credibility tier. */
export declare function determineCredibility(author: string): Credibility;
13 changes: 13 additions & 0 deletions packages/models/dist/download.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { DownloadBridge, DownloadProgress, ModelEntry, ModelStore } from './types';
export declare class ModelDownloader {
private readonly bridge;
private readonly store;
private aborts;
private listeners;
constructor(bridge: DownloadBridge, store: ModelStore);
onProgress(cb: (p: DownloadProgress) => void): () => void;
isInstalled(modelId: string): boolean;
cancel(modelId: string): void;
private emit;
download(entry: ModelEntry): Promise<boolean>;
}
13 changes: 13 additions & 0 deletions packages/models/dist/download.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { DownloadBridge, DownloadProgress, ModelEntry, ModelStore } from './types';
export declare class ModelDownloader {
private readonly bridge;
private readonly store;
private aborts;
private listeners;
constructor(bridge: DownloadBridge, store: ModelStore);
onProgress(cb: (p: DownloadProgress) => void): () => void;
isInstalled(modelId: string): boolean;
cancel(modelId: string): void;
private emit;
download(entry: ModelEntry): Promise<boolean>;
}
106 changes: 106 additions & 0 deletions packages/models/dist/filters.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { Credibility } from './credibility';
export type ModelTypeFilter = 'all' | 'text' | 'vision' | 'code' | 'image-gen';
export type CredibilityFilter = 'all' | Credibility;
export type SizeFilter = 'all' | 'tiny' | 'small' | 'medium' | 'large';
export type SortOption = 'recommended' | 'bestfit' | 'size' | 'downloads' | 'recency';
export interface FilterState {
orgs: string[];
type: ModelTypeFilter;
source: CredibilityFilter;
size: SizeFilter;
quant: string;
sort: SortOption;
}
export declare const initialFilterState: FilterState;
/** Normalized model the filters/sorts operate on (map HF results into this). */
export interface FilterableModel {
id: string;
name: string;
org: string;
credibility?: Credibility;
params?: number | null;
tags?: string[];
downloads?: number;
likes?: number;
lastModified?: string;
minRamGb?: number;
files?: {
sizeBytes?: number;
quant?: string;
}[];
}
export declare const SIZE_OPTIONS: readonly [{
readonly key: "tiny";
readonly label: "Tiny (<2B)";
readonly min: 0;
readonly max: 2;
}, {
readonly key: "small";
readonly label: "Small (2-5B)";
readonly min: 2;
readonly max: 5;
}, {
readonly key: "medium";
readonly label: "Medium (5-15B)";
readonly min: 5;
readonly max: 15;
}, {
readonly key: "large";
readonly label: "Large (15B+)";
readonly min: 15;
readonly max: number;
}];
export declare const MODEL_TYPE_OPTIONS: readonly [{
readonly key: "text";
readonly label: "Text";
}, {
readonly key: "vision";
readonly label: "Vision";
}, {
readonly key: "code";
readonly label: "Code";
}, {
readonly key: "image-gen";
readonly label: "Image";
}];
export declare const CREDIBILITY_OPTIONS: readonly [{
readonly key: "offgrid";
readonly label: "Off Grid";
}, {
readonly key: "official";
readonly label: "Official";
}, {
readonly key: "verified-quantizer";
readonly label: "Verified";
}, {
readonly key: "community";
readonly label: "Community";
}];
export declare const SORT_OPTIONS: readonly [{
readonly key: "recommended";
readonly label: "Recommended";
}, {
readonly key: "bestfit";
readonly label: "Best fit";
}, {
readonly key: "downloads";
readonly label: "Downloads";
}, {
readonly key: "size";
readonly label: "Size";
}, {
readonly key: "recency";
readonly label: "Recent";
}];
/** Parse a billions-of-parameters count from a model name/id, in billions
* ("Qwen3.5-2B" -> 2, "SmolVLM2-500M" -> 0.5). Returns null if none found. */
export declare function parseParamCount(nameOrId: string): number | null;
/** Detect a model's type from its name + tags. */
export declare function getModelType(name: string, tags?: string[]): ModelTypeFilter;
/** Lower is better. Ideal model uses ~40% of RAM; penalize >75% (too slow). */
export declare function bestFitScore(m: FilterableModel, ramGb: number): number;
export declare function hasActiveFilters(state: FilterState): boolean;
export declare function applyFilters<T extends FilterableModel>(models: T[], state: FilterState): T[];
export declare function applySort<T extends FilterableModel>(models: T[], sort: SortOption, ramGb?: number): T[];
/** Apply filters then sort in one pass. */
export declare function filterAndSort<T extends FilterableModel>(models: T[], state: FilterState, ramGb?: number): T[];
106 changes: 106 additions & 0 deletions packages/models/dist/filters.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { Credibility } from './credibility';
export type ModelTypeFilter = 'all' | 'text' | 'vision' | 'code' | 'image-gen';
export type CredibilityFilter = 'all' | Credibility;
export type SizeFilter = 'all' | 'tiny' | 'small' | 'medium' | 'large';
export type SortOption = 'recommended' | 'bestfit' | 'size' | 'downloads' | 'recency';
export interface FilterState {
orgs: string[];
type: ModelTypeFilter;
source: CredibilityFilter;
size: SizeFilter;
quant: string;
sort: SortOption;
}
export declare const initialFilterState: FilterState;
/** Normalized model the filters/sorts operate on (map HF results into this). */
export interface FilterableModel {
id: string;
name: string;
org: string;
credibility?: Credibility;
params?: number | null;
tags?: string[];
downloads?: number;
likes?: number;
lastModified?: string;
minRamGb?: number;
files?: {
sizeBytes?: number;
quant?: string;
}[];
}
export declare const SIZE_OPTIONS: readonly [{
readonly key: "tiny";
readonly label: "Tiny (<2B)";
readonly min: 0;
readonly max: 2;
}, {
readonly key: "small";
readonly label: "Small (2-5B)";
readonly min: 2;
readonly max: 5;
}, {
readonly key: "medium";
readonly label: "Medium (5-15B)";
readonly min: 5;
readonly max: 15;
}, {
readonly key: "large";
readonly label: "Large (15B+)";
readonly min: 15;
readonly max: number;
}];
export declare const MODEL_TYPE_OPTIONS: readonly [{
readonly key: "text";
readonly label: "Text";
}, {
readonly key: "vision";
readonly label: "Vision";
}, {
readonly key: "code";
readonly label: "Code";
}, {
readonly key: "image-gen";
readonly label: "Image";
}];
export declare const CREDIBILITY_OPTIONS: readonly [{
readonly key: "offgrid";
readonly label: "Off Grid";
}, {
readonly key: "official";
readonly label: "Official";
}, {
readonly key: "verified-quantizer";
readonly label: "Verified";
}, {
readonly key: "community";
readonly label: "Community";
}];
export declare const SORT_OPTIONS: readonly [{
readonly key: "recommended";
readonly label: "Recommended";
}, {
readonly key: "bestfit";
readonly label: "Best fit";
}, {
readonly key: "downloads";
readonly label: "Downloads";
}, {
readonly key: "size";
readonly label: "Size";
}, {
readonly key: "recency";
readonly label: "Recent";
}];
/** Parse a billions-of-parameters count from a model name/id, in billions
* ("Qwen3.5-2B" -> 2, "SmolVLM2-500M" -> 0.5). Returns null if none found. */
export declare function parseParamCount(nameOrId: string): number | null;
/** Detect a model's type from its name + tags. */
export declare function getModelType(name: string, tags?: string[]): ModelTypeFilter;
/** Lower is better. Ideal model uses ~40% of RAM; penalize >75% (too slow). */
export declare function bestFitScore(m: FilterableModel, ramGb: number): number;
export declare function hasActiveFilters(state: FilterState): boolean;
export declare function applyFilters<T extends FilterableModel>(models: T[], state: FilterState): T[];
export declare function applySort<T extends FilterableModel>(models: T[], sort: SortOption, ramGb?: number): T[];
/** Apply filters then sort in one pass. */
export declare function filterAndSort<T extends FilterableModel>(models: T[], state: FilterState, ramGb?: number): T[];
Loading
Loading