diff --git a/extension/src/popup/components/item-form.ts b/extension/src/popup/components/item-form.ts
index 6f513c2..5773e6e 100644
--- a/extension/src/popup/components/item-form.ts
+++ b/extension/src/popup/components/item-form.ts
@@ -1,7 +1,7 @@
/// Typed-item add/edit form dispatcher. Each type's renderForm lives in
/// its own module under ./types/. Document stays "coming soon" until γ.
-import { navigate, getState, setState, escapeHtml, popOutToTab } from '../../shared/state';
+import { navigate, getState, setState, escapeHtml, popOutToTab, isInTab } from '../../shared/state';
import type { Item, ItemType } from '../../shared/types';
const TYPE_OPTIONS: Array<{ type: ItemType; icon: string; label: string }> = [
@@ -58,7 +58,7 @@ function renderTypeSelection(app: HTMLElement): void {
${TYPE_OPTIONS.map((opt) => `
diff --git a/extension/src/popup/components/types/__tests__/popout-button-fullscreen.test.ts b/extension/src/popup/components/types/__tests__/popout-button-fullscreen.test.ts
new file mode 100644
index 0000000..b9ccfb3
--- /dev/null
+++ b/extension/src/popup/components/types/__tests__/popout-button-fullscreen.test.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+
+vi.mock('../../../../shared/state', () => ({
+ sendMessage: vi.fn(),
+ getState: () => ({ newType: 'login', generatorDefaults: null, error: null, loading: false, vaultSettings: null, entries: [] }),
+ setState: vi.fn(),
+ navigate: vi.fn(),
+ escapeHtml: (s: string) => s,
+ popOutToTab: vi.fn(),
+ isInTab: () => true, // FULLSCREEN context
+ openVaultTab: vi.fn(),
+ registerHost: vi.fn(),
+}));
+
+vi.mock('../../generator-panel', () => ({
+ openGeneratorPanel: vi.fn(),
+ closeGeneratorPanel: vi.fn(),
+ isGeneratorPanelOpen: () => false,
+}));
+
+import * as login from '../login';
+import * as secureNote from '../secure-note';
+import * as identity from '../identity';
+import * as card from '../card';
+import * as key from '../key';
+import * as totp from '../totp';
+import * as documentType from '../document';
+
+const forms: Array<[string, (app: HTMLElement, mode: 'add' | 'edit', existing: null) => void]> = [
+ ['login', login.renderForm],
+ ['secure-note', secureNote.renderForm],
+ ['identity', identity.renderForm],
+ ['card', card.renderForm],
+ ['key', key.renderForm],
+ ['totp', totp.renderForm],
+ ['document', documentType.renderForm],
+];
+
+describe('popout-to-tab button (fullscreen context)', () => {
+ beforeEach(() => { document.body.innerHTML = '
'; });
+
+ it.each(forms)('%s form does NOT render the popout button', (_name, render) => {
+ render(document.getElementById('app')!, 'add', null);
+ expect(document.getElementById('popout-btn')).toBeNull();
+ });
+});
diff --git a/extension/src/popup/components/types/__tests__/popout-button.test.ts b/extension/src/popup/components/types/__tests__/popout-button.test.ts
new file mode 100644
index 0000000..3232f02
--- /dev/null
+++ b/extension/src/popup/components/types/__tests__/popout-button.test.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+
+vi.mock('../../../../shared/state', () => ({
+ sendMessage: vi.fn(),
+ getState: () => ({ newType: 'login', generatorDefaults: null, error: null, loading: false, vaultSettings: null, entries: [] }),
+ setState: vi.fn(),
+ navigate: vi.fn(),
+ escapeHtml: (s: string) => s,
+ popOutToTab: vi.fn(),
+ isInTab: () => false, // POPUP context
+ openVaultTab: vi.fn(),
+ registerHost: vi.fn(),
+}));
+
+vi.mock('../../generator-panel', () => ({
+ openGeneratorPanel: vi.fn(),
+ closeGeneratorPanel: vi.fn(),
+ isGeneratorPanelOpen: () => false,
+}));
+
+import * as login from '../login';
+import * as secureNote from '../secure-note';
+import * as identity from '../identity';
+import * as card from '../card';
+import * as key from '../key';
+import * as totp from '../totp';
+import * as documentType from '../document';
+
+const forms: Array<[string, (app: HTMLElement, mode: 'add' | 'edit', existing: null) => void]> = [
+ ['login', login.renderForm],
+ ['secure-note', secureNote.renderForm],
+ ['identity', identity.renderForm],
+ ['card', card.renderForm],
+ ['key', key.renderForm],
+ ['totp', totp.renderForm],
+ ['document', documentType.renderForm],
+];
+
+describe('popout-to-tab button (popup context)', () => {
+ beforeEach(() => { document.body.innerHTML = '
'; });
+
+ it.each(forms)('%s form renders the popout button', (_name, render) => {
+ render(document.getElementById('app')!, 'add', null);
+ expect(document.getElementById('popout-btn')).not.toBeNull();
+ });
+});
diff --git a/extension/src/popup/components/types/card.ts b/extension/src/popup/components/types/card.ts
index 481fa02..6c7725b 100644
--- a/extension/src/popup/components/types/card.ts
+++ b/extension/src/popup/components/types/card.ts
@@ -1,7 +1,7 @@
/// Card: number / holder / expiry MonthYear / cvv / pin / kind.
/// Detail view has a styled card-silhouette signature block.
-import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
+import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
import type { Item, ItemId, ManifestEntry, CardKind, Section, AttachmentRef } from '../../../shared/types';
import {
@@ -177,7 +177,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
${mode === 'add' ? 'new card' : 'edit card'}
-
+ ${isInTab() ? '' : '
'}
${state.error ? `
${escapeHtml(state.error)}
` : ''}
diff --git a/extension/src/popup/components/types/document.ts b/extension/src/popup/components/types/document.ts
index 62260df..cccdcf8 100644
--- a/extension/src/popup/components/types/document.ts
+++ b/extension/src/popup/components/types/document.ts
@@ -2,7 +2,7 @@
/// notes/tags + optional supplementary attachments.
/// Primary attachment is referenced by ID from the item's attachments array.
-import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
+import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
import {
@@ -88,7 +88,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
${isEdit ? 'edit document' : 'new document'}
-
+ ${isInTab() ? '' : '
'}
${state.error ? `
${escapeHtml(state.error)}
` : ''}