Extension
Location
The extension lives in apps/modri-extension/ and is built with WXT plus Vue 3 and Nuxt UI (@nuxt/ui/vite, colorMode: false — theme is toggled manually).
Useful root scripts:
pnpm extension:dev— local extension developmentpnpm extension:build— production build →apps/modri-extension/dist/chrome-mv3pnpm extension:zip— zip for distribution
Load unpacked from apps/modri-extension/dist/chrome-mv3 (or the WXT .output/… path if your local scripts still point there).
Relationship to the web app
- Web app lives in
apps/modri(@sourcing/modri); extension inapps/modri-extension - One repository builds the website, dashboard, and extension
- Supabase is the source of truth; the extension uses the same workspace + RLS model
- Do not assume a shared UI package: Nuxt and WXT have different runtime and style-isolation constraints
- Shared pure TypeScript lives under
shared/(URL + Boolean); shared Vue pieces underui/where needed - Both apps read the repo-root
.env(NUXT_*/WXT_*)
Product surface
See LinkedIn extension for user-facing behavior.
Entrypoints
| Entrypoint | Role |
|---|---|
entrypoints/popup/ | Sign-in, workspace glance; follows OS prefers-color-scheme |
entrypoints/background.ts | Service worker — local chrome.history visit lookups for SERP badges |
entrypoints/linkedin.content/ | Floating note + ProfileModal on /in/{slug} |
entrypoints/google.content/ | FAB + right SERP overlay of /in/ results + local visit badges on organic hits |
Both content scripts use WXT createShadowRootUi (cssInjectionMode: 'ui'), anchored on html, with pointer-events: none on :host and pointer-events-auto on interactive UI.
Auth handoff
- Signed-out LinkedIn/Google panels and the popup show the shared
AuthForm(UAuthForm) — email sign-in/sign-up and Continue with Google — so the session lands inchrome.storagewithout leaving the page. - Google uses
chrome.identity.launchWebAuthFlowin the background service worker, thensupabase.auth.signInWithIdToken(requiresidentity+oauth2.client_idfromWXT_GOOGLE_OAUTH_CLIENT_ID). Register that Chrome Extension client ID in Supabase → Auth → Google → Client IDs. - Auth API errors (wrong password, etc.) render inline via the
UAuthForm#validationslot (UAlert), not only as toasts — content-script toasters also useportal: falseso they stay in the shadow root. - Forgot password opens
{WXT_SITE_URL}/forgot-passwordin a new tab. - Helpers:
utils/siteUrl.ts(getSignupUrl,getLoginUrl,getForgotPasswordUrl).
Shared contact layer (apps/modri-extension/contact/)
Used by LinkedIn and Google UIs:
contactApi.ts/contactTypes.ts— load/create/update contacts, activity, statuses/positionsProfileModal.vue— centered modal (notes, comments, activity);:portal="false"so it stays in the shadow rootuseExtensionWorkspace.ts— workspace gate / entitlement
Contacts are not upserted merely by opening the modal. The first write (status, position, note, or comment) creates the contact.
LinkedIn URL identity
Canonical identity for matching is the profile slug, normalized via shared/contact/linkedinUrl.ts:
- Unwrap Google redirect URLs (
/url?q=…) - Accept country LinkedIn hosts → always
https://www.linkedin.com/in/{slug} - Used by Google SERP scraping and LinkedIn profile flows so the same person matches across surfaces
Google SERP panel
- Matches Google Search URLs (
*.google.com/search*,www.google.pl/search*) scrapeGoogleSerp.ts+useGoogleSerp.ts— MutationObserver, de-dupe by canonical URL, current page only (no background pagination, no HTML upload, no LinkedIn Search scraping)- Rows show workspace status + position; avatar/name open ProfileModal; external link opens LinkedIn
- Panel open state persists in
sessionStorageacross SERP pagination within the tab - FAB is draggable; the sidebar stays docked on the right
- Local visit badges (
scrapeOrganicResults.ts+useVisitMarkers.ts): lime chips next to organic result titles with last-visit time fromchrome.historyonly — never sent to Supabase. Requires thehistorypermission; hover shows the exact timestamp. - Inline LinkedIn controls (
useSerpInlineLayouts.ts): status + position selects and an Open profile button rendered under each/in/card in the extension shadow UI (position: fixed) so Google SERP CSS cannot flip them.
Typical loop with product Boolean: build an X-ray string in /app/boolean → paste into Google → use the overlay to triage /in/ hits.
Host color mode
Nuxt UI dark tokens are class-based (.dark { --ui-* }). UApp / ConfigProvider does not render a DOM node, so :class on <UApp> is a no-op.
- Content scripts:
useHostColorMode()+ a real wrapper<div :class="{ dark: isDark }"> - Detection: LinkedIn
theme--dark,data-color-scheme/data-theme,meta[name=color-scheme],color-scheme, background luminance, thenprefers-color-scheme - Popup: OS preference only
Manifest / permissions
Configured in apps/modri-extension/wxt.config.ts: storage, history, identity, host permissions for LinkedIn, Google Search, Supabase, and Google accounts. Keep the surface narrow — no <all_urls>, no cookie/network interception. Google OAuth needs oauth2.client_id from WXT_GOOGLE_OAUTH_CLIENT_ID.
Related
- Boolean search (internals)
- App map
- Repo technical notes:
docs/technical/architecture.md