browser-tabs-ui
Skill0 starsChanged 10 days agoMITView on GitHub
Add browser-style app tabs with independent state and optional pinned live sessions. Use for app-wide browser tabs or parallel page instances; confirm the tab model only if unresolved. Ordinary page section tabs do not use this architecture.
In this repository34 other files it gives its agents.
- chatgpt/AGENTS.mdAGENTS.md
- opencode/AGENTS.mdAGENTS.md
- zcode/AGENTS.mdAGENTS.md
- claude/CLAUDE.mdCLAUDE.md
- agent-doc-authoringSkill
- agent-experienceSkill
- ai-image-promptsSkill
- api-behavior-flowSkill
- architecture-and-performanceSkill
- behavioral-testingSkill
- behavior-auditSkill
- code-qualitySkill
- context-compactSkill
- design-principlesSkill
- design-to-deliverySkill
- design-validationSkill
- e2e-test-authoringSkill
- engineering-reviewSkill
- excalidrawSkill
- external-ai-consultSkill
- google-omni-videoSkill
- llm-agent-tool-disciplineSkill
- parallel-subagentsSkill
- qa-test-scenariosSkill
- reliable-executionSkill
- research-and-reuseSkill
- server-security-hardeningSkill
- session-handoffSkill
- shadcn-librarySkill
- sudo-handoffSkill
- task-intakeSkill
- tauri-v2-docsSkill
- test-failure-triageSkill
- user-behavior-flowSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 65
Check tablist/tab/tabpanel semantics where appropriate, visible focus, keyboard selection/activation/close, and focus restoration after …
- Distinctive: line 67
Start a session → pinned tab appears green.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Browser-Style Tabs — UI shell pattern
Purpose
Give an app a tab strip where each tab holds independent work (its own page instance, state, and live sessions), switching never loses state, and live things (screen sessions, connections) appear as pinned status tabs. This skill captures the architecture and the four pitfalls that cost real debugging time.
When to use
- "Add tabs like a web browser / like OpenCode", "tab bar", "+ new tab", "two File Managers at once", "parallel sessions as tabs".
- Retrofitting tabs onto an app whose pages currently unmount on navigation (state loss on view switch).
- Ambiguous tab requests require model clarification; ordinary section tabs use the project's existing component system rather than this app-shell architecture.
Preconditions
- Know the app's view/routing state (e.g., a
currentViewunion + conditional rendering). - Pages keep state in component-local state or stores; identify which state must survive tab switches.
Workflow
- Establish the tab MODEL first (see Rules #1). Use the user's current/prior instructions and existing architecture. If still ambiguous, ask with a tiny mockup; do not re-confirm a model already chosen.
- Create a tab store (zustand or equivalent) — the whole model is ~60 lines:
Invariants: never zero tabs (fall back to Home); closing the active tab activates the next-or-previous neighbor; closing a session tab restoresinterface AppTab { key: string; view: View } // key = stable per-tab identity state: { tabs: AppTab[]; activeKey: string; previousKey: string | null; // return target after a pinned session closes presence: Record<string, string | null>; // per-tab status (e.g. connected device) } actions: openView(view, {forceNew}) // focus existing tab of the view, or create navigateActive(view) // change the ACTIVE tab's page — no new tab closeTab(key) // activate a neighbor; if last tab → open Home activateSession() / sessionOpened() / sessionClosed()previousKey(validated — it may have been closed meanwhile). - Render keep-alive: map over tabs and mount EVERY tab's page, hidden when inactive — never unmount on switch:
Passing the tab key as the page component's prop is what enables multiple instances of the SAME page (two file managers). Hidden-not-unmounted is what preserves state and keeps timers, sockets, and tests running.{tabs.map(t => <div key={t.key} className={t.key === activeKey ? "h-full" : "hidden"}> <PageForView view={t.view} instanceKey={t.key} /> // key → independent instances </div>)} - Model live sessions as synthetic pinned tabs — NOT entries in
tabs[]. A screen-share/room/session tab exists while the connection exists, shows status (pulsing green), has no close ✕ (it closes via the session's own Disconnect), andsessionOpened()rememberspreviousKey/sessionClosed()restores it. - Navigation semantics (the corrected model): sidebar/menu items NAVIGATE the active tab (
navigateActive) — page changes never spawn tabs. Tabs are created ONLY explicitly via the "+" button. If the active tab is the synthetic session tab, navigate by focusing a page tab instead. "+" opens a page-picker menu; pressing "+" again while the menu is open opens a new Home tab directly. - Per-tab presence: pages report their status up via the store keyed by their tab key (e.g.,
setFmDevice(tabKey, deviceName)in a mount/update effect with cleanup). The strip renders the green dot / device name from that map. Keying by tab key is what keeps two same-page instances independent.
Rules
- Resolve ambiguous tab semantics before coding. The three candidates: (a) app-wide browser tabs (this skill), (b) one tab strip per page for parallel sessions, (c) page-level section tabs. Ask only when session/repository evidence does not settle the choice; this workflow applies primarily to (a).
- Dropdowns must never live inside an
overflow-auto/scroll/hiddenancestor. CSS clips them invisibly — the menu "doesn't open" from the user's view. Either portal the menu todocument.bodywith fixed positioning measured viagetBoundingClientRect, or place the trigger+menu outside the scroll container. Portals are the robust default. - Outside-click detection must know about portals: a portal'd menu is NOT inside the trigger's ref — the outside-click check must exclude both the trigger element and the menu panel ref, or every menu click closes it.
- Key by tab key, not by view. Two tabs of the same page must be two independent instances; reusing the view as key merges them.
- Keep-alive consumes real resources. Hidden tabs can retain DOM, timers, sockets and memory. Preserve required live sessions, gate background UI work/input on activity and clean up on close. Set limits/eviction only from observed workload and state-preservation requirements; do not invent an arbitrary 10-tab threshold.
- Sessions are not pages. A live session tab is derived state from the connection, never a user-created tab, and its close path is the session's Disconnect (ideally confirmed).
- Global stores stay global across tabs (transfers, devices, toasts); only per-tab UI state lives in instances. Decide deliberately which is which before rendering multiple instances.
Common mistakes
- Putting the + menu inside the tab strip's
overflow-x-auto→ menu silently clipped (bug looked like "menu won't open" and "old version" to the user). - Reusing
viewinstead of a uniquekeyfor page instances → two tabs share one component instance. - Creating a tab on every sidebar click → tab pollution. Navigation ≠ tab creation.
- Losing the "return to where I was" behavior: closing a pinned session tab must restore
previousKey(and validate it still exists). - Forgetting effect cleanup when a page reports presence — stale green dots after the page instance goes away.
- An
aria-liveregion oraria-hiddenwrapper around hidden tabs without handling focus — hidden-iframes/canvas keep running; pause input capture on inactive tabs (gate key handlers on anactiveprop).
Validation
- Check tablist/tab/tabpanel semantics where appropriate, visible focus, keyboard selection/activation/close, and focus restoration after closing; hidden panels must not keep capturing keyboard input.
- Open three tabs including two of the SAME page → each keeps independent state across switches.
- Start a session → pinned tab appears green; switch tabs → session keeps streaming; disconnect → session tab disappears and the previous tab refocuses.
- "+" menu opens fully visible below the + (test with the strip at max width); ✕, outside click, and Escape close it; "+ again" opens Home.
- Close tabs in order: active → neighbor focuses; last → Home appears; a page reporting presence clears its dot when its tab closes or navigates away.
- Type in an input on tab A, switch to B, return → text and focus state behave (no keyboard capture stealing between tabs).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

