[Step 2-A-3/3] Making It Yours — Icons, Task Buttons, and a Tool You’ll Actually Use (Reseller Edition)

📌Expert Workflows · Step 2: AI Chrome Extension · Edition A (Reseller) · Part 3 of 3 — final

🎯 What You’re Actually Building Here

This Chrome extension task buttons tutorial starts with a working personal prototype: it reads selected text, sends it to an AI provider, and returns a rewritten version. It also has a grey puzzle-piece icon, exactly one button, and forgets everything the moment you look away.

That gap — between “it works” and “I reach for it without thinking” — is smaller than it looks and matters more than it sounds. A tool you have to concentrate to use gets abandoned in a week. By the end of this post yours will have a proper icon in the toolbar, three buttons for the three jobs you actually do, and a memory that survives the popup closing.

We’ll also finish with the honest conversation nobody has in extension tutorials: what would genuinely need to change before you could hand this to another reseller.

Project context

the example user is a composite secondhand-clothing reseller created for this educational scenario. She represents a beginner improving a personal extension through three distinct listing tasks: rewriting a description, shortening it for a social caption, and generating title options. This is not a claim about a real person’s routine or results.

Other editions of this build:Edition B (Young Coder)andEdition C (Solo Founder).

🛠️ What to Have Ready

  • Parts 1 and 2 complete — a working extension that rewrites selected text
  • Allow roughly 35–60 minutes, depending on debugging and image preparation
  • Optionally, an image tool for the icon (details below)

How to Use This Troubleshooting Tutorial

  • 🔴 BEFORE shows an intentionally incomplete or flawed example so readers can reproduce and understand a representative problem.
  • 🧠 CAUSE explains why the problem occurs.
  • 🟢 AFTER presents the corrected implementation for this lesson.
  • ✅ VERIFICATION explains how to confirm that the correction works.

📝 Step 1: Giving It a Real Icon

The grey puzzle piece is Chrome’s placeholder for “this extension didn’t provide an icon.” Fixing it takes five minutes and changes how the tool feels more than any code you’ll write today.

You need three square PNG images — 16, 48, and 128 pixels. The 16 is what sits in your toolbar; the others appear in the extensions list and elsewhere. Make one 128×128 image and resize it twice.

Where to get the image? Three realistic options. Draw something simple in any image editor — a coloured square with a letter on it is completely respectable. Use a free icon site. Or generate one, which is the option worth trying if you followed the earlier levels: theAI image generation guidecovers how to art-direct a clean, flat icon rather than accepting whatever the model offers first. A prompt like“flat vector app icon, simple clothing tag symbol, two colours, plain background, no text”gets you close in a couple of tries.

Create a folder callediconsinside your extension folder and save the three files asicon16.png,icon48.png, andicon128.png. Then updatemanifest.json— note both places the icons appear:

{
  "manifest_version": 3,
  "name": "Listing Helper",
  "version": "1.2",
  "description": "Rewrites, shortens and titles your listings using AI.",
  "permissions": ["activeTab", "scripting", "storage"],
  "host_permissions": ["https://your-provider-url/*"],
  "background": { "service_worker": "background.js" },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": {
    "default_popup": "popup.html",
    "default_title": "Listing Helper",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  }
}

Refresh the extension atchrome://extensionsand look at your toolbar. That’s your tool, with your mark on it. (Chrome’sextension documentationlists the icon sizes used in each surface, if you want to be thorough.)

📝 Step 2: Three Buttons, Three Jobs

One button currently performs one task. In this Troubleshooting example, the example user needs three separate listing actions, so the interface should match those distinct jobs.

Replace the button area inpopup.html:

<div style="display: flex; gap: 6px;">
  <button class="task" data-task="rewrite">Rewrite</button>
  <button class="task" data-task="shorten">Shorten</button>
  <button class="task" data-task="title">Titles</button>
</div>

Thedata-taskattribute is the trick that keeps this simple — each button carries a label saying which job it wants, so one piece of code handles all three instead of you writing three near-identical copies.

Inpopup.js, replace the single click handler:

document.querySelectorAll(".task").forEach((button) => {
  button.addEventListener("click", () => runTask(button.dataset.task));
});

async function runTask(task) {
  const status = document.getElementById("status");
  const out = document.getElementById("out");

  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  const [{ result: selected }] = await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => window.getSelection().toString(),
  });

  if (!selected) {
    status.textContent = "Select some text on the page first.";
    return;
  }

  status.textContent = "Working…";
  out.value = "";

  chrome.runtime.sendMessage({ type: "RUN", task, text: selected }, (reply) => {
    if (reply && reply.ok) {
      out.value = reply.result;
      status.textContent = "Done — you can close this and come back.";
    } else {
      status.textContent = "Error: " + (reply ? reply.error : "no response");
    }
  });
}

And inbackground.js, give each task its own instruction while sharing one safety rule:

const GUARDRAIL =
  " Keep every factual detail exactly as given. Do not invent sizes, brands, " +
  "materials, measurements or condition. Do not add claims that are not in the text.";

const PROMPTS = {
  rewrite:
    "Rewrite this as a clear, warm marketplace description that is easy to skim." +
    GUARDRAIL,
  shorten:
    "Rewrite this as a single friendly sentence under 30 words, suitable for a " +
    "social media caption." + GUARDRAIL,
  title:
    "Write 3 listing title options, each under 60 characters, each on its own line. " +
    "Put the most searchable words first." + GUARDRAIL,
};

Then update your message listener to usePROMPTS[message.task]when building the request. One sharedGUARDRAILstring means the “don’t invent details” protection applies to every task automatically — change it once, and all three buttons inherit the fix.


⚠️ Troubleshooting — “Cannot read properties of null”

(Troubleshooting example — a common mistake reconstructed for learning, not a personal claim.)

🔴 BEFORE

After adding the buttons, nothing happened at all. Opening the popup’s console (right-click inside the popup → Inspect) showed:

Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

🤔 Cause

My code was looking for an element that didn’t exist at the moment it looked. Two ways that happens, and I’d managed both across two attempts.

The first was a spelling mismatch — my HTML saidid="output"and my JavaScript asked for"out". The browser returned nothing, and asking nothing to listen for clicks fails exactly like this.

The second was ordering. I’d moved my<script>tag up into the top of the file while tidying, so the code ranbeforethe buttons below it existed. It searched an empty page, found nothing, and stopped — which also silently killed every line after it, including the working handlers.

🟢 AFTER

Two rules that prevent both permanently.

Keep the script tag at the very end of<body>, after everything it needs to find — which is howpopup.htmlhas been written since part 2. If you prefer it in the head, adddefer(<script src="popup.js" defer></script>), which tells the browser to wait until the page is built.

Copy your ids, don’t retype them.Nearly every “cannot read properties of null” is a name that doesn’t match. When you see this error, check the exact spelling in both files before changing anything else.

To open the popup’s console, right-clickinside the open popupand choose Inspect — a separate window from the service worker console you met in part 2. Extensions have several consoles, and knowing which one holds your error is half of debugging them.

💡 Scenario takeaway: A spelling mismatch near the first event listener can stop later handlers from being registered. When several buttons fail together, check the earliest console error and compare every HTML id with the corresponding JavaScript selector.


📝 Step 3: A Memory That Survives

Here’s the thing that quietly ruins the tool in daily use. You click Rewrite, the AI takes four seconds, you glance at the page to check a detail — the popup closes, and when you reopen it, your result is gone.


⚠️ Troubleshooting — The result that vanished when I looked away

(Troubleshooting example — a common mistake reconstructed for learning, not a personal claim.)

🔴 BEFORE

No error message. Click a button, look at the page while it works, click the icon again: empty box, as if nothing had happened. Sometimes the result had arrived; sometimes it hadn’t. There was no way to tell.

🤔 Cause

A popup isn’t a window that stays open — Chrome destroys it entirely the moment it loses focus, along with everything it was displaying. This is normal, expected behaviour and it isn’t going to change.

The work itself was fine: the request runs in the background service worker (that’s why part 2 put it there), so the AI finished the job perfectly well. The answer just came back to a popup that no longer existed and had nowhere to go.

🟢 AFTER

Save the result where it outlives the popup, then restore it when the popup reopens. Inbackground.js, after a successful call:

await chrome.storage.local.set({
  lastResult: result,
  lastTask: task,
  lastAt: Date.now(),
});

And at the top ofpopup.js:

chrome.storage.local.get(["lastResult", "lastTask"]).then((saved) => {
  if (saved.lastResult) {
    document.getElementById("out").value = saved.lastResult;
    document.getElementById("status").textContent =
      "Showing your last " + saved.lastTask + " result.";
  }
});

Now the popup can restore the last non-sensitive result after it reopens. chrome.storage.local persists data, but it is not an encrypted secret vault: extension contexts and anyone with access to the browser profile may retrieve stored values. Use this client-side build only as a personal learning prototype, restrict provider keys and spending limits, and move production requests behind a server. See the Chrome Storage API documentation.

💡 Scenario takeaway: Persisting the last non-sensitive result lets a user leave the popup and return later. Avoid storing confidential listing data longer than necessary, and provide a clear way to overwrite or remove saved results on shared devices.


⚠️ What Still Needs Doing

You now have a useful personal prototype, and the natural next thought is “my reseller group would love this.” Before you send anyone the folder, three things you need to know.

Your key travels with the folder if you’re careless.The key itself lives in browser storage, not in your files, so a copied folder doesn’t carry it — that’s exactly why part 2 set it up that way. But if you ever pasted a key into a file while experimenting, it’s still in that file. Check before sharing, and if you find one, revoke it in your provider’s dashboard and generate a new one rather than just deleting the line.

Even done properly, this isn’t a shippable product yet.Anyone using a copied build may need separate API credentials and a provider account. Free access, quotas, billing requirements, and model availability vary by provider and account, which can be a real barrier for non-technical users. Distributing publicly through the Chrome Web Store means a developer account, a privacy policy, and a review process — and, more importantly, a small server standing between the extension and the AI so users aren’t handling keys at all. That’s Phase 5 of this course, and it’s a genuinely different project rather than a bigger version of this one.

Everything the model writes still carries your name.The guardrail sentence in your prompts helps a lot; it doesn’t make the model incapable of smoothing “small stain” into something friendlier. On a marketplace, an inaccurate description is your problem regardless of what wrote it — the accountability principle fromAI Hallucination and Ethicsapplies most sharply when there’s a buyer at the other end. Read before you post. It takes five seconds and it’s the whole difference between a tool that saves your evening and one that costs you a rating.

✅ What You’ve Built

A Chrome extension with your own icon in the toolbar, three purpose-built buttons that share one safety rule, an AI connection running from the correct part of the architecture, provider requests running from the extension’s service worker for this personal prototype, and results that survive you looking away. It handles a real workflow, not a demo — and you built it from an empty folder in three sittings. For the authoritative details, seeChrome extensions documentation.

Three posts ago, “make a browser extension” was something other people did.

💡 For the Ambitious

Add a fourth button for whateveryoukeep doing manually — a “translate to plain English” button, a “list the measurements as bullet points” button, a “check this for policy-risky words” button. The code doesn’t change at all: add a button with adata-taskname, add a matching line toPROMPTS, done. That’s the moment a tool becomes yours, and it’s theprompt-as-product idea from Essentials Step 6in its most practical form.

❓ Try This Yourself

Use your extension for one full listing session — ten items, start to finish — and keep a note of every moment you thought “I wish it also…”. That list is your roadmap. Build the top item as a fourth button before you touch anything else in this course; nothing teaches faster than adding a feature you personally want.

🔗 What’s Next

This Chrome-extension project is complete. Continue with the Expert Workflows roadmap for the current published project path.

Next in the course:[Step 3] Build an AI Telegram Bot— where your tool stops living in your own browser and starts answering other people, which is the first time what you’ve built becomes something withusers. The full path is mapped in The Expert Workflows Curriculum Roadmap.



Verified Part 3 Build Evidence

The Part 2 base and Part 3 additions were consolidated and checked on 30 August 2026 using Windows 10 and Node.js v24.19.0. The package contains three correctly sized placeholder icons and no real API key, account data, selected page text, or live provider configuration.

Reproduction input and observed outputs

Mock input: Blue mug, chipped rim.

  • Rewrite: Mock rewrite result
  • Shorten: Mock shorten result
  • Titles: Mock title result

Each request included the original input and the shared rule not to invent sizes, brands, materials, measurements, condition, or unsupported claims. The latest result and task were also written to the mocked storage layer and restored by the popup test.

Automated verification results

Check Result
Manifest V3 and integration files PASS
Icon files and dimensions PASS
JavaScript syntax PASS
Three task buttons PASS
Shared factual guardrail PASS
Secret and placeholder boundary PASS
Missing-key and unknown-task guards PASS
All task prompts and mocked outputs PASS
HTTP error handling PASS
Popup restore and empty-selection guards PASS

Download the verified Part 3 tutorial files (ZIP)
SHA-256: B2121A76D367E88841F526C84D820502A28E6FD390EE1980C7C131AE99627CE7

Verification boundary: this is a deterministic mock test, not evidence that a named provider accepted a live request. The included icons are locally generated plain blue placeholder PNGs at the declared sizes, not commissioned brand artwork. Configure the provider and model using current documentation before live use.