diff --git a/AppSupport/Info.plist b/AppSupport/Info.plist
index 9cf2470..31c84ac 100644
--- a/AppSupport/Info.plist
+++ b/AppSupport/Info.plist
@@ -1,6 +1,6 @@
SUFeedURL
- https://grammagem.app/appcast.xml
+ https://www.grammargem.com/appcast.xml
SUPublicEDKey
IwYch+DRgNk3gmCwe86z+SIc4sRdyDf4Z6xGpS+JXDc=
SUEnableAutomaticChecks
diff --git a/Package.swift b/Package.swift
index 4eb49a7..bceb8a5 100644
--- a/Package.swift
+++ b/Package.swift
@@ -2,7 +2,7 @@
import PackageDescription
import Foundation
-// GrammaGem — native macOS menu-bar writing assistant.
+// GrammarGem — native macOS menu-bar writing assistant.
//
// Layer-1 grammar is the real **Harper** core (Apache-2.0), embedded as a Rust
// C-FFI static library (see `harper-ffi/`). Build the lib first with
@@ -18,41 +18,41 @@ let packageRoot = URL(fileURLWithPath: #filePath).deletingLastPathComponent().pa
let harperLibDir = packageRoot + "/harper-ffi/lib"
let package = Package(
- name: "GrammaGem",
+ name: "GrammarGem",
platforms: [
.macOS(.v14) // Apple Silicon, macOS 14+ (per the product spec)
],
products: [
- .executable(name: "GrammaGem", targets: ["GrammaGem"])
+ .executable(name: "GrammarGem", targets: ["GrammarGem"])
],
dependencies: [
// On-device LLM runtime. Pinned to an exact tag (the LLM libraries still
// ship from this repo at 2.25.9; the package name is "mlx-libraries").
.package(url: "https://github.com/ml-explore/mlx-swift-examples.git", exact: "2.25.9"),
- // Secure auto-updates (EdDSA-signed appcast served from grammagem.app).
+ // Secure auto-updates (EdDSA-signed appcast served from grammargem.com).
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.6.0"),
],
targets: [
// C shim exposing libharper_ffi's C ABI (harper-ffi/include/harper.h) to Swift.
.target(name: "CHarper", path: "Sources/CHarper"),
.executableTarget(
- name: "GrammaGem",
+ name: "GrammarGem",
dependencies: [
"CHarper",
.product(name: "MLXLLM", package: "mlx-swift-examples"),
.product(name: "MLXLMCommon", package: "mlx-swift-examples"),
.product(name: "Sparkle", package: "Sparkle"),
],
- path: "Sources/GrammaGem",
+ path: "Sources/GrammarGem",
linkerSettings: [
// Link the prebuilt Harper static library (arm64 slice).
.unsafeFlags(["-L\(harperLibDir)", "-lharper_ffi"])
]
),
.testTarget(
- name: "GrammaGemTests",
- dependencies: ["GrammaGem"],
- path: "Tests/GrammaGemTests"
+ name: "GrammarGemTests",
+ dependencies: ["GrammarGem"],
+ path: "Tests/GrammarGemTests"
),
]
)
diff --git a/README.md b/README.md
index c4b5b01..5a7a26e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# ✒️ GrammaGem
+# ✒️ GrammarGem
### Write it once. Get it right.
@@ -12,7 +12,7 @@ Fix grammar, sharpen tone, and rewrite anything — in every app — for one pay


-[**⬇️ Download GrammaGem**](https://grammagem.app/#pricing) · [Docs](https://grammagem.app/docs) · [How it works](https://grammagem.app)
+[**⬇️ Download GrammarGem**](https://www.grammargem.com/#pricing) · [Docs](https://www.grammargem.com/docs) · [How it works](https://www.grammargem.com)
@@ -20,7 +20,7 @@ Fix grammar, sharpen tone, and rewrite anything — in every app — for one pay
## Stop renting your grammar checker.
-Grammarly costs around **$144 every year**, reads everything you type, and sends it to their servers. GrammaGem does the same job — **better** — entirely on your Mac, for a **single payment**.
+Grammarly costs around **$144 every year**, reads everything you type, and sends it to their servers. GrammarGem does the same job — **better** — entirely on your Mac, for a **single payment**.
Select text in any app, press one hotkey, and it’s polished in place. No copy-paste, no switching windows, and **nothing ever leaves your machine**.
@@ -30,7 +30,7 @@ Select text in any app, press one hotkey, and it’s polished in place. No copy-
## Why people switch
-| | Grammarly | **GrammaGem** |
+| | Grammarly | **GrammarGem** |
|---|:---:|:---:|
| **Price** | ~$144 / year, forever | **Pay once** |
| **Your words** | Sent to the cloud | **Never leave your Mac** |
@@ -46,7 +46,7 @@ Select text in any app, press one hotkey, and it’s polished in place. No copy-
- 🔒 **Private by design** — everything runs on your Mac’s Neural Engine. Your writing is never uploaded, logged, or used to train a model.
- ⌨️ **One global hotkey** — fix or rewrite the selected text in any app: Mail, Slack, Notion, Chrome, Messages, VS Code, and more.
- ✨ **Ask & rewrite** — “make it shorter,” “more formal,” “translate to German” — done in place.
-- 🎚️ **Writing Modes** — Email, Post, Team Chat, Academic, Code — switch the tone instantly, or let GrammaGem pick the right one for the app you’re in.
+- 🎚️ **Writing Modes** — Email, Post, Team Chat, Academic, Code — switch the tone instantly, or let GrammarGem pick the right one for the app you’re in.
- 📖 **Personal dictionary** — teach it your names, brand terms, and jargon once; it stops “correcting” them forever.
- 🖥️ **A clean management app** — manage your devices, shortcuts, modes, and privacy from one tidy window.
@@ -67,7 +67,7 @@ No subscription. No per-seat fees. One payment, lifetime updates.
-[**⬇️ Get GrammaGem**](https://grammagem.app/#pricing)
+[**⬇️ Get GrammarGem**](https://www.grammargem.com/#pricing)
@@ -85,17 +85,17 @@ No. Core features work fully offline.
No — it’s a one-time purchase with lifetime updates and a 14-day money-back guarantee.
**What about plagiarism detection?**
-GrammaGem is private-first, so it doesn’t do cloud plagiarism scanning — that would require sending your writing to a server. Everything else you’d expect is here.
+GrammarGem is private-first, so it doesn’t do cloud plagiarism scanning — that would require sending your writing to a server. Everything else you’d expect is here.
---
## Built in public
-GrammaGem is open source under the **MIT License**. We believe a privacy tool should be one you can actually inspect. Issues and pull requests are welcome.
+GrammarGem is open source under the **MIT License**. We believe a privacy tool should be one you can actually inspect. Issues and pull requests are welcome.
-**[grammagem.app](https://grammagem.app)**
+**[grammargem.com](https://www.grammargem.com)**
© 2026 FounderGem LLC · 1309 Coffeen Avenue STE 1200, Sheridan, WY 82801, USA
diff --git a/Resources/Modes/README.md b/Resources/Modes/README.md
index 3f56204..f343466 100644
--- a/Resources/Modes/README.md
+++ b/Resources/Modes/README.md
@@ -1,7 +1,7 @@
# Built-in Writing Modes
Each Mode is a named system-prompt preset for the local LLM. The canonical,
-compiled definitions live in [`Sources/GrammaGem/AI/ModeRegistry.swift`](../../Sources/GrammaGem/AI/ModeRegistry.swift);
+compiled definitions live in [`Sources/GrammarGem/AI/ModeRegistry.swift`](../../Sources/GrammarGem/AI/ModeRegistry.swift);
these JSON files mirror them for transparency and easy editing.
| Mode | Tier | In public repo? |
diff --git a/Sources/CHarper/shim.c b/Sources/CHarper/shim.c
index 5089f89..4b76007 100644
--- a/Sources/CHarper/shim.c
+++ b/Sources/CHarper/shim.c
@@ -1,4 +1,4 @@
// CHarper is a header-only shim that exposes libharper_ffi's C ABI (declared in
// include/harper.h) to Swift as the `CHarper` module. The actual symbols are
-// provided by libharper_ffi.a, linked via the GrammaGem target's linkerSettings.
+// provided by libharper_ffi.a, linked via the GrammarGem target's linkerSettings.
// This translation unit exists only so SwiftPM treats CHarper as a C target.
diff --git a/Sources/GrammarGem/AI/CustomModesStore.swift b/Sources/GrammarGem/AI/CustomModesStore.swift
index 9c15c09..76629fc 100644
--- a/Sources/GrammarGem/AI/CustomModesStore.swift
+++ b/Sources/GrammarGem/AI/CustomModesStore.swift
@@ -8,8 +8,8 @@ final class CustomModesStore: ObservableObject {
/// bundleID -> mode id (overrides the default App-Aware mapping).
@Published private(set) var appOverrides: [String: String]
- private let modesKey = "GrammaGem.customModes"
- private let mapKey = "GrammaGem.appModeOverrides"
+ private let modesKey = "GrammarGem.customModes"
+ private let mapKey = "GrammarGem.appModeOverrides"
private let d = UserDefaults.standard
init() {
diff --git a/Sources/GrammarGem/AI/ModelManager.swift b/Sources/GrammarGem/AI/ModelManager.swift
index 0402aff..c30d67c 100644
--- a/Sources/GrammarGem/AI/ModelManager.swift
+++ b/Sources/GrammarGem/AI/ModelManager.swift
@@ -38,7 +38,7 @@ final class ModelManager: ObservableObject {
nonisolated static func modelsDirectory() -> URL {
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
- return base.appendingPathComponent("GrammaGem/Models", isDirectory: true)
+ return base.appendingPathComponent("GrammarGem/Models", isDirectory: true)
}
nonisolated static func modelDir(_ repo: String) -> URL {
diff --git a/Sources/GrammarGem/App/AppState.swift b/Sources/GrammarGem/App/AppState.swift
index 66d0bb0..6d8b2d5 100644
--- a/Sources/GrammarGem/App/AppState.swift
+++ b/Sources/GrammarGem/App/AppState.swift
@@ -115,7 +115,7 @@ final class AppState: ObservableObject {
}
Task { await license.validateIfNeeded() }
- Log.app.info("GrammaGem started. Licensed: \(self.license.isLicensed, privacy: .public)")
+ Log.app.info("GrammarGem started. Licensed: \(self.license.isLicensed, privacy: .public)")
}
// MARK: - Hotkey handling
@@ -205,19 +205,19 @@ final class AppState: ObservableObject {
return decision
}
- /// Pause/resume all of GrammaGem (hotkeys + live monitoring).
+ /// Pause/resume all of GrammarGem (hotkeys + live monitoring).
func togglePause() {
isPaused.toggle()
liveMonitor.setPaused(isPaused)
lastStatus = isPaused ? "Paused" : "Ready"
}
- /// A reason GrammaGem should stay out of the way right now, or nil.
+ /// A reason GrammarGem should stay out of the way right now, or nil.
private func blockReason() -> String? {
- if isPaused { return "GrammaGem is paused." }
+ if isPaused { return "GrammarGem is paused." }
let front = detector.frontmost()
if exclusions.isBlocked(bundleID: front?.bundleID, domain: detector.frontmostDomain()) {
- return "GrammaGem is off for \(front?.name ?? "this app")."
+ return "GrammarGem is off for \(front?.name ?? "this app")."
}
return nil
}
@@ -282,7 +282,7 @@ final class AppState: ObservableObject {
if onboardingWindow == nil {
let host = NSHostingController(rootView: OnboardingView().environmentObject(self))
let win = NSWindow(contentViewController: host)
- win.title = "Welcome to GrammaGem"
+ win.title = "Welcome to GrammarGem"
win.styleMask = [.titled, .closable, .fullSizeContentView]
win.setContentSize(NSSize(width: 560, height: 720))
win.isReleasedWhenClosed = false
@@ -311,7 +311,7 @@ final class AppState: ObservableObject {
.environmentObject(liveMonitor)
let host = NSHostingController(rootView: root)
let win = NSWindow(contentViewController: host)
- win.title = "GrammaGem"
+ win.title = "GrammarGem"
win.styleMask = [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView]
win.setContentSize(NSSize(width: 940, height: 660))
win.minSize = NSSize(width: 820, height: 560)
@@ -349,7 +349,7 @@ final class AppState: ObservableObject {
if askWindow == nil {
let host = NSHostingController(rootView: AskView().environmentObject(self))
let win = NSWindow(contentViewController: host)
- win.title = "Ask GrammaGem"
+ win.title = "Ask GrammarGem"
win.styleMask = [.titled, .closable]
win.setContentSize(NSSize(width: 460, height: 220))
win.isReleasedWhenClosed = false
diff --git a/Sources/GrammarGem/App/AskView.swift b/Sources/GrammarGem/App/AskView.swift
index ce02754..618c873 100644
--- a/Sources/GrammarGem/App/AskView.swift
+++ b/Sources/GrammarGem/App/AskView.swift
@@ -8,7 +8,7 @@ struct AskView: View {
var body: some View {
VStack(alignment: .leading, spacing: 14) {
- Text("Ask GrammaGem")
+ Text("Ask GrammarGem")
.font(.headline)
Text("Acts on the text selected in the frontmost app. Runs on-device.")
.font(.caption)
diff --git a/Sources/GrammarGem/App/GrammarGemApp.swift b/Sources/GrammarGem/App/GrammarGemApp.swift
index 2f0031e..5a2bcf4 100644
--- a/Sources/GrammarGem/App/GrammarGemApp.swift
+++ b/Sources/GrammarGem/App/GrammarGemApp.swift
@@ -5,7 +5,7 @@ import AppKit
/// and runs one real generation (verifies the MLX runtime + metallib headlessly),
/// then exits. Otherwise the normal menu-bar app launches.
@main
-enum GrammaGemMain {
+enum GrammarGemMain {
static func main() {
if CommandLine.arguments.contains("--mlx-selftest") {
MLXSelfTest.run()
@@ -16,7 +16,7 @@ enum GrammaGemMain {
UpdaterSelfTest.run(feedURL: feed)
return
}
- GrammaGemApp.main()
+ GrammarGemApp.main()
}
}
@@ -47,7 +47,7 @@ enum MLXSelfTest {
/// App entry point. A menu-bar (`LSUIElement`) SwiftUI app: the only persistent
/// scene is the `MenuBarExtra`; Settings uses the standard scene; Onboarding and
/// Ask are AppKit-hosted windows opened on demand (see `AppState`).
-struct GrammaGemApp: App {
+struct GrammarGemApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
@StateObject private var app = AppState.shared
diff --git a/Sources/GrammarGem/App/MainWindow/AccountViews.swift b/Sources/GrammarGem/App/MainWindow/AccountViews.swift
index 6304145..e77fec2 100644
--- a/Sources/GrammarGem/App/MainWindow/AccountViews.swift
+++ b/Sources/GrammarGem/App/MainWindow/AccountViews.swift
@@ -8,7 +8,7 @@ struct GeneralView: View {
@EnvironmentObject private var permissions: Permissions
var body: some View {
- DetailScaffold(title: "General", subtitle: "Make GrammaGem feel like yours.") {
+ DetailScaffold(title: "General", subtitle: "Make GrammarGem feel like yours.") {
Card {
Toggle("Launch at login", isOn: $prefs.launchAtLogin)
Divider()
@@ -134,7 +134,7 @@ struct AboutView: View {
.frame(width: 56, height: 56)
.background(GG.emerald.opacity(0.12), in: RoundedRectangle(cornerRadius: 14))
VStack(alignment: .leading, spacing: 2) {
- Text("GrammaGem").font(.title2.bold())
+ Text("GrammarGem").font(.title2.bold())
Text("Version \(AppConfig.appVersion)").foregroundStyle(.secondary)
Text("The private, on-device writing assistant for Mac.")
.font(.callout).foregroundStyle(.secondary)
@@ -153,7 +153,7 @@ struct AboutView: View {
Card {
Text("Open source").font(.headline)
- Text("GrammaGem is open source under the MIT License — a privacy tool you can inspect.")
+ Text("GrammarGem is open source under the MIT License — a privacy tool you can inspect.")
.font(.callout).foregroundStyle(.secondary)
}
diff --git a/Sources/GrammarGem/App/MainWindow/DashboardView.swift b/Sources/GrammarGem/App/MainWindow/DashboardView.swift
index 7bdc8b3..b57c192 100644
--- a/Sources/GrammarGem/App/MainWindow/DashboardView.swift
+++ b/Sources/GrammarGem/App/MainWindow/DashboardView.swift
@@ -91,7 +91,7 @@ struct DashboardView: View {
Label("Fix selection", systemImage: "checkmark.circle")
}
Button { app.showAsk() } label: {
- Label("Ask GrammaGem", systemImage: "sparkles")
+ Label("Ask GrammarGem", systemImage: "sparkles")
}
if !license.isLicensed {
Spacer()
diff --git a/Sources/GrammarGem/App/MainWindow/DataViews.swift b/Sources/GrammarGem/App/MainWindow/DataViews.swift
index de9c92d..4b35b4b 100644
--- a/Sources/GrammarGem/App/MainWindow/DataViews.swift
+++ b/Sources/GrammarGem/App/MainWindow/DataViews.swift
@@ -12,7 +12,7 @@ struct DictionaryView: View {
var body: some View {
DetailScaffold(
title: "Personal Dictionary",
- subtitle: "Words GrammaGem should never “correct” — names, brands, jargon."
+ subtitle: "Words GrammarGem should never “correct” — names, brands, jargon."
) {
Card {
HStack {
diff --git a/Sources/GrammarGem/App/MainWindow/DevicesView.swift b/Sources/GrammarGem/App/MainWindow/DevicesView.swift
index d2147f9..d39a93e 100644
--- a/Sources/GrammarGem/App/MainWindow/DevicesView.swift
+++ b/Sources/GrammarGem/App/MainWindow/DevicesView.swift
@@ -83,7 +83,7 @@ struct DevicesView: View {
HStack {
Link("Open license portal", destination: AppConfig.modelPortalURL)
Spacer()
- Text("Add a Mac: install GrammaGem there and paste the same key.")
+ Text("Add a Mac: install GrammarGem there and paste the same key.")
.font(.caption).foregroundStyle(.secondary)
}
}
diff --git a/Sources/GrammarGem/App/MainWindow/ExclusionsView.swift b/Sources/GrammarGem/App/MainWindow/ExclusionsView.swift
index 74e4667..0c005d8 100644
--- a/Sources/GrammarGem/App/MainWindow/ExclusionsView.swift
+++ b/Sources/GrammarGem/App/MainWindow/ExclusionsView.swift
@@ -8,12 +8,12 @@ struct ExclusionsView: View {
var body: some View {
DetailScaffold(
title: "Page Blocker",
- subtitle: "Keep GrammaGem out of the way in sensitive apps and sites."
+ subtitle: "Keep GrammarGem out of the way in sensitive apps and sites."
) {
Card {
Label("How it works", systemImage: "hand.raised")
.font(.headline)
- Text("When the frontmost app or website is on these lists, GrammaGem stops monitoring and the hotkeys do nothing — useful for password managers, banking, and private work.")
+ Text("When the frontmost app or website is on these lists, GrammarGem stops monitoring and the hotkeys do nothing — useful for password managers, banking, and private work.")
.font(.callout).foregroundStyle(.secondary)
}
diff --git a/Sources/GrammarGem/App/MainWindow/MainWindowView.swift b/Sources/GrammarGem/App/MainWindow/MainWindowView.swift
index bc81ab6..2b2d224 100644
--- a/Sources/GrammarGem/App/MainWindow/MainWindowView.swift
+++ b/Sources/GrammarGem/App/MainWindow/MainWindowView.swift
@@ -59,7 +59,7 @@ enum MainSection: String, CaseIterable, Identifiable {
]
}
-/// The full GrammaGem management window: a clean sidebar + detail layout.
+/// The full GrammarGem management window: a clean sidebar + detail layout.
struct MainWindowView: View {
@State private var selection: MainSection?
diff --git a/Sources/GrammarGem/App/MainWindow/SystemViews.swift b/Sources/GrammarGem/App/MainWindow/SystemViews.swift
index 41b5d08..bec3bb8 100644
--- a/Sources/GrammarGem/App/MainWindow/SystemViews.swift
+++ b/Sources/GrammarGem/App/MainWindow/SystemViews.swift
@@ -17,7 +17,7 @@ struct ShortcutsView: View {
combo: $prefs.fixHotkey)
Divider()
shortcutRow(
- name: "Ask GrammaGem",
+ name: "Ask GrammarGem",
detail: "Rewrite with an instruction",
combo: $prefs.askHotkey)
}
@@ -141,7 +141,7 @@ struct PrivacyView: View {
}
Card {
- Text("The only times GrammaGem uses the network").font(.headline)
+ Text("The only times GrammarGem uses the network").font(.headline)
networkRow("One-time AI model download", "Hugging Face, on first run")
Divider()
networkRow("License activation & checks", "To enforce your device cap")
diff --git a/Sources/GrammarGem/App/MainWindow/WritingModesView.swift b/Sources/GrammarGem/App/MainWindow/WritingModesView.swift
index e25b9f2..f981b27 100644
--- a/Sources/GrammarGem/App/MainWindow/WritingModesView.swift
+++ b/Sources/GrammarGem/App/MainWindow/WritingModesView.swift
@@ -15,7 +15,7 @@ struct WritingModesView: View {
var body: some View {
DetailScaffold(
title: "Writing Modes",
- subtitle: "Tune the tone for every context — or let GrammaGem pick it for you."
+ subtitle: "Tune the tone for every context — or let GrammarGem pick it for you."
) {
builtInCard
customCard
@@ -88,7 +88,7 @@ struct WritingModesView: View {
private var appAwareCard: some View {
Card {
HStack { Text("App-aware switching").font(.headline); if !paid { UpgradeBadge() } }
- Text("Map an app to a Mode and GrammaGem applies it automatically when that app is in front.")
+ Text("Map an app to a Mode and GrammarGem applies it automatically when that app is in front.")
.font(.callout).foregroundStyle(.secondary)
if !customModes.appOverrides.isEmpty {
diff --git a/Sources/GrammarGem/App/MenuBarContent.swift b/Sources/GrammarGem/App/MenuBarContent.swift
index 053e3dd..4c00415 100644
--- a/Sources/GrammarGem/App/MenuBarContent.swift
+++ b/Sources/GrammarGem/App/MenuBarContent.swift
@@ -49,7 +49,7 @@ struct MenuBarContent: View {
actionRow(title: "Fix selection", shortcut: app.preferences.fixHotkey.display) {
Task { await app.runFix() }
}
- actionRow(title: "Ask GrammaGem…", shortcut: app.preferences.askHotkey.display) {
+ actionRow(title: "Ask GrammarGem…", shortcut: app.preferences.askHotkey.display) {
app.showAsk()
}
if app.gate.appAwareEnabled {
@@ -57,14 +57,14 @@ struct MenuBarContent: View {
Task { await app.runAppAwareRewrite() }
}
}
- actionRow(title: app.isPaused ? "Resume GrammaGem" : "Pause GrammaGem", shortcut: nil) {
+ actionRow(title: app.isPaused ? "Resume GrammarGem" : "Pause GrammarGem", shortcut: nil) {
app.togglePause()
}
Divider()
actionRow(title: "Show live issues…", shortcut: nil) { app.showLiveCheck() }
- actionRow(title: "Open GrammaGem…", shortcut: nil) { app.showMainWindow() }
+ actionRow(title: "Open GrammarGem…", shortcut: nil) { app.showMainWindow() }
actionRow(title: "Manage devices…", shortcut: nil) { app.showMainWindow(select: .devices) }
actionRow(title: "Page blocker…", shortcut: nil) { app.showMainWindow(select: .exclusions) }
actionRow(title: "Check for Updates…", shortcut: nil) { app.updater.checkForUpdates() }
@@ -90,7 +90,7 @@ struct MenuBarContent: View {
.frame(width: 290)
}
- /// Shown when Sparkle has found a newer signed build on grammagem.app.
+ /// Shown when Sparkle has found a newer signed build on grammargem.com.
private func updateBanner(version: String) -> some View {
HStack(spacing: 8) {
Image(systemName: "arrow.down.circle.fill").foregroundStyle(GG.emerald)
@@ -110,7 +110,7 @@ struct MenuBarContent: View {
HStack(spacing: 8) {
Image(systemName: "pencil.and.scribble").foregroundStyle(.tint)
VStack(alignment: .leading, spacing: 1) {
- Text("GrammaGem").font(.headline)
+ Text("GrammarGem").font(.headline)
Text(app.license.tier.displayName + (app.license.isLicensed ? " · licensed" : " · free"))
.font(.caption).foregroundStyle(.secondary)
}
diff --git a/Sources/GrammarGem/App/OnboardingView.swift b/Sources/GrammarGem/App/OnboardingView.swift
index 8527961..b926043 100644
--- a/Sources/GrammarGem/App/OnboardingView.swift
+++ b/Sources/GrammarGem/App/OnboardingView.swift
@@ -1,6 +1,6 @@
import SwiftUI
-/// First-run onboarding: explains why GrammaGem needs Accessibility, deep-links
+/// First-run onboarding: explains why GrammarGem needs Accessibility, deep-links
/// to System Settings, and advances automatically the moment permission is
/// granted (even if granted directly in System Settings).
struct OnboardingView: View {
@@ -18,7 +18,7 @@ struct OnboardingView: View {
ScrollView {
VStack(alignment: .leading, spacing: 18) {
VStack(alignment: .leading, spacing: 6) {
- Text("Welcome to GrammaGem")
+ Text("Welcome to GrammarGem")
.font(.largeTitle.bold())
Text("A private, on-device writing assistant that works in every app.")
.foregroundStyle(.secondary)
@@ -36,7 +36,7 @@ struct OnboardingView: View {
stepCard(
number: 1,
title: "Turn it on everywhere",
- body: "GrammaGem reads the text you select and writes the correction back. macOS requires Accessibility permission for this. Your text is processed on-device and never leaves your Mac.",
+ body: "GrammarGem reads the text you select and writes the correction back. macOS requires Accessibility permission for this. Your text is processed on-device and never leaves your Mac.",
granted: permissions.accessibilityTrusted
) {
if permissions.accessibilityTrusted {
@@ -47,7 +47,7 @@ struct OnboardingView: View {
Button("Open System Settings") { permissions.openAccessibilitySettings() }
Button("Request permission") { permissions.requestAccessibility() }
}
- Text("This updates automatically once you flip GrammaGem on in Accessibility.")
+ Text("This updates automatically once you flip GrammarGem on in Accessibility.")
.font(.caption).foregroundStyle(.secondary)
}
}
@@ -114,7 +114,7 @@ struct OnboardingView: View {
/// A safe, on-rails first-run demo: a local sample with planted mistakes that
/// the REAL grammar engine fixes in one click. It operates on a plain string
/// (never a live Accessibility element), so it's completely risk-free — the
-/// "guaranteed visible win" before GrammaGem asks for anything.
+/// "guaranteed visible win" before GrammarGem asks for anything.
private struct DemoStep: View {
@State private var text = "i has wrote alot of mistakes here, and it dont look profesional"
@State private var issues: [Suggestion] = []
@@ -151,7 +151,7 @@ private struct DemoStep: View {
}
private var headline: String {
- if cleaned { return "Clean ✨ — GrammaGem does this in every app you type." }
+ if cleaned { return "Clean ✨ — GrammarGem does this in every app you type." }
if issues.isEmpty { return "Checking…" }
return "We spotted \(issues.count) thing\(issues.count == 1 ? "" : "s") to tidy up — one click fixes them."
}
diff --git a/Sources/GrammarGem/App/SettingsView.swift b/Sources/GrammarGem/App/SettingsView.swift
index 32981b9..b293065 100644
--- a/Sources/GrammarGem/App/SettingsView.swift
+++ b/Sources/GrammarGem/App/SettingsView.swift
@@ -25,7 +25,7 @@ private struct GeneralSettings: View {
Form {
Section("Shortcuts") {
LabeledContent("Fix selection", value: "⌘;")
- LabeledContent("Ask GrammaGem", value: "⌘'")
+ LabeledContent("Ask GrammarGem", value: "⌘'")
Text("Custom shortcuts coming via the KeyboardShortcuts recorder (see README).")
.font(.caption).foregroundStyle(.secondary)
}
@@ -147,7 +147,7 @@ private struct DictionarySettings: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
- TextField("Add a word GrammaGem should never correct", text: $newWord)
+ TextField("Add a word GrammarGem should never correct", text: $newWord)
.textFieldStyle(.roundedBorder)
.onSubmit(add)
Button("Add", action: add).disabled(newWord.trimmingCharacters(in: .whitespaces).isEmpty)
diff --git a/Sources/GrammarGem/Core/AppConfig.swift b/Sources/GrammarGem/Core/AppConfig.swift
index a1016a2..7df8bab 100644
--- a/Sources/GrammarGem/Core/AppConfig.swift
+++ b/Sources/GrammarGem/Core/AppConfig.swift
@@ -1,15 +1,15 @@
import Foundation
/// Compile-time configuration. The Lemon Squeezy identifiers are hard-coded so
-/// the licensing layer can *hard-verify* that a key belongs to GrammaGem (and to
+/// the licensing layer can *hard-verify* that a key belongs to GrammarGem (and to
/// the claimed tier) rather than to some other Lemon Squeezy product.
/// TODO(real-integration): replace every `TODO_*` value with your real IDs.
enum AppConfig {
- static let appName = "GrammaGem"
- static let bundleIdentifier = "com.foundergem.grammagem" // TODO(real-integration)
- static let supportEmail = "support@grammagem.app" // TODO(real-integration)
- static let modelPortalURL = URL(string: "https://grammagem.app/license")! // TODO
- static let websiteURL = URL(string: "https://grammagem.app")!
+ static let appName = "GrammarGem"
+ static let bundleIdentifier = "com.foundergem.grammargem" // TODO(real-integration)
+ static let supportEmail = "support@grammargem.com" // TODO(real-integration)
+ static let modelPortalURL = URL(string: "https://www.grammargem.com/license")! // TODO
+ static let websiteURL = URL(string: "https://www.grammargem.com")!
static let appVersion = "0.1.0"
// Legal entity (shown in About / legal).
diff --git a/Sources/GrammarGem/Entitlements/FeatureGate.swift b/Sources/GrammarGem/Entitlements/FeatureGate.swift
index 981a02c..13e56ef 100644
--- a/Sources/GrammarGem/Entitlements/FeatureGate.swift
+++ b/Sources/GrammarGem/Entitlements/FeatureGate.swift
@@ -72,7 +72,7 @@ final class FeatureGate: ObservableObject {
/// Free-tier AI action counter. Persists `{date, count}` in UserDefaults and
/// resets at local midnight (compares the stored local date string to today's).
final class DailyCounter {
- private let key = "GrammaGem.aiActions.daily"
+ private let key = "GrammarGem.aiActions.daily"
private let defaults = UserDefaults.standard
private static let formatter: DateFormatter = {
diff --git a/Sources/GrammarGem/Grammar/PersonalDictionary.swift b/Sources/GrammarGem/Grammar/PersonalDictionary.swift
index 97764ac..29afb66 100644
--- a/Sources/GrammarGem/Grammar/PersonalDictionary.swift
+++ b/Sources/GrammarGem/Grammar/PersonalDictionary.swift
@@ -1,13 +1,13 @@
import Foundation
-/// The user's personal dictionary — names, brand terms, jargon GrammaGem should
+/// The user's personal dictionary — names, brand terms, jargon GrammarGem should
/// never "correct". Feeds Harper's ignore-list and the LLM context. Capped at 25
/// entries on free, unlimited on paid (enforced by `FeatureGate`, not here).
@MainActor
final class PersonalDictionary: ObservableObject {
@Published private(set) var entries: [String]
- private let key = "GrammaGem.personalDictionary"
+ private let key = "GrammarGem.personalDictionary"
private let defaults = UserDefaults.standard
init() {
diff --git a/Sources/GrammarGem/Licensing/DeviceFingerprint.swift b/Sources/GrammarGem/Licensing/DeviceFingerprint.swift
index be5ae2b..b17ee51 100644
--- a/Sources/GrammarGem/Licensing/DeviceFingerprint.swift
+++ b/Sources/GrammarGem/Licensing/DeviceFingerprint.swift
@@ -38,7 +38,7 @@ enum DeviceFingerprint {
}
private static func fallbackID() -> String {
- let key = "GrammaGem.fallbackDeviceID"
+ let key = "GrammarGem.fallbackDeviceID"
if let existing = UserDefaults.standard.string(forKey: key) { return existing }
let id = UUID().uuidString
UserDefaults.standard.set(id, forKey: key)
diff --git a/Sources/GrammarGem/Licensing/LemonSqueezyClient.swift b/Sources/GrammarGem/Licensing/LemonSqueezyClient.swift
index 5b9a6b4..15f2f69 100644
--- a/Sources/GrammarGem/Licensing/LemonSqueezyClient.swift
+++ b/Sources/GrammarGem/Licensing/LemonSqueezyClient.swift
@@ -2,7 +2,7 @@ import Foundation
/// Talks to the **Lemon Squeezy License API** directly from the app — no server
/// of ours, no database, no per-user cost (spec §6). Exposes activate / validate
-/// / deactivate and hard-verifies that a key belongs to GrammaGem + the claimed tier.
+/// / deactivate and hard-verifies that a key belongs to GrammarGem + the claimed tier.
final class LemonSqueezyClient {
private let session: URLSession
private let base = AppConfig.LemonSqueezy.apiBase
@@ -54,7 +54,7 @@ final class LemonSqueezyClient {
// MARK: - Hard verification (spec §6)
/// Confirm the key is for OUR store/product, then resolve which tier its
- /// variant corresponds to. Prevents a key from another LS product unlocking GrammaGem.
+ /// variant corresponds to. Prevents a key from another LS product unlocking GrammarGem.
private func verifyAndResolveTier(meta: LSMeta?, licenseKey: LSLicenseKey?) throws -> Tier {
guard let meta else { throw LicenseError.wrongProduct }
diff --git a/Sources/GrammarGem/Licensing/LicenseModels.swift b/Sources/GrammarGem/Licensing/LicenseModels.swift
index a9196a5..140a3e2 100644
--- a/Sources/GrammarGem/Licensing/LicenseModels.swift
+++ b/Sources/GrammarGem/Licensing/LicenseModels.swift
@@ -62,7 +62,7 @@ struct LSLicenseKey: Codable {
}
/// `meta` carries the store/product/variant identity we *hard-verify* against
-/// AppConfig so a key from another Lemon Squeezy product can't unlock GrammaGem.
+/// AppConfig so a key from another Lemon Squeezy product can't unlock GrammarGem.
struct LSMeta: Codable {
let store_id: Int?
let product_id: Int?
diff --git a/Sources/GrammarGem/Stats/UsageStats.swift b/Sources/GrammarGem/Stats/UsageStats.swift
index 47e90c4..40d5db0 100644
--- a/Sources/GrammarGem/Stats/UsageStats.swift
+++ b/Sources/GrammarGem/Stats/UsageStats.swift
@@ -14,12 +14,12 @@ final class UsageStats: ObservableObject {
}
/// What a Grammarly-style subscription runs per year (USD) — used only for the
- /// honest, local "subscription you didn't pay" figure. GrammaGem is one-time.
+ /// honest, local "subscription you didn't pay" figure. GrammarGem is one-time.
static let comparableAnnualSubscriptionUSD = 144.0
@Published private(set) var data: Snapshot
- private let key = "GrammaGem.usageStats"
+ private let key = "GrammarGem.usageStats"
private let d = UserDefaults.standard
private static let fmt: DateFormatter = {
@@ -64,7 +64,7 @@ final class UsageStats: ObservableObject {
var currentStreak: Int { Self.streak(perDay: data.perDay, asOf: Date()) }
/// The Grammarly subscription you DIDN'T pay: their annual price prorated over
- /// how long you've owned GrammaGem. Honest, local, illustrative — and it only
+ /// how long you've owned GrammarGem. Honest, local, illustrative — and it only
/// grows, which is the point of a one-time purchase (see the value plan §5).
var subscriptionAvoidedUSD: Int {
Self.subscriptionAvoidedUSD(
diff --git a/Sources/GrammarGem/System/AXSupport.swift b/Sources/GrammarGem/System/AXSupport.swift
index a80141c..48f155c 100644
--- a/Sources/GrammarGem/System/AXSupport.swift
+++ b/Sources/GrammarGem/System/AXSupport.swift
@@ -43,7 +43,7 @@ enum AX {
///
/// On recent macOS, `AXUIElementCreateSystemWide()` returns
/// `kAXErrorCannotComplete` (-25204) for *every* query made by a background /
- /// accessory process (which is exactly what a menu-bar app like GrammaGem is).
+ /// accessory process (which is exactly what a menu-bar app like GrammarGem is).
/// When that happens the system-wide focused-element read yields nil and the
/// app "sees" no text or text fields at all. Resolving the focused element
/// through the **frontmost application's** own AX element works reliably in
diff --git a/Sources/GrammarGem/System/Exclusions.swift b/Sources/GrammarGem/System/Exclusions.swift
index 4587ed7..84ac71f 100644
--- a/Sources/GrammarGem/System/Exclusions.swift
+++ b/Sources/GrammarGem/System/Exclusions.swift
@@ -1,7 +1,7 @@
import Foundation
import AppKit
-/// The "page blocker": apps and domains where GrammaGem should stay completely
+/// The "page blocker": apps and domains where GrammarGem should stay completely
/// out of the way — password managers, banking sites, anything sensitive.
/// When the frontmost context is excluded, live monitoring and the hotkeys are
/// suppressed.
@@ -10,8 +10,8 @@ final class Exclusions: ObservableObject {
@Published private(set) var blockedApps: [String] // bundle identifiers
@Published private(set) var blockedDomains: [String] // hostnames, e.g. "chase.com"
- private let appsKey = "GrammaGem.blockedApps"
- private let domainsKey = "GrammaGem.blockedDomains"
+ private let appsKey = "GrammarGem.blockedApps"
+ private let domainsKey = "GrammarGem.blockedDomains"
private let d = UserDefaults.standard
/// Common sensitive apps offered as one-tap suggestions in the UI.
@@ -31,7 +31,7 @@ final class Exclusions: ObservableObject {
/// a browser's URL can't be read).
var hasDomainRules: Bool { !blockedDomains.isEmpty }
- /// Whether GrammaGem should be suppressed for this frontmost context.
+ /// Whether GrammarGem should be suppressed for this frontmost context.
func isBlocked(bundleID: String?, domain: String?) -> Bool {
if let bundleID, blockedApps.contains(bundleID) { return true }
if let host = domain?.lowercased(), !host.isEmpty {
diff --git a/Sources/GrammarGem/System/LiveMonitor.swift b/Sources/GrammarGem/System/LiveMonitor.swift
index 2cc22ff..ad6b65c 100644
--- a/Sources/GrammarGem/System/LiveMonitor.swift
+++ b/Sources/GrammarGem/System/LiveMonitor.swift
@@ -19,7 +19,7 @@ struct DetectedField: Identifiable {
/// Performance & safety: the Accessibility-tree traversal (the part that can hang
/// on a busy/wedged app) runs OFF the main thread with a per-message timeout; only
/// the bounded spell/grammar pass and publish happen on the main actor. It never
-/// scans GrammaGem itself, respects the page blocker, and applies fixes against the
+/// scans GrammarGem itself, respects the page blocker, and applies fixes against the
/// element's *live* value so it can't overwrite text typed since the last scan.
@MainActor
final class LiveMonitor: ObservableObject {
@@ -179,7 +179,7 @@ final class LiveMonitor: ObservableObject {
if scanning && !force { return } // don't pile up scans
guard let app = NSWorkspace.shared.frontmostApplication else { clear(); return }
- // Never scan GrammaGem's own windows.
+ // Never scan GrammarGem's own windows.
if app.processIdentifier == ownPID { clear(); return }
activeAppName = app.localizedName ?? ""
diff --git a/Sources/GrammarGem/System/Permissions.swift b/Sources/GrammarGem/System/Permissions.swift
index 044f44d..3ac8ec2 100644
--- a/Sources/GrammarGem/System/Permissions.swift
+++ b/Sources/GrammarGem/System/Permissions.swift
@@ -2,7 +2,7 @@ import Foundation
import AppKit
import ApplicationServices
-/// Tracks and requests the macOS permissions GrammaGem needs to read & replace
+/// Tracks and requests the macOS permissions GrammarGem needs to read & replace
/// text system-wide: **Accessibility** (required) and, for the synthetic-keystroke
/// clipboard fallback, **Input Monitoring** may also be prompted by the system.
@MainActor
diff --git a/Sources/GrammarGem/System/SnippetStore.swift b/Sources/GrammarGem/System/SnippetStore.swift
index 4d17914..a959497 100644
--- a/Sources/GrammarGem/System/SnippetStore.swift
+++ b/Sources/GrammarGem/System/SnippetStore.swift
@@ -13,7 +13,7 @@ struct Snippet: Codable, Identifiable, Equatable {
final class SnippetStore: ObservableObject {
@Published private(set) var snippets: [Snippet]
- private let key = "GrammaGem.snippets"
+ private let key = "GrammarGem.snippets"
private let d = UserDefaults.standard
init() {
diff --git a/Sources/GrammarGem/System/TextCapture.swift b/Sources/GrammarGem/System/TextCapture.swift
index e9df9a2..80e4f48 100644
--- a/Sources/GrammarGem/System/TextCapture.swift
+++ b/Sources/GrammarGem/System/TextCapture.swift
@@ -4,7 +4,7 @@ import ApplicationServices
import Carbon.HIToolbox
/// Reads the user's selected text from the frontmost app and writes a corrected
-/// value back — the mechanism that makes GrammaGem work *everywhere*.
+/// value back — the mechanism that makes GrammarGem work *everywhere*.
///
/// Two paths, mirroring the spec (§4):
/// 1. **Accessibility API** (preferred): read/write `kAXSelectedTextAttribute`
@@ -27,7 +27,7 @@ final class TextCapture {
var errorDescription: String? {
switch self {
case .noSelection: return "Select some text first, then press the hotkey."
- case .notPermitted: return "GrammaGem needs Accessibility permission to read and replace text."
+ case .notPermitted: return "GrammarGem needs Accessibility permission to read and replace text."
}
}
}
diff --git a/Sources/GrammarGem/System/TextFieldScanner.swift b/Sources/GrammarGem/System/TextFieldScanner.swift
index e2c40fb..ff5ac1d 100644
--- a/Sources/GrammarGem/System/TextFieldScanner.swift
+++ b/Sources/GrammarGem/System/TextFieldScanner.swift
@@ -15,7 +15,7 @@ struct ScannedField {
/// Walks the Accessibility tree of the frontmost window and collects every
/// editable text element (text fields, text areas, combo/search boxes) with
-/// content — so GrammaGem can check *all* the text on screen, not just the one
+/// content — so GrammarGem can check *all* the text on screen, not just the one
/// the cursor is in. Bounded so huge web/Electron trees can't stall the app, and
/// every cross-process message has a timeout so a wedged app can't hang the scan.
///
diff --git a/Sources/GrammarGem/System/UpdaterManager.swift b/Sources/GrammarGem/System/UpdaterManager.swift
index c8aac65..e4c2981 100644
--- a/Sources/GrammarGem/System/UpdaterManager.swift
+++ b/Sources/GrammarGem/System/UpdaterManager.swift
@@ -1,7 +1,7 @@
import Foundation
import Sparkle
-/// Wraps Sparkle's updater. It background-checks the appcast on grammagem.app and
+/// Wraps Sparkle's updater. It background-checks the appcast on grammargem.com and
/// publishes `updateAvailableVersion` when a newer, EdDSA-verified build exists, so
/// the menu bar can show an "Update available" button. Triggering an update runs
/// Sparkle's verified download + install flow.
diff --git a/Tests/GrammarGemTests/GrammarGemTests.swift b/Tests/GrammarGemTests/GrammarGemTests.swift
index 82f420f..41bc914 100644
--- a/Tests/GrammarGemTests/GrammarGemTests.swift
+++ b/Tests/GrammarGemTests/GrammarGemTests.swift
@@ -1,7 +1,7 @@
import XCTest
-@testable import GrammaGem
+@testable import GrammarGem
-final class GrammaGemTests: XCTestCase {
+final class GrammarGemTests: XCTestCase {
// MARK: - Tiers / entitlements
@@ -117,10 +117,10 @@ final class GrammaGemTests: XCTestCase {
/// MLX integration end-to-end.
func testMLXRealGenerationIfModelPresent() async throws {
// Opt-in: `swift test` (CLI) can't place mlx-swift's metallib, so this is
- // gated. Run it via the app's `--mlx-selftest`, or set GRAMMAGEM_MLX_TEST=1
+ // gated. Run it via the app's `--mlx-selftest`, or set GRAMMARGEM_MLX_TEST=1
// with the metallib colocated. See scripts/build-metallib.sh.
- guard ProcessInfo.processInfo.environment["GRAMMAGEM_MLX_TEST"] == "1" else {
- throw XCTSkip("set GRAMMAGEM_MLX_TEST=1 (metallib colocated) to run the MLX integration test")
+ guard ProcessInfo.processInfo.environment["GRAMMARGEM_MLX_TEST"] == "1" else {
+ throw XCTSkip("set GRAMMARGEM_MLX_TEST=1 (metallib colocated) to run the MLX integration test")
}
guard let dir = ModelManager.completedModelDirectory(repo: AppConfig.Model.defaultRepo) else {
throw XCTSkip("on-device model not downloaded; skipping MLX integration test")
diff --git a/docs/grammarly-value-plan.md b/docs/grammarly-value-plan.md
index a37e54c..11bf563 100644
--- a/docs/grammarly-value-plan.md
+++ b/docs/grammarly-value-plan.md
@@ -1,6 +1,6 @@
-# Grammarly Value Teardown -> GrammaGem Plan
+# Grammarly Value Teardown -> GrammarGem Plan
-*Senior product + Swift engineering review. Every claim below is grounded in the current GrammaGem source. File references use absolute paths; the load-bearing bug is the mis-casing in `SystemGrammarEngine.swift:42-45`.*
+*Senior product + Swift engineering review. Every claim below is grounded in the current GrammarGem source. File references use absolute paths; the load-bearing bug is the mis-casing in `SystemGrammarEngine.swift:42-45`.*
---
@@ -8,34 +8,34 @@
Grammarly's perceived value is NOT its grammar engine. It is a stack of interaction and psychology layers on top of a competent engine. Ranked by leverage for a one-time-purchase, on-device Mac utility:
-1. **Trustworthy correction quality (the foundation).** Every felt-value mechanic above it collapses if a fix is wrong. Grammarly's "accept-accept-accept" rhythm only works because in-place application is reliable and casing/context is preserved. GrammaGem's engine currently *demonstrably violates this* (`problemm` -> `Problem` mid-sentence). Fix this first; nothing else matters until it's solid.
+1. **Trustworthy correction quality (the foundation).** Every felt-value mechanic above it collapses if a fix is wrong. Grammarly's "accept-accept-accept" rhythm only works because in-place application is reliable and casing/context is preserved. GrammarGem's engine currently *demonstrably violates this* (`problemm` -> `Problem` mid-sentence). Fix this first; nothing else matters until it's solid.
-2. **The fix-as-button atomic loop: read the fix and apply it in one motion.** Grammarly's clickable green replacement chip collapses diagnosis + remedy + action into a single low-friction act, with a cheap, always-present dismiss so saying no is as easy as saying yes. GrammaGem has an `Apply` button but no dismiss/ignore/add-to-dictionary, no keyboard accept, and the apply path is brittle (whole-field AX overwrite).
+2. **The fix-as-button atomic loop: read the fix and apply it in one motion.** Grammarly's clickable green replacement chip collapses diagnosis + remedy + action into a single low-friction act, with a cheap, always-present dismiss so saying no is as easy as saying yes. GrammarGem has an `Apply` button but no dismiss/ignore/add-to-dictionary, no keyboard accept, and the apply path is brittle (whole-field AX overwrite).
-3. **Categorization + counts + a score: turning a wall of red into a finite, beatable list.** Four named, color-coded buckets, per-category count chips, one running issue badge, and a 0-100 score convert dread into a checklist with a meter that only goes up. GrammaGem has a raw issue count (3 colors) and *zero* score concept.
+3. **Categorization + counts + a score: turning a wall of red into a finite, beatable list.** Four named, color-coded buckets, per-category count chips, one running issue badge, and a 0-100 score convert dread into a checklist with a meter that only goes up. GrammarGem has a raw issue count (3 colors) and *zero* score concept.
-4. **The rigged first-run "wow": guaranteed visible win in under 60 seconds.** A pre-seeded, error-salted demo doc means the product proves itself before asking for anything. GrammaGem's onboarding only handles Accessibility + model download — it never demonstrates the correction loop at all.
+4. **The rigged first-run "wow": guaranteed visible win in under 60 seconds.** A pre-seeded, error-salted demo doc means the product proves itself before asking for anything. GrammarGem's onboarding only handles Accessibility + model download — it never demonstrates the correction loop at all.
-5. **Ongoing perceived value without surveillance: progress you can see.** Grammarly uses cloud percentiles + weekly behavioral emails. GrammaGem can recreate the *feeling* — self-vs-self, fully on-device — with a local score trend, streaks, milestones, and a "subscription you didn't pay" money figure. The plumbing (`UsageStats.perDay`) already exists.
+5. **Ongoing perceived value without surveillance: progress you can see.** Grammarly uses cloud percentiles + weekly behavioral emails. GrammarGem can recreate the *feeling* — self-vs-self, fully on-device — with a local score trend, streaks, milestones, and a "subscription you didn't pay" money figure. The plumbing (`UsageStats.perDay`) already exists.
6. **The generative "writing partner" layer: hand me the better sentence, not just the flag.** Sentence rewrites, tone detection/adjustment, conciseness. This is real value but is currently 100% simulated (`MLXEngine.swift` is a string-hack stub) and gated by a daily counter. It is the right *paid* tier, but it must produce real output before it can be sold.
-The strategic asymmetry to exploit throughout: every Grammarly value moment carries an implicit "and you'll pay again next year." GrammaGem's identical mechanics should be framed "yours once, forever, on this Mac."
+The strategic asymmetry to exploit throughout: every Grammarly value moment carries an implicit "and you'll pay again next year." GrammarGem's identical mechanics should be framed "yours once, forever, on this Mac."
---
-## 2. Mechanic-by-mechanic: how Grammarly does it -> how GrammaGem should do it
+## 2. Mechanic-by-mechanic: how Grammarly does it -> how GrammarGem should do it
### Mechanic 1 — Trustworthy correction quality (see §3 for the full engineering plan)
- **Grammarly:** ranks by confidence, suppresses low-confidence edits, never garbles surrounding text, preserves casing, and limits bulk-apply to high-confidence objective mechanics only.
-- **GrammaGem today:** `SystemGrammarEngine` blindly trusts `NSSpellChecker.correction(forWordRange:)`, which is sentence-position-aware and capitalizes a word it thinks starts a sentence — producing `problemm` -> `Problem` mid-sentence (`SystemGrammarEngine.swift:42-45`). `Suggestion` has no confidence/severity/alternatives (`Models.swift:48-59`). Default `correct()` applies *every* suggestion including no-op grammar flags where `replacement == original` (`GrammarEngine.swift:22-29`).
+- **GrammarGem today:** `SystemGrammarEngine` blindly trusts `NSSpellChecker.correction(forWordRange:)`, which is sentence-position-aware and capitalizes a word it thinks starts a sentence — producing `problemm` -> `Problem` mid-sentence (`SystemGrammarEngine.swift:42-45`). `Suggestion` has no confidence/severity/alternatives (`Models.swift:48-59`). Default `correct()` applies *every* suggestion including no-op grammar flags where `replacement == original` (`GrammarEngine.swift:22-29`).
- **Do:** real Harper FFI; case/whitespace post-processing; add `confidence`/`severity`/`alternatives` to `Suggestion`; never auto-apply no-ops or low-confidence flags.
- **Files:** `SystemGrammarEngine.swift`, `HarperEngine.swift`, `GrammarEngine.swift`, `Models.swift`, `harper-ffi/`.
- **Impact: high · Effort: L**
### Mechanic 2 — The fix-as-button atomic loop (+ dismiss / ignore / add-to-dictionary)
- **Grammarly:** the suggested word *is* the button (green = go/safe/done); an always-present cheap dismiss (trash/Esc) and add-to-dictionary make accepting feel safe; Tab-to-navigate / Enter-to-accept / Esc-to-dismiss removes the mouse round-trip.
-- **GrammaGem today:** `FieldCard` shows a message + one `original -> replacement` + a small `Apply` button (`LiveCheckView.swift:104-119`). There is **no dismiss, no ignore, no add-to-dictionary, no keyboard accept.** Per-issue `apply()` bails silently if the field changed at all since the snapshot (`LiveMonitor.swift:82-89`), and even single-issue apply rewrites the *whole* AX value (`LiveMonitor.swift:101-103`), risking caret/undo/formatting loss.
+- **GrammarGem today:** `FieldCard` shows a message + one `original -> replacement` + a small `Apply` button (`LiveCheckView.swift:104-119`). There is **no dismiss, no ignore, no add-to-dictionary, no keyboard accept.** Per-issue `apply()` bails silently if the field changed at all since the snapshot (`LiveMonitor.swift:82-89`), and even single-issue apply rewrites the *whole* AX value (`LiveMonitor.swift:101-103`), risking caret/undo/formatting loss.
- **Do (realistic for a cross-app AX tool — we cannot draw a green chip inside arbitrary apps):**
- Make the replacement text itself the primary tappable control in the card (the chip *is* the fix), styled by category color.
- Add a per-suggestion overflow: **Dismiss** (model a `dismissedIDs: Set` on the monitor so it survives re-scans), **Ignore word** / **Add to dictionary** (route into `PersonalDictionary` -> `ignoreList`), gated by `freeDictionaryCap`.
@@ -47,9 +47,9 @@ The strategic asymmetry to exploit throughout: every Grammarly value moment carr
### Mechanic 3 — Categorization, counts, and an on-device document score
- **Grammarly:** four named buckets (Correctness/Clarity/Engagement/Delivery) each with a color and a count chip that doubles as a filter; one master running badge; a 0-100 percentile score that ticks up on every accept; cards ordered by severity (objective errors first) so the early wins build trust.
-- **GrammaGem today:** `GrammarKind` has 6 cases but `.punctuation` is dead; the panel reduces everything to 3 dot colors (`LiveCheckView.swift:136-142`); `issueCount` is a raw sum (`LiveMonitor.swift:32`); there is **no score, no per-category chips, no severity ordering, no filter**.
+- **GrammarGem today:** `GrammarKind` has 6 cases but `.punctuation` is dead; the panel reduces everything to 3 dot colors (`LiveCheckView.swift:136-142`); `issueCount` is a raw sum (`LiveMonitor.swift:32`); there is **no score, no per-category chips, no severity ordering, no filter**.
- **Do (on-device, no cloud, no percentile-vs-others):**
- - Introduce GrammaGem's own named buckets that fit the engine: e.g. **Correctness** (spelling/grammar/punctuation), **Clarity** (phrasing/conciseness — from the real LLM), **Polish** (repetition/capitalization/style). Use original color semantics (do NOT clone Grammarly's exact red/blue/green/purple mapping or wording).
+ - Introduce GrammarGem's own named buckets that fit the engine: e.g. **Correctness** (spelling/grammar/punctuation), **Clarity** (phrasing/conciseness — from the real LLM), **Polish** (repetition/capitalization/style). Use original color semantics (do NOT clone Grammarly's exact red/blue/green/purple mapping or wording).
- Add **count chips per bucket** in the panel header that filter the card list; keep the menu-bar badge as the master tally (`MenuBarLabel`).
- Compute a **local cleanliness score** at check-time: a deterministic function of issues-per-100-words by severity (e.g. `100 - weighted_issues`). It is honest because it's defined relative to *this document*, not a hidden user corpus. Show it on the card header and trend it on the dashboard.
- **Order cards by severity then document position** (objective spelling/grammar first) so the first accept is a high-confidence win.
@@ -59,18 +59,18 @@ The strategic asymmetry to exploit throughout: every Grammarly value moment carr
### Mechanic 4 — The first-run "wow" (see §4 for the full sequence)
- **Grammarly:** land in an error-salted demo doc -> "we found N issues" -> one-click fixes vanish underlines + decrement counter -> goal setup -> install-everywhere prompt at the dopamine peak.
-- **GrammaGem today:** `OnboardingView` only covers Accessibility + model download (`OnboardingView.swift`); it never shows the correction loop.
+- **GrammarGem today:** `OnboardingView` only covers Accessibility + model download (`OnboardingView.swift`); it never shows the correction loop.
- **Do:** add an in-app demo step with a pre-seeded sample, a "we found N things to fix" reveal, and a satisfying one-click-to-clean animation — *before* asking for nothing-but-praise (no install-everywhere pressure; we're already system-wide). Detail in §4.
- **Files:** `OnboardingView.swift`, plus a small `DemoDocument` view reusing the highlight + apply UI.
- **Impact: high · Effort: M**
### Mechanic 5 — Ongoing perceived value (privacy-native, self-vs-self)
- **Grammarly:** 0-100 score, weekly Insights email ("more productive than X% of users"), lifetime words-checked counter, streaks — all cloud + behavioral-email, the exact surveillance we market against.
-- **GrammaGem today:** `UsageStats` tracks `totalCorrections`, `totalAIActions`, `totalWords`, and `perDay` (`UsageStats.swift`); the dashboard shows four tiles + a 7-bar chart, but **no score, no streak, no week-over-week, no milestone, no money-saved figure**. `estimatedMinutesSaved` is a crude untuned constant (`8s/correction + 45s/AI-action`). Stats are invisible outside the main window.
+- **GrammarGem today:** `UsageStats` tracks `totalCorrections`, `totalAIActions`, `totalWords`, and `perDay` (`UsageStats.swift`); the dashboard shows four tiles + a 7-bar chart, but **no score, no streak, no week-over-week, no milestone, no money-saved figure**. `estimatedMinutesSaved` is a crude untuned constant (`8s/correction + 45s/AI-action`). Stats are invisible outside the main window.
- **Do (all local, no telemetry, stops nagging the moment they own it):**
- **Local writing-score trend** on the dashboard (from Mechanic 3), compared only to the user's own past.
- **Streak + milestones** from existing `perDay` data ("7-day streak", "1,000th correction", "100k words polished") — cheap, high word-of-mouth, and the right re-engagement hook for a no-renewal product.
- - **"Subscription you didn't pay" figure** — translate usage into money: the marketing site already hammers "~\$144/yr vs pay once," but the *app* never does. Show "You've polished N times — that's the price of GrammaGem recovered M times over," computed locally. This is the strongest one-time-purchase value teaser and it lives where the user actually is.
+ - **"Subscription you didn't pay" figure** — translate usage into money: the marketing site already hammers "~\$144/yr vs pay once," but the *app* never does. Show "You've polished N times — that's the price of GrammarGem recovered M times over," computed locally. This is the strongest one-time-purchase value teaser and it lives where the user actually is.
- **Week-over-week delta** on the bar chart and a lightweight in-app weekly recap card (NOT an email; no pipeline, no surveillance).
- **Surface one stat in the menu bar dropdown** (the most-seen surface) so value reminders don't require opening the dashboard.
- Tune `estimatedMinutesSaved` to scale by words changed / action type so the headline "was it worth it?" number is credible.
@@ -79,7 +79,7 @@ The strategic asymmetry to exploit throughout: every Grammarly value moment carr
### Mechanic 6 — The generative "writing partner" layer (real LLM, honest gating)
- **Grammarly:** sentence rewrites ("Replace with This Version" + Highlight-Changes diff), tone detection (emoji + label), tone adjustment, conciseness, GrammarlyGO — all one-click, reversible, before/after.
-- **GrammaGem today:** `MLXEngine` is a stub doing `replacingOccurrences` string hacks (`MLXEngine.swift`); `tidy()` even re-introduces casing risk; `detectTone` keys off substrings. `ModelManager` downloads real weights but nothing loads them. Gated by `freeDailyAIActions = 10` shared across rewrite/tone/Ask/translate.
+- **GrammarGem today:** `MLXEngine` is a stub doing `replacingOccurrences` string hacks (`MLXEngine.swift`); `tidy()` even re-introduces casing risk; `detectTone` keys off substrings. `ModelManager` downloads real weights but nothing loads them. Gated by `freeDailyAIActions = 10` shared across rewrite/tone/Ask/translate.
- **Do:**
- Wire the **real MLX model** from `ModelManager.modelsDirectory` (the download is currently a dead-end); uncomment `mlx-swift` in `Package.swift`.
- Adopt the **one-click + reversible + before/after diff** delivery contract for every rewrite — this is what neutralizes "the AI is taking over my voice."
@@ -133,7 +133,7 @@ Wire dismiss / add-to-dictionary (Mechanic 2) back into ranking so repeated fals
---
-## 4. The first-run "wow" sequence for GrammaGem (original copy)
+## 4. The first-run "wow" sequence for GrammarGem (original copy)
Goal: a guaranteed visible win in under 60 seconds, *inside our own window* (we don't need Grammarly's blank-canvas demo trick or its install-everywhere upsell — we're already system-wide). Onboarding today (`OnboardingView.swift`) skips this entirely.
@@ -145,7 +145,7 @@ Proposed flow:
3. **One satisfying click.** A single "Clean it up" button animates the highlights resolving one by one; the count ticks to 0; the paragraph visibly improves. This is the operant loop — trivial action, immediate visible reward — experienced once, on rails, with zero risk. (Reuse the apply path; it's a local string, not a live AX element, so it's completely safe.)
-4. **The reveal:** "That's GrammaGem. It does this in Mail, Slack, Notes, your browser — everywhere you type." Then the *first* ask: **Accessibility** (the existing step 2 / `stepCard`), now framed as "Turn it on everywhere."
+4. **The reveal:** "That's GrammarGem. It does this in Mail, Slack, Notes, your browser — everywhere you type." Then the *first* ask: **Accessibility** (the existing step 2 / `stepCard`), now framed as "Turn it on everywhere."
5. **Optional model step:** keep the existing "Download the on-device model (optional) — grammar works instantly with no download" card. Frame the AI features as a bonus, not a blocker.
diff --git a/harper-ffi/README.md b/harper-ffi/README.md
index 2d4153f..6fd2d49 100644
--- a/harper-ffi/README.md
+++ b/harper-ffi/README.md
@@ -5,7 +5,7 @@ A thin Rust static library that exposes the **Harper** grammar core
integration (spec §1, §2, §8).
The Swift side talks to this through the `GrammarEngine` protocol; today
-[`HarperEngine.swift`](../Sources/GrammaGem/Grammar/HarperEngine.swift) ships a
+[`HarperEngine.swift`](../Sources/GrammarGem/Grammar/HarperEngine.swift) ships a
small pure-Swift rule stub so the system loop + tests run with zero native deps.
## Planned shape
diff --git a/scripts/build-metallib.sh b/scripts/build-metallib.sh
index ce22774..755a7b4 100755
--- a/scripts/build-metallib.sh
+++ b/scripts/build-metallib.sh
@@ -15,7 +15,7 @@ cd "$ROOT"
DD=".xcode-build"
echo "==> xcodebuild (compiles mlx-swift metal kernels — a few minutes)"
-xcodebuild build -scheme GrammaGem -configuration Release \
+xcodebuild build -scheme GrammarGem -configuration Release \
-destination 'platform=macOS,arch=arm64' \
-derivedDataPath "$DD" -skipMacroValidation >/dev/null
diff --git a/scripts/build.sh b/scripts/build.sh
index e530154..1d66509 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Build a runnable GrammaGem.app bundle (arm64 / Apple Silicon — the MLX
+# Build a runnable GrammarGem.app bundle (arm64 / Apple Silicon — the MLX
# on-device LLM runtime is Metal-only, so Intel is not supported).
# Usage: ./scripts/build.sh (run from the mac/ directory)
set -euo pipefail
@@ -8,7 +8,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
CONFIG="release"
-APP="GrammaGem.app"
+APP="GrammarGem.app"
DIST="dist"
echo "==> Building Harper static lib (universal)"
@@ -24,7 +24,7 @@ echo "==> Assembling ${APP}"
rm -rf "${DIST}/${APP}"
mkdir -p "${DIST}/${APP}/Contents/MacOS" "${DIST}/${APP}/Contents/Resources"
-cp "${BIN}/GrammaGem" "${DIST}/${APP}/Contents/MacOS/GrammaGem"
+cp "${BIN}/GrammarGem" "${DIST}/${APP}/Contents/MacOS/GrammarGem"
cp "AppSupport/Info.plist" "${DIST}/${APP}/Contents/Info.plist"
cp "AppSupport/AppIcon.icns" "${DIST}/${APP}/Contents/Resources/AppIcon.icns"
@@ -52,8 +52,8 @@ SPARKLE_FW="$(find .build -path '*Sparkle.xcframework/macos-arm64*/Sparkle.frame
mkdir -p "${DIST}/${APP}/Contents/Frameworks"
cp -R "${SPARKLE_FW}" "${DIST}/${APP}/Contents/Frameworks/"
install_name_tool -add_rpath "@executable_path/../Frameworks" \
- "${DIST}/${APP}/Contents/MacOS/GrammaGem" 2>/dev/null || true
+ "${DIST}/${APP}/Contents/MacOS/GrammarGem" 2>/dev/null || true
echo "==> Built ${DIST}/${APP}"
-lipo -info "${DIST}/${APP}/Contents/MacOS/GrammaGem"
+lipo -info "${DIST}/${APP}/Contents/MacOS/GrammarGem"
echo " Run with: open \"${DIST}/${APP}\" (grant Accessibility on first launch)"
diff --git a/scripts/make-icon.swift b/scripts/make-icon.swift
index 81c0e3c..3226134 100644
--- a/scripts/make-icon.swift
+++ b/scripts/make-icon.swift
@@ -1,5 +1,5 @@
#!/usr/bin/env swift
-// Generates AppIcon.iconset (PNGs) for GrammaGem — a gold pen-nib on the ink
+// Generates AppIcon.iconset (PNGs) for GrammarGem — a gold pen-nib on the ink
// brand square. Run from mac/: swift scripts/make-icon.swift
// Then: iconutil -c icns AppIcon.iconset -o AppSupport/AppIcon.icns
import AppKit
diff --git a/scripts/release.sh b/scripts/release.sh
index 5699081..6711020 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
-# Cut a GrammaGem release and publish the Sparkle appcast (hosted on grammagem.app).
+# Cut a GrammarGem release and publish the Sparkle appcast (hosted on grammargem.com).
#
# ./scripts/release.sh e.g. ./scripts/release.sh 0.2.0
#
# Steps: stamp version -> build -> sign inside-out (incl. Sparkle) -> notarize +
# staple -> zip -> generate the EdDSA-signed appcast.xml. Then upload the .zip and
-# appcast.xml to grammagem.app so every installed app shows "Update available".
+# appcast.xml to grammargem.com so every installed app shows "Update available".
#
# Notarization needs ONE of:
# NOTARY_PROFILE (xcrun notarytool store-credentials)
@@ -17,10 +17,10 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
VERSION="${1:?usage: release.sh e.g. release.sh 0.2.0}"
-APP="dist/GrammaGem.app"
+APP="dist/GrammarGem.app"
RELDIR="dist/releases"
-ZIP="${RELDIR}/GrammaGem-${VERSION}.zip"
-DL_PREFIX="${DL_PREFIX:-https://grammagem.app/releases/}"
+ZIP="${RELDIR}/GrammarGem-${VERSION}.zip"
+DL_PREFIX="${DL_PREFIX:-https://www.grammargem.com/releases/}"
PB=/usr/libexec/PlistBuddy
# 1. Stamp the marketing version + bump the monotonic build number.
@@ -61,11 +61,11 @@ GA="$(find .build/artifacts -name generate_appcast -type f | head -1)"
"$GA" --download-url-prefix "$DL_PREFIX" "$RELDIR"
echo
-echo "✓ Release ${VERSION} built. Upload these to grammagem.app:"
+echo "✓ Release ${VERSION} built. Upload these to grammargem.com:"
echo " ${ZIP}"
-echo " -> ${DL_PREFIX}GrammaGem-${VERSION}.zip"
+echo " -> ${DL_PREFIX}GrammarGem-${VERSION}.zip"
echo " ${RELDIR}/appcast.xml"
-echo " -> https://grammagem.app/appcast.xml (matches Info.plist SUFeedURL)"
+echo " -> https://www.grammargem.com/appcast.xml (matches Info.plist SUFeedURL)"
echo
echo " Once uploaded, installed apps show an 'Update available' button within a day"
echo " (or immediately via menu bar -> Check for Updates...)."
diff --git a/scripts/sign-app.sh b/scripts/sign-app.sh
index 869f88a..743b882 100755
--- a/scripts/sign-app.sh
+++ b/scripts/sign-app.sh
@@ -1,17 +1,17 @@
#!/usr/bin/env bash
-# Code-sign GrammaGem.app with Developer ID + Hardened Runtime, signing the
+# Code-sign GrammarGem.app with Developer ID + Hardened Runtime, signing the
# embedded Sparkle.framework's nested helpers INSIDE-OUT first (required for a
# valid, notarizable signature).
#
-# Usage: ./scripts/sign-app.sh [path/to/GrammaGem.app]
+# Usage: ./scripts/sign-app.sh [path/to/GrammarGem.app]
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
DEV_ID="${DEV_ID:-Developer ID Application: Marc Schultheiss (D5KT5B9Z9M)}"
-ENTITLEMENTS="AppSupport/GrammaGem.entitlements"
-APP="${1:-dist/GrammaGem.app}"
+ENTITLEMENTS="AppSupport/GrammarGem.entitlements"
+APP="${1:-dist/GrammarGem.app}"
sign() { codesign --force --timestamp --options runtime --sign "$DEV_ID" "$@"; }
diff --git a/scripts/sign-notarize.sh b/scripts/sign-notarize.sh
index a5d58cc..38787db 100755
--- a/scripts/sign-notarize.sh
+++ b/scripts/sign-notarize.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Build (universal), codesign (Hardened Runtime + Developer ID), notarize, and
-# staple GrammaGem.app. Requires the Apple Developer Program ($99/yr) — a FIXED
+# staple GrammarGem.app. Requires the Apple Developer Program ($99/yr) — a FIXED
# cost, not per-user.
#
# Credentials are read from the environment or a stored keychain profile — never
@@ -15,9 +15,9 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
DEV_ID="${DEV_ID:-Developer ID Application: Marc Schultheiss (D5KT5B9Z9M)}"
-APP="dist/GrammaGem.app"
-ZIP="dist/GrammaGem.zip"
-ENTITLEMENTS="AppSupport/GrammaGem.entitlements"
+APP="dist/GrammarGem.app"
+ZIP="dist/GrammarGem.zip"
+ENTITLEMENTS="AppSupport/GrammarGem.entitlements"
echo "==> Building universal bundle"
./scripts/build.sh >/dev/null