v2.13.0 — Obsidian Bridge MV3 Reliability Fix
Changelog — Glassy Companion
All notable changes to the Glassy browser extension are documented here.
Format follows Keep a Changelog.
[2.13.0] — 2026-07-22 — Obsidian Bridge MV3 Reliability Fix
Fixed — Membership Gate Removed from Popup
- Removed the
no_entitlement/ UpsellCard wall from the popup. The popup
previously checkedentitlements.glassy_keepon auth and showed an
"Unlock GlassyKeep — $15" upsell card if absent. This incorrectly blocked
self-hosted admins (whoseclear_lifetimetier is resolved server-side via
isClearMember(), not inentitlements_json) and created unwanted friction
for all users. The extension no longer does any client-side membership
gating — the server remains the single gatekeeper viarequireKeep. - Softened the background-save entitlement toast. A 403 from the server no
longer shows "Upgrade required / GlassyKeep is required" — it now shows
"Save unavailable — Saving from the extension requires a Pro plan. Manage
your items in your Glassy workspace."
Fixed — SSE Bridge Moved to Offscreen Document
- Critical fix: SSE EventSource moved from service worker to offscreen document.
Chrome MV3 evicts service workers after ~30s of inactivity, silently killing the
EventSourceholding the Obsidian Bridge SSE connection. The extension UI showed
"Bridge connected" (stalechrome.storage.localstatus) but the server registry
reportedconnected:false— Obsidian operations silently failed. The offscreen
document is persistent and never evicted by Chrome, so the SSE connection stays
alive indefinitely while the browser is open. chrome.runtime.onSuspendhandler added to the service worker — flips
bridge status toconnected:falsewhen the SW is evicted, preventing the
stale "connected" indicator. Belt-and-braces alongside the offscreen doc.connectSSEInServiceWorker()retained as legacy fallback for Firefox <120
which doesn't support the offscreen Documents API.
Fixed — Test Connection Now Tests the Full Bridge Loop
- Test Connection previously tested only extension→Obsidian direct fetch,
giving false confidence that the bridge worked. It now delegates to the
offscreen document which reports both the SSE bridge status AND the direct
Obsidian fetch result. The popup shows both legs: green "✓ Connected · plugin
v4.x" plus a warning if "SSE bridge to server is not connected."
Added — Settings Sync to Server
POST /api/ext/obsidian-bridge/settings— on SSEonopen, the extension
pushes its Obsidian URL to the server'susers.obsidian_urlcolumn. This
eliminates the dual-store drift where the web-app DB and extension storage
could hold different URLs. The Obsidian API token stays extension-side only
(never sent to the server — the extension is the canonical source for
bridge-routed requests).GET /api/ext/obsidian-bridge/statusnow includesserverObsidianUrl
for the extension's reference.
Changed — Save Triggers Reconnect
saveBridgeSettings()now triggers a bridge reconnect when the URL or
token is changed while the bridge is enabled (previously required a manual
toggle off/on).
[2.12.0] — 2026-07-08 — Unified Save Card & Obsidian Bridge
Added
- Obsidian Bridge — the extension now acts as a local proxy between the
Glassy server and the user's Obsidian instance. The server pushes proxy
requests via SSE; the extension calls Obsidian on127.0.0.1:27124and
returns the result. This solves the WSL2/Docker networking problem where
the container cannot reach Windows localhost. - Obsidian Bridge settings UI — new section in Settings with Obsidian URL
input, API key, test connection button, and connection status indicator. - Push-to-Obsidian — captures saved via the extension can now be pushed
directly to the Obsidian vault as markdown files with YAML frontmatter,
without a server round-trip. - Optional host permissions — declared
optional_host_permissionsfor
127.0.0.1:27123and127.0.0.1:27124so the Obsidian bridge can request
localhost access at runtime without widening the base permission set. - Unified
SaveCardcomponent — single capture card with progressive
disclosure, replacing the legacy two-screen Quick Save (BookmarkCard) +
Smart Save (SmartSavePanel) flow. The "⚙ Smart capture" toggle pill sits
high in the form (right after QuickActions, before the Save button) so the
most important affordance is one tap away instead of buried at the bottom.
Default collapsed state shows essentials (title, collection, tags, note,
QuickActions, Save); one tap expands preset type chips, public/pin toggles,
and content preview inline. No screen swap, no duplicated fields. Design
spec:docs/superpowers/specs/2026-07-08-unified-save-card-design.md.
Changed
- Server URL change flow — changing the Glassy server URL in Settings now
detects the change, saves the new URL, invalidates caches, reconnects the
Obsidian bridge SSE, and prompts re-authentication instead of silently
keeping an invalid JWT. - Logout stops the bridge — the
LOGOUThandler now calls
stopObsidianBridge()before clearing auth so the SSE connection is cleanly
closed instead of reconnecting with an expired token. - Service worker lifecycle — the bridge SSE is started on
onInstalled,
onStartup, and on SW wake. A 2-minute alarm reconnects the SSE if the
service worker was evicted. - SaveView simplification — removed the
modestate and the buried
"✨ Switch to Smart Save" button.SaveViewnow rendersSaveCarddirectly,
passingruleDefaultsasdefaults, pluspendingElement/
pendingScreenshotfor auto-expand on pending captures. vite.config.js—manualChunks.ui-componentsnow includesSaveCard
instead of the deletedBookmarkCard+SmartSavePanel. Resulting chunk
size: 62.94 KB (gzip 18.77 KB) — under the 200 KB Chrome Web Store limit.- Draft persistence extension —
glassy_bookmark_draftnow stores
contentType,isPublic,isPinned,aiAutoTag, andsmartExpanded
alongside the legacy fields, so the user's smart-capture state survives a
popup reopen. Legacy fields remain identical → existing drafts restore
seamlessly.
Fixed
- Server URL change left bridge connected to old server — the SSE endpoint
URL was computed fromgetBaseUrl()at connection time but never
re-established when the server URL changed. NowreconnectBridge()stops
and restarts the SSE against the new server. - "Switch to Smart Save" buried at the bottom — the affordance to access
Smart Save was below the Save button, QuickActions, and the collapsed note,
requiring the user to scroll past everything to find the most important
secondary action. Replaced with the always-visible "⚙ Smart capture" toggle
pill placed high in the form.
Removed
src/popup/components/BookmarkCard.jsx— logic merged intoSaveCard.jsx.src/popup/components/SmartSavePanel.jsx— logic merged intoSaveCard.jsx.src/popup/components/__tests__/BookmarkCard.test.jsx— replaced by
SaveCard.test.jsx(preserves all 6 original draft-contract tests + adds a
smart-capture-state test; 7 tests total).
Preserved smartness (verified)
The unified SaveCard preserves every piece of smartness from the legacy
components: draft persistence with stale-draft discard, capture-rule
pre-population via ruleDefaults, pending element + pending/live screenshot
pre-load with auto-expand, deferred screenshot upload with 3-attempt backoff
(never on mount), content-type re-fetch on preset change (GET_PAGE_META →
structuredData), structured-data passthrough for type-aware reader
rendering, tag autocomplete, collection picker, AI auto-tag toggle,
multi-account routing, already-saved badge, unsavable URL guard, SaveToast,
content preview, and save-all-tabs.
[2.11.2] — 2026-07-07 — Self-Hosted CSP & Local Network Support
Fixed
- Extension cannot reach an
httpself-host —manifest.jsonand
manifest.firefox.jsoncontent_security_policy.connect-srcnow allows
http://localhost:*,http://127.0.0.1:*,http://[::1]:*,
private-LAN ranges (192.168.*.*,10.*.*,172.16.*.*), and
Tailscale (100.*.*,*.ts.net). Without this, self-hosters running
the appliance onhttp://localhost:3000or a Tailscale IP hit a CSP
rejection at the manifest layer before any request fires. - HTTPS-only client validation loosened for self-host —
src/lib/auth.js
andsrc/lib/api.jsvalidateServerUrl()now accept the same allowlist
(http://localhost(:port)already worked; private-LAN and Tailscale ranges
added).https://*remains the preferred default; the warning fires only
forhttp://against a non-loopback, non-private-LAN host. BUILD_DOMAINplaceholder swap —LoginCard.jsxand
SettingsView.jsxnow use a build-time__GLASSY_BUILD_DOMAIN__populated
by Vitedefine()instead of the hardcodedhttps://glassy.fyi. No more
CSP/wording drift on packaged builds. Configurable via
GLASSY_BUILD_DOMAINenv var.
Changed
manifest.json+manifest.firefox.json—connect-srcwidened with the
allowlist above;https://*retained.src/lib/auth.js+src/lib/api.js—validateServerUrl()private-range
detector covers all IPv4 RFC1918 ranges + Tailscale CGNAT range +*.ts.net.vite.config.js—define()injects__GLASSY_BUILD_DOMAIN__; resolves to
process.env.GLASSY_BUILD_DOMAINfirst, falls back tohttps://glassy.fyi.
[2.12.0] — 2026-07-08 — Unified Save Card & Obsidian Bridge
Added
- Obsidian Bridge — the extension now acts as a local proxy between the
Glassy server and the user's Obsidian instance. The server pushes proxy
requests via SSE; the extension calls Obsidian on127.0.0.1:27124and
returns the result. This solves the WSL2/Docker networking problem where
the container cannot reach Windows localhost. - Obsidian Bridge settings UI — new section in Settings with Obsidian URL
input, API key, test connection button, and connection status indicator. - Push-to-Obsidian — captures saved via the extension can now be pushed
directly to the Obsidian vault as markdown files with YAML frontmatter,
without a server round-trip. - Optional host permissions — declared
optional_host_permissionsfor
127.0.0.1:27123and127.0.0.1:27124so the Obsidian bridge can request
localhost access at runtime without widening the base permission set. - Unified
SaveCardcomponent — single capture card with progressive
disclosure, replacing the legacy two-screen Quick Save (BookmarkCard) +
Smart Save (SmartSavePanel) flow. The "⚙ Smart capture" toggle pill sits
high in the form (right after QuickActions, before the Save button) so the
most important affordance is one tap away instead of buried at the bottom.
Default collapsed state shows essentials (title, collection, tags, note,
QuickActions, Save); one tap expands preset type chips, public/pin toggles,
and content preview inline. No screen swap, no duplicated fields. Design
spec:docs/superpowers/specs/2026-07-08-unified-save-card-design.md.
Changed
- Server URL change flow — changing the Glassy server URL in Settings now
detects the change, saves the new URL, invalidates caches, reconnects the
Obsidian bridge SSE, and prompts re-authentication instead of silently
keeping an invalid JWT. - Logout stops the bridge — the
LOGOUThandler now calls
stopObsidianBridge()before clearing auth so the SSE connection is cleanly
closed instead of reconnecting with an expired token. - Service worker lifecycle — the bridge SSE is started on
onInstalled,
onStartup, and on SW wake. A 2-minute alarm reconnects the SSE if the
service worker was evicted. - SaveView simplification — removed the
modestate and the buried
"✨ Switch to Smart Save" button.SaveViewnow rendersSaveCarddirectly,
passingruleDefaultsasdefaults, pluspendingElement/
pendingScreenshotfor auto-expand on pending captures. vite.config.js—manualChunks.ui-componentsnow includesSaveCard
instead of the deletedBookmarkCard+SmartSavePanel. Resulting chunk
size: 62.94 KB (gzip 18.77 KB) — under the 200 KB Chrome Web Store limit.- Draft persistence extension —
glassy_bookmark_draftnow stores
contentType,isPublic,isPinned,aiAutoTag, andsmartExpanded
alongside the legacy fields, so the user's smart-capture state survives a
popup reopen. Legacy fields remain identical → existing drafts restore
seamlessly.
Fixed
- Server URL change left bridge connected to old server — the SSE endpoint
URL was computed fromgetBaseUrl()at connection time but never
re-established when the server URL changed. NowreconnectBridge()stops
and restarts the SSE against the new server. - "Switch to Smart Save" buried at the bottom — the affordance to access
Smart Save was below the Save button, QuickActions, and the collapsed note,
requiring the user to scroll past everything to find the most important
secondary action. Replaced with the always-visible "⚙ Smart capture" toggle
pill placed high in the form.
Removed
src/popup/components/BookmarkCard.jsx— logic merged intoSaveCard.jsx.src/popup/components/SmartSavePanel.jsx— logic merged intoSaveCard.jsx.src/popup/components/__tests__/BookmarkCard.test.jsx— replaced by
SaveCard.test.jsx(preserves all 6 original draft-contract tests + adds a
smart-capture-state test; 7 tests total).
Preserved smartness (verified)
The unified SaveCard preserves every piece of smartness from the legacy
components: draft persistence with stale-draft discard, capture-rule
pre-population via ruleDefaults, pending element + pending/live screenshot
pre-load with auto-expand, deferred screenshot upload with 3-attempt backoff
(never on mount), content-type re-fetch on preset change (GET_PAGE_META →
structuredData), structured-data passthrough for type-aware reader
rendering, tag autocomplete, collection picker, AI auto-tag toggle,
multi-account routing, already-saved badge, unsavable URL guard, SaveToast,
content preview, and save-all-tabs.
[2.11.1] — 2026-06-15 — Draft Stale-Data Fix
Fixed
- BookmarkCard preview showing previous page —
chrome.storage.localdraft
restoration was racingpageMetaarrival. The draft callback fired after the
pageMeta?.titleeffect, overwriting the current page title with the previous
page's draft. Now bothBookmarkCardandNoteViewstore the pageurlin
the draft and validate it on restore; mismatched drafts are discarded instead
of restored. Effects now depend onpageMeta?.urlso they only run once the
active tab context is known.
Changed
BookmarkCard.jsx—BOOKMARK_DRAFT_KEYnow includesurl; restore effect
depends on[pageMeta?.url]and removes stale drafts.NoteView.jsx—DRAFT_KEYnow includesurl; restore effect split from
textarea focus and depends on[pageMeta?.url].BookmarkCard.test.jsx— addedurlfield to draft contract tests and a
stale-draft discard scenario.
[2.11.0] — 2026-06-13 — Store Readiness & Reliability Hardening
Added
- Firefox CSP —
content_security_policyadded tomanifest.firefox.json
(matching Chrome manifest, withoutwasm-unsafe-eval). Required for AMO
submission. - Storage quota monitoring —
STORAGE_QUOTA_ALARMruns every 6 hours via
chrome.alarms. The alarm listener dispatches tocheckStorageQuota()
which warns at 80% usage and auto-trims the offline queue at 95% critical.
Useschrome.storage.local.getBytesInUse()(Firefox logs a warning and
exits cleanly —getBytesInUseis unsupported on Firefox MV3).
Notes
- The
ErrorBoundarychunk is 186.84 KB (largest of 19 chunks) — under
the Chrome Web Store 200 KB limit (gzipped: ~59 KB). Vite'schunkSizeWarningLimit
fires at 200 KB raw, so the build no longer warns for this file.
Seedocs/bundle-sizes.mdfor the full measurement.
Changed
- Bundle size optimization —
vite.config.jsnow usesmanualChunksto
split vendor libraries (vendor-react,vendor-state) and UI components
(ui-components,kb-view). All chunks under 200KB for store compliance.
chunkSizeWarningLimitset to 200KB.
[2.10.0] — 2026-06-12 — Knowledge Base Search
Added
- KB tab and search view — new "KB 🧠" tab in the popup for searching the
Glassy Knowledge Base (bookmarks, notes, vault files). Debounced search
(400 ms), source type filter tabs (All / Bookmarks / Notes / Vault), corpus
indexing status banner, result items with source badges, snippets, and
relevance scores. KbSearchView.jsx— full search UI usingsearchKnowledgeBase()and
getKbStatus()from the API layer.AppShell.jsx— added{ id: 'kb', label: 'KB', icon: '🧠' }tab.Popup.jsx— addedKbSearchViewimport andview === 'kb'render block.useAppState.js— added#/kbhash route andglassy_open_viewsupport.
[2.9.0] — 2026-06-09 — Structured Capture & Reliable Execution
Foundational refactor that makes Save Page and Screenshot the two perfect,
first-class capture tools. Removes fragile supplementary buttons. Wires up
structured enrichment end-to-end so YouTube, GitHub and article captures carry
type-specific metadata into the reader.
Changed
- Main bar simplified to two actions — Save Page and Screenshot. Region,
Element Picker, and AI Summary removed from the popup bar. AI Summary
remains available as a post-save action in Keep. Region and Element remain
in code for future in-reader affordances. - Screenshot routes directly through the service worker —
captureVisibleTab
no longer requires a content-script relay. Works on restricted URLs, PDFs,
and stale tabs where the content script is absent. - Four first-class content types —
article(absorbsresearch),video,
repo,bookmark(fallback). Presets collapsed from 9 to 4. - Smart Save chips re-run the page interpreter on type change, so switching
frombookmarktovideorefreshes structured metadata before save.
Added
ensureContentScript(tabId, tabUrl)in the service worker — programmatic
chrome.scripting.executeScriptfallback when the static content-script
injection is missing (stale tabs, post-update tabs). Eliminates the
"Could not establish connection" failure class forSAVE_CAPTUREand
GET_PAGE_META.- Structured capture pipeline —
extractor.jsbuilds a typedstructuredData
object per content type (video:videoId,provider,channelName,duration;
repo:owner,repo,stars,language,license,topics; article:
abstract,doi,language).capturePipeline.jspreserves it through to
the server payload. - YouTube interpreter now extracts
videoIdreliably via regex (handles all
URL variants including shorts and query-param watch URLs), and always sets
provider: 'youtube'. - GitHub interpreter now extracts
owner,repo, andtopics[]from both
Schema.org and fallback DOM scraping.
Fixed
- Interpreter-enriched fields (
videoId,stars, owner/repo, etc.) were silently
dropped bybuildCaptureItem— root cause was selective field copy in
extractor.js. Fixed bybuildStructuredData()typed packaging. - Screenshot button was non-functional on restricted pages due to content-script
relay. Fixed by direct SW routing. - Smart Save content-type chips had no effect on actual scraping behavior —
saveModefield in presets was dead code. Fixed byhandleContentTypeChange
re-runningGET_PAGE_META.
[2.8.0] — 2026-06-07 — Capture Quality & Screenshot UX
Eliminates junk captures on app/SPA pages and makes screenshots a first-class
one-click workflow instead of a two-popup dance.
Fixed
- Save Page quality gate —
getStructuredContent()now scores candidate
containers by text density (rejecting navigation-heavy / link-dense sections)
and returns an empty string when the page yields fewer than 200 meaningful
characters. On SPA dashboards and app pages this means the capture falls through
to a clean bookmark instead of creating a note containing a lone decorative image
or code block. Previously, saving the Glassy dashboard itself produced a note
whose body was. - Decorative image filtering —
nodeToMarkdownnow skips images that carry
role="presentation",role="none",aria-hidden="true", or explicit
sub-pixel dimensions (tracking pixels ≤ 2 px). Content images are unaffected. - Improved content container scoring —
findMainContent()now evaluates
ARIA landmarks ([role="main"],[role="article"]), link density, and an
extended set of semantic class/id selectors (post-content,entry-content,
article-body, etc.) before falling back todocument.body. - Thin-content guard in service worker —
SAVE_CAPTUREnow only uses
extracted content when the markdown is > 50 chars, preventing a subsequent
assemblePremiumMarkdowncall from embedding the empty quality-gate result.
Changed
- Screenshot → immediate SmartSavePanel — clicking the Screenshot (📸) button
now switches the popup straight to Smart Save with the captured image
pre-loaded, instead of storing it silently and requiring the user to re-open
the popup. The old storage-based flow is retained as a fallback if the callback
is absent (e.g. headless tests). - NoteCard lone-image guard — notes whose content resolves to only an
<img>
tag with no surrounding text now show an empty preview on the card rather than
a dangling picture with no context. The full image is still visible on note open. - Clearer tooltips — Save Page tooltip notes it extracts readable text
(best on articles); Screenshot tooltip notes it opens Smart Save for review.
Added (Tests)
GET_STRUCTURED_CONTENTtests: semantic article extraction, SPA quality-gate
(empty return), high-link-density rejection,[role="main"]preference.decorative image filteringtests in formatter:role="presentation/none",
aria-hidden, 1×1 tracking pixel, 2 px boundary, content-image passthrough.
[2.7.0] — 2026-06-07 — Multi-Account Capture & Frictionless Server Setup
Fixes the "where did my save go?" problem for users who work across multiple
account profiles, and removes the login/logout dance previously required to
point the extension at a different Glassy server.
Added
- Account picker — new
src/popup/components/AccountPicker.jsx. Multi-account
users can now choose which account a capture is written to. The selection is
sent to the server as theX-Account-Idheader. Previously the extension was
pinned to the primary account, so captures made while working in another
profile "vanished" into the wrong account.- Full variant in Settings (
SettingsView) with the caption "New saves
from the extension go here." - Compact variant at the top of the Save view, visible while a save is
pending so the destination is confirmed before committing.
- Full variant in Settings (
- Active-account persistence —
SET_ACTIVE_ACCOUNTservice-worker message
(wired throughsetActiveAccount()inuseExtensionBridge) stores the chosen
account and clears account-scoped caches so the next read reflects the switch. - Destination in confirmations — background/context-menu save toasts now read
"Saved to {account} ✓" when more than one account exists. - Pre-login server selection —
LoginCardnow exposes a "Server" control
before authentication. Users point the extension at their server (e.g. a
self-hosted instance) up front instead of logging into the default server,
changing the URL, and logging in again. - Unsavable-URL guard — new
isUnsavableUrl()insrc/lib/urlUtils.js
mirrors the server's SSRF rules (loopback, private IP ranges, metadata hosts,
non-http(s) schemes). The Save view now shows a friendly warning for pages
that can't be saved (e.g.chrome://,localhost) instead of letting the
user hit an opaque server error.
Changed
- Account-aware cache invalidation —
invalidateAccountScopedCaches()drops
both the collections and tags caches; switching accounts also clears the
saved-URL checkmark cache so badges reflect the new account. - Duplicate messaging — the 409 "already saved" toast now hints that the page
exists in the selected account and that switching accounts allows saving it
elsewhere.
[2.6.0] — 2026-06-06 — Capture-to-Application Premium Experience
A major uplift for how captured artifacts reach the application. Screenshots,
element captures, and articles now render as rich, first-class objects with
native image galleries, offline-durable reader content, and content-type
visual identity.
Added
- Region screenshot capture — new drag-to-select overlay in
src/content/regionPicker.js. User drags a rectangle on the page; the
service worker captures the visible viewport and delegates cropping to the
offscreen document viaOFFSCREEN_CROP_IMAGE. Returns a cropped PNG stored
as a pending screenshot for the popup. - Structured image manifest (
images[]) on captures —CaptureItemtype
extended withScreenshotMetaandCaptureImagetypedefs. Screenshot and
element captures now sendimages[]alongside Markdown so the app can render
a native hero + lightbox instead of thumbnailing them to 80px. - Element picker image harvesting —
elementPicker.jsnow collects all
<img>URLs from the selected element intoimages[]and adds a
"Clipped from {site}" attribution header to the Markdown.
Changed
- SmartSavePanel populates
payload.imagesfor bothscreenshotand
highlight(element) content types so the server can store them in
images_json. - QuickActions gained a "Region" button between Screenshot and Element.
[2.5.0] — 2026-06-01 — Reliability & Capture Hardening
A reliability-focused release: no captured data is silently lost, content-script
failures are now observable, the offline queue scales cleanly, and screenshot
upload is deferred and instance-aware.
Added
- Content-script error telemetry —
extractor.jsnow reports handler
failures viareportContentError()and arespondSync()wrapper, relaying a
CONTENT_SCRIPT_ERRORmessage to a new sink in the service worker instead of
silently swallowing the error.GET_PAGE_METAgained a missing.catch()so
the popup no longer hangs on extraction failure.
Fixed
- Invalid
offscreenkey in manifest.json (2026-06-02 post-release fix).
Chrome MV3 rejects unknownmanifest.jsonkeys. The block
"offscreen": { "reason": ["WORKERS"], ... }was declared in the manifest
even though offscreen documents must be created programmatically via
chrome.offscreen.createDocument(). Removed frommanifest.json; the service
worker already handled runtime creation correctly. Fixed, rebuilt, and
re-uploaded to the v2.5.0 release. CRXJS 2.4.0 has no code path that
re-injects this key. - Failed online saves were never queued (silent data loss).
offscreen.js
calledplanBackgroundSaveFailurewithout importing it, throwing a
ReferenceErroron every online-save failure — so a capture that failed on a
flaky network was dropped instead of queued for retry. Added the missing
import. - Screenshot uploads orphaned images on cancel.
SmartSavePaneluploaded
the screenshot on panel mount, so opening then cancelling left an unreferenced
image on the server. Upload is now deferred to save time with a bounded
3-attempt backoff and inline error surfacing. - Embedded screenshots pointed at a hardcoded host.
uploadCaptureImage()
now resolves the server's host-relative path against the configured base
URL, so screenshots embed the user's actual instance (glassy.fyi, self-hosted,
or dev) rather thanhttps://glassy.fyi. - Premium markdown could double its header.
assemblePremiumMarkdown()is
now idempotent (skips re-prepending an already-assembled header) and strips a
duplicate leading H1 from page-extracted content; adds Canonical/Published
metadata lines. BookmarkCardcould throw inside a storage callback when the extension
context was invalidated — now guardschrome.runtime.lastError.
Performance
- Offline-queue flush is now O(n) instead of O(n²). The alarm flush called
dequeue/incrementAttemptsper item (a full storage read+write each), and
the offscreen flusher double-mutated the queue. A new
applyFlushOutcomes({remove, increment})applies all outcomes in a single
read-modify-write (re-reading at apply time so items enqueued during the
flush survive); the offscreen flusher is now pure and the service worker is
the single queue-mutation owner.
Verification
npx vitest run→ 143 passed (12 test files; +8 over 2.4.0)npm run build→ ✓ Chrome artifact (dist/)
[2.4.0] — 2026-05-30 — Screenshot Upload Pipeline, Popup Crash Fix
Added
- Screenshot upload pipeline — Screenshots captured via QuickActions now automatically upload to the server (base64 → WebP) and are embedded as
in the save markdown. The SmartSavePanel shows upload progress and auto-populates title/content. uploadCaptureImage()— New API client function forPOST /api/ext/capture-image.captureImageconstant — API path for the new screenshot endpoint.
Fixed
- Popup crash on save completion —
SaveView.jsxreferenced undefined variabletoastType(ReferenceError). Changed tosaveStatusprop, which is the actual value the SaveToast component expects. - AI summarize endpoint always returning 503 —
aiService.jscalledrouter_ai.generateContent()which does not exist on theProviderRouterclass. Changed toexecuteTask('text-generation', ...).
Verification
npm test→ 129 passed (11 test files, same as 2.3.2)npm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)npm run zip→glassy-companion-v2.4.0.zip(540 KB, 23 files)npm run zip:firefox→glassy-companion-v2.4.0-firefox.xpi(540 KB, 23 files)
[2.3.2] — 2026-05-29
Added
- Content Security Policy — Added
content_security_policytomanifest.jsonfor stricter execution isolation.
Changed
- MV3 Offscreen Document (refined) — The offscreen document architecture (introduced in v2.3.1-dev) is now the exclusive save path for Chrome. The service worker delegates all capture processing to a persistent offscreen page, avoiding the Chrome MV3 30-second service worker kill window. Firefox falls back to in-service-worker processing automatically.
- Shared Module Refactoring —
src/lib/capturePipeline.jsandsrc/lib/urlUtils.jsextracted as shared modules between service worker and offscreen document paths. - Code cleanup — Inline
getHostname(),sameDocumentUrl(), andassemblePremiumMarkdown()removed from service worker; use shared modules instead.
Fixed
- Offscreen doc test mocks — Service worker tests now mock
chrome.offscreen,premiumMarkdown, andurlUtilsfor proper CI coverage. - Extension download flow — Firefox users now download the correct v2.3.2 XPI via the pinned version link inside the app.
Verification
npm test→ 129 passed (11 test files)npm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)web-ext lint --source-dir=dist-firefox --self-hosted→ 0 errors, 8 warnings (same pre-existing set as v2.3.0)
[2.3.1] — 2026-05-27
Architecture
- MV3 Offscreen Document — Chrome MV3 kills service workers after ~30s of inactivity. A hidden persistent offscreen document (
src/offscreen/) now handles all heavy capture processing: content-script metadata extraction, Markdown assembly, and API calls. The service worker acts as a pure message broker, staying safely within the kill window. - Firefox Fallback Path — Browsers without the
chrome.offscreenAPI (Firefox MV2, Safari) fall back to in-service-worker processing viaprocessCaptureInServiceWorker(). One codebase, two execution paths. - Shared Module Refactoring —
src/lib/capturePipeline.js(buildCaptureItem) andsrc/lib/urlUtils.js(getHostname,sameDocumentUrl) extracted to eliminate duplicate logic between the service worker and offscreen document. Both paths now produce identical capture items. - Content Security Policy — Added
content_security_policytomanifest.jsonfor stricter execution isolation.
Fixed
- P0: Screenshot Double-Wrapping —
GET_SCREENSHOTresponse no longer double-wrapsdataUrl; screenshots flow directly to the popup. - P0: Element Picker State Lost — Selected element/screenshot state now persisted in Zustand with
persistmiddleware; survives tab switch and popup close. - P0: Side Panel Bricked — Removed
chrome.sidePanel.setOptions({ enabled: false })call that globally disabled the side panel after every save. - P0: QuickActions Legacy Path — "Save Page" in QuickActions now routes through the unified
SAVE_CAPTUREpipeline instead of the legacySAVE_PAGEflow. - Cross-Tab Metadata Leakage — Context-menu link saves now guard against extracting metadata from the wrong tab via
sameDocumentUrl(). Only fetches metadata whensourceUrlmatches the active tab.
Changed
- Alarm Handler Deduplication — Offline queue flush response branches collapsed from four to two.
saveHighlightFromContextUnified — Now delegates capture to the offscreen document viadelegateCapture(), consistent with all other save paths.flushQueueItemPlanner Fix — Corrected to callplanQueueFailure(which returns{ action }) instead ofplanBackgroundSaveFailure(which has no.action).
Verification
npm test→ 129 passed (11 test files)npm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)web-ext lint --source-dir=dist-firefox --self-hosted→ 0 errors, 8 warnings (same pre-existing set as v2.3.0)
[2.3.0] — 2026-05-22
Added
- Visual Element Picker — Click the 🎯 "Element" button in QuickActions to enter element-selection mode on the page. Hovering highlights page elements with a purple glow; clicking captures the element as rich Markdown. Press Escape to cancel.
- Screenshot Capture — 📸 "Screenshot" button in QuickActions captures the visible viewport as a PNG image and attaches it to the capture.
- Site-Specific Interpreters — YouTube (video metadata from Schema.org), GitHub (repo stars, language, license, description), product pages (price, brand, rating), and scholarly articles (abstract, DOI) are now automatically detected and enriched with structured metadata.
- Side Panel Mode (Chrome only) — Press
Ctrl+Shift+Por use the right-click context menu to open Glassy as a persistent side panel that stays open while browsing. Falls back to the popup on Firefox. - Content Preview — "Preview Content" button in Smart Save renders the extracted page Markdown as rich formatted HTML with word count and reading time estimates. Toggle between Rendered and Raw (editable) modes before saving.
- Duplicate Pre-Flight Check — Pages already saved to Glassy show a green "Already saved" banner on popup open, with the option to save again (update).
- Undo Save — After saving, an "Undo" button appears in the success toast for 8 seconds, allowing instant deletion of the last capture.
- Tag Intelligence — Local tag frequency tracking ranks autocomplete suggestions by usage count. Keyword extraction fallback for AI auto-tag when server-side inference is unavailable.
- Skeleton Loading UI — Shimmer placeholders replace the spinner during data loading.
- Accessibility Styles — Focus indicators, skip-link,
prefers-reduced-motion,prefers-contrast, and screen-reader-only utilities. - Screenshot Preset — 📸 content type available in Smart Save preset grid.
Changed
extractPageMeta()is now async to support site-specific interpreters.- BookmarkCard and SmartSavePanel are more keyboard-navigable with visible focus rings.
- QuickActions layout expanded from 2 columns to 4 (Save Page, Screenshot, Element, AI Summary).
Verification
npm test→ 129 passed (11 test files)npm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)web-ext lint --source-dir=dist-firefox --self-hosted→ 0 errors, 8 warnings (3 pre-existing React innerHTML, 4 sidePanel API not in Firefox — expected, 1 data_collection_permissions advisory)
[2.2.2] — 2026-05-07
Fixed
- "Have to log in every time" — the JWT token and active-account selection now live in
chrome.storage.localinstead ofchrome.storage.session. Chrome's session storage is wiped on every browser restart, which forced users to re-authenticate on every cold start; with this change the session persists until the JWT's ownexpclaim elapses or the user explicitly signs out. A one-shot migration ingetToken()promotes any legacy session-stored token to local storage so existing users aren't logged out by the upgrade. Both stores are still cleared onclearAuth()for belt-and-braces hygiene.
Changed
- Login screen polish — removed the redundant in-card brand badge (the header chip already shows the Glassy lockup), tightened the heading copy, increased form spacing, and added a "You'll stay signed in on this device." subtext under the Sign in button so the persistence behaviour is visible to users.
Verification
npm test→ 129 passed, including newauth.test.jscases asserting local-storage persistence and the legacy-token migration.npm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)
[2.2.1] — 2026-05-05
Fixed
- 429 retry handling — API calls now retry once after HTTP 429 responses, honoring
Retry-Aftervalues expressed as seconds or HTTP dates and capping the wait at 10 seconds. This keeps background saves and popup actions aligned with the server's rate-limit backoff contract instead of failing immediately during short bursts.
Release
- Chrome and Firefox package metadata now report v2.2.1, including
manifest.json,manifest.firefox.json,package.json, andpackage-lock.json.
Verification
npm test→ 128 passednpm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)npm run zip→glassy-companion-v2.2.1.zip(271 KB)npm run zip:firefox→glassy-companion-v2.2.1-firefox.xpi(271 KB)web-ext lint --source-dir=dist-firefox --self-hosted→ 0 errors, 3 warnings
[2.2.0] — 2026-05-05
Added
- Firefox support —
manifest.firefox.jsonaddsbrowser_specific_settings.gecko(id: companion@glassy.fyi,strict_min_version: 121.0) and switches to thebackground.scriptsarray form required by CRXJS's Firefox path. No source-code changes were needed: everychrome.*API used is available and promise-returning in Firefox MV3.vite.config.jsselects the Firefox manifest when--mode firefox, emits todist-firefox/.web-ext lintreports 0 errors against the built artifact. - Capture rules pre-population —
useAppStatenow fetches/api/capture-ruleson popup open and evaluates them against the active page URL. If any rule matches, itscontentType,projectId,tags, andpublicCandidateare passed as defaults toSmartSavePanel, automatically seeding the form before the user interacts with it. - AI auto-tag toggle in SmartSavePanel — a new checkbox ("AI auto-tag") lets users enable or disable Glassy's server-side tag inference per-save. Defaults to
trueand is included in the capture payload asaiAutoTag. - Highlight context menu item — a "Highlight selection in Glassy" entry now appears in the right-click context menu for any text selection. Triggering it ensures the page is saved (or finds the existing capture on 409), then calls
POST /api/ext/bookmarks/:id/highlightswith the selected text. - Richer Markdown formatter —
src/content/formatter.jshandles tables (with header detection + column padding), fenced code blocks with language auto-detection,<figure>/<figcaption>/<picture>, GitHub-style task-list checkboxes,<mark>(==…==),<kbd>,<del>/<s>/<strike>(~~),<sup>,<sub>,<h5>/<h6>, and ordered lists that honour thestartattribute. - Comprehensive error UX in QuickActions — the AI summary action now surfaces three distinct error messages ("Can't read this page (try a regular http(s) page).", "No summary returned.", and the raw error message for unexpected failures) with a dismissible error toast.
Changed
- Route unification: quick and smart saves both use
SAVE_CAPTURE—SaveView.handleSavenow always dispatchesSAVE_CAPTUREwhen the payload has acaptureMode, routing through the canonicalPOST /api/capturessurface. The legacySAVE_BOOKMARKdispatch is retained only for bare bookmark payloads without a captureMode. BookmarkCardemits the full canonicalCaptureItemshape — quick saves now sendsourceUrl,canonicalUrl,coverImageUrl,favicon_url,siteName,author,publishedAt,contentType,captureMode: 'quick',status: 'inbox',aiAutoTag, andvisibleTags/systemTagsinstead of the legacy bookmark-only fields.backgroundSavefailure path is fully branched — the service worker now handlesauth(queue + "Sign in again" notification),duplicate(silent),entitlement("Upgrade required"),gone("Account unavailable"), and a generic fallback with the raw error message. Previously all failures produced a single generic notification.ApiErrorcarries the parsed response body —ApiErrornow accepts abodyparameter and stores it asthis.body.apiFetchparses the error body once and passes it through, allowing callers (e.g.saveHighlightFromContext) to read the existingidout of a 409 response without a second fetch.- Badge skipped on duplicate saves —
saveCaptureFromPopup(andsaveBookmarkFromPopup) now gateupdateBadge(1)on!result?.duplicate, so seeing the "Already Saved" toast no longer increments the badge. savedUrlCachecleared on LOGOUT — prevents a re-login on a different account from showing the previous user's saved-state checkmarks on tab badges.
Fixed
- Duplicate
formatMarkdownexport removed — the formatter rewrite accidentally left two identicalexport function formatMarkdownblocks; the duplicate was removed, resolving theSyntaxError: Identifier 'formatMarkdown' has already been declaredtest failure. SaveViewsurfaces duplicate flag from server — whensaveCaptureFromPopupreturns{data: {duplicate: true}}the popup now callssetDuplicate()so the "Already Saved" toast is shown correctly.
Tests
- 10 new formatter unit tests covering table rendering, code-fence language tags, figure/figcaption, task-list checkboxes, mark/kbd/del/sup/sub, and h5/h6.
assemblePremiumMarkdownshape test — verifies author, source link, and personal-note sections are rendered in the SAVE_CAPTURE contentMarkdown.- SAVE_CAPTURE 409 → duplicate — asserts that the result carries
{data: {duplicate: true}}and that the badge update is skipped. - Rules engine additions — tag-merging across multiple matching rules (dedup),
publicCandidatepropagation, and path-only rule matching.
Verification
npm test -- --run→ 128 passed (0 failed, 0 skipped)npm run build→ ✓ Chrome artifact (dist/)npm run build:firefox→ ✓ Firefox artifact (dist-firefox/)npm run zip→glassy-companion-v2.2.0.zip(271 KB)npm run zip:firefox→glassy-companion-v2.2.0-firefox.xpi(271 KB)web-ext lint --source-dir=dist-firefox --self-hosted→ 0 errors, 2 React-internalinnerHTMLwarnings, 1 notice
[2.1.0] — 2026-05-03
Added
- Canonical capture integration — the extension now treats structured saves as first-class
CaptureItempayloads against the dashboard's canonical capture surface:POST /api/captures,GET /api/capture-rules,PATCH /api/items/:id, andPOST /api/items/:id/promote. - Routing regression coverage — added tests for context-menu link saves and offline page replay to lock in the new capture/document behavior.
- Formatter + rule engine coverage — new tests cover absolute URL formatting in markdown output and stricter domain/path rule evaluation.
Changed
- Release metadata aligned to 2.1.0 —
manifest.json,package.json, docs, and release-facing UI now report the same version. - Rule evaluation is stricter and safer — domain rules now match exact domains/subdomains instead of lookalike suffixes, path rules combine correctly with domain rules, and invalid URLs return an empty routing result instead of throwing.
- HTML formatting resolves live URLs — markdown conversion now uses resolved
node.href/node.srcvalues so relative links and images are preserved as absolute URLs in captures.
Fixed
- Queued page/document saves now replay through
saveDocument()— offline page saves no longer fall through the queue flusher as notes. - Link context-menu saves no longer scrape the surrounding page — background capture now checks whether the target URL matches the active tab before requesting page metadata/content, preventing link saves from inheriting the wrong title or body.
Verification
npm test -- --run→ 114 passednpm run build→ success
[2.0.5] — 2026-04-28
Added
- Offline queue size cap (
MAX_QUEUE_SIZE = 200) —src/lib/offlineQueue.jsnow refuses new enqueue requests once the queue holds 200 items, throwing a typedQueueFullError(codeQUEUE_FULL). Background save handler insrc/background/service-worker.jscatches this and surfaces a "Glassy — Queue Full" notification instead of silently failing or eventually trippingchrome.storage.localquota. Older queued items are preserved; new saves are dropped with user feedback. - Response body size guard (
MAX_RESPONSE_BYTES = 5 MiB) —apiFetchinsrc/lib/api.jsnow reads response bodies viatext()first, bounds them at 5 MiB, and throwsApiError(413, 'Response too large.')if exceeded. Protects the popup process from a rogue/oversized API response ballooning extension memory. Falls back tores.json()for callers/mocks that only implementjson().
Fixed
activeAccountIdrace on JWT expiry —apiFetchpreviously rangetToken()andgetApiContext()in parallel viaPromise.all.getToken()may callclearAuth()when it detects an expired JWT, which removesactiveAccountIdfrom session storage; the parallel read could therefore see a staleactiveAccountIdand send the next request with the wrong account header. Sequenced the calls sogetToken()completes (and anyclearAuth()runs) beforegetApiContext()reads the active account.
Removed
build:firefoxscript — removed in v2.0.5; fully re-added in v2.2.0 with Firefox manifest, CRXJSbrowser: 'firefox'config, and separatedist-firefox/output.
[2.0.4] — 2026-04-26
Fixed
- Saves no longer retry forever for deleted accounts (P1-11) —
classifySaveErrorinsrc/background/savePolicy.jsnow treats HTTP410 Gone(ACCOUNT_INACTIVITY_DELETED) as a terminalgoneoutcome.planBackgroundSaveFailurereturns{ queue: false }andplanQueueFailurereturns{ action: 'drop' }for this kind, so a queued save against a server-side-deleted account is dropped instead of retried indefinitely. activeAccountIdis session-scoped (P2-15) —getActiveAccountId/setActiveAccountId/getApiContextinsrc/lib/auth.jsnow read from and write tochrome.storage.sessioninstead ofchrome.storage.local. The active-account choice now tracks the JWT lifetime instead of persisting across browser restarts, removing a class of "wrong account selected after restart" bugs.clearAuthstill removes the legacychrome.storage.localkey so users upgrading from 2.0.3 are migrated cleanly.
[2.0.3] — 2026-04-22
Fixed
- SAVE_PAGE no longer fails on SPAs / auth-gated / Cloudflare-protected pages — the popup's "Save page" action previously sent only
{url, title}to the server, which then re-fetched the URL and ran Mozilla Readability. For single-page apps (Twitter/X, Reddit, Gmail) the server received an empty JS shell, for auth-gated pages it received a login redirect, and for Cloudflare-protected pages it received a challenge page — all of which caused Readability to return null and the request to fail with a 422 "Could not extract readable content" error. The popup now extracts the page's visible text via the existingGET_PAGE_TEXTcontent-script message and includes it in the SAVE_PAGE payload. The server route/api/ext/documentsnow has two paths: when client-extracted content is present it is saved directly (skipping the URL re-fetch), otherwise it falls back to the original Readability flow for traditional articles.
[2.0.1] — 2026-04-17
Fixed
- Offline queue
'page'type now flushes correctly — the queue flush loop inservice-worker.jspreviously sent queued page-save items tosaveNote()(wrong API call). Added an explicit'page'branch that correctly routes tosaveDocument(). handleSummarizecallback/promise mismatch —QuickActions.jsxwas using the old callback-basedchrome.tabs.sendMessage(id, msg, callback)API inside anasyncfunction. Errors thrown inside the callback couldn't propagate to the outertry/catch. Replaced with the promise-based form and afinallyblock for reliablesetSummaryLoading(false)cleanup.setSavedunused parameter removed —useAppState.jsdeclaredsetSaved((url) => ...)but never usedurl. Parameter removed to eliminate dead code and misleading intent.- Tags sent as array —
BookmarkCard.jsxwas sendingtags: tags.join(',')(comma-separated string) to the API. ServersanitizeTags()accepted both formats but the canonical type is an array. Now sendstags: tagsdirectly.
Changed
- Permissions cleanup — removed unused
"offscreen"and"sidePanel"permissions frommanifest.json. - API call batching —
api.jsnow batch-readsbaseUrl+activeAccountIdin a singlechrome.storage.local.get()viagetApiContext(), replacing three sequential storage reads per request. - Badge debounce —
tabs.onActivatedandtabs.onUpdatednow use a 250 ms per-tab debounce before callingcheckSavedPageBadge()to avoid flooding the API on rapid tab switching. - Badge batch update —
saveAllTabsFromPopup()accumulates a saved count and callsupdateBadge(saved)once instead of once per tab. - Search debounce —
SearchView.jsxsearch input debounce increased from 300 ms to 500 ms.
Added
getApiContext()helper — new export inauth.jsthat readsbaseUrlandactiveAccountIdin a single storage call.API_PATHScompleteness —highlights,highlightsDelete,tags,documents, andsearchBookmarkspaths added toconstants.js. All extension API endpoints are now centralized.searchBookmarkspath comment — clarifies that the search function intentionally uses the/api/keep/bookmarkssurface (which natively supports?q=full-text search) rather than the ext API.
[2.0.0] — 2026-04-16
Added
- Full Page Save — new
SAVE_PAGEmessage handler callssaveDocument(), which POSTs to/api/ext/documents. The server runs Mozilla Readability on the submitted HTML and stores the parsed article content (up to 200 KB). - Content extraction pipeline —
extractor.jscontent script now exportsgetSelectionHtml()(TreeWalker-based, preserves heading/list/code/blockquote structure) andgetPageText()(full visible text). New message typesGET_SELECTION_HTMLandGET_PAGE_HTMLallow the service worker to request these from any tab. - HTML selection save —
CTX_SAVE_SELECTIONcontext-menu handler now requests full HTML from the content script before saving, falling back to plain text. ErrorBoundarycomponent — wraps the entire popup UI; catches React render errors and shows a "Try again" screen instead of a blank panel.saveDocument()API function — new export inapi.jsthat POSTs{ url, title, html, text }to/api/ext/documents./api/ext/documentsserver route — new POST endpoint inextensionRoutes.js: accepts HTML body, runs Readability, stores result; enforces 200 KB limit and requires auth.- 103-test suite — three new test files added:
src/lib/__tests__/auth.test.js— 23 tests covering JWT expiry, HTTPS enforcement, email validation, network errorssrc/content/__tests__/extractor.test.js— 11 tests covering message handlers, HTML extraction, meta extraction (jsdom environment)src/background/__tests__/service-worker.test.js— 17 tests covering all message handler types and offline queue flush logic
Changed
- React 19.0.0 — popup upgraded from React 18 to React 19;
react,react-dom,@types/react,@types/react-domall updated. - Zustand 5 — state management library added (
^5.0.0); popup index wraps<Popup />in<ErrorBoundary>. api.jshardened —AbortControllertimeout (30 s) on every fetch; HTTPS-only enforcement (setBaseUrlrejects non-HTTPS); 1-retry on 5xx/network errors;ApiErrorclass exported.auth.jshardened —getToken()checks JWTexpclaim and clears expired tokens automatically;setBaseUrl()rejects non-HTTPS URLs;login()validates email format before sending.- Offline queue flush lock —
service-worker.jsuses a module-levelflushLockflag to prevent concurrent queue flushes when alarms fire close together. - Note limit raised —
/api/ext/notesnow accepts up to 50,000 characters (previously 10,000) andhtmlas a validcontent_format. QuickActions.jsxredesigned — "Save page" button added; broken "Save Selection" and "Open Tab" buttons removed.
Removed
SettingsPanel.jsx— legacy monolithic settings component removed; settings UI lives inSettingsView.jsx.
[1.2.1] — 2026-04-04
Fixed
- Multi-account auth —
verifyToken()now sendsX-Account-Idheader and persists the active account ID so subsequent API calls are correctly scoped - Note content format — notes saved from the Quick Note Composer now include
content_format: 'markdown'to align with the dashboard's rendering - Price display — service-worker notification updated to reflect the correct $15 Glassy Keep price
Changed
- Note composer placeholder updated: "Capture a note... Markdown and pasted links are preserved."
- Character count footer now shows "Markdown supported • ⌘+Enter to save"
[1.2.0] — 2026-03-30
Added
- Quick Note Composer — new "Note" tab in the popup for capturing thoughts, with title, rich textarea (10k char limit), Cmd/Ctrl+Enter shortcut, character count, and draft auto-save (500ms debounce to
chrome.storage.local) - Link to Current Page — toggle in Note view attaches the active tab's URL, title, and favicon to saved notes
- Tag Autocomplete —
TagEditornow fetches all tags from the server and shows a filtered dropdown as you type, with arrow-key navigation - Inline Collection Create — "+ New collection" button inside
CollectionPickerdropdown with inline name input - Saved Page Badge — green "✓" badge on the extension icon when the active tab URL is already saved to Keep, with in-memory cache (500 entries)
- AI Summary Display —
SummaryCardcomponent renders AI-generated summaries with Copy and Save-as-Note actions - Quick Note Keyboard Shortcut —
Ctrl+Shift+N/⌘+Shift+Nopens popup directly to Note view - Quick Note Context Menu — right-click "New Glassy Note" opens popup in note mode
- GET_QUEUE_LENGTH message — service worker responds with offline queue count for logout confirmation
- New API functions —
updateBookmark,deleteBookmark,fetchHighlights,createHighlight,deleteHighlight,fetchTags,createCollection - Tags cache —
getTags()/invalidateTags()with 10-minute TTL incache.js - Test suite — 3 new test files:
api.test.js(9 tests),cache.test.js(13 tests),useExtensionBridge.test.js(9 tests); total suite now 44 tests
Changed
- Popup architecture — decomposed ~500-line
Popup.jsxmonolith intoAppShell+useAppStatehook + 4 view components (SaveView,NoteView,SearchView,SettingsView); Popup.jsx reduced to ~80 lines - Tab navigation — popup now has Save / Note / Search tabs in the header, with active tab indicator
- Logout confirmation —
SettingsViewchecks offline queue length before logout and warns if items would be lost - Error handling hardened —
apiFetchnow wrapsfetch()in try/catch for network errors and handles non-JSON responses;SearchViewshows error state instead of silent empty results;SummaryCardshows clipboard failure feedback;NoteViewhandleschrome.storageerrors on draft restore;SettingsViewcatches mount-time failures - Tag normalization —
TagEditornow uses a sharednormalizeTag()function for consistent input and autocomplete comparison
Fixed
- AI Summary display —
QuickActionsnow stores and renders AI summary results viaSummaryCard(previously called API but never displayed result) - 429 rate limiting —
savePolicy.jsnow classifies HTTP 429 as "retryable" instead of "fatal"
Permissions
- Added
sidePanelandscriptingpermissions tomanifest.json - Added
quick-notecommand (Ctrl+Shift+N/⌘+Shift+N)
[1.1.1] — 2026-03-10
Changed
- UpsellCard price (
src/popup/components/UpsellCard.jsx): Updated displayed Glassy Keep price from$9to$15.
Documentation
- README version badge updated to
v1.1.0; Save All Tabs and Quick Search added to feature table and "What's New" section.
[1.1.0] — 2026-03-09
Added
- Save All Tabs — new button in popup header saves every HTTP/HTTPS tab in the current window to Glassy Keep; duplicate tabs are silently skipped, badge count incremented for each saved tab
- Quick Search — search bar in popup lets users find existing Keep bookmarks without opening the dashboard; results rendered inline with title + domain
- Search API —
searchBookmarks(query)added tosrc/lib/api.js; background message handlerSEARCH_BOOKMARKSwired in service worker
Changed
- Background service worker now imports and dispatches
saveBookmark,saveNote, andsearchBookmarksfromapi.js - Popup header layout updated to accommodate new action buttons
[1.0.3] — 2026-03-09
Fixed
- Corrected production API URL — extension was pointing at the wrong domain;
updated tohttps://glassy.fyi(03b2d25) - Form accessibility — improved label/input associations in
LoginCardand
BookmarkCardfor screen-reader compatibility (fa26ee1)
Changed
- Synchronized
package.jsonversion to matchmanifest.json(9018f17)
[1.0.2] — 2026-03-06
Fixed
- Hardened background save handling — rewrote
service-worker.jsto handle
offline queuing and retry logic more robustly; addedsavePolicy.jsmodule for
configurable save behaviour - Offline queue — added
src/lib/offlineQueue.jswith full test coverage
(src/lib/__tests__/offlineQueue.test.js)
Added
src/background/savePolicy.js— dedicated save-policy module with tests
(src/background/__tests__/savePolicy.test.js)
[1.0.1] — 2026-02-15
Fixed
- Auth check shape mismatch — popup was always redirecting to login on every
open; fixedauthenticatedvsokproperty check - Save Selection —
GET_SELECTED_TEXThandler was missing from content script;
Save Selection button in QuickActions was silently doing nothing - CSS path — corrected CSS link in
popup/index.html
(./styles/popup.csswas incorrect)
Added
- Extension icons (
assets/) — 16/32/48/128 px indigo sparkle PNGs scripts/zip.js— native Node zip packager (no external dependencies)showNotificationsadded toDEFAULT_SETTINGS(was orphaned reference)LICENSE— MIT
Changed
package.jsonset to"type": "module"for clean ESM- OG image height normalized to 100px (consistent with container)
BookmarkCardtitle field now syncs whenpageMetahydrates- Bumped version to
1.0.1inmanifest.jsonandpackage.json
[1.0.0] — 2026-02-14
Initial release of Glassy Companion.
Features
- Save current page to Glassy dashboard with one click
- Save selected text as a highlight
- QuickActions popup for fast note capture
- Offline queue with automatic retry when dashboard is unreachable
- JWT authentication synced with Glassy dashboard session
- Built with Vite + React; packaged as Manifest V3 Chrome extension