Skip to content

Commit 2069f3f

Browse files
committed
feat: markdown rendering + per-message copy/share in dashboard
- react-markdown + remark-gfm render Message.content as markdown - @tailwindcss/typography provides prose styles (prose-sm max-w-none) - MarkdownContent component enforces overflow guards: pre -> overflow-x-auto (horizontal scroll for wide code blocks) code -> break-all (inline code) img -> max-w-full h-auto table -> wrapped in overflow-x-auto div a -> break-all (long URLs) - stripMarkdown util strips markdown symbols to plain text (no deps) - Per-message hover action bar (group/group-hover): Copy: copies raw MD or stripped plain text MD/TXT toggle pill switches copy mode for all messages Share: Web Share API with clipboard fallback Copied! feedback via transient copiedId state + Check icon
1 parent 93a52ed commit 2069f3f

10 files changed

Lines changed: 1941 additions & 101 deletions

File tree

.memory/decisions.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
- [save-all-chats] Save All Chats feature: FloatingDial speed-dial action calls listConversations() then fetchThread() with per-provider jittered delays. Threads sent in batches of 10 via SAVE_BULK_DATA message. Vault uses saveBulkThreads() for single-write batch upsert. Skips already-saved threads by comparing updatedAt. AbortController for cancellation. Gemini excluded (intercept-only).
2020

21-
2221
- [unlimited-storage-permission] The extension declares the unlimitedStorage permission in the manifest to support bulk-saving hundreds of threads without hitting the default 10 MB chrome.storage.local quota. Users upgrading from a version without this permission will be prompted to re-approve the extension.
2322

2423
- [gemini-exclusion-policy] Gemini is permanently excluded from any feature that requires background or bulk API fetching because its extraction relies solely on a MAIN-world fetch interceptor. Any new bulk or batch feature must document this exclusion explicitly.
@@ -29,8 +28,28 @@
2928

3029
- [save-all-minimize] Save All modal has a Minimize2 button (visible during listing/saving) that hides the modal while saving continues. FAB shows an SVG arc ring (58px, r=26, circumference=163.4, starts at 12-o'clock) + numeric badge (n/total or …) while minimized. Clicking the FAB reopens the modal. Phase done/cancelled auto-resurfaces the modal by setting saveAllMinimized=false.
3130

32-
- [save-all-fab-spec] While the Save All modal is minimized, the FAB displays an SVG arc progress ring (58px diameter, radius 26, circumference 163.4, origin at 12-o'clock) with a numeric badge showing completed/total or an ellipsis during indeterminate phases. Clicking the FAB reopens the modal. When the save phase reaches done or cancelled, the modal auto-resurfaces by setting saveAllMinimized to false.
33-
3431
- [modal-minimize-behavior] The Save All modal exposes a Minimize button (Minimize2 icon) that is visible only during the listing and saving phases. Pressing it hides the modal visually while the save operation continues uninterrupted in the background. No other modal in the extension has a minimize affordance.
3532

3633
- [save-all-skip-policy] Save All must skip any thread whose updatedAt timestamp matches the value already stored in the Vault, avoiding redundant network fetches and storage writes for unchanged conversations.
34+
35+
- [gemini-intercept-only] Gemini extraction relies exclusively on a MAIN-world fetch interceptor that captures responses in-page. No background API calls, no listConversations, and no fetchThread implementation exist or should be added for Gemini. Any extractor code for Gemini must be scoped entirely to the content script MAIN world.
36+
37+
- [universal-json-schema] All extracted chat data must be normalised into the Universal JSON Schema before being sent to the background worker via SAVE_EXTRACTED_DATA or SAVE_BULK_DATA. No provider-specific shape may be written directly to chrome.storage.local. Transformation to the Universal schema is always the content script's responsibility.
38+
39+
- [gemini-save-all-ui-exclusion] The Save All Chats UI must explicitly hide or disable the Save All action for Gemini sessions. If the active provider is detected as Gemini, the feature must be omitted from the FloatingDial and no listConversations or fetchThread call may be attempted for it.
40+
41+
- [save-bulk-data-background-write] The background worker's handler for SAVE_BULK_DATA must call saveBulkThreads as a single atomic upsert per batch. It must never iterate the batch and write threads one at a time, and it must never pass raw provider data directly to storage without a prior Universal JSON transformation having been completed in the content script.
42+
43+
- [export-import-feature] Export: EXPORT_VAULT message → vault.exportAll() → downloads aichatbackup-<date>.json. Import parsing runs in options page (Blob#text() + detectAndAdapt), then sends IMPORT_THREADS to background. Supports: own format, ChatGPT conversations.json, Claude export. Gemini/Perplexity/Grok stubs (isXxx always false). lastExportedAt stored at vault:meta:lastExportedAt, read directly via chrome.storage.local in options page.
44+
45+
- [uninstall-safety] onInstalled listener in background sets setUninstallURL to https://vchan-in.github.io/migrate/uninstall. Options sidebar shows amber nudge if never exported or last export > 7 days ago.
46+
47+
- [uninstall-url-registration] The background service worker must register the uninstall URL by calling chrome.runtime.setUninstallURL with the value https://vchan-in.github.io/migrate/uninstall inside a chrome.runtime.onInstalled listener. This must be done unconditionally on every install and update event.
48+
49+
- [export-nudge-policy] The Options sidebar must display an amber nudge indicator whenever the user has never exported their Vault or the lastExportedAt timestamp is more than 7 days in the past. This nudge must be driven by reading vault:meta:lastExportedAt from chrome.storage.local at Options page load time.
50+
51+
- [import-format-detection] Import parsing is the responsibility of the options page, not the background worker. The options page reads the uploaded file via Blob text, runs detectAndAdapt to normalise it into Universal JSON, and only then sends an IMPORT_THREADS message to the background. The background worker must never receive or parse raw third-party export formats.
52+
53+
- [markdown-rendering] Messages in the dashboard render markdown via react-markdown + remark-gfm + @tailwindcss/typography. MarkdownContent component lives in src/components/MarkdownContent.tsx. Overflow is hard-capped: pre=overflow-x-auto, img=max-w-full, table wrapped in overflow-x-auto div, links=break-all.
54+
55+
- [message-copy-share] Per-message actions: Copy (MD/TXT toggle, shared copyMode state) + Share (Web Share API with clipboard fallback). Action bar is hover-revealed via Tailwind group/group-hover pattern. stripMarkdown utility lives in src/utils/stripMarkdown.ts (regex-only, no deps).

.memory/instructions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@
99
- [bulk-batch-size] Bulk saves must always be chunked into batches of 10 threads per SAVE_BULK_DATA message to the background worker. Never send all threads in a single message or write them individually one at a time.
1010

1111
- [abort-controller-save-all] Every Save All bulk operation must be wired to an AbortController so that cancellation immediately halts further fetchThread calls and batch sends. The cancellation path must set the phase to cancelled and auto-resurface the modal if it was minimized.
12+
13+
- [save-all-skip-check] Before calling fetchThread for any thread during a Save All operation, always compare the thread's updatedAt value against the stored Vault entry. Only fetch threads where updatedAt differs or no stored entry exists. Never re-fetch or re-write a thread that is already up to date.
14+
15+
- [content-script-fetch-only] All same-origin authenticated fetches to AI provider APIs must be made from the content script, never from the background service worker. The background worker is only responsible for receiving parsed Universal JSON messages and writing them to storage.
16+
17+
- [universal-json-transform-location] The transformation from provider-specific API response shapes into Universal JSON Schema must always be performed inside the content script before any message is sent to the background worker. The background worker must never receive raw provider API payloads and must never contain provider-specific parsing logic.

.memory/quirks.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
- [crxjs-sw-loader-bug] CRXJS beta always writes localhost:5173 imports into service-worker-loader.js even in production builds. Fix: add a `closeBundle` Vite plugin that finds `dist/assets/index.ts-*.js` and rewrites the loader with the correct relative import.
44

55
- [project-quirks] Project-specific weirdness — the non-obvious stuff.
6+
7+
- [claude-inject-fallback] Claude's `#chat-input-file-upload-onpage` is lazy-rendered — absent on existing chats until the composer is interacted with. Use MutationObserver to wait 2 s for it; fall back to ClipboardEvent paste into ProseMirror `div[contenteditable]` if still absent.

.memory/security.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# Security
22

33
- [hard-rules] Rules that must NEVER be broken. Always read this file.
4+
5+
- [content-script-auth-cookie-scope] Authenticated same-origin fetches issued from the content script inherit the user's session cookies automatically via the browser. No auth tokens, session cookies, or credentials may be extracted, stored, or forwarded by the extension. The content script must only use implicit cookie authentication and discard all credential material after the fetch completes.
6+
7+
- [import-source-validation] During import, the detectAndAdapt parser must silently reject or skip any entry that cannot be mapped to the Universal JSON Schema. No malformed or unrecognised provider data may be forwarded to the background worker or written to chrome.storage.local.

0 commit comments

Comments
 (0)