Internals

Extension

WXT Chrome extension — LinkedIn profile panel, Google SERP overlay, shared contact layer.
Living notes for builders. LinkedIn/Google DOM details require ongoing spikes.

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 development
  • pnpm extension:build — production build → apps/modri-extension/dist/chrome-mv3
  • pnpm 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 in apps/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 under ui/ where needed
  • Both apps read the repo-root .env (NUXT_* / WXT_*)

Product surface

See LinkedIn extension for user-facing behavior.

Entrypoints

EntrypointRole
entrypoints/popup/Sign-in, workspace glance; follows OS prefers-color-scheme
entrypoints/background.tsService 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 in chrome.storage without leaving the page.
  • Google uses chrome.identity.launchWebAuthFlow in the background service worker, then supabase.auth.signInWithIdToken (requires identity + oauth2.client_id from WXT_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 #validation slot (UAlert), not only as toasts — content-script toasters also use portal: false so they stay in the shadow root.
  • Forgot password opens {WXT_SITE_URL}/forgot-password in 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/positions
  • ProfileModal.vue — centered modal (notes, comments, activity); :portal="false" so it stays in the shadow root
  • useExtensionWorkspace.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 sessionStorage across 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 from chrome.history only — never sent to Supabase. Requires the history permission; 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, then prefers-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.