Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/tab-manager/src/entrypoints/sidepanel/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
import { authState } from '$lib/state/auth.svelte';
import { browserState } from '$lib/state/browser-state.svelte';
import { unifiedViewState } from '$lib/state/unified-view-state.svelte';
import { registerDevice } from '$lib/workspace';

// Auth initialization — check cached session on mount
onMount(() => {
authState.checkSession();
void registerDevice();
// External sign-in handled by $effect in auth.svelte.ts
// Sync naturally handles auth token changes (stable client, no rebuild needed)
const onVisibilityChange = () => {
Expand Down
38 changes: 10 additions & 28 deletions apps/tab-manager/src/lib/components/command-palette/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
* ```
*/

import { confirmationDialog } from '@epicenter/ui/confirmation-dialog';
import type { CommandPaletteItem } from '@epicenter/ui/command-palette';
import { confirmationDialog } from '@epicenter/ui/confirmation-dialog';
import ArchiveIcon from '@lucide/svelte/icons/archive';
import ArrowDownAZIcon from '@lucide/svelte/icons/arrow-down-a-z';
import CopyMinusIcon from '@lucide/svelte/icons/copy-minus';
Expand All @@ -27,28 +27,18 @@ import { Ok, tryAsync } from 'wellcrafted/result';
import { browserState } from '$lib/state/browser-state.svelte';
import { savedTabState } from '$lib/state/saved-tab-state.svelte';
import { findDuplicateGroups, groupTabsByDomain } from '$lib/utils/tab-helpers';
import type { TabCompositeId } from '$lib/workspace';
import { parseTabId } from '$lib/workspace';


// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────

/**
* Batch-resolve composite tab IDs to native Chrome tab IDs.
*/
function compositeToNativeIds(compositeIds: TabCompositeId[]): number[] {
return compositeIds
.map((id) => parseTabId(id)?.tabId)
.filter((id) => id !== undefined);
}

/**
* Get all tabs across all windows as a flat array.
*/
function getAllTabs() {
return browserState.windows.flatMap((w) => browserState.tabsByWindow(w.id));
return browserState.windows.flatMap((w) =>
browserState.tabsByWindow(w.id),
);
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -86,9 +76,8 @@ export const items: CommandPaletteItem[] = [
description: `Found ${totalDuplicates} duplicate tab${totalDuplicates === 1 ? '' : 's'} across ${dupes.size} URL${dupes.size === 1 ? '' : 's'}. Close them?`,
confirm: { text: 'Close Duplicates', variant: 'destructive' },
async onConfirm() {
const nativeIds = compositeToNativeIds(toClose);
await tryAsync({
try: () => browser.tabs.remove(nativeIds),
try: () => browser.tabs.remove(toClose),
catch: () => Ok(undefined),
});
},
Expand All @@ -109,9 +98,7 @@ export const items: CommandPaletteItem[] = [
const groupOps = [...domains.entries()]
.filter(([, tabs]) => tabs.length >= 2)
.map(([domain, tabs]) => {
const nativeIds = compositeToNativeIds(
tabs.map((t) => t.id),
);
const nativeIds = tabs.map((t) => t.id);
return nativeIds.length >= 2 ? { domain, nativeIds } : null;
})
.filter((op) => op !== null);
Expand All @@ -136,17 +123,13 @@ export const items: CommandPaletteItem[] = [
async onSelect() {
for (const window of browserState.windows) {
const tabs = browserState.tabsByWindow(window.id);
const sorted = [...tabs].sort((a, b) =>
(a.title ?? '').localeCompare(b.title ?? ''),
);
const sorted = [...tabs].sort((a, b) => (a.title ?? '').localeCompare(b.title ?? ''));

for (let i = 0; i < sorted.length; i++) {
const tab = sorted[i];
if (!tab) continue;
const parsed = parseTabId(tab.id);
if (!parsed) continue;
await tryAsync({
try: () => browser.tabs.move(parsed.tabId, { index: i }),
try: () => browser.tabs.move(tab.id, { index: i }),
catch: () => Ok(undefined),
});
}
Expand Down Expand Up @@ -180,9 +163,8 @@ export const items: CommandPaletteItem[] = [
description: `Close ${topCount} tab${topCount === 1 ? '' : 's'} from ${topDomain}?`,
confirm: { text: 'Close Tabs', variant: 'destructive' },
async onConfirm() {
const nativeIds = compositeToNativeIds(tabIds);
await tryAsync({
try: () => browser.tabs.remove(nativeIds),
try: () => browser.tabs.remove(tabIds),
catch: () => Ok(undefined),
});
},
Expand All @@ -207,7 +189,7 @@ export const items: CommandPaletteItem[] = [
async onConfirm() {
const tabsWithUrls = allTabs.filter((tab) => tab.url);
await Promise.allSettled(
tabsWithUrls.map((tab) => savedTabState.actions.save(tab)),
tabsWithUrls.map((tab) => savedTabState.save(tab)),
);
},
});
Expand Down
22 changes: 10 additions & 12 deletions apps/tab-manager/src/lib/components/tabs/TabItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
import VolumeXIcon from '@lucide/svelte/icons/volume-x';
import XIcon from '@lucide/svelte/icons/x';
import { bookmarkState } from '$lib/state/bookmark-state.svelte';
import { browserState } from '$lib/state/browser-state.svelte';
import { browserState, type BrowserTab } from '$lib/state/browser-state.svelte';
import { savedTabState } from '$lib/state/saved-tab-state.svelte';
import { getDomain } from '$lib/utils/format';
import type { Tab } from '$lib/workspace';
import TabFavicon from './TabFavicon.svelte';

let { tab }: { tab: Tab } = $props();
let { tab }: { tab: BrowserTab } = $props();

const tabId = $derived(tab.tabId);
const domain = $derived(tab.url ? getDomain(tab.url) : '');
</script>

Expand All @@ -36,7 +34,7 @@
<button
type="button"
{...props}
onclick={() => browserState.activate(tabId)}
onclick={() => browserState.activate(tab.id)}
>
<Item.Media> <TabFavicon src={tab.favIconUrl} /> </Item.Media>

Expand Down Expand Up @@ -83,9 +81,9 @@
onclick={(e: MouseEvent) => {
e.stopPropagation();
if (tab.pinned) {
browserState.unpin(tabId);
browserState.unpin(tab.id);
} else {
browserState.pin(tabId);
browserState.pin(tab.id);
}
}}
>
Expand All @@ -104,9 +102,9 @@
onclick={(e: MouseEvent) => {
e.stopPropagation();
if (tab.mutedInfo?.muted) {
browserState.unmute(tabId);
browserState.unmute(tab.id);
} else {
browserState.mute(tabId);
browserState.mute(tab.id);
}
}}
>
Expand All @@ -124,7 +122,7 @@
tooltip="Reload"
onclick={(e: MouseEvent) => {
e.stopPropagation();
browserState.reload(tabId);
browserState.reload(tab.id);
}}
>
<RefreshCwIcon />
Expand All @@ -136,7 +134,7 @@
tooltip="Duplicate"
onclick={(e: MouseEvent) => {
e.stopPropagation();
browserState.duplicate(tabId);
browserState.duplicate(tab.id);
}}
>
<CopyIcon />
Expand Down Expand Up @@ -173,7 +171,7 @@
tooltip="Close"
onclick={(e: MouseEvent) => {
e.stopPropagation();
browserState.close(tabId);
browserState.close(tab.id);
}}
>
<XIcon />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
type="button"
onclick={() => {
if (!unifiedViewState.isFiltering) {
unifiedViewState.toggleWindow(item.window.id);
unifiedViewState.toggleWindow(item.window.id);
}
}}
class="sticky top-0 z-10 flex w-full cursor-pointer items-center gap-2 border-b bg-muted/50 px-4 py-2 text-sm text-muted-foreground backdrop-blur transition hover:bg-muted/80"
Expand Down
4 changes: 2 additions & 2 deletions apps/tab-manager/src/lib/state/bookmark-state.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import {
type Bookmark,
type BookmarkId,
generateBookmarkId,
type Tab,
workspace,
} from '$lib/workspace';
import type { BrowserTab } from '$lib/state/browser-state.svelte';

function createBookmarkState() {
const bookmarksMap = fromTable(workspace.tables.bookmarks);
Expand All @@ -60,7 +60,7 @@ function createBookmarkState() {
*
* Silently no-ops for tabs without a URL.
*/
async add(tab: Tab) {
async add(tab: BrowserTab) {
if (!tab.url) return;
const deviceId = await getDeviceId();
workspace.tables.bookmarks.set({
Expand Down
Loading
Loading