Old flow: click Import Backup -> OS file picker opens -> select file
-> modal asks merge/replace. Awkward because the user was already
deep in a filesystem dialog before being asked the meaningful
question.
New flow: click Import Backup -> modal asks merge/replace + "Choose
File..." -> file picker opens -> import runs with the chosen mode.
Refactor:
- showImportBackupModal(backup, onConfirm) -> showImportModeModal(onConfirm).
No longer takes a file/backup arg; no file-contents preview (snapshot
/export counts, creation date) since the file isn't selected yet.
Modal copy now reads "Choose how the imported data should be
combined with your current data, then pick a backup file."
- importBackup(file, onComplete) -> importBackup(file, mode, onComplete).
No internal modal call; just validates the file and applies the
pre-chosen mode.
- options.js and browse.js each gained a pendingImportMode state
variable that bridges the async modal-close -> file-picker-change
boundary. Consumed and reset on file-picker change to prevent stale
reuse.
Both browsers identical. Tests still pass (54/54).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
exportAllFiltered was intersecting selectedConversations with
filteredConversations, so any chat the user had checked but later
filtered out (e.g. via the funnel "New / Updated" filter or by typing
a search term) was silently excluded from the export. The "Export
Selected (N)" button text continued to show the full selection
count, so the user got fewer chats than they expected.
Now uses allConversations.filter() when selectedConversations.size > 0
so every checked chat ships, regardless of current view state. The
header select-all checkbox still only operates on visible rows
(unchanged) — that's the user's preferred behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous commit landed the code fix and tests but the manifest/changelog
edits failed (file-modified-since-read race) and weren't retried.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When Anthropic's `enabled_artifacts_attachments` conversation setting
is false, Claude creates files via the skills-runner `create_file` MCP
tool instead of the legacy `artifacts` tool. The display_content shape
is identical (json_block with language / code / filename), only the
tool_use.name differs. The v1.9.1 strict allowlist was rejecting it,
silently dropping every artifact in those conversations.
Allowlist now accepts both 'artifacts' and 'create_file'. Other skills
tools that happen to share json_block display (view, list_directory,
etc.) stay filtered out by name.
Two regression tests added:
- positive: create_file with markdown json_block extracts correctly
- negative: view tool with similar json_block stays filtered
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After v1.10.13 deduplicated the export toast to a single emit per
call, the "with N artifact(s)" detail was lost. Restored it without
re-introducing the double-toast pattern:
- Added function-scope `let artifactCount = 0;` at the top of
exportConversation.
- Set artifactCount = artifactFiles.length inside the artifact
extraction branch when files are actually found.
- Unified post-save toast now reads:
artifactCount > 0
? "Exported: X with N artifact(s)"
: "Exported: X"
Both browsers identical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
exportConversation was emitting three toasts on a successful export
with artifacts:
1. "Exporting X..." (line 656)
2. "Exported: X with N artifact(s)" (line 741, branch-specific)
3. "Exported: X" (line 794, unified post-save)
Same triple-emit on the "no artifacts found" sub-branch (lines 656,
762, 794). The "normal export without artifact extraction" branch
correctly only emitted two (initial + unified).
Removed the branch-specific toasts at 741 and 762 — the unified
post-save toast at line 794 already covers all three branches.
Firefox already had this pattern (commented "Toast handled below
after timestamp save"). Chrome had regressed.
Trade-off: lose the "with N artifact(s)" detail from the toast,
gain consistent single-emit UX across all export paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multiple files had drifted out of sync over recent commits. Brought
Firefox in line with Chrome (canonical copy per CLAUDE.md):
- popup.html: removed Chrome typo "dd" after --error-text: #ff9999;
- popup.html: synced firefox label { margin: 6px 0 } -> 6px (chrome's value)
- browse.html: synced firefox td { padding: 15px } -> 15px 20px
- browse.html: removed stale .btn-view CSS from firefox (the v1.10.9
cleanup only touched chrome)
- browse.html: bumped firefox body min-width 1140 -> 1200 and added
min-width: 1200px to .container to match chrome's recent tweak
- browse.js: synced firefox tooltip label "Now using" -> "Currently"
- options.html: synced firefox width: 800px -> 810px
- content.js: removed firefox-only [Claude Exporter] debug console.log
lines at top of file
Legitimate differences left alone:
- background.js: chrome MV3 scripting.executeScript vs firefox MV2
tabs.executeScript loop (per CLAUDE.md API differences)
- popup.js: chrome.tabs.* (chrome) vs browser.tabs.* (firefox idiom)
Uncertain (left for direction):
- browse.js exportConversation toast handling differs (chrome emits
inline showToast per branch; firefox has them removed with a
"Toast handled below after timestamp save" comment, but no
unified toast was found later in the function)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Firefox had drifted to padding: 15px 10px and text-align: center.
Missed in the previous commit. Bringing both browsers back in sync
per the parallel-browsers rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- .checkbox-col now text-align: right (was center). With center +
padding 15/25, the checkbox drifted toward the middle of the cell
whenever the table grew wider than its min-width. Right-aligning
pins the checkbox to the cell's right edge with the existing 25px
right padding regardless of cell width.
- th: added white-space: nowrap and bumped padding-right from 15px
to 25px. Headers can no longer wrap onto multiple lines on narrow
columns, and the extra right padding guarantees room for the
absolute-positioned sort-direction arrow without text collision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, even with the table's min-width: 1100px, the
.conversations-table parent stayed at body width (= viewport width),
and its overflow: hidden was clipping the table's right side rather
than letting the page grow.
Set body { min-width: 1140px } (= 1100 table min-width + 2*20px
.container padding). Now the body is forced to at least 1140px, the
table sits inside at its full natural width, and when the viewport
is narrower than 1140 the PAGE scrolls horizontally. Single page
scrollbar, no internal table scroll, all columns always visible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Removed the redundant "View" button from each conversation row in
the browse table. The chat name in the Name column is already an
<a> link to the conversation, so View was duplicating that action.
Dropped the button markup, its click handler, and the .btn-view
CSS rules. Narrower Actions column.
- Reverted the table-level horizontal scroll from v1.10.6/1.10.7:
.conversations-table is back to overflow: hidden, so when the
viewport is narrower than the table's min-width, the PAGE scrolls
horizontally instead of the table getting its own scrollbar. User
preferred this — single scrollbar rather than two.
- Table min-width: 1200px -> 1100px since the Actions column is
narrower now.
The flex-wrap cascade from v1.10.7/1.10.8 stays so the header
controls still wrap and don't unnecessarily inflate page width.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.10.7's .controls flex-wrap let the three outer groups (search,
filter, export controls) wrap, but the export-controls-wrapper was
itself a fixed-width row (Chat format + 3 checkboxes + divider +
Artifact format + 3 checkboxes + Export All button) at ~900px wide,
which kept forcing the body wider than the viewport.
Added flex-wrap: wrap to .export-controls-wrapper, .export-settings,
.export-row, and .export-section so individual checkbox labels can
wrap to multiple rows on narrow viewports. With the page no longer
overflowing horizontally, the table's overflow-x: auto + min-width:
1200px finally provides its own internal scrollbar without dual
page-level + table-level scrolling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.10.6's overflow-x: auto on .conversations-table didn't actually
help because (a) the table had width: 100% with no min-width, so it
shrank to fit the container instead of overflowing, and (b) the
header controls had no flex-wrap, so they forced the whole page
wider than the viewport, creating page-level horizontal scroll that
confused the cutoff visual.
Two changes:
- .controls { flex-wrap: wrap }: header items (search box, filter
funnel, export controls wrapper) now stack to multiple rows on
narrow viewports. No more page-level horizontal scroll.
- table { min-width: 1200px }: keeps columns usable AND triggers the
.conversations-table overflow-x: auto when viewport is narrower
than 1200px, giving the table its own horizontal scrollbar within
the container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.conversations-table had overflow: hidden, which clipped the right
side of the table when the browser window was narrower than the
table's natural width AND prevented any scrollbar from appearing.
Switched to overflow-x: auto so the table scrolls horizontally on
demand. Border-radius still clips the rounded corners; the existing
container max-width caps things on the upper end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local markdownlint override (disables MD036) lives at src/.markdownlint.json
and shouldn't be tracked. The _Published_ markers in docs/CHANGELOG.md are
intentional convention but trigger no-emphasis-as-heading on every CHANGELOG
edit, which created persistent IDE diagnostic noise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced the three Contact & Diagnostics buttons (Email developer,
Generate diagnostics, Clear log) with two inline anchors inside a
single sentence:
Found a bug or have feedback? [Email the developer]. If reporting
a bug, please [attach a diagnostics file] (extension/browser info,
stored counts, recent errors -- all UUIDs redacted, no conversation
content).
Reasoning: most users who actually file bug reports are technical
enough to recognize underlined links as actions, and a section with
three buttons + two paragraphs read as cluttered.
- Removed clearDiagnosticsBtn entirely and the clearDiagnosticsLog
helper in utils.js (nothing calls it now).
- Added .section a link styling (--link-color / --primary-hover) so
the new inline anchors look correct in both light and dark mode
instead of using browser default link colors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setupEventListeners() now runs immediately after initTheme() instead
of after the await chain for loadOrgId / load*Prefs / loadConversations.
The settings gear, filter funnel, search bar, and other UI controls
are interactive from the start of the loading screen instead of only
after the conversation list arrives.
Handlers that read conversation data either iterate empty arrays
harmlessly or already guard with `if (data)` checks, so attaching
them early is safe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New Options section with three buttons:
- Email developer: opens a mailto with pre-filled subject (incl. version)
and a short body template
- Generate diagnostics: downloads a sanitized JSON bundle the user can
attach to a bug report
- Clear log: wipes the captured error ring buffer
Error capture (utils.js):
- initErrorCapture(context) registers window.error +
unhandledrejection listeners that push entries to a 50-entry FIFO
ring buffer in chrome.storage.local (errorLog key)
- Sanitization runs at capture time: UUID-shaped substrings (chat /
org / project IDs that may appear in fetch URLs or stack traces)
replaced with "<id>" before they hit storage
- Re-entry guard prevents the listener from looping on its own writes
- Called from popup, browse, content, options. Skipped in background --
it's tiny and would need importScripts overhead for marginal value.
Diagnostics bundle includes: extension/browser version, navigator UA
+ platform + language, counts of modelSnapshots / exportTimestamps /
errors, current preferences (dateFormat, timeFormat, modelDisplay),
orgIdConfigured boolean (NOT the value), and the error log. No
conversation content. No org ID. Nothing is sent anywhere -- user
downloads the file and chooses whether to attach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Removed "Test connection" item from the browse settings dropdown
(chrome+firefox HTML + JS handler). Available in Advanced Options.
- Model column "*" bounce marker:
- Color now matches the badge per family (sonnet/opus/haiku/default)
instead of always rendering var(--badge-default) at 0.7 opacity.
- Tooltip on the bounced cell now fires when hovering EITHER the
badge or the asterisk: wrapped both in a .model-cell span that
carries the title + cursor: help.
- Popup header (#header-title) is now populated from manifest.name in
popup.js — testing branch's "Claude Exporter Beta" surfaces in the
popup automatically without a separate HTML edit per branch.
- src/CLAUDE.md extended the existing manifest-name rule to note that
popup.js reads manifest.name into #header-title. (Workspace-root
CLAUDE.md was mirrored locally but isn't tracked in git.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New 4th option in the browse page filter dropdown, separated from the
existing status filters by a divider. When selected:
- Search bar placeholder switches to "Search projects by name..."
- applyFiltersAndSort branches: search term is matched against the
conversation's project name (via getProjectName); conversations
without a project ("-") are excluded
- Status filter logic (new/exported) is skipped in this mode — the
funnel option is the active filter
Filter button still gets its "active" highlight since the selection is
not the default "all".
Version jumped 1.9.17 → 1.10.1 per request (skipping 1.10.0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two-column layout from v1.9.16 cramped the radio descriptions.
Stacked layout reads cleaner — restoring it and dropping the
.model-display-grid CSS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Browse dropdown's "Edit Org ID" and "Advanced Options" now navigate
the current tab to options.html via window.location.href instead of
chrome.runtime.openOptionsPage() (which spawns a new tab on both
Chrome MV3 and Firefox with open_in_tab). The browser back button
returns to the browse view, restoring its scroll position.
- Browse page listens for pageshow with event.persisted === true
(bfcache restore) and reloads itself. Without this, hitting Back
would restore the cached page with stale preferences and the user
would have to refresh manually.
- Model Display radios wrapped in a .model-display-grid (2-column),
matching the Date & Time Format section's two-column layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts the v1.9.14 rename to claude-database-* and replaces the ISO-
with-dashes timestamp with the YYYYMMDD-HHMMSS format already used by
conversation/artifact export filenames (see getLocalDateTimeString in
content.js / browse.js).
Inlined the date formatting here rather than extracting a third copy
of the helper — utils.js doesn't currently expose one and pulling
content.js/browse.js's copy into utils.js is scope creep beyond the
filename fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Backup filename prefix: claude-exporter-backup-* → claude-database-*
(same timestamp format, still JSON)
- Browse Model column display is now controlled by a new modelDisplay
preference saved in chrome.storage.local. Default is 'original'
(revert of v1.9.12's flip-to-current), with 'current' available as
an opt-in via a new "Model Display" section on the Options page.
- Bounced chats keep the "*" marker in either mode. Tooltip text
adapts: "Originally X" when displaying current, "Now using X" when
displaying original. Bounce signal is preserved regardless of which
model the cell shows.
- getDisplayModel now returns { model, other, otherLabel, bounced }
with the displayed value chosen by the preference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames throughout the UI:
- Options page buttons: "Download Backup" → "Export Backup",
"Restore from File" → "Import Backup"
- Browse dropdown items: "Backup" → "Export Backup",
"Restore" → "Import Backup"
Import now opens a custom modal (built and styled from utils.js so it
works identically on both options and browse pages) showing the backup
file's contents (snapshot/export counts + creation date) and letting
the user choose:
- Merge (default): missing keys added from backup; for plain-object
values (UUID-keyed records like exportTimestamps and modelSnapshots),
sub-keys are merged with local winning on overlap. Scalar conflicts
(org ID, date format, etc.) keep the local value.
- Replace all: prior behavior — overwrite local data with the backup.
Modal supports Esc to cancel, Enter to import, and click-outside to
cancel. Z-index 100000 to clear the browse-page sticky header.
Function rename: restoreExtensionData → importBackup. mergeStorageData
helper added (exported for future tests). Toast wording adjusted to
match the new vocab ("Backup exported", "Import complete (merge)" /
"(replace)", "Import cancelled").
Bundled the in-progress button padding tweak (5px → 8px) and mirrored
it to firefox/options.html for parity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Model column previously displayed the original (first-seen) model since
v1.9.4. Flipped to show the **current** model so the cell reflects what
model the chat will actually use on reopen. Bounced chats (current
differs from first-seen) get a small "*" asterisk marker with an
"Originally X" tooltip — replaces the prior "→" arrow.
getDisplayModel now returns { model: current, original, bounced }.
Sorting follows the new display name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Merged Test Connection into the Organization ID section next to
Save Settings using a flex .button-row container. Dropped the
standalone "Test Your Settings" section.
- Fixed button text vertical centering with inline-flex + line-height: 1
(was inheriting body's 1.6 line-height, shifting the perceived
baseline relative to the smaller 5px symmetric padding).
- Date/Time format selects now sit side-by-side in a two-column grid
instead of stacked.
- Mirrored prior chrome/options.html CSS tweaks (800px width, button
padding, .section h3 margins) into firefox/options.html — Firefox
copy had fallen behind.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Popup error when org-ID detection fails now reads:
"Failed to obtain organization ID: Please set this value in Options."
"Options" is a clickable link that opens the options page (same DOM
pattern as the existing 403/404 hint).
Lowercased "Claude.ai" → "claude.ai" in all user-facing strings across
popup.js, browse.js, and options.js (chrome + firefox) to match the
actual domain. Comments and HTML titles left as-is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Org ID is auto-detected on every export action since v1.8.12, so the
upfront "Organization ID not configured" banner was dead weight. Dropped
the #setupNotice div, the load-time auto-detect-or-warn check, and the
options-page link handler. Updated the fallback export-button error
message to suggest opening the popup from a claude.ai tab instead of
pointing at the now-removed link. Manual override on the options page is
preserved as the fallback path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The markdown export was missing the truncated flag and per-message
attachment metadata (file_name, file_size, file_type) that the JSON
export already includes. File attachments now render as:
### Attachment: <name> _(size, type)_
followed by extracted_content in a quadruple-backtick block. Pasted
content (no file_name) keeps the existing ### Pasted label.
Adopted from upstream commit 318d4a7. Skipped the manifest rename to
"Local" since the project uses the "Beta" suffix convention on testing.
Reorganize repo root: the three doc files now live in docs/. README links
updated by user; CLAUDE.md path refs (Documentation Upkeep and Release
Process sections) updated to match. Rename detection preserves history.
Tidy the repo root: package.json moves to src/tests/ (still tracked) so the
Vitest harness lives next to the tests it runs. package-lock.json is now
untracked (matches CLAUDE.md's stated intent) -- gitignored at the new path.
node_modules/ moved alongside (still gitignored).
Run npm test from src/tests/ now (not src/). CLAUDE.md Testing section
updated.
- Date and Time format toggles moved out of the browse dropdown into a new
'Date & Time Format' section on the options page
- That dropdown slot now holds 'Backup/Restore Database' with a hover flyout
submenu (Backup / Restore)
- Backup/Restore core logic extracted to shared utils.js (backupExtensionData,
restoreExtensionData); options page and browse dropdown both call it
- Version bumped to 1.9.7
New divider-bounded 'Advanced Options' item between Time and Test connection;
opens the options page via chrome.runtime.openOptionsPage(), same pattern as
Edit Org ID. Makes Backup & Restore reachable from the browse view.
Download all chrome.storage.local + chrome.storage.sync data to a structured
JSON file ({_meta, local, sync}) and restore it later. Solves uninstall/
reinstall data loss and migration between separate extension builds (store
vs. GitHub) that have separate storage. Restore validates _meta.app and
confirms before overwriting.
getDisplayModel() resolves each conversation to its first-seen model from
the modelSnapshots map, falling back to the current/inferred model when no
snapshot exists. Bounced chats (original != current) get a → marker with an
'Originally X, now Y' tooltip. Model column sorting follows the displayed
model.
recordModelSnapshots() persists each conversation's current API model to
chrome.storage.local whenever the conversation list is fetched (browse page
load or popup Export All). Stores first-seen, current, and change history
per UUID — raw API model only, never an inferred guess. Mirrors the existing
exportTimestamps tracking pattern.