Engineering Specification V1: Normalize Once, Export Forever
Source: /Users/nitishchauhan/Downloads/Engineering Specification V1.pdf
Phase: final
Status: draft
Mode: Authority
Index: Index - Engineering Specification V1
Trajectory: Trajectory - Inner Map
Pipeline: CONSTITUTION - Publishable Asset Pipeline
Master: 00 - Master Index
Slug folder: Work/engineering-specification-v1/
The boundary that keeps the extension from rotting
Here is the real engineering problem, not the marketing one.
You want a browser extension that can pull a conversation off ChatGPT today, Claude tomorrow, some random article page next week, drop each capture into a temporary queue, and spit out Markdown, JSON, HTML, or plain text without drama. The product job is simple. The rot starts when site-specific DOM logic leaks into export code, into the sidebar, into storage, into settings.
Engineering Specification V1 is the architecture that refuses that rot.
Objective, frozen as stated:
Implement a lightweight, modular browser extension capable of capturing conversations and webpages from multiple websites, staging them in a temporary queue, and exporting them reliably in multiple formats. The implementation should prioritize maintainability, extensibility, and faithful content preservation.
Everything below is how you keep those three priorities true when websites change their markup every month.
High-level pipeline
Draw the system as a one-way pipe. Each stage has one job.
Browser
→ Content Script
→ Site Extractor
→ Normalized Conversation Object
→ Sidebar Queue
→ Preview
→ Edit Title
→ Remove
→ Exporter
→ Markdown
→ JSON
→ HTML
→ Plain TextRule that does not get negotiated: every module remains independent. Extractors do not know about ZIP packing. The queue does not know ChatGPT selectors. The exporter never branches on source === "claude".
That independence is the product’s long-term speed.
Project structure
Suggested layout under /src:
| Area | Role |
|---|---|
background/ | Extension service worker / coordination |
content/ | Page injection, extraction trigger |
sidebar/ | Temporary queue workspace UI |
popup/ | Quick capture surface |
export/ | Format writers, packaging |
extractors/ | Per-site + generic capture |
storage/ | IndexedDB / persistence |
models/ | Conversation, Message, Attachment, Metadata |
utils/, hooks/, assets/ | Shared helpers and static assets |
Folders are not decoration. They are the first enforcement of “website logic lives in extractors only.”
1. Extractor layer
Responsible for pulling content from supported websites. Each website gets its own extractor.
Suggested set:
chatgpt.tsclaude.tsgemini.tsgrok.tsperplexity.tscursor.tsgeneric.ts
Shared interface (every extractor exposes the same contract):
canHandle(url)
extract()
normalize()If no extractor matches the URL, fall back to the Generic Extractor.
That fallback is a product guarantee, not a sad path. Arbitrary pages remain exportable even when you have not written a specialized scraper yet.
Coding constraint that keeps the system clean: avoid website-specific logic outside extractor modules. No hardcoded UI selectors inside shared components.
2. Normalizer: erase origin on purpose
Every extractor returns different raw data. The Normalizer converts everything into one common format.
Example shape after normalization:
Conversation
id
title
source
url
createdAt
messages[]
assets[]
metadataEverything after this stage should ignore where the conversation came from.
That sentence is the whole architecture in miniature. Capture sites are chaotic. Your domain model is not. Normalize once so queue, preview, rename, multi-select, search, and every exporter stay boring and stable.
3. Sidebar: temporary workspace, not a second brain
The sidebar is a staging surface for captures, not a knowledge product.
Capabilities:
- View captured items
- Rename
- Delete
- Multi-select
- Search
- Reorder
- Preview metadata
Hard non-goals (stated as absences in the spec):
- No AI
- No summaries
- No tagging
Those refusals are engineering constraints. The moment the sidebar starts “helping,” you inherit interpretation bugs, latency, and product identity drift. Faithful preservation happens upstream at capture and downstream at export. The queue just holds truth until you decide.
4. Queue persistence: IndexedDB
The queue should persist between browser restarts.
Suggested storage: IndexedDB
Reason: large conversations may exceed chrome.storage limits.
A queue that evaporates when Chrome restarts teaches users not to trust capture. Persistence is part of the Definition of Done, not a nice-to-have.
5. Export engine: fully independent of extraction
Export should be completely independent of extraction.
| Input | Normalized Conversation |
| Output | Markdown, JSON, HTML, TXT |
| Future | Pluggable exporters |
If a new format shows up later (custom ZIP flavor, a notebook format, a lab-specific package), it plugs in as an export provider. It does not reach back into site extractors.
Suggested ZIP layout
conversation/
chat.md
chat.json
chat.html
meta.json
assets/
images/
attachments/One portable unit: prose, structured data, HTML view, metadata, and binary assets together.
Data models
Conversation
id
title
source
url
capturedAt
messages[]
attachments[]
metadataMessage
role
content
timestamp
order
formatAttachment
type
filename
mimeType
localPath
urlMetadata
browser
website
model
conversationId
exportVersion
extensionVersionModels carry provenance and packaging needs without forcing every consumer to re-parse the host site’s DOM.
UI surfaces
Popup
Purpose: quick capture.
Buttons:
- Capture Conversation
- Capture Page
- Open Sidebar
- Settings
Sidebar
Displays: conversation title, website, capture time, selection checkbox.
Actions: Rename, Delete, Export, Clear Queue, Search.
Settings
Minimal only:
- Default export format
- Default export location
- Auto ZIP
- Theme
- Future VPS endpoint (reserved, not required for V1 core)
Settings stay thin so the extension does not become a configuration novel.
Generic webpage capture
If no supported extractor exists, still capture:
- Title
- URL
- Visible HTML
- Selected text
- Images
- Links
- Metadata
This guarantees every website is exportable. Universal support is a fallback contract, not a promise that every site has a perfect transcript scraper on day one.
Error handling: never silently discard
| Failure | Response |
|---|---|
| Extractor failure | Fall back to generic extractor |
| Storage failure | Retry |
| Export failure | Keep item in queue |
Never silently discard data.
Trust dies the first time a capture disappears without a clear reason. Graceful recovery is part of the product, not a QA afterthought.
Performance goals
| Operation | Target |
|---|---|
| Capture | Under 2 seconds |
| Queue update | Instant |
| Export (normal conversations) | Under 5 seconds |
| Memory | Keep lightweight; avoid unnecessary DOM duplication |
Fast capture protects the real workflow: finish thinking, click once, close the tab.
Security and locality
- No analytics
- No telemetry
- No user tracking
- No cloud dependency
Everything remains local unless the user explicitly exports elsewhere.
That matches a capture utility whose job is preservation and exit, not surveillance or cloud lock-in. A future VPS endpoint can exist as an optional setting without becoming the default pipeline.
Coding standards
- Use TypeScript throughout
- Prefer composition over inheritance
- Keep modules small and isolated
- Avoid website-specific logic outside extractor modules
- No hardcoded UI selectors inside shared components
These are process rules that protect the architecture diagram when real code starts to thrash.
Future extension points (optional, non-core)
Reserved interfaces for:
- Cloud Upload Provider
- Export Provider
- Extractor Provider
- Search Provider
- Content Processor
These modules should be optional and not affect the core capture pipeline.
You can grow toward upload, search, or processing later. You do not rewire capture to depend on them.
Definition of Done
The extension is considered complete when it can:
- Capture conversations from supported AI websites
- Capture arbitrary webpages through the generic extractor
- Queue multiple captures
- Preview queued items
- Export reliably in selected formats
- Preserve formatting and metadata with minimal loss
- Recover gracefully from failures
Most importantly:
The user should be able to finish a conversation, click capture, close the tab, and trust that the information has been preserved without thinking about it again.
That last line is not soft product poetry. It is the engineering success criterion. Feature checklists can pass while the user still leaves tabs open from fear. Done means cognitive trust after close.
What to steal from this blueprint
- Normalize once. Site extractors stop at a shared Conversation object. Export and UI never re-special-case the origin site.
- Queue without AI. Temporary staging is a deliberate constraint. Summaries and tags are not free upgrades; they change the product.
- IndexedDB for conversation-scale payloads. Storage choice follows real capture size, not default extension APIs.
- Independent export engine. Formats are plugins on a stable input type.
- Fallback and never silent discard. Generic capture and error paths are how you earn the close-tab trust.
- Definition of Done as trust. Reliability after the user has already left the page is the finish line.
This V1 engineering map is the maintainable skeleton under a capture-and-export extension. Keep the pipe short, keep modules independent, erase origin after extract, and treat “I can close the tab” as the only done that matters.