diff options
| author | user <user@clank> | 2026-08-13 13:06:44 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-13 13:06:44 +0200 |
| commit | 6d9e46274c0d7613441338e10fea6972e4d335dd (patch) | |
| tree | 02a22eeb155f1a9e3182eb9a21caf034a0bb8d40 /cli/src/commands | |
| parent | cli: create sessions by attaching, to dodge the phantom-client bug (diff) | |
| download | sl0ppi-6d9e46274c0d7613441338e10fea6972e4d335dd.tar.gz sl0ppi-6d9e46274c0d7613441338e10fea6972e4d335dd.tar.xz sl0ppi-6d9e46274c0d7613441338e10fea6972e4d335dd.zip | |
cli: keep the status bar, so other projects are visible
Reported: only one tab for proj_a, where was proj_b? Both tabs existed --
list-tabs showed proj_b at position 1 the whole time -- but there was nothing on
screen to say so, because a generated session had NO tab bar and NO status bar.
sl0ppi passes its own `--layout` when creating a session, which replaces the
user's default layout, and the shipped config gets its chrome from
`layouts/default.kdl`'s `default_tab_template` (the zjstatus pane, which is what
draws the tab names). So a root with five projects looked exactly like a root
with one.
The obvious fix is to emit that `default_tab_template` into the generated
layout. It does not work: a templated tab comes back with `purpose=None`,
because the fork hangs a tab's purpose on the tab's root layout and templating
replaces it. Identity is load-bearing here -- with no purposes `sl0ppi ls` sees
no project tabs and `up` would happily create a second set of them. Caught by
checking list-tabs after the change rather than trusting the tab bar appearing.
So the template is INLINED instead: its body is taken, the project's panes are
substituted for its `children` node, and the result becomes the tab's contents.
The `tab` node keeps its own name/purpose/cwd, and the status bar comes along.
A template with no `children` node is skipped rather than mangled.
The extractor brace-matches by hand but respects strings and comments: a real
zjstatus template is full of `{index}` and `{name}` inside quoted format
strings, and counting those would truncate the block and emit a layout that does
not parse. It reads the live config, so a user's own default.kdl is honoured.
Verified on the test box: tab bar reads "1 proj_a 2 proj_b", tab purposes are
project:proj_a / project:proj_b, `sl0ppi ls` sees both, `up` still says "up to
date", and no phantom-client marker.
Diffstat (limited to 'cli/src/commands')
| -rw-r--r-- | cli/src/commands/workspace.ts | 34 |
1 files changed, 29 insertions, 5 deletions
diff --git a/cli/src/commands/workspace.ts b/cli/src/commands/workspace.ts index 4eec946..f688ce4 100644 --- a/cli/src/commands/workspace.ts +++ b/cli/src/commands/workspace.ts @@ -14,7 +14,12 @@ import { tmpdir } from "node:os"; import { checkFragment } from "./doctor.ts"; import { discoverProjects, LAYOUT_FILE, MARKER_DIR } from "../core/discover.ts"; import { UserError } from "../core/errors.ts"; -import { sessionLayoutKdl, singleTabLayoutKdl, type TabSpec } from "../core/layout.ts"; +import { + defaultTabTemplateFrom, + sessionLayoutKdl, + singleTabLayoutKdl, + type TabSpec, +} from "../core/layout.ts"; import { isNoOp, planTabs, type Plan } from "../core/plan.ts"; import { projectPurpose } from "../core/purpose.ts"; import { rememberRoot, rootExists, type ResolvedRoot } from "../core/root.ts"; @@ -32,9 +37,28 @@ export interface Context { * it BY ATTACHING to it. See `UpResult.createWith`. */ deferCreate?: boolean; + /** Cached tab template; "" means "looked, found none". */ + tabTemplate?: string; log: (message: string) => void; } +/** + * The tab template every generated tab is wrapped in, so sessions keep the + * status/tab bar the shipped config provides. Without it a multi-project + * session shows no tab bar at all and the other projects are invisible. + */ +function tabTemplate(ctx: Context): string | undefined { + if (ctx.tabTemplate === undefined) { + ctx.tabTemplate = defaultTabTemplateFrom(sl0ppijConfigDir()) ?? ""; + } + return ctx.tabTemplate === "" ? undefined : ctx.tabTemplate; +} + +export function sl0ppijConfigDir(env: NodeJS.ProcessEnv = process.env): string { + if (env.SL0PPIJ_CONFIG_DIR) return env.SL0PPIJ_CONFIG_DIR; + return join(env.HOME ?? "", ".config", "sl0ppij"); +} + function fragmentFor(root: string, relPath: string): string | undefined { const path = join(root, relPath, MARKER_DIR, LAYOUT_FILE); try { @@ -165,7 +189,7 @@ export function up(ctx: Context): UpResult { // one rather than an error, so `sl0ppi` still gives you a terminal. const layout = specs.length > 0 - ? sessionLayoutKdl(specs) + ? sessionLayoutKdl(specs, tabTemplate(ctx)) : "layout {\n tab name=\"scratch\" {\n pane\n }\n}\n"; const dir = mkdtempSync(join(tmpdir(), "sl0ppi-layout-")); const layoutPath = join(dir, "session.kdl"); @@ -217,7 +241,7 @@ export function up(ctx: Context): UpResult { for (const spec of specs) { try { - client.newTab(singleTabLayoutKdl(spec)); + client.newTab(singleTabLayoutKdl(spec, tabTemplate(ctx))); ctx.log(`+ ${spec.relPath}`); } catch (error) { const message = error instanceof Error ? error.message.split("\n")[0] : String(error); @@ -299,7 +323,7 @@ export function open(ctx: Context, name: string): TabInfo { const purpose = projectPurpose(relPath); let tab = client.listTabs().find((t) => t.purpose === purpose); if (!tab) { - const position = client.newTab(singleTabLayoutKdl(specFor(root, relPath))); + const position = client.newTab(singleTabLayoutKdl(specFor(root, relPath), tabTemplate(ctx))); ctx.log(`+ ${relPath}`); tab = client.listTabs().find((t) => t.position === position) ?? { position, @@ -353,5 +377,5 @@ function emptyPlan(): Plan { /** The layout sl0ppi would use for a project — for eyeballing and debugging. */ export function print(ctx: Context, name: string): string { const relPath = resolveProjectName(ctx, name); - return singleTabLayoutKdl(specFor(ctx.resolved.root, relPath)); + return singleTabLayoutKdl(specFor(ctx.resolved.root, relPath), tabTemplate(ctx)); } |
