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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ npm run dev
- Last browser session tabs are restored on startup (up to 20 tabs).
- Renderer supports a coder-focused orange light/dark theme toggle (persisted per user).
- Bookmarks MVP is wired to SQLite: star action opens a name+URL save prompt (prefilled defaults), plus favicon bookmark bar and sidebar open/remove flow.
- History MVP records visits automatically and exposes a sidebar with recent entries and clear action.

## Storage Foundation

Expand Down Expand Up @@ -113,6 +114,7 @@ Use this gate before cutting a release branch or tag.
| Cmd/Ctrl + L | Focus address bar |
| Cmd/Ctrl + R | Reload |
| Cmd/Ctrl + D | Toggle bookmark for active page |
| Cmd/Ctrl + H | Toggle history sidebar |
| Cmd/Ctrl + Shift + B | Toggle bookmark bar |
| Cmd/Ctrl + Shift + O | Toggle floating window |

Expand Down
62 changes: 60 additions & 2 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
BookmarkSnapshot,
BookmarkUpsertPayload,
BrowserBounds,
HistorySnapshot,
TabSnapshot,
TabsStateSnapshot,
} from '../shared/ipc';
Expand All @@ -27,6 +28,7 @@ const VITE_DEV_SERVER_URL = process.env.ELECTRON_RENDERER_URL;
const RENDERER_DIST = path.join(__dirname, '../renderer');
const TABS_SESSION_KEY = 'tabsSession';
const MAX_RESTORED_TABS = 20;
const HISTORY_LIST_LIMIT = 200;

interface ManagedTab {
id: number;
Expand Down Expand Up @@ -147,6 +149,22 @@ function getBookmarksSnapshot(): BookmarkSnapshot[] {
});
}

function getHistorySnapshot(): HistorySnapshot[] {
if (!storageLayer) {
return [];
}

return storageLayer.history.listRecent(HISTORY_LIST_LIMIT).map(historyEntry => {
return {
id: historyEntry.id,
url: historyEntry.url,
title: historyEntry.title,
visitCount: historyEntry.visitCount,
lastVisitedAt: historyEntry.lastVisitedAt,
};
});
}

function emitBookmarksChanged(): void {
if (!mainWindow || mainWindow.isDestroyed()) {
return;
Expand All @@ -155,6 +173,14 @@ function emitBookmarksChanged(): void {
mainWindow.webContents.send(IPC_CHANNELS.BOOKMARKS_CHANGED, getBookmarksSnapshot());
}

function emitHistoryChanged(): void {
if (!mainWindow || mainWindow.isDestroyed()) {
return;
}

mainWindow.webContents.send(IPC_CHANNELS.HISTORY_CHANGED, getHistorySnapshot());
}

function toggleActiveBookmark(): BookmarkSnapshot[] {
const activeTab = getActiveTab();
if (!activeTab?.url || !storageLayer) {
Expand Down Expand Up @@ -197,6 +223,24 @@ function upsertBookmark(payload: BookmarkUpsertPayload): BookmarkSnapshot[] {
return bookmarks;
}

function recordHistoryVisit(url: string | null, title: string | null): void {
if (!storageLayer || !url || !isHttpNavigationUrl(url)) {
return;
}

const historyTitle = title?.trim() || url;
storageLayer.history.recordVisit(url, historyTitle);
emitHistoryChanged();
}

function clearHistory(): HistorySnapshot[] {
storageLayer?.history.clear();

const history = getHistorySnapshot();
emitHistoryChanged();
return history;
}

function persistTabsSession(): void {
const activeTabIndex = tabs.findIndex(tab => tab.id === activeTabId);
const sessionSnapshot: PersistedTabSession = {
Expand Down Expand Up @@ -362,11 +406,16 @@ function configureTabEvents(tab: ManagedTab): void {
});

webContents.on('did-stop-loading', () => {
const currentUrl = webContents.getURL() || null;
const currentTitle = webContents.getTitle() || null;

syncTabFromContents(webContents.id, entry => {
entry.isLoading = false;
entry.url = webContents.getURL() || null;
entry.title = webContents.getTitle() || entry.title;
entry.url = currentUrl;
entry.title = currentTitle || entry.title;
});

recordHistoryVisit(currentUrl, currentTitle);
});

webContents.on('did-navigate', (_event, navigationUrl) => {
Expand Down Expand Up @@ -590,6 +639,7 @@ function createMainWindow(): void {

emitTabsState();
emitBookmarksChanged();
emitHistoryChanged();
});

mainWindow.on('resize', () => {
Expand Down Expand Up @@ -778,6 +828,14 @@ ipcMain.handle(IPC_CHANNELS.BOOKMARKS_REMOVE, (_event, payload: unknown) => {
return removeBookmarkById(bookmarkId);
});

ipcMain.handle(IPC_CHANNELS.HISTORY_GET, () => {
return getHistorySnapshot();
});

ipcMain.handle(IPC_CHANNELS.HISTORY_CLEAR, () => {
return clearHistory();
});

process.on('uncaughtException', (error) => {
console.error('[main] uncaughtException', error);
});
Expand Down
29 changes: 29 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
parseBookmarkUpsertPayload,
parseBrowserBoundsPayload,
parseFloatNavigatePayload,
parseHistorySnapshotsPayload,
parseTabIdPayload,
parseTabNavigatePayload,
parseTabsStateSnapshotPayload,
Expand All @@ -14,6 +15,7 @@ import type {
BookmarkSnapshot,
BookmarkUpsertPayload,
BrowserBounds,
HistorySnapshot,
TabsStateSnapshot,
} from '../shared/ipc-contract';

Expand Down Expand Up @@ -167,5 +169,32 @@ contextBridge.exposeInMainWorld('orb', {
};
},

getHistory: async (): Promise<HistorySnapshot[]> => {
const payload = await ipcRenderer.invoke(IPC_CHANNELS.HISTORY_GET);
return parseHistorySnapshotsPayload(payload) ?? [];
},

clearHistory: async (): Promise<HistorySnapshot[]> => {
const payload = await ipcRenderer.invoke(IPC_CHANNELS.HISTORY_CLEAR);
return parseHistorySnapshotsPayload(payload) ?? [];
},

onHistoryChanged: (callback: (history: HistorySnapshot[]) => void) => {
const handler = (_event: Electron.IpcRendererEvent, payload: unknown): void => {
const parsedHistory = parseHistorySnapshotsPayload(payload);
if (!parsedHistory) {
return;
}

callback(parsedHistory);
};

ipcRenderer.on(IPC_CHANNELS.HISTORY_CHANGED, handler);

return () => {
ipcRenderer.removeListener(IPC_CHANNELS.HISTORY_CHANGED, handler);
};
},

platform: process.platform,
});
13 changes: 12 additions & 1 deletion src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<button class="nav-btn flex h-7 w-7 items-center justify-center rounded-orb border border-orb-border bg-orb-surface text-sm text-orb-text-dim transition hover:bg-orb-surface-2 hover:text-orb-text" id="btn-bookmark" title="Bookmark this page">☆</button>
<button class="nav-btn flex h-7 w-7 items-center justify-center rounded-orb border border-orb-border bg-orb-surface text-xs text-orb-text-dim transition hover:bg-orb-surface-2 hover:text-orb-text" id="btn-bookmark-bar" title="Toggle bookmarks bar (Cmd/Ctrl+Shift+B)">▤</button>
<button class="nav-btn flex h-7 w-7 items-center justify-center rounded-orb border border-orb-border bg-orb-surface text-xs text-orb-text-dim transition hover:bg-orb-surface-2 hover:text-orb-text" id="btn-bookmarks" title="Toggle bookmarks sidebar">☰</button>
<button class="nav-btn flex h-7 w-7 items-center justify-center rounded-orb border border-orb-border bg-orb-surface text-xs text-orb-text-dim transition hover:bg-orb-surface-2 hover:text-orb-text" id="btn-history" title="Toggle history sidebar (Cmd/Ctrl+H)">H</button>
<input id="address-bar" type="text" placeholder="Search or enter a URL…" spellcheck="false" class="h-8 flex-1 rounded-orb border border-orb-border bg-orb-surface px-3 text-[13px] text-orb-text outline-none transition focus:border-orb-accent" />
<button id="btn-float" title="Floating window (Cmd+Shift+O)" class="flex h-8 w-8 shrink-0 items-center justify-center rounded-orb border-0 bg-orb-accent text-base text-white transition hover:bg-orb-accent-dim">◎</button>
</div>
Expand All @@ -46,7 +47,8 @@ <h1 class="bg-gradient-to-br from-orb-accent to-orange-300 bg-clip-text text-[52
<p class="hint text-xs text-orb-text-dim opacity-70">
<kbd class="rounded border border-orb-border bg-orb-surface-2 px-1.5 py-0.5 text-[11px]">Cmd+T</kbd> new tab &nbsp;·&nbsp;
<kbd class="rounded border border-orb-border bg-orb-surface-2 px-1.5 py-0.5 text-[11px]">Cmd+W</kbd> close tab &nbsp;·&nbsp;
<kbd class="rounded border border-orb-border bg-orb-surface-2 px-1.5 py-0.5 text-[11px]">Cmd+D</kbd> bookmark page
<kbd class="rounded border border-orb-border bg-orb-surface-2 px-1.5 py-0.5 text-[11px]">Cmd+D</kbd> bookmark page &nbsp;·&nbsp;
<kbd class="rounded border border-orb-border bg-orb-surface-2 px-1.5 py-0.5 text-[11px]">Cmd+H</kbd> history
</p>
</div>
</div>
Expand All @@ -59,6 +61,15 @@ <h2 class="font-mono text-xs uppercase tracking-[1.2px] text-orb-text-dim">Bookm
<ul id="bookmarks-list" class="max-h-full overflow-y-auto px-2 py-2"></ul>
<p id="bookmarks-empty" class="px-3 py-4 text-sm text-orb-text-dim">No bookmarks yet. Open a page and press the star.</p>
</aside>

<aside id="history-sidebar" class="hidden w-[360px] shrink-0 border-l border-orb-border bg-orb-surface">
<div class="flex items-center justify-between border-b border-orb-border px-3 py-2">
<h2 class="font-mono text-xs uppercase tracking-[1.2px] text-orb-text-dim">History</h2>
<button id="btn-history-clear" class="rounded-orb border border-orb-border bg-orb-bg px-2 py-1 text-[11px] text-orb-text-dim transition hover:bg-orb-surface-2 hover:text-orb-text">Clear</button>
</div>
<ul id="history-list" class="max-h-full overflow-y-auto px-2 py-2"></ul>
<p id="history-empty" class="px-3 py-4 text-sm text-orb-text-dim">No history yet. Start browsing and entries will appear here.</p>
</aside>
</div>

<script type="module" src="./main.ts"></script>
Expand Down
Loading
Loading