diff --git a/ivette/src/renderer/Laboratory.tsx b/ivette/src/renderer/Laboratory.tsx
index 41b3b48c526edabe89dda4d766125678742a4b20..e9bee9670d02c2dbe22172ea9d7793babf75a57b 100644
--- a/ivette/src/renderer/Laboratory.tsx
+++ b/ivette/src/renderer/Laboratory.tsx
@@ -49,6 +49,7 @@ interface Split { H: number, V: number }
 interface Layout { A: compId, B: compId, C: compId, D: compId }
 
 interface TabViewState {
+  key: string,
   viewId: viewId,
   custom: number, /* -1: transient, 0: favorite, n: custom */
   split: Split,
@@ -327,14 +328,12 @@ function findTab(tabs: TabViewState[], viewId: viewId) : number
   return tabs.findIndex(tab => tab.viewId === viewId && tab.custom <= 0);
 }
 
-/*
-function duplicateTab(tabs: TabViewState[], viewId: viewId): number
+function newCustom(tabs: TabViewState[], viewId: viewId): number
 {
-  return 1 + tabs.reduce((n, { view, custom }) => (
-    view === viewId ? Math.max(n, custom) : n
+  return 1 + tabs.reduce((n, tab) => (
+    tab.viewId === viewId ? Math.max(n, tab.custom) : n
   ), 0);
 }
-*/
 
 function newTab(
   tabs: TabViewState[],
@@ -343,6 +342,7 @@ function newTab(
 ): TabViewState[]
 {
   return tabs.concat({
+    key: `${view.id}@${custom < 0 ? 0 : custom}`,
     viewId: view.id, custom,
     split: defaultSplit,
     stack: [makeViewLayout(view.layout)],
@@ -424,6 +424,16 @@ function applyTab(index = -1): void {
   });
 }
 
+function duplicateView(view: Ivette.ViewLayoutProps): void {
+  const state = LAB.getValue();
+  const custom = newCustom(state.tabs, view.id);
+  const tabs = newTab(state.tabs, view, custom);
+  LAB.setValue({
+    ...state,
+    tabs,
+  });
+}
+
 function closeTab(index: number): void {
   const state = LAB.getValue();
   const old = state.tabIndex;
@@ -883,11 +893,12 @@ function ViewItem(props: ViewItemProps): JSX.Element {
     const onDisplay = (): void => applyView(view);
     const onFavorite = (): void => applyFavorite(view, !favorite);
     const onRestore = (): void => restoreDefault(view);
+    const onDuplicate = (): void => duplicateView(view);
     const favAction = !favorite ? 'Add to Favorite' : 'Remove from Favorite';
     Dome.popupMenu([
       { label: 'Display View', enabled: !displayed, onClick: onDisplay },
       { label: favAction, onClick: onFavorite },
-      // { label: 'Duplicate View' },
+      { label: 'Duplicate View', onClick: onDuplicate },
       { label: 'Restore Default', enabled: modified, onClick: onRestore },
     ]);
   };
@@ -1158,14 +1169,14 @@ function TabView(props: TabViewProps): JSX.Element | null {
   const { tab, index, tabIndex } = props;
   const { viewId, custom } = tab;
   const view = Ext.useElement(VIEW, viewId);
-  if (!view /* || custom < 0*/) return null;
+  if (!view) return null;
   const selected = index === tabIndex;
   const top = tab.stack[0] ?? defaultLayout;
   const layout = selected ? props.layout : top;
   const modified = !compareLayout(layout, makeViewLayout(view.layout));
   const vname = view.label;
   const favorite = custom === 0;
-  const tname = custom > 0 ? `${vname} — ${custom}` : vname;
+  const tname = custom > 0 ? `${vname} ~ ${custom}` : vname;
   const label = modified ? `${tname}*` : tname;
   const tdup = custom > 0 ? 'Custom ' : '';
   const tmod = modified ? ' (modified)': '';
@@ -1210,9 +1221,9 @@ function TabView(props: TabViewProps): JSX.Element | null {
 export function Tabs(): JSX.Element {
   const [{ tabIndex, stack, tabs }] = States.useGlobalState(LAB);
   const layout = stack[0] ?? defaultLayout;
-  const items = tabs.map((tab, k) => (
+  const items = tabs.map((tab: TabViewState, k: number) => (
     <TabView
-      key={tab.viewId}
+      key={tab.key}
       tab={tab}
       index={k}
       tabIndex={tabIndex}