all writing

Your browser is an AI runtime now

Chrome ships Gemini Nano directly into the browser, with no API keys or token cost. I am very excited about this and built three AI features on this site with it.

Every product in the world seems to be shipping a new layer of AI features right now, and sometimes it feels like teams are force-fitting AI just to stay on the bandwagon, such as adding a chat interface to schedule a report when a good cron builder would be cheaper and more user-friendly. Some of these features are still genuinely useful, but they usually follow the same pattern: send data to a cloud model, wait for a response, and absorb ongoing latency, cost, and operational overhead.

That is why this felt different to me. Chrome introduced a path where Gemini Nano runs directly on the reader’s device, which means I can build useful AI behavior into this site without a server round-trip, without API key plumbing, and without treating every interaction like a metered backend call. It also gives customers stronger peace of mind because their data stays on their machine instead of leaving it for routine inference.

I have always been excited about new browser capabilities, and when I saw this land, I immediately wanted to ship it here, not as a demo, but as a real part of the site.

Three features I shipped

I implemented three real features with Chrome’s built-in AI APIs.

1. TL;DR with Summarizer

Under each post header, there is a TL;DR button that summarizes the article into key bullets when you click it.

The feature is not about a universal speed win, since performance depends a lot on the user’s device. On my Mac Studio it feels quick, but the real value is that summarization runs in the browser on the reader’s machine, so it feels less like a remote AI call and more like a built-in web capability.

The TL;DR panel expanded beneath the post header, showing three key point bullets summarized from the article The TL;DR panel expanded beneath the post header, showing three key point bullets summarized from the article
tsPostSummarizer.ts
// TL;DR: summarize article body into key points.
const proseEl = document.querySelector('.vk-prose');
const text = proseEl?.textContent?.trim() ?? '';

const summarizer = await Summarizer.create({
  type: 'key-points',
  format: 'plain-text',
  length: 'short',
});

const result = await summarizer.summarize(text);
summarizer.destroy();

const points = result
  .split('\n')
  .map((line) => line.replace(/^[\s\-*•\d.]+/, '').trim())
  .filter((line) => line.length > 10);

2. Translation with Translator

Each article can be translated into 11 languages inside the same page, and if your browser language is not English, it pre-selects your language while still letting you switch back with “Show original”. Among Indian languages, Hindi is currently the only option here, and I am hoping for the day Tamil is available natively too.

The stronger win here is continuity: readers can stay on the same page, keep the original layout and code examples in view, and read in their preferred language without copying content into an external translator or a browser plugin, which also keeps the text on their own device.

The Translate panel showing eleven language options including Spanish, French, German, Japanese, Korean, Chinese, Portuguese, Hindi, Arabic, Italian, and Russian The Translate panel showing eleven language options including Spanish, French, German, Japanese, Korean, Chinese, Portuguese, Hindi, Arabic, Italian, and Russian
tsPostTranslator.ts
// Translate selected post elements in-place.
const translator = await Translator.create({
  sourceLanguage: 'en',
  targetLanguage: targetLang,
});

for (const el of document.querySelectorAll('.vk-prose p, .vk-prose h2, .vk-prose li')) {
  if (el.closest('pre') || el.closest('.vk-code')) continue;
  const text = (el as HTMLElement).innerText?.trim();
  if (!text) continue;
  el.textContent = await translator.translate(text);
}

translator.destroy();

3. Semantic search with LanguageModel

Search now handles intent, not just exact string matches, so someone typing “handling conflict on a team” can still find posts about engineering management even when those words are not literal matches.

I do query expansion with LanguageModel first, then match against post content. When available, a small ✦ AI badge appears in the search UI to make that behavior explicit.

The search bar with a blue AI badge in the top right corner, indicating semantic search via LanguageModel is active The search bar with a blue AI badge in the top right corner, indicating semantic search via LanguageModel is active
tsSearchBar.ts
// Expand natural-language query into related keywords.
const session = await LanguageModel.create({
  expectedOutputs: [{ type: 'text', languages: ['en'] }],
});

const raw = await session.prompt(
  `Output ONLY a JSON array of 4-6 short search keywords related to: "${query}".`
);

const match = raw.match(/\[[\s\S]*?\]/);
const keywords = match ? (JSON.parse(match[0]) as string[]) : [];

document.dispatchEvent(
  new CustomEvent('search-expanded', { detail: { original: query, keywords } })
);

Progressive enhancement

I did not want flashy fallback states or broken controls, so every AI UI element starts hidden and appears only when the API reports support.

If Summarizer.availability() or Translator.availability() returns a usable state ('available' or 'downloadable'), the controls appear; if not, the page remains exactly the same as before, with no dead buttons and no noisy warnings.

That matters to me because this is how the web should evolve: new capabilities should feel additive, not coercive.

The rough reality (and why I still love it)

The built-in AI APIs changed quickly between early experiments and current Chrome builds.

window.ai.summarizer and window.ai.translator are gone. The new model is global constructors: Summarizer, Translator, LanguageModel. Availability states changed to 'available' | 'downloadable' | 'downloading' | 'unavailable'. Output configuration for LanguageModel.create() changed too.

I had to rework the implementation more than once, and I also added tests so I can quickly validate all three APIs after Chrome updates.

If you want to try this today: use a recent Chrome build, enable #summarization-api-for-gemini-nano, #translation-api-all-languages, and #prompt-api-for-gemini-nano in chrome://flags (the exact flag names can shift a bit across builds), and make sure Gemini Nano is installed in chrome://on-device-internals.

A bigger thought about the web

The browser started as a document viewer, then became an app platform, and now it is becoming an AI runtime. It is not just an AI website or an AI wrapper, but a runtime local to the user and integrated into the same environment where we render, compute, store, and interact.

That shift is practical and philosophical at the same time: practical because we get lower latency (well, it depends!), better privacy, and no server inference bill for these cases, and philosophical because capability is moving closer to the person using the web rather than farther away into invisible infrastructure.

We are still early, and that shows in young APIs, awkward flags, and limited support. But I can feel the direction, and I wanted this site to carry that feeling now, while it is still raw and becoming.

Varunkumar
Varunkumar Nagarajan

Software engineer · Engineering leader · Hacker · Wildlife photographer. SVP of Technology at Arcesium.

// discussion