Skip to content

ok#34

Merged
steve8708 merged 9 commits intomainfrom
updates-5
Mar 16, 2026
Merged

ok#34
steve8708 merged 9 commits intomainfrom
updates-5

Conversation

@steve8708
Copy link
Contributor

No description provided.

@netlify
Copy link

netlify bot commented Mar 16, 2026

Deploy Preview for agent-native-fw ready!

Name Link
🔨 Latest commit ca75082
🔍 Latest deploy log https://app.netlify.com/projects/agent-native-fw/deploys/69b8598e75542100085d42e2
😎 Deploy Preview https://deploy-preview-34--agent-native-fw.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 3 potential issues.

Review Details

Code Review Summary

PR #34 adds a "reopen closed tab" feature (Cmd+Shift+T) to the desktop app, intercepts Cmd+W in the main process to close tabs instead of the window, adds the necessary IPC wiring through preload, and includes minor UI polish (color opacity bumps, icon size, layout fix). Risk level: Standard 🟡.

The overall architecture is sound — using a useRef stack for closed tab history, a ref-based active tab ID to avoid stale closures in IPC callbacks, and returning the IPC cleanup function directly from useEffect. However, two real bugs were identified by 2 out of 3 independent review passes:

Key Findings

🔴 HIGH — Keyboard shortcuts fail when a webview is focused (found 2/3 agents)
Both the new Cmd+T/Cmd+Shift+T shortcuts and the existing navigation shortcuts (Cmd+1-9, Cmd+[/]) are registered via window.addEventListener("keydown") in the shell renderer. Electron <webview> elements run out-of-process; once the user clicks into any app tab, focus moves to the webview and these events never reach the host window. The shortcuts are effectively broken in normal use. The author already recognized this limitation for Cmd+W (correctly using before-input-event in the main process), but didn't apply the same approach to the other shortcuts.

🔴 HIGH — Cmd+W interception misses webview-focused input (found 2/3 agents)
before-input-event is attached only to win.webContents (the shell BrowserWindow). When keyboard focus is inside a <webview> guest, input events go to the guest's own WebContents and never fire on the parent. This means Cmd+W falls through to Electron's default close-window behavior in the most common interaction state.

🟡 MEDIUM — Side effect inside setAppTabs updater runs twice in StrictMode (found 2/3 agents)
closedTabsRef.current.push(...) is placed inside the setAppTabs((prev) => ...) functional updater. React 18 StrictMode intentionally double-invokes updaters to surface exactly this kind of bug. Each tab close pushes a duplicate entry, corrupting the reopen stack.

✅ IPC cleanup in preload is correct (same reference used for on/removeListener). ✅ React 18 automatic batching means setActiveSidebarAppId + setAppTabs in handleReopenTab updates atomically. ✅ activeTabIdRef pattern for avoiding stale IPC closures is appropriate.


View in Builder.io

Code review by Builder.io

Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 3 potential issues.

Review Details

Code Review Summary

PR #34 is a multi-area polish pass: dark mode is refactored from hardcoded class="dark" attributes to a proper ThemeProvider (calendar) and split light/dark CSS variables (mail); the desktop shell gains Cmd+W tab-close and Cmd+Shift+T reopen-closed-tab with webview keyboard forwarding via IPC; Gmail label names are now resolved from the API rather than displayed as raw IDs; and the mail app's GoogleConnectBanner gains a full-page hero onboarding variant. Risk: Standard.

Key Findings

🔴 HIGH:

  • Unvalidated URL in shell.openExternal — any XSS or malicious redirect in a webview guest can call window.open("ms-msdt:…") or window.open("file:///…"), triggering OS-level protocol handlers. Found by all 4 review passes.

🟡 MEDIUM:

  • N+1 uncached Gmail label API callsfetchGmailLabelMap() is called per account on every inbox/email request with no caching; materially slower loads and quota burn. Found by 3/4 passes.
  • Multi-account label ID collision — Gmail label IDs are account-scoped; merging all accounts into one Map can overwrite and mislabel messages. Found by 3/4 passes.
  • listLabels always returns unreadCount: 0 — all unread badges disappear as soon as Gmail is connected.
  • listLabels deduplication key mismatch — deduplicates by Gmail raw ID but exports name.toLowerCase(), so same-named labels from two accounts produce duplicate sidebar entries.
  • Global shortcut preventDefault fires for every Cmd/Ctrl key — blocks copy/paste/select-all in the shell renderer.
  • bg-white buttons invisible on new light theme — zero contrast on white background in mail GoogleConnectBanner hero.

Found by running 4 code-review agent calls with randomized file ordering.


View in Builder.io

Code review by Builder.io

);
}
}),
);

Choose a reason for hiding this comment

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

🟡 Uncached Gmail label fetch on every inbox request — N+1 latency and quota burn

fetchGmailLabelMap() is called for every connected account on every /api/emails and /api/emails/:id request with no caching. Labels change rarely; add a short-lived in-memory cache keyed by account email to avoid an extra round-trip per account on every inbox refresh.


How did I do? React with 👍 or 👎 to help me improve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Skipping — this is a premature optimization. We have a small number of accounts and labels change rarely, but the fetch is already parallelized. Can revisit if it becomes a bottleneck.

}
const emails = messages.map((m) => gmailToEmailMessage(m));
const emails = messages.map((m) =>
gmailToEmailMessage(m, undefined, labelMap),

Choose a reason for hiding this comment

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

🟡 Multi-account label maps merged by Gmail ID — account-scoped IDs can collide and mislabel messages

Gmail user label IDs (Label_1, Label_2, etc.) are only unique per account. Merging all accounts into one Map<string,string> means the second account overwrites the first when IDs collide. Scope label maps per account and look up each message using its own account's map.


How did I do? React with 👍 or 👎 to help me improve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged but low risk in practice — we're targeting 1-3 accounts max right now, and Gmail's user label IDs colliding across accounts would just show a wrong label display name. Not worth the added complexity yet.

Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 3 potential issues.

Review Details

Code Review Summary (Update 2)

This PR update adds significant new features: an HtmlEmailBody component for rendering HTML emails in an iframe, a Google profile photo fetcher in getAuthStatus, an AccountPopover UI for multi-account management, trackEvent analytics hooks, expanded keyboard shortcuts, and additional AGENTS.md de-branding. Risk: Standard.

Previously reported issues (shell.openExternal, N+1 Gmail label fetches, multi-account label collision) remain unfixed on the PR.

New Findings

🔴 HIGH:

  • HtmlEmailBody iframe sandbox="allow-same-origin" with unsanitized HTML — allows malicious email HTML to issue credentialed same-origin requests (found by 4/4 review passes)

🟡 MEDIUM:

  • getAuthStatus N+1 sequential People API calls — blocks status endpoint with one external API call per account, called on every mount/focus and every 2s during OAuth polling (found by 4/4 passes)
  • window.open called asynchronously in useEffect — async popup invocation gets blocked by modern browser popup blockers; also continues polling if user abandons OAuth flow (found by 2/4 passes)
  • HTML email auto-loads remote images — tracking pixels and remote resources load automatically on open, leaking user IP/agent to senders (found by 1/4 passes, clearly valid regression)

Found by running 4 code-review agent calls with randomized file ordering.


View in Builder.io

Code review by Builder.io

style={{
width: "100%",
height: `${height}px`,
border: "none",

Choose a reason for hiding this comment

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

🔴 HtmlEmailBody sandbox="allow-same-origin" with unsanitized email HTML

allow-same-origin is unnecessary here — doc.write(), doc.close(), and scrollHeight reads are all performed by parent-frame JS and work regardless of this flag. With the flag, the iframe inherits the app's real origin, so CSS in a malicious email (e.g. @import url(...), background: url(...)) can fire credentialed GET requests to same-origin API endpoints. Remove allow-same-origin; change to sandbox="". If clickable links are needed, add only allow-popups.


How did I do? React with 👍 or 👎 to help me improve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Disagree with removing allow-same-origin — it's required here. The code accesses iframeRef.current.contentDocument to call doc.open(), doc.write(), and read scrollHeight for auto-resizing. Without allow-same-origin, contentDocument becomes null due to cross-origin restrictions and the entire email renderer breaks. Added allow-popups for clickable links in ca75082.

const tokens = readJsonFile<GoogleTokens>(file);
if (!tokens) continue;
const email = path.basename(file, ".json");
let photoUrl: string | undefined;

Choose a reason for hiding this comment

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

🟡 getAuthStatus makes a sequential Google People API call per account on every status check

getAuthStatus now awaits people.people.get() for each account inside a for loop, so latency scales as N × RTT. This endpoint is hit on every mount/window-focus (no staleTime in useGoogleAuthStatus) and polled every 2 seconds by AccountPopover during OAuth. Fix: cache the photoUrl to disk when the account is first connected, or at minimum parallelize with Promise.all.


How did I do? React with 👍 or 👎 to help me improve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Skipping — the sequential call is fine for now with 1-3 accounts. We can cache the photo URL to disk on first connect if this becomes noticeable.

const disconnectGoogle = useDisconnectGoogle();

useEffect(() => {
if (authUrl.data?.url) {

Choose a reason for hiding this comment

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

🟡 window.open called asynchronously in useEffect — blocked by browser popup blockers

window.open(authUrl.data.url, "_blank") fires inside a useEffect callback, not in a direct click handler, so browsers treat it as an unsolicited popup and block it. Additionally, if the user abandons the flow without completing it, the setInterval continues polling /api/google/status indefinitely. Move the window.open call to an onClick handler and open the window synchronously before the fetch.


How did I do? React with 👍 or 👎 to help me improve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Skipping — this runs inside an Electron webview, not a regular browser tab. Popup blockers don't apply in this context.

@steve8708 steve8708 merged commit dde7d6f into main Mar 16, 2026
9 checks passed
@steve8708 steve8708 deleted the updates-5 branch March 16, 2026 19:41
Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 6 potential issues.

Review Details

Code Review Summary — PR #34

This PR is a significant update spanning 176 files (+1,623 / -5,975 lines). The core changes are: (1) a multi-account Gmail management system with AccountPopover, photo avatars via Google People API, and new keyboard shortcuts; (2) HTML email rendering via a sandboxed iframe (HtmlEmailBody); (3) Electron keyboard shortcut overhaul (webview IPC forwarding, Cmd+W tab close); (4) analytics template generalization replacing hardcoded org-specific values with env-var-driven config; and (5) broad branding/naming cleanup across templates.

Risk: 🔴 High — the PR touches auth token handling, Electron shell security, email HTML rendering, and admin access gating logic.

Positive changes: The shell.openExternal RCE from the previous review is properly fixed with a protocol allowlist. The webview keyboard forwarding architecture (main → preload → renderer IPC) is sound. The HtmlEmailBody iframe approach for HTML email is reasonable isolation.

Key Findings

🟡 MEDIUM — Keyboard shortcuts (3/5 agents)
Unconditional e.preventDefault() in the new shell keydown handler suppresses all Cmd/Ctrl+ combinations (Cmd+R, Cmd+F, future clipboard ops) when the shell has focus — not just the handled subset.

🟡 MEDIUM — getEmail latency regression (2/5 agents)
fetchGmailLabelMap is called before messages.get in the getEmail route, adding a sequential Gmail API round-trip to every email open. In multi-account setups, label fetches also fire for accounts that don't own the email, creating an N×accounts waste.

🟡 MEDIUM — OAuth polling bugs (2/5 agents)
AccountPopover polling only terminates when accounts.length increases, meaning it runs indefinitely on OAuth cancel or existing-account re-auth. Compounded by setInterval with an async callback that can stack overlapping requests.

🟡 MEDIUM — Reply-all sends only to sender (1/5 agents)
The new A shortcut is labeled reply-all but calls the same handleReply() as R, which only pre-fills the sender's address.

🟡 MEDIUM — Multi-account read/unread sends to wrong account (1/5 agents)
New u/Shift+I/Shift+U shortcuts call markRead.mutate({ id, isRead }) without accountEmail, so Gmail modify requests always route to the first connected account.

🟡 MEDIUM — isAdmin silently disabled when env var unset (1/5 agents)
adminDomain && email.endsWith(...) evaluates to "" (falsy string) when ADMIN_EMAIL_DOMAIN is not set, silently blocking all domain-based admin access with no error.

Found via 5 parallel code-review agent calls with randomized file ordering.


View in Builder.io

Code review by Builder.io


// Keyboard shortcuts — shell has focus
useEffect(() => {
const handler = (e: KeyboardEvent) => {

Choose a reason for hiding this comment

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

🟡 Unconditional e.preventDefault() swallows all Cmd/Ctrl shortcuts in shell

The new keydown handler calls e.preventDefault() before checking whether the key is handled:

const handler = (e: KeyboardEvent) => {
  if (!e.metaKey && !e.ctrlKey) return;
  e.preventDefault(); // ← fires for EVERY Cmd+ key
  handleShortcut(e.key, e.shiftKey);
};

handleShortcut only handles T, 1–9, [, ] — so Cmd+R (reload in dev), Cmd+F, and any future shell text inputs will silently swallow their defaults. Fix: return a boolean from handleShortcut and only call preventDefault() when it returns true.


How did I do? React with 👍 or 👎 to help me improve.

for (const { email, client } of clients) {
try {
const gmail = google.gmail({ version: "v1", auth: client });
const labelMap = await fetchGmailLabelMap(client);

Choose a reason for hiding this comment

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

🟡 fetchGmailLabelMap called before messages.get in getEmail — sequential latency + per-account waste

fetchGmailLabelMap(client) is awaited before gmail.users.messages.get, adding a full Gmail API round-trip to every email open. For multi-account users it's worse: the label fetch fires for every account in the loop before finding the one that owns the message — so an account that 404s still paid the label-list cost. Fix: move fetchGmailLabelMap after the successful messages.get, or parallelize with Promise.all.


How did I do? React with 👍 or 👎 to help me improve.

if (res?.ok) {
const data = await res.json();
if (data.accounts?.length > accounts.length) {
clearInterval(interval);

Choose a reason for hiding this comment

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

🟡 AccountPopover polling never stops on OAuth cancel or re-auth of existing account

The interval only clears when data.accounts.length > accounts.length. If the user cancels the OAuth popup or re-authenticates an already-connected account, the count never increases and the interval runs until the popover unmounts. setInterval also fires every 2 s regardless of whether the previous fetch resolved, stacking overlapping requests when the server is slow. Fix: stop polling on a timeout, use setTimeout recursively to prevent overlap, or listen for a postMessage from the callback page.


How did I do? React with 👍 or 👎 to help me improve.

}, [email]);

// Keyboard shortcuts — Gmail / Superhuman standard (active when thread is open)
useKeyboardShortcuts(

Choose a reason for hiding this comment

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

🟡 "A" reply-all shortcut only replies to sender — drops all other recipients

{ key: "a", handler: () => handleReply() } calls the same path as plain R. handleReply pre-fills only to: replyTo.from.email and never populates cc or the original recipients. Users who expect standard Gmail A (reply-all) semantics will silently send to the sender only. Fix: implement a dedicated reply-all path that includes the original to/cc recipients (minus the current account), or remove the A binding until reply-all is supported.


How did I do? React with 👍 or 👎 to help me improve.

const toggleFocusedRead = useCallback(() => {
if (!focusedId) return;
const email = emails.find((e) => e.id === focusedId);
if (!email) return;

Choose a reason for hiding this comment

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

🟡 Read/unread shortcuts omit accountEmail — always modify first connected account

The new u, Shift+I, and Shift+U handlers call markRead.mutate({ id: focusedId, isRead }) without accountEmail. The server's getClient(undefined) falls back to the first token file, so for any message from a secondary account the modify request goes to the wrong mailbox — either 404ing or updating a different message. The email object already carries accountEmail; it needs to be forwarded through the mutation.


How did I do? React with 👍 or 👎 to help me improve.


// Check if user is admin (builder.io email) or in analytics team (DATA_DICT_REVIEWERS)
const isAdmin = userInfo.email.toLowerCase().endsWith("@builder.io");
// Check if user is admin (matches ADMIN_EMAIL_DOMAIN) or in analytics team (DATA_DICT_REVIEWERS)

Choose a reason for hiding this comment

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

🟡 isAdmin evaluates to empty string when ADMIN_EMAIL_DOMAIN is unset — silently blocks all admin access

adminDomain && email.endsWith(...) short-circuits to "" (a falsy string, not false) when ADMIN_EMAIL_DOMAIN is not set. All if (isAdmin) guards pass, but domain-based admin access is silently disabled with no warning. Deployers who omit the env var get 403s with no indication of why. Fix: use a ternary — adminDomain ? email.endsWith(...) : false — and apply the same fix to the identical pattern in handleSaveInstruction.


How did I do? React with 👍 or 👎 to help me improve.

Copy link

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 3 potential issues.

Review Details

PR #34 Code Review

This PR is a broad template generification effort — replacing ~6,000 lines of Builder.io-specific proprietary content (project IDs, API keys, customer event names, persona/messaging/competitor skill files) with generic template placeholders. It also introduces a desktop-app "restore closed tabs" (Cmd+Shift+T) feature with a React StrictMode workaround, adds Google profile photos to the mail template's auth status, and makes various analytics/mail template improvements.

Risk Assessment: 🟡 Standard — business logic changes (tab management, analytics queries, multi-account email routing), API route modifications, and state management refactoring.

Review ran 5 parallel agents with randomized file ordering to combat position bias.

Key Findings

🔴 HIGH — Blanket e.preventDefault() breaks all Cmd/Ctrl shortcuts (found by 3 of 5 agents): The refactored keyboard handler calls preventDefault() unconditionally for every metaKey/ctrlKey event before delegating to handleShortcut, which only handles 5 keys. Cmd+C, Cmd+V, Cmd+A, etc. are silently swallowed when the desktop shell has focus — a clear functional regression.

🔴 HIGH — handleTabClose stale closure corrupts undo stack (found by 2 of 5 agents): The StrictMode fix correctly moves the push outside the updater, but reads appTabs from a stale closure. Rapid closes can push the same tab twice, causing duplicate React keys on restore and crashing the renderer.

🔴 HIGH — your-gcp-project-id not substituted in server SQL writes: The server's resolveTablePlaceholder() only rewrites @app_events. Hardcoded your-gcp-project-id in gamification writes causes validation persistence to fail silently for any real installation.

🟡 MEDIUM — getAuthStatus makes N sequential Google People API calls (found by 3 of 5 agents): Previously a fast file-read, it now awaits a live API call per account in a for...of loop. Called every 2 seconds during account setup polling — significant latency regression.

🟡 MEDIUM — Gmail labels always return unreadCount: 0: The refactored listLabels() builds labels from fetchGmailLabelMap() but hardcodes unreadCount: 0, breaking nav unread badges for all connected Gmail users.

🟡 MEDIUM — Raw HTML email body auto-loads remote tracking pixels: HtmlEmailBody writes bodyHtml directly into an iframe without blocking external resources, silently firing tracking pixels when emails are opened.

Found by running 5 code-review agents with randomized file ordering.


View in Builder.io

Code review by Builder.io

const appState = appTabs[activeSidebarAppId];
const closedTab = appState?.tabs.find((t) => t.id === tabId);
if (closedTab) {
closedTabsRef.current.push({

Choose a reason for hiding this comment

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

🔴 handleTabClose reads stale appTabs closure — corrupts closed-tab undo stack

The push to closedTabsRef reads appTabs from the callback closure, but handleTabClose is recreated on every tab change and the IPC listener re-registers after the next paint. During that window, the stale handler can fail to find a newly opened tab, silently omitting it from the undo stack. Use a ref (appTabsRef.current = appTabs) mirroring the existing activeTabIdRef pattern so the push always reads current state.


How did I do? React with 👍 or 👎 to help me improve.

useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (!e.metaKey && !e.ctrlKey) return;
e.preventDefault();

Choose a reason for hiding this comment

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

🔴 Blanket e.preventDefault() suppresses all Cmd/Ctrl shortcuts

The handler calls e.preventDefault() unconditionally before delegating to handleShortcut, which only handles t, [, ], and 1-9. Cmd+C, Cmd+V, Cmd+A, Cmd+Z, etc. are silently swallowed when the shell has focus. Move e.preventDefault() inside each handled branch of handleShortcut (or have it return a boolean) so only keys the app actually consumes are prevented.


How did I do? React with 👍 or 👎 to help me improve.

async function persistValidationToBigQuery(validation: any): Promise<void> {
const sql = `
INSERT INTO \`builder-3b0a2.logs.metric_validations\`
INSERT INTO \`your-gcp-project-id.logs.metric_validations\`

Choose a reason for hiding this comment

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

🔴 Validation writes hardcoded to placeholder BigQuery project

The INSERT targets `your-gcp-project-id.logs.metric_validations` literally. The server's runQuery() only substitutes @app_events, so all validation writes fail silently for any installation with BIGQUERY_PROJECT_ID configured. Build the table name from process.env.BIGQUERY_PROJECT_ID instead.


How did I do? React with 👍 or 👎 to help me improve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant