diff --git a/manifest.json b/manifest.json
index 16c93fa..3a9dd8a 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "QueryCraft",
- "version": "1.0.2",
+ "version": "1.0.8",
"description": "Inspect, edit, save, and replay URL query parameters on the active tab.",
"action": {
"default_popup": "src/popup/index.html",
diff --git a/package-lock.json b/package-lock.json
index ba0a606..3de4f4f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "querycraft",
- "version": "1.0.0",
+ "version": "1.0.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "querycraft",
- "version": "1.0.0",
+ "version": "1.0.8",
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/package.json b/package.json
index 6d5d40b..7e6af30 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "querycraft",
- "version": "1.0.0",
+ "version": "1.0.8",
"description": "Inspect, edit, save, and replay URL query parameters on the active tab.",
"private": true,
"type": "module",
diff --git a/src/__tests__/GroupedLinksList.test.tsx b/src/__tests__/GroupedLinksList.test.tsx
new file mode 100644
index 0000000..7d71b8c
--- /dev/null
+++ b/src/__tests__/GroupedLinksList.test.tsx
@@ -0,0 +1,45 @@
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { GroupedLinksList } from '@/components/SavedLinksDrawer/GroupedLinksList';
+import type { Group, SavedLink } from '@/types';
+
+const group: Group = { id: 'g1', name: 'Unsorted', createdAt: 0 };
+const link: SavedLink = {
+ id: 'l1',
+ url: 'https://example.com/?foo=bar',
+ label: 'Example',
+ createdAt: 0,
+ groupId: 'g1',
+};
+
+function renderList(onLoadLink = vi.fn()) {
+ render(
+ ,
+ );
+ return onLoadLink;
+}
+
+describe('GroupedLinksList', () => {
+ it('loads the saved URL when its name is clicked', async () => {
+ const onLoadLink = renderList();
+
+ await userEvent.click(screen.getByText('Example'));
+
+ expect(onLoadLink).toHaveBeenCalledWith('https://example.com/?foo=bar');
+ });
+
+ it('exposes the name as a button so it can be reached by keyboard', () => {
+ renderList();
+
+ expect(screen.getByRole('button', { name: /^Example/ })).toBeInTheDocument();
+ });
+});
diff --git a/src/__tests__/useActiveTabUrl.test.ts b/src/__tests__/useActiveTabUrl.test.ts
index 0538e7e..3cff16d 100644
--- a/src/__tests__/useActiveTabUrl.test.ts
+++ b/src/__tests__/useActiveTabUrl.test.ts
@@ -52,6 +52,21 @@ describe('useActiveTabUrl', () => {
});
});
+ it('keeps the tab id when Chrome hides the URL (e.g. a new tab page)', async () => {
+ vi.mocked(chrome.tabs.query).mockResolvedValueOnce([
+ { id: 3, active: true } as chrome.tabs.Tab,
+ ]);
+
+ renderHook(() => useActiveTabUrl());
+
+ await waitFor(() => {
+ expect(useAppStore.getState().tabState.status).toBe('unsupported');
+ });
+
+ // The tab id is what lets a saved link be loaded from this tab.
+ expect(useAppStore.getState().tabState).toMatchObject({ status: 'unsupported', tabId: 3 });
+ });
+
it('sets error status when chrome.tabs.query rejects', async () => {
vi.mocked(chrome.tabs.query).mockRejectedValueOnce(new Error('Permission denied'));
diff --git a/src/components/SavedLinksDrawer/GroupedLinksList.tsx b/src/components/SavedLinksDrawer/GroupedLinksList.tsx
index 4887480..38b066f 100644
--- a/src/components/SavedLinksDrawer/GroupedLinksList.tsx
+++ b/src/components/SavedLinksDrawer/GroupedLinksList.tsx
@@ -83,10 +83,15 @@ export function GroupedLinksList({
const name = link.label || link.url;
return (
-
+
+
{
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
- if (!tab || tab.id === undefined || !tab.url) return null;
- return { id: tab.id, url: tab.url };
+ if (!tab || tab.id === undefined) return null;
+ // Keep the id even without a URL so a saved link can still be loaded into this tab.
+ return { id: tab.id, url: tab.url ?? '' };
},
async updateUrl(tabId: number, url: string): Promise {
diff --git a/src/styles/tokens.css b/src/styles/tokens.css
index 174eaaf..1aee8f7 100644
--- a/src/styles/tokens.css
+++ b/src/styles/tokens.css
@@ -24,28 +24,31 @@
/* Surfaces */
--color-bg: oklch(0.15 0 0); /* popup panel */
--color-bg-outer: oklch(0.1 0 0); /* page behind the panel */
- --color-surface-inset: oklch(0.19 0 0); /* inputs, url box, saved items */
- --color-surface-header: oklch(0.18 0 0); /* card header strips */
+ --color-surface-inset: oklch(0.22 0 0); /* inputs, url box, saved items */
+ --color-surface-header: oklch(0.21 0 0); /* card header strips */
- /* Text — descending emphasis */
+ /*
+ * Text — descending emphasis. Every step, faint included, stays at or above
+ * 4.5:1 on bg, surface-inset and surface-header (the README's AA rule).
+ */
--color-text-primary: oklch(0.95 0 0);
--color-text-strong: oklch(0.9 0 0); /* param keys, active values */
--color-text-body: oklch(0.94 0 0); /* editable input text */
--color-text-secondary: oklch(0.75 0 0); /* outlined button labels */
- --color-text-muted: oklch(0.55 0 0); /* section labels, meta */
- --color-text-faint: oklch(0.45 0 0); /* placeholders, idle icons */
+ --color-text-muted: oklch(0.67 0 0); /* section labels, meta */
+ --color-text-faint: oklch(0.62 0 0); /* placeholders, idle icons */
/* Borders — white at low alpha */
- --color-border-subtle: oklch(1 0 0 / 0.06);
- --color-border: oklch(1 0 0 / 0.08);
- --color-border-card: oklch(1 0 0 / 0.09);
- --color-border-strong: oklch(1 0 0 / 0.12);
+ --color-border-subtle: oklch(1 0 0 / 0.08);
+ --color-border: oklch(1 0 0 / 0.1);
+ --color-border-card: oklch(1 0 0 / 0.12);
+ --color-border-strong: oklch(1 0 0 / 0.16);
--color-border-focus: oklch(1 0 0 / 0.3);
/* Fills for hover / inset controls */
- --color-fill-subtle: oklch(1 0 0 / 0.06);
- --color-fill: oklch(1 0 0 / 0.08);
- --color-fill-strong: oklch(1 0 0 / 0.1);
+ --color-fill-subtle: oklch(1 0 0 / 0.08);
+ --color-fill: oklch(1 0 0 / 0.1);
+ --color-fill-strong: oklch(1 0 0 / 0.14);
/*
* Primary action — inverted block by default. When an accent color is
@@ -81,12 +84,12 @@
/* JSON syntax — monochrome, separated by lightness + weight */
--json-key: oklch(0.95 0 0);
- --json-index: oklch(0.6 0 0);
+ --json-index: oklch(0.66 0 0);
--json-string: oklch(0.8 0 0);
--json-number: oklch(0.9 0 0);
--json-boolean: oklch(0.85 0 0);
- --json-null: oklch(0.55 0 0);
- --json-punct: oklch(0.5 0 0);
+ --json-null: oklch(0.64 0 0);
+ --json-punct: oklch(0.62 0 0);
}
/* ── Light ────────────────────────────────────────────────────────── */