@agimon-ai/doompi-ui 0.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +1 -0
- package/dist/components/configOverlay.cjs +0 -0
- package/dist/components/configOverlay.cjs.map +1 -0
- package/dist/components/configOverlay.d.cts +0 -0
- package/dist/components/configOverlay.d.cts.map +1 -0
- package/dist/components/configOverlay.d.mts +0 -0
- package/dist/components/configOverlay.d.mts.map +1 -0
- package/dist/components/configOverlay.mjs +0 -0
- package/dist/components/configOverlay.mjs.map +1 -0
- package/dist/components/doomEditor.cjs +2 -0
- package/dist/components/doomEditor.cjs.map +1 -0
- package/dist/components/doomEditor.d.cts +59 -0
- package/dist/components/doomEditor.d.cts.map +1 -0
- package/dist/components/doomEditor.d.mts +59 -0
- package/dist/components/doomEditor.d.mts.map +1 -0
- package/dist/components/doomEditor.mjs +2 -0
- package/dist/components/doomEditor.mjs.map +1 -0
- package/dist/components/doomFooter.cjs +2 -0
- package/dist/components/doomFooter.cjs.map +1 -0
- package/dist/components/doomFooter.d.cts +32 -0
- package/dist/components/doomFooter.d.cts.map +1 -0
- package/dist/components/doomFooter.d.mts +32 -0
- package/dist/components/doomFooter.d.mts.map +1 -0
- package/dist/components/doomFooter.mjs +2 -0
- package/dist/components/doomFooter.mjs.map +1 -0
- package/dist/components/doomHeader.cjs +2 -0
- package/dist/components/doomHeader.cjs.map +1 -0
- package/dist/components/doomHeader.d.cts +34 -0
- package/dist/components/doomHeader.d.cts.map +1 -0
- package/dist/components/doomHeader.d.mts +34 -0
- package/dist/components/doomHeader.d.mts.map +1 -0
- package/dist/components/doomHeader.mjs +2 -0
- package/dist/components/doomHeader.mjs.map +1 -0
- package/dist/components/doomOverlay.cjs +2 -0
- package/dist/components/doomOverlay.cjs.map +1 -0
- package/dist/components/doomOverlay.d.cts +79 -0
- package/dist/components/doomOverlay.d.cts.map +1 -0
- package/dist/components/doomOverlay.d.mts +79 -0
- package/dist/components/doomOverlay.d.mts.map +1 -0
- package/dist/components/doomOverlay.mjs +2 -0
- package/dist/components/doomOverlay.mjs.map +1 -0
- package/dist/components/leaderHints.cjs +2 -0
- package/dist/components/leaderHints.cjs.map +1 -0
- package/dist/components/leaderHints.d.cts +37 -0
- package/dist/components/leaderHints.d.cts.map +1 -0
- package/dist/components/leaderHints.d.mts +37 -0
- package/dist/components/leaderHints.d.mts.map +1 -0
- package/dist/components/leaderHints.mjs +2 -0
- package/dist/components/leaderHints.mjs.map +1 -0
- package/dist/components/toolsOverlay.cjs +2 -0
- package/dist/components/toolsOverlay.cjs.map +1 -0
- package/dist/components/toolsOverlay.d.cts +29 -0
- package/dist/components/toolsOverlay.d.cts.map +1 -0
- package/dist/components/toolsOverlay.d.mts +29 -0
- package/dist/components/toolsOverlay.d.mts.map +1 -0
- package/dist/components/toolsOverlay.mjs +2 -0
- package/dist/components/toolsOverlay.mjs.map +1 -0
- package/dist/config.cjs +2 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +5 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +5 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +2 -0
- package/dist/config.mjs.map +1 -0
- package/dist/extension.cjs +2 -0
- package/dist/extension.cjs.map +1 -0
- package/dist/extension.d.cts +7 -0
- package/dist/extension.d.cts.map +1 -0
- package/dist/extension.d.mts +7 -0
- package/dist/extension.d.mts.map +1 -0
- package/dist/extension.mjs +2 -0
- package/dist/extension.mjs.map +1 -0
- package/dist/extensionName.cjs +2 -0
- package/dist/extensionName.cjs.map +1 -0
- package/dist/extensionName.d.cts +5 -0
- package/dist/extensionName.d.cts.map +1 -0
- package/dist/extensionName.d.mts +5 -0
- package/dist/extensionName.d.mts.map +1 -0
- package/dist/extensionName.mjs +2 -0
- package/dist/extensionName.mjs.map +1 -0
- package/dist/extensions/pi.cjs +2 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +2 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/footer.cjs +2 -0
- package/dist/footer.cjs.map +1 -0
- package/dist/footer.d.cts +5 -0
- package/dist/footer.d.cts.map +1 -0
- package/dist/footer.d.mts +5 -0
- package/dist/footer.d.mts.map +1 -0
- package/dist/footer.mjs +2 -0
- package/dist/footer.mjs.map +1 -0
- package/dist/harnessMetadata.cjs +2 -0
- package/dist/harnessMetadata.cjs.map +1 -0
- package/dist/harnessMetadata.d.cts +21 -0
- package/dist/harnessMetadata.d.cts.map +1 -0
- package/dist/harnessMetadata.d.mts +21 -0
- package/dist/harnessMetadata.d.mts.map +1 -0
- package/dist/harnessMetadata.mjs +2 -0
- package/dist/harnessMetadata.mjs.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +25 -0
- package/dist/index.d.mts +25 -0
- package/dist/index.mjs +1 -0
- package/dist/leader.cjs +1 -0
- package/dist/leader.d.cts +2 -0
- package/dist/leader.d.mts +2 -0
- package/dist/leader.mjs +1 -0
- package/dist/leaderRegistry.cjs +2 -0
- package/dist/leaderRegistry.cjs.map +1 -0
- package/dist/leaderRegistry.d.cts +52 -0
- package/dist/leaderRegistry.d.cts.map +1 -0
- package/dist/leaderRegistry.d.mts +52 -0
- package/dist/leaderRegistry.d.mts.map +1 -0
- package/dist/leaderRegistry.mjs +2 -0
- package/dist/leaderRegistry.mjs.map +1 -0
- package/dist/logSinkTelemetry.cjs +2 -0
- package/dist/logSinkTelemetry.cjs.map +1 -0
- package/dist/logSinkTelemetry.d.cts +29 -0
- package/dist/logSinkTelemetry.d.cts.map +1 -0
- package/dist/logSinkTelemetry.d.mts +29 -0
- package/dist/logSinkTelemetry.d.mts.map +1 -0
- package/dist/logSinkTelemetry.mjs +2 -0
- package/dist/logSinkTelemetry.mjs.map +1 -0
- package/dist/mode.cjs +2 -0
- package/dist/mode.cjs.map +1 -0
- package/dist/mode.d.cts +5 -0
- package/dist/mode.d.cts.map +1 -0
- package/dist/mode.d.mts +5 -0
- package/dist/mode.d.mts.map +1 -0
- package/dist/mode.mjs +2 -0
- package/dist/mode.mjs.map +1 -0
- package/dist/rendering.cjs +2 -0
- package/dist/rendering.cjs.map +1 -0
- package/dist/rendering.d.cts +11 -0
- package/dist/rendering.d.cts.map +1 -0
- package/dist/rendering.d.mts +11 -0
- package/dist/rendering.d.mts.map +1 -0
- package/dist/rendering.mjs +2 -0
- package/dist/rendering.mjs.map +1 -0
- package/dist/skills.cjs +1 -0
- package/dist/skills.d.cts +1 -0
- package/dist/skills.d.mts +1 -0
- package/dist/skills.mjs +1 -0
- package/dist/theme.cjs +2 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +95 -0
- package/dist/theme.d.cts.map +1 -0
- package/dist/theme.d.mts +95 -0
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +2 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/toolInventory.cjs +2 -0
- package/dist/toolInventory.cjs.map +1 -0
- package/dist/toolInventory.d.cts +40 -0
- package/dist/toolInventory.d.cts.map +1 -0
- package/dist/toolInventory.d.mts +40 -0
- package/dist/toolInventory.d.mts.map +1 -0
- package/dist/toolInventory.mjs +2 -0
- package/dist/toolInventory.mjs.map +1 -0
- package/dist/uiState.cjs +2 -0
- package/dist/uiState.cjs.map +1 -0
- package/dist/uiState.d.cts +37 -0
- package/dist/uiState.d.cts.map +1 -0
- package/dist/uiState.d.mts +37 -0
- package/dist/uiState.d.mts.map +1 -0
- package/dist/uiState.mjs +2 -0
- package/dist/uiState.mjs.map +1 -0
- package/package.json +171 -0
- package/themes/doom-pi-dark.json +86 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DoomUiState } from "../uiState.mjs";
|
|
2
|
+
import { footer_d_exports } from "../footer.mjs";
|
|
3
|
+
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Component, TUI } from "@earendil-works/pi-tui";
|
|
5
|
+
//#region src/components/doomFooter.d.ts
|
|
6
|
+
declare class DoomFooter implements Component {
|
|
7
|
+
private readonly theme;
|
|
8
|
+
private readonly context;
|
|
9
|
+
private readonly footerData;
|
|
10
|
+
private readonly footerStatuses;
|
|
11
|
+
private readonly uiState;
|
|
12
|
+
private readonly unsubscribeBranch;
|
|
13
|
+
private readonly unsubscribeFooterStatuses;
|
|
14
|
+
private readonly unsubscribeState;
|
|
15
|
+
constructor(tui: TUI, theme: Theme, context: ExtensionContext, footerData: ReadonlyFooterDataProvider, footerStatuses: footer_d_exports.DoomFooterStatusRegistry, uiState: DoomUiState);
|
|
16
|
+
dispose(): void;
|
|
17
|
+
invalidate(): void;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
private locationSegment;
|
|
20
|
+
private runtimeSegment;
|
|
21
|
+
/** Effort rides the theme's rising ramp, from grey at off to red at max. */
|
|
22
|
+
private thinkingColor;
|
|
23
|
+
/** Context usage warms as the window fills, so pressure is visible at a glance. */
|
|
24
|
+
private contextColor;
|
|
25
|
+
private modelineSummary;
|
|
26
|
+
private footerStatusSummary;
|
|
27
|
+
private renderFooterSegments;
|
|
28
|
+
private compactModel;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DoomFooter };
|
|
32
|
+
//# sourceMappingURL=doomFooter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.d.mts","names":[],"sources":["../../src/components/doomFooter.ts"],"mappings":";;;;;cAqCa,sBAAsB;mBAOd;mBACA;mBACA;mBACA;mBACA;mBAVF;mBACA;mBACA;EAGf,YAAA,KAAK,KACY,OAAO,OACP,SAAS,kBACT,YAAY,4BACZ,gBAAgB,iBAAA,0BAChB,SAAS;EAO5B;EAMA;EAEA,OAAO;UA+BC;UAUA;;UAuBA;;UAKA;UAOA;UAKA;UAaA;UASA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{fitLine as e,padLine as t}from"../rendering.mjs";import{visibleWidth as n}from"@earendil-works/pi-tui";import r from"node:path";const i=` · `,a=`muted`,o=`toolPendingBg`,s=`userMessageBg`,c=`modeline`,l={off:`thinkingOff`,minimal:`thinkingMinimal`,low:`thinkingLow`,medium:`thinkingMedium`,high:`thinkingHigh`,xhigh:`thinkingXhigh`,max:`thinkingMax`};function u(e){return e.replace(/[\r\n\t]+/gu,` `).replace(/ {2,}/gu,` `).trim()}var d=class{theme;context;footerData;footerStatuses;uiState;unsubscribeBranch;unsubscribeFooterStatuses;unsubscribeState;constructor(e,t,n,r,i,a){this.theme=t,this.context=n,this.footerData=r,this.footerStatuses=i,this.uiState=a,this.unsubscribeBranch=r.onBranchChange(()=>e.requestRender()),this.unsubscribeFooterStatuses=i.subscribe(()=>e.requestRender()),this.unsubscribeState=a.subscribe(()=>e.requestRender())}dispose(){this.unsubscribeBranch(),this.unsubscribeFooterStatuses(),this.unsubscribeState()}invalidate(){}render(i){if(i<=0)return[];let a=i<90,s=this.uiState.getSnapshot(),c=s.active?s.prefix.join(` `):a?`I`:`INSERT`,l=s.active?this.theme.inverse(this.theme.bold(this.theme.fg(`mdHeading`,` ${c} `))):this.theme.inverse(this.theme.bold(this.theme.fg(`accent`,` ${c} `))),u=r.basename(this.context.cwd)||this.context.cwd,d=this.footerData.getGitBranch(),f=`${l}${this.locationSegment(u,d,a)}`,p=this.footerStatusSummary(`beforeModel`,a),m=this.runtimeSegment(a),h=this.runtimeSegment(a,p),g=p&&n(l)+n(h)<=i?h:m;if(n(f)+n(g)>i)return[e(`${l}${g}`,i)];let _=i-n(f)-n(g),v=this.modelineSummary(Math.max(0,_-1),a),y=this.theme.bg(o,t(` ${v}`,_));return[e(`${f}${y}${g}`,i)]}locationSegment(e,t,n){let r=this.theme.bg(s,this.theme.fg(`text`,` ${e}`));if(!t)return`${r}${this.theme.bg(s,` `)}`;let i=n?` `:` `;return`${r}${this.theme.bg(s,this.theme.fg(`success`,`${i}${t} `))}`}runtimeSegment(t,n=``){let r=this.context.getContextUsage()?.percent,a=r==null?t?`?`:`ctx ?`:t?`${Math.round(r)}%`:`ctx ${Math.round(r)}%`,s=t?18:30,c=e(this.compactModel(this.context.model?.id??`no-model`),s),l=this.context.thinkingLevel??`off`,u=[...n?[n]:[],this.theme.fg(`warning`,c),this.theme.fg(this.thinkingColor(l),l),this.theme.fg(this.contextColor(r),a)].join(i);return this.theme.bg(o,` ${u} `)}thinkingColor(e){return l[e]??`thinkingText`}contextColor(e){return e==null?a:e>=85?`error`:e>=60?`warning`:a}modelineSummary(t,n){return t<=0?``:e(this.footerStatusSummary(c,n),t)}footerStatusSummary(e,t){return this.footerStatuses.getStatuses().filter(t=>(t.placement??c)===e).map(e=>{let n=t?e.compactSegments:e.fullSegments;return n?.length?this.renderFooterSegments(n):this.theme.fg(`dim`,u(t?e.compactText:e.fullText))}).filter(Boolean).join(this.theme.fg(`dim`,i))}renderFooterSegments(e){return e.map(e=>{let t=e.text.replace(/[\r\n\t]+/gu,` `);return this.theme.fg(e.color??`dim`,t)}).join(``)}compactModel(e){return(e.split(`/`).at(-1)??e).replace(/^claude-/,``)}};export{d as DoomFooter};
|
|
2
|
+
//# sourceMappingURL=doomFooter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.mjs","names":[],"sources":["../../src/components/doomFooter.ts"],"sourcesContent":["import path from 'node:path';\nimport type { FooterStatusPlacement, FooterTextSegment } from '@agimon-ai/doompi-extension-contracts/footer';\nimport type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { visibleWidth, type Component, type TUI } from '@earendil-works/pi-tui';\nimport type { DoomFooterStatusRegistry } from '../footer.ts';\nimport { fitLine, padLine } from '../rendering.ts';\nimport type { DoomUiState } from '../uiState.ts';\n\nconst NARROW_FOOTER_WIDTH = 90;\nconst SEGMENT_SEPARATOR = ' · ';\nconst DIM_COLOR = 'dim';\nconst MUTED_COLOR = 'muted';\nconst TOOL_PENDING_BACKGROUND = 'toolPendingBg';\nconst USER_MESSAGE_BACKGROUND = 'userMessageBg';\nconst DEFAULT_THINKING_LEVEL = 'off';\nconst MODELINE_PLACEMENT = 'modeline' satisfies FooterStatusPlacement;\nconst BEFORE_MODEL_PLACEMENT = 'beforeModel' satisfies FooterStatusPlacement;\n// The theme ships a rising ramp for effort, ending at red for max.\nconst THINKING_COLORS: Record<string, ThemeColor> = {\n off: 'thinkingOff',\n minimal: 'thinkingMinimal',\n low: 'thinkingLow',\n medium: 'thinkingMedium',\n high: 'thinkingHigh',\n xhigh: 'thinkingXhigh',\n max: 'thinkingMax',\n};\nconst CONTEXT_WARN_PERCENT = 60;\nconst CONTEXT_CRITICAL_PERCENT = 85;\n\nfunction normalizeFooterText(text: string): string {\n return text\n .replace(/[\\r\\n\\t]+/gu, ' ')\n .replace(/ {2,}/gu, ' ')\n .trim();\n}\n\nexport class DoomFooter implements Component {\n private readonly unsubscribeBranch: () => void;\n private readonly unsubscribeFooterStatuses: () => void;\n private readonly unsubscribeState: () => void;\n\n constructor(\n tui: TUI,\n private readonly theme: Theme,\n private readonly context: ExtensionContext,\n private readonly footerData: ReadonlyFooterDataProvider,\n private readonly footerStatuses: DoomFooterStatusRegistry,\n private readonly uiState: DoomUiState,\n ) {\n this.unsubscribeBranch = footerData.onBranchChange(() => tui.requestRender());\n this.unsubscribeFooterStatuses = footerStatuses.subscribe(() => tui.requestRender());\n this.unsubscribeState = uiState.subscribe(() => tui.requestRender());\n }\n\n dispose(): void {\n this.unsubscribeBranch();\n this.unsubscribeFooterStatuses();\n this.unsubscribeState();\n }\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n\n const narrow = width < NARROW_FOOTER_WIDTH;\n const snapshot = this.uiState.getSnapshot();\n const modeText = snapshot.active ? snapshot.prefix.join(' ') : narrow ? 'I' : 'INSERT';\n const mode = snapshot.active\n ? this.theme.inverse(this.theme.bold(this.theme.fg('mdHeading', ` ${modeText} `)))\n : this.theme.inverse(this.theme.bold(this.theme.fg('accent', ` ${modeText} `)));\n const repository = path.basename(this.context.cwd) || this.context.cwd;\n const branch = this.footerData.getGitBranch();\n const location = this.locationSegment(repository, branch, narrow);\n const left = `${mode}${location}`;\n const beforeModel = this.footerStatusSummary(BEFORE_MODEL_PLACEMENT, narrow);\n const baseRuntime = this.runtimeSegment(narrow);\n const runtimeWithBeforeModel = this.runtimeSegment(narrow, beforeModel);\n const runtime =\n beforeModel && visibleWidth(mode) + visibleWidth(runtimeWithBeforeModel) <= width\n ? runtimeWithBeforeModel\n : baseRuntime;\n\n if (visibleWidth(left) + visibleWidth(runtime) > width) {\n return [fitLine(`${mode}${runtime}`, width)];\n }\n\n const middleWidth = width - visibleWidth(left) - visibleWidth(runtime);\n const middleText = this.modelineSummary(Math.max(0, middleWidth - 1), narrow);\n const middle = this.theme.bg(TOOL_PENDING_BACKGROUND, padLine(` ${middleText}`, middleWidth));\n return [fitLine(`${left}${middle}${runtime}`, width)];\n }\n\n private locationSegment(repository: string, branch: string | null, narrow: boolean): string {\n const repositoryPart = this.theme.bg(USER_MESSAGE_BACKGROUND, this.theme.fg('text', ` ${repository}`));\n if (!branch) return `${repositoryPart}${this.theme.bg(USER_MESSAGE_BACKGROUND, ' ')}`;\n const branchPrefix = narrow ? ' ' : ' ';\n return `${repositoryPart}${this.theme.bg(\n USER_MESSAGE_BACKGROUND,\n this.theme.fg('success', `${branchPrefix}${branch} `),\n )}`;\n }\n\n private runtimeSegment(narrow: boolean, beforeModel = ''): string {\n const contextPercent = this.context.getContextUsage()?.percent;\n const contextUsage =\n contextPercent === null || contextPercent === undefined\n ? narrow\n ? '?'\n : 'ctx ?'\n : narrow\n ? `${Math.round(contextPercent)}%`\n : `ctx ${Math.round(contextPercent)}%`;\n const modelLimit = narrow ? 18 : 30;\n const model = fitLine(this.compactModel(this.context.model?.id ?? 'no-model'), modelLimit);\n const thinking = this.context.thinkingLevel ?? DEFAULT_THINKING_LEVEL;\n const content = [\n ...(beforeModel ? [beforeModel] : []),\n this.theme.fg('warning', model),\n this.theme.fg(this.thinkingColor(thinking), thinking),\n this.theme.fg(this.contextColor(contextPercent), contextUsage),\n ].join(SEGMENT_SEPARATOR);\n return this.theme.bg(TOOL_PENDING_BACKGROUND, ` ${content} `);\n }\n\n /** Effort rides the theme's rising ramp, from grey at off to red at max. */\n private thinkingColor(level: string): ThemeColor {\n return THINKING_COLORS[level] ?? 'thinkingText';\n }\n\n /** Context usage warms as the window fills, so pressure is visible at a glance. */\n private contextColor(percent: number | null | undefined): ThemeColor {\n if (percent === null || percent === undefined) return MUTED_COLOR;\n if (percent >= CONTEXT_CRITICAL_PERCENT) return 'error';\n if (percent >= CONTEXT_WARN_PERCENT) return 'warning';\n return MUTED_COLOR;\n }\n\n private modelineSummary(availableWidth: number, compact: boolean): string {\n if (availableWidth <= 0) return '';\n return fitLine(this.footerStatusSummary(MODELINE_PLACEMENT, compact), availableWidth);\n }\n\n private footerStatusSummary(placement: FooterStatusPlacement, compact: boolean): string {\n return this.footerStatuses\n .getStatuses()\n .filter((status) => (status.placement ?? MODELINE_PLACEMENT) === placement)\n .map((status) => {\n const segments = compact ? status.compactSegments : status.fullSegments;\n if (segments?.length) return this.renderFooterSegments(segments);\n return this.theme.fg(DIM_COLOR, normalizeFooterText(compact ? status.compactText : status.fullText));\n })\n .filter(Boolean)\n .join(this.theme.fg(DIM_COLOR, SEGMENT_SEPARATOR));\n }\n\n private renderFooterSegments(segments: readonly FooterTextSegment[]): string {\n return segments\n .map((segment) => {\n const text = segment.text.replace(/[\\r\\n\\t]+/gu, ' ');\n return this.theme.fg((segment.color ?? DIM_COLOR) as ThemeColor, text);\n })\n .join('');\n }\n\n private compactModel(model: string): string {\n const modelId = model.split('/').at(-1) ?? model;\n return modelId.replace(/^claude-/, '');\n }\n}\n"],"mappings":"uIAQA,MACM,EAAoB,MAEpB,EAAc,QACd,EAA0B,gBAC1B,EAA0B,gBAE1B,EAAqB,WAGrB,EAA8C,CAClD,IAAK,cACL,QAAS,kBACT,IAAK,cACL,OAAQ,iBACR,KAAM,eACN,MAAO,gBACP,IAAK,aACP,EAIA,SAAS,EAAoB,EAAsB,CACjD,OAAO,EACJ,QAAQ,cAAe,GAAG,CAAC,CAC3B,QAAQ,UAAW,GAAG,CAAC,CACvB,KAAK,CACV,CAEA,IAAa,EAAb,KAA6C,CAOxB,MACA,QACA,WACA,eACA,QAVnB,kBACA,0BACA,iBAEA,YACE,EACA,EACA,EACA,EACA,EACA,EACA,CALiB,KAAA,MAAA,EACA,KAAA,QAAA,EACA,KAAA,WAAA,EACA,KAAA,eAAA,EACA,KAAA,QAAA,EAEjB,KAAK,kBAAoB,EAAW,mBAAqB,EAAI,cAAc,CAAC,EAC5E,KAAK,0BAA4B,EAAe,cAAgB,EAAI,cAAc,CAAC,EACnF,KAAK,iBAAmB,EAAQ,cAAgB,EAAI,cAAc,CAAC,CACrE,CAEA,SAAgB,CACd,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,CACxB,CAEA,YAAmB,CAAC,CAEpB,OAAO,EAAyB,CAC9B,GAAI,GAAS,EAAG,MAAO,CAAC,EAExB,IAAM,EAAS,EAAQ,GACjB,EAAW,KAAK,QAAQ,YAAY,EACpC,EAAW,EAAS,OAAS,EAAS,OAAO,KAAK,GAAG,EAAI,EAAS,IAAM,SACxE,EAAO,EAAS,OAClB,KAAK,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,YAAa,IAAI,EAAS,EAAE,CAAC,CAAC,EAC/E,KAAK,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,SAAU,IAAI,EAAS,EAAE,CAAC,CAAC,EAC1E,EAAa,EAAK,SAAS,KAAK,QAAQ,GAAG,GAAK,KAAK,QAAQ,IAC7D,EAAS,KAAK,WAAW,aAAa,EAEtC,EAAO,GAAG,IADC,KAAK,gBAAgB,EAAY,EAAQ,CAC5B,IACxB,EAAc,KAAK,oBAAoB,cAAwB,CAAM,EACrE,EAAc,KAAK,eAAe,CAAM,EACxC,EAAyB,KAAK,eAAe,EAAQ,CAAW,EAChE,EACJ,GAAe,EAAa,CAAI,EAAI,EAAa,CAAsB,GAAK,EACxE,EACA,EAEN,GAAI,EAAa,CAAI,EAAI,EAAa,CAAO,EAAI,EAC/C,MAAO,CAAC,EAAQ,GAAG,IAAO,IAAW,CAAK,CAAC,EAG7C,IAAM,EAAc,EAAQ,EAAa,CAAI,EAAI,EAAa,CAAO,EAC/D,EAAa,KAAK,gBAAgB,KAAK,IAAI,EAAG,EAAc,CAAC,EAAG,CAAM,EACtE,EAAS,KAAK,MAAM,GAAG,EAAyB,EAAQ,IAAI,IAAc,CAAW,CAAC,EAC5F,MAAO,CAAC,EAAQ,GAAG,IAAO,IAAS,IAAW,CAAK,CAAC,CACtD,CAEA,gBAAwB,EAAoB,EAAuB,EAAyB,CAC1F,IAAM,EAAiB,KAAK,MAAM,GAAG,EAAyB,KAAK,MAAM,GAAG,OAAQ,IAAI,GAAY,CAAC,EACrG,GAAI,CAAC,EAAQ,MAAO,GAAG,IAAiB,KAAK,MAAM,GAAG,EAAyB,GAAG,IAClF,IAAM,EAAe,EAAS,KAAO,OACrC,MAAO,GAAG,IAAiB,KAAK,MAAM,GACpC,EACA,KAAK,MAAM,GAAG,UAAW,GAAG,IAAe,EAAO,EAAE,CACtD,GACF,CAEA,eAAuB,EAAiB,EAAc,GAAY,CAChE,IAAM,EAAiB,KAAK,QAAQ,gBAAgB,CAAC,EAAE,QACjD,EACJ,GAAmB,KACf,EACE,IACA,QACF,EACE,GAAG,KAAK,MAAM,CAAc,EAAE,GAC9B,OAAO,KAAK,MAAM,CAAc,EAAE,GACpC,EAAa,EAAS,GAAK,GAC3B,EAAQ,EAAQ,KAAK,aAAa,KAAK,QAAQ,OAAO,IAAM,UAAU,EAAG,CAAU,EACnF,EAAW,KAAK,QAAQ,eAAiB,MACzC,EAAU,CACd,GAAI,EAAc,CAAC,CAAW,EAAI,CAAC,EACnC,KAAK,MAAM,GAAG,UAAW,CAAK,EAC9B,KAAK,MAAM,GAAG,KAAK,cAAc,CAAQ,EAAG,CAAQ,EACpD,KAAK,MAAM,GAAG,KAAK,aAAa,CAAc,EAAG,CAAY,CAC/D,CAAC,CAAC,KAAK,CAAiB,EACxB,OAAO,KAAK,MAAM,GAAG,EAAyB,IAAI,EAAQ,EAAE,CAC9D,CAGA,cAAsB,EAA2B,CAC/C,OAAO,EAAgB,IAAU,cACnC,CAGA,aAAqB,EAAgD,CAInE,OAHI,GAAY,KAAsC,EAClD,GAAW,GAAiC,QAC5C,GAAW,GAA6B,UACrC,CACT,CAEA,gBAAwB,EAAwB,EAA0B,CAExE,OADI,GAAkB,EAAU,GACzB,EAAQ,KAAK,oBAAoB,EAAoB,CAAO,EAAG,CAAc,CACtF,CAEA,oBAA4B,EAAkC,EAA0B,CACtF,OAAO,KAAK,eACT,YAAY,CAAC,CACb,OAAQ,IAAY,EAAO,WAAa,KAAwB,CAAS,CAAC,CAC1E,IAAK,GAAW,CACf,IAAM,EAAW,EAAU,EAAO,gBAAkB,EAAO,aAE3D,OADI,GAAU,OAAe,KAAK,qBAAqB,CAAQ,EACxD,KAAK,MAAM,GAAG,MAAW,EAAoB,EAAU,EAAO,YAAc,EAAO,QAAQ,CAAC,CACrG,CAAC,CAAC,CACD,OAAO,OAAO,CAAC,CACf,KAAK,KAAK,MAAM,GAAG,MAAW,CAAiB,CAAC,CACrD,CAEA,qBAA6B,EAAgD,CAC3E,OAAO,EACJ,IAAK,GAAY,CAChB,IAAM,EAAO,EAAQ,KAAK,QAAQ,cAAe,GAAG,EACpD,OAAO,KAAK,MAAM,GAAI,EAAQ,OAAS,MAA0B,CAAI,CACvE,CAAC,CAAC,CACD,KAAK,EAAE,CACZ,CAEA,aAAqB,EAAuB,CAE1C,OADgB,EAAM,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAK,EAAA,CAC5B,QAAQ,WAAY,EAAE,CACvC,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../rendering.cjs"),n=require("../harnessMetadata.cjs");let r=require("@earendil-works/pi-tui"),i=require("node:path");i=e.__toESM(i,1);const a=`default`,o=`no-model`,s=`core`,c=`BOOT / 01`,l=`● READY`,u=` · `,d=/^claude-/,f=`REPOSITORY`,p=`PROFILE`,m=`LAYERS`,h=`MODE`,g=`MODEL`,_=`─`.repeat(8),v=`accent`,y=`success`,b=`muted`,x=`text`,S=`mdCode`,C=`warning`,w=`borderMuted`;var T=class{theme;cwd;readMetadata;runtime;getTerminalRows;constructor(e,t,r=n.readDoomHarnessMetadata,i,a=()=>24){this.theme=e,this.cwd=t,this.readMetadata=r,this.runtime=i,this.getTerminalRows=a}invalidate(){}render(e){if(e<=0)return[];if(e<4)return[this.hairline(e)];let t=this.readMetadata();if(this.runtime?.sessionManager.getEntries().some(e=>e.type===`message`)??!1)return[];let n=this.getTerminalRows();if(n<24)return this.renderCompact(e,t);let r=e<90?this.renderNarrowDashboard(e,t):this.renderWideDashboard(e,t),i=this.renderHints(e),a=2+r.length+i.length+2,o=Math.max(3,n-8-a);return[...Array.from({length:2},()=>``),...r,``,...i,``,...this.renderEmptyState(e,o)]}renderWideDashboard(e,n){let r=Math.max(1,e-30-3),i=t.alignLine(this.theme.bold(this.theme.fg(`mdHeading`,`SESSION CONTEXT`)),this.theme.fg(`dim`,`${new Date().toLocaleTimeString(`en-GB`,{hour12:!1})} / local`),r),l=this.compactPath(n.root??this.cwd),d=n.profile??a,_=n.domains.join(`, `)||a,w=n.layers.join(u)||s,T=this.runtime?.model?.id??o,E=this.runtime?.thinkingLevel??`off`,D=`${this.theme.fg(y,`━━━━━━━━`)} ${this.theme.bold(this.theme.fg(y,`READY`))}`;return[this.columns(this.identityCell(this.theme.fg(`dim`,c)),i,e),this.columns(this.identityCell(this.wordmark()),this.contextRow(f,l,x),e),this.columns(this.identityCell(this.theme.fg(b,`Pi, configured for focus.`)),this.contextRow(p,d,S),e),this.columns(this.identityCell(D),this.contextRow(`DOMAINS`,_,v),e),this.columns(this.identityCell(``),this.contextRow(m,w,b),e),this.columns(this.identityCell(``),this.contextRow(h,n.majorMode,y),e),this.columns(this.identityCell(``),this.contextRow(g,`${T} / ${E}`,C),e),this.hairline(e)]}renderNarrowDashboard(e,n){let r=i.default.basename(n.root??this.cwd)||this.cwd,d=n.profile??a,_=n.domains.join(`, `)||a,v=this.runtime?.sessionManager.getSessionId().slice(0,6)??`new`,w=n.layers.join(u)||s,T=this.compactModel(this.runtime?.model?.id??o),E=this.runtime?.thinkingLevel??`off`,D=this.theme.bold(this.theme.fg(y,l));return[...[t.alignLine(this.identityCell(`${this.theme.fg(`dim`,c)} ${this.wordmark()}`),D,e),this.identityCell(this.contextRow(f,r,x)),this.identityCell(this.contextRow(p,`${d} / ${_} / ${v}`,S)),this.identityCell(this.contextRow(m,w,b)),this.identityCell(this.contextRow(h,n.majorMode,y)),this.identityCell(this.contextRow(g,`${T} / ${E}`,C))].map(n=>t.fitLine(n,e)),this.hairline(e)]}renderCompact(e,n){let r=i.default.basename(n.root??this.cwd)||this.cwd,s=n.profile??a,c=n.domains.join(`,`)||a,u=this.compactModel(this.runtime?.model?.id??o),d=this.runtime?.thinkingLevel??`off`,f=this.runtime?.isIdle()===!1?`● ACTIVE`:l;return[t.alignLine(`${this.wordmark()} ${this.theme.bold(this.theme.fg(x,r))}`,this.theme.fg(y,f),e),t.fitLine(`${s} / ${c} / ${n.majorMode}`,e),t.alignLine(this.theme.fg(C,`${u} / ${d}`),this.theme.fg(`dim`,`CTRL+SPACE leader · SPC when empty`),e),this.hairline(e)]}renderHints(e){let n=[this.keyHint(`CTRL+SPACE`,`leader anywhere`),this.keyHint(`SPC`,`when empty`),this.keyHint(`/`,`commands`),this.keyHint(`!`,`shell`)].join(` `);return[t.fitLine(n,e)]}renderEmptyState(e,t){let n=Array.from({length:Math.max(3,t)},()=>``),r=this.theme.fg(w,_),i=`${r} ${this.theme.fg(`dim`,`NO TRANSCRIPT YET`)} ${r}`,a=this.theme.fg(b,`Describe the work below. Pi keeps your existing config and session history.`),o=Math.max(0,Math.floor(n.length/2)-1);return n[o]=this.centerLine(i,e),n.length>o+1&&(n[o+1]=this.centerLine(a,e)),n}hairline(e){return this.theme.fg(w,`─`.repeat(Math.max(0,e)))}centerLine(e,n){let i=t.fitLine(e,n),a=` `.repeat(Math.max(0,Math.floor((n-(0,r.visibleWidth)(i))/2)));return t.fitLine(`${a}${i}`,n)}identityCell(e){let t=this.theme.fg(v,`▌`);return e?`${t} ${e}`:t}columns(e,n,r){let i=Math.max(0,r-30-3);return t.fitLine(`${t.padLine(e,30)}${` `.repeat(3)}${t.fitLine(n,i)}`,r)}contextRow(e,t,n){return`${this.theme.fg(`dim`,e.padEnd(12))}${this.theme.fg(n,t)}`}keyHint(e,t){return`${this.theme.bg(`selectedBg`,this.theme.fg(v,` ${e} `))} ${this.theme.fg(`dim`,t)}`}wordmark(){return`${this.theme.bold(this.theme.fg(v,`DOOM`))} ${this.theme.inverse(this.theme.bold(this.theme.fg(x,` PI `)))}`}compactModel(e){return e.split(`/`).at(-1)?.replace(d,``)??e}compactPath(e){let t=process.env.HOME;return t&&e.startsWith(t)?`~${e.slice(t.length)}`:e}};exports.DoomHeader=T;
|
|
2
|
+
//# sourceMappingURL=doomHeader.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.cjs","names":["readDoomHarnessMetadata","alignLine","path","fitLine","visibleWidth","padLine"],"sources":["../../src/components/doomHeader.ts"],"sourcesContent":["import path from 'node:path';\nimport { type ExtensionContext, type Theme, type ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { visibleWidth, type Component } from '@earendil-works/pi-tui';\nimport { type DoomHarnessMetadata, readDoomHarnessMetadata } from '../harnessMetadata.ts';\nimport { alignLine, fitLine, padLine } from '../rendering.ts';\n\nconst WIDE_HEADER_WIDTH = 90;\nconst FULL_STARTUP_MIN_ROWS = 24;\n// Rows Pi renders below the header: the four-line editor frame, the modeline,\n// and the spacing around them. Under-reserving scrolls the dashboard off the top.\nconst RESERVED_INTERFACE_ROWS = 8;\nconst MIN_EMPTY_STATE_HEIGHT = 3;\nconst IDENTITY_COLUMN_WIDTH = 30;\nconst COLUMN_GAP = 3;\nconst MIN_RENDER_WIDTH = 4;\nconst SECTION_SEPARATOR_ROWS = 2;\n/** Breathing room between the shell prompt Pi launched from and the dashboard. */\nconst TOP_PADDING_ROWS = 2;\nconst CONTEXT_LABEL_WIDTH = 12;\nconst SESSION_ID_PREVIEW_LENGTH = 6;\nconst EMPTY_STATE_RULE_LENGTH = 8;\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst DEFAULT_METADATA_LABEL = 'default';\nconst DEFAULT_MODEL_LABEL = 'no-model';\nconst DEFAULT_THINKING_LABEL = 'off';\nconst DEFAULT_LAYERS_LABEL = 'core';\nconst DEFAULT_SESSION_LABEL = 'new';\nconst BOOT_LABEL = 'BOOT / 01';\nconst READY_STATUS = '● READY';\nconst ACTIVE_STATUS = '● ACTIVE';\nconst EMPTY_STATE_LABEL = 'NO TRANSCRIPT YET';\nconst EMPTY_STATE_GUIDANCE = 'Describe the work below. Pi keeps your existing config and session history.';\nconst PRODUCT_DESCRIPTOR = 'Pi, configured for focus.';\nconst SEGMENT_SEPARATOR = ' · ';\nconst DISPLAY_SEPARATOR = ' / ';\nconst LIST_SEPARATOR = ', ';\nconst MESSAGE_ENTRY_TYPE = 'message';\nconst TIME_LOCALE = 'en-GB';\nconst HOME_ENV = 'HOME';\nconst CLAUDE_MODEL_PREFIX = /^claude-/;\nconst REPOSITORY_LABEL = 'REPOSITORY';\nconst PROFILE_LABEL = 'PROFILE';\nconst DOMAINS_LABEL = 'DOMAINS';\nconst LAYERS_LABEL = 'LAYERS';\nconst MODE_LABEL = 'MODE';\nconst MODEL_LABEL = 'MODEL';\nconst HORIZONTAL_BORDER = '─';\nconst IDENTITY_ACCENT_BAR = '▌';\nconst READY_RULE = '━━━━━━━━';\nconst EMPTY_STATE_RULE = HORIZONTAL_BORDER.repeat(EMPTY_STATE_RULE_LENGTH);\nconst DIM_COLOR: ThemeColor = 'dim';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst SUCCESS_COLOR: ThemeColor = 'success';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst CODE_COLOR: ThemeColor = 'mdCode';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst SELECTED_BG_COLOR = 'selectedBg';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n\ntype MetadataReader = () => DoomHarnessMetadata;\ntype TerminalRowsReader = () => number;\ntype HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;\n\nexport class DoomHeader implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly cwd: string,\n private readonly readMetadata: MetadataReader = readDoomHarnessMetadata,\n private readonly runtime?: HeaderRuntime,\n private readonly getTerminalRows: TerminalRowsReader = () => DEFAULT_TERMINAL_ROWS,\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < MIN_RENDER_WIDTH) return [this.hairline(width)];\n\n const metadata = this.readMetadata();\n const hasTranscript =\n this.runtime?.sessionManager.getEntries().some((entry) => entry.type === MESSAGE_ENTRY_TYPE) ?? false;\n if (hasTranscript) return [];\n\n const terminalRows = this.getTerminalRows();\n if (terminalRows < FULL_STARTUP_MIN_ROWS) return this.renderCompact(width, metadata);\n\n const dashboard =\n width < WIDE_HEADER_WIDTH\n ? this.renderNarrowDashboard(width, metadata)\n : this.renderWideDashboard(width, metadata);\n const hints = this.renderHints(width);\n const fixedHeaderRows = TOP_PADDING_ROWS + dashboard.length + hints.length + SECTION_SEPARATOR_ROWS;\n const emptyStateHeight = Math.max(MIN_EMPTY_STATE_HEIGHT, terminalRows - RESERVED_INTERFACE_ROWS - fixedHeaderRows);\n const topPadding = Array.from({ length: TOP_PADDING_ROWS }, () => '');\n\n return [...topPadding, ...dashboard, '', ...hints, '', ...this.renderEmptyState(width, emptyStateHeight)];\n }\n\n private renderWideDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const contextWidth = Math.max(1, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n const contextHeading = alignLine(\n this.theme.bold(this.theme.fg(HEADING_COLOR, 'SESSION CONTEXT')),\n this.theme.fg(\n DIM_COLOR,\n `${new Date().toLocaleTimeString(TIME_LOCALE, { hour12: false })}${DISPLAY_SEPARATOR}local`,\n ),\n contextWidth,\n );\n const repository = this.compactPath(metadata.root ?? this.cwd);\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL;\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = `${this.theme.fg(SUCCESS_COLOR, READY_RULE)} ${this.theme.bold(\n this.theme.fg(SUCCESS_COLOR, 'READY'),\n )}`;\n const rows = [\n this.columns(this.identityCell(this.theme.fg(DIM_COLOR, BOOT_LABEL)), contextHeading, width),\n this.columns(\n this.identityCell(this.wordmark()),\n this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR),\n width,\n ),\n this.columns(\n this.identityCell(this.theme.fg(MUTED_COLOR, PRODUCT_DESCRIPTOR)),\n this.contextRow(PROFILE_LABEL, profile, CODE_COLOR),\n width,\n ),\n this.columns(this.identityCell(ready), this.contextRow(DOMAINS_LABEL, domains, ACCENT_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR), width),\n this.columns(\n this.identityCell(''),\n this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderNarrowDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const session =\n this.runtime?.sessionManager.getSessionId().slice(0, SESSION_ID_PREVIEW_LENGTH) ?? DEFAULT_SESSION_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = this.theme.bold(this.theme.fg(SUCCESS_COLOR, READY_STATUS));\n const rows = [\n alignLine(this.identityCell(`${this.theme.fg(DIM_COLOR, BOOT_LABEL)} ${this.wordmark()}`), ready, width),\n this.identityCell(this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR)),\n this.identityCell(\n this.contextRow(\n PROFILE_LABEL,\n `${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${session}`,\n CODE_COLOR,\n ),\n ),\n this.identityCell(this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR)),\n this.identityCell(this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR)),\n this.identityCell(this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR)),\n ].map((row) => fitLine(row, width));\n\n return [...rows, this.hairline(width)];\n }\n\n private renderCompact(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(',') || DEFAULT_METADATA_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const activity = this.runtime?.isIdle() === false ? ACTIVE_STATUS : READY_STATUS;\n const rows = [\n alignLine(\n `${this.wordmark()} ${this.theme.bold(this.theme.fg(TEXT_COLOR, repository))}`,\n this.theme.fg(SUCCESS_COLOR, activity),\n width,\n ),\n fitLine(`${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${metadata.majorMode}`, width),\n alignLine(\n this.theme.fg(WARNING_COLOR, `${model}${DISPLAY_SEPARATOR}${thinking}`),\n this.theme.fg(DIM_COLOR, 'CTRL+SPACE leader · SPC when empty'),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderHints(width: number): string[] {\n const content = [\n this.keyHint('CTRL+SPACE', 'leader anywhere'),\n this.keyHint('SPC', 'when empty'),\n this.keyHint('/', 'commands'),\n this.keyHint('!', 'shell'),\n ].join(' ');\n return [fitLine(content, width)];\n }\n\n private renderEmptyState(width: number, height: number): string[] {\n const lines = Array.from({ length: Math.max(MIN_EMPTY_STATE_HEIGHT, height) }, () => '');\n const rule = this.theme.fg(MUTED_BORDER_COLOR, EMPTY_STATE_RULE);\n const label = `${rule} ${this.theme.fg(DIM_COLOR, EMPTY_STATE_LABEL)} ${rule}`;\n const guidance = this.theme.fg(MUTED_COLOR, EMPTY_STATE_GUIDANCE);\n const labelIndex = Math.max(0, Math.floor(lines.length / SECTION_SEPARATOR_ROWS) - 1);\n lines[labelIndex] = this.centerLine(label, width);\n if (lines.length > labelIndex + 1) lines[labelIndex + 1] = this.centerLine(guidance, width);\n\n return lines;\n }\n\n private hairline(width: number): string {\n return this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(Math.max(0, width)));\n }\n\n private centerLine(content: string, width: number): string {\n const fitted = fitLine(content, width);\n const leftPadding = ' '.repeat(Math.max(0, Math.floor((width - visibleWidth(fitted)) / SECTION_SEPARATOR_ROWS)));\n return fitLine(`${leftPadding}${fitted}`, width);\n }\n\n private identityCell(content: string): string {\n const bar = this.theme.fg(ACCENT_COLOR, IDENTITY_ACCENT_BAR);\n return content ? `${bar} ${content}` : bar;\n }\n\n private columns(left: string, right: string, width: number): string {\n const rightWidth = Math.max(0, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n return fitLine(\n `${padLine(left, IDENTITY_COLUMN_WIDTH)}${' '.repeat(COLUMN_GAP)}${fitLine(right, rightWidth)}`,\n width,\n );\n }\n\n private contextRow(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.fg(DIM_COLOR, label.padEnd(CONTEXT_LABEL_WIDTH))}${this.theme.fg(color, value)}`;\n }\n\n private keyHint(key: string, label: string): string {\n const badge = this.theme.bg(SELECTED_BG_COLOR, this.theme.fg(ACCENT_COLOR, ` ${key} `));\n return `${badge} ${this.theme.fg(DIM_COLOR, label)}`;\n }\n\n private wordmark(): string {\n return `${this.theme.bold(this.theme.fg(ACCENT_COLOR, 'DOOM'))} ${this.theme.inverse(\n this.theme.bold(this.theme.fg(TEXT_COLOR, ' PI ')),\n )}`;\n }\n\n private compactModel(model: string): string {\n return model.split('/').at(-1)?.replace(CLAUDE_MODEL_PREFIX, '') ?? model;\n }\n\n private compactPath(value: string): string {\n const home = process.env[HOME_ENV];\n return home && value.startsWith(home) ? `~${value.slice(home.length)}` : value;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DoomHarnessMetadata } from "../harnessMetadata.cjs";
|
|
2
|
+
import { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
4
|
+
//#region src/components/doomHeader.d.ts
|
|
5
|
+
type MetadataReader = () => DoomHarnessMetadata;
|
|
6
|
+
type TerminalRowsReader = () => number;
|
|
7
|
+
type HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;
|
|
8
|
+
declare class DoomHeader implements Component {
|
|
9
|
+
private readonly theme;
|
|
10
|
+
private readonly cwd;
|
|
11
|
+
private readonly readMetadata;
|
|
12
|
+
private readonly runtime?;
|
|
13
|
+
private readonly getTerminalRows;
|
|
14
|
+
constructor(theme: Theme, cwd: string, readMetadata?: MetadataReader, runtime?: HeaderRuntime | undefined, getTerminalRows?: TerminalRowsReader);
|
|
15
|
+
invalidate(): void;
|
|
16
|
+
render(width: number): string[];
|
|
17
|
+
private renderWideDashboard;
|
|
18
|
+
private renderNarrowDashboard;
|
|
19
|
+
private renderCompact;
|
|
20
|
+
private renderHints;
|
|
21
|
+
private renderEmptyState;
|
|
22
|
+
private hairline;
|
|
23
|
+
private centerLine;
|
|
24
|
+
private identityCell;
|
|
25
|
+
private columns;
|
|
26
|
+
private contextRow;
|
|
27
|
+
private keyHint;
|
|
28
|
+
private wordmark;
|
|
29
|
+
private compactModel;
|
|
30
|
+
private compactPath;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { DoomHeader };
|
|
34
|
+
//# sourceMappingURL=doomHeader.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.d.cts","names":[],"sources":["../../src/components/doomHeader.ts"],"mappings":";;;;KA6DK,uBAAuB;KACvB;KACA,gBAAgB,KAAK;cAEb,sBAAsB;mBAEd;mBACA;mBACA;mBACA;mBACA;EAJA,YAAA,OAAO,OACP,aACA,eAAc,gBACd,UAAU,2BACV,kBAAiB;EAGpC;EAEA,OAAO;UAwBC;UA4CA;UA4BA;UAwBA;UAUA;UAYA;UAIA;UAMA;UAKA;UAQA;UAIA;UAKA;UAMA;UAIA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DoomHarnessMetadata } from "../harnessMetadata.mjs";
|
|
2
|
+
import { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
4
|
+
//#region src/components/doomHeader.d.ts
|
|
5
|
+
type MetadataReader = () => DoomHarnessMetadata;
|
|
6
|
+
type TerminalRowsReader = () => number;
|
|
7
|
+
type HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;
|
|
8
|
+
declare class DoomHeader implements Component {
|
|
9
|
+
private readonly theme;
|
|
10
|
+
private readonly cwd;
|
|
11
|
+
private readonly readMetadata;
|
|
12
|
+
private readonly runtime?;
|
|
13
|
+
private readonly getTerminalRows;
|
|
14
|
+
constructor(theme: Theme, cwd: string, readMetadata?: MetadataReader, runtime?: HeaderRuntime | undefined, getTerminalRows?: TerminalRowsReader);
|
|
15
|
+
invalidate(): void;
|
|
16
|
+
render(width: number): string[];
|
|
17
|
+
private renderWideDashboard;
|
|
18
|
+
private renderNarrowDashboard;
|
|
19
|
+
private renderCompact;
|
|
20
|
+
private renderHints;
|
|
21
|
+
private renderEmptyState;
|
|
22
|
+
private hairline;
|
|
23
|
+
private centerLine;
|
|
24
|
+
private identityCell;
|
|
25
|
+
private columns;
|
|
26
|
+
private contextRow;
|
|
27
|
+
private keyHint;
|
|
28
|
+
private wordmark;
|
|
29
|
+
private compactModel;
|
|
30
|
+
private compactPath;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { DoomHeader };
|
|
34
|
+
//# sourceMappingURL=doomHeader.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.d.mts","names":[],"sources":["../../src/components/doomHeader.ts"],"mappings":";;;;KA6DK,uBAAuB;KACvB;KACA,gBAAgB,KAAK;cAEb,sBAAsB;mBAEd;mBACA;mBACA;mBACA;mBACA;EAJA,YAAA,OAAO,OACP,aACA,eAAc,gBACd,UAAU,2BACV,kBAAiB;EAGpC;EAEA,OAAO;UAwBC;UA4CA;UA4BA;UAwBA;UAUA;UAYA;UAIA;UAMA;UAKA;UAQA;UAIA;UAKA;UAMA;UAIA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{alignLine as e,fitLine as t,padLine as n}from"../rendering.mjs";import{readDoomHarnessMetadata as r}from"../harnessMetadata.mjs";import{visibleWidth as i}from"@earendil-works/pi-tui";import a from"node:path";const o=`default`,s=`no-model`,c=`core`,l=`BOOT / 01`,u=`● READY`,d=` · `,f=/^claude-/,p=`REPOSITORY`,m=`PROFILE`,h=`LAYERS`,g=`MODE`,_=`MODEL`,v=`─`.repeat(8),y=`accent`,b=`success`,x=`muted`,S=`text`,C=`mdCode`,w=`warning`,T=`borderMuted`;var E=class{theme;cwd;readMetadata;runtime;getTerminalRows;constructor(e,t,n=r,i,a=()=>24){this.theme=e,this.cwd=t,this.readMetadata=n,this.runtime=i,this.getTerminalRows=a}invalidate(){}render(e){if(e<=0)return[];if(e<4)return[this.hairline(e)];let t=this.readMetadata();if(this.runtime?.sessionManager.getEntries().some(e=>e.type===`message`)??!1)return[];let n=this.getTerminalRows();if(n<24)return this.renderCompact(e,t);let r=e<90?this.renderNarrowDashboard(e,t):this.renderWideDashboard(e,t),i=this.renderHints(e),a=2+r.length+i.length+2,o=Math.max(3,n-8-a);return[...Array.from({length:2},()=>``),...r,``,...i,``,...this.renderEmptyState(e,o)]}renderWideDashboard(t,n){let r=Math.max(1,t-30-3),i=e(this.theme.bold(this.theme.fg(`mdHeading`,`SESSION CONTEXT`)),this.theme.fg(`dim`,`${new Date().toLocaleTimeString(`en-GB`,{hour12:!1})} / local`),r),a=this.compactPath(n.root??this.cwd),u=n.profile??o,f=n.domains.join(`, `)||o,v=n.layers.join(d)||c,T=this.runtime?.model?.id??s,E=this.runtime?.thinkingLevel??`off`,D=`${this.theme.fg(b,`━━━━━━━━`)} ${this.theme.bold(this.theme.fg(b,`READY`))}`;return[this.columns(this.identityCell(this.theme.fg(`dim`,l)),i,t),this.columns(this.identityCell(this.wordmark()),this.contextRow(p,a,S),t),this.columns(this.identityCell(this.theme.fg(x,`Pi, configured for focus.`)),this.contextRow(m,u,C),t),this.columns(this.identityCell(D),this.contextRow(`DOMAINS`,f,y),t),this.columns(this.identityCell(``),this.contextRow(h,v,x),t),this.columns(this.identityCell(``),this.contextRow(g,n.majorMode,b),t),this.columns(this.identityCell(``),this.contextRow(_,`${T} / ${E}`,w),t),this.hairline(t)]}renderNarrowDashboard(n,r){let i=a.basename(r.root??this.cwd)||this.cwd,f=r.profile??o,v=r.domains.join(`, `)||o,y=this.runtime?.sessionManager.getSessionId().slice(0,6)??`new`,T=r.layers.join(d)||c,E=this.compactModel(this.runtime?.model?.id??s),D=this.runtime?.thinkingLevel??`off`,O=this.theme.bold(this.theme.fg(b,u));return[...[e(this.identityCell(`${this.theme.fg(`dim`,l)} ${this.wordmark()}`),O,n),this.identityCell(this.contextRow(p,i,S)),this.identityCell(this.contextRow(m,`${f} / ${v} / ${y}`,C)),this.identityCell(this.contextRow(h,T,x)),this.identityCell(this.contextRow(g,r.majorMode,b)),this.identityCell(this.contextRow(_,`${E} / ${D}`,w))].map(e=>t(e,n)),this.hairline(n)]}renderCompact(n,r){let i=a.basename(r.root??this.cwd)||this.cwd,c=r.profile??o,l=r.domains.join(`,`)||o,d=this.compactModel(this.runtime?.model?.id??s),f=this.runtime?.thinkingLevel??`off`,p=this.runtime?.isIdle()===!1?`● ACTIVE`:u;return[e(`${this.wordmark()} ${this.theme.bold(this.theme.fg(S,i))}`,this.theme.fg(b,p),n),t(`${c} / ${l} / ${r.majorMode}`,n),e(this.theme.fg(w,`${d} / ${f}`),this.theme.fg(`dim`,`CTRL+SPACE leader · SPC when empty`),n),this.hairline(n)]}renderHints(e){let n=[this.keyHint(`CTRL+SPACE`,`leader anywhere`),this.keyHint(`SPC`,`when empty`),this.keyHint(`/`,`commands`),this.keyHint(`!`,`shell`)].join(` `);return[t(n,e)]}renderEmptyState(e,t){let n=Array.from({length:Math.max(3,t)},()=>``),r=this.theme.fg(T,v),i=`${r} ${this.theme.fg(`dim`,`NO TRANSCRIPT YET`)} ${r}`,a=this.theme.fg(x,`Describe the work below. Pi keeps your existing config and session history.`),o=Math.max(0,Math.floor(n.length/2)-1);return n[o]=this.centerLine(i,e),n.length>o+1&&(n[o+1]=this.centerLine(a,e)),n}hairline(e){return this.theme.fg(T,`─`.repeat(Math.max(0,e)))}centerLine(e,n){let r=t(e,n),a=` `.repeat(Math.max(0,Math.floor((n-i(r))/2)));return t(`${a}${r}`,n)}identityCell(e){let t=this.theme.fg(y,`▌`);return e?`${t} ${e}`:t}columns(e,r,i){let a=Math.max(0,i-30-3);return t(`${n(e,30)}${` `.repeat(3)}${t(r,a)}`,i)}contextRow(e,t,n){return`${this.theme.fg(`dim`,e.padEnd(12))}${this.theme.fg(n,t)}`}keyHint(e,t){return`${this.theme.bg(`selectedBg`,this.theme.fg(y,` ${e} `))} ${this.theme.fg(`dim`,t)}`}wordmark(){return`${this.theme.bold(this.theme.fg(y,`DOOM`))} ${this.theme.inverse(this.theme.bold(this.theme.fg(S,` PI `)))}`}compactModel(e){return e.split(`/`).at(-1)?.replace(f,``)??e}compactPath(e){let t=process.env.HOME;return t&&e.startsWith(t)?`~${e.slice(t.length)}`:e}};export{E as DoomHeader};
|
|
2
|
+
//# sourceMappingURL=doomHeader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomHeader.mjs","names":[],"sources":["../../src/components/doomHeader.ts"],"sourcesContent":["import path from 'node:path';\nimport { type ExtensionContext, type Theme, type ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { visibleWidth, type Component } from '@earendil-works/pi-tui';\nimport { type DoomHarnessMetadata, readDoomHarnessMetadata } from '../harnessMetadata.ts';\nimport { alignLine, fitLine, padLine } from '../rendering.ts';\n\nconst WIDE_HEADER_WIDTH = 90;\nconst FULL_STARTUP_MIN_ROWS = 24;\n// Rows Pi renders below the header: the four-line editor frame, the modeline,\n// and the spacing around them. Under-reserving scrolls the dashboard off the top.\nconst RESERVED_INTERFACE_ROWS = 8;\nconst MIN_EMPTY_STATE_HEIGHT = 3;\nconst IDENTITY_COLUMN_WIDTH = 30;\nconst COLUMN_GAP = 3;\nconst MIN_RENDER_WIDTH = 4;\nconst SECTION_SEPARATOR_ROWS = 2;\n/** Breathing room between the shell prompt Pi launched from and the dashboard. */\nconst TOP_PADDING_ROWS = 2;\nconst CONTEXT_LABEL_WIDTH = 12;\nconst SESSION_ID_PREVIEW_LENGTH = 6;\nconst EMPTY_STATE_RULE_LENGTH = 8;\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst DEFAULT_METADATA_LABEL = 'default';\nconst DEFAULT_MODEL_LABEL = 'no-model';\nconst DEFAULT_THINKING_LABEL = 'off';\nconst DEFAULT_LAYERS_LABEL = 'core';\nconst DEFAULT_SESSION_LABEL = 'new';\nconst BOOT_LABEL = 'BOOT / 01';\nconst READY_STATUS = '● READY';\nconst ACTIVE_STATUS = '● ACTIVE';\nconst EMPTY_STATE_LABEL = 'NO TRANSCRIPT YET';\nconst EMPTY_STATE_GUIDANCE = 'Describe the work below. Pi keeps your existing config and session history.';\nconst PRODUCT_DESCRIPTOR = 'Pi, configured for focus.';\nconst SEGMENT_SEPARATOR = ' · ';\nconst DISPLAY_SEPARATOR = ' / ';\nconst LIST_SEPARATOR = ', ';\nconst MESSAGE_ENTRY_TYPE = 'message';\nconst TIME_LOCALE = 'en-GB';\nconst HOME_ENV = 'HOME';\nconst CLAUDE_MODEL_PREFIX = /^claude-/;\nconst REPOSITORY_LABEL = 'REPOSITORY';\nconst PROFILE_LABEL = 'PROFILE';\nconst DOMAINS_LABEL = 'DOMAINS';\nconst LAYERS_LABEL = 'LAYERS';\nconst MODE_LABEL = 'MODE';\nconst MODEL_LABEL = 'MODEL';\nconst HORIZONTAL_BORDER = '─';\nconst IDENTITY_ACCENT_BAR = '▌';\nconst READY_RULE = '━━━━━━━━';\nconst EMPTY_STATE_RULE = HORIZONTAL_BORDER.repeat(EMPTY_STATE_RULE_LENGTH);\nconst DIM_COLOR: ThemeColor = 'dim';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst SUCCESS_COLOR: ThemeColor = 'success';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst CODE_COLOR: ThemeColor = 'mdCode';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst SELECTED_BG_COLOR = 'selectedBg';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n\ntype MetadataReader = () => DoomHarnessMetadata;\ntype TerminalRowsReader = () => number;\ntype HeaderRuntime = Pick<ExtensionContext, 'isIdle' | 'model' | 'sessionManager' | 'thinkingLevel'>;\n\nexport class DoomHeader implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly cwd: string,\n private readonly readMetadata: MetadataReader = readDoomHarnessMetadata,\n private readonly runtime?: HeaderRuntime,\n private readonly getTerminalRows: TerminalRowsReader = () => DEFAULT_TERMINAL_ROWS,\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < MIN_RENDER_WIDTH) return [this.hairline(width)];\n\n const metadata = this.readMetadata();\n const hasTranscript =\n this.runtime?.sessionManager.getEntries().some((entry) => entry.type === MESSAGE_ENTRY_TYPE) ?? false;\n if (hasTranscript) return [];\n\n const terminalRows = this.getTerminalRows();\n if (terminalRows < FULL_STARTUP_MIN_ROWS) return this.renderCompact(width, metadata);\n\n const dashboard =\n width < WIDE_HEADER_WIDTH\n ? this.renderNarrowDashboard(width, metadata)\n : this.renderWideDashboard(width, metadata);\n const hints = this.renderHints(width);\n const fixedHeaderRows = TOP_PADDING_ROWS + dashboard.length + hints.length + SECTION_SEPARATOR_ROWS;\n const emptyStateHeight = Math.max(MIN_EMPTY_STATE_HEIGHT, terminalRows - RESERVED_INTERFACE_ROWS - fixedHeaderRows);\n const topPadding = Array.from({ length: TOP_PADDING_ROWS }, () => '');\n\n return [...topPadding, ...dashboard, '', ...hints, '', ...this.renderEmptyState(width, emptyStateHeight)];\n }\n\n private renderWideDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const contextWidth = Math.max(1, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n const contextHeading = alignLine(\n this.theme.bold(this.theme.fg(HEADING_COLOR, 'SESSION CONTEXT')),\n this.theme.fg(\n DIM_COLOR,\n `${new Date().toLocaleTimeString(TIME_LOCALE, { hour12: false })}${DISPLAY_SEPARATOR}local`,\n ),\n contextWidth,\n );\n const repository = this.compactPath(metadata.root ?? this.cwd);\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL;\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = `${this.theme.fg(SUCCESS_COLOR, READY_RULE)} ${this.theme.bold(\n this.theme.fg(SUCCESS_COLOR, 'READY'),\n )}`;\n const rows = [\n this.columns(this.identityCell(this.theme.fg(DIM_COLOR, BOOT_LABEL)), contextHeading, width),\n this.columns(\n this.identityCell(this.wordmark()),\n this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR),\n width,\n ),\n this.columns(\n this.identityCell(this.theme.fg(MUTED_COLOR, PRODUCT_DESCRIPTOR)),\n this.contextRow(PROFILE_LABEL, profile, CODE_COLOR),\n width,\n ),\n this.columns(this.identityCell(ready), this.contextRow(DOMAINS_LABEL, domains, ACCENT_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR), width),\n this.columns(this.identityCell(''), this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR), width),\n this.columns(\n this.identityCell(''),\n this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderNarrowDashboard(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(LIST_SEPARATOR) || DEFAULT_METADATA_LABEL;\n const session =\n this.runtime?.sessionManager.getSessionId().slice(0, SESSION_ID_PREVIEW_LENGTH) ?? DEFAULT_SESSION_LABEL;\n const layers = metadata.layers.join(SEGMENT_SEPARATOR) || DEFAULT_LAYERS_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const ready = this.theme.bold(this.theme.fg(SUCCESS_COLOR, READY_STATUS));\n const rows = [\n alignLine(this.identityCell(`${this.theme.fg(DIM_COLOR, BOOT_LABEL)} ${this.wordmark()}`), ready, width),\n this.identityCell(this.contextRow(REPOSITORY_LABEL, repository, TEXT_COLOR)),\n this.identityCell(\n this.contextRow(\n PROFILE_LABEL,\n `${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${session}`,\n CODE_COLOR,\n ),\n ),\n this.identityCell(this.contextRow(LAYERS_LABEL, layers, MUTED_COLOR)),\n this.identityCell(this.contextRow(MODE_LABEL, metadata.majorMode, SUCCESS_COLOR)),\n this.identityCell(this.contextRow(MODEL_LABEL, `${model}${DISPLAY_SEPARATOR}${thinking}`, WARNING_COLOR)),\n ].map((row) => fitLine(row, width));\n\n return [...rows, this.hairline(width)];\n }\n\n private renderCompact(width: number, metadata: DoomHarnessMetadata): string[] {\n const repository = path.basename(metadata.root ?? this.cwd) || this.cwd;\n const profile = metadata.profile ?? DEFAULT_METADATA_LABEL;\n const domains = metadata.domains.join(',') || DEFAULT_METADATA_LABEL;\n const model = this.compactModel(this.runtime?.model?.id ?? DEFAULT_MODEL_LABEL);\n const thinking = this.runtime?.thinkingLevel ?? DEFAULT_THINKING_LABEL;\n const activity = this.runtime?.isIdle() === false ? ACTIVE_STATUS : READY_STATUS;\n const rows = [\n alignLine(\n `${this.wordmark()} ${this.theme.bold(this.theme.fg(TEXT_COLOR, repository))}`,\n this.theme.fg(SUCCESS_COLOR, activity),\n width,\n ),\n fitLine(`${profile}${DISPLAY_SEPARATOR}${domains}${DISPLAY_SEPARATOR}${metadata.majorMode}`, width),\n alignLine(\n this.theme.fg(WARNING_COLOR, `${model}${DISPLAY_SEPARATOR}${thinking}`),\n this.theme.fg(DIM_COLOR, 'CTRL+SPACE leader · SPC when empty'),\n width,\n ),\n ];\n\n return [...rows, this.hairline(width)];\n }\n\n private renderHints(width: number): string[] {\n const content = [\n this.keyHint('CTRL+SPACE', 'leader anywhere'),\n this.keyHint('SPC', 'when empty'),\n this.keyHint('/', 'commands'),\n this.keyHint('!', 'shell'),\n ].join(' ');\n return [fitLine(content, width)];\n }\n\n private renderEmptyState(width: number, height: number): string[] {\n const lines = Array.from({ length: Math.max(MIN_EMPTY_STATE_HEIGHT, height) }, () => '');\n const rule = this.theme.fg(MUTED_BORDER_COLOR, EMPTY_STATE_RULE);\n const label = `${rule} ${this.theme.fg(DIM_COLOR, EMPTY_STATE_LABEL)} ${rule}`;\n const guidance = this.theme.fg(MUTED_COLOR, EMPTY_STATE_GUIDANCE);\n const labelIndex = Math.max(0, Math.floor(lines.length / SECTION_SEPARATOR_ROWS) - 1);\n lines[labelIndex] = this.centerLine(label, width);\n if (lines.length > labelIndex + 1) lines[labelIndex + 1] = this.centerLine(guidance, width);\n\n return lines;\n }\n\n private hairline(width: number): string {\n return this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(Math.max(0, width)));\n }\n\n private centerLine(content: string, width: number): string {\n const fitted = fitLine(content, width);\n const leftPadding = ' '.repeat(Math.max(0, Math.floor((width - visibleWidth(fitted)) / SECTION_SEPARATOR_ROWS)));\n return fitLine(`${leftPadding}${fitted}`, width);\n }\n\n private identityCell(content: string): string {\n const bar = this.theme.fg(ACCENT_COLOR, IDENTITY_ACCENT_BAR);\n return content ? `${bar} ${content}` : bar;\n }\n\n private columns(left: string, right: string, width: number): string {\n const rightWidth = Math.max(0, width - IDENTITY_COLUMN_WIDTH - COLUMN_GAP);\n return fitLine(\n `${padLine(left, IDENTITY_COLUMN_WIDTH)}${' '.repeat(COLUMN_GAP)}${fitLine(right, rightWidth)}`,\n width,\n );\n }\n\n private contextRow(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.fg(DIM_COLOR, label.padEnd(CONTEXT_LABEL_WIDTH))}${this.theme.fg(color, value)}`;\n }\n\n private keyHint(key: string, label: string): string {\n const badge = this.theme.bg(SELECTED_BG_COLOR, this.theme.fg(ACCENT_COLOR, ` ${key} `));\n return `${badge} ${this.theme.fg(DIM_COLOR, label)}`;\n }\n\n private wordmark(): string {\n return `${this.theme.bold(this.theme.fg(ACCENT_COLOR, 'DOOM'))} ${this.theme.inverse(\n this.theme.bold(this.theme.fg(TEXT_COLOR, ' PI ')),\n )}`;\n }\n\n private compactModel(model: string): string {\n return model.split('/').at(-1)?.replace(CLAUDE_MODEL_PREFIX, '') ?? model;\n }\n\n private compactPath(value: string): string {\n const home = process.env[HOME_ENV];\n return home && value.startsWith(home) ? `~${value.slice(home.length)}` : value;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rendering.cjs");let t=require("@earendil-works/pi-tui");const n=`borderMuted`,r=`accent`,i=[`╭`,`╮`],a=[`├`,`┤`],o=[`╰`,`╯`],s={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}};function c(e,n){return!e||n<=0||n<Math.min((0,t.visibleWidth)(e),8)?``:(0,t.truncateToWidth)(e,n,`…`)}var l=class{tui;theme;constructor(e,t){this.tui=e,this.theme=t}invalidate(){this.tui.requestRender()}render(t){let r=Math.max(0,Math.floor(t)),s=Math.max(0,Math.floor(this.tui.terminal?.rows??24));if(r===0||s===0)return[];let c=this.getChrome();if(s<6||r<8)return this.renderCompact(c,r,s);let l=r-2,u=l-2,d=s-6,f=this.renderBody(u,d).slice(0,d);for(;f.length<d;)f.push(``);let p=c.accent??n;return[this.borderRow(i,l,p),this.contentRow(this.headerRow(c,u),u,r,p),this.borderRow(a,l,p,n),...f.map(e=>this.contentRow(e,u,r,p)),this.borderRow(a,l,p,n),this.contentRow(this.footerRow(c,u),u,r,p),this.borderRow(o,l,p)].map(t=>e.padLine(t,r))}borderRow([e,t],r,i=n,a=i){let o=i??n;return[this.theme.fg(o,e),this.theme.fg(a??o,`─`.repeat(r)),this.theme.fg(o,t)].join(``)}contentRow(t,r,i,a=n){let o=this.theme.fg(a??n,`│`);return e.frameLine(` ${e.padLine(t,r)} `,i,o,o)}headerRow(e,n){let i=e.accent,a=i?n-2:n,o=(0,t.truncateToWidth)(e.title,Math.max(0,a),`…`),s=i?this.theme.inverse(this.theme.bold(this.theme.fg(i,` ${o} `))):this.theme.bold(this.theme.fg(r,o)),l=(0,t.visibleWidth)(o)+(i?2:0),u=c(e.breadcrumb,n-l-2);return u&&(s+=`${` `.repeat(2)}${this.theme.fg(`dim`,u)}`,l+=2+(0,t.visibleWidth)(u)),this.withRightStatus(s,l,e.headerRight,n)}footerRow(e,n){let r=e.footerHints?.length?this.hintLegend(e.footerHints,n):this.theme.fg(`dim`,(0,t.truncateToWidth)(e.footer,n,`…`));return this.withRightStatus(r,(0,t.visibleWidth)(r),e.footerRight,n)}hintLegend(e,n){let r=``,i=0;for(let[a,o]of e){let e=(0,t.visibleWidth)(a)+(0,t.visibleWidth)(o)+6;if(i+e>n)break;let s=this.theme.bg(`selectedBg`,this.theme.fg(`text`,` ${a} `));r+=`${r?` `:``}${s}${this.theme.fg(`dim`,` ${o}`)}`,i+=e}return r}withRightStatus(e,n,r,i){let a=r?(0,t.visibleWidth)(r):0;return!r||a===0||n+2+a>i?e:`${e}${` `.repeat(i-n-a)}${this.theme.fg(`dim`,r)}`}renderCompact(t,n,i){let a=Array.from({length:i},()=>``);return a[0]=this.theme.bold(this.theme.fg(r,t.title)),i>1&&(a[i-1]=this.theme.fg(`dim`,t.footer)),a.map(t=>e.padLine(e.fitLine(t,n),n))}};exports.DOOM_FULLSCREEN_UI_OPTIONS=s,exports.DOOM_OVERLAY_ACCENT=`mdHeading`,exports.DoomOverlay=l;
|
|
2
|
+
//# sourceMappingURL=doomOverlay.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.cjs","names":["visibleWidth","truncateToWidth","padLine","frameLine","fitLine"],"sources":["../../src/components/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { fitLine, frameLine, padLine } from '../rendering.ts';\n\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst FULL_CHROME_ROWS = 6;\n/** Two frame columns and two gutter columns, leaving content worth framing. */\nconst MIN_FRAMED_WIDTH = 8;\nconst GUTTER = ' ';\nconst GUTTER_COLUMNS = 2;\n/** Blank columns kept between a row's left cluster and its right-aligned status. */\nconst ROW_GAP = 2;\n/** A secondary segment thinner than this is dropped rather than shredded into a stub. */\nconst MIN_SEGMENT_WIDTH = 8;\nconst ELLIPSIS = '…';\nconst BORDER_COLOR = 'borderMuted';\nconst TEXT_COLOR = 'text';\n/** Filled key caps read as pressable, matching the doom mockups' footers. */\nconst HINT_CAP_BACKGROUND = 'selectedBg';\nconst HINT_SEPARATOR = ' ';\n/** A cap's two pad columns plus the space before its label. */\nconst HINT_CHROME_COLUMNS = 6;\nconst TITLE_COLOR = 'accent';\nconst SECONDARY_COLOR = 'dim';\nconst HORIZONTAL_BORDER = '─';\nconst VERTICAL_BORDER = '│';\nconst TOP_CORNERS = ['╭', '╮'] as const;\nconst DIVIDER_JOINTS = ['├', '┤'] as const;\nconst BOTTOM_CORNERS = ['╰', '╯'] as const;\n\nexport interface DoomOverlayTui {\n terminal?: {\n rows: number;\n columns?: number;\n };\n requestRender(force?: boolean): void;\n}\n\nexport interface DoomOverlayChrome {\n title: string;\n breadcrumb?: string;\n headerRight?: string;\n footer: string;\n footerRight?: string;\n /**\n * Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.\n * Takes precedence over `footer`, which stays for surfaces that want a plain\n * sentence rather than a key legend.\n */\n footerHints?: readonly (readonly [string, string])[];\n /**\n * Signature colour for the surface. When set, the frame takes it and the\n * title renders as a filled badge rather than plain accent text, matching the\n * doom mockups. Left unset, the frame stays muted and the title plain.\n */\n accent?: Parameters<Theme['fg']>[0];\n}\n\n/**\n * One signature colour for every doom overlay: the frame and the title badge.\n * Per-surface colours were tried and read as arbitrary, and they also borrowed\n * tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit\n * elsewhere would have repainted a frame. Change it here to change them all.\n */\nexport const DOOM_OVERLAY_ACCENT = 'mdHeading' satisfies DoomOverlayChrome['accent'];\n\nexport const DOOM_FULLSCREEN_UI_OPTIONS = {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n} as const;\n\n/**\n * Fits a secondary segment into `budget`, or drops it entirely when too few\n * columns remain for it to stay readable. Truncating everything to fit is what\n * makes a tight header read as one run-on string.\n */\nfunction fitSegment(text: string | undefined, budget: number): string {\n if (!text || budget <= 0) return '';\n return budget < Math.min(visibleWidth(text), MIN_SEGMENT_WIDTH) ? '' : truncateToWidth(text, budget, ELLIPSIS);\n}\n\nexport abstract class DoomOverlay implements Component {\n protected constructor(\n protected readonly tui: DoomOverlayTui,\n protected readonly theme: Theme,\n ) {}\n\n invalidate(): void {\n this.tui.requestRender();\n }\n\n render(width: number): string[] {\n const safeWidth = Math.max(0, Math.floor(width));\n const height = Math.max(0, Math.floor(this.tui.terminal?.rows ?? DEFAULT_TERMINAL_ROWS));\n if (safeWidth === 0 || height === 0) return [];\n\n const chrome = this.getChrome();\n if (height < FULL_CHROME_ROWS || safeWidth < MIN_FRAMED_WIDTH) {\n return this.renderCompact(chrome, safeWidth, height);\n }\n\n const innerWidth = safeWidth - GUTTER_COLUMNS;\n const contentWidth = innerWidth - GUTTER_COLUMNS;\n const bodyHeight = height - FULL_CHROME_ROWS;\n const body = this.renderBody(contentWidth, bodyHeight).slice(0, bodyHeight);\n while (body.length < bodyHeight) body.push('');\n\n const frame = chrome.accent ?? BORDER_COLOR;\n return [\n this.borderRow(TOP_CORNERS, innerWidth, frame),\n this.contentRow(this.headerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n ...body.map((line) => this.contentRow(line, contentWidth, safeWidth, frame)),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n this.contentRow(this.footerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(BOTTOM_CORNERS, innerWidth, frame),\n ].map((line) => padLine(line, safeWidth));\n }\n\n protected abstract getChrome(): DoomOverlayChrome;\n\n protected abstract renderBody(width: number, height: number): string[];\n\n /**\n * Edge glyphs and the rule between them are coloured separately: the outer\n * rectangle carries the signature colour while an internal separator stays\n * muted, meeting the frame at accent-coloured junctions.\n */\n private borderRow(\n [left, right]: readonly [string, string],\n innerWidth: number,\n edge: DoomOverlayChrome['accent'] = BORDER_COLOR,\n fill: DoomOverlayChrome['accent'] = edge,\n ): string {\n const edgeColour = edge ?? BORDER_COLOR;\n return [\n this.theme.fg(edgeColour, left),\n this.theme.fg(fill ?? edgeColour, HORIZONTAL_BORDER.repeat(innerWidth)),\n this.theme.fg(edgeColour, right),\n ].join('');\n }\n\n private contentRow(\n content: string,\n contentWidth: number,\n width: number,\n colour: DoomOverlayChrome['accent'] = BORDER_COLOR,\n ): string {\n const edge = this.theme.fg(colour ?? BORDER_COLOR, VERTICAL_BORDER);\n return frameLine(`${GUTTER}${padLine(content, contentWidth)}${GUTTER}`, width, edge, edge);\n }\n\n private headerRow(chrome: DoomOverlayChrome, width: number): string {\n // A signature colour turns the title into a filled badge, as the mockups\n // show; without one it stays plain accent text.\n const accent = chrome.accent;\n const titleBudget = accent ? width - GUTTER_COLUMNS : width;\n const title = truncateToWidth(chrome.title, Math.max(0, titleBudget), ELLIPSIS);\n let left = accent\n ? this.theme.inverse(this.theme.bold(this.theme.fg(accent, ` ${title} `)))\n : this.theme.bold(this.theme.fg(TITLE_COLOR, title));\n let leftWidth = visibleWidth(title) + (accent ? GUTTER_COLUMNS : 0);\n\n // The breadcrumb claims room before the right status: it says where you are,\n // which outranks ambient context when the terminal is narrow.\n const breadcrumb = fitSegment(chrome.breadcrumb, width - leftWidth - ROW_GAP);\n if (breadcrumb) {\n left += `${' '.repeat(ROW_GAP)}${this.theme.fg(SECONDARY_COLOR, breadcrumb)}`;\n leftWidth += ROW_GAP + visibleWidth(breadcrumb);\n }\n return this.withRightStatus(left, leftWidth, chrome.headerRight, width);\n }\n\n private footerRow(chrome: DoomOverlayChrome, width: number): string {\n const footer = chrome.footerHints?.length\n ? this.hintLegend(chrome.footerHints, width)\n : this.theme.fg(SECONDARY_COLOR, truncateToWidth(chrome.footer, width, ELLIPSIS));\n return this.withRightStatus(footer, visibleWidth(footer), chrome.footerRight, width);\n }\n\n /**\n * Key legend with each key in a filled cap. Every segment sets its own colour\n * so the caps survive: a cap's reset would otherwise strip the surrounding\n * dim from the labels that follow it.\n */\n private hintLegend(hints: readonly (readonly [string, string])[], width: number): string {\n let legend = '';\n let used = 0;\n for (const [key, label] of hints) {\n const segmentWidth = visibleWidth(key) + visibleWidth(label) + HINT_CHROME_COLUMNS;\n if (used + segmentWidth > width) break;\n const cap = this.theme.bg(HINT_CAP_BACKGROUND, this.theme.fg(TEXT_COLOR, ` ${key} `));\n legend += `${legend ? HINT_SEPARATOR : ''}${cap}${this.theme.fg(SECONDARY_COLOR, ` ${label}`)}`;\n used += segmentWidth;\n }\n return legend;\n }\n\n /**\n * Right-aligns a status cluster, dropping it whole when the row cannot spare\n * the columns. A clipped status reads as a stub (`doom-log…`) that carries\n * less than the blank space it costs.\n */\n private withRightStatus(left: string, leftWidth: number, right: string | undefined, width: number): string {\n const statusWidth = right ? visibleWidth(right) : 0;\n if (!right || statusWidth === 0 || leftWidth + ROW_GAP + statusWidth > width) return left;\n return `${left}${' '.repeat(width - leftWidth - statusWidth)}${this.theme.fg(SECONDARY_COLOR, right)}`;\n }\n\n private renderCompact(chrome: DoomOverlayChrome, width: number, height: number): string[] {\n const lines = Array.from({ length: height }, () => '');\n lines[0] = this.theme.bold(this.theme.fg(TITLE_COLOR, chrome.title));\n if (height > 1) lines[height - 1] = this.theme.fg(SECONDARY_COLOR, chrome.footer);\n return lines.map((line) => padLine(fitLine(line, width), width));\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
3
|
+
//#region src/components/doomOverlay.d.ts
|
|
4
|
+
interface DoomOverlayTui {
|
|
5
|
+
terminal?: {
|
|
6
|
+
rows: number;
|
|
7
|
+
columns?: number;
|
|
8
|
+
};
|
|
9
|
+
requestRender(force?: boolean): void;
|
|
10
|
+
}
|
|
11
|
+
interface DoomOverlayChrome {
|
|
12
|
+
title: string;
|
|
13
|
+
breadcrumb?: string;
|
|
14
|
+
headerRight?: string;
|
|
15
|
+
footer: string;
|
|
16
|
+
footerRight?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.
|
|
19
|
+
* Takes precedence over `footer`, which stays for surfaces that want a plain
|
|
20
|
+
* sentence rather than a key legend.
|
|
21
|
+
*/
|
|
22
|
+
footerHints?: readonly (readonly [string, string])[];
|
|
23
|
+
/**
|
|
24
|
+
* Signature colour for the surface. When set, the frame takes it and the
|
|
25
|
+
* title renders as a filled badge rather than plain accent text, matching the
|
|
26
|
+
* doom mockups. Left unset, the frame stays muted and the title plain.
|
|
27
|
+
*/
|
|
28
|
+
accent?: Parameters<Theme['fg']>[0];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One signature colour for every doom overlay: the frame and the title badge.
|
|
32
|
+
* Per-surface colours were tried and read as arbitrary, and they also borrowed
|
|
33
|
+
* tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit
|
|
34
|
+
* elsewhere would have repainted a frame. Change it here to change them all.
|
|
35
|
+
*/
|
|
36
|
+
declare const DOOM_OVERLAY_ACCENT = "mdHeading";
|
|
37
|
+
declare const DOOM_FULLSCREEN_UI_OPTIONS: {
|
|
38
|
+
readonly overlay: true;
|
|
39
|
+
readonly overlayOptions: {
|
|
40
|
+
readonly anchor: "top-left";
|
|
41
|
+
readonly width: "100%";
|
|
42
|
+
readonly maxHeight: "100%";
|
|
43
|
+
readonly margin: 0;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
declare abstract class DoomOverlay implements Component {
|
|
47
|
+
protected readonly tui: DoomOverlayTui;
|
|
48
|
+
protected readonly theme: Theme;
|
|
49
|
+
protected constructor(tui: DoomOverlayTui, theme: Theme);
|
|
50
|
+
invalidate(): void;
|
|
51
|
+
render(width: number): string[];
|
|
52
|
+
protected abstract getChrome(): DoomOverlayChrome;
|
|
53
|
+
protected abstract renderBody(width: number, height: number): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Edge glyphs and the rule between them are coloured separately: the outer
|
|
56
|
+
* rectangle carries the signature colour while an internal separator stays
|
|
57
|
+
* muted, meeting the frame at accent-coloured junctions.
|
|
58
|
+
*/
|
|
59
|
+
private borderRow;
|
|
60
|
+
private contentRow;
|
|
61
|
+
private headerRow;
|
|
62
|
+
private footerRow;
|
|
63
|
+
/**
|
|
64
|
+
* Key legend with each key in a filled cap. Every segment sets its own colour
|
|
65
|
+
* so the caps survive: a cap's reset would otherwise strip the surrounding
|
|
66
|
+
* dim from the labels that follow it.
|
|
67
|
+
*/
|
|
68
|
+
private hintLegend;
|
|
69
|
+
/**
|
|
70
|
+
* Right-aligns a status cluster, dropping it whole when the row cannot spare
|
|
71
|
+
* the columns. A clipped status reads as a stub (`doom-log…`) that carries
|
|
72
|
+
* less than the blank space it costs.
|
|
73
|
+
*/
|
|
74
|
+
private withRightStatus;
|
|
75
|
+
private renderCompact;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { DOOM_FULLSCREEN_UI_OPTIONS, DOOM_OVERLAY_ACCENT, DoomOverlay, DoomOverlayChrome, DoomOverlayTui };
|
|
79
|
+
//# sourceMappingURL=doomOverlay.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.d.cts","names":[],"sources":["../../src/components/doomOverlay.ts"],"mappings":";;;UA8BiB;EACf;IACE;IACA;;EAEF,cAAc;;UAGC;EACf;EACA;EACA;EACA;EACA;;;;;;EAMA;;;;;;EAMA,SAAS,WAAW;;;;;;;;cAST;cAEA;;;;;;;;;uBAoBS,uBAAuB;qBAEtB,KAAK;qBACL,OAAO;YAFnB,YACY,KAAK,gBACL,OAAO;EAG5B;EAIA,OAAO;qBA4BY,aAAa;qBAEb,WAAW,eAAe;;;;;;UAOrC;UAcA;UAUA;UAqBA;;;;;;UAYA;;;;;;UAkBA;UAMA"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
3
|
+
//#region src/components/doomOverlay.d.ts
|
|
4
|
+
interface DoomOverlayTui {
|
|
5
|
+
terminal?: {
|
|
6
|
+
rows: number;
|
|
7
|
+
columns?: number;
|
|
8
|
+
};
|
|
9
|
+
requestRender(force?: boolean): void;
|
|
10
|
+
}
|
|
11
|
+
interface DoomOverlayChrome {
|
|
12
|
+
title: string;
|
|
13
|
+
breadcrumb?: string;
|
|
14
|
+
headerRight?: string;
|
|
15
|
+
footer: string;
|
|
16
|
+
footerRight?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.
|
|
19
|
+
* Takes precedence over `footer`, which stays for surfaces that want a plain
|
|
20
|
+
* sentence rather than a key legend.
|
|
21
|
+
*/
|
|
22
|
+
footerHints?: readonly (readonly [string, string])[];
|
|
23
|
+
/**
|
|
24
|
+
* Signature colour for the surface. When set, the frame takes it and the
|
|
25
|
+
* title renders as a filled badge rather than plain accent text, matching the
|
|
26
|
+
* doom mockups. Left unset, the frame stays muted and the title plain.
|
|
27
|
+
*/
|
|
28
|
+
accent?: Parameters<Theme['fg']>[0];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One signature colour for every doom overlay: the frame and the title badge.
|
|
32
|
+
* Per-surface colours were tried and read as arbitrary, and they also borrowed
|
|
33
|
+
* tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit
|
|
34
|
+
* elsewhere would have repainted a frame. Change it here to change them all.
|
|
35
|
+
*/
|
|
36
|
+
declare const DOOM_OVERLAY_ACCENT = "mdHeading";
|
|
37
|
+
declare const DOOM_FULLSCREEN_UI_OPTIONS: {
|
|
38
|
+
readonly overlay: true;
|
|
39
|
+
readonly overlayOptions: {
|
|
40
|
+
readonly anchor: "top-left";
|
|
41
|
+
readonly width: "100%";
|
|
42
|
+
readonly maxHeight: "100%";
|
|
43
|
+
readonly margin: 0;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
declare abstract class DoomOverlay implements Component {
|
|
47
|
+
protected readonly tui: DoomOverlayTui;
|
|
48
|
+
protected readonly theme: Theme;
|
|
49
|
+
protected constructor(tui: DoomOverlayTui, theme: Theme);
|
|
50
|
+
invalidate(): void;
|
|
51
|
+
render(width: number): string[];
|
|
52
|
+
protected abstract getChrome(): DoomOverlayChrome;
|
|
53
|
+
protected abstract renderBody(width: number, height: number): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Edge glyphs and the rule between them are coloured separately: the outer
|
|
56
|
+
* rectangle carries the signature colour while an internal separator stays
|
|
57
|
+
* muted, meeting the frame at accent-coloured junctions.
|
|
58
|
+
*/
|
|
59
|
+
private borderRow;
|
|
60
|
+
private contentRow;
|
|
61
|
+
private headerRow;
|
|
62
|
+
private footerRow;
|
|
63
|
+
/**
|
|
64
|
+
* Key legend with each key in a filled cap. Every segment sets its own colour
|
|
65
|
+
* so the caps survive: a cap's reset would otherwise strip the surrounding
|
|
66
|
+
* dim from the labels that follow it.
|
|
67
|
+
*/
|
|
68
|
+
private hintLegend;
|
|
69
|
+
/**
|
|
70
|
+
* Right-aligns a status cluster, dropping it whole when the row cannot spare
|
|
71
|
+
* the columns. A clipped status reads as a stub (`doom-log…`) that carries
|
|
72
|
+
* less than the blank space it costs.
|
|
73
|
+
*/
|
|
74
|
+
private withRightStatus;
|
|
75
|
+
private renderCompact;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { DOOM_FULLSCREEN_UI_OPTIONS, DOOM_OVERLAY_ACCENT, DoomOverlay, DoomOverlayChrome, DoomOverlayTui };
|
|
79
|
+
//# sourceMappingURL=doomOverlay.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.d.mts","names":[],"sources":["../../src/components/doomOverlay.ts"],"mappings":";;;UA8BiB;EACf;IACE;IACA;;EAEF,cAAc;;UAGC;EACf;EACA;EACA;EACA;EACA;;;;;;EAMA;;;;;;EAMA,SAAS,WAAW;;;;;;;;cAST;cAEA;;;;;;;;;uBAoBS,uBAAuB;qBAEtB,KAAK;qBACL,OAAO;YAFnB,YACY,KAAK,gBACL,OAAO;EAG5B;EAIA,OAAO;qBA4BY,aAAa;qBAEb,WAAW,eAAe;;;;;;UAOrC;UAcA;UAUA;UAqBA;;;;;;UAYA;;;;;;UAkBA;UAMA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{fitLine as e,frameLine as t,padLine as n}from"../rendering.mjs";import{truncateToWidth as r,visibleWidth as i}from"@earendil-works/pi-tui";const a=`borderMuted`,o=`accent`,s=[`╭`,`╮`],c=[`├`,`┤`],l=[`╰`,`╯`],u=`mdHeading`,d={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}};function f(e,t){return!e||t<=0||t<Math.min(i(e),8)?``:r(e,t,`…`)}var p=class{tui;theme;constructor(e,t){this.tui=e,this.theme=t}invalidate(){this.tui.requestRender()}render(e){let t=Math.max(0,Math.floor(e)),r=Math.max(0,Math.floor(this.tui.terminal?.rows??24));if(t===0||r===0)return[];let i=this.getChrome();if(r<6||t<8)return this.renderCompact(i,t,r);let o=t-2,u=o-2,d=r-6,f=this.renderBody(u,d).slice(0,d);for(;f.length<d;)f.push(``);let p=i.accent??a;return[this.borderRow(s,o,p),this.contentRow(this.headerRow(i,u),u,t,p),this.borderRow(c,o,p,a),...f.map(e=>this.contentRow(e,u,t,p)),this.borderRow(c,o,p,a),this.contentRow(this.footerRow(i,u),u,t,p),this.borderRow(l,o,p)].map(e=>n(e,t))}borderRow([e,t],n,r=a,i=r){let o=r??a;return[this.theme.fg(o,e),this.theme.fg(i??o,`─`.repeat(n)),this.theme.fg(o,t)].join(``)}contentRow(e,r,i,o=a){let s=this.theme.fg(o??a,`│`);return t(` ${n(e,r)} `,i,s,s)}headerRow(e,t){let n=e.accent,a=n?t-2:t,s=r(e.title,Math.max(0,a),`…`),c=n?this.theme.inverse(this.theme.bold(this.theme.fg(n,` ${s} `))):this.theme.bold(this.theme.fg(o,s)),l=i(s)+(n?2:0),u=f(e.breadcrumb,t-l-2);return u&&(c+=`${` `.repeat(2)}${this.theme.fg(`dim`,u)}`,l+=2+i(u)),this.withRightStatus(c,l,e.headerRight,t)}footerRow(e,t){let n=e.footerHints?.length?this.hintLegend(e.footerHints,t):this.theme.fg(`dim`,r(e.footer,t,`…`));return this.withRightStatus(n,i(n),e.footerRight,t)}hintLegend(e,t){let n=``,r=0;for(let[a,o]of e){let e=i(a)+i(o)+6;if(r+e>t)break;let s=this.theme.bg(`selectedBg`,this.theme.fg(`text`,` ${a} `));n+=`${n?` `:``}${s}${this.theme.fg(`dim`,` ${o}`)}`,r+=e}return n}withRightStatus(e,t,n,r){let a=n?i(n):0;return!n||a===0||t+2+a>r?e:`${e}${` `.repeat(r-t-a)}${this.theme.fg(`dim`,n)}`}renderCompact(t,r,i){let a=Array.from({length:i},()=>``);return a[0]=this.theme.bold(this.theme.fg(o,t.title)),i>1&&(a[i-1]=this.theme.fg(`dim`,t.footer)),a.map(t=>n(e(t,r),r))}};export{d as DOOM_FULLSCREEN_UI_OPTIONS,u as DOOM_OVERLAY_ACCENT,p as DoomOverlay};
|
|
2
|
+
//# sourceMappingURL=doomOverlay.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomOverlay.mjs","names":[],"sources":["../../src/components/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { fitLine, frameLine, padLine } from '../rendering.ts';\n\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst FULL_CHROME_ROWS = 6;\n/** Two frame columns and two gutter columns, leaving content worth framing. */\nconst MIN_FRAMED_WIDTH = 8;\nconst GUTTER = ' ';\nconst GUTTER_COLUMNS = 2;\n/** Blank columns kept between a row's left cluster and its right-aligned status. */\nconst ROW_GAP = 2;\n/** A secondary segment thinner than this is dropped rather than shredded into a stub. */\nconst MIN_SEGMENT_WIDTH = 8;\nconst ELLIPSIS = '…';\nconst BORDER_COLOR = 'borderMuted';\nconst TEXT_COLOR = 'text';\n/** Filled key caps read as pressable, matching the doom mockups' footers. */\nconst HINT_CAP_BACKGROUND = 'selectedBg';\nconst HINT_SEPARATOR = ' ';\n/** A cap's two pad columns plus the space before its label. */\nconst HINT_CHROME_COLUMNS = 6;\nconst TITLE_COLOR = 'accent';\nconst SECONDARY_COLOR = 'dim';\nconst HORIZONTAL_BORDER = '─';\nconst VERTICAL_BORDER = '│';\nconst TOP_CORNERS = ['╭', '╮'] as const;\nconst DIVIDER_JOINTS = ['├', '┤'] as const;\nconst BOTTOM_CORNERS = ['╰', '╯'] as const;\n\nexport interface DoomOverlayTui {\n terminal?: {\n rows: number;\n columns?: number;\n };\n requestRender(force?: boolean): void;\n}\n\nexport interface DoomOverlayChrome {\n title: string;\n breadcrumb?: string;\n headerRight?: string;\n footer: string;\n footerRight?: string;\n /**\n * Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.\n * Takes precedence over `footer`, which stays for surfaces that want a plain\n * sentence rather than a key legend.\n */\n footerHints?: readonly (readonly [string, string])[];\n /**\n * Signature colour for the surface. When set, the frame takes it and the\n * title renders as a filled badge rather than plain accent text, matching the\n * doom mockups. Left unset, the frame stays muted and the title plain.\n */\n accent?: Parameters<Theme['fg']>[0];\n}\n\n/**\n * One signature colour for every doom overlay: the frame and the title badge.\n * Per-surface colours were tried and read as arbitrary, and they also borrowed\n * tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit\n * elsewhere would have repainted a frame. Change it here to change them all.\n */\nexport const DOOM_OVERLAY_ACCENT = 'mdHeading' satisfies DoomOverlayChrome['accent'];\n\nexport const DOOM_FULLSCREEN_UI_OPTIONS = {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n} as const;\n\n/**\n * Fits a secondary segment into `budget`, or drops it entirely when too few\n * columns remain for it to stay readable. Truncating everything to fit is what\n * makes a tight header read as one run-on string.\n */\nfunction fitSegment(text: string | undefined, budget: number): string {\n if (!text || budget <= 0) return '';\n return budget < Math.min(visibleWidth(text), MIN_SEGMENT_WIDTH) ? '' : truncateToWidth(text, budget, ELLIPSIS);\n}\n\nexport abstract class DoomOverlay implements Component {\n protected constructor(\n protected readonly tui: DoomOverlayTui,\n protected readonly theme: Theme,\n ) {}\n\n invalidate(): void {\n this.tui.requestRender();\n }\n\n render(width: number): string[] {\n const safeWidth = Math.max(0, Math.floor(width));\n const height = Math.max(0, Math.floor(this.tui.terminal?.rows ?? DEFAULT_TERMINAL_ROWS));\n if (safeWidth === 0 || height === 0) return [];\n\n const chrome = this.getChrome();\n if (height < FULL_CHROME_ROWS || safeWidth < MIN_FRAMED_WIDTH) {\n return this.renderCompact(chrome, safeWidth, height);\n }\n\n const innerWidth = safeWidth - GUTTER_COLUMNS;\n const contentWidth = innerWidth - GUTTER_COLUMNS;\n const bodyHeight = height - FULL_CHROME_ROWS;\n const body = this.renderBody(contentWidth, bodyHeight).slice(0, bodyHeight);\n while (body.length < bodyHeight) body.push('');\n\n const frame = chrome.accent ?? BORDER_COLOR;\n return [\n this.borderRow(TOP_CORNERS, innerWidth, frame),\n this.contentRow(this.headerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n ...body.map((line) => this.contentRow(line, contentWidth, safeWidth, frame)),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n this.contentRow(this.footerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(BOTTOM_CORNERS, innerWidth, frame),\n ].map((line) => padLine(line, safeWidth));\n }\n\n protected abstract getChrome(): DoomOverlayChrome;\n\n protected abstract renderBody(width: number, height: number): string[];\n\n /**\n * Edge glyphs and the rule between them are coloured separately: the outer\n * rectangle carries the signature colour while an internal separator stays\n * muted, meeting the frame at accent-coloured junctions.\n */\n private borderRow(\n [left, right]: readonly [string, string],\n innerWidth: number,\n edge: DoomOverlayChrome['accent'] = BORDER_COLOR,\n fill: DoomOverlayChrome['accent'] = edge,\n ): string {\n const edgeColour = edge ?? BORDER_COLOR;\n return [\n this.theme.fg(edgeColour, left),\n this.theme.fg(fill ?? edgeColour, HORIZONTAL_BORDER.repeat(innerWidth)),\n this.theme.fg(edgeColour, right),\n ].join('');\n }\n\n private contentRow(\n content: string,\n contentWidth: number,\n width: number,\n colour: DoomOverlayChrome['accent'] = BORDER_COLOR,\n ): string {\n const edge = this.theme.fg(colour ?? BORDER_COLOR, VERTICAL_BORDER);\n return frameLine(`${GUTTER}${padLine(content, contentWidth)}${GUTTER}`, width, edge, edge);\n }\n\n private headerRow(chrome: DoomOverlayChrome, width: number): string {\n // A signature colour turns the title into a filled badge, as the mockups\n // show; without one it stays plain accent text.\n const accent = chrome.accent;\n const titleBudget = accent ? width - GUTTER_COLUMNS : width;\n const title = truncateToWidth(chrome.title, Math.max(0, titleBudget), ELLIPSIS);\n let left = accent\n ? this.theme.inverse(this.theme.bold(this.theme.fg(accent, ` ${title} `)))\n : this.theme.bold(this.theme.fg(TITLE_COLOR, title));\n let leftWidth = visibleWidth(title) + (accent ? GUTTER_COLUMNS : 0);\n\n // The breadcrumb claims room before the right status: it says where you are,\n // which outranks ambient context when the terminal is narrow.\n const breadcrumb = fitSegment(chrome.breadcrumb, width - leftWidth - ROW_GAP);\n if (breadcrumb) {\n left += `${' '.repeat(ROW_GAP)}${this.theme.fg(SECONDARY_COLOR, breadcrumb)}`;\n leftWidth += ROW_GAP + visibleWidth(breadcrumb);\n }\n return this.withRightStatus(left, leftWidth, chrome.headerRight, width);\n }\n\n private footerRow(chrome: DoomOverlayChrome, width: number): string {\n const footer = chrome.footerHints?.length\n ? this.hintLegend(chrome.footerHints, width)\n : this.theme.fg(SECONDARY_COLOR, truncateToWidth(chrome.footer, width, ELLIPSIS));\n return this.withRightStatus(footer, visibleWidth(footer), chrome.footerRight, width);\n }\n\n /**\n * Key legend with each key in a filled cap. Every segment sets its own colour\n * so the caps survive: a cap's reset would otherwise strip the surrounding\n * dim from the labels that follow it.\n */\n private hintLegend(hints: readonly (readonly [string, string])[], width: number): string {\n let legend = '';\n let used = 0;\n for (const [key, label] of hints) {\n const segmentWidth = visibleWidth(key) + visibleWidth(label) + HINT_CHROME_COLUMNS;\n if (used + segmentWidth > width) break;\n const cap = this.theme.bg(HINT_CAP_BACKGROUND, this.theme.fg(TEXT_COLOR, ` ${key} `));\n legend += `${legend ? HINT_SEPARATOR : ''}${cap}${this.theme.fg(SECONDARY_COLOR, ` ${label}`)}`;\n used += segmentWidth;\n }\n return legend;\n }\n\n /**\n * Right-aligns a status cluster, dropping it whole when the row cannot spare\n * the columns. A clipped status reads as a stub (`doom-log…`) that carries\n * less than the blank space it costs.\n */\n private withRightStatus(left: string, leftWidth: number, right: string | undefined, width: number): string {\n const statusWidth = right ? visibleWidth(right) : 0;\n if (!right || statusWidth === 0 || leftWidth + ROW_GAP + statusWidth > width) return left;\n return `${left}${' '.repeat(width - leftWidth - statusWidth)}${this.theme.fg(SECONDARY_COLOR, right)}`;\n }\n\n private renderCompact(chrome: DoomOverlayChrome, width: number, height: number): string[] {\n const lines = Array.from({ length: height }, () => '');\n lines[0] = this.theme.bold(this.theme.fg(TITLE_COLOR, chrome.title));\n if (height > 1) lines[height - 1] = this.theme.fg(SECONDARY_COLOR, chrome.footer);\n return lines.map((line) => padLine(fitLine(line, width), width));\n }\n}\n"],"mappings":"kJAIA,MAWM,EAAe,cAOf,EAAc,SAId,EAAc,CAAC,IAAK,GAAG,EACvB,EAAiB,CAAC,IAAK,GAAG,EAC1B,EAAiB,CAAC,IAAK,GAAG,EAoCnB,EAAsB,YAEtB,EAA6B,CACxC,QAAS,GACT,eAAgB,CACd,OAAQ,WACR,MAAO,OACP,UAAW,OACX,OAAQ,CACV,CACF,EAOA,SAAS,EAAW,EAA0B,EAAwB,CAEpE,MADI,CAAC,GAAQ,GAAU,GAChB,EAAS,KAAK,IAAI,EAAa,CAAI,EAAG,CAAiB,EAD7B,GACsC,EAAgB,EAAM,EAAQ,GAAQ,CAC/G,CAEA,IAAsB,EAAtB,KAAuD,CAEhC,IACA,MAFrB,YACE,EACA,EACA,CAFmB,KAAA,IAAA,EACA,KAAA,MAAA,CAClB,CAEH,YAAmB,CACjB,KAAK,IAAI,cAAc,CACzB,CAEA,OAAO,EAAyB,CAC9B,IAAM,EAAY,KAAK,IAAI,EAAG,KAAK,MAAM,CAAK,CAAC,EACzC,EAAS,KAAK,IAAI,EAAG,KAAK,MAAM,KAAK,IAAI,UAAU,MAAQ,EAAqB,CAAC,EACvF,GAAI,IAAc,GAAK,IAAW,EAAG,MAAO,CAAC,EAE7C,IAAM,EAAS,KAAK,UAAU,EAC9B,GAAI,EAAS,GAAoB,EAAY,EAC3C,OAAO,KAAK,cAAc,EAAQ,EAAW,CAAM,EAGrD,IAAM,EAAa,EAAY,EACzB,EAAe,EAAa,EAC5B,EAAa,EAAS,EACtB,EAAO,KAAK,WAAW,EAAc,CAAU,CAAC,CAAC,MAAM,EAAG,CAAU,EAC1E,KAAO,EAAK,OAAS,GAAY,EAAK,KAAK,EAAE,EAE7C,IAAM,EAAQ,EAAO,QAAU,EAC/B,MAAO,CACL,KAAK,UAAU,EAAa,EAAY,CAAK,EAC7C,KAAK,WAAW,KAAK,UAAU,EAAQ,CAAY,EAAG,EAAc,EAAW,CAAK,EACpF,KAAK,UAAU,EAAgB,EAAY,EAAO,CAAY,EAC9D,GAAG,EAAK,IAAK,GAAS,KAAK,WAAW,EAAM,EAAc,EAAW,CAAK,CAAC,EAC3E,KAAK,UAAU,EAAgB,EAAY,EAAO,CAAY,EAC9D,KAAK,WAAW,KAAK,UAAU,EAAQ,CAAY,EAAG,EAAc,EAAW,CAAK,EACpF,KAAK,UAAU,EAAgB,EAAY,CAAK,CAClD,CAAC,CAAC,IAAK,GAAS,EAAQ,EAAM,CAAS,CAAC,CAC1C,CAWA,UACE,CAAC,EAAM,GACP,EACA,EAAoC,EACpC,EAAoC,EAC5B,CACR,IAAM,EAAa,GAAQ,EAC3B,MAAO,CACL,KAAK,MAAM,GAAG,EAAY,CAAI,EAC9B,KAAK,MAAM,GAAG,GAAQ,EAAY,IAAkB,OAAO,CAAU,CAAC,EACtE,KAAK,MAAM,GAAG,EAAY,CAAK,CACjC,CAAC,CAAC,KAAK,EAAE,CACX,CAEA,WACE,EACA,EACA,EACA,EAAsC,EAC9B,CACR,IAAM,EAAO,KAAK,MAAM,GAAG,GAAU,EAAc,GAAe,EAClE,OAAO,EAAU,IAAY,EAAQ,EAAS,CAAY,KAAc,EAAO,EAAM,CAAI,CAC3F,CAEA,UAAkB,EAA2B,EAAuB,CAGlE,IAAM,EAAS,EAAO,OAChB,EAAc,EAAS,EAAQ,EAAiB,EAChD,EAAQ,EAAgB,EAAO,MAAO,KAAK,IAAI,EAAG,CAAW,EAAG,GAAQ,EAC1E,EAAO,EACP,KAAK,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAQ,IAAI,EAAM,EAAE,CAAC,CAAC,EACvE,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAa,CAAK,CAAC,EACjD,EAAY,EAAa,CAAK,GAAK,EAAS,EAAiB,GAI3D,EAAa,EAAW,EAAO,WAAY,EAAQ,EAAY,CAAO,EAK5E,OAJI,IACF,GAAQ,GAAG,IAAI,OAAO,CAAO,IAAI,KAAK,MAAM,GAAG,MAAiB,CAAU,IAC1E,GAAa,EAAU,EAAa,CAAU,GAEzC,KAAK,gBAAgB,EAAM,EAAW,EAAO,YAAa,CAAK,CACxE,CAEA,UAAkB,EAA2B,EAAuB,CAClE,IAAM,EAAS,EAAO,aAAa,OAC/B,KAAK,WAAW,EAAO,YAAa,CAAK,EACzC,KAAK,MAAM,GAAG,MAAiB,EAAgB,EAAO,OAAQ,EAAO,GAAQ,CAAC,EAClF,OAAO,KAAK,gBAAgB,EAAQ,EAAa,CAAM,EAAG,EAAO,YAAa,CAAK,CACrF,CAOA,WAAmB,EAA+C,EAAuB,CACvF,IAAI,EAAS,GACT,EAAO,EACX,IAAK,GAAM,CAAC,EAAK,KAAU,EAAO,CAChC,IAAM,EAAe,EAAa,CAAG,EAAI,EAAa,CAAK,EAAI,EAC/D,GAAI,EAAO,EAAe,EAAO,MACjC,IAAM,EAAM,KAAK,MAAM,GAAG,aAAqB,KAAK,MAAM,GAAG,OAAY,IAAI,EAAI,EAAE,CAAC,EACpF,GAAU,GAAG,EAAS,MAAiB,KAAK,IAAM,KAAK,MAAM,GAAG,MAAiB,IAAI,GAAO,IAC5F,GAAQ,CACV,CACA,OAAO,CACT,CAOA,gBAAwB,EAAc,EAAmB,EAA2B,EAAuB,CACzG,IAAM,EAAc,EAAQ,EAAa,CAAK,EAAI,EAElD,MADI,CAAC,GAAS,IAAgB,GAAK,EAAY,EAAU,EAAc,EAAc,EAC9E,GAAG,IAAO,IAAI,OAAO,EAAQ,EAAY,CAAW,IAAI,KAAK,MAAM,GAAG,MAAiB,CAAK,GACrG,CAEA,cAAsB,EAA2B,EAAe,EAA0B,CACxF,IAAM,EAAQ,MAAM,KAAK,CAAE,OAAQ,CAAO,MAAS,EAAE,EAGrD,MAFA,GAAM,GAAK,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,EAAa,EAAO,KAAK,CAAC,EAC/D,EAAS,IAAG,EAAM,EAAS,GAAK,KAAK,MAAM,GAAG,MAAiB,EAAO,MAAM,GACzE,EAAM,IAAK,GAAS,EAAQ,EAAQ,EAAM,CAAK,EAAG,CAAK,CAAC,CACjE,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rendering.cjs");let t=require("@earendil-works/pi-tui");const n=` · `,r=` `,i=`text`,a=`accent`,o=`warning`,s=`mdHeading`,c=[`mdCode`,`toolDiffAdded`,`warning`,`muted`];var l=class{theme;snapshot;context;footerData;modes;constructor(e,t,n,r,i=[]){this.theme=e,this.snapshot=t,this.context=n,this.footerData=r,this.modes=i}invalidate(){}render(t){if(t<=0||!this.snapshot.active)return[];if(t<4)return[this.theme.fg(`borderAccent`,`─`.repeat(t))];let n=Math.max(1,t-4),r=n=>this.theme.bg(`userMessageBg`,e.padLine(` ${n}`,t)),a=this.theme.inverse(this.theme.bold(this.theme.fg(s,` LEADER `))),o=`${this.snapshot.prefix.join(` › `)} / ${this.snapshot.label}`,c=r(e.alignLine(`${a} ${this.theme.bold(this.theme.fg(i,o))}`,this.theme.fg(`dim`,`esc cancel · backspace up`),n)),l=this.snapshot.rootOptions?.map(e=>`${e.key} ${e.label}`).join(` · `),u=t>=100&&this.snapshot.prefix.length>1&&l?[r(this.theme.fg(`dim`,`root ${l}`))]:[],d=Math.max(1,14-u.length),f=this.renderOptionGrid(n).slice(0,d).map(r),p=Math.max(0,12-u.length-f.length),m=this.renderDiagnostics(n).slice(0,p).map(r);return[...[r(``),c,r(``),...f,...u,...m].slice(0,15),r(``)]}renderOptionGrid(n){if(this.snapshot.options.length===0)return[];let o=this.snapshot.options.map(e=>({heading:`${this.theme.inverse(this.theme.bold(this.theme.fg(a,` ${e.key} `)))} ${this.theme.bold(this.theme.fg(i,e.label))}`,headingWidth:4+(0,t.visibleWidth)(e.label),detail:e.detail?`${r}${this.theme.fg(`dim`,e.detail)}`:``,detailWidth:e.detail?(0,t.visibleWidth)(r)+(0,t.visibleWidth)(e.detail):0})),s=Math.max(...o.map(e=>Math.max(e.headingWidth,e.detailWidth))),c=Math.min(n,s),l=Math.max(1,Math.floor((n+2)/(c+2))),u=` `.repeat(2),d=[];for(let t=0;t<o.length;t+=l){let r=o.slice(t,t+l);d.push(e.fitLine(r.map(t=>e.padLine(t.heading,c)).join(u),n)),d.push(e.fitLine(r.map(t=>e.padLine(t.detail,c)).join(u),n))}return d}renderDiagnostics(e){let t=this.sessionDiagnostics(),n=this.extensionDiagnostics(e),r=this.modeDiagnostics();if(!t&&!r&&n.length===0)return[];let i=[this.theme.fg(`borderMuted`,`─`.repeat(e))];r&&i.push(this.diagnosticLine(`MODES`,r,o)),t&&i.push(this.diagnosticLine(`SESSION`,t,s));for(let[e,t]of n.entries()){let n=e===0;i.push(this.diagnosticLine(n?`EXTENSIONS`:`↳`,t,n?a:`dim`))}return i}diagnosticLine(e,t,n){return`${this.theme.bold(this.theme.fg(n,e.padEnd(11)))}${t}`}modeDiagnostics(){if(this.modes.length!==0)return this.modes.map(e=>{let t=this.theme.fg(o,e.label.toLowerCase());return e.detail?`${t}${this.theme.fg(`dim`,` (${e.detail})`)}`:t}).join(this.theme.fg(`dim`,n))}sessionDiagnostics(){if(!this.context)return;let t=0,r=0,i=0,a=0,s=0;for(let e of this.context.sessionManager.getBranch())e.type===`message`&&e.message.role===`assistant`&&(t+=e.message.usage.input,r+=e.message.usage.output,i+=e.message.usage.cacheRead,a+=e.message.usage.cacheWrite,s+=e.message.usage.cost.total);let c=t+i+a,l=c===0?0:i/c*100,u=this.context.getContextUsage(),d=u?.percent===null||u?.percent===void 0?`ctx ?`:`ctx ${u.percent.toFixed(1)}% / ${e.formatTokens(u.contextWindow)}`;return`${this.theme.fg(`muted`,`↑${e.formatTokens(t)} ↓${e.formatTokens(r)} R${e.formatTokens(i)} W${e.formatTokens(a)} CH${l.toFixed(1)}%`)} ${this.theme.fg(o,`$${s.toFixed(3)}`)}${this.theme.fg(`dim`,`${n}${d}`)}`}extensionDiagnostics(t){if(!this.footerData)return[];let r=[...this.footerData.getExtensionStatuses().values()],i=this.footerData.getAvailableProviderCount(),a=[...r];i>0&&a.push(`${i} provider${i===1?``:`s`}`);let o=a.map((e,t)=>this.theme.fg(c[t%c.length],e)),s=Math.max(1,t-15);return e.packSegments(o,s,this.theme.fg(`dim`,n)).slice(0,2)}};exports.LeaderHints=l;
|
|
2
|
+
//# sourceMappingURL=leaderHints.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaderHints.cjs","names":["padLine","alignLine","visibleWidth","fitLine","formatTokens","packSegments"],"sources":["../../src/components/leaderHints.ts"],"sourcesContent":["import type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\nimport { visibleWidth, type Component } from '@earendil-works/pi-tui';\nimport { alignLine, fitLine, formatTokens, packSegments, padLine } from '../rendering.ts';\nimport type { DoomModeStatus } from '../mode.ts';\nimport type { LeaderSnapshot } from '../uiState.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst WIDE_HINT_WIDTH = 100;\n/**\n * No frame rows to pay for, so the whole budget is content. Two-line option\n * cards need roughly double the grid rows a single-line list did, plus a blank\n * row of breathing space at the top and bottom.\n */\nconst MAX_WIDGET_LINES = 16;\n/** Leading pad inside the shaded block, mirrored by padLine on the right. */\nconst PANEL_INSET = ' ';\nconst MIN_RENDER_WIDTH = 4;\nconst CONTENT_INSET = 4;\nconst GRID_GAP = 2;\nconst DIAGNOSTIC_LABEL_WIDTH = 11;\nconst DIAGNOSTIC_VALUE_INSET = 15;\nconst MAX_EXTENSION_LINES = 2;\nconst STATUS_SEPARATOR = ' · ';\nconst SEGMENT_GAP = ' ';\n/** The inverse key badge is ` k `, three columns wide. */\nconst KEY_BADGE_WIDTH = 3;\n/** Puts the description under the label rather than under the badge. */\nconst DETAIL_INDENT = ' ';\nconst HORIZONTAL_BORDER = '─';\nconst CONTINUATION_MARKER = '↳';\nconst DIM_COLOR: ThemeColor = 'dim';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst BORDER_COLOR: ThemeColor = 'borderAccent';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n/** Raised surface tone: one step above the terminal background, no frame needed. */\nconst PANEL_BACKGROUND: ThemeBg = 'userMessageBg';\n// The mockup colors each extension status differently so adjacent values stay\n// separable at a glance; cycling this palette reproduces that without hardcoding\n// a color per status key.\nconst STATUS_COLORS: ThemeColor[] = ['mdCode', 'toolDiffAdded', 'warning', 'muted'];\n\nexport class LeaderHints implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly snapshot: LeaderSnapshot,\n private readonly context?: ExtensionContext,\n private readonly footerData?: ReadonlyFooterDataProvider,\n private readonly modes: readonly DoomModeStatus[] = [],\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0 || !this.snapshot.active) return [];\n if (width < MIN_RENDER_WIDTH) return [this.theme.fg(BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n\n // A filled block rather than a framed one: the shade alone separates the\n // panel from the transcript, which buys back the two rows a frame costs.\n const contentWidth = Math.max(1, width - CONTENT_INSET);\n const shade = (content: string): string =>\n this.theme.bg(PANEL_BACKGROUND, padLine(`${PANEL_INSET}${content}`, width));\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg(HEADING_COLOR, ' LEADER ')));\n const breadcrumb = `${this.snapshot.prefix.join(' › ')} / ${this.snapshot.label}`;\n const header = shade(\n alignLine(\n `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, breadcrumb))}`,\n this.theme.fg(DIM_COLOR, 'esc cancel · backspace up'),\n contentWidth,\n ),\n );\n const rootTrail = this.snapshot.rootOptions?.map((option) => `${option.key} ${option.label}`).join(' · ');\n const trail =\n width >= WIDE_HINT_WIDTH && this.snapshot.prefix.length > 1 && rootTrail\n ? [shade(this.theme.fg(DIM_COLOR, `root ${rootTrail}`))]\n : [];\n // Options are the interactive surface, so they claim space before the\n // diagnostics do; truncating them would hide a reachable key.\n const optionBudget = Math.max(1, MAX_WIDGET_LINES - 2 - trail.length);\n const optionLines = this.renderOptionGrid(contentWidth).slice(0, optionBudget).map(shade);\n // Four rows are spoken for before diagnostics get any: the blank, header and\n // blank above, and the closing blank below. Counting only two of them let\n // this budget overrun, and the final `slice` did the real cutting silently\n // from the end, which is how the EXTENSIONS row went missing when MODES\n // arrived. Take all four off here so what is dropped is decided in one place.\n const diagnosticsBudget = Math.max(0, MAX_WIDGET_LINES - 4 - trail.length - optionLines.length);\n const diagnostics = this.renderDiagnostics(contentWidth).slice(0, diagnosticsBudget).map(shade);\n // Blank shaded rows top and bottom: the panel butts against the editor above\n // and the modeline below, so the shade needs room before its first glyph.\n const body = [shade(''), header, shade(''), ...optionLines, ...trail, ...diagnostics];\n\n return [...body.slice(0, MAX_WIDGET_LINES - 1), shade('')];\n }\n\n /**\n * Lays options out on a fixed cell grid rather than a packed run of segments,\n * so keys and labels stay column-aligned when the list wraps.\n */\n private renderOptionGrid(width: number): string[] {\n if (this.snapshot.options.length === 0) return [];\n\n // Two lines per option, as the mockup lays them out: the key badge and a\n // bold label, then the description indented under the label.\n const cells = this.snapshot.options.map((option) => {\n const key = this.theme.inverse(this.theme.bold(this.theme.fg(ACCENT_COLOR, ` ${option.key} `)));\n return {\n heading: `${key} ${this.theme.bold(this.theme.fg(TEXT_COLOR, option.label))}`,\n headingWidth: KEY_BADGE_WIDTH + 1 + visibleWidth(option.label),\n detail: option.detail ? `${DETAIL_INDENT}${this.theme.fg(DIM_COLOR, option.detail)}` : '',\n detailWidth: option.detail ? visibleWidth(DETAIL_INDENT) + visibleWidth(option.detail) : 0,\n };\n });\n const widest = Math.max(...cells.map((cell) => Math.max(cell.headingWidth, cell.detailWidth)));\n const cellWidth = Math.min(width, widest);\n const columns = Math.max(1, Math.floor((width + GRID_GAP) / (cellWidth + GRID_GAP)));\n const gap = ' '.repeat(GRID_GAP);\n const rows: string[] = [];\n for (let index = 0; index < cells.length; index += columns) {\n const group = cells.slice(index, index + columns);\n rows.push(fitLine(group.map((cell) => padLine(cell.heading, cellWidth)).join(gap), width));\n // Always two rows per card, even where no option carries a description:\n // dropping the empty one lets the next row's badges close up against the\n // ones above, which reads as a vertical block of keys rather than cards.\n rows.push(fitLine(group.map((cell) => padLine(cell.detail, cellWidth)).join(gap), width));\n }\n return rows;\n }\n\n /** Content lines only: the caller paints the shade and pads to full width. */\n private renderDiagnostics(width: number): string[] {\n const session = this.sessionDiagnostics();\n const extensions = this.extensionDiagnostics(width);\n const modes = this.modeDiagnostics();\n if (!session && !modes && extensions.length === 0) return [];\n\n const lines = [this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n // Above SESSION because it is the more consequential fact: session counters\n // describe what this session has spent, a mode describes what it will do.\n //\n // No blank rows between these three. The diagnostics budget is what is left\n // after the option grid takes its share, which at a full grid is exactly one\n // row per label: a spacer here does not cost breathing room, it costs the\n // EXTENSIONS row entirely. Distinct labels in distinct colours separate them\n // well enough without one.\n if (modes) lines.push(this.diagnosticLine('MODES', modes, WARNING_COLOR));\n if (session) lines.push(this.diagnosticLine('SESSION', session, HEADING_COLOR));\n for (const [index, extension] of extensions.entries()) {\n const isFirst = index === 0;\n lines.push(\n this.diagnosticLine(\n isFirst ? 'EXTENSIONS' : CONTINUATION_MARKER,\n extension,\n isFirst ? ACCENT_COLOR : DIM_COLOR,\n ),\n );\n }\n return lines;\n }\n\n private diagnosticLine(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.bold(this.theme.fg(color, label.padEnd(DIAGNOSTIC_LABEL_WIDTH)))}${value}`;\n }\n\n /**\n * The enabled modes with their detail, which is the long form of what the\n * editor's badge row shows as bare names.\n *\n * This panel is opened deliberately and has room, so it is the right place\n * for `plan (normal - read only)`: the badge row is glanced at mid-typing and\n * only has to answer which modes are on.\n */\n private modeDiagnostics(): string | undefined {\n if (this.modes.length === 0) return undefined;\n return this.modes\n .map((mode) => {\n const label = this.theme.fg(WARNING_COLOR, mode.label.toLowerCase());\n return mode.detail ? `${label}${this.theme.fg(DIM_COLOR, ` (${mode.detail})`)}` : label;\n })\n .join(this.theme.fg(DIM_COLOR, STATUS_SEPARATOR));\n }\n\n private sessionDiagnostics(): string | undefined {\n if (!this.context) return undefined;\n\n let input = 0;\n let output = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let cost = 0;\n for (const entry of this.context.sessionManager.getBranch()) {\n if (entry.type !== 'message' || entry.message.role !== 'assistant') continue;\n input += entry.message.usage.input;\n output += entry.message.usage.output;\n cacheRead += entry.message.usage.cacheRead;\n cacheWrite += entry.message.usage.cacheWrite;\n cost += entry.message.usage.cost.total;\n }\n\n const cachedTotal = input + cacheRead + cacheWrite;\n const cacheHit = cachedTotal === 0 ? 0 : (cacheRead / cachedTotal) * 100;\n const usage = this.context.getContextUsage();\n const context =\n usage?.percent === null || usage?.percent === undefined\n ? 'ctx ?'\n : `ctx ${usage.percent.toFixed(1)}% / ${formatTokens(usage.contextWindow)}`;\n // Built as sibling segments rather than nested theme calls so each color\n // closes cleanly instead of resetting the one around it.\n const metrics = this.theme.fg(\n MUTED_COLOR,\n `↑${formatTokens(input)} ↓${formatTokens(output)} R${formatTokens(cacheRead)} W${formatTokens(\n cacheWrite,\n )} CH${cacheHit.toFixed(1)}%`,\n );\n const costText = this.theme.fg(WARNING_COLOR, `$${cost.toFixed(3)}`);\n return `${metrics}${SEGMENT_GAP}${costText}${this.theme.fg(DIM_COLOR, `${STATUS_SEPARATOR}${context}`)}`;\n }\n\n private extensionDiagnostics(width: number): string[] {\n if (!this.footerData) return [];\n\n const statuses = [...this.footerData.getExtensionStatuses().values()];\n const providers = this.footerData.getAvailableProviderCount();\n const values = [...statuses];\n if (providers > 0) values.push(`${providers} provider${providers === 1 ? '' : 's'}`);\n const colored = values.map((value, index) =>\n this.theme.fg(STATUS_COLORS[index % STATUS_COLORS.length] as ThemeColor, value),\n );\n const valueWidth = Math.max(1, width - DIAGNOSTIC_VALUE_INSET);\n return packSegments(colored, valueWidth, this.theme.fg(DIM_COLOR, STATUS_SEPARATOR)).slice(0, MAX_EXTENSION_LINES);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mode_d_exports } from "../mode.cjs";
|
|
2
|
+
import { LeaderSnapshot } from "../uiState.cjs";
|
|
3
|
+
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Component } from "@earendil-works/pi-tui";
|
|
5
|
+
//#region src/components/leaderHints.d.ts
|
|
6
|
+
declare class LeaderHints implements Component {
|
|
7
|
+
private readonly theme;
|
|
8
|
+
private readonly snapshot;
|
|
9
|
+
private readonly context?;
|
|
10
|
+
private readonly footerData?;
|
|
11
|
+
private readonly modes;
|
|
12
|
+
constructor(theme: Theme, snapshot: LeaderSnapshot, context?: ExtensionContext | undefined, footerData?: ReadonlyFooterDataProvider | undefined, modes?: readonly mode_d_exports.DoomModeStatus[]);
|
|
13
|
+
invalidate(): void;
|
|
14
|
+
render(width: number): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Lays options out on a fixed cell grid rather than a packed run of segments,
|
|
17
|
+
* so keys and labels stay column-aligned when the list wraps.
|
|
18
|
+
*/
|
|
19
|
+
private renderOptionGrid;
|
|
20
|
+
/** Content lines only: the caller paints the shade and pads to full width. */
|
|
21
|
+
private renderDiagnostics;
|
|
22
|
+
private diagnosticLine;
|
|
23
|
+
/**
|
|
24
|
+
* The enabled modes with their detail, which is the long form of what the
|
|
25
|
+
* editor's badge row shows as bare names.
|
|
26
|
+
*
|
|
27
|
+
* This panel is opened deliberately and has room, so it is the right place
|
|
28
|
+
* for `plan (normal - read only)`: the badge row is glanced at mid-typing and
|
|
29
|
+
* only has to answer which modes are on.
|
|
30
|
+
*/
|
|
31
|
+
private modeDiagnostics;
|
|
32
|
+
private sessionDiagnostics;
|
|
33
|
+
private extensionDiagnostics;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { LeaderHints };
|
|
37
|
+
//# sourceMappingURL=leaderHints.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaderHints.d.cts","names":[],"sources":["../../src/components/leaderHints.ts"],"mappings":";;;;;cAgDa,uBAAuB;mBAEf;mBACA;mBACA;mBACA;mBACA;EAJA,YAAA,OAAO,OACP,UAAU,gBACV,UAAU,8BACV,aAAa,wCACb,iBAAgB,eAAA;EAGnC;EAEA,OAAO;;;;;UA6CC;;UA+BA;UA8BA;;;;;;;;;UAYA;UAUA;UAoCA"}
|