Skip to content

v0.2.6

Pre-release
Pre-release

Choose a tag to compare

@AbdelrahmanBerchan AbdelrahmanBerchan released this 19 Apr 06:30
· 92 commits to main since this release
579f626

Axis Browser v0.2.6

Branding — macOS

  • Axis_logo.png (project root) is the single source for the app image: Dock (app.dock.setIcon), all BrowserWindow icon paths (main, secondary, incognito), and About Axis (dialog.showMessageBox with the same nativeImage — the system role: 'about' panel cannot show a custom image in dev because it uses the app bundle icon). Previous code pointed at a non-existent src/Axis_logo.png.
  • Dock squircle: app.dock.setIcon() does not apply the same mask as a bundled .icns, so the logo is preprocessed with Jimp 1.x: contain 512×512superellipse n=4 alpha (full squircle silhouette), then resize that whole squircle to ~78% and center on a transparent 512×512 tile (safe-zone margin like system icons). Insetting the logo before masking produced a square blob. Disk cache under userData/dock-squircle-cache-v3.png skips Jimp when Axis_logo.png is unchanged. Dock icon applies after createWindow() so startup is not blocked. prestart/predev removed — patch script runs postinstall only (with a fast no-op when already patched).
  • About copy: Copyright © 2026 Abdelrahman Berchan.
  • electron-builder build.icon / mac.icon plus Axis_logo.png in build.files so packaged .app bundles get the correct icon in Finder and the Dock.
  • Display name “Axis”: root productName in package.json so app.getName() (About dialog, app menu) is Axis instead of lowercase axis. app.setName('Axis') remains at startup. macOS dev Dock (bottom bar): scripts/patch-electron-mac-display-name.js runs on postinstall: renames node_modules/electron/dist/Electron.appAxis.app, patches Info.plist (CFBundleDisplayName / CFBundleName), and rewrites node_modules/electron/path.txt so npx electron . launches Axis.app. path.txt must not end with a newline (Electron’s loader does not trim), or spawn fails with ENOENT. If the tooltip still shows “Electron” once, quit the app and run killall Dock (or log out) so Launch Services refreshes.

Settings window

  • White flash on open/close: backgroundColor from nativeTheme.shouldUseDarkColors (light #f5f5f7 / dark #1e1e1e) so the shell matches settings.html in light and dark (previously macOS-only light gray); html background in settings.html matches body before first paint; nativeTheme.on('updated') calls setBackgroundColor while the window is open. (paintWhenInitiallyHidden: false is not used — it prevents ready-to-show from firing in Electron.)

Security — dependencies

  • file-type (CVE-2026-31808 / GHSA-5v7r-6r5c-r473 — ASF / WMV-WMA detection infinite loop on malformed input): jimp is upgraded from 0.22.x to ^1.6.1 (uses file-type ^21). package.json overrides set file-type >= 21.3.1 so Dependabot’s vulnerable 16.x transitive range is not resolved.

Development — console noise

  • Quit / stderr: Native NSLog (e.g. representedObject is not a WeakPtrToElectronMenuModelAsNSObject, sysctlbynamekern.hv_vmm_present) bypasses Node’s process.stderr.write hook. npm start / npm run dev now use scripts/run-electron.js, which spawns Electron with stderr piped and drops lines matching scripts/suppress-stderr-patterns.js. src/main.js loads the same list for JS-side stderr/stdout.write filtering.

Startup / first interaction

  • Renderer init: loadSettings and refreshShortcutCache run in parallel (Promise.all) so the shell is ready sooner.
  • Page context menu: webview.executeJavaScript for speechSynthesis.speaking runs only when the menu will show Speech (selection + speech on). When needed, it is **Promise.race**d with ~90ms so a cold guest page cannot block the menu for seconds.
  • Main app.whenReady: warmUpMainProcessNativePaths()clipboard.readText(), getShortcuts(), throwaway Menu.buildFromTemplate — reduces first native context menu cold stall.

Tab groups — open / close animation

  • Open: Height is measured with .tab-group-content.open applied so padding and layout match the expanded state (no ad-hoc +48px buffer). transitionend on max-height (plus a short timeout fallback) clears the toggling class and resets max-height to none after the animation.
  • Close: max-height → 0 no longer fights .open padding under border-box (which caused stutter). Inline padding is pinned from getComputedStyle, then max-height and padding animate together to 0 / 0 6px; .open is removed only in finishClose after the transition so there is no one-frame padding snap.
  • Timing: ~200ms in src/renderer.js and src/styles.css with matching easing; double requestAnimationFrame where needed so the “from” state is committed before the transition runs.
  • Empty group: When the last tab is moved out of a group that was expanded, the group auto-collapses with the same runTabGroupCollapseAnimation as a manual close (unless settings.autoCollapseEmptyTabGroup is false).
  • Context menu target: Right-click on a tab inside a group no longer opens the tab group menu; the group’s capture-phase contextmenu handler skips when the hit target is a .tab inside .tab-group-content, so the tab menu runs as usual.
  • Move between groups: Tabs removed from a group’s DOM with remove() were no longer found by querySelector, so getOrCreateTabElement built duplicate tab rows (broken webview / selection). Tabs are now reparented into a hidden #tab-element-pool until re-mounted. addTabToTabGroup resolves string/number Map keys via findTabGroupKey, removes the tab from all other groups, sets destination open: true, and applies the same empty-group collapse animation for the source group when it becomes empty.
  • In-group reorder drag: Smooth vertical drag for tabs inside .tab-group-content clamps translateY and drop-slot math to the group content box (setupTabDragDrop / draggedPosCenter) and to the top of the tab list scroll port (max(group.top, #tabs-container top)) so rows cannot be dragged above the visible sidebar tab area.

Sidebar — smooth tab drag (setupTabDragDrop)

  • Pinned/unpinned separator (live): When the row directly below #tabs-separator is the item being dragged toward pinned tabs, the separator’s translateY now follows the last pinned row’s slide (sepShiftForIndex, firstUnpinnedIndex - 1) during the drag. Previously that case was skipped (j === currentDragIdx), so the line only moved after mouseup.
  • Pinned band (separator + New Tab + incognito): The same smoothed shift is applied to #sidebar-new-tab-btn and #incognito-indicator via setPinnedUnpinnedBandShift / clearPinnedUnpinnedBandTransforms, so the + New Tab row stays aligned with the separator during drag (not only on drop).
  • Top of tab list (main sidebar): For #tabs-container drags (tabs and tab groups), draggedCenter is clamped so the row center cannot move above #tabs-container’s viewport top (getBoundingClientRect().top) plus half row height — bottom is unconstrained (only the top edge is walled), matching the in-group idea but only for the upper bound.

Native context menus — behavior (src/renderer.js + src/main.js)

  • Search selection: “Search for …” opens a tab with getSearchUrl(trimmedSelection) (native IPC path and legacy DOM handler); previously it called createNewTab() with no URL.
  • Webview edit actions: webview.focus() before guest execCommand for cut / copy / select all. Copy falls back to window.getSelection() + navigator.clipboard.writeText when execCommand('copy') fails. Paste reads navigator.clipboard.readText() and inserts into the guest focused field (pastePlainIntoWebview) because execCommand('paste') is unreliable in <webview> guests; Paste and Match Style for the page reuses that injector after reading the clipboard once.
  • URL bar menu: Paste and Match Style inserts plain text into the focused URL field via insertTextInInput (clipboard read in the handler). It no longer calls pasteMatchStyle(), which targeted the wrong document and could lose focus after await on the clipboard.
  • Position: Menu.popup({ x, y }) was fed clientX / clientY from the renderer, which are not the same coordinate space Electron expects for menu.popup on macOS (title bar, scaling, etc.), so menus appeared far from the pointer. All native context menus again use menu.popup({ window }) only so the OS places the menu at the cursor (correct right-click position).

macOS menu bar (application menu)

  • Structure: File (new tab, new window/incognito, focus URL, print, Downloads; Quit only on non-macOS — Axis app menu handles Quit on darwin), Edit (standard roles plus Paste and Match Style, Copy Page URL, Copy URL as Markdown (Cmd/Ctrl+Shift+Alt+C), Find, darwin Delete / Start/Stop Speaking), Tabs (new/close/reopen/duplicate/pin/mute, Show Next/Previous Tab — macOS Option+Cmd+←/→, Windows/Linux Ctrl+Tab / Ctrl+Shift+Tab), View (Back/Forward/Stop/Reload, devtools, zoom, sidebar/chat, full screen), History, Window, Help.
  • Routing: Menu actions use sendBrowserShortcut() so the focused browser window (including incognito) receives browser-shortcut IPC, not only mainWindow.
  • Renderer: go-back, go-forward, stop-loading handled in executeBrowserShortcut; copy-url-markdown copies [title](url) (settings tab: [Settings](axis://settings)); next-tab / previous-tab use switchToAdjacentTab.
  • Defaults (Windows/Linux): pin-tab default is Ctrl+Alt+P so it does not duplicate File → New Incognito (Ctrl+Shift+P). macOS unchanged (Shift+Cmd+P).
  • Help menu: removed Axis Browser Help (readme link) and Keyboard Shortcuts… (shortcuts remain under the Axis app menu on macOS and Edit → Settings on other platforms).
  • Context menus: Consistent ordering in src/main.jssidebar: New Tab → Tab Group → Incognito → toggle → move; page: link → image → navigation → edit → selection (search/speech) → page (including Copy URL as Markdown); URL bar: cut/copy/paste/paste & match/select all → paste and go; tab: actions → (if in a tab group: Remove from Tab Group, Move to another tab group with other groups only; else Add to Tab Group when groups exist) → separator → Close; tab group unchanged (rename/duplicate/color/icon → delete). index.html templates and renderer.js listeners aligned; webpage DOM menu adds paste & match, markdown, print.
  • Sidebar tabs: Pinned tabs with an open webview use on the close control (empty pinned slots use ×); pinned/closed state refresh calls updateIcon. Pinned/unpinned separator: idle = full line (::before); hover = opacity crossfade to a shorter line (::after, --tabs-separator-short-right) — no right animation; Clear still fades in via opacity.

Full Changelog: v0.2.5...v0.2.6

Screen.Recording.2026-04-19.at.9.01.54.AM.mp4