agentleFS
omidnwomidnw/my-skills/skills/browser-tabs-ui/SKILL.md

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.

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
  1. Very specific:

    Check tablist/tab/tabpanel semantics where appropriate, visible focus, keyboard selection/activation/close, and focus restoration after …

    line 65
  2. Distinctive:

    Start a session → pinned tab appears green.

    line 67

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 currentView union + conditional rendering).
  • Pages keep state in component-local state or stores; identify which state must survive tab switches.

Workflow

  1. 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.
  2. Create a tab store (zustand or equivalent) — the whole model is ~60 lines:
    interface 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()
    Invariants: never zero tabs (fall back to Home); closing the active tab activates the next-or-previous neighbor; closing a session tab restores previousKey (validated — it may have been closed meanwhile).
  3. Render keep-alive: map over tabs and mount EVERY tab's page, hidden when inactive — never unmount on switch:
    {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>)}
    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.
  4. 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), and sessionOpened() remembers previousKey/sessionClosed() restores it.
  5. 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.
  6. 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

  1. 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).
  2. Dropdowns must never live inside an overflow-auto/scroll/hidden ancestor. CSS clips them invisibly — the menu "doesn't open" from the user's view. Either portal the menu to document.body with fixed positioning measured via getBoundingClientRect, or place the trigger+menu outside the scroll container. Portals are the robust default.
  3. 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.
  4. 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.
  5. 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.
  6. 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).
  7. 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 view instead of a unique key for 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-live region or aria-hidden wrapper around hidden tabs without handling focus — hidden-iframes/canvas keep running; pause input capture on inactive tabs (gate key handlers on an active prop).

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).

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.