The assistant needed context without surrendering control
Thoughtline helps a person understand a visible LinkedIn conversation and shape a reply or post in their own voice. Generating text was the easy demo. Building a browser extension I could trust with page content, API credentials, writing history, and provider failures was the actual project.
The assistant needs enough context to be useful, but the LinkedIn page is neither my database nor a trusted instruction source. It is mutable third-party content. Passing the DOM directly into a prompt would mix data, instructions, unrelated page text, and potentially hostile content into one opaque string.
I designed the system around a stricter rule: page content may inform a request, but it does not control the application.
An AI feature starts at a trust boundary, not at the model call.
I put untrusted content inside a bounded envelope
The content script extracts only the context needed by a workflow. Before anything reaches a provider, the data enters a typed, size-bounded, validated envelope. That gives the application an explicit contract for what was captured and prevents arbitrary page structure from leaking through the rest of the system.
Validation does not magically eliminate prompt injection, and I do not present it as doing so. It reduces the attack and data-sprawl surface, preserves a clear distinction between application instructions and quoted source material, and gives malformed extraction a safe place to fail.
The same decision improved maintainability. When LinkedIn changes its DOM, extraction can break at one boundary without forcing the side panel, prompt builder, and storage model to understand selectors.
Model fallback became a policy, not a catch block
Thoughtline uses Gemini first and Groq only for eligible fallback. I did not let either provider's response shape spread through the UI. Typed drafting and source-research ports define what the product needs; adapters translate provider-specific requests, responses, and errors.
That makes fallback an explicit application policy. The system can decide which failures are eligible, attempt one controlled fallback, and still return a normalized result. It avoids the dangerous pattern where every error silently triggers another provider and users cannot tell which service received their data.
Provider abstraction was useful here because it removed real volatility from the product core. It was not abstraction for the sake of making two SDK calls look identical.
Permissions, credentials, and retention needed visible ownership
A browser extension lives close to the user's browsing activity, so convenience cannot justify broad invisible access. Thoughtline asks for capabilities when a workflow needs them instead of treating every permission as an onboarding checkbox.
Provider credentials and settings live in encrypted browser storage with migrations and recovery paths. Configuration backup, history archives, retention controls, and restore validation make local-first storage operable over time; encryption alone would not solve schema drift, accidental loss, or unclear ownership.
These choices add branches to the product. A permission can be declined, stored data can require migration, and a restore can fail validation. I accepted that complexity because each branch corresponds to control the user should actually have.
I tested the packaged system, not only the React components
The quality gates cover static checks, unit behavior, the packed extension, accessibility, responsive layouts, and visual regression. That matters because an extension can pass component tests and still fail after Manifest V3 packaging, permission resolution, or content-script injection.
The result is a writing assistant with deliberate limits: bounded page context, replaceable provider adapters, defined fallback, recoverable local data, and permissions connected to visible capabilities.
The senior engineering work was not making the model write more. It was deciding what the model could see, what the page could influence, how providers could fail, and where the user remained in control.
