Is Linguix: Grammar Checker and AI Writing App safe?
Linguix sends text fragments surrounding grammar corrections to Amplitude analytics on any website.
When a user accepts a grammar or writing suggestion, Linguix captures up to 100 characters of surrounding text (the 'context' field) along with the rule triggered and the replacement made. This data is transmitted to Amplitude via its HTTP API using a hardcoded project key. The user's Linguix account ID or a generated UUID is attached to each event as the Amplitude userId.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Typed writing context is sent to Amplitude after accepted suggestions
When you accept a Linguix suggestion, the extension logs a ReplacementClicked event to Amplitude.
Fields come from the rule, nearby context, replacement text, and source; the user ID comes from the Linguix account ID or a stored UUID.
You accept a suggested writing replacement on a webpage.
This can happen inside forms or editable page content where the extension is active.
The extension logs an analytics event that includes the nearby writing context.
The event is named ReplacementClicked and is routed through the Amplitude SDK.
| Field | Value | Why it matters | |
|---|---|---|---|
Rule that fired | MORFOLOGIK_RULE_EN_US (illustrative) | Shows which writing rule matched the text you were editing. | |
Nearby typed text | Please reviev the onboarding notes before Friday. (illustrative) | Can reveal part of what you were writing on the page when you accepted the suggestion. | |
Replacement text | review (illustrative) | Shows the corrected word or phrase you chose from the suggestion popover. | |
Analytics user identifier | 8f2c6b7e-41a6-4e28-9e5f-1d43d6a9c2b4 (illustrative) | Lets the analytics provider link the event to the same extension account or stored browser profile over time. |
The accepted replacement is converted into an Amplitude analytics event
replace(t, n) {
this.prevent(n), !this.authUser.actionRequired() || this.authUser.actionRequired() !== z.TRIAL_ENDED || this.authUser.isPremium() ? (this.authUser.actionRequired() && this.authUser.actionRequired() === z.TRIAL_ENDS_SOON && this.alert.isPremium && chrome.storage.local.set({
[z0]: this.premiumReplacesDoneToday + 1
}), this.checkable.replace(this.alert.id, t), this.auth.isGuest() && chrome.storage.local.get("guestReplacementCount").then(a => {
chrome.storage.local.set({
guestReplacementCount: (a.guestReplacementCount || 0) + 1
})
}), chrome.storage.local.get("totalReplacementCount").then(a => {
chrome.storage.local.set({
totalReplacementCount: (a.totalReplacementCount || 0) + 1
})
}), this.onMessage.emit({
type: s1,
message: {
sessionId: this.checkable.id,
rule: this.alert.rule,
context: this.alert.context,
replacement: t,
from: "alert",
sentence: this.alert.wholeSentence
}
})) : this.goPremiumTrialExpired()
}this.onLogReplacement$ = ge(() => this.withOnlineCheckerAction(Gu).pipe($e(({
action: M,
checker: Y
}) => {
Y.ensureIsConnected(), Y.logReplacement(M.rule, M.context, M.replacement, M.sentence), Cr.replacementClickedEvent(M.rule, M.context, M.replacement, M.from)
})), {
dispatch: !1
})class Cr {
static init(n) {
S0("050aec1607a54971d0794e2397b6979b", n)
}
static rephraseRequestedEvent(n, r, a, u) {
Cr.logEvent("RephraseRequested", {
type: n,
limitReached: r,
domain: a,
isSimplifier: u
})
}
static replacementClickedEvent(n, r, a, u) {
Cr.logEvent("ReplacementClicked", {
rule: n,
context: r,
replacement: a,
from: u
})
}
static logEvent(n, r) {
Yi(n, r)
}
}constructReport(n, r, a = 100) {
if (!this.currentData) return;
const p = this.currentData.text.split("\n")[n.lineNumber];
if (!p) return;
const C = p.substring(Math.max(0, n.offset - a), n.offset + n.length + a);
return {
id: ts.generateUuid(),
type: r,
context: C
}
}- api2.amplitude.com
Amplitude HTTP API endpoint that receives the ReplacementClicked analytics event.