一键导入
getbyshell
GetByShell desktop shell — niri WM integration, panel system, calendar widget, popover components, modal patterns, and Nix-based configuration.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
GetByShell desktop shell — niri WM integration, panel system, calendar widget, popover components, modal patterns, and Nix-based configuration.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Meta-skill for @tmnl/effect-sui development: Effect v4/effect-smol patterns, Sui ontology, PTB/Flow/Query/package modules, localnet testing, Move publishing, diagnostics, and Nix mission-control tooling.
Effect v4 Atom patterns — reactive state management with effect-atom. Atom.make, Atom.family, Registry, runtime atoms, and React integration via useAtomValue.
Effect v4 Schema patterns — defining, validating, transforming, and serializing data with Schema. Covers v4 API, v3→v4 migration, classes, structs, unions, collections, transformations.
Effect v4 Service patterns — Context.Service, Layer composition, ManagedRuntime, dependency injection, and service testing with @effect/vitest.
Skill governance codemod — create, inspect, update, delete, adopt, and dogfood skills. The meta-skill that governs all skills with CRUD protocols, verification utils, and self-referential governance.
NX workspace architecture — monorepo structure, generators, plugins, boundaries, Effect v4 isolation, dependency constraints, and workspace-map navigation.
| name | getbyshell |
| description | GetByShell desktop shell — niri WM integration, panel system, calendar widget, popover components, modal patterns, and Nix-based configuration. |
prereqs: none provides: getbyshell-architecture, getbyshell-nix, getbyshell-debugging children: CHANGELOG.md, references/INDEX.md governed-by: metaskill update-strategy: re-derive from src/lib/getbyshell/AGENTS.md + nix/lib/getbyshell/SPEC.md + systemd unit files update-trigger: surface topology changes, Tauri config changes, Nix lib changes, new widget added
GetByShell is a multi-surface widget system for the TMNL desktop environment. Each "surface" is an independent Wayland layer-shell window backed by a Tauri + Vite + React stack, deployed as systemd user services via Nix/Home-Manager. Surfaces communicate via Tauri IPC, PubSub channels, and serialized data contracts.
Not a single app. Not a component library. An ecosystem of persistent UI surfaces that compose the user's desktop shell.
nix/lib/getbyshell/ or nix/modules/getbyshell/src-shell/, src-panel-tauri/, src/lib/getbyshell/What are you doing?
│
├─ Debugging a crash / service not starting
│ → §1 Systemd Topology + §2 Common Failure Modes
│
├─ Working on the bar (48px strip)
│ → ref/bar.md + ref/popover.md + ref/modal.md
│
├─ Working on the panel surface
│ → ref/panel.md
│
├─ Working on calendar / Chronicle
│ → ref/calendar.md
│
├─ Modifying Nix service generation
│ → §3 Nix Library Architecture
│
├─ Adding a new surface
│ → §4 Adding a Surface
│
├─ Working on cross-surface communication
│ → §5 Shared Infrastructure
│
└─ Working on logging / observability
→ ref/logging.md
getbyshell.target ← Groups all surfaces
├── tmnl-bar-vite.service ← :1421 Vite dev server
├── tmnl-bar.service ← Tauri layer-shell (Layer::Top)
├── tmnl-panel-vite.service ← :1422 Vite dev server
└── tmnl-panel.service ← Tauri layer-shell (Layer::Overlay)
Key relationships:
tmnl-{name}-vite (Vite) + tmnl-{name} (Tauri)Requires + After its Vite serviceExecStartPre health check (curl loop on Vite port)After=graphical-session.target + ConditionEnvironment=WAYLAND_DISPLAYgetbyshell.target groups all surfaces, WantedBy=default.targetDiagnostic commands:
getbyshell-status # All surfaces status
getbyshell-logs # Tail all surface journals
getbyshell-bar-status # Bar-specific status
getbyshell-bar-logs # Bar-specific logs
systemctl --user status tmnl-panel.service
journalctl --user -u tmnl-panel --since "5 min ago" --no-pager
Service files location: ~/.config/systemd/user/ (generated by Home-Manager)
Symptom: PluginInitialization("log", "Error deserializing 'plugins.log' ... invalid type: map, expected unit")
Cause: tauri.conf.json has a plugins.log config object, but the Rust code initializes the plugin via Builder::new().build() (no config deserialization). Tauri tries to deserialize the JSON config into the plugin's config type, which expects unit {} not a map.
Fix: Either remove plugins.log from tauri.conf.json, or pass config through the Rust builder:
// WRONG
"plugins": { "log": { "level": "info" } }
// RIGHT — empty, let Rust code handle config
"plugins": {}
Symptom: Failed to setup app: a webview with label 'panel' already exists
Cause: tauri.conf.json declares a window with label X, AND the Rust setup hook creates a WebviewWindowBuilder with the same label. Tauri creates config windows first, then runs setup — collision.
Fix: If the Rust code creates the window programmatically (e.g., for GTK layer-shell transplanting), remove the window from tauri.conf.json:
// RIGHT — Rust creates the window
"windows": []
Symptom: Found ordering cycle: getbyshell.target/start after tmnl-panel.service/start after graphical-session.target/start
Cause: getbyshell.target has WantedBy=graphical-session.target AND After=tmnl-{name}.service, but services have After=graphical-session.target. Creates: graphical-session → target → service → graphical-session.
Fix: Target uses WantedBy=default.target, NOT graphical-session.target. Individual services handle their own graphical-session dependency.
Symptom: ExecStartPre fails, Tauri never starts.
Cause: Vite dev server didn't start in time. Check tmnl-{name}-vite service logs.
Fix: Check port conflicts, increase healthCheckTimeout in surface config, check Vite config validity.
Symptom: Blank/tiny/invisible content in the layer-shell window.
Cause: Compositor compositing bugs (especially WSLg).
Fix: Set WEBKIT_DISABLE_COMPOSITING_MODE=1 environment variable.
Location: nix/lib/getbyshell/
nix/lib/getbyshell/
├── SPEC.md # Design specification
├── default.nix # Flake-parts module (mission-control + exports)
├── hm-module.nix # Home-Manager module (systemd services)
├── types.nix # Surface submodule type definition
├── surface.nix # mkSurface: generates service pair from config
├── shared-env.nix # mkSharedEnv: runtime PATH/PKG_CONFIG/LD_LIBRARY
├── health-check.nix # mkHealthCheck: curl loop per surface
├── mission-control.nix # mkSurfaceScripts + mkGlobalScripts
└── target.nix # mkTarget: getbyshell.target + legacy aliases
Consumer API (in ~/.config/nix/modules/home/getbyshell.nix):
gbg.getbyshell = {
enable = true;
projectDir = "/path/to/packages/tmnl";
surfaces = {
bar = { port = 1421; layer = "top"; viteConfig = "vite.config.shell.ts"; tauriDir = "src-shell-tauri"; };
panel = { port = 1422; layer = "overlay"; viteConfig = "vite.config.panel.ts"; tauriDir = "src-panel-tauri"; };
};
};
Adding a surface = add an attrset. All services, scripts, and health checks generate automatically.
src-{name}-tauri/ with Cargo.toml, tauri.conf.json, src/lib.rsvite.config.{name}.ts at package rootsurfaces.newname = {
port = 1423;
layer = "overlay";
viteConfig = "vite.config.newname.ts";
tauriDir = "src-newname-tauri";
};
sudo nixos-rebuild switch to deploy systemd servicesgetbyshell-newname-start to bring it upCritical: If Rust code creates the webview programmatically (layer-shell transplant), set "windows": [] in tauri.conf.json.
listen() / emit() within a surfacepkill -USR1 tmnl-panel → toggle handlerupdate_input_region — pointer-event pass-through syncset_surface_width — dynamic surface expansion for popovers/modalsshell_log_batch — structured log batching to journaldAtom.make() primary, services mutate via Atom.set(), React subscribessrc/lib/getbyshell/ # Core lib (bar state, hooks, services)
├── types.ts # Schema: Workspace, NiriWindow, ConnectionStatus
├── atoms.ts # Runtime atom, workspace/window/clock state, operations
├── hooks.ts # useClockTick, useNiriSync, useWorkspaces, etc.
├── niri.ts # NiriService (Effect.Service → Tauri IPC)
├── popover/ # Floating panel compound component
├── modal/ # Full-overlay compound component
├── calendar/ # Month grid + Chronicle day entities
│ └── chronicle/ # Rich day entity (schemas, state, machines, services, atoms)
├── logging/ # Effect Logger → Tauri IPC bridge
└── index.ts # Public API barrel
src-shell/ # Bar surface entry points + components
├── App.tsx # Bar root
├── main.tsx # Vite entry
├── components/ # BarLayout, Clock, CalendarPanel, etc.
└── *.html # Multi-entry HTML files
src-panel-tauri/ # Panel surface (Rust + Tauri)
├── src/lib.rs # Layer-shell setup, SIGUSR1 handler, toggle logic
└── tauri.conf.json # Panel Tauri config (windows: [] — Rust creates them)
nix/lib/getbyshell/ # Nix library (service generation)
nix/modules/getbyshell/ # Monorepo flake-parts module
function_casts_as_integer warning on signal handler cast — cosmetic, fix with handler as *const () as libc::sighandler_tgetbyshell.target dependency cycle if WantedBy=graphical-session.target — must use default.targetpkill -USR1 tmnl-panel or bar IPC