diff --git a/AI_STATUS.md b/AI_STATUS.md
index 2cfd1a2..1b55bdf 100644
--- a/AI_STATUS.md
+++ b/AI_STATUS.md
@@ -1,7 +1,7 @@
# Project Status
> Auto-updated. Read this before starting any work.
-> Last updated: 2026-04-30 09:15
+> Last updated: 2026-06-12 (v1.3.5 prepared locally)
## Goal
@@ -10,8 +10,21 @@ Success = 1000+ active users and positive reviews before reintroducing any paid
## In Progress
-- LinkClean v1.3.3 layout-safety hotfix submitted to Chrome Web Store and Microsoft Edge Add-ons on 2026-04-30; Firefox AMO submission is blocked by API throttle until roughly 2026-04-30 21:50 UTC / 2026-05-01 02:50 Asia/Qyzylorda.
-- LinkClean v1.3.2 submitted to Chrome Web Store, Firefox AMO, and Microsoft Edge Add-ons on 2026-04-28; waiting for store review/propagation.
+- **v1.3.5 promoted-post hotfix prepared locally (2026-06-12)** after a second FormSubmit uninstall feedback ("It didn't hide promoted posts correctly", 2026-06-12 00:37 UTC). Root cause: LinkedIn's 2026 feed DOM rewrite (`data-testid="mainFeed"`, `article[data-id="main-feed-card"]`, actor line in `p[componentkey]`) was invisible to the v1.3.4 detector — the loose label collector explicitly skipped all `
` content as body copy, and only ~15 of 28 locales had promoted keywords. Evidence base: open-source LinkedinSponsorBlock (Hogwai, updated 2026-05-31) modern profile selectors. Fix: (1) new-DOM containers in discovery/identity; (2) structural promoted markers `data-sponsored-tracking-url`, `data-view-tracking-scope*="sponsored"`, `data-promoted-tracking-control-name`; (3) strict-equality matching of `
` metadata texts (full text, direct text nodes, direct child spans) against keyword sets — catches new-DOM labels without body-copy false positives; (4) promoted/suggested keywords expanded to ~40 languages incl. CJK/Thai (substring match only for no-space scripts, length ≥ 3); (5) hiding moved from inline `style.display` to injected CSS rule on `data-linkclean-hidden` with `!important` so LinkedIn re-renders can't un-hide posts; (6) mutation handling switched from resetting debounce to 250 ms throttle (no starvation during continuous feed mutations); (7) poll filter no longer scans `innerHTML` for "poll" (organic posts mentioning polls survive). Consilium council review (run 20260612T092359, R4 audit skipped: cursor quota) flagged and we fixed: tracking-scope matching anchored to quoted `"sponsored"` token + post root only (organic `sponsored-content-follow` interactions survive); CJK matching via scope-not-length (strict equality + token match inside LinkedIn-generated `sub-description` lines only, no substring rule); NFKC + zero-width stripping in `normalizeText`; leading+trailing throttle; static CSS pre-hide rules on LinkedIn's own sponsored attrs (no ad flash). Bonus fix found by new tests: container-div label collection false positive (short organic post mentioning "sponsored" in a `div[data-urn]` container was hidden even in v1.3.4) — label elements containing body copy are now excluded. Verified: `npm test` 33/33, `npm run typecheck`, `npm run build`, `npm run zip` (manifest `version=1.3.5`), Prettier clean. Committed locally as `87bf7be`.
+- **v1.3.5 submitted to all stores (2026-06-12, owner-approved)** via MiranaApps `publish.sh --all`: Chrome Web Store upload `SUCCEEDED` + publish HTTP 200, official v2 `fetchStatus` confirms `published=PUBLISHED submitted=PENDING_REVIEW version=1.3.5`; Firefox AMO validation 0 errors / 9 warnings (https://addons.mozilla.org/en-US/developers/addon/2994972/file/4847482/validation), published successfully; Edge Add-ons package uploaded and submitted for review (product `91c40340-8f47-426f-adb0-2ba51223fc92`). Git push to `origin/master` was blocked by the local permission classifier (direct default-branch push) — commit `87bf7be` is local-only until Tugelbay pushes or allows it.
+- **Repo git object store was corrupted and repaired (2026-06-12)**: `git fsck` showed invalid sha1 pointers/reflog entries and `git log`/`git diff` failed. `git fetch origin --refetch` restored all objects from GitHub; local master fast-forwarded to origin/master (`97f0413 chore: make CodeRabbit advisory`); fsck now clean.
+- LinkClean v1.3.4 promoted-post reliability hotfix is published on Chrome Web Store after uninstall feedback that promoted posts were not hidden correctly. Fresh CWS official `fetchStatus` reports `published=PUBLISHED`, `submitted=null`, `latest_crx_version=1.3.4`.
+- Firefox AMO v1.3.4 is public/current in authenticated status (`id=6258981`, `reviewed=2026-05-12T21:46:08Z`), with `0` average daily users / `0` weekly downloads.
+- Microsoft Edge Add-ons v1.3.4 was submitted after fixing MiranaApps `scripts/publish.sh` for Edge API v1.1 ApiKey auth. Upload operation `3396b617-e3fa-4fb3-bda7-fe7eaa630ce8` completed and the submit call was accepted; wait for Edge review/public listing propagation.
+- Firefox AMO v1.3.3 was manually retried after the throttle window on 2026-05-01. The scheduled retry log showed AMO API `read ECONNRESET`, not a remaining throttle. The manual safe-wrapper retry completed upload + listed-version submit with validation 0 errors / 9 warnings (`https://addons.mozilla.org/en-US/developers/addon/2994972/file/4787089/validation`), but the public AMO API still reports current public version `1.3.2`, so v1.3.3 is submitted and awaiting AMO review/publication.
+- Firefox AMO v1.3.3 was retried again on 2026-05-05 08:38 Asia/Qyzylorda via `powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1`. AMO returned `409 Conflict` on version creation with `_data.version: "Version 1.3.3 already exists."` This is a duplicate-version blocker, not throttle/auth failure; current action is to wait for AMO review/publication of the existing 1.3.3 submission.
+
+- Firefox AMO v1.3.3 retry rerun on 2026-05-17 11:10 Asia/Qyzylorda. C:\Projects\LinkClean is empty, so active repo path \\wsl.localhost\Ubuntu-24.04\home\tugelbay\Projects\00-Workspace\LinkClean was used. Preflight confirmed git status; 1.3.3 ZIP artifacts were missing, npm run zip:firefox produced only 1.3.4 ZIPs. Running powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1 from repo context failed before AMO API with sanitized blocker Missing FIREFOX_JWT_ISSUER, FIREFOX_JWT_SECRET, or FIREFOX_EXTENSION_ID (no submit attempted).
+- Firefox AMO v1.3.3 retry rerun on 2026-05-18 10:20 Asia/Qyzylorda in active repo \\wsl.localhost\Ubuntu-24.04\home\tugelbay\Projects\00-Workspace\LinkClean (C:\Projects\LinkClean remains empty). Preflight confirmed git status; 1.3.3 ZIP artifacts were missing and `npm run zip:firefox` again produced only 1.3.4 ZIPs. Running `powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1` failed before AMO API with sanitized blocker `Missing FIREFOX_JWT_ISSUER, FIREFOX_JWT_SECRET, or FIREFOX_EXTENSION_ID`, so AMO did not accept a new retry submission.
+- Firefox AMO v1.3.3 retry rerun on 2026-05-20 03:06 Asia/Qyzylorda in active repo `\\wsl.localhost\Ubuntu-24.04\home\tugelbay\Projects\00-Workspace\LinkClean` (`C:\Projects\LinkClean` is still not a git repo). Preflight in active repo confirmed git status; `1.3.3` ZIP artifacts were missing and `npm run zip:firefox` produced only `1.3.4` ZIPs. Running `powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1` failed before AMO API with sanitized blocker `Missing FIREFOX_JWT_ISSUER, FIREFOX_JWT_SECRET, or FIREFOX_EXTENSION_ID`, so AMO did not accept a new retry submission.
+- Firefox AMO v1.3.3 retry rerun on 2026-05-22 03:22 Asia/Qyzylorda in active repo `\\wsl.localhost\Ubuntu-24.04\home\tugelbay\Projects\00-Workspace\LinkClean` (`C:\Projects\LinkClean` is still empty/not a git repo). Preflight in active repo confirmed git status; `1.3.3` ZIP artifacts were missing and `npm run zip:firefox` produced only `1.3.4` ZIPs. Running `powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1` failed before AMO API with sanitized blocker `Missing FIREFOX_JWT_ISSUER, FIREFOX_JWT_SECRET, or FIREFOX_EXTENSION_ID`, so AMO did not accept a new retry submission.
+- Firefox AMO v1.3.3 retry rerun on 2026-05-23 03:32 Asia/Qyzylorda in active repo `\\wsl.localhost\Ubuntu-24.04\home\tugelbay\Projects\00-Workspace\LinkClean` (`C:\Projects\LinkClean` is still empty/not a git repo). Preflight in active repo confirmed git status; `1.3.3` ZIP artifacts were missing and `npm run zip:firefox` produced only `1.3.4` ZIPs. Running `powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1` via `powershell.exe` failed before AMO API with sanitized blocker `Missing FIREFOX_JWT_ISSUER, FIREFOX_JWT_SECRET, or FIREFOX_EXTENSION_ID`, so AMO did not accept a new retry submission.
+- Firefox AMO v1.3.3 retry rerun on 2026-05-25 06:53 Asia/Qyzylorda in active repo \\wsl.localhost\Ubuntu-24.04\home\tugelbay\Projects\00-Workspace\LinkClean (C:\Projects\LinkClean is not a git repo). Preflight in active repo confirmed git status and both required artifacts exist: .output/linkclean-1.3.3-firefox.zip and .output/linkclean-1.3.3-sources.zip. Running powershell -ExecutionPolicy Bypass -File .\scripts\submit-firefox.ps1 failed before AMO API with sanitized blocker Missing FIREFOX_JWT_ISSUER, FIREFOX_JWT_SECRET, or FIREFOX_EXTENSION_ID, so AMO did not accept the submission.
- Investigated version bump: repo version **1.3.0** was introduced by commit `2aa51d3` on 2026-03-31 by Tugelbay Konabayev; there is no `v1.3.0` git tag, so the standard GitHub tag-based release workflow did not publish it.
## Done
@@ -36,6 +49,10 @@ Success = 1000+ active users and positive reviews before reintroducing any paid
- **v1.3.2 submitted to all stores (2026-04-28)**: Chrome Web Store upload/publish API returned OK for extension `ipdckibncofmlnoaajkdhnbclbpgppdg`; Firefox AMO accepted the listed submission with validation result 0 errors / 9 warnings (`https://addons.mozilla.org/en-US/developers/addon/2994972/file/4783244/validation`); Microsoft Edge Add-ons API v1.1 accepted package upload operation `958c355a-77de-4ed8-ae81-9acda7a867d0` and publish operation `6d559182-96ca-4fb1-8b01-ac96413ea391`.
- **v1.3.3 layout-safety hotfix prepared locally (2026-04-30)**: content script now only mutates LinkedIn `/feed`, restores only elements LinkClean hid, preserves previous inline `display`, re-checks after SPA path changes, avoids generic profile/about/sidebar articles as feed posts, and no longer treats normal jobs/premium links as sidebar ads without promo labels. Regression tests were added for profile articles, sidebar content, non-feed paths, organic `suggested` text, and normal jobs links.
- **v1.3.3 submitted to Chrome and Edge (2026-04-30)**: Chrome Web Store `wxt submit` completed upload + submit-for-review successfully; Microsoft Edge Add-ons API v1.1 accepted package upload operation `b33ef930-8365-47af-8b64-d4fd976960e6` and publish operation `69bb5560-6623-4c0a-99d2-a2b72a9010ad`. Firefox AMO returned 429 throttle during version creation (`Expected available in 63167 seconds`), so Firefox retry is pending.
+- **v1.3.3 Chrome re-submit verified (2026-04-30 15:17 Asia/Qyzylorda)**: MiranaApps `publish.sh --chrome` uploaded `.output/linkclean-1.3.3-chrome.zip` and Chrome Web Store publish API returned `Status: OK`. A fresh CWS official `fetchStatus` then confirmed `LinkClean: published=PUBLISHED submitted=PENDING_REVIEW version=1.3.3`.
+- **v1.3.3 Edge retry attempted (2026-04-30 15:17 Asia/Qyzylorda)**: `scripts/submit-edge-v11.ps1` uploaded package operation `dda87669-2ae6-47d8-ad38-8d560341d300` successfully, then publish operation `53b63987-6b1c-4488-8fbd-1cc4e12d59bd` failed with `Can't publish extension as your extension submission is in progress. Please try again later.`
+- **v1.3.3 Firefox retry attempted and automated (2026-04-30 15:18 Asia/Qyzylorda)**: `scripts/submit-firefox.ps1` reached AMO version creation but returned 429 throttle. One-off Windows Scheduled Task `MiranaApps-LinkClean-Firefox-1.3.3-Retry` is ready for 2026-05-01 03:55 Asia/Qyzylorda.
+- **v1.3.4 promoted-post reliability hotfix prepared and submitted to all stores (2026-05-13)**: audited the LinkedIn feed detector with Claude Opus/max second opinion after FormSubmit uninstall feedback. The fix expands feed post discovery to newer LinkedIn `urn:li:*`, `data-finite-scroll-hotkey-item`, and `data-view-name="feed-full-update"` containers; detects promoted labels inside actor metadata lines, compact `div` labels, `aria-describedby`, `alt`/`title`/`aria-label`, and `data-promoted-tracking-control-name`; re-processes existing posts when lazy-loaded ad label text/attributes change; keeps sidebar label matching stricter to avoid body-copy false positives. Verified with `npm test` (`23/23`), `npm run typecheck`, targeted Prettier check, `npm run build`, `npm run zip`, zip manifest `version=1.3.4`, and `npm audit --audit-level=high` (`0` vulnerabilities). MiranaApps `publish.sh --chrome` uploaded `.output/linkclean-1.3.4-chrome.zip`; CWS publish API returned `Status: OK`; official CWS status now shows `published=PUBLISHED`, `submitted=null`, `latest_crx_version=1.3.4`. Firefox AMO status now shows current/public `1.3.4`, reviewed `2026-05-12T21:46:08Z`. Microsoft Edge Add-ons accepted the v1.3.4 submission after the publisher switched to Edge API v1.1 ApiKey auth.
- Firefox AMO uploaded via web-ext sign (waiting review)
- Dev.to article published: https://dev.to/konabayev/i-built-a-chrome-extension-to-clean-my-linkedin-feed-heres-how-541c
- 2 GitHub Awesome list PRs: best-chrome-extensions#22, awesome-productivity#228
@@ -47,8 +64,8 @@ Success = 1000+ active users and positive reviews before reintroducing any paid
## Next Up
-1. Retry Firefox AMO submission for LinkClean **v1.3.3** after 2026-05-01 02:50 Asia/Qyzylorda, then monitor all stores for review/propagation.
-1. Monitor Chrome Web Store, Firefox AMO, and Microsoft Edge Add-ons review/propagation for LinkClean **v1.3.2**; confirm public store pages show the new version after approval.
+1. Monitor Edge Add-ons review/public listing propagation for LinkClean **v1.3.4**.
+1. Watch uninstall feedback and CWS private WAU after the v1.3.4 fix; latest private CWS funnel is `552` impressions -> `48` page views -> `63` installs -> `7` WAU.
1. Handle Firefox `data_collection_permissions` before the next AMO policy deadline; current build warning says future submissions may require it.
1. Confirm whether the live CWS `1.3.0` publish used local `publish.sh` / manual CWS upload path and document the exact release path.
1. Sync scripts to VPS + set up daily cron for auto-scan.
diff --git a/CLAUDE.md b/CLAUDE.md
index 11df2aa..1c28fdd 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,7 @@
# Local Claude Instructions
+
@/home/tugelbay/Projects/CLAUDE.md
Root Claude rules apply first. These local notes only add project-specific context.
@@ -10,7 +11,7 @@ Root Claude rules apply first. These local notes only add project-specific conte
# LinkClean — LinkedIn Feed Cleaner
> Part of Mirana Apps portfolio. Parent: /home/tugelbay/Projects/MiranaApps/CLAUDE.md
-> CWS knowledge: /mnt/c/Brain/Knowledge/cws-unified.md
+> CWS knowledge: /home/tugelbay/Projects/Brain/Knowledge/cws-unified.md
## This Extension
diff --git a/package-lock.json b/package-lock.json
index ee5f361..e933df1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "linkclean",
- "version": "1.3.3",
+ "version": "1.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "linkclean",
- "version": "1.3.3",
+ "version": "1.3.5",
"hasInstallScript": true,
"dependencies": {
"lucide-react": "^0.468.0",
diff --git a/package.json b/package.json
index 7809d0f..8d588f5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "linkclean",
- "version": "1.3.3",
+ "version": "1.3.5",
"description": "LinkClean — LinkedIn Feed Cleaner, Filter Ads & Promoted Posts",
"type": "module",
"scripts": {
diff --git a/src/entrypoints/linkedin-feed.content.ts b/src/entrypoints/linkedin-feed.content.ts
index 4c8b0c2..8c3277d 100644
--- a/src/entrypoints/linkedin-feed.content.ts
+++ b/src/entrypoints/linkedin-feed.content.ts
@@ -24,6 +24,62 @@ import {
const POST_HIDDEN_ATTR = "data-linkclean-hidden";
const SIDEBAR_HIDDEN_ATTR = "data-linkclean-sidebar";
const PREVIOUS_DISPLAY_ATTR = "data-linkclean-previous-display";
+const HIDE_STYLE_ID = "linkclean-hide-style";
+
+const OBSERVED_ATTRIBUTES = [
+ "aria-label",
+ "aria-describedby",
+ "title",
+ "alt",
+ "data-test-id",
+ "data-promoted-tracking-control-name",
+ "data-sponsored-tracking-url",
+ "data-view-tracking-scope",
+ "data-finite-scroll-hotkey-item",
+ "data-urn",
+ "data-id",
+ "componentkey",
+];
+
+/**
+ * Hide via stylesheet keyed on our marker attribute, not inline styles.
+ * LinkedIn re-renders overwrite element.style and would un-hide posts while
+ * the marker attribute survives — the CSS rule keeps them hidden.
+ *
+ * When the promoted filter is active on the feed, also pre-hide elements
+ * carrying LinkedIn's own sponsored markers so ads never paint at all
+ * (no visible-then-hidden flash while the JS pass catches up).
+ */
+function buildHideStyleContent(
+ filters: FilterSettings,
+ onFeedSurface: boolean,
+): string {
+ const rules = [
+ `[${POST_HIDDEN_ATTR}="true"], [${SIDEBAR_HIDDEN_ATTR}="true"] { display: none !important; }`,
+ ];
+
+ if (onFeedSurface && filters.enabled && filters.hidePromoted) {
+ rules.push(
+ "main article[data-sponsored-tracking-url], " +
+ "main [data-urn*='promoted'], " +
+ "main [data-id*='promoted'] { display: none !important; }",
+ );
+ }
+
+ return rules.join("\n");
+}
+
+function ensureHideStyle(content: string): void {
+ let style = document.getElementById(HIDE_STYLE_ID) as HTMLStyleElement | null;
+ if (!style || !style.isConnected) {
+ style = document.createElement("style");
+ style.id = HIDE_STYLE_ID;
+ (document.head ?? document.documentElement).appendChild(style);
+ }
+ if (style.textContent !== content) {
+ style.textContent = content;
+ }
+}
export default defineContentScript({
matches: ["*://*.linkedin.com/*"],
@@ -34,11 +90,12 @@ export default defineContentScript({
let pendingCount = 0;
let flushTimer: ReturnType | null = null;
let processTimer: ReturnType | null = null;
+ let lastProcessedAt = 0;
let currentPathname = window.location.pathname;
chrome.storage.onChanged.addListener((changes, area) => {
if (area === "local" && changes.linkclean_filters) {
- filters = changes.linkclean_filters.newValue;
+ filters = changes.linkclean_filters.newValue ?? filters;
scheduleProcessAll();
}
});
@@ -58,8 +115,6 @@ export default defineContentScript({
function hideManagedElement(element: HTMLElement, attr: string): boolean {
if (element.getAttribute(attr) === "true") return false;
- element.setAttribute(PREVIOUS_DISPLAY_ATTR, element.style.display);
- element.style.display = "none";
element.setAttribute(attr, "true");
return true;
}
@@ -67,14 +122,19 @@ export default defineContentScript({
function restoreManagedElement(element: HTMLElement, attr: string): void {
if (element.getAttribute(attr) !== "true") return;
- const previousDisplay = element.getAttribute(PREVIOUS_DISPLAY_ATTR) ?? "";
- if (previousDisplay) {
- element.style.display = previousDisplay;
- } else {
- element.style.removeProperty("display");
- }
element.removeAttribute(attr);
- element.removeAttribute(PREVIOUS_DISPLAY_ATTR);
+
+ // Cleanup for elements hidden by older LinkClean versions that used
+ // inline display styles.
+ const previousDisplay = element.getAttribute(PREVIOUS_DISPLAY_ATTR);
+ if (previousDisplay !== null) {
+ if (previousDisplay) {
+ element.style.display = previousDisplay;
+ } else {
+ element.style.removeProperty("display");
+ }
+ element.removeAttribute(PREVIOUS_DISPLAY_ATTR);
+ }
}
function restoreAllManagedElements(): void {
@@ -108,13 +168,23 @@ export default defineContentScript({
}
}
+ function runProcessPass(): void {
+ lastProcessedAt = Date.now();
+ ensureHideStyle(buildHideStyleContent(filters, isActiveFeedSurface()));
+ processAllPosts();
+ hideSidebarAds();
+ }
+
function scheduleProcessAll(): void {
- if (processTimer) clearTimeout(processTimer);
+ // Leading+trailing throttle, not debounce: LinkedIn's feed mutates
+ // continuously while scrolling, and a resetting debounce would starve
+ // processing. After a quiet period the first batch runs immediately.
+ if (processTimer) return;
+ const delay = Date.now() - lastProcessedAt > 600 ? 0 : 250;
processTimer = setTimeout(() => {
- processAllPosts();
- hideSidebarAds();
processTimer = null;
- }, 250);
+ runProcessPass();
+ }, delay);
}
function scheduleFlush(): void {
@@ -240,7 +310,23 @@ export default defineContentScript({
}
}
- // MutationObserver — watch for new posts added to the feed
+ function shouldProcessMutation(mutation: MutationRecord): boolean {
+ if (mutation.type === "childList") {
+ return mutation.addedNodes.length > 0;
+ }
+
+ if (mutation.type === "characterData") {
+ return true;
+ }
+
+ if (mutation.type === "attributes") {
+ return OBSERVED_ATTRIBUTES.includes(mutation.attributeName ?? "");
+ }
+
+ return false;
+ }
+
+ // MutationObserver — watch for new posts and lazy-loaded ad labels
const observer = new MutationObserver((mutations) => {
let hasNewNodes = false;
const pathnameChanged = currentPathname !== window.location.pathname;
@@ -249,7 +335,7 @@ export default defineContentScript({
}
for (const mutation of mutations) {
- if (mutation.addedNodes.length > 0) {
+ if (shouldProcessMutation(mutation)) {
hasNewNodes = true;
break;
}
@@ -260,10 +346,15 @@ export default defineContentScript({
});
// Observe the entire body to catch both feed posts and lazy-loaded sidebar ads
- observer.observe(document.body, { childList: true, subtree: true });
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ characterData: true,
+ attributes: true,
+ attributeFilter: OBSERVED_ATTRIBUTES,
+ });
// Process existing posts + sidebar
- processAllPosts();
- hideSidebarAds();
+ runProcessPass();
},
});
diff --git a/src/lib/linkedin-detector.test.ts b/src/lib/linkedin-detector.test.ts
index 87c9ee8..6fa290f 100644
--- a/src/lib/linkedin-detector.test.ts
+++ b/src/lib/linkedin-detector.test.ts
@@ -53,6 +53,54 @@ describe("LinkedIn feed detector", () => {
expect(posts[0].getAttribute("data-urn")).toBe("urn:li:activity:123");
});
+ it("finds newer LinkedIn feed update containers", () => {
+ const document = render(`
+
+
+ Alex Morgan
+
This is a normal feed post with enough text to look realistic.