diff --git a/frontend/src/components/editor/EditorMenuBar.css b/frontend/src/components/editor/EditorMenuBar.css
index 502bc19a..ae014e3e 100644
--- a/frontend/src/components/editor/EditorMenuBar.css
+++ b/frontend/src/components/editor/EditorMenuBar.css
@@ -83,3 +83,18 @@
background: #3c3c3c;
margin: 4px 6px;
}
+
+/* Same PRO pill the old toolbar overflow used (tb-overflow-pro) — users
+ should know an item is premium BEFORE clicking, not via a surprise
+ upgrade prompt. */
+.emb-pro {
+ font-size: 9.5px;
+ font-weight: 700;
+ letter-spacing: 0.5px;
+ padding: 2px 6px;
+ border-radius: 3px;
+ background: linear-gradient(135deg, #ffd54f 0%, #ffb300 100%);
+ color: #1a1a1a;
+ flex-shrink: 0;
+ margin-left: 8px;
+}
diff --git a/frontend/src/components/editor/EditorMenuBar.tsx b/frontend/src/components/editor/EditorMenuBar.tsx
index 72e13adb..5f9c9d3e 100644
--- a/frontend/src/components/editor/EditorMenuBar.tsx
+++ b/frontend/src/components/editor/EditorMenuBar.tsx
@@ -31,7 +31,7 @@ import {
import './EditorMenuBar.css';
type Item =
- | { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string }
+ | { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string; pro?: boolean }
| { kind: 'link'; href: string; label: string }
| { kind: 'separator' };
@@ -89,12 +89,20 @@ export const EditorMenuBar: React.FC = () => {
{ kind: 'separator' },
{ kind: 'command', id: 'project.import', label: t('editor.toolbar.importLabel', 'Import project') },
{ kind: 'command', id: 'project.export', label: t('editor.toolbar.exportLabel', 'Export project (.zip)') },
- { kind: 'command', id: 'project.exportBom', label: t('editor.toolbar.exportBomLabel', 'Bill of Materials (CSV)') },
+ { kind: 'command', id: 'project.exportBom', label: t('editor.toolbar.exportBomLabel', 'Bill of Materials (CSV)'), pro: true },
{
kind: 'command',
id: 'project.exportScreenshot',
label: t('editor.toolbar.exportScreenshotLabel', 'Schematic image (PNG)'),
+ pro: true,
},
+ { kind: 'separator' },
+ // The toolbar's "..." menu folded in here — same actions, same PRO
+ // pills, one button fewer in the strip.
+ { kind: 'command', id: 'project.share', label: t('editor.toolbar.shareLabel', 'Share / Embed') },
+ { kind: 'command', id: 'project.githubSync', label: t('editor.toolbar.githubSyncLabel', 'Sync to GitHub'), pro: true },
+ { kind: 'command', id: 'firmware.upload', label: t('editor.toolbar.uploadFirmwareLabel', 'Upload firmware') },
+ { kind: 'command', id: 'sim.record', label: t('editor.toolbar.recordLabel', 'Record simulation'), pro: true },
];
const helpItems: Item[] = [
@@ -142,7 +150,10 @@ export const EditorMenuBar: React.FC = () => {
runEditorCommand(item.id);
}}
>
- {item.label}
+
+ {item.label}
+ {item.pro && PRO}
+
{item.shortcut && {item.shortcut}}
);
diff --git a/frontend/src/components/editor/EditorToolbar.css b/frontend/src/components/editor/EditorToolbar.css
index 50e73035..3a071aa9 100644
--- a/frontend/src/components/editor/EditorToolbar.css
+++ b/frontend/src/components/editor/EditorToolbar.css
@@ -350,67 +350,11 @@
}
/* ── Overflow (…) menu ──────────────────────────── */
-.tb-overflow-wrap {
- position: relative;
-}
-.tb-btn-overflow {
- color: #9d9d9d;
-}
-.tb-btn-overflow:hover {
- background: rgba(255, 255, 255, 0.07);
- color: #ccc;
-}
-.tb-btn-overflow-active {
- background: rgba(255, 255, 255, 0.1);
- color: #fff;
-}
-.tb-overflow-menu {
- position: absolute;
- top: calc(100% + 6px);
- right: 0;
- z-index: 200;
- background: #2a2a2c;
- border: 1px solid #3a3a3c;
- border-radius: 10px;
- padding: 6px;
- min-width: 180px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-.tb-overflow-item {
- display: flex;
- align-items: center;
- gap: 10px;
- width: 100%;
- padding: 8px 10px;
- background: transparent;
- border: none;
- border-radius: 7px;
- color: #ccc;
- font-size: 13px;
- font-family: inherit;
- cursor: pointer;
- text-align: left;
- white-space: nowrap;
- transition:
- background 0.12s,
- color 0.12s;
-}
-.tb-overflow-item:hover {
- background: rgba(255, 255, 255, 0.08);
- color: #fff;
-}
-.tb-overflow-item svg {
- flex-shrink: 0;
- color: var(--color-fg-default);
-}
/* ── Run split-button (multi-board: primary Run = all boards + menu) ──── */
.tb-run-split {
@@ -468,24 +412,10 @@
cursor: default;
}
-.tb-overflow-label {
- flex: 1;
- text-align: left;
-}
/* Pro pill — signals premium items in the overflow menu BEFORE the user
clicks. Used in place of a hidden gate so free users aren't surprised
by an upgrade prompt on what looked like a free button. */
-.tb-overflow-pro {
- font-size: 9.5px;
- font-weight: 700;
- letter-spacing: 0.5px;
- padding: 2px 6px;
- border-radius: 3px;
- background: linear-gradient(135deg, #ffd54f 0%, #ffb300 100%);
- color: #1a1a1a;
- flex-shrink: 0;
-}
/* ── Error detail bar (for long compiler errors) ── */
.toolbar-error-detail {
diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx
index 71589ec6..843e3b7f 100644
--- a/frontend/src/components/editor/EditorToolbar.tsx
+++ b/frontend/src/components/editor/EditorToolbar.tsx
@@ -255,8 +255,6 @@ export const EditorToolbar = ({
const firmwareInputRef = useRef(null);
const toolbarRef = useRef(null);
const [missingLibHint, setMissingLibHint] = useState(false);
- const [moreMenuOpen, setMoreMenuOpen] = useState(false);
- const moreMenuRef = useRef(null);
// Split-button menu for the multi-board Run control ("Run all" / "Run active only").
const [runMenuOpen, setRunMenuOpen] = useState(false);
const runMenuRef = useRef(null);
@@ -291,23 +289,6 @@ export const EditorToolbar = ({
return () => window.removeEventListener('velxio-circuit-fault', onFault);
}, [setCompileLogs]);
- useEffect(() => {
- if (!moreMenuOpen) return;
- const onClickOutside = (e: MouseEvent) => {
- if (moreMenuRef.current && !moreMenuRef.current.contains(e.target as Node)) {
- setMoreMenuOpen(false);
- }
- };
- const onEsc = (e: KeyboardEvent) => {
- if (e.key === 'Escape') setMoreMenuOpen(false);
- };
- document.addEventListener('mousedown', onClickOutside);
- document.addEventListener('keydown', onEsc);
- return () => {
- document.removeEventListener('mousedown', onClickOutside);
- document.removeEventListener('keydown', onEsc);
- };
- }, [moreMenuOpen]);
// Close the Run split-menu on outside click / Escape (mirrors the more-menu).
useEffect(() => {
@@ -1393,24 +1374,40 @@ export const EditorToolbar = ({
// File-menu commands owned by this toolbar (the handlers close over its
// state). Registered through a latest-ref so the one-time registration
// always invokes the current render's closure, never a stale one.
- const menuCommandsRef = useRef({
+ const makeMenuCommands = () => ({
import: () => importInputRef.current?.click(),
export: () => void handleExport(),
bom: () => void handleExportBom(),
screenshot: () => void handleExportScreenshot(),
+ firmware: () => firmwareInputRef.current?.click(),
+ // Pro actions fire the same window events the old "..." menu items
+ // fired; without the overlay they are silent no-ops, which is fine —
+ // OSS builds cannot have linked repos or shared projects anyway.
+ share: () =>
+ window.dispatchEvent(new CustomEvent('velxio-pro-share-prompt', {
+ detail: { projectId: currentProject?.id ?? null },
+ })),
+ githubSync: () =>
+ window.dispatchEvent(new CustomEvent('velxio-pro-github-sync-prompt', {
+ detail: { projectId: currentProject?.id ?? null },
+ })),
+ record: () =>
+ window.dispatchEvent(new CustomEvent('velxio-pro-replay-record-toggle', {
+ detail: { projectId: currentProject?.id ?? null },
+ })),
});
- menuCommandsRef.current = {
- import: () => importInputRef.current?.click(),
- export: () => void handleExport(),
- bom: () => void handleExportBom(),
- screenshot: () => void handleExportScreenshot(),
- };
+ const menuCommandsRef = useRef(makeMenuCommands());
+ menuCommandsRef.current = makeMenuCommands();
useEffect(() => {
const offs = [
registerEditorCommand('project.import', () => menuCommandsRef.current.import()),
registerEditorCommand('project.export', () => menuCommandsRef.current.export()),
registerEditorCommand('project.exportBom', () => menuCommandsRef.current.bom()),
registerEditorCommand('project.exportScreenshot', () => menuCommandsRef.current.screenshot()),
+ registerEditorCommand('firmware.upload', () => menuCommandsRef.current.firmware()),
+ registerEditorCommand('project.share', () => menuCommandsRef.current.share()),
+ registerEditorCommand('project.githubSync', () => menuCommandsRef.current.githubSync()),
+ registerEditorCommand('sim.record', () => menuCommandsRef.current.record()),
];
return () => offs.forEach((off) => off());
}, []);
@@ -1757,104 +1754,8 @@ export const EditorToolbar = ({
a small "PRO" pill in the menu so users know they're
premium BEFORE clicking, instead of being surprised by an
upgrade prompt. */}
-
-
- {moreMenuOpen && (
-
-
- {/* Sync to GitHub — Pro feature. Fires a window event the
- pro overlay listens for; if no overlay is loaded (OSS
- build) the click is a silent no-op which is fine —
- OSS users can't have linked repos anyway. */}
-
- {/* Share / Embed — free for all users with a public project.
- Watermark removal on the embed is the Pro perk; the
- Share modal itself is open to everyone so they can
- copy the link / iframe snippet. */}
-
- {/* Record simulation — Pro feature. Dispatches a toggle the
- pro overlay handles (plan check, board-type check,
- start/stop the recorder). OSS build → no listener →
- silent no-op. */}
-
-
- )}
-
-
+ {/* The "..." menu is gone: every item it held now lives in the
+ File menu (with PRO pills where they apply). */}
{/* Output Console toggle */}
diff --git a/frontend/src/lib/editorCommands.ts b/frontend/src/lib/editorCommands.ts
index f2f05b0f..6e3c493e 100644
--- a/frontend/src/lib/editorCommands.ts
+++ b/frontend/src/lib/editorCommands.ts
@@ -25,6 +25,10 @@ export type EditorCommandId =
| 'project.exportBom'
| 'project.exportScreenshot'
| 'file.new'
+ | 'project.share'
+ | 'project.githubSync'
+ | 'firmware.upload'
+ | 'sim.record'
| 'view.reset'
| 'view.zoomIn'
| 'view.zoomOut';