elm-add-panel
SkillDocs & knowledgeAdd a new app-wide "Shared panel" (like Accounts/Starred Posts/Markdown) to the Elm SPA (frontends/elm-spa). Use when asked to add a new global overlay/panel driven from Shared.Model, not page-local state.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the elm-add-panel skill
What this skill tells your AI
The instructions your AI receives, as published by jonlatane/jonline in .claude/skills/elm-add-panel/SKILL.md and read by ahel’s review.
Rellm's Elm SPA has a handful of app-wide overlay panels (Accounts Panel, Starred Posts Panel, Admin Panel, Markdown Panel) that live in Shared.Model rather than any one page, so they're available everywhere and survive route changes. There's no central "list of panels" registry — each one is wired by hand into Shared.elm and UI.elm following the same shape. Add a new one the same way.
1. The panel module itself
New file: frontends/elm-spa/src/Shared/<Name>Panel.elm, exposing Model, Msg(..), init, update, view (and whatever helpers callers need).
updatesignature depends on whether the panel needs to resolve servers/accounts:- Needs
AccountsPanel.Model(to look up aServer/Account, make an RPC call, etc.):update : AccountsPanel.Model -> Msg -> Model -> ( Model, Cmd Msg, Maybe AccountsPanel.Msg ). The trailingMaybe AccountsPanel.Msglets the panel askShared.updateto forward something toAccountsPanelon its behalf (typicallyAccountsPanel.AccountRefreshedafter a token refresh) — it can't dispatch that itself without importingShared, which would cycle (Sharedimports the panel, not the other way around). SeeShared/StarredPostsPanel.elmandShared/MarkdownPanel.elm. - No server/account dependency at all: plain
update : Msg -> Model -> Model(noCmd, no tuple) — seeShared/AdminPanel.elm, the minimal example.
- Needs
- A panel module can freely import
Components.*,Shared.AccountsPanel,Shared.MaybeAccountRequest(none of those importShared) — but neverShared,UI, orUI.EmittedStylesheet, all of which depend onShared.Model, which embeds your panel'sModel. - "Always rendered" — the view renders in both open and closed states (never conditionally absent), so opening/closing is a pure CSS opacity/transform transition rather than the element mounting/unmounting. Drive that with
UI.Classes.openClosedClass isOpenadded as a class alongside"nav-panel"— see any panel'sview.
2. Wiring into Shared.elm
Mechanical, copy the shape of the existing StarredPostsPanelMsg/MarkdownPanelMsg branches:
- Add a field to
Shared.Model(e.g.yourPanel : YourPanel.Model). - Add a constructor to
Shared.Msg(e.g.YourPanelMsg YourPanel.Msg). - Initialize it in
Shared.init. - In
Shared.update, add a branch that calls the panel's ownupdate, applies any forwardedAccountsPanel.MsgviaAccountsPanel.update, andCmd.batchs both mapped commands together. If the panel hassubscriptions, batch those intoShared.subscriptionstoo (gate on the panel being open if the subscription is expensive/animation-only).
3. Mounting the view in UI.elm
Two patterns depending on how the panel opens:
- Toggled from a nav icon (Starred Posts): add a toggle button + the panel's own render as siblings in
headerNav, followingstarredPostsToggle/starredPostsPanel. - Opened contextually from a page (Markdown Panel, opened via a page's own Edit/Reply button dispatching
Effect.fromShared (Shared.YourPanelMsg (YourPanel.Open ...))): just mount it once, unconditionally, inlayout's children list:Html.map toMsg (yourPanel shared)whereyourPanel shared = Html.map Shared.YourPanelMsg (YourPanel.view shared.accountsPanel shared.yourPanel). - Either way, add an entry to
sharedBackdrop'spanelslist so a background tap closes it. That list is ordered nearest-first: a tap only closes the first (topmost, by z-index) open panel in the list, not all of them — put a lower-z-index panel later in the list. Setblurs = Trueif the panel should block interaction with the rest of the page while open (most editing/login-style panels do);Falseif it's meant to coexist with using the rest of the app (Starred Posts).
4. CSS
- Reuse the shared
.nav-panelbase rule (public/style/shared/accounts_panel.css) for position/opacity/pointer-events transition plumbing, then override position/size/z-index in your ownpublic/style/shared/<name>_panel.css. Link the new file inpublic/index.html— CSS files aren't auto-discovered. - Existing z-indices: Accounts Panel
20, Starred Posts Panel25(deliberately above Accounts). Pick your panel's z-index relative to those based on where it should sit if multiple are open at once. - To tint an element with a specific server's brand color (e.g. a submit button matching the relevant Post/Group's server) without importing
UI/UI.EmittedStylesheet(cycle risk), just add the classes[ frontendHostString, "background-color-primary" ](or-nav,-primary-5/10/25/50,-primary-background,border-color-primary...— seeUI/EmittedStylesheet.elm's doc comment for the full list).UI.EmittedStylesheet.view, mounted once app-wide, already emits a CSS rule per known server keyed by that literal host string as a class — no plumbing needed beyond the class names themselves. UseUI.Classes.hostnameToCSSClassto buildfrontendHostString(don't hand-roll it — it's the same escapingEmittedStylesheetused to generate the rule's own selector).- Each of these utility rules is emitted twice —
.<host> .background-color-nav(descendant) and.<host>.background-color-nav(same element, compound) — viawithDescendantsinEmittedStylesheet.elm. So you only need the literal host class on the one element that doesn't already have a themed ancestor; an element nested inside something that already carrieshostnameToCSSClass someHost(e.g. anything insideUI.elm's.navbar, which carriesmainFrontendHost's class) can skip repeating it and just add the bare utility class ("background-color-nav"alone).Shared.StarredPanel's "Organize" button does this (relies on.navbar'smainFrontendHostclass); its per-entry "Unstar" button instead addshostnameToCSSClass hostitself, since a starred post's originating server generally isn'tmainFrontendHostand there's no such ancestor to inherit from.
- Each of these utility rules is emitted twice —
Signals
- GitHub stars
- 65
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
elm-add-panel- Source
- github.com/jonlatane/jonline