Skip to content
Closed
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
1,095 changes: 1,095 additions & 0 deletions design/omnibox-redesign.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion drizzle/0001_tough_scream.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ CREATE TABLE `pinned_tabs` (
`position` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX `idx_pinned_tabs_profile_id` ON `pinned_tabs` (`profile_id`);
CREATE INDEX `idx_pinned_tabs_profile_id` ON `pinned_tabs` (`profile_id`);
13 changes: 13 additions & 0 deletions drizzle/0003_omnibox_shortcuts.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS `omnibox_shortcuts` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`input_text` text NOT NULL,
`destination_url` text NOT NULL,
`destination_title` text DEFAULT '' NOT NULL,
`match_type` text NOT NULL,
`hit_count` integer DEFAULT 1 NOT NULL,
`last_access_time` integer NOT NULL
);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS `idx_omnibox_shortcuts_input` ON `omnibox_shortcuts` (`input_text`);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS `idx_omnibox_shortcuts_destination` ON `omnibox_shortcuts` (`destination_url`);
Comment on lines +1 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for cleanup/deletion logic using lastAccessTime
rg -n -A3 "lastAccessTime|last_access_time" --type ts | grep -E "(delete|cleanup|prune|expire|WHERE)"

Repository: MultiboxLabs/flow-browser

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Search for all references to omnibox_shortcuts (all file types)
echo "=== References to omnibox_shortcuts ===" 
rg -i "omnibox_shortcuts" -A 2 -B 2

echo ""
echo "=== References to lastAccessTime or last_access_time (all file types) ==="
rg "lastAccessTime|last_access_time" -A 2 -B 2

echo ""
echo "=== Files in drizzle directory ===" 
ls -la drizzle/ | head -20

Repository: MultiboxLabs/flow-browser

Length of output: 6963


Consider adding an index on last_access_time for cleanup and sorting operations.

The schema includes indexes on input_text and destination_url, but last_access_time is used in both a cleanup query (.where(lastAccessTime < cutoff)) that performs record expiration and an ORDER BY clause for sorting results by recency. Without an index, these operations require a full table scan.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@drizzle/0003_omnibox_shortcuts.sql` around lines 11 - 23, Add an index on
omnibox_shortcuts.last_access_time to speed cleanup and recency ordering; update
the migration to create an index such as idx_omnibox_shortcuts_last_access_time
ON `omnibox_shortcuts` (`last_access_time`) so queries using
.where(lastAccessTime < cutoff) and ORDER BY last_access_time use the index.

7 changes: 7 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
"when": 1772400000000,
"tag": "0002_chromium_history",
"breakpoints": true
},
{
"idx": 3,
"version": "6",
"when": 1774141367000,
"tag": "0003_omnibox_shortcuts",
"breakpoints": true
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const createFileMenu = (): MenuItemConstructorOptions => ({
omnibox.hide();
} else {
omnibox.setBounds(null);
omnibox.loadInterface(null);
omnibox.sendShowEvent({ currentInput: null, openIn: "new_tab" });
omnibox.show();
Comment on lines 50 to 53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor inconsistency in call order.

The call sequence here is setBoundssendShowEventshow, whereas src/main/ipc/app/new-tab.ts uses sendShowEventsetBoundsshow. While both work (since show() is the final trigger), consider unifying the order for maintainability.

♻️ Suggested consistent ordering
        } else {
-         omnibox.setBounds(null);
          omnibox.sendShowEvent({ currentInput: null, openIn: "new_tab" });
+         omnibox.setBounds(null);
          omnibox.show();
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
omnibox.setBounds(null);
omnibox.loadInterface(null);
omnibox.sendShowEvent({ currentInput: null, openIn: "new_tab" });
omnibox.show();
} else {
omnibox.sendShowEvent({ currentInput: null, openIn: "new_tab" });
omnibox.setBounds(null);
omnibox.show();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/controllers/app-menu-controller/menu/items/file.ts` around lines 50
- 53, Reorder the omnibox call sequence to match the project convention: call
omnibox.sendShowEvent(...) before omnibox.setBounds(...), and then call
omnibox.show(); specifically update the block that currently calls
omnibox.setBounds(null); omnibox.sendShowEvent({ currentInput: null, openIn:
"new_tab" }); omnibox.show(); so it becomes sendShowEvent -> setBounds -> show
using the omnibox.sendShowEvent, omnibox.setBounds, and omnibox.show methods.

}
}
Expand Down
109 changes: 108 additions & 1 deletion src/main/controllers/windows-controller/utils/browser/omnibox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ const omniboxes = new Map<BrowserWindow, Omnibox>();

type QueryParams = { [key: string]: string };

/** Parameters sent to the renderer when showing the omnibox. */
interface OmniboxShowParams {
currentInput: string | null;
openIn: "current" | "new_tab";
}

export class Omnibox {
public view: WebContentsView;
public webContents: WebContents;
Expand All @@ -17,6 +23,13 @@ export class Omnibox {

private isDestroyed: boolean = false;

/** Whether the initial load of the omnibox URL has completed. */
private initialLoadComplete: boolean = false;
/** Whether the renderer has registered its IPC listeners and can receive show events. */
private rendererReady: boolean = false;
/** Most recent show payload queued while waiting for renderer readiness. */
private pendingShowParams: OmniboxShowParams | null = null;

constructor(parentWindow: BrowserWindow) {
debugPrint("OMNIBOX", `Creating new omnibox for window ${parentWindow.id}`);
const onmiboxView = new WebContentsView({
Expand Down Expand Up @@ -46,6 +59,18 @@ export class Omnibox {
this.updateBounds();
});

onmiboxWC.on("did-start-loading", () => {
this.rendererReady = false;
});

// Track when the initial load finishes
onmiboxWC.on("did-finish-load", () => {
if (!this.initialLoadComplete) {
this.initialLoadComplete = true;
debugPrint("OMNIBOX", "Initial load complete");
}
});

setTimeout(() => {
this.loadInterface(null);
this.updateBounds();
Expand All @@ -65,9 +90,33 @@ export class Omnibox {
}
}

/**
* Load the omnibox interface URL. Only used for initial load.
* After the first load, the renderer stays alive and receives IPC messages.
*/
loadInterface(params: QueryParams | null) {
this.assertNotDestroyed();

// If the omnibox renderer is already loaded, send params via IPC instead of reloading
if (this.initialLoadComplete) {
if (!this.rendererReady) {
debugPrint("OMNIBOX", "Renderer not ready yet, queueing show event until listener registration");
this.pendingShowParams = {
currentInput: params?.currentInput ?? null,
openIn: (params?.openIn as "current" | "new_tab") ?? "new_tab"
};
return;
}

debugPrint("OMNIBOX", "Omnibox already loaded, sending show event via IPC instead of reloading");
const showParams: OmniboxShowParams = {
currentInput: params?.currentInput ?? null,
openIn: (params?.openIn as "current" | "new_tab") ?? "new_tab"
};
this.webContents.send("omnibox:do-show", showParams);
return;
}

debugPrint("OMNIBOX", `Loading interface with params: ${JSON.stringify(params)}`);
const onmiboxWC = this.webContents;

Expand All @@ -88,6 +137,56 @@ export class Omnibox {
}
}

/**
* Send a show event to the already-loaded omnibox renderer via IPC.
* This is the preferred method after the initial load — no reload, no flicker.
*/
sendShowEvent(params: OmniboxShowParams) {
this.assertNotDestroyed();

if (!this.initialLoadComplete) {
debugPrint("OMNIBOX", "Initial load not complete, routing show event through loadInterface");
const queryParams: QueryParams = { openIn: params.openIn };
if (params.currentInput !== null) {
queryParams.currentInput = params.currentInput;
}
this.loadInterface(queryParams);
return;
}

if (!this.rendererReady) {
debugPrint("OMNIBOX", "Renderer not ready yet, queueing show event");
this.pendingShowParams = params;
return;
Comment on lines +157 to +160
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Drop queued show payloads when an explicit hide wins the race.

A show request can sit in pendingShowParams while rendererReady is false. If the omnibox is hidden before readiness, markRendererReady() still flushes that stale payload and replays omnibox:do-show into a renderer that should stay hidden, which brings the show/hide desync back.

Also applies to: 171-178, 284-291

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/controllers/windows-controller/utils/browser/omnibox.ts` around
lines 157 - 160, The code must drop queued show payloads when a hide races
ahead: when hiding the omnibox (the method that dispatches
"omnibox:do-hide"/sets hidden state), clear pendingShowParams so stale show
requests won't be replayed; additionally, update markRendererReady() to check
the current hidden/shown state before flushing pendingShowParams and only emit
"omnibox:do-show" if the omnibox is still expected to be visible. In short,
clear pendingShowParams in the hide handler and gate the pendingShowParams flush
in markRendererReady() by checking the same visibility flag used for hide/show
to avoid replaying stale payloads.

}

debugPrint("OMNIBOX", `Sending show event with params: ${JSON.stringify(params)}`);
this.webContents.send("omnibox:do-show", params);
}

markRendererReady() {
this.assertNotDestroyed();
this.rendererReady = true;

if (!this.pendingShowParams) {
return;
}

const params = this.pendingShowParams;
this.pendingShowParams = null;
debugPrint("OMNIBOX", "Renderer ready, flushing queued show event");
this.webContents.send("omnibox:do-show", params);
}

/**
* Send a hide event to the renderer so it can reset its state.
*/
sendHideEvent() {
this.assertNotDestroyed();
debugPrint("OMNIBOX", "Sending hide event to renderer");
this.webContents.send("omnibox:do-hide");
}

updateBounds() {
this.assertNotDestroyed();

Expand Down Expand Up @@ -140,7 +239,7 @@ export class Omnibox {
this.assertNotDestroyed();

debugPrint("OMNIBOX", "Showing omnibox");
// Hide omnibox if it is already visible
// Hide omnibox if it is already visible (safe: hide() no longer sends IPC)
this.hide();

// Show UI
Expand Down Expand Up @@ -182,6 +281,14 @@ export class Omnibox {
debugPrint("OMNIBOX", "Hiding omnibox");
this.view.setVisible(false);

// Do NOT send a hide IPC event here. The native view visibility
// (view.setVisible) is the sole mechanism for main-process hides.
// Sending hide IPC caused a race condition: blur events during show()
// would trigger maybeHide() → hide() → sendHideEvent(), undoing the
// prior sendShowEvent() and leaving the renderer with isVisible=false.
// The renderer handles its own state for user-initiated hides (Escape,
// match selection) and the show handler always resets state on next open.

if (omniboxWasFocused) {
// Focuses the parent window instead
this.window.webContents.focus();
Expand Down
2 changes: 1 addition & 1 deletion src/main/ipc/app/new-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function openNewTab(window: BrowserWindow) {
if (omnibox.isVisible()) {
omnibox.hide();
} else {
omnibox.loadInterface(null);
omnibox.sendShowEvent({ currentInput: null, openIn: "new_tab" });
omnibox.setBounds(null);
omnibox.show();
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/ipc/data/omnibox-shortcuts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ipcMain } from "electron";
import { omniboxShortcutsService } from "@/saving/omnibox-shortcuts/shortcuts-service";

// --- Omnibox Shortcuts IPC Handlers ---

/**
* Search for shortcuts matching the given input text.
* Used by ShortcutsProvider in the renderer for omnibox suggestions.
*/
ipcMain.handle("omnibox-shortcuts:search", async (_event, inputText: string, limit?: number) => {
return omniboxShortcutsService.search(inputText, limit);
});

/**
* Record a shortcut when the user selects an omnibox suggestion.
* Fire-and-forget — called from the renderer when the user picks a result.
*/
ipcMain.on(
"omnibox-shortcuts:record-usage",
(_event, inputText: string, destinationUrl: string, destinationTitle: string, matchType: string) => {
omniboxShortcutsService.recordUsage(inputText, destinationUrl, destinationTitle, matchType);
}
);
Comment on lines +18 to +23
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider basic input validation for IPC parameters.

The record-usage handler accepts parameters directly from the renderer without validation. While the service layer may handle this, validating at the IPC boundary provides defense-in-depth against malformed or malicious data.

♻️ Suggested validation
 ipcMain.on(
   "omnibox-shortcuts:record-usage",
   (_event, inputText: string, destinationUrl: string, destinationTitle: string, matchType: string) => {
+    if (typeof inputText !== "string" || typeof destinationUrl !== "string") {
+      return; // Silently ignore malformed requests
+    }
     omniboxShortcutsService.recordUsage(inputText, destinationUrl, destinationTitle, matchType);
   }
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ipcMain.on(
"omnibox-shortcuts:record-usage",
(_event, inputText: string, destinationUrl: string, destinationTitle: string, matchType: string) => {
omniboxShortcutsService.recordUsage(inputText, destinationUrl, destinationTitle, matchType);
}
);
ipcMain.on(
"omnibox-shortcuts:record-usage",
(_event, inputText: string, destinationUrl: string, destinationTitle: string, matchType: string) => {
if (typeof inputText !== "string" || typeof destinationUrl !== "string") {
return; // Silently ignore malformed requests
}
omniboxShortcutsService.recordUsage(inputText, destinationUrl, destinationTitle, matchType);
}
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/data/omnibox-shortcuts.ts` around lines 18 - 23, The IPC handler
registered with ipcMain.on for "omnibox-shortcuts:record-usage" currently
forwards renderer-provided parameters directly to
omniboxShortcutsService.recordUsage; add defensive validation at that boundary:
verify inputText, destinationUrl, destinationTitle, and matchType are strings
(and not excessively long), ensure inputText/destinationTitle are non-empty,
validate/sanitize destinationUrl (e.g., URL.parse/try-catch or whitelist schemes
like http/https), and ensure matchType is one of the expected enum/values before
calling omniboxShortcutsService.recordUsage; if validation fails, log the
rejection and return early (do not call the service).


/**
* Get all shortcuts for a specific destination URL.
*/
ipcMain.handle("omnibox-shortcuts:get-for-url", async (_event, destinationUrl: string) => {
return omniboxShortcutsService.getForUrl(destinationUrl);
});

/**
* Cleanup old shortcuts. Returns number of rows deleted.
*/
ipcMain.handle("omnibox-shortcuts:cleanup", async (_event, maxAgeDays?: number) => {
return omniboxShortcutsService.cleanup(maxAgeDays);
});
3 changes: 3 additions & 0 deletions src/main/ipc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import "@/ipc/app/icons";
import "@/ipc/app/open-external";
import "@/ipc/app/onboarding";

// Data APIs
import "@/ipc/data/omnibox-shortcuts";

// Special
import "@/ipc/listeners-manager";
import "@/ipc/webauthn";
20 changes: 19 additions & 1 deletion src/main/ipc/window/omnibox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ ipcMain.on("omnibox:show", (event, bounds: Electron.Rectangle | null, params: {

const omnibox = parentWindow.omnibox;
omnibox.setBounds(bounds);
omnibox.loadInterface(params);
// Send params via IPC instead of reloading
omnibox.sendShowEvent({
currentInput: params?.currentInput ?? null,
openIn: (params?.openIn as "current" | "new_tab") ?? "new_tab"
});
Comment on lines +23 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type cast is unsafe — validate or fallback defensively.

The cast (params?.openIn as "current" | "new_tab") trusts the incoming IPC data. If a caller passes an unexpected value (e.g., typo or malformed data), the type assertion silently accepts it.

🛡️ Proposed defensive validation
   // Send params via IPC instead of reloading
+  const openIn = params?.openIn === "current" ? "current" : "new_tab";
   omnibox.sendShowEvent({
     currentInput: params?.currentInput ?? null,
-    openIn: (params?.openIn as "current" | "new_tab") ?? "new_tab"
+    openIn
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/window/omnibox.ts` around lines 23 - 27, The cast on
params?.openIn is unsafe: instead of asserting (params?.openIn as "current" |
"new_tab"), validate the incoming value before passing to omnibox.sendShowEvent;
check params?.openIn against the allowed strings "current" and "new_tab" (e.g.,
const openIn = params?.openIn === "current" ? "current" : "new_tab") and pass
that validated/fallback value into omnibox.sendShowEvent along with currentInput
to avoid accepting malformed IPC data.

omnibox.show();
});

Expand All @@ -40,3 +44,17 @@ ipcMain.on("omnibox:hide", (event) => {
const omnibox = parentWindow.omnibox;
omnibox.hide();
});

ipcMain.on("omnibox:renderer-ready", (event) => {
const omniboxWindow = browserWindowsManager
.getAll()
.find((window) => "omnibox" in window && window.omnibox.webContents === event.sender);

if (!omniboxWindow || !browserWindowsManager.isInstanceOf(omniboxWindow)) {
debugPrint("OMNIBOX", "Renderer-ready received for unknown omnibox webContents");
return;
}

debugPrint("OMNIBOX", "Renderer reported ready for IPC show events");
omniboxWindow.omnibox.markRendererReady();
Comment on lines +48 to +59
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

LGTM — renderer-ready handler is correctly implemented.

The handler properly matches the sender webContents to identify the correct omnibox instance and calls markRendererReady() to flush queued show events.

Note: Per context snippets, sendHideEvent() in omnibox.ts (lines 181-188) is defined but never called anywhere. The comment in hide() explains this was intentional to avoid race conditions, but the orphaned method is now dead code and could be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/window/omnibox.ts` around lines 48 - 59, The method
sendHideEvent() in the Omnibox class is dead code (defined but never called);
remove the entire sendHideEvent() implementation and any attendant unused
imports or references, and keep the hide() method comment/behavior as-is to
preserve the intentional race-condition avoidance; search for sendHideEvent,
hide, and Omnibox to locate the code to delete and ensure no remaining
references or unused variables remain after removal.

});
25 changes: 25 additions & 0 deletions src/main/saving/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,28 @@ export const historyVisits = sqliteTable(

export type HistoryUrlRow = typeof historyUrls.$inferSelect;
export type HistoryVisitRow = typeof historyVisits.$inferSelect;

// --- Omnibox Shortcuts Table ---
// Learned input-to-destination mappings (e.g., typing "gi" → github.com).
// Separate from browsing history: this tracks what users select for specific
// omnibox input, not every page visit.

export const omniboxShortcuts = sqliteTable(
"omnibox_shortcuts",
{
id: integer("id").primaryKey({ autoIncrement: true }),
inputText: text("input_text").notNull(),
destinationUrl: text("destination_url").notNull(),
destinationTitle: text("destination_title").notNull().default(""),
matchType: text("match_type").notNull(),
hitCount: integer("hit_count").notNull().default(1),
lastAccessTime: integer("last_access_time").notNull()
},
(table) => [
index("idx_omnibox_shortcuts_input").on(table.inputText),
index("idx_omnibox_shortcuts_destination").on(table.destinationUrl)
]
);
Comment on lines +132 to +147
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how recordUsage handles existing shortcuts
echo "=== shortcuts-service recordUsage implementation ==="
ast-grep --pattern 'recordUsage($$$) {
  $$$
}'

echo ""
echo "=== Check for upsert/conflict handling ==="
rg -n "onConflict|upsert|INSERT OR" --type ts src/main/saving/omnibox-shortcuts/

Repository: MultiboxLabs/flow-browser

Length of output: 165


🏁 Script executed:

#!/bin/bash
# Find files related to omnibox shortcuts
echo "=== Finding omnibox/shortcuts related files ==="
find . -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "recordUsage" | head -20

echo ""
echo "=== Looking for shortcuts service ==="
fd -e ts -e tsx "shortcut" | grep -v node_modules | head -20

echo ""
echo "=== Search for omniboxShortcuts usage ==="
rg "omniboxShortcuts" --type ts -A 3 -B 3 | head -100

Repository: MultiboxLabs/flow-browser

Length of output: 7539


🏁 Script executed:

#!/bin/bash
# Read the shortcuts-service.ts file
echo "=== shortcuts-service.ts full content ==="
cat -n src/main/saving/omnibox-shortcuts/shortcuts-service.ts

Repository: MultiboxLabs/flow-browser

Length of output: 5837


The schema doesn't require a unique constraint on (inputText, destinationUrl) since recordUsage already implements upsert logic.

The service checks for existing (inputText, destinationUrl) pairs and updates their hitCount and lastAccessTime rather than inserting duplicates. However, adding a unique constraint would be a defensible database-level safeguard to prevent data corruption if the application logic changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/saving/db/schema.ts` around lines 132 - 147, The omnibox_shortcuts
schema (omniboxShortcuts) lacks a database-level uniqueness constraint on
(inputText, destinationUrl), so add a unique constraint/index on those two
columns to enforce uniqueness at the DB level; update the sqliteTable definition
for omniboxShortcuts to include a unique composite index/constraint on
table.inputText and table.destinationUrl (so recordUsage's upsert logic remains
compatible but the DB prevents duplicates if application logic changes).


export type OmniboxShortcutRow = typeof omniboxShortcuts.$inferSelect;
export type OmniboxShortcutInsert = typeof omniboxShortcuts.$inferInsert;
2 changes: 1 addition & 1 deletion src/main/saving/history/browsing-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function listBrowsingHistoryForProfile(profileId: string): BrowsingHistor
.from(historyUrls)
.where(eq(historyUrls.profileId, profileId))
.orderBy(desc(historyUrls.lastVisitTime))
.limit(200)
.limit(2000)
.all();

return rows.map((row) => ({
Expand Down
Loading
Loading