Build PromptDC

YESreplaces $9/mosaves $108/yrback to the verdict

0%0 of 16 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A browser extension whose hard problems are putting rewritten text back into rich editors without corrupting the document, surviving a background worker that is shut down mid-request, and keeping the API key away from every page you visit.

estimated effort one sittingthe files for this build are in the project pack

RuntimeA third generation manifest extension, plain HTML, CSS and JavaScript, no build stepBackgroundA service worker that assumes it will be killed at any momentStorageThe local extension storage area for settings and in-flight requests, never the synced areaInsertionRange-based replacement with events dispatched, clipboard as the honest fallbackPermissionsOptional host permissions requested per site, never every site up front

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why You will load the folder unpacked dozens of times, and the worker lifecycle behaviour you are designing around is only observable in the real browser.

    Get it Enable developer mode on the extensions page and use Load unpacked.

    Verify The extension loads and its service worker is visible and can be stopped from the extensions page

  2. API keypay per call; a few rewrites a day is small change, and the ceiling here is what keeps it that way

    Why Every rewrite is one call, and this is the only cost in the project.

    Get it Create a key and set a spend limit in the provider's console.

    Verify The options page test button returns a short completion

  3. have readyfree

    Why Insertion is where this category of extension fails, and you cannot test it against a live site you might corrupt.

    Get it Write one local page with an input, a textarea, an editable element inside a framework, and a read-only paragraph.

    Verify A rewrite in each case behaves as specified, including the clipboard fallback

  4. decidefree

    Why This decides the install prompt, which is the single thing that makes an extension feel either useful or invasive.

    Get it List the handful of sites you actually rewrite text on, and design the permission flow around asking per site.

    Verify The manifest declares no broad host access and the flow works on a site granted at runtime

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- settings(provider, endpoint, model_id, updated_at) · in the local storage area only, and the key is stored separately from anything ever sent to a page
- profiles(id, name, system_prompt, order, built_in, edited_at) · plain editable text the user can read before anything is sent
- requests(id, tab_id, profile_id, started_at, finished_at, state, error) · state is pending, running, done or failed, and it lives in storage rather than a variable
- captures(request_id, kind, container_signature, start_offset, end_offset, text) · kind is input, textarea, contenteditable or readonly
- usage(month, requests, input_chars, output_chars, estimated_cost_minor) · against a ceiling you set
- The key appears in exactly one place and is never written into a request record, a log line, or anything a content script can read.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
MODEL_API_KEYsecretrequiredsk-...redactedEntered on the options page and stored in the local extension storage area. Not a file in the repository.
MODEL_ENDPOINTrequiredhttps://api.example.com/v1/messagesThe provider endpoint. Also the only host permission the worker needs.
MODEL_IDrequireda text model identifierStored with settings and shown on the options page.
REQUEST_TIMEOUT_MSrequired45000Your own timeout, set well inside the platform ceiling for a single request.
MONTHLY_CHAR_CEILINGrequired500000Characters in plus out per month. Requests stop here until the next month.

The build, in order

  1. Capture the selection, and know what kind it is

    The selection is captured with everything needed to restore it before any asynchronous work starts.

    1. By the time the response arrives the selection is gone.

      Files content.js

    2. Each one has a different way back, and guessing later is how text gets destroyed.

      Files content.js

    3. The worker that answers may not be the one that asked.

      Files content.jsbackground.js

    done when · tick each as it passes
    watch out
    • Reading the selection again after the model responds finds nothing, because clicking your own button, switching tabs or the page re-rendering has already collapsed it, and the result then lands at the caret or nowhere at all. Capture offsets and a way to find the container at the moment of the click, store them, and treat the live selection as gone from that instant.
  2. A request that survives the worker being killed

    The model call is resumable, because the background worker is shut down on a short idle timer.

    1. The worker is disposable and behaves like it.

      Files background.js

    2. It is what stops the worker being shut down underneath you.

      Files content.jsbackground.js

    3. Something must decide what happened to the one that vanished.

      Files background.js

    done when · tick each as it passes
    watch out
    • Holding the in-flight request in a variable in the background worker loses it the moment the worker is shut down, and the documented behaviour is that an extension service worker is terminated after thirty seconds of inactivity with a five minute ceiling on processing a single request, so a slow rewrite simply vanishes with no error anywhere. Put the request state in storage, keep a connection open from the content script while it runs, and reconcile orphaned requests on startup.
  3. Putting the text back without destroying the document

    Replacement affects only the captured range and the page's own editor notices the change.

    1. The surrounding text is somebody's work in progress.

      Files content.js

    2. Assigning markup destroys the rest of the editor.

      Files content.js

    3. A framework that did not hear about it will undo it.

      Files content.js

    done when · tick each as it passes
    watch out
    • Writing the result with an assignment to the element's markup destroys everything else in that editor, and in a framework-managed field the change is invisible to the framework, so the old text reappears on the next render and the work is gone. Replace only the captured range, use a text node, dispatch events that bubble, and fall back to the clipboard whenever the range cannot be resolved.
  4. Profiles you can read before you send

    Every rewrite mode is an editable prompt the user can see, change and reset.

    1. The user should be able to see what is being said about their writing.

      Files options.htmloptions.js

    2. An experiment nobody can undo is not an experiment.

      Files options.js

    3. Otherwise you cannot tell which prompt produced the odd output.

      Files background.js

    done when · tick each as it passes
    watch out
    • Burying the prompts in the extension's code means the user cannot tell why a rewrite keeps changing their meaning, and the only remedy available to them is to stop using it. Put the prompt text in front of them, let them edit it, and keep a reset so an experiment is never permanent.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more ai writing.